Merge branch '2.0' of github.com:payloadcms/payload into 2.0

This commit is contained in:
Dan Ribbens
2023-10-04 15:38:40 -04:00
52 changed files with 391 additions and 238 deletions

View File

@@ -18,14 +18,10 @@
"fix": "eslint \"packages/**/*.ts\" --fix", "fix": "eslint \"packages/**/*.ts\" --fix",
"lint": "eslint \"packages/**/*.ts\"", "lint": "eslint \"packages/**/*.ts\"",
"lint-staged": "lint-staged", "lint-staged": "lint-staged",
"release:beta": "release-it pre --preReleaseId=beta --npm.tag=beta --config .release-it.pre.json",
"release:canary": "release-it pre --preReleaseId=canary --npm.tag=canary --config .release-it.pre.json",
"release:major": "release-it major",
"release:minor": "release-it minor",
"release:patch": "release-it patch",
"pretest": "pnpm build", "pretest": "pnpm build",
"reinstall": "./scripts/reinstall.sh", "reinstall": "./scripts/reinstall.sh",
"release:list:beta": "./scripts/list_published_packages.sh beta", "release:list:beta": "./scripts/list_published_packages.sh beta",
"script:release:beta": "./scripts/release_beta.sh",
"test": "pnpm test:int && pnpm test:components && pnpm test:e2e", "test": "pnpm test:int && pnpm test:components && pnpm test:e2e",
"test:components": "cross-env jest --config=jest.components.config.js", "test:components": "cross-env jest --config=jest.components.config.js",
"test:e2e": "npx playwright install --with-deps && ts-node -T ./test/runE2E.ts", "test:e2e": "npx playwright install --with-deps && ts-node -T ./test/runE2E.ts",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@payloadcms/bundler-vite", "name": "@payloadcms/bundler-vite",
"version": "0.1.0-beta.4", "version": "0.1.0-beta.6",
"description": "The officially supported Vite bundler adapter for Payload", "description": "The officially supported Vite bundler adapter for Payload",
"repository": "https://github.com/payloadcms/payload", "repository": "https://github.com/payloadcms/payload",
"license": "MIT", "license": "MIT",
@@ -44,6 +44,7 @@
"types": "./dist/index.d.ts" "types": "./dist/index.d.ts"
}, },
"files": [ "files": [
"dist" "dist",
"mock.js"
] ]
} }

View File

@@ -9,7 +9,6 @@ import getPort from 'get-port'
import path from 'path' import path from 'path'
import virtual from 'vite-plugin-virtual' import virtual from 'vite-plugin-virtual'
const bundlerPath = path.resolve(__dirname, './')
const mockModulePath = path.resolve(__dirname, './mocks/emptyModule.js') const mockModulePath = path.resolve(__dirname, './mocks/emptyModule.js')
const mockDotENVPath = path.resolve(__dirname, './mocks/dotENV.js') const mockDotENVPath = path.resolve(__dirname, './mocks/dotENV.js')
@@ -26,11 +25,10 @@ export const getViteConfig = async (payloadConfig: SanitizedConfig): Promise<Inl
const hmrPort = await getPort() const hmrPort = await getPort()
const absoluteAliases = { const absoluteAliases = {}
[`${bundlerPath}`]: path.resolve(__dirname, './mock.js'),
}
const alias = [ const alias = [
{ find: '@payloadcms/bundler-vite', replacement: path.resolve(__dirname, '../mock.js') },
{ find: 'path', replacement: require.resolve('path-browserify') }, { find: 'path', replacement: require.resolve('path-browserify') },
{ find: 'payload-config', replacement: payloadConfig.paths.rawConfig }, { find: 'payload-config', replacement: payloadConfig.paths.rawConfig },
{ find: /payload$/, replacement: mockModulePath }, { find: /payload$/, replacement: mockModulePath },
@@ -89,7 +87,9 @@ export const getViteConfig = async (payloadConfig: SanitizedConfig): Promise<Inl
exclude: [ exclude: [
// Dependencies that need aliases should be excluded // Dependencies that need aliases should be excluded
// from pre-bundling // from pre-bundling
'@payloadcms/bundler-vite',
], ],
include: ['payload/components/root'],
}, },
plugins: [ plugins: [
{ {

View File

@@ -1,6 +1,6 @@
{ {
"name": "@payloadcms/db-mongodb", "name": "@payloadcms/db-mongodb",
"version": "1.0.0-beta.3", "version": "1.0.0-beta.4",
"description": "The officially supported MongoDB database adapter for Payload", "description": "The officially supported MongoDB database adapter for Payload",
"repository": "https://github.com/payloadcms/payload", "repository": "https://github.com/payloadcms/payload",
"license": "MIT", "license": "MIT",
@@ -40,6 +40,7 @@
"types": "./dist/index.d.ts" "types": "./dist/index.d.ts"
}, },
"files": [ "files": [
"dist" "dist",
"mock.js"
] ]
} }

View File

@@ -0,0 +1,19 @@
import type { SanitizedConfig } from 'payload/config'
export const vite = (config) => {
return {
...config,
optimizeDeps: {
...config.optimizeDeps,
exclude: [...config.optimizeDeps.exclude, '@payloadcms/db-mongodb'],
},
}
}
export const extendViteConfig = (config: SanitizedConfig) => {
const existingViteConfig = config.admin.vite ? config.admin.vite : (viteConfig) => viteConfig
config.admin.vite = (webpackConfig) => {
return vite(existingViteConfig(webpackConfig))
}
}

View File

@@ -0,0 +1,28 @@
import type { SanitizedConfig } from 'payload/config'
import path from 'path'
export const webpack = (config) => {
const aliasPath = path.resolve(__dirname, '../mock.js')
return {
...config,
resolve: {
...(config.resolve || {}),
alias: {
...(config.resolve?.alias || {}),
'@payloadcms/db-mongodb': aliasPath,
},
},
}
}
export const extendWebpackConfig = (config: SanitizedConfig) => {
const existingWebpackConfig = config.admin.webpack
? config.admin.webpack
: (webpackConfig) => webpackConfig
config.admin.webpack = (webpackConfig) => {
return webpack(existingWebpackConfig(webpackConfig))
}
}

View File

@@ -19,6 +19,8 @@ import { deleteMany } from './deleteMany'
import { deleteOne } from './deleteOne' import { deleteOne } from './deleteOne'
import { deleteVersions } from './deleteVersions' import { deleteVersions } from './deleteVersions'
import { destroy } from './destroy' import { destroy } from './destroy'
import { extendViteConfig } from './extendViteConfig'
import { extendWebpackConfig } from './extendWebpackConfig'
import { find } from './find' import { find } from './find'
import { findGlobal } from './findGlobal' import { findGlobal } from './findGlobal'
import { findGlobalVersions } from './findGlobalVersions' import { findGlobalVersions } from './findGlobalVersions'
@@ -33,7 +35,6 @@ import { updateGlobal } from './updateGlobal'
import { updateGlobalVersion } from './updateGlobalVersion' import { updateGlobalVersion } from './updateGlobalVersion'
import { updateOne } from './updateOne' import { updateOne } from './updateOne'
import { updateVersion } from './updateVersion' import { updateVersion } from './updateVersion'
import { webpack } from './webpack'
export interface Args { export interface Args {
/** Set to false to disable auto-pluralization of collection names, Defaults to true */ /** Set to false to disable auto-pluralization of collection names, Defaults to true */
@@ -88,6 +89,9 @@ export function mongooseAdapter({
function adapter({ payload }: { payload: Payload }) { function adapter({ payload }: { payload: Payload }) {
mongoose.set('strictQuery', false) mongoose.set('strictQuery', false)
extendWebpackConfig(payload.config)
extendViteConfig(payload.config)
return createDatabaseAdapter<MongooseAdapter>({ return createDatabaseAdapter<MongooseAdapter>({
autoPluralization, autoPluralization,
beginTransaction, beginTransaction,
@@ -126,7 +130,6 @@ export function mongooseAdapter({
updateVersion, updateVersion,
url, url,
versions: {}, versions: {},
webpack,
}) })
} }

View File

@@ -1,19 +0,0 @@
import type { Webpack } from 'payload/database'
import path from 'path'
export const webpack: Webpack = (config) => {
const aliasPath = path.resolve(__dirname, 'mock.js')
return {
...config,
resolve: {
...(config.resolve || {}),
alias: {
...(config.resolve?.alias || {}),
'@payloadcms/db-mongodb': aliasPath,
[path.resolve(__dirname, './index')]: aliasPath,
},
},
}
}

View File

@@ -1,6 +1,6 @@
{ {
"name": "@payloadcms/db-postgres", "name": "@payloadcms/db-postgres",
"version": "0.1.0-beta.8", "version": "0.1.0-beta.10",
"description": "The officially supported Postgres database adapter for Payload", "description": "The officially supported Postgres database adapter for Payload",
"repository": "https://github.com/payloadcms/payload", "repository": "https://github.com/payloadcms/payload",
"license": "MIT", "license": "MIT",
@@ -43,6 +43,7 @@
"types": "./dist/index.d.ts" "types": "./dist/index.d.ts"
}, },
"files": [ "files": [
"dist" "dist",
"mock.js"
] ]
} }

View File

@@ -0,0 +1,19 @@
import type { SanitizedConfig } from 'payload/config'
export const vite = (config) => {
return {
...config,
optimizeDeps: {
...config.optimizeDeps,
exclude: [...config.optimizeDeps.exclude, '@payloadcms/db-postgres'],
},
}
}
export const extendViteConfig = (config: SanitizedConfig) => {
const existingViteConfig = config.admin.vite ? config.admin.vite : (viteConfig) => viteConfig
config.admin.vite = (webpackConfig) => {
return vite(existingViteConfig(webpackConfig))
}
}

View File

@@ -0,0 +1,28 @@
import type { SanitizedConfig } from 'payload/config'
import path from 'path'
export const webpack = (config) => {
const aliasPath = path.resolve(__dirname, '../mock.js')
return {
...config,
resolve: {
...(config.resolve || {}),
alias: {
...(config.resolve?.alias || {}),
'@payloadcms/db-postgres': aliasPath,
},
},
}
}
export const extendWebpackConfig = (config: SanitizedConfig) => {
const existingWebpackConfig = config.admin.webpack
? config.admin.webpack
: (webpackConfig) => webpackConfig
config.admin.webpack = (webpackConfig) => {
return webpack(existingWebpackConfig(webpackConfig))
}
}

View File

@@ -17,6 +17,8 @@ import { deleteMany } from './deleteMany'
import { deleteOne } from './deleteOne' import { deleteOne } from './deleteOne'
import { deleteVersions } from './deleteVersions' import { deleteVersions } from './deleteVersions'
import { destroy } from './destroy' import { destroy } from './destroy'
import { extendViteConfig } from './extendViteConfig'
import { extendWebpackConfig } from './extendWebpackConfig'
import { find } from './find' import { find } from './find'
import { findGlobal } from './findGlobal' import { findGlobal } from './findGlobal'
import { findGlobalVersions } from './findGlobalVersions' import { findGlobalVersions } from './findGlobalVersions'
@@ -33,11 +35,14 @@ import { updateOne } from './update'
import { updateGlobal } from './updateGlobal' import { updateGlobal } from './updateGlobal'
import { updateGlobalVersion } from './updateGlobalVersion' import { updateGlobalVersion } from './updateGlobalVersion'
import { updateVersion } from './updateVersion' import { updateVersion } from './updateVersion'
import { webpack } from './webpack'
export function postgresAdapter(args: Args): PostgresAdapterResult { export function postgresAdapter(args: Args): PostgresAdapterResult {
function adapter({ payload }: { payload: Payload }) { function adapter({ payload }: { payload: Payload }) {
const migrationDir = args.migrationDir || path.resolve(process.cwd(), 'src/migrations') const migrationDir = args.migrationDir || path.resolve(process.cwd(), 'src/migrations')
extendWebpackConfig(payload.config)
extendViteConfig(payload.config)
return createDatabaseAdapter<PostgresAdapter>({ return createDatabaseAdapter<PostgresAdapter>({
...args, ...args,
name: 'postgres', name: 'postgres',
@@ -81,7 +86,6 @@ export function postgresAdapter(args: Args): PostgresAdapterResult {
updateGlobalVersion, updateGlobalVersion,
updateOne, updateOne,
updateVersion, updateVersion,
webpack,
}) })
} }

View File

@@ -14,11 +14,7 @@ export const init: Init = async function init(this: PostgresAdapter) {
if (this.payload.config.localization) { if (this.payload.config.localization) {
this.enums.enum__locales = pgEnum( this.enums.enum__locales = pgEnum(
'_locales', '_locales',
// TODO: types out of sync with core, monorepo please this.payload.config.localization.locales.map(({ code }) => code) as [string, ...string[]],
// this.payload.config.localization.localeCodes,
(this.payload.config.localization.locales as unknown as { code: string }[]).map(
({ code }) => code,
) as [string, ...string[]],
) )
} }
@@ -28,6 +24,7 @@ export const init: Init = async function init(this: PostgresAdapter) {
buildTable({ buildTable({
adapter: this, adapter: this,
buildRelationships: true, buildRelationships: true,
collectionIndexes: collection.indexes,
disableUnique: false, disableUnique: false,
fields: collection.fields, fields: collection.fields,
tableName, tableName,

View File

@@ -1,7 +1,7 @@
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
import type { Relation } from 'drizzle-orm' import type { Relation } from 'drizzle-orm'
import type { IndexBuilder, PgColumnBuilder, UniqueConstraintBuilder } from 'drizzle-orm/pg-core' import type { IndexBuilder, PgColumnBuilder, UniqueConstraintBuilder } from 'drizzle-orm/pg-core'
import type { Field } from 'payload/types' import type { Field, SanitizedCollectionConfig } from 'payload/types'
import { relations } from 'drizzle-orm' import { relations } from 'drizzle-orm'
import { import {
@@ -27,6 +27,7 @@ type Args = {
baseColumns?: Record<string, PgColumnBuilder> baseColumns?: Record<string, PgColumnBuilder>
baseExtraConfig?: Record<string, (cols: GenericColumns) => IndexBuilder | UniqueConstraintBuilder> baseExtraConfig?: Record<string, (cols: GenericColumns) => IndexBuilder | UniqueConstraintBuilder>
buildRelationships?: boolean buildRelationships?: boolean
collectionIndexes?: SanitizedCollectionConfig['indexes']
disableUnique: boolean disableUnique: boolean
fields: Field[] fields: Field[]
rootRelationsToBuild?: Map<string, string> rootRelationsToBuild?: Map<string, string>
@@ -45,6 +46,7 @@ export const buildTable = ({
baseColumns = {}, baseColumns = {},
baseExtraConfig = {}, baseExtraConfig = {},
buildRelationships, buildRelationships,
collectionIndexes = [],
disableUnique = false, disableUnique = false,
fields, fields,
rootRelationsToBuild, rootRelationsToBuild,
@@ -96,6 +98,7 @@ export const buildTable = ({
} = traverseFields({ } = traverseFields({
adapter, adapter,
buildRelationships, buildRelationships,
collectionIndexes,
columns, columns,
disableUnique, disableUnique,
fields, fields,

View File

@@ -5,13 +5,22 @@ import type { GenericColumn } from '../types'
type CreateIndexArgs = { type CreateIndexArgs = {
columnName: string columnName: string
name: string name: string | string[]
unique?: boolean unique?: boolean
} }
export const createIndex = ({ name, columnName, unique }: CreateIndexArgs) => { export const createIndex = ({ name, columnName, unique }: CreateIndexArgs) => {
return (table: { [x: string]: GenericColumn }) => { return (table: { [x: string]: GenericColumn }) => {
if (unique) return uniqueIndex(`${columnName}_idx`).on(table[name]) let columns
return index(`${columnName}_idx`).on(table[name]) if (Array.isArray(name)) {
columns = name
.map((columnName) => table[columnName])
// exclude fields were included in compound indexes but do not exist on the table
.filter((col) => typeof col !== 'undefined')
} else {
columns = [table[name]]
}
if (unique) return uniqueIndex(`${columnName}_idx`).on(columns[0], ...columns.slice(1))
return index(`${columnName}_idx`).on(columns[0], ...columns.slice(1))
} }
} }

View File

@@ -1,7 +1,7 @@
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
import type { Relation } from 'drizzle-orm' import type { Relation } from 'drizzle-orm'
import type { IndexBuilder, PgColumnBuilder, UniqueConstraintBuilder } from 'drizzle-orm/pg-core' import type { IndexBuilder, PgColumnBuilder, UniqueConstraintBuilder } from 'drizzle-orm/pg-core'
import type { Field, TabAsField } from 'payload/types' import type { Field, SanitizedCollectionConfig, TabAsField } from 'payload/types'
import { relations } from 'drizzle-orm' import { relations } from 'drizzle-orm'
import { import {
@@ -33,6 +33,7 @@ import { validateExistingBlockIsIdentical } from './validateExistingBlockIsIdent
type Args = { type Args = {
adapter: PostgresAdapter adapter: PostgresAdapter
buildRelationships: boolean buildRelationships: boolean
collectionIndexes: SanitizedCollectionConfig['indexes']
columnPrefix?: string columnPrefix?: string
columns: Record<string, PgColumnBuilder> columns: Record<string, PgColumnBuilder>
disableUnique?: boolean disableUnique?: boolean
@@ -61,6 +62,7 @@ type Result = {
export const traverseFields = ({ export const traverseFields = ({
adapter, adapter,
buildRelationships, buildRelationships,
collectionIndexes,
columnPrefix, columnPrefix,
columns, columns,
disableUnique = false, disableUnique = false,
@@ -109,6 +111,24 @@ export const traverseFields = ({
targetIndexes = localesIndexes targetIndexes = localesIndexes
} }
const collectionIndex = collectionIndexes
? collectionIndexes.findIndex((index) => {
return Object.keys(index.fields).some((indexField) => indexField === fieldName)
})
: -1
if (collectionIndex > -1) {
const name = toSnakeCase(
`${Object.keys(collectionIndexes[collectionIndex].fields).join('_')}`,
)
targetIndexes[`${name}Idx`] = createIndex({
name: Object.keys(collectionIndexes[collectionIndex].fields),
columnName: name,
unique: collectionIndexes[collectionIndex].options.unique,
})
collectionIndexes.splice(collectionIndex)
}
if ( if (
(field.unique || field.index) && (field.unique || field.index) &&
!['array', 'blocks', 'group', 'point', 'relationship', 'upload'].includes(field.type) && !['array', 'blocks', 'group', 'point', 'relationship', 'upload'].includes(field.type) &&
@@ -415,6 +435,7 @@ export const traverseFields = ({
} = traverseFields({ } = traverseFields({
adapter, adapter,
buildRelationships, buildRelationships,
collectionIndexes,
columnPrefix, columnPrefix,
columns, columns,
disableUnique, disableUnique,
@@ -448,6 +469,7 @@ export const traverseFields = ({
} = traverseFields({ } = traverseFields({
adapter, adapter,
buildRelationships, buildRelationships,
collectionIndexes,
columnPrefix: `${columnName}_`, columnPrefix: `${columnName}_`,
columns, columns,
disableUnique, disableUnique,
@@ -482,6 +504,7 @@ export const traverseFields = ({
} = traverseFields({ } = traverseFields({
adapter, adapter,
buildRelationships, buildRelationships,
collectionIndexes,
columnPrefix, columnPrefix,
columns, columns,
disableUnique, disableUnique,
@@ -518,6 +541,7 @@ export const traverseFields = ({
} = traverseFields({ } = traverseFields({
adapter, adapter,
buildRelationships, buildRelationships,
collectionIndexes,
columnPrefix, columnPrefix,
columns, columns,
disableUnique, disableUnique,

View File

@@ -69,7 +69,7 @@ export type MigrateDownArgs = { payload: Payload }
declare module 'payload' { declare module 'payload' {
export interface DatabaseAdapter extends Args { export interface DatabaseAdapter extends Args {
db: DrizzleDB drizzle: DrizzleDB
enums: Record<string, GenericEnum> enums: Record<string, GenericEnum>
pool: Pool pool: Pool
relations: Record<string, GenericRelation> relations: Record<string, GenericRelation>

View File

@@ -1,19 +0,0 @@
import type { Webpack } from 'payload/database'
import path from 'path'
export const webpack: Webpack = (config) => {
const aliasPath = path.resolve(__dirname, 'mock.js')
return {
...config,
resolve: {
...(config.resolve || {}),
alias: {
...(config.resolve?.alias || {}),
'@payloadcms/db-postgres': aliasPath,
[path.resolve(__dirname, './index')]: aliasPath,
},
},
}
}

View File

@@ -1,4 +1,4 @@
export { BaseDatabaseAdapter, BeginTransaction, CommitTransaction, Connect, Create, CreateArgs, CreateGlobal, CreateGlobalArgs, CreateGlobalVersion, CreateGlobalVersionArgs, CreateMigration, CreateVersion, CreateVersionArgs, DeleteMany, DeleteManyArgs, DeleteOne, DeleteOneArgs, DeleteVersions, DeleteVersionsArgs, Destroy, Find, FindArgs, FindGlobal, FindGlobalArgs, FindGlobalVersions, FindGlobalVersionsArgs, FindOne, FindOneArgs, FindVersions, FindVersionsArgs, Init, Migration, MigrationData, PaginatedDocs, QueryDrafts, QueryDraftsArgs, RollbackTransaction, Transaction, TypeWithVersion, UpdateGlobal, UpdateGlobalArgs, UpdateGlobalVersion, UpdateGlobalVersionArgs, UpdateOne, UpdateOneArgs, UpdateVersion, UpdateVersionArgs, Webpack, } from './dist/database/types'; export { BaseDatabaseAdapter, BeginTransaction, CommitTransaction, Connect, Create, CreateArgs, CreateGlobal, CreateGlobalArgs, CreateGlobalVersion, CreateGlobalVersionArgs, CreateMigration, CreateVersion, CreateVersionArgs, DeleteMany, DeleteManyArgs, DeleteOne, DeleteOneArgs, DeleteVersions, DeleteVersionsArgs, Destroy, Find, FindArgs, FindGlobal, FindGlobalArgs, FindGlobalVersions, FindGlobalVersionsArgs, FindOne, FindOneArgs, FindVersions, FindVersionsArgs, Init, Migration, MigrationData, PaginatedDocs, QueryDrafts, QueryDraftsArgs, RollbackTransaction, Transaction, TypeWithVersion, UpdateGlobal, UpdateGlobalArgs, UpdateGlobalVersion, UpdateGlobalVersionArgs, UpdateOne, UpdateOneArgs, UpdateVersion, UpdateVersionArgs, } from './dist/database/types';
export * from './dist/database/queryValidation/types'; export * from './dist/database/queryValidation/types';
export { combineQueries } from './dist/database/combineQueries'; export { combineQueries } from './dist/database/combineQueries';
export { createDatabaseAdapter } from './dist/database/createDatabaseAdapter'; export { createDatabaseAdapter } from './dist/database/createDatabaseAdapter';

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
{ {
"name": "payload", "name": "payload",
"version": "2.0.0-beta.7", "version": "2.0.0-beta.11",
"description": "Node, React and MongoDB Headless CMS and Application Framework", "description": "Node, React and MongoDB Headless CMS and Application Framework",
"license": "MIT", "license": "MIT",
"main": "./src/index.ts", "main": "./src/index.ts",

View File

@@ -105,6 +105,9 @@ export const Routes: React.FC = () => {
if (initialized === true && !isLoadingUser) { if (initialized === true && !isLoadingUser) {
return ( return (
<Switch> <Switch>
<Route path={`${match.url}/create-first-user`}>
<Redirect to={`${match.url}/`} />
</Route>
{customRoutes({ {customRoutes({
canAccessAdmin, canAccessAdmin,
customRoutes: customRoutesConfig, customRoutes: customRoutesConfig,

View File

@@ -9,7 +9,7 @@ import baseAPIKeyFields from '../../auth/baseFields/apiKey'
import baseAuthFields from '../../auth/baseFields/auth' import baseAuthFields from '../../auth/baseFields/auth'
import baseVerificationFields from '../../auth/baseFields/verification' import baseVerificationFields from '../../auth/baseFields/verification'
import TimestampsRequired from '../../errors/TimestampsRequired' import TimestampsRequired from '../../errors/TimestampsRequired'
import sanitizeFields from '../../fields/config/sanitize' import { sanitizeFields } from '../../fields/config/sanitize'
import { fieldAffectsData } from '../../fields/config/types' import { fieldAffectsData } from '../../fields/config/types'
import mergeBaseFields from '../../fields/mergeBaseFields' import mergeBaseFields from '../../fields/mergeBaseFields'
import { extractTranslations } from '../../translations/extractTranslations' import { extractTranslations } from '../../translations/extractTranslations'
@@ -142,7 +142,7 @@ const sanitizeCollection = (
// Sanitize fields // Sanitize fields
// ///////////////////////////////// // /////////////////////////////////
const validRelationships = config.collections.map((c) => c.slug) const validRelationships = config.collections.map((c) => c.slug) || []
sanitized.fields = sanitizeFields({ sanitized.fields = sanitizeFields({
config, config,
fields: sanitized.fields, fields: sanitized.fields,

View File

@@ -1,6 +1,5 @@
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
import type { MarkOptional } from 'ts-essentials' import type { MarkOptional } from 'ts-essentials'
import type { Configuration } from 'webpack'
import type { import type {
BaseDatabaseAdapter, BaseDatabaseAdapter,
@@ -35,16 +34,6 @@ export function createDatabaseAdapter<T extends BaseDatabaseAdapter>(
| 'transaction' | 'transaction'
>, >,
): T { ): T {
// Need to implement DB Webpack config extensions here
if (args.webpack) {
const existingWebpackConfig = args.payload.config.admin.webpack
? args.payload.config.admin.webpack
: (webpackConfig) => webpackConfig
args.payload.config.admin.webpack = (webpackConfig: Configuration) => {
return args.webpack(existingWebpackConfig(webpackConfig))
}
}
return { return {
// Default 'null' transaction functions // Default 'null' transaction functions
beginTransaction, beginTransaction,

View File

@@ -1,5 +1,3 @@
import type { Configuration } from 'webpack'
import type { TypeWithID } from '../collections/config/types' import type { TypeWithID } from '../collections/config/types'
import type { TypeWithID as GlobalsTypeWithID } from '../globals/config/types' import type { TypeWithID as GlobalsTypeWithID } from '../globals/config/types'
import type { Payload } from '../payload' import type { Payload } from '../payload'
@@ -140,10 +138,6 @@ export interface BaseDatabaseAdapter {
* assign the transaction to use when making queries, defaults to the last started transaction * assign the transaction to use when making queries, defaults to the last started transaction
*/ */
useTransaction?: (id: number | string) => void useTransaction?: (id: number | string) => void
/**
* Used to alias server only modules or make other changes to webpack configuration
*/
webpack?: Webpack
} }
export type Init = (payload: Payload) => Promise<void> export type Init = (payload: Payload) => Promise<void>
@@ -152,8 +146,6 @@ export type Connect = (payload: Payload) => Promise<void>
export type Destroy = (payload: Payload) => Promise<void> export type Destroy = (payload: Payload) => Promise<void>
export type Webpack = (config: Configuration) => Configuration
export type CreateMigration = (payload: Payload, migrationName?: string) => Promise<void> export type CreateMigration = (payload: Payload, migrationName?: string) => Promise<void>
export type Transaction = ( export type Transaction = (

View File

@@ -6,3 +6,4 @@ export { defaults } from '../config/defaults'
export { sanitizeConfig } from '../config/sanitize' export { sanitizeConfig } from '../config/sanitize'
export { baseBlockFields } from '../fields/baseFields/baseBlockFields' export { baseBlockFields } from '../fields/baseFields/baseBlockFields'
export { baseIDField } from '../fields/baseFields/baseIDField' export { baseIDField } from '../fields/baseFields/baseIDField'
export { sanitizeFields } from '../fields/config/sanitize'

View File

@@ -46,7 +46,6 @@ export {
UpdateOneArgs, UpdateOneArgs,
UpdateVersion, UpdateVersion,
UpdateVersionArgs, UpdateVersionArgs,
Webpack,
} from '../database/types' } from '../database/types'
export * from '../database/queryValidation/types' export * from '../database/queryValidation/types'

View File

@@ -9,7 +9,7 @@ import type {
} from './types' } from './types'
import { Config } from '../../config/types' import { Config } from '../../config/types'
import { InvalidFieldName, InvalidFieldRelationship, MissingFieldType } from '../../errors' import { InvalidFieldName, InvalidFieldRelationship, MissingFieldType } from '../../errors'
import sanitizeFields from './sanitize' import { sanitizeFields } from './sanitize'
import { DatabaseAdapter } from '../..' import { DatabaseAdapter } from '../..'
const dummyConfig: Config = { const dummyConfig: Config = {

View File

@@ -12,10 +12,14 @@ import { fieldAffectsData, tabHasName } from './types'
type Args = { type Args = {
config: Config config: Config
fields: Field[] fields: Field[]
validRelationships: string[] /**
* If not null, will validate that upload and relationship fields do not relate to a collection that is not in this array.
* This validation will be skipped if validRelationships is null.
*/
validRelationships: null | string[]
} }
const sanitizeFields = ({ config, fields, validRelationships }: Args): Field[] => { export const sanitizeFields = ({ config, fields, validRelationships }: Args): Field[] => {
if (!fields) return [] if (!fields) return []
return fields.map((unsanitizedField) => { return fields.map((unsanitizedField) => {
@@ -48,12 +52,16 @@ const sanitizeFields = ({ config, fields, validRelationships }: Args): Field[] =
} }
if (field.type === 'relationship' || field.type === 'upload') { if (field.type === 'relationship' || field.type === 'upload') {
const relationships = Array.isArray(field.relationTo) ? field.relationTo : [field.relationTo] if (validRelationships) {
relationships.forEach((relationship: string) => { const relationships = Array.isArray(field.relationTo)
if (!validRelationships.includes(relationship)) { ? field.relationTo
throw new InvalidFieldRelationship(field, relationship) : [field.relationTo]
} relationships.forEach((relationship: string) => {
}) if (!validRelationships.includes(relationship)) {
throw new InvalidFieldRelationship(field, relationship)
}
})
}
if (field.type === 'relationship') { if (field.type === 'relationship') {
if (field.min && !field.minRows) { if (field.min && !field.minRows) {
@@ -155,5 +163,3 @@ const sanitizeFields = ({ config, fields, validRelationships }: Args): Field[] =
return field return field
}) })
} }
export default sanitizeFields

View File

@@ -2,7 +2,7 @@ import type { Config } from '../../config/types'
import type { SanitizedGlobalConfig } from './types' import type { SanitizedGlobalConfig } from './types'
import defaultAccess from '../../auth/defaultAccess' import defaultAccess from '../../auth/defaultAccess'
import sanitizeFields from '../../fields/config/sanitize' import { sanitizeFields } from '../../fields/config/sanitize'
import { fieldAffectsData } from '../../fields/config/types' import { fieldAffectsData } from '../../fields/config/types'
import mergeBaseFields from '../../fields/mergeBaseFields' import mergeBaseFields from '../../fields/mergeBaseFields'
import translations from '../../translations' import translations from '../../translations'
@@ -92,7 +92,7 @@ const sanitizeGlobals = (config: Config): SanitizedGlobalConfig[] => {
}) })
} }
const validRelationships = collections.map((c) => c.slug) const validRelationships = collections.map((c) => c.slug) || []
sanitizedGlobal.fields = sanitizeFields({ sanitizedGlobal.fields = sanitizeFields({
config, config,
fields: sanitizedGlobal.fields, fields: sanitizedGlobal.fields,

View File

@@ -68,8 +68,6 @@ export const BlockContent: React.FC<Props> = (props) => {
}) })
}, [editor, nodeKey, collapsed]) }, [editor, nodeKey, collapsed])
console.log('BLOCK', block)
return ( return (
<React.Fragment> <React.Fragment>
<Collapsible <Collapsible

View File

@@ -8,6 +8,8 @@
--font-body --font-body
); // Reset font to non-serif, body font, as the lexical editor parent uses a serif font. ); // Reset font to non-serif, body font, as the lexical editor parent uses a serif font.
margin: 0 0 1.5em;
&__header { &__header {
h3 { h3 {
margin: 0; margin: 0;

View File

@@ -7,6 +7,9 @@ const baseClass = 'lexical-block'
import type { Data } from 'payload/types' import type { Data } from 'payload/types'
import { useConfig } from 'payload/components/utilities'
import { sanitizeFields } from 'payload/config'
import type { BlocksFeatureProps } from '..' import type { BlocksFeatureProps } from '..'
import { useEditorConfigContext } from '../../../lexical/config/EditorConfigProvider' import { useEditorConfigContext } from '../../../lexical/config/EditorConfigProvider'
@@ -23,6 +26,7 @@ type Props = {
export const BlockComponent: React.FC<Props> = (props) => { export const BlockComponent: React.FC<Props> = (props) => {
const { children, className, fields, format, nodeKey } = props const { children, className, fields, format, nodeKey } = props
const payloadConfig = useConfig()
const { editorConfig, field } = useEditorConfigContext() const { editorConfig, field } = useEditorConfigContext()
@@ -30,6 +34,14 @@ export const BlockComponent: React.FC<Props> = (props) => {
editorConfig?.resolvedFeatureMap?.get('blocks')?.props as BlocksFeatureProps editorConfig?.resolvedFeatureMap?.get('blocks')?.props as BlocksFeatureProps
)?.blocks?.find((block) => block.slug === fields?.data?.blockType) )?.blocks?.find((block) => block.slug === fields?.data?.blockType)
// Sanitize block's fields here. This is done here and not in the feature, because the payload config is available here
const validRelationships = payloadConfig.collections.map((c) => c.slug) || []
block.fields = sanitizeFields({
config: payloadConfig,
fields: block.fields,
validRelationships,
})
const initialDataRef = React.useRef<Data>(buildInitialState(fields.data || {})) // Store initial value in a ref, so it doesn't change on re-render and only gets initialized once const initialDataRef = React.useRef<Data>(buildInitialState(fields.data || {})) // Store initial value in a ref, so it doesn't change on re-render and only gets initialized once
// Memoized Form JSX // Memoized Form JSX

View File

@@ -30,12 +30,8 @@ export const BlocksFeature = (props?: BlocksFeatureProps): FeatureProvider => {
? formatLabels(unsanitizedBlock.slug) ? formatLabels(unsanitizedBlock.slug)
: unsanitizedBlock.labels : unsanitizedBlock.labels
// TODO // unsanitizedBlock.fields are sanitized in the React component and not here.
/*unsanitizedBlock.fields = sanitizeFields({ // That's because we do not have access to the payload config here.
config,
fields: block.fields,
validRelationships,
})*/
return unsanitizedBlock return unsanitizedBlock
}) })

View File

@@ -2,7 +2,7 @@
html[data-theme='light'] { html[data-theme='light'] {
.link-editor { .link-editor {
box-shadow: 0px 6px 20px 0px rgba(0, 0, 0, 0.2); @include shadow-m;
} }
} }
@@ -35,7 +35,7 @@ html[data-theme='light'] {
border: 0; border: 0;
outline: 0; outline: 0;
position: relative; position: relative;
font-family: inherit; font-family: var(--font-body);
a { a {
text-decoration: none; text-decoration: none;

View File

@@ -101,6 +101,7 @@ const Component: React.FC<Props> = (props) => {
<div className={`${baseClass}__actions`}> <div className={`${baseClass}__actions`}>
<Button <Button
buttonStyle="icon-label" buttonStyle="icon-label"
className={`${baseClass}__swapButton`}
disabled={field?.admin?.readOnly} disabled={field?.admin?.readOnly}
el="div" el="div"
icon="swap" icon="swap"

View File

@@ -10,6 +10,7 @@
border: 1px solid var(--theme-elevation-100); border: 1px solid var(--theme-elevation-100);
max-width: base(15); max-width: base(15);
font-family: var(--font-body); font-family: var(--font-body);
margin: 0 0 1.5em;
&:hover { &:hover {
border: 1px solid var(--theme-elevation-150); border: 1px solid var(--theme-elevation-150);
@@ -56,7 +57,14 @@
& > * { & > * {
margin: 0; margin: 0;
} }
}
&__swapButton {
margin: 0;
}
&__doc-drawer-toggler,
&__swapButton {
&:disabled { &:disabled {
color: var(--theme-elevation-300); color: var(--theme-elevation-300);
pointer-events: none; pointer-events: none;

View File

@@ -10,6 +10,7 @@
border: 1px solid var(--theme-elevation-100); border: 1px solid var(--theme-elevation-100);
position: relative; position: relative;
font-family: var(--font-body); font-family: var(--font-body);
margin: 0 0 1.5em;
.btn { .btn {
margin: 0; margin: 0;

View File

@@ -1,4 +0,0 @@
@import 'payload/scss';
.editor-upload {
}

View File

@@ -12,8 +12,6 @@ import {
} from 'lexical' } from 'lexical'
import * as React from 'react' import * as React from 'react'
import { UploadFeatureProps } from '..'
// @ts-expect-error TypeScript being dumb // @ts-expect-error TypeScript being dumb
const RawUploadComponent = React.lazy(async () => await import('../component')) const RawUploadComponent = React.lazy(async () => await import('../component'))

View File

@@ -94,7 +94,7 @@ html[data-theme='light'] {
.floating-select-toolbar-popup__dropdown { .floating-select-toolbar-popup__dropdown {
&--items { &--items {
position: absolute; position: absolute;
box-shadow: 0px 6px 20px 0px rgba(0, 0, 0, 0.2); @include shadow-m;
} }
} }
} }

View File

@@ -2,7 +2,7 @@
html[data-theme='light'] { html[data-theme='light'] {
.floating-select-toolbar-popup { .floating-select-toolbar-popup {
box-shadow: 0px 6px 20px 0px rgba(0, 0, 0, 0.2); @include shadow-m;
} }
} }

View File

@@ -527,11 +527,17 @@ export function useMenuAnchorRef(
if (left + menuWidth > rootElementRect.right) { if (left + menuWidth > rootElementRect.right) {
containerDiv.style.left = `${rootElementRect.right - menuWidth + window.scrollX}px` containerDiv.style.left = `${rootElementRect.right - menuWidth + window.scrollX}px`
} }
const margin = 10
const wouldGoOffTopOfScreen = top < menuHeight
const wouldGoOffBottomOfContainer = top + menuHeight > rootElementRect.bottom
// Position slash menu above the cursor instead of below (default) if it would otherwise go off the bottom of the screen.
if ( if (
(top + menuHeight > window.innerHeight || top + menuHeight > rootElementRect.bottom) && (top + menuHeight > window.innerHeight ||
(wouldGoOffBottomOfContainer && !wouldGoOffTopOfScreen)) &&
top - rootElementRect.top > menuHeight top - rootElementRect.top > menuHeight
) { ) {
const margin = 24
containerDiv.style.top = `${ containerDiv.style.top = `${
top + VERTICAL_OFFSET - menuHeight + window.scrollY - (height + margin) top + VERTICAL_OFFSET - menuHeight + window.scrollY - (height + margin)
}px` }px`

View File

@@ -1,4 +1,10 @@
import type { LexicalCommand, LexicalEditor, RangeSelection, TextNode } from 'lexical' import type {
LexicalCommand,
LexicalEditor,
ParagraphNode,
RangeSelection,
TextNode,
} from 'lexical'
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext' import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext'
import { mergeRegister } from '@lexical/utils' import { mergeRegister } from '@lexical/utils'
@@ -53,7 +59,8 @@ function tryToPositionRange(leadOffset: number, range: Range, editorWindow: Wind
try { try {
range.setStart(anchorNode, startOffset) range.setStart(anchorNode, startOffset)
range.setEnd(anchorNode, endOffset) // if endOffset is 0, positioning the range for when you click the plus button to open the slash menu will fial
range.setEnd(anchorNode, endOffset > 1 ? endOffset : 1)
} catch (error) { } catch (error) {
return false return false
} }
@@ -176,7 +183,7 @@ export type TypeaheadMenuPluginProps = {
} }
export const ENABLE_SLASH_MENU_COMMAND: LexicalCommand<{ export const ENABLE_SLASH_MENU_COMMAND: LexicalCommand<{
rect: DOMRect node: ParagraphNode
}> = createCommand('ENABLE_SLASH_MENU_COMMAND') }> = createCommand('ENABLE_SLASH_MENU_COMMAND')
export function LexicalTypeaheadMenuPlugin({ export function LexicalTypeaheadMenuPlugin({
@@ -215,7 +222,7 @@ export function LexicalTypeaheadMenuPlugin({
return mergeRegister( return mergeRegister(
editor.registerCommand( editor.registerCommand(
ENABLE_SLASH_MENU_COMMAND, ENABLE_SLASH_MENU_COMMAND,
({ rect }) => { ({ node }) => {
editor.getEditorState().read(() => { editor.getEditorState().read(() => {
const match: MenuTextMatch = { const match: MenuTextMatch = {
leadOffset: 0, leadOffset: 0,
@@ -223,15 +230,22 @@ export function LexicalTypeaheadMenuPlugin({
replaceableString: '', replaceableString: '',
} }
if (match !== null && !isSelectionOnEntityBoundary(editor, match.leadOffset)) { if (match !== null && !isSelectionOnEntityBoundary(editor, match.leadOffset)) {
if (rect !== null) { if (node !== null) {
startTransition(() => const editorWindow = editor._window ?? window
openTypeahead({ const range = editorWindow.document.createRange()
getRect: () => {
return rect const isRangePositioned = tryToPositionRange(match.leadOffset, range, editorWindow)
}, if (isRangePositioned !== null) {
match, startTransition(() =>
}), openTypeahead({
) getRect: () => {
return range.getBoundingClientRect()
},
match,
}),
)
}
return return
} }
} }
@@ -270,7 +284,9 @@ export function LexicalTypeaheadMenuPlugin({
if (isRangePositioned !== null) { if (isRangePositioned !== null) {
startTransition(() => startTransition(() =>
openTypeahead({ openTypeahead({
getRect: () => range.getBoundingClientRect(), getRect: () => {
return range.getBoundingClientRect()
},
match, match,
}), }),
) )

View File

@@ -2,7 +2,7 @@
html[data-theme='light'] { html[data-theme='light'] {
.slash-menu { .slash-menu {
box-shadow: 0px 6px 20px 0px rgba(0, 0, 0, 0.2); @include shadow-m;
} }
} }
@@ -12,6 +12,9 @@ html[data-theme='light'] {
color: var(--color-base-800); color: var(--color-base-800);
border-radius: 4px; border-radius: 4px;
list-style: none; list-style: none;
font-family: var(--font-body);
max-height: 300px;
overflow-y: scroll;
.group { .group {
padding-bottom: 8px; padding-bottom: 8px;
@@ -24,7 +27,10 @@ html[data-theme='light'] {
} }
.item { .item {
padding-left: 8px; all: unset;
padding-left: 12px;
font-size: 13px;
box-sizing: border-box;
background: none; background: none;
border: none; border: none;
color: var(--color-base-900); color: var(--color-base-900);

View File

@@ -239,9 +239,9 @@ function useAddBlockHandle(
if (!emptyBlockElem) { if (!emptyBlockElem) {
return return
} }
let node: ParagraphNode
editor.update(() => { editor.update(() => {
const node: ParagraphNode = $getNearestNodeFromDOMNode(emptyBlockElem) as ParagraphNode node = $getNearestNodeFromDOMNode(emptyBlockElem) as ParagraphNode
if (!node || node.getType() !== 'paragraph') { if (!node || node.getType() !== 'paragraph') {
return return
} }
@@ -257,7 +257,7 @@ function useAddBlockHandle(
// Otherwise, this won't work // Otherwise, this won't work
setTimeout(() => { setTimeout(() => {
editor.dispatchCommand(ENABLE_SLASH_MENU_COMMAND, { editor.dispatchCommand(ENABLE_SLASH_MENU_COMMAND, {
rect: emptyBlockElem.getBoundingClientRect(), node: node,
}) })
}, 0) }, 0)

26
scripts/release_beta.sh Executable file
View File

@@ -0,0 +1,26 @@
#!/usr/bin/env bash
set -ex
# Build packages/payload
package_name=$1
package_dir="packages/$package_name"
if [ -z "$package_name" ]; then
echo "Please specify a package to publish"
exit 1
fi
# Check if packages/$package_name exists
if [ ! -d "$package_dir" ]; then
echo "Package $package_name does not exist"
exit 1
fi
npm --prefix "$package_dir" version pre --preid beta
git add "$package_dir"/package.json
new_version=$(node -p "require('./$package_dir/package.json').version")
git commit -m "chore(release): $package_name@$new_version"
pnpm publish -C "$package_dir" --tag beta --no-git-checks

View File

@@ -76,11 +76,11 @@ export function buildConfigWithDefaults(testConfig?: Partial<Config>): Promise<S
...existingConfig.resolve?.alias, ...existingConfig.resolve?.alias,
[path.resolve(__dirname, '../packages/db-postgres/src/index')]: path.resolve( [path.resolve(__dirname, '../packages/db-postgres/src/index')]: path.resolve(
__dirname, __dirname,
'../packages/db-postgres/src/mock.js', '../packages/db-postgres/mock.js',
), ),
[path.resolve(__dirname, '../packages/db-mongodb/src/index')]: path.resolve( [path.resolve(__dirname, '../packages/db-mongodb/src/index')]: path.resolve(
__dirname, __dirname,
'../packages/db-mongodb/src/mock.js', '../packages/db-mongodb/mock.js',
), ),
[path.resolve(__dirname, '../packages/bundler-webpack/src/index')]: path.resolve( [path.resolve(__dirname, '../packages/bundler-webpack/src/index')]: path.resolve(
__dirname, __dirname,
@@ -88,7 +88,7 @@ export function buildConfigWithDefaults(testConfig?: Partial<Config>): Promise<S
), ),
[path.resolve(__dirname, '../packages/bundler-vite/src/index')]: path.resolve( [path.resolve(__dirname, '../packages/bundler-vite/src/index')]: path.resolve(
__dirname, __dirname,
'../packages/bundler-vite/src/mock.js', '../packages/bundler-vite/mock.js',
), ),
react: path.resolve(__dirname, '../packages/payload/node_modules/react'), react: path.resolve(__dirname, '../packages/payload/node_modules/react'),
}, },

View File

@@ -41,7 +41,7 @@ describe('Fields', () => {
;({ serverURL } = await initPayloadTest({ __dirname, init: { local: false } })) ;({ serverURL } = await initPayloadTest({ __dirname, init: { local: false } }))
config = await configPromise config = await configPromise
client = new RESTClient(config, { serverURL, defaultSlug: 'point-fields' }) client = new RESTClient(config, { defaultSlug: 'point-fields', serverURL })
const graphQLURL = `${serverURL}${config.routes.api}${config.routes.graphQL}` const graphQLURL = `${serverURL}${config.routes.api}${config.routes.graphQL}`
graphQLClient = new GraphQLClient(graphQLURL) graphQLClient = new GraphQLClient(graphQLURL)
token = await client.login() token = await client.login()
@@ -64,7 +64,7 @@ describe('Fields', () => {
}) })
it('should populate default values in beforeValidate hook', async () => { it('should populate default values in beforeValidate hook', async () => {
const { fieldWithDefaultValue, dependentOnFieldWithDefaultValue } = await payload.create({ const { dependentOnFieldWithDefaultValue, fieldWithDefaultValue } = await payload.create({
collection: 'text-fields', collection: 'text-fields',
data: { text }, data: { text },
}) })
@@ -89,10 +89,6 @@ describe('Fields', () => {
depth: 0, depth: 0,
where: { where: {
updatedAt: { updatedAt: {
// TODO:
// drizzle is not adjusting for timezones
// tenMinutesAgo: "2023-08-29T15:49:39.897Z" UTC
// doc.updatedAt: "2023-08-29T11:59:43.738Z" GMT -4
greater_than_equal: tenMinutesAgo, greater_than_equal: tenMinutesAgo,
}, },
}, },
@@ -121,15 +117,15 @@ describe('Fields', () => {
beforeAll(async () => { beforeAll(async () => {
const { id } = await payload.create({ const { id } = await payload.create({
collection: 'select-fields', collection: 'select-fields',
locale: 'en',
data: { data: {
selectHasManyLocalized: ['one', 'two'], selectHasManyLocalized: ['one', 'two'],
}, },
locale: 'en',
}) })
doc = await payload.findByID({ doc = await payload.findByID({
id,
collection: 'select-fields', collection: 'select-fields',
locale: 'all', locale: 'all',
id,
}) })
}) })
@@ -146,8 +142,8 @@ describe('Fields', () => {
}) })
const updatedDoc = await payload.update({ const updatedDoc = await payload.update({
collection: 'select-fields',
id, id,
collection: 'select-fields',
data: { data: {
select: 'one', select: 'one',
}, },
@@ -245,15 +241,15 @@ describe('Fields', () => {
const localizedHasMany = [5, 10] const localizedHasMany = [5, 10]
const { id } = await payload.create({ const { id } = await payload.create({
collection: 'number-fields', collection: 'number-fields',
locale: 'en',
data: { data: {
localizedHasMany, localizedHasMany,
}, },
locale: 'en',
}) })
const localizedDoc = await payload.findByID({ const localizedDoc = await payload.findByID({
id,
collection: 'number-fields', collection: 'number-fields',
locale: 'all', locale: 'all',
id,
}) })
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -284,81 +280,44 @@ describe('Fields', () => {
it('should have indexes', () => { it('should have indexes', () => {
expect(definitions.text).toEqual(1) expect(definitions.text).toEqual(1)
}) })
it('should have unique indexes', () => { it('should have unique indexes', () => {
expect(definitions.uniqueText).toEqual(1) expect(definitions.uniqueText).toEqual(1)
expect(options.uniqueText).toMatchObject({ unique: true }) expect(options.uniqueText).toMatchObject({ unique: true })
}) })
it('should have 2dsphere indexes on point fields', () => {
expect(definitions.point).toEqual('2dsphere')
})
it('should have 2dsphere indexes on point fields in groups', () => {
expect(definitions['group.point']).toEqual('2dsphere')
})
it('should have a sparse index on a unique localized field in a group', () => {
expect(definitions['group.localizedUnique.en']).toEqual(1)
expect(options['group.localizedUnique.en']).toMatchObject({ unique: true, sparse: true })
expect(definitions['group.localizedUnique.es']).toEqual(1)
expect(options['group.localizedUnique.es']).toMatchObject({ unique: true, sparse: true })
})
it('should have unique indexes in a collapsible', () => {
expect(definitions['collapsibleLocalizedUnique.en']).toEqual(1)
expect(options['collapsibleLocalizedUnique.en']).toMatchObject({
unique: true,
sparse: true,
})
expect(definitions.collapsibleTextUnique).toEqual(1)
expect(options.collapsibleTextUnique).toMatchObject({ unique: true })
})
it('should have unique compound indexes', () => { it('should have unique compound indexes', () => {
expect(definitions.partOne).toEqual(1) expect(definitions.partOne).toEqual(1)
expect(options.partOne).toMatchObject({ expect(options.partOne).toMatchObject({
unique: true,
name: 'compound-index', name: 'compound-index',
sparse: true, sparse: true,
unique: true,
}) })
}) })
it('should throw validation error saving on unique fields', async () => {
const data = { it('should have 2dsphere indexes on point fields', () => {
text: 'a', expect(definitions.point).toEqual('2dsphere')
uniqueText: 'a',
}
await payload.create({
collection: 'indexed-fields',
data,
})
expect(async () => {
const result = await payload.create({
collection: 'indexed-fields',
data,
})
return result.error
}).toBeDefined()
}) })
it('should throw validation error saving on unique combined fields', async () => {
await payload.delete({ collection: 'indexed-fields', where: {} }) it('should have 2dsphere indexes on point fields in groups', () => {
const data1 = { expect(definitions['group.point']).toEqual('2dsphere')
text: 'a', })
uniqueText: 'a',
partOne: 'u', it('should have a sparse index on a unique localized field in a group', () => {
partTwo: 'u', expect(definitions['group.localizedUnique.en']).toEqual(1)
} expect(options['group.localizedUnique.en']).toMatchObject({ sparse: true, unique: true })
const data2 = { expect(definitions['group.localizedUnique.es']).toEqual(1)
text: 'b', expect(options['group.localizedUnique.es']).toMatchObject({ sparse: true, unique: true })
uniqueText: 'b', })
partOne: 'u',
partTwo: 'u', it('should have unique indexes in a collapsible', () => {
} expect(definitions['collapsibleLocalizedUnique.en']).toEqual(1)
await payload.create({ expect(options['collapsibleLocalizedUnique.en']).toMatchObject({
collection: 'indexed-fields', sparse: true,
data: data1, unique: true,
}) })
expect(async () => { expect(definitions.collapsibleTextUnique).toEqual(1)
const result = await payload.create({ expect(options.collapsibleTextUnique).toMatchObject({ unique: true })
collection: 'indexed-fields',
data: data2,
})
return result.error
}).toBeDefined()
}) })
}) })
@@ -386,9 +345,9 @@ describe('Fields', () => {
it('should have version indexes from collection indexes', () => { it('should have version indexes from collection indexes', () => {
expect(definitions['version.partOne']).toEqual(1) expect(definitions['version.partOne']).toEqual(1)
expect(options['version.partOne']).toMatchObject({ expect(options['version.partOne']).toMatchObject({
unique: true,
name: 'compound-index', name: 'compound-index',
sparse: true, sparse: true,
unique: true,
}) })
}) })
}) })
@@ -424,9 +383,9 @@ describe('Fields', () => {
doc = await payload.create({ doc = await payload.create({
collection: 'point-fields', collection: 'point-fields',
data: { data: {
point,
localized,
group, group,
localized,
point,
}, },
}) })
@@ -440,9 +399,9 @@ describe('Fields', () => {
payload.create({ payload.create({
collection: 'point-fields', collection: 'point-fields',
data: { data: {
point,
localized,
group, group,
localized,
point,
}, },
}), }),
).rejects.toThrow(Error) ).rejects.toThrow(Error)
@@ -463,6 +422,52 @@ describe('Fields', () => {
}) })
} }
describe('unique indexes', () => {
it('should throw validation error saving on unique fields', async () => {
const data = {
text: 'a',
uniqueText: 'a',
}
await payload.create({
collection: 'indexed-fields',
data,
})
expect(async () => {
const result = await payload.create({
collection: 'indexed-fields',
data,
})
return result.error
}).toBeDefined()
})
it('should throw validation error saving on unique combined fields', async () => {
await payload.delete({ collection: 'indexed-fields', where: {} })
const data1 = {
partOne: 'u',
partTwo: 'u',
text: 'a',
uniqueText: 'a',
}
const data2 = {
partOne: 'u',
partTwo: 'u',
text: 'b',
uniqueText: 'b',
}
await payload.create({
collection: 'indexed-fields',
data: data1,
})
expect(async () => {
const result = await payload.create({
collection: 'indexed-fields',
data: data2,
})
return result.error
}).toBeDefined()
})
})
describe('array', () => { describe('array', () => {
let doc let doc
const collection = arrayFieldsSlug const collection = arrayFieldsSlug
@@ -499,26 +504,26 @@ describe('Fields', () => {
}) })
const enDoc = await payload.update({ const enDoc = await payload.update({
collection,
id, id,
locale: 'en', collection,
data: { data: {
localized: [{ text: enText }], localized: [{ text: enText }],
}, },
locale: 'en',
}) })
const esDoc = await payload.update({ const esDoc = await payload.update({
collection,
id, id,
locale: 'es', collection,
data: { data: {
localized: [{ text: esText }], localized: [{ text: esText }],
}, },
locale: 'es',
}) })
const allLocales = (await payload.findByID({ const allLocales = (await payload.findByID({
collection,
id, id,
collection,
locale: 'all', locale: 'all',
})) as unknown as { localized: { en: unknown; es: unknown } } })) as unknown as { localized: { en: unknown; es: unknown } }
@@ -569,8 +574,8 @@ describe('Fields', () => {
it('should create with localized text inside a named tab', async () => { it('should create with localized text inside a named tab', async () => {
document = await payload.findByID({ document = await payload.findByID({
collection: tabsSlug,
id: document.id, id: document.id,
collection: tabsSlug,
locale: 'all', locale: 'all',
}) })
expect(document.localizedTab.en.text).toStrictEqual(localizedTextValue) expect(document.localizedTab.en.text).toStrictEqual(localizedTextValue)
@@ -578,8 +583,8 @@ describe('Fields', () => {
it('should allow access control on a named tab', async () => { it('should allow access control on a named tab', async () => {
document = await payload.findByID({ document = await payload.findByID({
collection: tabsSlug,
id: document.id, id: document.id,
collection: tabsSlug,
overrideAccess: false, overrideAccess: false,
}) })
expect(document.accessControlTab).toBeUndefined() expect(document.accessControlTab).toBeUndefined()
@@ -735,8 +740,8 @@ describe('Fields', () => {
expect(jsonFieldsDoc.json.state).toEqual({}) expect(jsonFieldsDoc.json.state).toEqual({})
const updatedJsonFieldsDoc = await payload.update({ const updatedJsonFieldsDoc = await payload.update({
collection: 'json-fields',
id: jsonFieldsDoc.id, id: jsonFieldsDoc.id,
collection: 'json-fields',
data: { data: {
json: { json: {
state: {}, state: {},
@@ -800,8 +805,8 @@ describe('Fields', () => {
expect(nodes).toBeDefined() expect(nodes).toBeDefined()
const child = nodes.flatMap((n) => n.children).find((c) => c.doc) const child = nodes.flatMap((n) => n.children).find((c) => c.doc)
expect(child).toMatchObject({ expect(child).toMatchObject({
type: 'link',
linkType: 'internal', linkType: 'internal',
type: 'link',
}) })
expect(child.doc.relationTo).toEqual('array-fields') expect(child.doc.relationTo).toEqual('array-fields')