Compare commits

..

30 Commits

Author SHA1 Message Date
Kendell Joseph
008437d62f chore: adds db name 2024-07-30 11:59:48 -04:00
Kendell Joseph
95112a873b chore: uses memory server first 2024-07-30 11:59:48 -04:00
Kendell Joseph
25da9b62d1 chore: changes url preference priority 2024-07-30 11:59:48 -04:00
Kendell Joseph
1e5e07489c chore: adds build schema options 2024-07-30 11:59:48 -04:00
Kendell Joseph
bcb1d4eb57 chore: adds memory server uri as option 2024-07-30 11:59:48 -04:00
Kendell Joseph
268fff2645 chore: init test 2024-07-30 11:59:48 -04:00
Kendell Joseph
349d68c719 chore: makes arg optional 2024-07-30 11:59:11 -04:00
Kendell Joseph
9fefe79998 chore: uses default schemaOptions when using mongoose 2024-07-30 11:59:11 -04:00
Kendell Joseph
9f73743806 chore: preserves sibling data 2024-07-30 11:58:29 -04:00
Kendell Joseph
cb00bc5856 chore: implements arg for sibling document keys 2024-07-30 11:58:29 -04:00
Kendell Joseph
bd22bb28aa chore: adds arg for document keys 2024-07-30 11:58:29 -04:00
Kendell Joseph
ed1e460ae7 chore: sends adapter to models 2024-07-30 11:58:29 -04:00
Kendell Joseph
78ac4fd89e chore: uses mongoose adapter 2024-07-30 11:58:29 -04:00
Kendell Joseph
c328c42b72 chore: use mongoose adapter 2024-07-30 11:58:29 -04:00
Kendell Joseph
8ede4d0098 chore: removes unused config 2024-07-30 11:58:29 -04:00
Kendell Joseph
31ad34595e chore: uses mongoose adapter 2024-07-30 11:58:29 -04:00
Kendell Joseph
33278aa8d8 chore: updates documentation 2024-07-30 11:58:29 -04:00
Kendell Joseph
aad186c8b4 chore: adds schema options 2024-07-30 11:58:28 -04:00
Elliot DeNolf
b5b2bb1907 fix(db-postgres): proper migrations table detection query (#7436)
Fixes postgres sql query to detect migrations table.

`error: syntax error at or near "exists"`
2024-07-30 11:38:28 -04:00
Elliot DeNolf
6f5cf5d916 feat(cpa): warn on unsupported Next.js version (#7434)
Improves messaging if running an unsupported version of Next.js.

Closes #7430
2024-07-30 11:14:57 -04:00
Alessio Gravili
aaf3a39f7e chore: upgrade typescript from 5.5.3 to 5.5.4 (#7435)
TypeScript 5.5.4 apparently speeds up linting by a lot:
https://github.com/microsoft/TypeScript/issues/59101
2024-07-30 15:09:28 +00:00
Jessica Chowdhury
5ef2951829 fix: formats locales for version comparison view (#7433)
Closes #7381
2024-07-30 10:24:55 -04:00
Paul
a943487fca fix: hide force unlock button if the user has no permissions to interact with it (#7418) 2024-07-29 20:49:57 +00:00
Jarrod Flesch
3a941c7c8a chore: duplicates prev value PRs from v2 (#7414)
Updates V3 with V2 PR's
- previousVersion type https://github.com/payloadcms/payload/pull/6805
- tests from https://github.com/payloadcms/payload/pull/6805
2024-07-29 16:28:28 -04:00
Dan Ribbens
354588898f feat(db-*, payload): better transactions (#7395)
## Description

### payload
- Removes calls to beginTransaction and commitTransaction from read
operations

### db-sqlite, db-postgres
- beginTransaction() options are passed through and used to create a
transaction
- declare module type adds beginTransaction with proper transaction
config args for postgres and sqlite
2024-07-29 15:35:19 -04:00
Jessica Chowdhury
ada9978a8c fix: page param not getting reset when applying filters (#7243)
Closes #7188

In the collection list view, after adding a filter, the page number
should be reset since the doc count will have changed.

---------

Co-authored-by: Jarrod Flesch <jarrodmflesch@gmail.com>
2024-07-29 13:25:43 -04:00
Jacob Fletcher
874279c530 fix(next): infinite loop when logging into root admin (#7412) 2024-07-29 12:57:57 -04:00
Paul
7ed6634bc5 fix: types for the 'validate' property across fields so internal validation functions can be reused (#7394)
Fixes the types for validate functions so that internal validation
functions can be re-used

Currently this has a type error
```ts
validate: (value, args) => {
  return text(value, args)
},
```
2024-07-29 12:36:28 -04:00
Michel v. Varendorff
09a0ee3ab9 fix: export default was not found in graphql (#6975) 2024-07-29 11:37:58 -04:00
Lynn Dylan Hurley
67acab2cd5 fix(searchPlugin): ensure search updates are unique to collection (#6363) 2024-07-29 11:33:59 -04:00
91 changed files with 1547 additions and 867 deletions

View File

@@ -196,7 +196,6 @@ jobs:
- postgres-custom-schema
- postgres-uuid
- supabase
- sqlite
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres

View File

@@ -33,6 +33,9 @@ export default buildConfig({
| Option | Description |
| -------------------- | ----------- |
| `autoPluralization` | Tell Mongoose to auto-pluralize any collection names if it encounters any singular words used as collection `slug`s. |
| `schemaOptions` | Customize schema options for all Mongoose schemas created internally. |
| `collections` | Options on a collection-by-collection basis. [More](#collections-options) |
| `globals` | Options for the Globals collection created by Payload. [More](#globals-options) |
| `connectOptions` | Customize MongoDB connection options. Payload will connect to your MongoDB database using default options which you can override and extend to include all the [options](https://mongoosejs.com/docs/connections.html#options) available to mongoose. |
| `disableIndexHints` | Set to true to disable hinting to MongoDB to use 'id' as index. This is currently done when counting documents for pagination, as it increases the speed of the count function used in that query. Disabling this optimization might fix some problems with AWS DocumentDB. Defaults to false |
| `migrationDir` | Customize the directory that migrations are stored. |
@@ -49,3 +52,27 @@ You can access Mongoose models as follows:
- Collection models - `payload.db.collections[myCollectionSlug]`
- Globals model - `payload.db.globals`
- Versions model (both collections and globals) - `payload.db.versions[myEntitySlug]`
### Collections Options
You can configure the way the MongoDB adapter works on a collection-by-collection basis, including customizing Mongoose `schemaOptions` for each collection schema created.
Example:
```ts
const db = mongooseAdapter({
url: 'your-url-here',
collections: {
users: {
//
schemaOptions: {
strict: false,
}
}
}
})
```
### Global Options
Payload automatically creates a single `globals` collection that correspond with any Payload globals that you define. When you initialize the `mongooseAdapter`, you can specify settings here for your globals in a similar manner to how you can for collections above. Right now, the only property available is `schemaOptions` but more may be added in the future.

View File

@@ -119,7 +119,7 @@
"create-payload-app": "workspace:*",
"cross-env": "7.0.3",
"dotenv": "16.4.5",
"drizzle-orm": "0.32.1",
"drizzle-orm": "0.29.4",
"escape-html": "^1.0.3",
"execa": "5.1.1",
"form-data": "3.0.1",
@@ -150,7 +150,7 @@
"tempy": "1.0.1",
"tsx": "4.16.2",
"turbo": "^1.13.3",
"typescript": "5.5.3"
"typescript": "5.5.4"
},
"peerDependencies": {
"react": "^19.0.0 || ^19.0.0-rc-6230622a1a-20240610",

View File

@@ -240,25 +240,63 @@ async function installDeps(projectDir: string, packageManager: PackageManager, d
export async function getNextAppDetails(projectDir: string): Promise<NextAppDetails> {
const isSrcDir = fs.existsSync(path.resolve(projectDir, 'src'))
// Match next.config.js, next.config.ts, next.config.mjs, next.config.cjs
const nextConfigPath: string | undefined = (
await globby('next.config.*(t|j)s', { absolute: true, cwd: projectDir })
await globby('next.config.(\\w)?(t|j)s', { absolute: true, cwd: projectDir })
)?.[0]
if (!nextConfigPath || nextConfigPath.length === 0) {
return {
hasTopLevelLayout: false,
isSrcDir,
isSupportedNextVersion: false,
nextConfigPath: undefined,
nextVersion: null,
}
}
const packageObj = await fse.readJson(path.resolve(projectDir, 'package.json'))
// Check if Next.js version is new enough
let nextVersion = null
if (packageObj.dependencies?.next) {
nextVersion = packageObj.dependencies.next
// Match versions using regex matching groups
const versionMatch = /(?<major>\d+)/.exec(nextVersion)
if (!versionMatch) {
p.log.warn(`Could not determine Next.js version from ${nextVersion}`)
return {
hasTopLevelLayout: false,
isSrcDir,
isSupportedNextVersion: false,
nextConfigPath,
nextVersion,
}
}
const { major } = versionMatch.groups as { major: string }
const majorVersion = parseInt(major)
if (majorVersion < 15) {
return {
hasTopLevelLayout: false,
isSrcDir,
isSupportedNextVersion: false,
nextConfigPath,
nextVersion,
}
}
}
const isSupportedNextVersion = true
// Check if Payload already installed
if (packageObj.dependencies?.payload) {
return {
hasTopLevelLayout: false,
isPayloadInstalled: true,
isSrcDir,
isSupportedNextVersion,
nextConfigPath,
nextVersion,
}
}
@@ -281,7 +319,15 @@ export async function getNextAppDetails(projectDir: string): Promise<NextAppDeta
? fs.existsSync(path.resolve(nextAppDir, 'layout.tsx'))
: false
return { hasTopLevelLayout, isSrcDir, nextAppDir, nextConfigPath, nextConfigType: configType }
return {
hasTopLevelLayout,
isSrcDir,
isSupportedNextVersion,
nextAppDir,
nextConfigPath,
nextConfigType: configType,
nextVersion,
}
}
function getProjectType(args: {

View File

@@ -85,7 +85,22 @@ export class Main {
// Detect if inside Next.js project
const nextAppDetails = await getNextAppDetails(process.cwd())
const { hasTopLevelLayout, isPayloadInstalled, nextAppDir, nextConfigPath } = nextAppDetails
const {
hasTopLevelLayout,
isPayloadInstalled,
isSupportedNextVersion,
nextAppDir,
nextConfigPath,
nextVersion,
} = nextAppDetails
if (nextConfigPath && !isSupportedNextVersion) {
p.log.warn(
`Next.js v${nextVersion} is unsupported. Next.js >= 15 is required to use Payload.`,
)
p.outro(feedbackOutro())
process.exit(0)
}
// Upgrade Payload in existing project
if (isPayloadInstalled && nextConfigPath) {

View File

@@ -70,9 +70,11 @@ export type NextAppDetails = {
hasTopLevelLayout: boolean
isPayloadInstalled?: boolean
isSrcDir: boolean
isSupportedNextVersion: boolean
nextAppDir?: string
nextConfigPath?: string
nextConfigType?: NextConfigType
nextVersion: null | string
}
export type NextConfigType = 'cjs' | 'esm' | 'ts'

View File

@@ -1,6 +1,6 @@
import type { CollationOptions, TransactionOptions } from 'mongodb'
import type { MongoMemoryReplSet } from 'mongodb-memory-server'
import type { ClientSession, ConnectOptions, Connection } from 'mongoose'
import type { ClientSession, ConnectOptions, Connection, SchemaOptions } from 'mongoose'
import type { BaseDatabaseAdapter, DatabaseAdapterObj, Payload } from 'payload'
import fs from 'fs'
@@ -66,6 +66,15 @@ export interface Args {
* Defaults to disabled.
*/
collation?: Omit<CollationOptions, 'locale'>
/** Define Mongoose options on a collection-by-collection basis.
*/
collections?: {
[slug: string]: {
/** Define Mongoose schema options for a given collection.
*/
schemaOptions?: SchemaOptions
}
}
/** Extra configuration options */
connectOptions?: {
/** Set false to disable $facet aggregation in non-supporting databases, Defaults to true */
@@ -73,23 +82,40 @@ export interface Args {
} & ConnectOptions
/** Set to true to disable hinting to MongoDB to use 'id' as index. This is currently done when counting documents for pagination. Disabling this optimization might fix some problems with AWS DocumentDB. Defaults to false */
disableIndexHints?: boolean
/** Define Mongoose options for the globals collection.
*/
globals?: {
schemaOptions?: SchemaOptions
}
migrationDir?: string
/**
* typed as any to avoid dependency
*/
mongoMemoryServer?: MongoMemoryReplSet
/** Define default Mongoose schema options for all schemas created.
*/
schemaOptions?: SchemaOptions
transactionOptions?: TransactionOptions | false
/** The URL to connect to MongoDB or false to start payload and prevent connecting */
url: false | string
}
export type MongooseAdapter = {
collectionOptions: {
[slug: string]: {
schemaOptions?: SchemaOptions
}
}
collections: {
[slug: string]: CollectionModel
}
connection: Connection
globals: GlobalModel
globalsOptions: {
schemaOptions?: SchemaOptions
}
mongoMemoryServer: MongoMemoryReplSet
schemaOptions?: SchemaOptions
sessions: Record<number | string, ClientSession>
versions: {
[slug: string]: CollectionModel
@@ -100,13 +126,22 @@ export type MongooseAdapter = {
declare module 'payload' {
export interface DatabaseAdapter
extends Omit<BaseDatabaseAdapter, 'sessions'>,
Omit<Args, 'migrationDir'> {
Omit<Args, 'collections' | 'globals' | 'migrationDir'> {
collectionOptions: {
[slug: string]: {
schemaOptions?: SchemaOptions
}
}
collections: {
[slug: string]: CollectionModel
}
connection: Connection
globals: GlobalModel
globalsOptions: {
schemaOptions?: SchemaOptions
}
mongoMemoryServer: MongoMemoryReplSet
schemaOptions?: SchemaOptions
sessions: Record<number | string, ClientSession>
transactionOptions: TransactionOptions
versions: {
@@ -117,10 +152,13 @@ declare module 'payload' {
export function mongooseAdapter({
autoPluralization = true,
collections,
connectOptions,
disableIndexHints = false,
globals,
migrationDir: migrationDirArg,
mongoMemoryServer,
schemaOptions,
transactionOptions = {},
url,
}: Args): DatabaseAdapterObj {
@@ -133,17 +171,21 @@ export function mongooseAdapter({
// Mongoose-specific
autoPluralization,
collectionOptions: collections || {},
collections: {},
connectOptions: connectOptions || {},
connection: undefined,
count,
disableIndexHints,
globals: undefined,
globalsOptions: globals || {},
mongoMemoryServer,
schemaOptions: schemaOptions || {},
sessions: {},
transactionOptions: transactionOptions === false ? undefined : transactionOptions,
url,
versions: {},
// DatabaseAdapter
beginTransaction: transactionOptions ? beginTransaction : undefined,
commitTransaction,

View File

@@ -16,20 +16,22 @@ import { getDBName } from './utilities/getDBName.js'
export const init: Init = function init(this: MongooseAdapter) {
this.payload.config.collections.forEach((collection: SanitizedCollectionConfig) => {
const schema = buildCollectionSchema(collection, this.payload.config)
const schema = buildCollectionSchema(collection, this)
if (collection.versions) {
const versionModelName = getDBName({ config: collection, versions: true })
const versionCollectionFields = buildVersionCollectionFields(collection)
const versionSchema = buildSchema(this.payload.config, versionCollectionFields, {
const versionSchema = buildSchema(this, versionCollectionFields, {
disableUnique: true,
draftsEnabled: true,
indexSortableFields: this.payload.config.indexSortableFields,
options: {
minimize: false,
timestamps: false,
...this.schemaOptions,
...(this.collectionOptions[collection.slug]?.schemaOptions || {}),
},
})
@@ -57,7 +59,7 @@ export const init: Init = function init(this: MongooseAdapter) {
this.collections[collection.slug] = model
})
const model = buildGlobalModel(this.payload.config)
const model = buildGlobalModel(this)
this.globals = model
this.payload.config.globals.forEach((global) => {
@@ -66,13 +68,15 @@ export const init: Init = function init(this: MongooseAdapter) {
const versionGlobalFields = buildVersionGlobalFields(global)
const versionSchema = buildSchema(this.payload.config, versionGlobalFields, {
const versionSchema = buildSchema(this, versionGlobalFields, {
disableUnique: true,
draftsEnabled: true,
indexSortableFields: this.payload.config.indexSortableFields,
options: {
minimize: false,
timestamps: false,
...this.schemaOptions,
...(this.globalsOptions.schemaOptions || {}),
},
})

View File

@@ -1,27 +1,29 @@
import type { PaginateOptions, Schema } from 'mongoose'
import type { SanitizedCollectionConfig, SanitizedConfig } from 'payload'
import type { SanitizedCollectionConfig } from 'payload'
import paginate from 'mongoose-paginate-v2'
import type { MongooseAdapter } from '../index.js'
import getBuildQueryPlugin from '../queries/buildQuery.js'
import buildSchema from './buildSchema.js'
const buildCollectionSchema = (
collection: SanitizedCollectionConfig,
config: SanitizedConfig,
schemaOptions = {},
adapter: MongooseAdapter,
): Schema => {
const schema = buildSchema(config, collection.fields, {
const schema = buildSchema(adapter, collection.fields, {
draftsEnabled: Boolean(typeof collection?.versions === 'object' && collection.versions.drafts),
indexSortableFields: config.indexSortableFields,
indexSortableFields: adapter.payload.config.indexSortableFields,
options: {
minimize: false,
timestamps: collection.timestamps !== false,
...schemaOptions,
...adapter.schemaOptions,
...(adapter.collectionOptions[collection.slug]?.schemaOptions || {}),
},
})
if (config.indexSortableFields && collection.timestamps !== false) {
if (adapter.payload.config.indexSortableFields && collection.timestamps !== false) {
schema.index({ updatedAt: 1 })
schema.index({ createdAt: 1 })
}

View File

@@ -1,27 +1,34 @@
import type { SanitizedConfig } from 'payload'
import mongoose from 'mongoose'
import type { MongooseAdapter } from '../index.js'
import type { GlobalModel } from '../types.js'
import getBuildQueryPlugin from '../queries/buildQuery.js'
import buildSchema from './buildSchema.js'
export const buildGlobalModel = (config: SanitizedConfig): GlobalModel | null => {
if (config.globals && config.globals.length > 0) {
export const buildGlobalModel = (adapter: MongooseAdapter): GlobalModel | null => {
if (adapter.payload.config.globals && adapter.payload.config.globals.length > 0) {
const globalsSchema = new mongoose.Schema(
{},
{ discriminatorKey: 'globalType', minimize: false, timestamps: true },
{
discriminatorKey: 'globalType',
minimize: false,
...adapter.schemaOptions,
...(adapter.globalsOptions.schemaOptions || {}),
timestamps: true,
},
)
globalsSchema.plugin(getBuildQueryPlugin())
const Globals = mongoose.model('globals', globalsSchema, 'globals') as unknown as GlobalModel
Object.values(config.globals).forEach((globalConfig) => {
const globalSchema = buildSchema(config, globalConfig.fields, {
Object.values(adapter.payload.config.globals).forEach((globalConfig) => {
const globalSchema = buildSchema(adapter, globalConfig.fields, {
options: {
minimize: false,
...adapter.schemaOptions,
...(adapter.globalsOptions.schemaOptions || {}),
},
})
Globals.discriminator(globalConfig.slug, globalSchema)

View File

@@ -19,7 +19,6 @@ import type {
RelationshipField,
RichTextField,
RowField,
SanitizedConfig,
SanitizedLocalizationConfig,
SelectField,
Tab,
@@ -37,6 +36,8 @@ import {
tabHasName,
} from 'payload/shared'
import type { MongooseAdapter } from '../index.js'
export type BuildSchemaOptions = {
allowIDField?: boolean
disableUnique?: boolean
@@ -48,7 +49,7 @@ export type BuildSchemaOptions = {
type FieldSchemaGenerator = (
field: Field,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
) => void
@@ -87,10 +88,10 @@ const localizeSchema = (
if (fieldIsLocalized(entity) && localization && Array.isArray(localization.locales)) {
return {
type: localization.localeCodes.reduce(
(localeSchema, locale) => ({
...localeSchema,
[locale]: schema,
}),
(localeSchema, locale) => {
localeSchema[locale] = schema
return localeSchema
},
{
_id: false,
},
@@ -102,7 +103,7 @@ const localizeSchema = (
}
const buildSchema = (
config: SanitizedConfig,
adapter: MongooseAdapter,
configFields: Field[],
buildSchemaOptions: BuildSchemaOptions = {},
): Schema => {
@@ -130,7 +131,7 @@ const buildSchema = (
const addFieldSchema: FieldSchemaGenerator = fieldToSchemaMap[field.type]
if (addFieldSchema) {
addFieldSchema(field, schema, config, buildSchemaOptions)
addFieldSchema(field, schema, adapter, buildSchemaOptions)
}
}
})
@@ -142,20 +143,22 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
array: (
field: ArrayField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
) => {
const baseSchema = {
...formatBaseSchema(field, buildSchemaOptions),
type: [
buildSchema(config, field.fields, {
buildSchema(adapter, field.fields, {
allowIDField: true,
disableUnique: buildSchemaOptions.disableUnique,
draftsEnabled: buildSchemaOptions.draftsEnabled,
options: {
minimize: false,
...(buildSchemaOptions.options || {}),
_id: false,
id: false,
minimize: false,
timestamps: false,
},
}),
],
@@ -163,36 +166,54 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
}
schema.add({
[field.name]: localizeSchema(field, baseSchema, config.localization),
[field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization),
})
},
blocks: (
field: BlockField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
const fieldSchema = {
type: [new mongoose.Schema({}, { _id: false, discriminatorKey: 'blockType' })],
type: [
new mongoose.Schema(
{},
{
_id: false,
discriminatorKey: 'blockType',
...(buildSchemaOptions.options || {}),
timestamps: false,
},
),
],
default: undefined,
}
schema.add({
[field.name]: localizeSchema(field, fieldSchema, config.localization),
[field.name]: localizeSchema(field, fieldSchema, adapter.payload.config.localization),
})
field.blocks.forEach((blockItem: Block) => {
const blockSchema = new mongoose.Schema({}, { _id: false, id: false })
const blockSchema = new mongoose.Schema(
{},
{
...(buildSchemaOptions.options || {}),
_id: false,
id: false,
timestamps: false,
},
)
blockItem.fields.forEach((blockField) => {
const addFieldSchema: FieldSchemaGenerator = fieldToSchemaMap[blockField.type]
if (addFieldSchema) {
addFieldSchema(blockField, blockSchema, config, buildSchemaOptions)
addFieldSchema(blockField, blockSchema, adapter, buildSchemaOptions)
}
})
if (field.localized && config.localization) {
config.localization.localeCodes.forEach((localeCode) => {
if (field.localized && adapter.payload.config.localization) {
adapter.payload.config.localization.localeCodes.forEach((localeCode) => {
// @ts-expect-error Possible incorrect typing in mongoose types, this works
schema.path(`${field.name}.${localeCode}`).discriminator(blockItem.slug, blockSchema)
})
@@ -205,69 +226,69 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
checkbox: (
field: CheckboxField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
const baseSchema = { ...formatBaseSchema(field, buildSchemaOptions), type: Boolean }
schema.add({
[field.name]: localizeSchema(field, baseSchema, config.localization),
[field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization),
})
},
code: (
field: CodeField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
const baseSchema = { ...formatBaseSchema(field, buildSchemaOptions), type: String }
schema.add({
[field.name]: localizeSchema(field, baseSchema, config.localization),
[field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization),
})
},
collapsible: (
field: CollapsibleField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
field.fields.forEach((subField: Field) => {
const addFieldSchema: FieldSchemaGenerator = fieldToSchemaMap[subField.type]
if (addFieldSchema) {
addFieldSchema(subField, schema, config, buildSchemaOptions)
addFieldSchema(subField, schema, adapter, buildSchemaOptions)
}
})
},
date: (
field: DateField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
const baseSchema = { ...formatBaseSchema(field, buildSchemaOptions), type: Date }
schema.add({
[field.name]: localizeSchema(field, baseSchema, config.localization),
[field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization),
})
},
email: (
field: EmailField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
const baseSchema = { ...formatBaseSchema(field, buildSchemaOptions), type: String }
schema.add({
[field.name]: localizeSchema(field, baseSchema, config.localization),
[field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization),
})
},
group: (
field: GroupField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
const formattedBaseSchema = formatBaseSchema(field, buildSchemaOptions)
@@ -280,26 +301,28 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
const baseSchema = {
...formattedBaseSchema,
type: buildSchema(config, field.fields, {
type: buildSchema(adapter, field.fields, {
disableUnique: buildSchemaOptions.disableUnique,
draftsEnabled: buildSchemaOptions.draftsEnabled,
indexSortableFields,
options: {
minimize: false,
...(buildSchemaOptions.options || {}),
_id: false,
id: false,
minimize: false,
timestamps: false,
},
}),
}
schema.add({
[field.name]: localizeSchema(field, baseSchema, config.localization),
[field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization),
})
},
json: (
field: JSONField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
const baseSchema = {
@@ -308,13 +331,13 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
}
schema.add({
[field.name]: localizeSchema(field, baseSchema, config.localization),
[field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization),
})
},
number: (
field: NumberField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
const baseSchema = {
@@ -323,13 +346,13 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
}
schema.add({
[field.name]: localizeSchema(field, baseSchema, config.localization),
[field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization),
})
},
point: (
field: PointField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
const baseSchema: SchemaTypeOptions<unknown> = {
@@ -348,7 +371,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
}
schema.add({
[field.name]: localizeSchema(field, baseSchema, config.localization),
[field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization),
})
if (field.index === true || field.index === undefined) {
@@ -357,8 +380,8 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
indexOptions.sparse = true
indexOptions.unique = true
}
if (field.localized && config.localization) {
config.localization.locales.forEach((locale) => {
if (field.localized && adapter.payload.config.localization) {
adapter.payload.config.localization.locales.forEach((locale) => {
schema.index({ [`${field.name}.${locale.code}`]: '2dsphere' }, indexOptions)
})
} else {
@@ -369,7 +392,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
radio: (
field: RadioField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
const baseSchema = {
@@ -382,21 +405,21 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
}
schema.add({
[field.name]: localizeSchema(field, baseSchema, config.localization),
[field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization),
})
},
relationship: (
field: RelationshipField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
) => {
const hasManyRelations = Array.isArray(field.relationTo)
let schemaToReturn: { [key: string]: any } = {}
if (field.localized && config.localization) {
if (field.localized && adapter.payload.config.localization) {
schemaToReturn = {
type: config.localization.localeCodes.reduce((locales, locale) => {
type: adapter.payload.config.localization.localeCodes.reduce((locales, locale) => {
let localeSchema: { [key: string]: any } = {}
if (hasManyRelations) {
@@ -465,7 +488,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
richText: (
field: RichTextField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
const baseSchema = {
@@ -474,27 +497,27 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
}
schema.add({
[field.name]: localizeSchema(field, baseSchema, config.localization),
[field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization),
})
},
row: (
field: RowField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
field.fields.forEach((subField: Field) => {
const addFieldSchema: FieldSchemaGenerator = fieldToSchemaMap[subField.type]
if (addFieldSchema) {
addFieldSchema(subField, schema, config, buildSchemaOptions)
addFieldSchema(subField, schema, adapter, buildSchemaOptions)
}
})
},
select: (
field: SelectField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
const baseSchema = {
@@ -514,39 +537,41 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
[field.name]: localizeSchema(
field,
field.hasMany ? [baseSchema] : baseSchema,
config.localization,
adapter.payload.config.localization,
),
})
},
tabs: (
field: TabsField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
field.tabs.forEach((tab) => {
if (tabHasName(tab)) {
const baseSchema = {
type: buildSchema(config, tab.fields, {
type: buildSchema(adapter, tab.fields, {
disableUnique: buildSchemaOptions.disableUnique,
draftsEnabled: buildSchemaOptions.draftsEnabled,
options: {
minimize: false,
...(buildSchemaOptions.options || {}),
_id: false,
id: false,
minimize: false,
timestamps: false,
},
}),
}
schema.add({
[tab.name]: localizeSchema(tab, baseSchema, config.localization),
[tab.name]: localizeSchema(tab, baseSchema, adapter.payload.config.localization),
})
} else {
tab.fields.forEach((subField: Field) => {
const addFieldSchema: FieldSchemaGenerator = fieldToSchemaMap[subField.type]
if (addFieldSchema) {
addFieldSchema(subField, schema, config, buildSchemaOptions)
addFieldSchema(subField, schema, adapter, buildSchemaOptions)
}
})
}
@@ -555,7 +580,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
text: (
field: TextField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
const baseSchema = {
@@ -564,25 +589,25 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
}
schema.add({
[field.name]: localizeSchema(field, baseSchema, config.localization),
[field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization),
})
},
textarea: (
field: TextareaField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
const baseSchema = { ...formatBaseSchema(field, buildSchemaOptions), type: String }
schema.add({
[field.name]: localizeSchema(field, baseSchema, config.localization),
[field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization),
})
},
upload: (
field: UploadField,
schema: Schema,
config: SanitizedConfig,
adapter: MongooseAdapter,
buildSchemaOptions: BuildSchemaOptions,
): void => {
const baseSchema = {
@@ -592,7 +617,7 @@ const fieldToSchemaMap: Record<string, FieldSchemaGenerator> = {
}
schema.add({
[field.name]: localizeSchema(field, baseSchema, config.localization),
[field.name]: localizeSchema(field, baseSchema, adapter.payload.config.localization),
})
},
}

View File

@@ -47,8 +47,8 @@
"dependencies": {
"@payloadcms/drizzle": "workspace:*",
"console-table-printer": "2.11.2",
"drizzle-kit": "0.23.0-eb2ca29",
"drizzle-orm": "0.32.1",
"drizzle-kit": "0.20.14-1f2c838",
"drizzle-orm": "0.29.4",
"pg": "8.11.3",
"prompts": "2.4.2",
"to-snake-case": "1.0.0",

View File

@@ -7,11 +7,10 @@ export const defaultDrizzleSnapshot: DrizzleSnapshotJSON = {
schemas: {},
tables: {},
},
dialect: 'postgresql',
dialect: 'pg',
enums: {},
prevId: '00000000-0000-0000-0000-00000000000',
schemas: {},
sequences: {},
tables: {},
version: '7',
version: '5',
}

View File

@@ -156,6 +156,7 @@ declare module 'payload' {
export interface DatabaseAdapter
extends Omit<Args, 'idType' | 'logger' | 'migrationDir' | 'pool'>,
DrizzleAdapter {
beginTransaction: (options?: PgTransactionConfig) => Promise<null | number | string>
drizzle: PostgresDB
enums: Record<string, GenericEnum>
/**

View File

@@ -46,8 +46,8 @@
"@libsql/client": "^0.6.2",
"@payloadcms/drizzle": "workspace:*",
"console-table-printer": "2.11.2",
"drizzle-kit": "0.23.0-eb2ca29",
"drizzle-orm": "0.32.1",
"drizzle-kit": "0.20.14-1f2c838",
"drizzle-orm": "0.29.4",
"prompts": "2.4.2",
"to-snake-case": "1.0.0",
"uuid": "9.0.0"

View File

@@ -10,5 +10,5 @@ export const defaultDrizzleSnapshot: DrizzleSQLiteSnapshotJSON = {
enums: {},
prevId: '00000000-0000-0000-0000-00000000000',
tables: {},
version: '6',
version: '5',
}

View File

@@ -1,8 +1,9 @@
import type { Relation } from 'drizzle-orm'
/* eslint-disable no-param-reassign */
import type { ColumnDataType, Relation } from 'drizzle-orm'
import type {
AnySQLiteColumn,
ForeignKeyBuilder,
IndexBuilder,
SQLiteColumn,
SQLiteColumnBuilder,
SQLiteTableWithColumns,
UniqueConstraintBuilder,
@@ -31,7 +32,18 @@ import { traverseFields } from './traverseFields.js'
export type BaseExtraConfig = Record<
string,
(cols: {
[x: string]: AnySQLiteColumn
[x: string]: SQLiteColumn<{
baseColumn: never
columnType: string
data: unknown
dataType: ColumnDataType
driverParam: unknown
enumValues: string[]
hasDefault: false
name: string
notNull: false
tableName: string
}>
}) => ForeignKeyBuilder | IndexBuilder | UniqueConstraintBuilder
>

View File

@@ -1,7 +1,8 @@
import type { AnySQLiteColumn} from 'drizzle-orm/sqlite-core';
/* eslint-disable no-param-reassign */
import { index, uniqueIndex } from 'drizzle-orm/sqlite-core'
import type { GenericColumn } from '../types.js'
type CreateIndexArgs = {
columnName: string
name: string | string[]
@@ -10,7 +11,7 @@ type CreateIndexArgs = {
}
export const createIndex = ({ name, columnName, tableName, unique }: CreateIndexArgs) => {
return (table: { [x: string]: AnySQLiteColumn }) => {
return (table: { [x: string]: GenericColumn }) => {
let columns
if (Array.isArray(name)) {
columns = name

View File

@@ -1,10 +1,10 @@
import type { Client, Config, ResultSet } from '@libsql/client'
import type { Operators } from '@payloadcms/drizzle'
import type { BuildQueryJoinAliases, DrizzleAdapter } from '@payloadcms/drizzle/types'
import type { DrizzleConfig, Relation, Relations, SQL } from 'drizzle-orm'
import type { ColumnDataType, DrizzleConfig, Relation, Relations, SQL } from 'drizzle-orm'
import type { LibSQLDatabase } from 'drizzle-orm/libsql'
import type {
AnySQLiteColumn,
SQLiteColumn,
SQLiteInsertOnConflictDoUpdateConfig,
SQLiteTableWithColumns,
SQLiteTransactionConfig,
@@ -25,8 +25,24 @@ export type Args = {
versionsSuffix?: string
}
export type GenericColumn = SQLiteColumn<
{
baseColumn: never
columnType: string
data: unknown
dataType: ColumnDataType
driverParam: unknown
enumValues: string[]
hasDefault: false
name: string
notNull: false
tableName: string
},
object
>
export type GenericColumns = {
[x: string]: AnySQLiteColumn
[x: string]: GenericColumn
}
export type GenericTable = SQLiteTableWithColumns<{
@@ -128,6 +144,7 @@ declare module 'payload' {
export interface DatabaseAdapter
extends Omit<Args, 'idType' | 'logger' | 'migrationDir' | 'pool'>,
DrizzleAdapter {
beginTransaction: (options?: SQLiteTransactionConfig) => Promise<null | number | string>
drizzle: LibSQLDatabase
/**
* An object keyed on each table, with a key value pair where the constraint name is the key, followed by the dot-notation field name

View File

@@ -39,7 +39,7 @@
},
"dependencies": {
"console-table-printer": "2.11.2",
"drizzle-orm": "0.32.1",
"drizzle-orm": "0.29.4",
"prompts": "2.4.2",
"to-snake-case": "1.0.0",
"uuid": "9.0.0"

View File

@@ -6,6 +6,7 @@ import type { DrizzleAdapter, DrizzleTransaction } from '../types.js'
export const beginTransaction: BeginTransaction = async function beginTransaction(
this: DrizzleAdapter,
options: DrizzleAdapter['transactionOptions'],
) {
let id
try {
@@ -41,7 +42,7 @@ export const beginTransaction: BeginTransaction = async function beginTransactio
}
transactionReady()
})
}, this.transactionOptions)
}, options || this.transactionOptions)
.catch(() => {
// swallow
})

View File

@@ -5,7 +5,7 @@ export const migrationTableExists = async (adapter: DrizzleAdapter): Promise<boo
if (adapter.name === 'postgres') {
const prependSchema = adapter.schemaName ? `"${adapter.schemaName}".` : ''
statement = `SELECT to_regclass('${prependSchema}"payload_migrations"') exists;`
statement = `SELECT to_regclass('${prependSchema}"payload_migrations"') AS exists;`
}
if (adapter.name === 'sqlite') {

View File

@@ -33,7 +33,7 @@
"eslint-plugin-react-hooks": "5.1.0-rc-85acf2d195-20240711",
"eslint-plugin-regexp": "2.6.0",
"globals": "15.8.0",
"typescript": "5.5.3",
"typescript": "5.5.4",
"typescript-eslint": "7.16.0"
}
}

View File

@@ -32,7 +32,7 @@
"eslint-plugin-react-hooks": "5.1.0-rc-85acf2d195-20240711",
"eslint-plugin-regexp": "2.6.0",
"globals": "15.8.0",
"typescript": "5.5.3",
"typescript": "5.5.4",
"typescript-eslint": "7.16.0"
}
}

View File

@@ -1,3 +1,3 @@
export { GraphQLJSON, GraphQLJSONObject } from '../packages/graphql-type-json/index.js'
export { buildPaginatedListType } from '../schema/buildPaginatedListType.js'
export { default as GraphQL } from 'graphql'
export * as GraphQL from 'graphql'

View File

@@ -7,7 +7,7 @@ import type {
import { notFound } from 'next/navigation.js'
import { isAdminAuthRoute, isAdminRoute } from './shared.js'
import { getRouteWithoutAdmin, isAdminAuthRoute, isAdminRoute } from './shared.js'
export const handleAdminPage = ({
adminRoute,
@@ -20,9 +20,9 @@ export const handleAdminPage = ({
permissions: Permissions
route: string
}) => {
if (isAdminRoute(route, adminRoute)) {
const baseAdminRoute = adminRoute && adminRoute !== '/' ? route.replace(adminRoute, '') : route
const routeSegments = baseAdminRoute.split('/').filter(Boolean)
if (isAdminRoute({ adminRoute, config, route })) {
const routeWithoutAdmin = getRouteWithoutAdmin({ adminRoute, route })
const routeSegments = routeWithoutAdmin.split('/').filter(Boolean)
const [entityType, entitySlug, createOrID] = routeSegments
const collectionSlug = entityType === 'collections' ? entitySlug : undefined
const globalSlug = entityType === 'globals' ? entitySlug : undefined
@@ -47,7 +47,7 @@ export const handleAdminPage = ({
}
}
if (!permissions.canAccessAdmin && !isAdminAuthRoute(config, route, adminRoute)) {
if (!permissions.canAccessAdmin && !isAdminAuthRoute({ adminRoute, config, route })) {
notFound()
}

View File

@@ -22,7 +22,7 @@ export const handleAuthRedirect = ({
routes: { admin: adminRoute },
} = config
if (!isAdminAuthRoute(config, route, adminRoute)) {
if (!isAdminAuthRoute({ adminRoute, config, route })) {
if (searchParams && 'redirect' in searchParams) delete searchParams.redirect
const redirectRoute = encodeURIComponent(
@@ -36,7 +36,7 @@ export const handleAuthRedirect = ({
const customLoginRoute =
typeof redirectUnauthenticatedUser === 'string' ? redirectUnauthenticatedUser : undefined
const loginRoute = isAdminRoute(route, adminRoute)
const loginRoute = isAdminRoute({ adminRoute, config, route })
? adminLoginRoute
: customLoginRoute || loginRouteFromConfig

View File

@@ -11,16 +11,42 @@ const authRouteKeys: (keyof SanitizedConfig['admin']['routes'])[] = [
'reset',
]
export const isAdminRoute = (route: string, adminRoute: string) => {
return route.startsWith(adminRoute)
export const isAdminRoute = ({
adminRoute,
config,
route,
}: {
adminRoute: string
config: SanitizedConfig
route: string
}): boolean => {
return route.startsWith(adminRoute) && !isAdminAuthRoute({ adminRoute, config, route })
}
export const isAdminAuthRoute = (config: SanitizedConfig, route: string, adminRoute: string) => {
export const isAdminAuthRoute = ({
adminRoute,
config,
route,
}: {
adminRoute: string
config: SanitizedConfig
route: string
}): boolean => {
const authRoutes = config.admin?.routes
? Object.entries(config.admin.routes)
.filter(([key]) => authRouteKeys.includes(key as keyof SanitizedConfig['admin']['routes']))
.map(([_, value]) => value)
: []
return authRoutes.some((r) => route.replace(adminRoute, '').startsWith(r))
return authRoutes.some((r) => getRouteWithoutAdmin({ adminRoute, route }).startsWith(r))
}
export const getRouteWithoutAdmin = ({
adminRoute,
route,
}: {
adminRoute: string
route: string
}): string => {
return adminRoute && adminRoute !== '/' ? route.replace(adminRoute, '') : route
}

View File

@@ -7,13 +7,14 @@ import {
EmailField,
PasswordField,
TextField,
useAuth,
useConfig,
useDocumentInfo,
useFormFields,
useFormModified,
useTranslation,
} from '@payloadcms/ui'
import React, { useCallback, useEffect, useState } from 'react'
import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { toast } from 'sonner'
import type { Props } from './types.js'
@@ -38,6 +39,7 @@ export const Auth: React.FC<Props> = (props) => {
verify,
} = props
const { permissions } = useAuth()
const [changingPassword, setChangingPassword] = useState(requirePassword)
const enableAPIKey = useFormFields(([fields]) => (fields && fields?.enableAPIKey) || null)
const dispatchFields = useFormFields((reducer) => reducer[1])
@@ -50,6 +52,23 @@ export const Auth: React.FC<Props> = (props) => {
serverURL,
} = useConfig()
const hasPermissionToUnlock: boolean = useMemo(() => {
const collection = permissions?.collections?.[collectionSlug]
if (collection) {
const unlock = 'unlock' in collection ? collection.unlock : undefined
if (unlock) {
// current types for permissions do not include auth permissions, this will be fixed in another branch soon, for now we need to ignore the types
// @todo: fix types
// @ts-expect-error
return unlock.permission
}
}
return false
}, [permissions, collectionSlug])
const handleChangePassword = useCallback(
(state: boolean) => {
if (!state) {
@@ -153,11 +172,11 @@ export const Auth: React.FC<Props> = (props) => {
{t('authentication:changePassword')}
</Button>
)}
{operation === 'update' && (
{operation === 'update' && hasPermissionToUnlock && (
<Button
buttonStyle="secondary"
disabled={disabled}
onClick={() => unlock()}
onClick={() => void unlock()}
size="small"
>
{t('authentication:forceUnlock')}

View File

@@ -81,6 +81,8 @@ export const DefaultVersionView: React.FC<DefaultVersionsViewProps> = ({
const canUpdate = docPermissions?.update?.permission
const localeValues = locales.map((locale) => locale.value)
return (
<main className={baseClass}>
<SetViewActions actions={componentMap?.actionsMap?.Edit?.Version} />
@@ -136,11 +138,7 @@ export const DefaultVersionView: React.FC<DefaultVersionsViewProps> = ({
fieldMap={fieldMap}
fieldPermissions={docPermissions?.fields}
i18n={i18n}
locales={
locales
? locales.map(({ label }) => (typeof label === 'string' ? label : undefined))
: []
}
locales={localeValues}
version={
globalConfig
? {

View File

@@ -99,9 +99,8 @@ export const VersionView: EditViewComponent = async (props) => {
const localeOptions: OptionObject[] =
localization &&
localization?.locales &&
localization.locales.map(({ code, label }) => ({
label: typeof label === 'string' ? label : '',
label,
value: code,
}))

View File

@@ -2,7 +2,6 @@ import type { AllOperations, PayloadRequest } from '../types/index.js'
import type { Permissions } from './types.js'
import { getEntityPolicies } from '../utilities/getEntityPolicies.js'
import isolateObjectProperty from '../utilities/isolateObjectProperty.js'
type GetAccessResultsArgs = {
req: PayloadRequest
@@ -45,9 +44,7 @@ export async function getAccessResults({ req }: GetAccessResultsArgs): Promise<P
type: 'collection',
entity: collection,
operations: collectionOperations,
// Do not re-use our existing req object, as we need a new req.transactionID. Cannot re-use our existing one, as this is run in parallel (Promise.all above) which is
// not supported on the same transaction ID. Not passing a transaction ID here creates a new transaction ID.
req: isolateObjectProperty(req, 'transactionID'),
req,
})
results.collections = {
...results.collections,
@@ -68,9 +65,7 @@ export async function getAccessResults({ req }: GetAccessResultsArgs): Promise<P
type: 'global',
entity: global,
operations: globalOperations,
// Do not re-use our existing req object, as we need a new req.transactionID. Cannot re-use our existing one, as this is run in parallel (Promise.all above) which is
// not supported on the same transaction ID. Not passing a transaction ID here creates a new transaction ID.
req: isolateObjectProperty(req, 'transactionID'),
req,
})
results.globals = {
...results.globals,

View File

@@ -1,8 +1,6 @@
import type { PayloadRequest } from '../../types/index.js'
import type { Permissions } from '../types.js'
import { commitTransaction } from '../../utilities/commitTransaction.js'
import { initTransaction } from '../../utilities/initTransaction.js'
import { killTransaction } from '../../utilities/killTransaction.js'
import { adminInit as adminInitTelemetry } from '../../utilities/telemetry/events/adminInit.js'
import { getAccessResults } from '../getAccessResults.js'
@@ -17,10 +15,7 @@ export const accessOperation = async (args: Arguments): Promise<Permissions> =>
adminInitTelemetry(req)
try {
const shouldCommit = await initTransaction(req)
const results = getAccessResults({ req })
if (shouldCommit) await commitTransaction(req)
return results
return getAccessResults({ req })
} catch (e: unknown) {
await killTransaction(req)
throw e

View File

@@ -6,8 +6,6 @@ import type { Collection } from '../config/types.js'
import executeAccess from '../../auth/executeAccess.js'
import { combineQueries } from '../../database/combineQueries.js'
import { validateQueryPaths } from '../../database/queryValidation/validateQueryPaths.js'
import { commitTransaction } from '../../utilities/commitTransaction.js'
import { initTransaction } from '../../utilities/initTransaction.js'
import { killTransaction } from '../../utilities/killTransaction.js'
import { buildAfterOperation } from './utils.js'
@@ -25,8 +23,6 @@ export const countOperation = async <TSlug extends CollectionSlug>(
let args = incomingArgs
try {
const shouldCommit = await initTransaction(args.req)
// /////////////////////////////////////
// beforeOperation - Collection
// /////////////////////////////////////
@@ -102,8 +98,6 @@ export const countOperation = async <TSlug extends CollectionSlug>(
// Return results
// /////////////////////////////////////
if (shouldCommit) await commitTransaction(req)
return result
} catch (error: unknown) {
await killTransaction(args.req)

View File

@@ -1,7 +1,7 @@
import httpStatus from 'http-status'
import type { AccessResult } from '../../config/types.js'
import type { CollectionSlug, GeneratedTypes } from '../../index.js'
import type { CollectionSlug } from '../../index.js'
import type { PayloadRequest, Where } from '../../types/index.js'
import type { BeforeOperationHook, Collection, DataFromCollectionSlug } from '../config/types.js'

View File

@@ -1,4 +1,4 @@
import type { CollectionSlug, JsonObject, TypeWithID } from '../../index.js'
import type { CollectionSlug } from '../../index.js'
import type { PayloadRequest } from '../../types/index.js'
import type { BeforeOperationHook, Collection, DataFromCollectionSlug } from '../config/types.js'

View File

@@ -2,9 +2,7 @@ import type { CollectionPermission } from '../../auth/index.js'
import type { AllOperations, PayloadRequest } from '../../types/index.js'
import type { Collection } from '../config/types.js'
import { commitTransaction } from '../../utilities/commitTransaction.js'
import { getEntityPolicies } from '../../utilities/getEntityPolicies.js'
import { initTransaction } from '../../utilities/initTransaction.js'
import { killTransaction } from '../../utilities/killTransaction.js'
const allOperations: AllOperations[] = ['create', 'read', 'update', 'delete']
@@ -37,8 +35,6 @@ export async function docAccessOperation(args: Arguments): Promise<CollectionPer
}
try {
const shouldCommit = await initTransaction(req)
const result = await getEntityPolicies({
id,
type: 'collection',
@@ -47,8 +43,6 @@ export async function docAccessOperation(args: Arguments): Promise<CollectionPer
req,
})
if (shouldCommit) await commitTransaction(req)
return result
} catch (e: unknown) {
await killTransaction(req)

View File

@@ -8,8 +8,6 @@ import executeAccess from '../../auth/executeAccess.js'
import { combineQueries } from '../../database/combineQueries.js'
import { validateQueryPaths } from '../../database/queryValidation/validateQueryPaths.js'
import { afterRead } from '../../fields/hooks/afterRead/index.js'
import { commitTransaction } from '../../utilities/commitTransaction.js'
import { initTransaction } from '../../utilities/initTransaction.js'
import { killTransaction } from '../../utilities/killTransaction.js'
import { buildVersionCollectionFields } from '../../versions/buildCollectionFields.js'
import { appendVersionToQueryKey } from '../../versions/drafts/appendVersionToQueryKey.js'
@@ -38,8 +36,6 @@ export const findOperation = async <TSlug extends CollectionSlug>(
let args = incomingArgs
try {
const shouldCommit = await initTransaction(args.req)
// /////////////////////////////////////
// beforeOperation - Collection
// /////////////////////////////////////
@@ -253,8 +249,6 @@ export const findOperation = async <TSlug extends CollectionSlug>(
// Return results
// /////////////////////////////////////
if (shouldCommit) await commitTransaction(req)
return result
} catch (error: unknown) {
await killTransaction(args.req)

View File

@@ -7,8 +7,6 @@ import executeAccess from '../../auth/executeAccess.js'
import { combineQueries } from '../../database/combineQueries.js'
import { NotFound } from '../../errors/index.js'
import { afterRead } from '../../fields/hooks/afterRead/index.js'
import { commitTransaction } from '../../utilities/commitTransaction.js'
import { initTransaction } from '../../utilities/initTransaction.js'
import { killTransaction } from '../../utilities/killTransaction.js'
import replaceWithDraftIfAvailable from '../../versions/drafts/replaceWithDraftIfAvailable.js'
import { buildAfterOperation } from './utils.js'
@@ -31,8 +29,6 @@ export const findByIDOperation = async <TSlug extends CollectionSlug>(
let args = incomingArgs
try {
const shouldCommit = await initTransaction(args.req)
// /////////////////////////////////////
// beforeOperation - Collection
// /////////////////////////////////////
@@ -182,8 +178,6 @@ export const findByIDOperation = async <TSlug extends CollectionSlug>(
// Return results
// /////////////////////////////////////
if (shouldCommit) await commitTransaction(req)
return result
} catch (error: unknown) {
await killTransaction(args.req)

View File

@@ -8,8 +8,6 @@ import executeAccess from '../../auth/executeAccess.js'
import { combineQueries } from '../../database/combineQueries.js'
import { APIError, Forbidden, NotFound } from '../../errors/index.js'
import { afterRead } from '../../fields/hooks/afterRead/index.js'
import { commitTransaction } from '../../utilities/commitTransaction.js'
import { initTransaction } from '../../utilities/initTransaction.js'
import { killTransaction } from '../../utilities/killTransaction.js'
export type Arguments = {
@@ -43,8 +41,6 @@ export const findVersionByIDOperation = async <TData extends TypeWithID = any>(
}
try {
const shouldCommit = await initTransaction(req)
// /////////////////////////////////////
// Access
// /////////////////////////////////////
@@ -141,8 +137,6 @@ export const findVersionByIDOperation = async <TData extends TypeWithID = any>(
// Return results
// /////////////////////////////////////
if (shouldCommit) await commitTransaction(req)
return result
} catch (error: unknown) {
await killTransaction(req)

View File

@@ -7,8 +7,6 @@ import executeAccess from '../../auth/executeAccess.js'
import { combineQueries } from '../../database/combineQueries.js'
import { validateQueryPaths } from '../../database/queryValidation/validateQueryPaths.js'
import { afterRead } from '../../fields/hooks/afterRead/index.js'
import { commitTransaction } from '../../utilities/commitTransaction.js'
import { initTransaction } from '../../utilities/initTransaction.js'
import { killTransaction } from '../../utilities/killTransaction.js'
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields.js'
import { buildVersionCollectionFields } from '../../versions/buildCollectionFields.js'
@@ -44,8 +42,6 @@ export const findVersionsOperation = async <TData extends TypeWithVersion<TData>
} = args
try {
const shouldCommit = await initTransaction(req)
// /////////////////////////////////////
// Access
// /////////////////////////////////////
@@ -175,8 +171,6 @@ export const findVersionsOperation = async <TData extends TypeWithVersion<TData>
docs: result.docs.map((doc) => sanitizeInternalFields<TData>(doc)),
}
if (shouldCommit) await commitTransaction(req)
return result
} catch (error: unknown) {
await killTransaction(req)

View File

@@ -10,8 +10,6 @@ import { combineQueries } from '../../database/combineQueries.js'
import { APIError, Forbidden, NotFound } from '../../errors/index.js'
import { afterChange } from '../../fields/hooks/afterChange/index.js'
import { afterRead } from '../../fields/hooks/afterRead/index.js'
import { commitTransaction } from '../../utilities/commitTransaction.js'
import { initTransaction } from '../../utilities/initTransaction.js'
import { killTransaction } from '../../utilities/killTransaction.js'
import { getLatestCollectionVersion } from '../../versions/getLatestCollectionVersion.js'
@@ -40,8 +38,6 @@ export const restoreVersionOperation = async <TData extends TypeWithID = any>(
} = args
try {
const shouldCommit = await initTransaction(req)
if (!id) {
throw new APIError('Missing ID of version to restore.', httpStatus.BAD_REQUEST)
}
@@ -200,8 +196,6 @@ export const restoreVersionOperation = async <TData extends TypeWithID = any>(
})) || result
}, Promise.resolve())
if (shouldCommit) await commitTransaction(req)
return result
} catch (error: unknown) {
await killTransaction(req)

View File

@@ -3,7 +3,7 @@ import type { DeepPartial } from 'ts-essentials'
import httpStatus from 'http-status'
import type { FindOneArgs } from '../../database/types.js'
import type { CollectionSlug, GeneratedTypes } from '../../index.js'
import type { CollectionSlug } from '../../index.js'
import type { PayloadRequest } from '../../types/index.js'
import type {
Collection,

View File

@@ -175,20 +175,22 @@ export type Labels = {
singular: LabelFunction | LabelStatic
}
export type BaseValidateOptions<TData, TSiblingData> = {
export type BaseValidateOptions<TData, TSiblingData, TValue> = {
data: Partial<TData>
id?: number | string
operation?: Operation
preferences: DocumentPreferences
previousValue?: TValue
req: PayloadRequest
siblingData: Partial<TSiblingData>
}
export type ValidateOptions<TData, TSiblingData, TFieldConfig extends object> = BaseValidateOptions<
export type ValidateOptions<
TData,
TSiblingData
> &
TFieldConfig
TSiblingData,
TFieldConfig extends object,
TValue,
> = BaseValidateOptions<TData, TSiblingData, TValue> & TFieldConfig
export type Validate<
TValue = any,
@@ -197,7 +199,7 @@ export type Validate<
TFieldConfig extends object = object,
> = (
value: TValue,
options: ValidateOptions<TData, TSiblingData, TFieldConfig>,
options: ValidateOptions<TData, TSiblingData, TFieldConfig, TValue>,
) => Promise<string | true> | string | true
export type ClientValidate = Omit<Validate, 'req'>
@@ -266,11 +268,12 @@ export type NumberField = {
/** Set a value for the number field to increment / decrement using browser controls. */
step?: number
} & Admin
/** Maximum value accepted. Used in the default `validation` function. */
/** Maximum value accepted. Used in the default `validate` function. */
max?: number
/** Minimum value accepted. Used in the default `validation` function. */
/** Minimum value accepted. Used in the default `validate` function. */
min?: number
type: 'number'
validate?: Validate<number | number[], unknown, unknown, NumberField>
} & (
| {
/** Makes this field an ordered array of numbers instead of just a single number. */
@@ -306,6 +309,7 @@ export type TextField = {
maxLength?: number
minLength?: number
type: 'text'
validate?: Validate<string | string[], unknown, unknown, TextField>
} & (
| {
/** Makes this field an ordered array of strings instead of just a single string. */
@@ -338,6 +342,7 @@ export type EmailField = {
placeholder?: Record<string, string> | string
} & Admin
type: 'email'
validate?: Validate<string, unknown, unknown, EmailField>
} & FieldBase
export type TextareaField = {
@@ -355,6 +360,7 @@ export type TextareaField = {
maxLength?: number
minLength?: number
type: 'textarea'
validate?: Validate<string, unknown, unknown, TextareaField>
} & FieldBase
export type CheckboxField = {
@@ -367,6 +373,7 @@ export type CheckboxField = {
}
} & Admin
type: 'checkbox'
validate?: Validate<unknown, unknown, unknown, CheckboxField>
} & FieldBase
export type DateField = {
@@ -381,6 +388,7 @@ export type DateField = {
placeholder?: Record<string, string> | string
} & Admin
type: 'date'
validate?: Validate<unknown, unknown, unknown, DateField>
} & FieldBase
export type GroupField = {
@@ -396,7 +404,8 @@ export type GroupField = {
*/
interfaceName?: string
type: 'group'
} & Omit<FieldBase, 'required' | 'validation'>
validate?: Validate<unknown, unknown, unknown, GroupField>
} & Omit<FieldBase, 'required'>
export type RowAdmin = Omit<Admin, 'description'>
@@ -404,7 +413,7 @@ export type RowField = {
admin?: RowAdmin
fields: Field[]
type: 'row'
} & Omit<FieldBase, 'admin' | 'label' | 'name'>
} & Omit<FieldBase, 'admin' | 'label' | 'name' | 'validate'>
export type CollapsibleField = {
fields: Field[]
@@ -426,7 +435,7 @@ export type CollapsibleField = {
label: Required<FieldBase['label']>
}
) &
Omit<FieldBase, 'label' | 'name'>
Omit<FieldBase, 'label' | 'name' | 'validate'>
export type TabsAdmin = Omit<Admin, 'description'>
@@ -435,7 +444,7 @@ type TabBase = {
fields: Field[]
interfaceName?: string
saveToJWT?: boolean | string
} & Omit<FieldBase, 'required' | 'validation'>
} & Omit<FieldBase, 'required' | 'validate'>
export type NamedTab = {
/** Customize generated GraphQL and Typescript schema names.
@@ -521,6 +530,7 @@ export type UploadField = {
maxDepth?: number
relationTo: CollectionSlug
type: 'upload'
validate?: Validate<unknown, unknown, unknown, UploadField>
} & FieldBase
type CodeAdmin = {
@@ -537,6 +547,7 @@ export type CodeField = {
maxLength?: number
minLength?: number
type: 'code'
validate?: Validate<string, unknown, unknown, CodeField>
} & Omit<FieldBase, 'admin'>
type JSONAdmin = {
@@ -555,6 +566,7 @@ export type JSONField = {
uri: string
}
type: 'json'
validate?: Validate<Record<string, unknown>, unknown, unknown, JSONField>
} & Omit<FieldBase, 'admin'>
export type SelectField = {
@@ -577,6 +589,7 @@ export type SelectField = {
hasMany?: boolean
options: Option[]
type: 'select'
validate?: Validate<string, unknown, unknown, SelectField>
} & FieldBase
type SharedRelationshipProperties = {
@@ -589,6 +602,7 @@ type SharedRelationshipProperties = {
*/
maxDepth?: number
type: 'relationship'
validate?: Validate<unknown, unknown, unknown, SharedRelationshipProperties>
} & (
| {
hasMany: true
@@ -627,12 +641,14 @@ type RelationshipAdmin = {
}
isSortable?: boolean
} & Admin
export type PolymorphicRelationshipField = {
admin?: {
sortOptions?: { [collectionSlug: CollectionSlug]: string }
} & RelationshipAdmin
relationTo: CollectionSlug[]
} & SharedRelationshipProperties
export type SingleRelationshipField = {
admin?: {
sortOptions?: string
@@ -707,6 +723,7 @@ export type ArrayField = {
maxRows?: number
minRows?: number
type: 'array'
validate?: Validate<unknown[], unknown, unknown, ArrayField>
} & FieldBase
export type RadioField = {
@@ -727,6 +744,7 @@ export type RadioField = {
enumName?: DBIdentifierName
options: Option[]
type: 'radio'
validate?: Validate<string, unknown, unknown, RadioField>
} & FieldBase
export type Block = {
@@ -781,10 +799,12 @@ export type BlockField = {
maxRows?: number
minRows?: number
type: 'blocks'
validate?: Validate<string, unknown, unknown, BlockField>
} & FieldBase
export type PointField = {
type: 'point'
validate?: Validate<unknown, unknown, unknown, PointField>
} & FieldBase
export type Field =

View File

@@ -121,7 +121,7 @@ export const promise = async ({
}
// Validate
if (!skipValidationFromHere && field.validate) {
if (!skipValidationFromHere && 'validate' in field && field.validate) {
const valueToValidate = siblingData[field.name]
let jsonError: object
@@ -140,9 +140,10 @@ export const promise = async ({
jsonError,
operation,
preferences: { fields: {} },
previousValue: siblingDoc[field.name],
req,
siblingData: deepMergeWithSourceArrays(siblingDoc, siblingData),
} as ValidateOptions<any, any, { jsonError: object }>)
} as ValidateOptions<any, any, { jsonError: object }, any>)
if (typeof validationResult === 'string') {
errors.push({

View File

@@ -33,6 +33,7 @@ type Args<T> = {
* The original siblingData (not modified by any hooks)
*/
siblingDoc: JsonObject
siblingDocKeys: Set<string>
}
// This function is responsible for the following actions, in order:
@@ -57,6 +58,7 @@ export const promise = async <T>({
req,
siblingData,
siblingDoc,
siblingDocKeys,
}: Args<T>): Promise<void> => {
const { path: fieldPath, schemaPath: fieldSchemaPath } = getFieldPaths({
field,
@@ -65,6 +67,15 @@ export const promise = async <T>({
})
if (fieldAffectsData(field)) {
// Remove the key from siblingDocKeys
// the goal is to keep any existing data present
// before updating, for users that want to maintain
// external data in the same collections as Payload manages,
// without having fields defined for them
if (siblingDocKeys.has(field.name)) {
siblingDocKeys.delete(field.name)
}
if (field.name === 'id') {
if (field.type === 'number' && typeof siblingData[field.name] === 'string') {
const value = siblingData[field.name] as string
@@ -308,6 +319,7 @@ export const promise = async <T>({
schemaPath: fieldSchemaPath,
siblingData: groupData as JsonObject,
siblingDoc: groupDoc as JsonObject,
siblingDocKeys,
})
break
@@ -335,6 +347,7 @@ export const promise = async <T>({
schemaPath: fieldSchemaPath,
siblingData: row as JsonObject,
siblingDoc: getExistingRowDoc(row as JsonObject, siblingDoc[field.name]),
siblingDocKeys,
}),
)
})
@@ -372,6 +385,7 @@ export const promise = async <T>({
schemaPath: fieldSchemaPath,
siblingData: row as JsonObject,
siblingDoc: rowSiblingDoc,
siblingDocKeys,
}),
)
}
@@ -399,6 +413,7 @@ export const promise = async <T>({
schemaPath: fieldSchemaPath,
siblingData,
siblingDoc,
siblingDocKeys,
})
break
@@ -407,7 +422,10 @@ export const promise = async <T>({
case 'tab': {
let tabSiblingData
let tabSiblingDoc
if (tabHasName(field)) {
const isNamedTab = tabHasName(field)
if (isNamedTab) {
if (typeof siblingData[field.name] !== 'object') siblingData[field.name] = {}
if (typeof siblingDoc[field.name] !== 'object') siblingDoc[field.name] = {}
@@ -433,6 +451,7 @@ export const promise = async <T>({
schemaPath: fieldSchemaPath,
siblingData: tabSiblingData,
siblingDoc: tabSiblingDoc,
siblingDocKeys: isNamedTab ? undefined : siblingDocKeys,
})
break
@@ -454,6 +473,7 @@ export const promise = async <T>({
schemaPath: fieldSchemaPath,
siblingData,
siblingDoc,
siblingDocKeys,
})
break

View File

@@ -26,6 +26,7 @@ type Args<T> = {
* The original siblingData (not modified by any hooks)
*/
siblingDoc: JsonObject
siblingDocKeys?: Set<string>
}
export const traverseFields = async <T>({
@@ -43,8 +44,11 @@ export const traverseFields = async <T>({
schemaPath,
siblingData,
siblingDoc,
siblingDocKeys: incomingSiblingDocKeys,
}: Args<T>): Promise<void> => {
const promises = []
const siblingDocKeys = incomingSiblingDocKeys || new Set(Object.keys(siblingDoc))
fields.forEach((field) => {
promises.push(
promise({
@@ -62,8 +66,18 @@ export const traverseFields = async <T>({
req,
siblingData,
siblingDoc,
siblingDocKeys,
}),
)
})
await Promise.all(promises)
// For any siblingDocKeys that have not been deleted,
// we will move the data to the siblingData object
// to preserve it
siblingDocKeys.forEach((key) => {
if (!['createdAt', 'globalType', 'id', 'updatedAt'].includes(key)) {
siblingData[key] = siblingDoc[key]
}
})
}

View File

@@ -4,8 +4,6 @@ import type { SanitizedGlobalConfig } from '../config/types.js'
import executeAccess from '../../auth/executeAccess.js'
import { afterRead } from '../../fields/hooks/afterRead/index.js'
import { commitTransaction } from '../../utilities/commitTransaction.js'
import { initTransaction } from '../../utilities/initTransaction.js'
import { killTransaction } from '../../utilities/killTransaction.js'
import replaceWithDraftIfAvailable from '../../versions/drafts/replaceWithDraftIfAvailable.js'
@@ -34,8 +32,6 @@ export const findOneOperation = async <T extends Record<string, unknown>>(
} = args
try {
const shouldCommit = await initTransaction(req)
// /////////////////////////////////////
// Retrieve and execute access
// /////////////////////////////////////
@@ -129,12 +125,6 @@ export const findOneOperation = async <T extends Record<string, unknown>>(
// Return results
// /////////////////////////////////////
if (shouldCommit) await commitTransaction(req)
// /////////////////////////////////////
// Return results
// /////////////////////////////////////
return doc
} catch (error: unknown) {
await killTransaction(req)

View File

@@ -7,9 +7,7 @@ import executeAccess from '../../auth/executeAccess.js'
import { combineQueries } from '../../database/combineQueries.js'
import { Forbidden, NotFound } from '../../errors/index.js'
import { afterRead } from '../../fields/hooks/afterRead/index.js'
import { commitTransaction } from '../../utilities/commitTransaction.js'
import { deepCopyObjectSimple } from '../../utilities/deepCopyObject.js'
import { initTransaction } from '../../utilities/initTransaction.js'
import { killTransaction } from '../../utilities/killTransaction.js'
export type Arguments = {
@@ -39,8 +37,6 @@ export const findVersionByIDOperation = async <T extends TypeWithVersion<T> = an
} = args
try {
const shouldCommit = await initTransaction(req)
// /////////////////////////////////////
// Access
// /////////////////////////////////////
@@ -136,12 +132,6 @@ export const findVersionByIDOperation = async <T extends TypeWithVersion<T> = an
})) || result.version
}, Promise.resolve())
// /////////////////////////////////////
// Return results
// /////////////////////////////////////
if (shouldCommit) await commitTransaction(req)
return result
} catch (error: unknown) {
await killTransaction(req)

View File

@@ -7,8 +7,6 @@ import executeAccess from '../../auth/executeAccess.js'
import { combineQueries } from '../../database/combineQueries.js'
import { validateQueryPaths } from '../../database/queryValidation/validateQueryPaths.js'
import { afterRead } from '../../fields/hooks/afterRead/index.js'
import { commitTransaction } from '../../utilities/commitTransaction.js'
import { initTransaction } from '../../utilities/initTransaction.js'
import { killTransaction } from '../../utilities/killTransaction.js'
import sanitizeInternalFields from '../../utilities/sanitizeInternalFields.js'
import { buildVersionGlobalFields } from '../../versions/buildGlobalFields.js'
@@ -44,8 +42,6 @@ export const findVersionsOperation = async <T extends TypeWithVersion<T>>(
const versionFields = buildVersionGlobalFields(globalConfig)
try {
const shouldCommit = await initTransaction(req)
// /////////////////////////////////////
// Access
// /////////////////////////////////////
@@ -147,8 +143,6 @@ export const findVersionsOperation = async <T extends TypeWithVersion<T>>(
docs: result.docs.map((doc) => sanitizeInternalFields<T>(doc)),
}
if (shouldCommit) await commitTransaction(req)
return result
} catch (error: unknown) {
await killTransaction(req)

View File

@@ -6,7 +6,11 @@ import type { PayloadRequest } from '../types/index.js'
export async function killTransaction(req: PayloadRequest): Promise<void> {
const { payload, transactionID } = req
if (transactionID && !(transactionID instanceof Promise)) {
await payload.db.rollbackTransaction(req.transactionID)
try {
await payload.db.rollbackTransaction(req.transactionID)
} catch (error) {
// swallow any errors while attempting to rollback
}
delete req.transactionID
}
}

View File

@@ -85,6 +85,9 @@ export const syncWithSearch: SyncWithSearch = async (args) => {
'doc.value': {
equals: id,
},
'doc.relationTo': {
equals: collection,
},
},
})

View File

@@ -73,7 +73,7 @@ export type NodeValidation<T extends SerializedLexicalNode = SerializedLexicalNo
node: T
nodeValidations: Map<string, Array<NodeValidation>>
validation: {
options: ValidateOptions<unknown, unknown, RichTextField>
options: ValidateOptions<unknown, unknown, RichTextField, SerializedEditorState>
value: SerializedEditorState
}
}) => Promise<string | true> | string | true

View File

@@ -11,7 +11,7 @@ export async function validateNodes({
nodeValidations: Map<string, Array<NodeValidation>>
nodes: SerializedLexicalNode[]
validation: {
options: ValidateOptions<unknown, unknown, RichTextField>
options: ValidateOptions<unknown, unknown, RichTextField, SerializedEditorState>
value: SerializedEditorState
}
}): Promise<string | true> {

View File

@@ -52,7 +52,7 @@
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.0",
"dotenv": "16.4.5",
"prettier": "3.3.2",
"typescript": "5.5.3"
"typescript": "5.5.4"
},
"publishConfig": {
"exports": {

View File

@@ -37,8 +37,8 @@ export const SortColumn: React.FC<SortColumnProps> = (props) => {
if (sort === desc) descClasses.push(`${baseClass}--active`)
const setSort = useCallback(
(newSort) => {
refineListData({
async (newSort: string) => {
await refineListData({
sort: newSort,
})
},
@@ -56,7 +56,7 @@ export const SortColumn: React.FC<SortColumnProps> = (props) => {
label,
})}
className={[...ascClasses, `${baseClass}__button`].filter(Boolean).join(' ')}
onClick={() => setSort(asc)}
onClick={() => void setSort(asc)}
type="button"
>
<ChevronIcon direction="up" />
@@ -67,7 +67,7 @@ export const SortColumn: React.FC<SortColumnProps> = (props) => {
label,
})}
className={[...descClasses, `${baseClass}__button`].filter(Boolean).join(' ')}
onClick={() => setSort(desc)}
onClick={() => void setSort(desc)}
type="button"
>
<ChevronIcon />

View File

@@ -143,8 +143,11 @@ export const WhereBuilder: React.FC<WhereBuilderProps> = (props) => {
React.useEffect(() => {
if (shouldUpdateQuery) {
handleWhereChange({ or: conditions })
setShouldUpdateQuery(false)
async function handleChange() {
await handleWhereChange({ or: conditions })
setShouldUpdateQuery(false)
}
void handleChange()
}
}, [conditions, handleWhereChange, shouldUpdateQuery])

View File

@@ -13,12 +13,19 @@ import { useSearchParams } from '../SearchParams/index.js'
export type ColumnPreferences = Pick<Column, 'accessor' | 'active'>[]
type Handlers = {
handlePageChange?: (page: number) => void
handlePerPageChange?: (limit: number) => void
handleSearchChange?: (search: string) => void
handleSortChange?: (sort: string) => void
handleWhereChange?: (where: Where) => void
type PropHandlers = {
handlePageChange?: (page: number) => Promise<void> | void
handlePerPageChange?: (limit: number) => Promise<void> | void
handleSearchChange?: (search: string) => Promise<void> | void
handleSortChange?: (sort: string) => Promise<void> | void
handleWhereChange?: (where: Where) => Promise<void> | void
}
type ContextHandlers = {
handlePageChange?: (page: number) => Promise<void>
handlePerPageChange?: (limit: number) => Promise<void>
handleSearchChange?: (search: string) => Promise<void>
handleSortChange?: (sort: string) => Promise<void>
handleWhereChange?: (where: Where) => Promise<void>
}
export type ListQueryProps = {
@@ -28,14 +35,14 @@ export type ListQueryProps = {
defaultSort?: string
modifySearchParams?: boolean
preferenceKey?: string
} & Handlers
} & PropHandlers
export type ListQueryContext = {
data: PaginatedDocs
defaultLimit?: number
defaultSort?: string
refineListData: (args: RefineOverrides) => void
} & Handlers
refineListData: (args: RefineOverrides) => Promise<void>
} & ContextHandlers
const Context = createContext({} as ListQueryContext)
@@ -71,6 +78,9 @@ export const ListQueryProvider: React.FC<ListQueryProps> = ({
async (query: RefineOverrides) => {
if (!modifySearchParams) return
let pageQuery = 'page' in query ? query.page : currentQuery?.page
if ('where' in query || 'search' in query) pageQuery = '1'
const updatedPreferences: Record<string, unknown> = {}
let updatePreferences = false
@@ -88,7 +98,7 @@ export const ListQueryProvider: React.FC<ListQueryProps> = ({
const params = {
limit: 'limit' in query ? query.limit : currentQuery?.limit,
page: 'page' in query ? query.page : currentQuery?.page,
page: pageQuery,
search: 'search' in query ? query.search : currentQuery?.search,
sort: 'sort' in query ? query.sort : currentQuery?.sort,
where: 'where' in query ? query.where : currentQuery?.where,
@@ -102,7 +112,7 @@ export const ListQueryProvider: React.FC<ListQueryProps> = ({
const handlePageChange = React.useCallback(
async (arg: number) => {
if (typeof handlePageChangeFromProps === 'function') {
handlePageChangeFromProps(arg)
await handlePageChangeFromProps(arg)
}
await refineListData({ page: String(arg) })
},
@@ -111,7 +121,7 @@ export const ListQueryProvider: React.FC<ListQueryProps> = ({
const handlePerPageChange = React.useCallback(
async (arg: number) => {
if (typeof handlePerPageChangeFromProps === 'function') {
handlePerPageChangeFromProps(arg)
await handlePerPageChangeFromProps(arg)
}
await refineListData({ limit: String(arg) })
},
@@ -120,7 +130,7 @@ export const ListQueryProvider: React.FC<ListQueryProps> = ({
const handleSearchChange = React.useCallback(
async (arg: string) => {
if (typeof handleSearchChangeFromProps === 'function') {
handleSearchChangeFromProps(arg)
await handleSearchChangeFromProps(arg)
}
await refineListData({ search: arg })
},
@@ -129,7 +139,7 @@ export const ListQueryProvider: React.FC<ListQueryProps> = ({
const handleSortChange = React.useCallback(
async (arg: string) => {
if (typeof handleSortChangeFromProps === 'function') {
handleSortChangeFromProps(arg)
await handleSortChangeFromProps(arg)
}
await refineListData({ sort: arg })
},
@@ -138,7 +148,7 @@ export const ListQueryProvider: React.FC<ListQueryProps> = ({
const handleWhereChange = React.useCallback(
async (arg: Where) => {
if (typeof handleWhereChangeFromProps === 'function') {
handleWhereChangeFromProps(arg)
await handleWhereChangeFromProps(arg)
}
await refineListData({ where: arg })
},

520
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -32,7 +32,7 @@
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.0",
"eslint": "^8",
"eslint-config-next": "15.0.0-canary.53",
"typescript": "5.5.3"
"typescript": "5.5.4"
},
"engines": {
"node": "^18.20.2 || >=20.9.0"

View File

@@ -32,7 +32,7 @@
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.0",
"eslint": "^8",
"eslint-config-next": "15.0.0-canary.53",
"typescript": "5.5.3"
"typescript": "5.5.4"
},
"engines": {
"node": "^18.20.2 || >=20.9.0"

View File

@@ -32,7 +32,7 @@
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.0",
"eslint": "^8",
"eslint-config-next": "15.0.0-canary.53",
"typescript": "5.5.3"
"typescript": "5.5.4"
},
"engines": {
"node": "^18.20.2 || >=20.9.0"

View File

@@ -33,7 +33,7 @@
"eslint-config-next": "15.0.0-canary.53",
"postcss": "^8",
"tailwindcss": "^3.3.0",
"typescript": "5.5.3"
"typescript": "5.5.4"
},
"engines": {
"node": "^18.20.2 || >=20.9.0"

View File

@@ -65,7 +65,7 @@
"postcss": "^8.4.38",
"prettier": "^3.0.3",
"tailwindcss": "^3.4.3",
"typescript": "5.5.3"
"typescript": "5.5.4"
},
"engines": {
"node": "^18.20.2 || >=20.9.0"

View File

@@ -17,7 +17,7 @@ dependencies:
version: 3.0.0-beta.67(react-dom@19.0.0-rc-6230622a1a-20240610)(react@19.0.0-rc-6230622a1a-20240610)
'@payloadcms/next':
specifier: 3.0.0-beta.67
version: 3.0.0-beta.67(graphql@16.9.0)(monaco-editor@0.50.0)(next@15.0.0-canary.58)(payload@3.0.0-beta.67)(react-dom@19.0.0-rc-6230622a1a-20240610)(react@19.0.0-rc-6230622a1a-20240610)(types-react@19.0.0-rc.0)(typescript@5.5.3)
version: 3.0.0-beta.67(graphql@16.9.0)(monaco-editor@0.50.0)(next@15.0.0-canary.58)(payload@3.0.0-beta.67)(react-dom@19.0.0-rc-6230622a1a-20240610)(react@19.0.0-rc-6230622a1a-20240610)(types-react@19.0.0-rc.0)(typescript@5.5.4)
'@payloadcms/plugin-cloud':
specifier: 3.0.0-beta.67
version: 3.0.0-beta.67(@aws-sdk/client-sso-oidc@3.614.0)(payload@3.0.0-beta.67)
@@ -80,7 +80,7 @@ dependencies:
version: 15.0.0-canary.58(react-dom@19.0.0-rc-6230622a1a-20240610)(react@19.0.0-rc-6230622a1a-20240610)
payload:
specifier: 3.0.0-beta.67
version: 3.0.0-beta.67(@swc/core@1.6.13)(@swc/types@0.1.9)(graphql@16.9.0)(typescript@5.5.3)
version: 3.0.0-beta.67(@swc/core@1.6.13)(@swc/types@0.1.9)(graphql@16.9.0)(typescript@5.5.4)
payload-admin-bar:
specifier: ^1.0.6
version: 1.0.6(react-dom@19.0.0-rc-6230622a1a-20240610)(react@19.0.0-rc-6230622a1a-20240610)
@@ -112,7 +112,7 @@ devDependencies:
version: 13.5.6
'@payloadcms/eslint-config':
specifier: ^1.1.1
version: 1.1.1(typescript@5.5.3)
version: 1.1.1(typescript@5.5.4)
'@tailwindcss/typography':
specifier: ^0.5.13
version: 0.5.13(tailwindcss@3.4.4)
@@ -139,7 +139,7 @@ devDependencies:
version: 8.57.0
eslint-config-next:
specifier: 15.0.0-canary.58
version: 15.0.0-canary.58(eslint@8.57.0)(typescript@5.5.3)
version: 15.0.0-canary.58(eslint@8.57.0)(typescript@5.5.4)
postcss:
specifier: ^8.4.38
version: 8.4.39
@@ -150,8 +150,8 @@ devDependencies:
specifier: ^3.4.3
version: 3.4.4
typescript:
specifier: 5.5.3
version: 5.5.3
specifier: 5.5.4
version: 5.5.4
packages:
@@ -1947,7 +1947,7 @@ packages:
http-status: 1.6.2
mongoose: 6.12.3(@aws-sdk/client-sso-oidc@3.614.0)
mongoose-paginate-v2: 1.7.22
payload: 3.0.0-beta.67(@swc/core@1.6.13)(@swc/types@0.1.9)(graphql@16.9.0)(typescript@5.5.3)
payload: 3.0.0-beta.67(@swc/core@1.6.13)(@swc/types@0.1.9)(graphql@16.9.0)(typescript@5.5.4)
prompts: 2.4.2
uuid: 10.0.0
transitivePeerDependencies:
@@ -1963,23 +1963,23 @@ packages:
payload: 3.0.0-beta.67
dependencies:
nodemailer: 6.9.10
payload: 3.0.0-beta.67(@swc/core@1.6.13)(@swc/types@0.1.9)(graphql@16.9.0)(typescript@5.5.3)
payload: 3.0.0-beta.67(@swc/core@1.6.13)(@swc/types@0.1.9)(graphql@16.9.0)(typescript@5.5.4)
dev: false
/@payloadcms/eslint-config@1.1.1(typescript@5.5.3):
/@payloadcms/eslint-config@1.1.1(typescript@5.5.4):
resolution: {integrity: sha512-LSf9oEPb6aMEMqdTFqj1v+7p/bdrJWG6hp7748xjVO3RL3yQESTKLK/NbjsMYITN4tKFXjfPWDUtcwHv0hS6/A==}
dependencies:
'@types/eslint': 8.44.2
'@typescript-eslint/eslint-plugin': 6.6.0(@typescript-eslint/parser@6.6.0)(eslint@8.48.0)(typescript@5.5.3)
'@typescript-eslint/parser': 6.6.0(eslint@8.48.0)(typescript@5.5.3)
'@typescript-eslint/eslint-plugin': 6.6.0(@typescript-eslint/parser@6.6.0)(eslint@8.48.0)(typescript@5.5.4)
'@typescript-eslint/parser': 6.6.0(eslint@8.48.0)(typescript@5.5.4)
eslint: 8.48.0
eslint-config-prettier: 9.0.0(eslint@8.48.0)
eslint-plugin-import: 2.28.1(@typescript-eslint/parser@6.6.0)(eslint@8.48.0)
eslint-plugin-jest: 27.2.3(@typescript-eslint/eslint-plugin@6.6.0)(eslint@8.48.0)(typescript@5.5.3)
eslint-plugin-jest: 27.2.3(@typescript-eslint/eslint-plugin@6.6.0)(eslint@8.48.0)(typescript@5.5.4)
eslint-plugin-jest-dom: 5.1.0(eslint@8.48.0)
eslint-plugin-jsx-a11y: 6.7.1(eslint@8.48.0)
eslint-plugin-node: 11.1.0(eslint@8.48.0)
eslint-plugin-perfectionist: 2.0.0(eslint@8.48.0)(typescript@5.5.3)
eslint-plugin-perfectionist: 2.0.0(eslint@8.48.0)(typescript@5.5.4)
eslint-plugin-playwright: 0.16.0(eslint-plugin-jest@27.2.3)(eslint@8.48.0)
eslint-plugin-react: 7.33.2(eslint@8.48.0)
eslint-plugin-react-hooks: 4.6.0(eslint@8.48.0)
@@ -1997,7 +1997,7 @@ packages:
- vue-eslint-parser
dev: true
/@payloadcms/graphql@3.0.0-beta.67(graphql@16.9.0)(payload@3.0.0-beta.67)(typescript@5.5.3):
/@payloadcms/graphql@3.0.0-beta.67(graphql@16.9.0)(payload@3.0.0-beta.67)(typescript@5.5.4):
resolution: {integrity: sha512-gvLd16Ndi2gLCA+Nt3RbkUJH2YdirloeHUzfvldTH7teeslsgYxxzAHopsKiHrQBB565+Q7aWBI2vrS01XF7SA==}
hasBin: true
peerDependencies:
@@ -2006,9 +2006,9 @@ packages:
dependencies:
graphql: 16.9.0
graphql-scalars: 1.22.2(graphql@16.9.0)
payload: 3.0.0-beta.67(@swc/core@1.6.13)(@swc/types@0.1.9)(graphql@16.9.0)(typescript@5.5.3)
payload: 3.0.0-beta.67(@swc/core@1.6.13)(@swc/types@0.1.9)(graphql@16.9.0)(typescript@5.5.4)
pluralize: 8.0.0
ts-essentials: 7.0.3(typescript@5.5.3)
ts-essentials: 7.0.3(typescript@5.5.4)
transitivePeerDependencies:
- typescript
dev: false
@@ -2028,7 +2028,7 @@ packages:
resolution: {integrity: sha512-fq/ntibiaP7kRAIuHgSoOSm/+3DD3MHQStlppPzRLyD3kvmizKtH71ngBZRx9wyg2aH2lqkTLDWLswyHp8KHzQ==}
dev: false
/@payloadcms/next@3.0.0-beta.67(graphql@16.9.0)(monaco-editor@0.50.0)(next@15.0.0-canary.58)(payload@3.0.0-beta.67)(react-dom@19.0.0-rc-6230622a1a-20240610)(react@19.0.0-rc-6230622a1a-20240610)(types-react@19.0.0-rc.0)(typescript@5.5.3):
/@payloadcms/next@3.0.0-beta.67(graphql@16.9.0)(monaco-editor@0.50.0)(next@15.0.0-canary.58)(payload@3.0.0-beta.67)(react-dom@19.0.0-rc-6230622a1a-20240610)(react@19.0.0-rc-6230622a1a-20240610)(types-react@19.0.0-rc.0)(typescript@5.5.4):
resolution: {integrity: sha512-rmR8hX+W6ZNsFD7QxUXoV/6Yq71oDNZVy0GX4PHBXvw/C2EPeTRCuopBsRFPp8PyvooLt7l3WUskIEmfkMdnGw==}
engines: {node: ^18.20.2 || >=20.9.0}
peerDependencies:
@@ -2037,7 +2037,7 @@ packages:
payload: 3.0.0-beta.67
dependencies:
'@dnd-kit/core': 6.0.8(react-dom@19.0.0-rc-6230622a1a-20240610)(react@19.0.0-rc-6230622a1a-20240610)
'@payloadcms/graphql': 3.0.0-beta.67(graphql@16.9.0)(payload@3.0.0-beta.67)(typescript@5.5.3)
'@payloadcms/graphql': 3.0.0-beta.67(graphql@16.9.0)(payload@3.0.0-beta.67)(typescript@5.5.4)
'@payloadcms/translations': 3.0.0-beta.67
'@payloadcms/ui': 3.0.0-beta.67(monaco-editor@0.50.0)(next@15.0.0-canary.58)(payload@3.0.0-beta.67)(react-dom@19.0.0-rc-6230622a1a-20240610)(react@19.0.0-rc-6230622a1a-20240610)(types-react@19.0.0-rc.0)
busboy: 1.6.0
@@ -2048,7 +2048,7 @@ packages:
http-status: 1.6.2
next: 15.0.0-canary.58(react-dom@19.0.0-rc-6230622a1a-20240610)(react@19.0.0-rc-6230622a1a-20240610)
path-to-regexp: 6.2.2
payload: 3.0.0-beta.67(@swc/core@1.6.13)(@swc/types@0.1.9)(graphql@16.9.0)(typescript@5.5.3)
payload: 3.0.0-beta.67(@swc/core@1.6.13)(@swc/types@0.1.9)(graphql@16.9.0)(typescript@5.5.4)
qs-esm: 7.0.2
react-diff-viewer-continued: 3.2.6(react-dom@19.0.0-rc-6230622a1a-20240610)(react@19.0.0-rc-6230622a1a-20240610)
sass: 1.77.4
@@ -2078,7 +2078,7 @@ packages:
'@payloadcms/email-nodemailer': 3.0.0-beta.67(payload@3.0.0-beta.67)
amazon-cognito-identity-js: 6.3.12
nodemailer: 6.9.10
payload: 3.0.0-beta.67(@swc/core@1.6.13)(@swc/types@0.1.9)(graphql@16.9.0)(typescript@5.5.3)
payload: 3.0.0-beta.67(@swc/core@1.6.13)(@swc/types@0.1.9)(graphql@16.9.0)(typescript@5.5.4)
resend: 0.17.2
transitivePeerDependencies:
- '@aws-sdk/client-sso-oidc'
@@ -2097,7 +2097,7 @@ packages:
'@payloadcms/ui': 3.0.0-beta.67(monaco-editor@0.50.0)(next@15.0.0-canary.58)(payload@3.0.0-beta.67)(react-dom@19.0.0-rc-6230622a1a-20240610)(react@19.0.0-rc-6230622a1a-20240610)(types-react@19.0.0-rc.0)
deepmerge: 4.3.1
escape-html: 1.0.3
payload: 3.0.0-beta.67(@swc/core@1.6.13)(@swc/types@0.1.9)(graphql@16.9.0)(typescript@5.5.3)
payload: 3.0.0-beta.67(@swc/core@1.6.13)(@swc/types@0.1.9)(graphql@16.9.0)(typescript@5.5.4)
react: 19.0.0-rc-6230622a1a-20240610
react-dom: 19.0.0-rc-6230622a1a-20240610(react@19.0.0-rc-6230622a1a-20240610)
transitivePeerDependencies:
@@ -2112,7 +2112,7 @@ packages:
peerDependencies:
payload: 3.0.0-beta.67
dependencies:
payload: 3.0.0-beta.67(@swc/core@1.6.13)(@swc/types@0.1.9)(graphql@16.9.0)(typescript@5.5.3)
payload: 3.0.0-beta.67(@swc/core@1.6.13)(@swc/types@0.1.9)(graphql@16.9.0)(typescript@5.5.4)
dev: false
/@payloadcms/plugin-redirects@3.0.0-beta.67(payload@3.0.0-beta.67):
@@ -2120,7 +2120,7 @@ packages:
peerDependencies:
payload: 3.0.0-beta.67
dependencies:
payload: 3.0.0-beta.67(@swc/core@1.6.13)(@swc/types@0.1.9)(graphql@16.9.0)(typescript@5.5.3)
payload: 3.0.0-beta.67(@swc/core@1.6.13)(@swc/types@0.1.9)(graphql@16.9.0)(typescript@5.5.4)
dev: false
/@payloadcms/plugin-seo@3.0.0-beta.67(@payloadcms/translations@3.0.0-beta.67)(@payloadcms/ui@3.0.0-beta.67)(payload@3.0.0-beta.67)(react-dom@19.0.0-rc-6230622a1a-20240610)(react@19.0.0-rc-6230622a1a-20240610):
@@ -2134,7 +2134,7 @@ packages:
dependencies:
'@payloadcms/translations': 3.0.0-beta.67
'@payloadcms/ui': 3.0.0-beta.67(monaco-editor@0.50.0)(next@15.0.0-canary.58)(payload@3.0.0-beta.67)(react-dom@19.0.0-rc-6230622a1a-20240610)(react@19.0.0-rc-6230622a1a-20240610)(types-react@19.0.0-rc.0)
payload: 3.0.0-beta.67(@swc/core@1.6.13)(@swc/types@0.1.9)(graphql@16.9.0)(typescript@5.5.3)
payload: 3.0.0-beta.67(@swc/core@1.6.13)(@swc/types@0.1.9)(graphql@16.9.0)(typescript@5.5.4)
react: 19.0.0-rc-6230622a1a-20240610
react-dom: 19.0.0-rc-6230622a1a-20240610(react@19.0.0-rc-6230622a1a-20240610)
dev: false
@@ -2175,14 +2175,14 @@ packages:
'@lexical/selection': 0.16.1
'@lexical/table': 0.16.1
'@lexical/utils': 0.16.1
'@payloadcms/next': 3.0.0-beta.67(graphql@16.9.0)(monaco-editor@0.50.0)(next@15.0.0-canary.58)(payload@3.0.0-beta.67)(react-dom@19.0.0-rc-6230622a1a-20240610)(react@19.0.0-rc-6230622a1a-20240610)(types-react@19.0.0-rc.0)(typescript@5.5.3)
'@payloadcms/next': 3.0.0-beta.67(graphql@16.9.0)(monaco-editor@0.50.0)(next@15.0.0-canary.58)(payload@3.0.0-beta.67)(react-dom@19.0.0-rc-6230622a1a-20240610)(react@19.0.0-rc-6230622a1a-20240610)(types-react@19.0.0-rc.0)(typescript@5.5.4)
'@payloadcms/translations': 3.0.0-beta.67
'@payloadcms/ui': 3.0.0-beta.67(monaco-editor@0.50.0)(next@15.0.0-canary.58)(payload@3.0.0-beta.67)(react-dom@19.0.0-rc-6230622a1a-20240610)(react@19.0.0-rc-6230622a1a-20240610)(types-react@19.0.0-rc.0)
'@types/uuid': 10.0.0
bson-objectid: 2.0.4
dequal: 2.0.3
lexical: 0.16.1
payload: 3.0.0-beta.67(@swc/core@1.6.13)(@swc/types@0.1.9)(graphql@16.9.0)(typescript@5.5.3)
payload: 3.0.0-beta.67(@swc/core@1.6.13)(@swc/types@0.1.9)(graphql@16.9.0)(typescript@5.5.4)
react: 19.0.0-rc-6230622a1a-20240610
react-dom: 19.0.0-rc-6230622a1a-20240610(react@19.0.0-rc-6230622a1a-20240610)
react-error-boundary: 4.0.13(react@19.0.0-rc-6230622a1a-20240610)
@@ -2218,7 +2218,7 @@ packages:
md5: 2.3.0
next: 15.0.0-canary.58(react-dom@19.0.0-rc-6230622a1a-20240610)(react@19.0.0-rc-6230622a1a-20240610)
object-to-formdata: 4.5.1
payload: 3.0.0-beta.67(@swc/core@1.6.13)(@swc/types@0.1.9)(graphql@16.9.0)(typescript@5.5.3)
payload: 3.0.0-beta.67(@swc/core@1.6.13)(@swc/types@0.1.9)(graphql@16.9.0)(typescript@5.5.4)
qs-esm: 7.0.2
react: 19.0.0-rc-6230622a1a-20240610
react-animate-height: 2.1.2(react-dom@19.0.0-rc-6230622a1a-20240610)(react@19.0.0-rc-6230622a1a-20240610)
@@ -3471,7 +3471,7 @@ packages:
'@types/webidl-conversions': 7.0.3
dev: false
/@typescript-eslint/eslint-plugin@6.6.0(@typescript-eslint/parser@6.6.0)(eslint@8.48.0)(typescript@5.5.3):
/@typescript-eslint/eslint-plugin@6.6.0(@typescript-eslint/parser@6.6.0)(eslint@8.48.0)(typescript@5.5.4):
resolution: {integrity: sha512-CW9YDGTQnNYMIo5lMeuiIG08p4E0cXrXTbcZ2saT/ETE7dWUrNxlijsQeU04qAAKkILiLzdQz+cGFxCJjaZUmA==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
@@ -3483,10 +3483,10 @@ packages:
optional: true
dependencies:
'@eslint-community/regexpp': 4.11.0
'@typescript-eslint/parser': 6.6.0(eslint@8.48.0)(typescript@5.5.3)
'@typescript-eslint/parser': 6.6.0(eslint@8.48.0)(typescript@5.5.4)
'@typescript-eslint/scope-manager': 6.6.0
'@typescript-eslint/type-utils': 6.6.0(eslint@8.48.0)(typescript@5.5.3)
'@typescript-eslint/utils': 6.6.0(eslint@8.48.0)(typescript@5.5.3)
'@typescript-eslint/type-utils': 6.6.0(eslint@8.48.0)(typescript@5.5.4)
'@typescript-eslint/utils': 6.6.0(eslint@8.48.0)(typescript@5.5.4)
'@typescript-eslint/visitor-keys': 6.6.0
debug: 4.3.5
eslint: 8.48.0
@@ -3494,13 +3494,13 @@ packages:
ignore: 5.3.1
natural-compare: 1.4.0
semver: 7.6.2
ts-api-utils: 1.3.0(typescript@5.5.3)
typescript: 5.5.3
ts-api-utils: 1.3.0(typescript@5.5.4)
typescript: 5.5.4
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/eslint-plugin@7.16.0(@typescript-eslint/parser@7.16.0)(eslint@8.57.0)(typescript@5.5.3):
/@typescript-eslint/eslint-plugin@7.16.0(@typescript-eslint/parser@7.16.0)(eslint@8.57.0)(typescript@5.5.4):
resolution: {integrity: sha512-py1miT6iQpJcs1BiJjm54AMzeuMPBSPuKPlnT8HlfudbcS5rYeX5jajpLf3mrdRh9dA/Ec2FVUY0ifeVNDIhZw==}
engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies:
@@ -3512,22 +3512,22 @@ packages:
optional: true
dependencies:
'@eslint-community/regexpp': 4.11.0
'@typescript-eslint/parser': 7.16.0(eslint@8.57.0)(typescript@5.5.3)
'@typescript-eslint/parser': 7.16.0(eslint@8.57.0)(typescript@5.5.4)
'@typescript-eslint/scope-manager': 7.16.0
'@typescript-eslint/type-utils': 7.16.0(eslint@8.57.0)(typescript@5.5.3)
'@typescript-eslint/utils': 7.16.0(eslint@8.57.0)(typescript@5.5.3)
'@typescript-eslint/type-utils': 7.16.0(eslint@8.57.0)(typescript@5.5.4)
'@typescript-eslint/utils': 7.16.0(eslint@8.57.0)(typescript@5.5.4)
'@typescript-eslint/visitor-keys': 7.16.0
eslint: 8.57.0
graphemer: 1.4.0
ignore: 5.3.1
natural-compare: 1.4.0
ts-api-utils: 1.3.0(typescript@5.5.3)
typescript: 5.5.3
ts-api-utils: 1.3.0(typescript@5.5.4)
typescript: 5.5.4
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/parser@6.6.0(eslint@8.48.0)(typescript@5.5.3):
/@typescript-eslint/parser@6.6.0(eslint@8.48.0)(typescript@5.5.4):
resolution: {integrity: sha512-setq5aJgUwtzGrhW177/i+DMLqBaJbdwGj2CPIVFFLE0NCliy5ujIdLHd2D1ysmlmsjdL2GWW+hR85neEfc12w==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
@@ -3539,16 +3539,16 @@ packages:
dependencies:
'@typescript-eslint/scope-manager': 6.6.0
'@typescript-eslint/types': 6.6.0
'@typescript-eslint/typescript-estree': 6.6.0(typescript@5.5.3)
'@typescript-eslint/typescript-estree': 6.6.0(typescript@5.5.4)
'@typescript-eslint/visitor-keys': 6.6.0
debug: 4.3.5
eslint: 8.48.0
typescript: 5.5.3
typescript: 5.5.4
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/parser@7.16.0(eslint@8.57.0)(typescript@5.5.3):
/@typescript-eslint/parser@7.16.0(eslint@8.57.0)(typescript@5.5.4):
resolution: {integrity: sha512-ar9E+k7CU8rWi2e5ErzQiC93KKEFAXA2Kky0scAlPcxYblLt8+XZuHUZwlyfXILyQa95P6lQg+eZgh/dDs3+Vw==}
engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies:
@@ -3560,11 +3560,11 @@ packages:
dependencies:
'@typescript-eslint/scope-manager': 7.16.0
'@typescript-eslint/types': 7.16.0
'@typescript-eslint/typescript-estree': 7.16.0(typescript@5.5.3)
'@typescript-eslint/typescript-estree': 7.16.0(typescript@5.5.4)
'@typescript-eslint/visitor-keys': 7.16.0
debug: 4.3.5
eslint: 8.57.0
typescript: 5.5.3
typescript: 5.5.4
transitivePeerDependencies:
- supports-color
dev: true
@@ -3601,7 +3601,7 @@ packages:
'@typescript-eslint/visitor-keys': 7.16.0
dev: true
/@typescript-eslint/type-utils@6.6.0(eslint@8.48.0)(typescript@5.5.3):
/@typescript-eslint/type-utils@6.6.0(eslint@8.48.0)(typescript@5.5.4):
resolution: {integrity: sha512-8m16fwAcEnQc69IpeDyokNO+D5spo0w1jepWWY2Q6y5ZKNuj5EhVQXjtVAeDDqvW6Yg7dhclbsz6rTtOvcwpHg==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
@@ -3611,17 +3611,17 @@ packages:
typescript:
optional: true
dependencies:
'@typescript-eslint/typescript-estree': 6.6.0(typescript@5.5.3)
'@typescript-eslint/utils': 6.6.0(eslint@8.48.0)(typescript@5.5.3)
'@typescript-eslint/typescript-estree': 6.6.0(typescript@5.5.4)
'@typescript-eslint/utils': 6.6.0(eslint@8.48.0)(typescript@5.5.4)
debug: 4.3.5
eslint: 8.48.0
ts-api-utils: 1.3.0(typescript@5.5.3)
typescript: 5.5.3
ts-api-utils: 1.3.0(typescript@5.5.4)
typescript: 5.5.4
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/type-utils@7.16.0(eslint@8.57.0)(typescript@5.5.3):
/@typescript-eslint/type-utils@7.16.0(eslint@8.57.0)(typescript@5.5.4):
resolution: {integrity: sha512-j0fuUswUjDHfqV/UdW6mLtOQQseORqfdmoBNDFOqs9rvNVR2e+cmu6zJu/Ku4SDuqiJko6YnhwcL8x45r8Oqxg==}
engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies:
@@ -3631,12 +3631,12 @@ packages:
typescript:
optional: true
dependencies:
'@typescript-eslint/typescript-estree': 7.16.0(typescript@5.5.3)
'@typescript-eslint/utils': 7.16.0(eslint@8.57.0)(typescript@5.5.3)
'@typescript-eslint/typescript-estree': 7.16.0(typescript@5.5.4)
'@typescript-eslint/utils': 7.16.0(eslint@8.57.0)(typescript@5.5.4)
debug: 4.3.5
eslint: 8.57.0
ts-api-utils: 1.3.0(typescript@5.5.3)
typescript: 5.5.3
ts-api-utils: 1.3.0(typescript@5.5.4)
typescript: 5.5.4
transitivePeerDependencies:
- supports-color
dev: true
@@ -3661,7 +3661,7 @@ packages:
engines: {node: ^18.18.0 || >=20.0.0}
dev: true
/@typescript-eslint/typescript-estree@5.62.0(typescript@5.5.3):
/@typescript-eslint/typescript-estree@5.62.0(typescript@5.5.4):
resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
@@ -3676,13 +3676,13 @@ packages:
globby: 11.1.0
is-glob: 4.0.3
semver: 7.6.2
tsutils: 3.21.0(typescript@5.5.3)
typescript: 5.5.3
tsutils: 3.21.0(typescript@5.5.4)
typescript: 5.5.4
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/typescript-estree@6.21.0(typescript@5.5.3):
/@typescript-eslint/typescript-estree@6.21.0(typescript@5.5.4):
resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
@@ -3698,13 +3698,13 @@ packages:
is-glob: 4.0.3
minimatch: 9.0.3
semver: 7.6.2
ts-api-utils: 1.3.0(typescript@5.5.3)
typescript: 5.5.3
ts-api-utils: 1.3.0(typescript@5.5.4)
typescript: 5.5.4
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/typescript-estree@6.6.0(typescript@5.5.3):
/@typescript-eslint/typescript-estree@6.6.0(typescript@5.5.4):
resolution: {integrity: sha512-hMcTQ6Al8MP2E6JKBAaSxSVw5bDhdmbCEhGW/V8QXkb9oNsFkA4SBuOMYVPxD3jbtQ4R/vSODBsr76R6fP3tbA==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
@@ -3719,13 +3719,13 @@ packages:
globby: 11.1.0
is-glob: 4.0.3
semver: 7.6.2
ts-api-utils: 1.3.0(typescript@5.5.3)
typescript: 5.5.3
ts-api-utils: 1.3.0(typescript@5.5.4)
typescript: 5.5.4
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/typescript-estree@7.16.0(typescript@5.5.3):
/@typescript-eslint/typescript-estree@7.16.0(typescript@5.5.4):
resolution: {integrity: sha512-a5NTvk51ZndFuOLCh5OaJBELYc2O3Zqxfl3Js78VFE1zE46J2AaVuW+rEbVkQznjkmlzWsUI15BG5tQMixzZLw==}
engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies:
@@ -3741,13 +3741,13 @@ packages:
is-glob: 4.0.3
minimatch: 9.0.5
semver: 7.6.2
ts-api-utils: 1.3.0(typescript@5.5.3)
typescript: 5.5.3
ts-api-utils: 1.3.0(typescript@5.5.4)
typescript: 5.5.4
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/utils@5.62.0(eslint@8.48.0)(typescript@5.5.3):
/@typescript-eslint/utils@5.62.0(eslint@8.48.0)(typescript@5.5.4):
resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
@@ -3758,7 +3758,7 @@ packages:
'@types/semver': 7.5.8
'@typescript-eslint/scope-manager': 5.62.0
'@typescript-eslint/types': 5.62.0
'@typescript-eslint/typescript-estree': 5.62.0(typescript@5.5.3)
'@typescript-eslint/typescript-estree': 5.62.0(typescript@5.5.4)
eslint: 8.48.0
eslint-scope: 5.1.1
semver: 7.6.2
@@ -3767,7 +3767,7 @@ packages:
- typescript
dev: true
/@typescript-eslint/utils@6.21.0(eslint@8.48.0)(typescript@5.5.3):
/@typescript-eslint/utils@6.21.0(eslint@8.48.0)(typescript@5.5.4):
resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
@@ -3778,7 +3778,7 @@ packages:
'@types/semver': 7.5.8
'@typescript-eslint/scope-manager': 6.21.0
'@typescript-eslint/types': 6.21.0
'@typescript-eslint/typescript-estree': 6.21.0(typescript@5.5.3)
'@typescript-eslint/typescript-estree': 6.21.0(typescript@5.5.4)
eslint: 8.48.0
semver: 7.6.2
transitivePeerDependencies:
@@ -3786,7 +3786,7 @@ packages:
- typescript
dev: true
/@typescript-eslint/utils@6.6.0(eslint@8.48.0)(typescript@5.5.3):
/@typescript-eslint/utils@6.6.0(eslint@8.48.0)(typescript@5.5.4):
resolution: {integrity: sha512-mPHFoNa2bPIWWglWYdR0QfY9GN0CfvvXX1Sv6DlSTive3jlMTUy+an67//Gysc+0Me9pjitrq0LJp0nGtLgftw==}
engines: {node: ^16.0.0 || >=18.0.0}
peerDependencies:
@@ -3797,7 +3797,7 @@ packages:
'@types/semver': 7.5.8
'@typescript-eslint/scope-manager': 6.6.0
'@typescript-eslint/types': 6.6.0
'@typescript-eslint/typescript-estree': 6.6.0(typescript@5.5.3)
'@typescript-eslint/typescript-estree': 6.6.0(typescript@5.5.4)
eslint: 8.48.0
semver: 7.6.2
transitivePeerDependencies:
@@ -3805,7 +3805,7 @@ packages:
- typescript
dev: true
/@typescript-eslint/utils@7.16.0(eslint@8.57.0)(typescript@5.5.3):
/@typescript-eslint/utils@7.16.0(eslint@8.57.0)(typescript@5.5.4):
resolution: {integrity: sha512-PqP4kP3hb4r7Jav+NiRCntlVzhxBNWq6ZQ+zQwII1y/G/1gdIPeYDCKr2+dH6049yJQsWZiHU6RlwvIFBXXGNA==}
engines: {node: ^18.18.0 || >=20.0.0}
peerDependencies:
@@ -3814,7 +3814,7 @@ packages:
'@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
'@typescript-eslint/scope-manager': 7.16.0
'@typescript-eslint/types': 7.16.0
'@typescript-eslint/typescript-estree': 7.16.0(typescript@5.5.3)
'@typescript-eslint/typescript-estree': 7.16.0(typescript@5.5.4)
eslint: 8.57.0
transitivePeerDependencies:
- supports-color
@@ -5026,7 +5026,7 @@ packages:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'}
/eslint-config-next@15.0.0-canary.58(eslint@8.57.0)(typescript@5.5.3):
/eslint-config-next@15.0.0-canary.58(eslint@8.57.0)(typescript@5.5.4):
resolution: {integrity: sha512-/lXrLXcEQ2w8dmvFBtzpfTkakVyLSm4uYCyzkV3YSx2mhx/VW4UH8q1jnv7GPmnpZJdtDr7r5Hd5Ffh1+Qlkmg==}
peerDependencies:
eslint: ^7.23.0 || ^8.0.0
@@ -5037,8 +5037,8 @@ packages:
dependencies:
'@next/eslint-plugin-next': 15.0.0-canary.58
'@rushstack/eslint-patch': 1.10.3
'@typescript-eslint/eslint-plugin': 7.16.0(@typescript-eslint/parser@7.16.0)(eslint@8.57.0)(typescript@5.5.3)
'@typescript-eslint/parser': 7.16.0(eslint@8.57.0)(typescript@5.5.3)
'@typescript-eslint/eslint-plugin': 7.16.0(@typescript-eslint/parser@7.16.0)(eslint@8.57.0)(typescript@5.5.4)
'@typescript-eslint/parser': 7.16.0(eslint@8.57.0)(typescript@5.5.4)
eslint: 8.57.0
eslint-import-resolver-node: 0.3.9
eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.16.0)(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1)(eslint@8.57.0)
@@ -5046,7 +5046,7 @@ packages:
eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0)
eslint-plugin-react: 7.34.3(eslint@8.57.0)
eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0)
typescript: 5.5.3
typescript: 5.5.4
transitivePeerDependencies:
- eslint-import-resolver-webpack
- supports-color
@@ -5115,7 +5115,7 @@ packages:
eslint-import-resolver-webpack:
optional: true
dependencies:
'@typescript-eslint/parser': 6.6.0(eslint@8.48.0)(typescript@5.5.3)
'@typescript-eslint/parser': 6.6.0(eslint@8.48.0)(typescript@5.5.4)
debug: 3.2.7
eslint: 8.48.0
eslint-import-resolver-node: 0.3.9
@@ -5144,7 +5144,7 @@ packages:
eslint-import-resolver-webpack:
optional: true
dependencies:
'@typescript-eslint/parser': 7.16.0(eslint@8.57.0)(typescript@5.5.3)
'@typescript-eslint/parser': 7.16.0(eslint@8.57.0)(typescript@5.5.4)
debug: 3.2.7
eslint: 8.57.0
eslint-import-resolver-node: 0.3.9
@@ -5174,7 +5174,7 @@ packages:
'@typescript-eslint/parser':
optional: true
dependencies:
'@typescript-eslint/parser': 6.6.0(eslint@8.48.0)(typescript@5.5.3)
'@typescript-eslint/parser': 6.6.0(eslint@8.48.0)(typescript@5.5.4)
array-includes: 3.1.8
array.prototype.findlastindex: 1.2.5
array.prototype.flat: 1.3.2
@@ -5209,7 +5209,7 @@ packages:
'@typescript-eslint/parser':
optional: true
dependencies:
'@typescript-eslint/parser': 7.16.0(eslint@8.57.0)(typescript@5.5.3)
'@typescript-eslint/parser': 7.16.0(eslint@8.57.0)(typescript@5.5.4)
array-includes: 3.1.8
array.prototype.findlastindex: 1.2.5
array.prototype.flat: 1.3.2
@@ -5249,7 +5249,7 @@ packages:
requireindex: 1.2.0
dev: true
/eslint-plugin-jest@27.2.3(@typescript-eslint/eslint-plugin@6.6.0)(eslint@8.48.0)(typescript@5.5.3):
/eslint-plugin-jest@27.2.3(@typescript-eslint/eslint-plugin@6.6.0)(eslint@8.48.0)(typescript@5.5.4):
resolution: {integrity: sha512-sRLlSCpICzWuje66Gl9zvdF6mwD5X86I4u55hJyFBsxYOsBCmT5+kSUjf+fkFWVMMgpzNEupjW8WzUqi83hJAQ==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
peerDependencies:
@@ -5262,8 +5262,8 @@ packages:
jest:
optional: true
dependencies:
'@typescript-eslint/eslint-plugin': 6.6.0(@typescript-eslint/parser@6.6.0)(eslint@8.48.0)(typescript@5.5.3)
'@typescript-eslint/utils': 5.62.0(eslint@8.48.0)(typescript@5.5.3)
'@typescript-eslint/eslint-plugin': 6.6.0(@typescript-eslint/parser@6.6.0)(eslint@8.48.0)(typescript@5.5.4)
'@typescript-eslint/utils': 5.62.0(eslint@8.48.0)(typescript@5.5.4)
eslint: 8.48.0
transitivePeerDependencies:
- supports-color
@@ -5335,7 +5335,7 @@ packages:
semver: 6.3.1
dev: true
/eslint-plugin-perfectionist@2.0.0(eslint@8.48.0)(typescript@5.5.3):
/eslint-plugin-perfectionist@2.0.0(eslint@8.48.0)(typescript@5.5.4):
resolution: {integrity: sha512-VqUk5WR7Dj8L0gNPqn7bl7NTHFYB8l5um4wo7hkMp0Dl+k8RHDAsOef4pPrty6G8vjnzvb3xIZNNshmDJI8SdA==}
peerDependencies:
astro-eslint-parser: ^0.14.0
@@ -5353,7 +5353,7 @@ packages:
vue-eslint-parser:
optional: true
dependencies:
'@typescript-eslint/utils': 6.21.0(eslint@8.48.0)(typescript@5.5.3)
'@typescript-eslint/utils': 6.21.0(eslint@8.48.0)(typescript@5.5.4)
eslint: 8.48.0
minimatch: 9.0.5
natural-compare-lite: 1.4.0
@@ -5372,7 +5372,7 @@ packages:
optional: true
dependencies:
eslint: 8.48.0
eslint-plugin-jest: 27.2.3(@typescript-eslint/eslint-plugin@6.6.0)(eslint@8.48.0)(typescript@5.5.3)
eslint-plugin-jest: 27.2.3(@typescript-eslint/eslint-plugin@6.6.0)(eslint@8.48.0)(typescript@5.5.4)
dev: true
/eslint-plugin-react-hooks@4.6.0(eslint@8.48.0):
@@ -7265,7 +7265,7 @@ packages:
react-dom: 19.0.0-rc-6230622a1a-20240610(react@19.0.0-rc-6230622a1a-20240610)
dev: false
/payload@3.0.0-beta.67(@swc/core@1.6.13)(@swc/types@0.1.9)(graphql@16.9.0)(typescript@5.5.3):
/payload@3.0.0-beta.67(@swc/core@1.6.13)(@swc/types@0.1.9)(graphql@16.9.0)(typescript@5.5.4):
resolution: {integrity: sha512-VXJTJ/VRZzIdjTzzRBake63NXNnSZRNJ4hcTwkZZ3ZfRN123jzfqMg0DtAtMHOArC/W8PIIfnCWiBhN1YgWS8A==}
engines: {node: ^18.20.2 || >=20.9.0}
hasBin: true
@@ -7303,7 +7303,7 @@ packages:
pluralize: 8.0.0
sanitize-filename: 1.6.3
scmp: 2.1.0
ts-essentials: 7.0.3(typescript@5.5.3)
ts-essentials: 7.0.3(typescript@5.5.4)
uuid: 10.0.0
transitivePeerDependencies:
- '@swc/types'
@@ -8670,21 +8670,21 @@ packages:
utf8-byte-length: 1.0.5
dev: false
/ts-api-utils@1.3.0(typescript@5.5.3):
/ts-api-utils@1.3.0(typescript@5.5.4):
resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==}
engines: {node: '>=16'}
peerDependencies:
typescript: '>=4.2.0'
dependencies:
typescript: 5.5.3
typescript: 5.5.4
dev: true
/ts-essentials@7.0.3(typescript@5.5.3):
/ts-essentials@7.0.3(typescript@5.5.4):
resolution: {integrity: sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ==}
peerDependencies:
typescript: '>=3.7.0'
dependencies:
typescript: 5.5.3
typescript: 5.5.4
dev: false
/ts-interface-checker@0.1.13:
@@ -8706,14 +8706,14 @@ packages:
resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==}
dev: false
/tsutils@3.21.0(typescript@5.5.3):
/tsutils@3.21.0(typescript@5.5.4):
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
engines: {node: '>= 6'}
peerDependencies:
typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta'
dependencies:
tslib: 1.14.1
typescript: 5.5.3
typescript: 5.5.4
dev: true
/tunnel-agent@0.6.0:
@@ -8797,8 +8797,8 @@ packages:
dependencies:
csstype: 3.1.3
/typescript@5.5.3:
resolution: {integrity: sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==}
/typescript@5.5.4:
resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==}
engines: {node: '>=14.17'}
hasBin: true

View File

@@ -32,7 +32,7 @@
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.0",
"eslint": "^8",
"eslint-config-next": "15.0.0-canary.53",
"typescript": "5.5.3"
"typescript": "5.5.4"
},
"engines": {
"node": "^18.20.2 || >=20.9.0"

View File

@@ -32,7 +32,7 @@
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.0",
"eslint": "^8",
"eslint-config-next": "15.0.0-canary.53",
"typescript": "5.5.3"
"typescript": "5.5.4"
},
"engines": {
"node": "^18.20.2 || >=20.9.0"

View File

@@ -32,7 +32,7 @@
"@types/react-dom": "npm:types-react-dom@19.0.0-rc.0",
"eslint": "^8",
"eslint-config-next": "15.0.0-canary.53",
"typescript": "5.5.3"
"typescript": "5.5.4"
},
"engines": {
"node": "^18.20.2 || >=20.9.0"

View File

@@ -18,7 +18,7 @@ import {
closeNav,
ensureCompilationIsDone,
exactText,
getAdminRoutes,
getRoutes,
initPageConsoleErrorCatch,
login,
openDocControls,
@@ -99,7 +99,7 @@ describe('access control', () => {
routes: { logout: logoutRoute },
},
routes: { admin: adminRoute },
} = getAdminRoutes({})
} = getRoutes({})
logoutURL = `${serverURL}${adminRoute}${logoutRoute}`
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -12,6 +12,13 @@ const dirname = path.dirname(filename)
export default buildConfigWithDefaults({
collections: [PostsCollection],
admin: {
autoLogin: {
email: devUser.email,
password: devUser.password,
prefillOnly: true,
},
},
cors: ['http://localhost:3000', 'http://localhost:3001'],
globals: [MenuGlobal],
routes: {

View File

@@ -5,7 +5,7 @@ import * as path from 'path'
import { adminRoute } from 'shared.js'
import { fileURLToPath } from 'url'
import { ensureCompilationIsDone, initPageConsoleErrorCatch } from '../helpers.js'
import { ensureCompilationIsDone, initPageConsoleErrorCatch, login } from '../helpers.js'
import { AdminUrlUtil } from '../helpers/adminUrlUtil.js'
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
import { TEST_TIMEOUT_LONG } from '../playwright.config.js'
@@ -29,6 +29,8 @@ test.describe('Admin Panel (Root)', () => {
page = await context.newPage()
initPageConsoleErrorCatch(page)
await login({ page, serverURL, customRoutes: { admin: adminRoute } })
await ensureCompilationIsDone({
customRoutes: {
admin: adminRoute,

View File

@@ -10,7 +10,7 @@ import {
checkPageTitle,
ensureCompilationIsDone,
exactText,
getAdminRoutes,
getRoutes,
initPageConsoleErrorCatch,
openDocControls,
openNav,
@@ -76,7 +76,7 @@ describe('admin1', () => {
let customFieldsURL: AdminUrlUtil
let disableDuplicateURL: AdminUrlUtil
let serverURL: string
let adminRoutes: ReturnType<typeof getAdminRoutes>
let adminRoutes: ReturnType<typeof getRoutes>
let loginURL: string
beforeAll(async ({ browser }, testInfo) => {
@@ -106,7 +106,7 @@ describe('admin1', () => {
await ensureCompilationIsDone({ customAdminRoutes, page, serverURL })
adminRoutes = getAdminRoutes({ customAdminRoutes })
adminRoutes = getRoutes({ customAdminRoutes })
loginURL = `${serverURL}${adminRoutes.routes.admin}${adminRoutes.admin.routes.login}`
})

View File

@@ -10,7 +10,7 @@ import type { Config, Geo, Post } from '../../payload-types.js'
import {
ensureCompilationIsDone,
exactText,
getAdminRoutes,
getRoutes,
initPageConsoleErrorCatch,
openDocDrawer,
openNav,
@@ -44,7 +44,7 @@ describe('admin2', () => {
let postsUrl: AdminUrlUtil
let serverURL: string
let adminRoutes: ReturnType<typeof getAdminRoutes>
let adminRoutes: ReturnType<typeof getRoutes>
beforeAll(async ({ browser }, testInfo) => {
const prebuild = Boolean(process.env.CI)
@@ -69,7 +69,7 @@ describe('admin2', () => {
await ensureCompilationIsDone({ customAdminRoutes, page, serverURL })
adminRoutes = getAdminRoutes({ customAdminRoutes })
adminRoutes = getRoutes({ customAdminRoutes })
})
beforeEach(async () => {
await reInitializeDB({
@@ -421,6 +421,42 @@ describe('admin2', () => {
await expect(page.getByPlaceholder('Enter a value')).toHaveValue('[object Object]')
await expect(page.locator(tableRowLocator)).toHaveCount(1)
})
test('should reset page when filters are applied', async () => {
await deleteAllPosts()
await mapAsync([...Array(6)], async () => {
await createPost()
})
await page.reload()
await mapAsync([...Array(6)], async () => {
await createPost({ title: 'test' })
})
await page.reload()
const pageInfo = page.locator('.collection-list__page-info')
const perPage = page.locator('.per-page')
const tableItems = page.locator(tableRowLocator)
await expect(tableItems).toHaveCount(10)
await expect(pageInfo).toHaveText('1-10 of 12')
await expect(perPage).toContainText('Per Page: 10')
// go to page 2
await page.goto(`${postsUrl.list}?limit=10&page=2`)
// add filter
await page.locator('.list-controls__toggle-where').click()
await page.locator('.where-builder__add-first-filter').click()
await page.locator('.condition__field .rs__control').click()
const options = page.locator('.rs__option')
await options.locator('text=Tab 1 > Title').click()
await page.locator('.condition__operator .rs__control').click()
await options.locator('text=equals').click()
await page.locator('.condition__value input').fill('test')
// expect to be on page 1
await expect(pageInfo).toHaveText('1-6 of 6')
})
})
describe('table columns', () => {

View File

@@ -13,7 +13,7 @@ import type { Config } from './payload-types.js'
import {
ensureCompilationIsDone,
getAdminRoutes,
getRoutes,
initPageConsoleErrorCatch,
saveDocAndAssert,
} from '../helpers.js'
@@ -49,7 +49,7 @@ const createFirstUser = async ({
routes: { createFirstUser: createFirstUserRoute },
},
routes: { admin: adminRoute },
} = getAdminRoutes({
} = getRoutes({
customAdminRoutes,
customRoutes,
})

View File

@@ -1,264 +1,308 @@
import type { Config } from 'payload'
import { mongooseAdapter } from '@payloadcms/db-mongodb'
import { fileURLToPath } from 'node:url'
import path from 'path'
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
import { devUser } from '../credentials.js'
export default buildConfigWithDefaults({
collections: [
{
slug: 'posts',
fields: [
{
name: 'title',
type: 'text',
required: true,
},
{
name: 'throwAfterChange',
type: 'checkbox',
defaultValue: false,
hooks: {
afterChange: [
({ value }) => {
if (value) {
throw new Error('throw after change')
const filename = fileURLToPath(import.meta.url)
const dirname = path.dirname(filename)
const [testSuiteDir] = process.argv.slice(4)
const migrationDir = path.resolve(
(process.env.PAYLOAD_CONFIG_PATH
? path.join(process.env.PAYLOAD_CONFIG_PATH, '..')
: testSuiteDir) || dirname,
'migrations',
)
const createDatabaseTestConfig = async () => {
const config: Partial<Config> = {
collections: [
{
slug: 'posts',
fields: [
{
name: 'title',
type: 'text',
required: true,
},
{
name: 'throwAfterChange',
type: 'checkbox',
defaultValue: false,
hooks: {
afterChange: [
({ value }) => {
if (value) {
throw new Error('throw after change')
}
},
],
},
},
],
hooks: {
beforeOperation: [
({ args, operation, req }) => {
if (operation === 'update') {
const defaultIDType = req.payload.db.defaultIDType
if (defaultIDType === 'number' && typeof args.id === 'string') {
throw new Error('ID was not sanitized to a number properly')
}
}
return args
},
],
},
},
{
slug: 'relation-a',
fields: [
{
name: 'title',
type: 'text',
},
{
name: 'relationship',
type: 'relationship',
relationTo: 'relation-b',
},
{
name: 'richText',
type: 'richText',
},
],
labels: {
plural: 'Relation As',
singular: 'Relation A',
},
},
{
slug: 'relation-b',
fields: [
{
name: 'title',
type: 'text',
},
{
name: 'relationship',
type: 'relationship',
relationTo: 'relation-a',
},
{
name: 'richText',
type: 'richText',
},
],
labels: {
plural: 'Relation Bs',
singular: 'Relation B',
},
},
{
slug: 'pg-migrations',
fields: [
{
name: 'relation1',
type: 'relationship',
relationTo: 'relation-a',
},
{
name: 'myArray',
type: 'array',
fields: [
{
name: 'relation2',
type: 'relationship',
relationTo: 'relation-b',
},
{
name: 'mySubArray',
type: 'array',
fields: [
{
name: 'relation3',
type: 'relationship',
localized: true,
relationTo: 'relation-b',
},
],
},
],
},
},
],
hooks: {
beforeOperation: [
({ args, operation, req }) => {
if (operation === 'update') {
const defaultIDType = req.payload.db.defaultIDType
if (defaultIDType === 'number' && typeof args.id === 'string') {
throw new Error('ID was not sanitized to a number properly')
}
}
return args
{
name: 'myGroup',
type: 'group',
fields: [
{
name: 'relation4',
type: 'relationship',
localized: true,
relationTo: 'relation-b',
},
],
},
{
name: 'myBlocks',
type: 'blocks',
blocks: [
{
slug: 'myBlock',
fields: [
{
name: 'relation5',
type: 'relationship',
relationTo: 'relation-a',
},
{
name: 'relation6',
type: 'relationship',
localized: true,
relationTo: 'relation-b',
},
],
},
],
},
],
versions: true,
},
},
{
slug: 'relation-a',
fields: [
{
name: 'title',
type: 'text',
{
slug: 'custom-schema',
dbName: 'customs',
fields: [
{
name: 'text',
type: 'text',
},
{
name: 'localizedText',
type: 'text',
localized: true,
},
{
name: 'relationship',
type: 'relationship',
hasMany: true,
relationTo: 'relation-a',
},
{
name: 'select',
type: 'select',
dbName: ({ tableName }) => `${tableName}_customSelect`,
enumName: 'selectEnum',
hasMany: true,
options: ['a', 'b', 'c'],
},
{
name: 'radio',
type: 'select',
enumName: 'radioEnum',
options: ['a', 'b', 'c'],
},
{
name: 'array',
type: 'array',
dbName: 'customArrays',
fields: [
{
name: 'text',
type: 'text',
},
{
name: 'localizedText',
type: 'text',
localized: true,
},
],
},
{
name: 'blocks',
type: 'blocks',
blocks: [
{
slug: 'block',
dbName: 'customBlocks',
fields: [
{
name: 'text',
type: 'text',
},
{
name: 'localizedText',
type: 'text',
localized: true,
},
],
},
],
},
],
versions: {
drafts: true,
},
{
name: 'richText',
type: 'richText',
},
],
labels: {
plural: 'Relation As',
singular: 'Relation A',
},
},
{
slug: 'relation-b',
fields: [
{
name: 'title',
type: 'text',
},
{
name: 'relationship',
type: 'relationship',
relationTo: 'relation-a',
},
{
name: 'richText',
type: 'richText',
},
],
labels: {
plural: 'Relation Bs',
singular: 'Relation B',
],
globals: [
{
slug: 'global',
dbName: 'customGlobal',
fields: [
{
name: 'text',
type: 'text',
},
],
versions: true,
},
],
localization: {
defaultLocale: 'en',
locales: ['en', 'es'],
},
{
slug: 'pg-migrations',
fields: [
{
name: 'relation1',
type: 'relationship',
relationTo: 'relation-a',
onInit: async (payload) => {
await payload.create({
collection: 'users',
data: {
email: devUser.email,
password: devUser.password,
},
{
name: 'myArray',
type: 'array',
fields: [
{
name: 'relation2',
type: 'relationship',
relationTo: 'relation-b',
},
{
name: 'mySubArray',
type: 'array',
fields: [
{
name: 'relation3',
type: 'relationship',
localized: true,
relationTo: 'relation-b',
},
],
},
],
},
{
name: 'myGroup',
type: 'group',
fields: [
{
name: 'relation4',
type: 'relationship',
localized: true,
relationTo: 'relation-b',
},
],
},
{
name: 'myBlocks',
type: 'blocks',
blocks: [
{
slug: 'myBlock',
fields: [
{
name: 'relation5',
type: 'relationship',
relationTo: 'relation-a',
},
{
name: 'relation6',
type: 'relationship',
localized: true,
relationTo: 'relation-b',
},
],
},
],
},
],
versions: true,
})
},
{
slug: 'custom-schema',
dbName: 'customs',
fields: [
{
name: 'text',
type: 'text',
},
{
name: 'localizedText',
type: 'text',
localized: true,
},
{
name: 'relationship',
type: 'relationship',
hasMany: true,
relationTo: 'relation-a',
},
{
name: 'select',
type: 'select',
dbName: ({ tableName }) => `${tableName}_customSelect`,
enumName: 'selectEnum',
hasMany: true,
options: ['a', 'b', 'c'],
},
{
name: 'radio',
type: 'select',
enumName: 'radioEnum',
options: ['a', 'b', 'c'],
},
{
name: 'array',
type: 'array',
dbName: 'customArrays',
fields: [
{
name: 'text',
type: 'text',
},
{
name: 'localizedText',
type: 'text',
localized: true,
},
],
},
{
name: 'blocks',
type: 'blocks',
blocks: [
{
slug: 'block',
dbName: 'customBlocks',
fields: [
{
name: 'text',
type: 'text',
},
{
name: 'localizedText',
type: 'text',
localized: true,
},
],
},
],
},
],
versions: {
drafts: true,
},
typescript: {
outputFile: path.resolve(dirname, 'payload-types.ts'),
},
],
globals: [
{
slug: 'global',
dbName: 'customGlobal',
fields: [
{
name: 'text',
type: 'text',
},
],
versions: true,
},
],
localization: {
defaultLocale: 'en',
locales: ['en', 'es'],
},
onInit: async (payload) => {
await payload.create({
collection: 'users',
data: {
email: devUser.email,
password: devUser.password,
}
const configWithDefaults = await buildConfigWithDefaults(config)
if (
process.env.PAYLOAD_DATABASE === 'mongoose' ||
process.env.PAYLOAD_DATABASE === 'mongodb' ||
!process.env.PAYLOAD_DATABASE
) {
configWithDefaults.db = mongooseAdapter({
migrationDir,
url:
process.env.MONGODB_MEMORY_SERVER_URI ||
process.env.DATABASE_URI ||
'mongodb://127.0.0.1/payloadtests',
// Disable strict mode for Mongoose
schemaOptions: {
strict: false,
},
})
},
typescript: {
outputFile: path.resolve(dirname, 'payload-types.ts'),
},
})
}
return configWithDefaults
}
export default createDatabaseTestConfig()
export const postDoc = {
title: 'test post',

View File

@@ -1,3 +1,4 @@
import type { MongooseAdapter } from '@payloadcms/db-mongodb'
import type { PostgresAdapter } from '@payloadcms/db-postgres/types'
import type { NextRESTClient } from 'helpers/NextRESTClient.js'
import type { Payload, PayloadRequest, TypeWithID } from 'payload'
@@ -6,6 +7,9 @@ import fs from 'fs'
import path from 'path'
import { commitTransaction, initTransaction } from 'payload'
import { fileURLToPath } from 'url'
import { v4 as uuid } from 'uuid'
import type { CustomSchema } from './payload-types.js'
import { devUser } from '../credentials.js'
import { initPayloadInt } from '../helpers/initPayloadInt.js'
@@ -461,4 +465,109 @@ describe('database', () => {
})
})
})
describe('existing data', () => {
let existingDataDoc: CustomSchema
beforeAll(async () => {
if (payload.db.name === 'mongoose') {
const Model = payload.db.collections['custom-schema']
const [doc] = await Model.create([
{
array: [
{
id: uuid(),
localizedText: {
en: 'goodbye',
},
noFieldDefined: 'hi',
text: 'hello',
},
],
blocks: [
{
id: uuid(),
blockType: 'block',
localizedText: {
en: 'goodbye',
},
noFieldDefined: 'hi',
text: 'hello',
},
],
localizedText: {
en: 'goodbye',
},
noFieldDefined: 'hi',
text: 'hello',
},
])
const result = JSON.parse(JSON.stringify(doc))
result.id = result._id
existingDataDoc = result
}
})
it('should allow storage of existing data', async () => {
expect(payload.db).toBeDefined()
if (payload.db.name === 'mongoose') {
expect(existingDataDoc.noFieldDefined).toStrictEqual('hi')
expect(existingDataDoc.array[0].noFieldDefined).toStrictEqual('hi')
expect(existingDataDoc.blocks[0].noFieldDefined).toStrictEqual('hi')
const docWithExistingData = await payload.findByID({
id: existingDataDoc.id,
collection: 'custom-schema',
})
expect(docWithExistingData.noFieldDefined).toStrictEqual('hi')
expect(docWithExistingData.array[0].noFieldDefined).toStrictEqual('hi')
expect(docWithExistingData.blocks[0].noFieldDefined).toStrictEqual('hi')
}
})
it('should maintain existing data while updating', async () => {
expect(payload.db).toBeDefined()
if (payload.db.name === 'mongoose') {
const result = await payload.update({
id: existingDataDoc.id,
collection: 'custom-schema',
data: {
array: [
{
id: existingDataDoc.array[0].id,
localizedText: 'adios',
text: 'hola',
},
],
blocks: [
{
id: existingDataDoc.blocks[0].id,
blockType: 'block',
localizedText: 'adios',
text: 'hola',
},
],
localizedText: 'adios',
text: 'hola',
},
})
expect(result.text).toStrictEqual('hola')
expect(result.array[0].text).toStrictEqual('hola')
expect(result.blocks[0].text).toStrictEqual('hola')
expect(result.localizedText).toStrictEqual('adios')
expect(result.array[0].localizedText).toStrictEqual('adios')
expect(result.blocks[0].localizedText).toStrictEqual('adios')
expect(result.noFieldDefined).toStrictEqual('hi')
expect(result.array[0].noFieldDefined).toStrictEqual('hi')
expect(result.blocks[0].noFieldDefined).toStrictEqual('hi')
}
})
})
})

View File

@@ -0,0 +1,59 @@
import type { CollectionConfig } from 'payload'
import * as QueryString from 'qs-esm'
import { collectionSlugs } from '../../shared.js'
export const PrevValue: CollectionConfig = {
slug: collectionSlugs.prevValue,
fields: [
{
name: 'title',
type: 'text',
required: true,
validate: async (value, options) => {
if (options.operation === 'create') return true
const query = QueryString.stringify(
{
where: {
previousValueRelation: {
in: [options.id],
},
},
},
{
addQueryPrefix: true,
},
)
try {
const relatedDocs = await fetch(
`http://localhost:3000/api/${collectionSlugs.prevValueRelation}${query}`,
{
credentials: 'include',
headers: {
'Content-Type': 'application/json',
},
},
).then((res) => res.json())
if (relatedDocs.docs.length > 0 && value !== options.previousValue) {
console.log({
value,
prev: options.previousValue,
})
return 'Doc is being referenced, cannot change title'
}
} catch (e) {
console.log(e)
}
return true
},
},
{
name: 'description',
type: 'text',
},
],
}

View File

@@ -0,0 +1,14 @@
import type { CollectionConfig } from 'payload'
import { collectionSlugs } from '../../shared.js'
export const PrevValueRelation: CollectionConfig = {
slug: collectionSlugs.prevValueRelation,
fields: [
{
relationTo: collectionSlugs.prevValue,
name: 'previousValueRelation',
type: 'relationship',
},
],
}

View File

@@ -1,11 +1,11 @@
import type { CollectionConfig } from 'payload'
import { slugs } from '../../shared.js'
import { collectionSlugs } from '../../shared.js'
import { ValidateDraftsOn } from '../ValidateDraftsOn/index.js'
export const ValidateDraftsOff: CollectionConfig = {
...ValidateDraftsOn,
slug: slugs.validateDraftsOff,
slug: collectionSlugs.validateDraftsOff,
versions: {
drafts: true,
},

View File

@@ -1,9 +1,9 @@
import type { CollectionConfig } from 'payload'
import { slugs } from '../../shared.js'
import { collectionSlugs } from '../../shared.js'
export const ValidateDraftsOn: CollectionConfig = {
slug: slugs.validateDraftsOn,
slug: collectionSlugs.validateDraftsOn,
fields: [
{
name: 'title',

View File

@@ -1,11 +1,11 @@
import type { CollectionConfig } from 'payload'
import { slugs } from '../../shared.js'
import { collectionSlugs } from '../../shared.js'
import { ValidateDraftsOn } from '../ValidateDraftsOn/index.js'
export const ValidateDraftsOnAndAutosave: CollectionConfig = {
...ValidateDraftsOn,
slug: slugs.validateDraftsOnAutosave,
slug: collectionSlugs.validateDraftsOnAutosave,
versions: {
drafts: {
autosave: true,

View File

@@ -5,6 +5,8 @@ const dirname = path.dirname(filename)
import { buildConfigWithDefaults } from '../buildConfigWithDefaults.js'
import { devUser } from '../credentials.js'
import { ErrorFieldsCollection } from './collections/ErrorFields/index.js'
import { PrevValue } from './collections/PrevValue/index.js'
import { PrevValueRelation } from './collections/PrevValueRelation/index.js'
import Uploads from './collections/Upload/index.js'
import { ValidateDraftsOff } from './collections/ValidateDraftsOff/index.js'
import { ValidateDraftsOn } from './collections/ValidateDraftsOn/index.js'
@@ -18,6 +20,8 @@ export default buildConfigWithDefaults({
ValidateDraftsOn,
ValidateDraftsOff,
ValidateDraftsOnAndAutosave,
PrevValue,
PrevValueRelation,
],
globals: [GlobalValidateDraftsOn],
onInit: async (payload) => {

View File

@@ -8,7 +8,7 @@ import { fileURLToPath } from 'url'
import { ensureCompilationIsDone, initPageConsoleErrorCatch, saveDocAndAssert } from '../helpers.js'
import { initPayloadE2ENoConfig } from '../helpers/initPayloadE2ENoConfig.js'
import { TEST_TIMEOUT_LONG } from '../playwright.config.js'
import { slugs } from './shared.js'
import { collectionSlugs } from './shared.js'
const { beforeAll, describe } = test
const filename = fileURLToPath(import.meta.url)
@@ -20,13 +20,17 @@ describe('field error states', () => {
let validateDraftsOff: AdminUrlUtil
let validateDraftsOn: AdminUrlUtil
let validateDraftsOnAutosave: AdminUrlUtil
let prevValue: AdminUrlUtil
let prevValueRelation: AdminUrlUtil
beforeAll(async ({ browser }, testInfo) => {
testInfo.setTimeout(TEST_TIMEOUT_LONG)
;({ serverURL } = await initPayloadE2ENoConfig({ dirname }))
validateDraftsOff = new AdminUrlUtil(serverURL, slugs.validateDraftsOff)
validateDraftsOn = new AdminUrlUtil(serverURL, slugs.validateDraftsOn)
validateDraftsOnAutosave = new AdminUrlUtil(serverURL, slugs.validateDraftsOnAutosave)
validateDraftsOff = new AdminUrlUtil(serverURL, collectionSlugs.validateDraftsOff)
validateDraftsOn = new AdminUrlUtil(serverURL, collectionSlugs.validateDraftsOn)
validateDraftsOnAutosave = new AdminUrlUtil(serverURL, collectionSlugs.validateDraftsOnAutosave)
prevValue = new AdminUrlUtil(serverURL, collectionSlugs.prevValue)
prevValueRelation = new AdminUrlUtil(serverURL, collectionSlugs.prevValueRelation)
const context = await browser.newContext()
page = await context.newPage()
initPageConsoleErrorCatch(page)
@@ -87,4 +91,33 @@ describe('field error states', () => {
await saveDocAndAssert(page, '#action-save', 'error')
})
})
describe('previous values', () => {
test('should pass previous value into validate function', async () => {
// save original
await page.goto(prevValue.create)
await page.locator('#field-title').fill('original value')
await saveDocAndAssert(page)
await page.locator('#field-title').fill('original value 2')
await saveDocAndAssert(page)
// create relation to doc
await page.goto(prevValueRelation.create)
await page.locator('#field-previousValueRelation .react-select').click()
await page.locator('#field-previousValueRelation .rs__option').first().click()
await saveDocAndAssert(page)
// go back to doc
await page.goto(prevValue.list)
await page.locator('.row-1 a').click()
await page.locator('#field-description').fill('some description')
await saveDocAndAssert(page)
await page.locator('#field-title').fill('changed')
await saveDocAndAssert(page, '#action-save', 'error')
// ensure value is the value before relationship association
await page.reload()
await expect(page.locator('#field-title')).toHaveValue('original value 2')
})
})
})

View File

@@ -1,9 +1,9 @@
import type { GlobalConfig } from 'payload'
import { slugs } from '../../shared.js'
import { globalSlugs } from '../../shared.js'
export const GlobalValidateDraftsOn: GlobalConfig = {
slug: slugs.globalValidateDraftsOn,
slug: globalSlugs.globalValidateDraftsOn,
fields: [
{
name: 'group',

View File

@@ -16,10 +16,15 @@ export interface Config {
'validate-drafts-on': ValidateDraftsOn;
'validate-drafts-off': ValidateDraftsOff;
'validate-drafts-on-autosave': ValidateDraftsOnAutosave;
'prev-value': PrevValue;
'prev-value-relation': PrevValueRelation;
users: User;
'payload-preferences': PayloadPreference;
'payload-migrations': PayloadMigration;
};
db: {
defaultIDType: string;
};
globals: {
'global-validate-drafts-on': GlobalValidateDraftsOn;
};
@@ -33,13 +38,16 @@ export interface UserAuthOperations {
email: string;
};
login: {
password: string;
email: string;
password: string;
};
registerFirstUser: {
email: string;
password: string;
};
unlock: {
email: string;
};
}
/**
* This interface was referenced by `Config`'s JSON-Schema
@@ -312,6 +320,27 @@ export interface ValidateDraftsOnAutosave {
createdAt: string;
_status?: ('draft' | 'published') | null;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "prev-value".
*/
export interface PrevValue {
id: string;
title: string;
description?: string | null;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "prev-value-relation".
*/
export interface PrevValueRelation {
id: string;
previousValueRelation?: (string | null) | PrevValue;
updatedAt: string;
createdAt: string;
}
/**
* This interface was referenced by `Config`'s JSON-Schema
* via the `definition` "payload-preferences".

View File

@@ -1,6 +1,17 @@
export const slugs = {
globalValidateDraftsOn: 'global-validate-drafts-on',
import type { CollectionSlug, GlobalSlug } from 'payload'
export const collectionSlugs: {
[key: string]: CollectionSlug
} = {
validateDraftsOff: 'validate-drafts-off',
validateDraftsOn: 'validate-drafts-on',
validateDraftsOnAutosave: 'validate-drafts-on-autosave',
prevValue: 'prev-value',
prevValueRelation: 'prev-value-relation',
}
export const globalSlugs: {
[key: string]: GlobalSlug
} = {
globalValidateDraftsOn: 'global-validate-drafts-on',
}

View File

@@ -1,6 +1,7 @@
import type { BrowserContext, ChromiumBrowserContext, Locator, Page } from '@playwright/test'
import type { Config } from 'payload'
import { formatAdminURL } from '@payloadcms/ui/shared'
import { expect } from '@playwright/test'
import { defaults } from 'payload'
import { wait } from 'payload/shared'
@@ -65,7 +66,7 @@ export async function ensureCompilationIsDone({
}): Promise<void> {
const {
routes: { admin: adminRoute },
} = getAdminRoutes({ customAdminRoutes, customRoutes })
} = getRoutes({ customAdminRoutes, customRoutes })
const adminURL = `${serverURL}${adminRoute}`
@@ -114,7 +115,7 @@ export async function firstRegister(args: FirstRegisterArgs): Promise<void> {
const {
routes: { admin: adminRoute },
} = getAdminRoutes({ customAdminRoutes, customRoutes })
} = getRoutes({ customAdminRoutes, customRoutes })
await page.goto(`${serverURL}${adminRoute}`)
await page.fill('#field-email', devUser.email)
@@ -130,27 +131,37 @@ export async function login(args: LoginArgs): Promise<void> {
const {
admin: {
routes: { createFirstUser: createFirstUserRoute, login: loginRoute },
routes: { createFirstUser, login: incomingLoginRoute },
},
routes: { admin: adminRoute },
} = getAdminRoutes({ customAdminRoutes, customRoutes })
routes: { admin: incomingAdminRoute },
} = getRoutes({ customAdminRoutes, customRoutes })
await page.goto(`${serverURL}${adminRoute}${loginRoute}`)
await page.waitForURL(`${serverURL}${adminRoute}${loginRoute}`)
const adminRoute = formatAdminURL({ serverURL, adminRoute: incomingAdminRoute, path: '' })
const loginRoute = formatAdminURL({
serverURL,
adminRoute: incomingAdminRoute,
path: incomingLoginRoute,
})
const createFirstUserRoute = formatAdminURL({
serverURL,
adminRoute: incomingAdminRoute,
path: createFirstUser,
})
await page.goto(loginRoute)
await page.waitForURL(loginRoute)
await wait(500)
await page.fill('#field-email', data.email)
await page.fill('#field-password', data.password)
await wait(500)
await page.click('[type=submit]')
await page.waitForURL(`${serverURL}${adminRoute}`)
await page.waitForURL(adminRoute)
await expect(() => expect(page.url()).not.toContain(`${adminRoute}${loginRoute}`)).toPass({
await expect(() => expect(page.url()).not.toContain(loginRoute)).toPass({
timeout: POLL_TOPASS_TIMEOUT,
})
await expect(() =>
expect(page.url()).not.toContain(`${adminRoute}${createFirstUserRoute}`),
).toPass({
await expect(() => expect(page.url()).not.toContain(createFirstUserRoute)).toPass({
timeout: POLL_TOPASS_TIMEOUT,
})
}
@@ -328,7 +339,7 @@ export function describeIfInCIOrHasLocalstack(): jest.Describe {
type AdminRoutes = Config['admin']['routes']
export function getAdminRoutes({
export function getRoutes({
customAdminRoutes,
customRoutes,
}: {

View File

@@ -11,8 +11,8 @@ const Hooks: CollectionConfig = {
},
hooks: {
beforeOperation: [
({ req }) => {
if (!req.transactionID) {
({ req, operation }) => {
if (!req.transactionID && ['create', 'delete', 'update'].includes(operation)) {
throw new Error('transactionID is missing in beforeOperation hook')
}
},

View File

@@ -68,14 +68,14 @@
"file-type": "17.1.6",
"http-status": "1.6.2",
"jwt-decode": "4.0.0",
"lexical": "0.15.0",
"lexical": "0.16.1",
"payload": "workspace:*",
"qs-esm": "7.0.2",
"server-only": "^0.0.1",
"slate": "0.91.4",
"tempy": "^1.0.1",
"ts-essentials": "7.0.3",
"typescript": "5.5.3",
"typescript": "5.5.4",
"uploadthing": "^6.10.1",
"uuid": "10.0.0"
},

View File

@@ -37,7 +37,7 @@
],
"paths": {
"@payload-config": [
"./test/admin/config.ts"
"./test/_community/config.ts"
],
"@payloadcms/live-preview": [
"./packages/live-preview/src"