diff --git a/docs/hooks/fields.mdx b/docs/hooks/fields.mdx index aec5d55aa..d00966ae3 100644 --- a/docs/hooks/fields.mdx +++ b/docs/hooks/fields.mdx @@ -62,7 +62,7 @@ All field-level hooks are formatted to accept the same arguments, although some Field Hooks receive one `args` argument that contains the following properties: | Option | Description | -| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **`data`** | The data passed to update the document within `create` and `update` operations, and the full document itself in the `afterRead` hook. | | **`siblingData`** | The sibling data passed to a field that the hook is running against. | | **`findMany`** | Boolean to denote if this hook is running against finding one, or finding many within the `afterRead` hook. | @@ -73,6 +73,10 @@ Field Hooks receive one `args` argument that contains the following properties: | **`req`** | The Express `request` object. It is mocked for Local API operations. | | **`value`** | The value of the field. | | **`previousValue`** | The previous value of the field, before changes were applied, only in `afterChange` hooks. | +| **`context`** | Context passed to this hook. More info can be found under [Context](/docs/hooks/context) | +| **`field`** | The field which the hook is running against. | +| **`collection`** | The collection which the field belongs to. If the field belongs to a global, this will be null. | +| **`global`** | The global which the field belongs to. If the field belongs to a collection, this will be null. | #### Return value diff --git a/packages/payload/src/admin/components/elements/DuplicateDocument/index.tsx b/packages/payload/src/admin/components/elements/DuplicateDocument/index.tsx index 36cf9ab9f..7291180aa 100644 --- a/packages/payload/src/admin/components/elements/DuplicateDocument/index.tsx +++ b/packages/payload/src/admin/components/elements/DuplicateDocument/index.tsx @@ -62,6 +62,7 @@ const Duplicate: React.FC = ({ id, collection, slug }) => { if (typeof collection.admin.hooks?.beforeDuplicate === 'function') { data = await collection.admin.hooks.beforeDuplicate({ + collection, data, locale, }) @@ -108,6 +109,7 @@ const Duplicate: React.FC = ({ id, collection, slug }) => { if (typeof collection.admin.hooks?.beforeDuplicate === 'function') { localizedDoc = await collection.admin.hooks.beforeDuplicate({ + collection, data: localizedDoc, locale, }) diff --git a/packages/payload/src/auth/operations/forgotPassword.ts b/packages/payload/src/auth/operations/forgotPassword.ts index 5e386be68..145657d83 100644 --- a/packages/payload/src/auth/operations/forgotPassword.ts +++ b/packages/payload/src/auth/operations/forgotPassword.ts @@ -38,6 +38,7 @@ async function forgotPassword(incomingArgs: Arguments): Promise { args = (await hook({ args, + collection: args.collection?.config, context: args.req.context, operation: 'forgotPassword', })) || args @@ -139,7 +140,7 @@ async function forgotPassword(incomingArgs: Arguments): Promise { await collectionConfig.hooks.afterForgotPassword.reduce(async (priorHook, hook) => { await priorHook - await hook({ args, context: req.context }) + await hook({ args, collection: args.collection?.config, context: req.context }) }, Promise.resolve()) // ///////////////////////////////////// @@ -148,6 +149,7 @@ async function forgotPassword(incomingArgs: Arguments): Promise { token = await buildAfterOperation({ args, + collection: args.collection?.config, operation: 'forgotPassword', result: token, }) diff --git a/packages/payload/src/auth/operations/local/forgotPassword.ts b/packages/payload/src/auth/operations/local/forgotPassword.ts index 1ce536b2a..450a7edb1 100644 --- a/packages/payload/src/auth/operations/local/forgotPassword.ts +++ b/packages/payload/src/auth/operations/local/forgotPassword.ts @@ -30,7 +30,7 @@ async function localForgotPassword( res, showHiddenFields, } = options - setRequestContext(options.req) + setRequestContext(req) const collection = payload.collections[collectionSlug] diff --git a/packages/payload/src/auth/operations/local/resetPassword.ts b/packages/payload/src/auth/operations/local/resetPassword.ts index 6ce6c5712..1f15c0aa5 100644 --- a/packages/payload/src/auth/operations/local/resetPassword.ts +++ b/packages/payload/src/auth/operations/local/resetPassword.ts @@ -24,7 +24,8 @@ async function localResetPassword options: Options, ): Promise { const { collection: collectionSlug, data, overrideAccess, req = {} as PayloadRequest } = options - setRequestContext(options.req) + + setRequestContext(req) const collection = payload.collections[collectionSlug] diff --git a/packages/payload/src/auth/operations/local/unlock.ts b/packages/payload/src/auth/operations/local/unlock.ts index 5072f431e..603e74efe 100644 --- a/packages/payload/src/auth/operations/local/unlock.ts +++ b/packages/payload/src/auth/operations/local/unlock.ts @@ -27,7 +27,7 @@ async function localUnlock( overrideAccess = true, req = {} as PayloadRequest, } = options - setRequestContext(options.req) + setRequestContext(req) const collection = payload.collections[collectionSlug] diff --git a/packages/payload/src/auth/operations/local/verifyEmail.ts b/packages/payload/src/auth/operations/local/verifyEmail.ts index 4975c8350..328e11570 100644 --- a/packages/payload/src/auth/operations/local/verifyEmail.ts +++ b/packages/payload/src/auth/operations/local/verifyEmail.ts @@ -18,7 +18,7 @@ async function localVerifyEmail( options: Options, ): Promise { const { collection: collectionSlug, req = {} as PayloadRequest, token } = options - setRequestContext(options.req) + setRequestContext(req) const collection = payload.collections[collectionSlug] diff --git a/packages/payload/src/auth/operations/login.ts b/packages/payload/src/auth/operations/login.ts index 84155e96d..33385d53b 100644 --- a/packages/payload/src/auth/operations/login.ts +++ b/packages/payload/src/auth/operations/login.ts @@ -54,6 +54,7 @@ async function login( args = (await hook({ args, + collection: args.collection?.config, context: args.req.context, operation: 'login', })) || args @@ -138,6 +139,7 @@ async function login( user = (await hook({ + collection: args.collection?.config, context: args.req.context, req: args.req, user, @@ -175,6 +177,7 @@ async function login( user = (await hook({ + collection: args.collection?.config, context: args.req.context, req: args.req, token, @@ -187,10 +190,11 @@ async function login( // ///////////////////////////////////// user = await afterRead({ + collection: collectionConfig, context: req.context, depth, doc: user, - entityConfig: collectionConfig, + global: null, overrideAccess, req, showHiddenFields, @@ -205,6 +209,7 @@ async function login( user = (await hook({ + collection: args.collection?.config, context: req.context, doc: user, req, @@ -220,6 +225,7 @@ async function login( user = (await hook({ + collection: args.collection?.config, context: req.context, doc: user, req, @@ -238,6 +244,7 @@ async function login( result = await buildAfterOperation({ args, + collection: args.collection?.config, operation: 'login', result, }) diff --git a/packages/payload/src/auth/operations/logout.ts b/packages/payload/src/auth/operations/logout.ts index 6d41ba44c..9d745f421 100644 --- a/packages/payload/src/auth/operations/logout.ts +++ b/packages/payload/src/auth/operations/logout.ts @@ -46,6 +46,7 @@ async function logout(incomingArgs: Arguments): Promise { args = (await hook({ + collection: args.collection?.config, context: req.context, req, res, diff --git a/packages/payload/src/auth/operations/me.ts b/packages/payload/src/auth/operations/me.ts index de4645062..bbfd5fae5 100644 --- a/packages/payload/src/auth/operations/me.ts +++ b/packages/payload/src/auth/operations/me.ts @@ -69,6 +69,7 @@ async function me({ collection, req }: Arguments): Promise { response = (await hook({ + collection: collection?.config, context: req.context, req, response, diff --git a/packages/payload/src/auth/operations/refresh.ts b/packages/payload/src/auth/operations/refresh.ts index d3a04328f..dda062f0e 100644 --- a/packages/payload/src/auth/operations/refresh.ts +++ b/packages/payload/src/auth/operations/refresh.ts @@ -39,6 +39,7 @@ async function refresh(incomingArgs: Arguments): Promise { args = (await hook({ args, + collection: args.collection?.config, context: args.req.context, operation: 'refresh', })) || args @@ -112,6 +113,7 @@ async function refresh(incomingArgs: Arguments): Promise { result = (await hook({ + collection: args.collection?.config, context: args.req.context, exp, req: args.req, @@ -126,6 +128,7 @@ async function refresh(incomingArgs: Arguments): Promise { result = await buildAfterOperation({ args, + collection: args.collection?.config, operation: 'refresh', result, }) diff --git a/packages/payload/src/collections/config/types.ts b/packages/payload/src/collections/config/types.ts index 8cb111bd0..39098adf1 100644 --- a/packages/payload/src/collections/config/types.ts +++ b/packages/payload/src/collections/config/types.ts @@ -41,6 +41,8 @@ type CreateOrUpdateOperation = Extract export type BeforeOperationHook = (args: { args?: any + /** The collection which this hook is being run on */ + collection: SanitizedCollectionConfig context: RequestContext /** * Hook operation being performed @@ -49,6 +51,8 @@ export type BeforeOperationHook = (args: { }) => any export type BeforeValidateHook = (args: { + /** The collection which this hook is being run on */ + collection: SanitizedCollectionConfig context: RequestContext data?: Partial /** @@ -65,6 +69,8 @@ export type BeforeValidateHook = (args: { }) => any export type BeforeChangeHook = (args: { + /** The collection which this hook is being run on */ + collection: SanitizedCollectionConfig context: RequestContext data: Partial /** @@ -81,6 +87,8 @@ export type BeforeChangeHook = (args: { }) => any export type AfterChangeHook = (args: { + /** The collection which this hook is being run on */ + collection: SanitizedCollectionConfig context: RequestContext doc: T /** @@ -92,6 +100,8 @@ export type AfterChangeHook = (args: { }) => any export type BeforeReadHook = (args: { + /** The collection which this hook is being run on */ + collection: SanitizedCollectionConfig context: RequestContext doc: T query: { [key: string]: any } @@ -99,6 +109,8 @@ export type BeforeReadHook = (args: { }) => any export type AfterReadHook = (args: { + /** The collection which this hook is being run on */ + collection: SanitizedCollectionConfig context: RequestContext doc: T findMany?: boolean @@ -107,12 +119,16 @@ export type AfterReadHook = (args: { }) => any export type BeforeDeleteHook = (args: { + /** The collection which this hook is being run on */ + collection: SanitizedCollectionConfig context: RequestContext id: number | string req: PayloadRequest }) => any export type AfterDeleteHook = (args: { + /** The collection which this hook is being run on */ + collection: SanitizedCollectionConfig context: RequestContext doc: T id: number | string @@ -127,15 +143,21 @@ export type AfterErrorHook = ( err: Error, res: unknown, context: RequestContext, + /** The collection which this hook is being run on. This is null if the AfterError hook was be added to the payload-wide config */ + collection: SanitizedCollectionConfig | null, ) => { response: any; status: number } | void export type BeforeLoginHook = (args: { + /** The collection which this hook is being run on */ + collection: SanitizedCollectionConfig context: RequestContext req: PayloadRequest user: T }) => any export type AfterLoginHook = (args: { + /** The collection which this hook is being run on */ + collection: SanitizedCollectionConfig context: RequestContext req: PayloadRequest token: string @@ -143,18 +165,24 @@ export type AfterLoginHook = (args: { }) => any export type AfterLogoutHook = (args: { + /** The collection which this hook is being run on */ + collection: SanitizedCollectionConfig context: RequestContext req: PayloadRequest res: Response }) => any export type AfterMeHook = (args: { + /** The collection which this hook is being run on */ + collection: SanitizedCollectionConfig context: RequestContext req: PayloadRequest response: unknown }) => any export type AfterRefreshHook = (args: { + /** The collection which this hook is being run on */ + collection: SanitizedCollectionConfig context: RequestContext exp: number req: PayloadRequest @@ -162,9 +190,16 @@ export type AfterRefreshHook = (args: { token: string }) => any -export type AfterForgotPasswordHook = (args: { args?: any; context: RequestContext }) => any +export type AfterForgotPasswordHook = (args: { + args?: any + /** The collection which this hook is being run on */ + collection: SanitizedCollectionConfig + context: RequestContext +}) => any type BeforeDuplicateArgs = { + /** The collection which this hook is being run on */ + collection: SanitizedCollectionConfig data: T locale?: string } diff --git a/packages/payload/src/collections/operations/create.ts b/packages/payload/src/collections/operations/create.ts index d9fbfd1a5..d2357a59f 100644 --- a/packages/payload/src/collections/operations/create.ts +++ b/packages/payload/src/collections/operations/create.ts @@ -65,6 +65,7 @@ async function create( args = (await hook({ args, + collection: args.collection.config, context: args.req.context, operation: 'create', })) || args @@ -139,10 +140,11 @@ async function create( // ///////////////////////////////////// data = await beforeValidate({ + collection: collectionConfig, context: req.context, data, doc: {}, - entityConfig: collectionConfig, + global: null, operation: 'create', overrideAccess, req, @@ -158,6 +160,7 @@ async function create( data = (await hook({ + collection: collectionConfig, context: req.context, data, operation: 'create', @@ -184,6 +187,7 @@ async function create( data = (await hook({ + collection: collectionConfig, context: req.context, data, operation: 'create', @@ -196,11 +200,12 @@ async function create( // ///////////////////////////////////// const resultWithLocales = await beforeChange>({ + collection: collectionConfig, context: req.context, data, doc: {}, docWithLocales: {}, - entityConfig: collectionConfig, + global: null, operation: 'create', req, skipValidation: shouldSaveDraft, @@ -293,10 +298,11 @@ async function create( // ///////////////////////////////////// result = await afterRead({ + collection: collectionConfig, context: req.context, depth, doc: result, - entityConfig: collectionConfig, + global: null, overrideAccess, req, showHiddenFields, @@ -311,6 +317,7 @@ async function create( result = (await hook({ + collection: collectionConfig, context: req.context, doc: result, req, @@ -322,10 +329,11 @@ async function create( // ///////////////////////////////////// result = await afterChange({ + collection: collectionConfig, context: req.context, data, doc: result, - entityConfig: collectionConfig, + global: null, operation: 'create', previousDoc: {}, req, @@ -353,6 +361,7 @@ async function create( result = (await hook({ + collection: collectionConfig, context: req.context, doc: result, operation: 'create', @@ -369,6 +378,7 @@ async function create( result = await buildAfterOperation({ args, + collection: collectionConfig, operation: 'create', result, }) diff --git a/packages/payload/src/collections/operations/delete.ts b/packages/payload/src/collections/operations/delete.ts index 68150980e..0db76a0a0 100644 --- a/packages/payload/src/collections/operations/delete.ts +++ b/packages/payload/src/collections/operations/delete.ts @@ -49,6 +49,7 @@ async function deleteOperation({ args, + collection: collectionConfig, operation: 'delete', result, }) diff --git a/packages/payload/src/collections/operations/deleteByID.ts b/packages/payload/src/collections/operations/deleteByID.ts index 2962ba384..e17d6b16b 100644 --- a/packages/payload/src/collections/operations/deleteByID.ts +++ b/packages/payload/src/collections/operations/deleteByID.ts @@ -40,6 +40,7 @@ async function deleteByID( args = (await hook({ args, + collection: args.collection.config, context: args.req.context, operation: 'delete', })) || args @@ -82,6 +83,7 @@ async function deleteByID( return hook({ id, + collection: collectionConfig, context: req.context, req, }) @@ -148,10 +150,11 @@ async function deleteByID( // ///////////////////////////////////// result = await afterRead({ + collection: collectionConfig, context: req.context, depth, doc: result, - entityConfig: collectionConfig, + global: null, overrideAccess, req, showHiddenFields, @@ -166,6 +169,7 @@ async function deleteByID( result = (await hook({ + collection: collectionConfig, context: req.context, doc: result, req, @@ -182,6 +186,7 @@ async function deleteByID( result = (await hook({ id, + collection: collectionConfig, context: req.context, doc: result, req, @@ -194,6 +199,7 @@ async function deleteByID( result = await buildAfterOperation({ args, + collection: collectionConfig, operation: 'deleteByID', result, }) diff --git a/packages/payload/src/collections/operations/find.ts b/packages/payload/src/collections/operations/find.ts index 23eb0e3ca..32aa6f3c7 100644 --- a/packages/payload/src/collections/operations/find.ts +++ b/packages/payload/src/collections/operations/find.ts @@ -46,6 +46,7 @@ async function find>( args = (await hook({ args, + collection: args.collection.config, context: args.req.context, operation: 'read', })) || args @@ -166,6 +167,7 @@ async function find>( docRef = (await hook({ + collection: collectionConfig, context: req.context, doc: docRef, query: fullWhere, @@ -187,12 +189,13 @@ async function find>( docs: await Promise.all( result.docs.map(async (doc) => afterRead({ + collection: collectionConfig, context: req.context, currentDepth, depth, doc, - entityConfig: collectionConfig, findMany: true, + global: null, overrideAccess, req, showHiddenFields, @@ -216,6 +219,7 @@ async function find>( docRef = (await hook({ + collection: collectionConfig, context: req.context, doc: docRef, findMany: true, @@ -235,6 +239,7 @@ async function find>( result = await buildAfterOperation({ args, + collection: collectionConfig, operation: 'find', result, }) diff --git a/packages/payload/src/collections/operations/findByID.ts b/packages/payload/src/collections/operations/findByID.ts index 6c0ce2ed5..1af101a1f 100644 --- a/packages/payload/src/collections/operations/findByID.ts +++ b/packages/payload/src/collections/operations/findByID.ts @@ -39,6 +39,7 @@ async function findByID(incomingArgs: Arguments): Promise< args = (await hook({ args, + collection: args.collection.config, context: args.req.context, operation: 'read', })) || args @@ -138,6 +139,7 @@ async function findByID(incomingArgs: Arguments): Promise< result = (await hook({ + collection: collectionConfig, context: req.context, doc: result, query: findOneArgs.where, @@ -150,11 +152,12 @@ async function findByID(incomingArgs: Arguments): Promise< // ///////////////////////////////////// result = await afterRead({ + collection: collectionConfig, context: req.context, currentDepth, depth, doc: result, - entityConfig: collectionConfig, + global: null, overrideAccess, req, showHiddenFields, @@ -169,6 +172,7 @@ async function findByID(incomingArgs: Arguments): Promise< result = (await hook({ + collection: collectionConfig, context: req.context, doc: result, query: findOneArgs.where, @@ -182,6 +186,7 @@ async function findByID(incomingArgs: Arguments): Promise< result = await buildAfterOperation({ args, + collection: collectionConfig, operation: 'findByID', result: result as any, }) // TODO: fix this typing diff --git a/packages/payload/src/collections/operations/findVersionByID.ts b/packages/payload/src/collections/operations/findVersionByID.ts index bce4f0082..b60ae7d18 100644 --- a/packages/payload/src/collections/operations/findVersionByID.ts +++ b/packages/payload/src/collections/operations/findVersionByID.ts @@ -93,6 +93,7 @@ async function findVersionByID( result.version = (await hook({ + collection: collectionConfig, context: req.context, doc: result.version, query: fullWhere, @@ -105,11 +106,12 @@ async function findVersionByID( // ///////////////////////////////////// result.version = await afterRead({ + collection: collectionConfig, context: req.context, currentDepth, depth, doc: result.version, - entityConfig: collectionConfig, + global: null, overrideAccess, req, showHiddenFields, @@ -124,6 +126,7 @@ async function findVersionByID( result.version = (await hook({ + collection: collectionConfig, context: req.context, doc: result.version, query: fullWhere, diff --git a/packages/payload/src/collections/operations/findVersions.ts b/packages/payload/src/collections/operations/findVersions.ts index e2d0ca628..10954cdd5 100644 --- a/packages/payload/src/collections/operations/findVersions.ts +++ b/packages/payload/src/collections/operations/findVersions.ts @@ -94,6 +94,7 @@ async function findVersions>( docRef.version = (await hook({ + collection: collectionConfig, context: req.context, doc: docRef.version, query: fullWhere, @@ -116,11 +117,12 @@ async function findVersions>( result.docs.map(async (data) => ({ ...data, version: await afterRead({ + collection: collectionConfig, context: req.context, depth, doc: data.version, - entityConfig: collectionConfig, findMany: true, + global: null, overrideAccess, req, showHiddenFields, @@ -144,6 +146,7 @@ async function findVersions>( docRef.version = (await hook({ + collection: collectionConfig, context: req.context, doc: doc.version, findMany: true, diff --git a/packages/payload/src/collections/operations/local/find.ts b/packages/payload/src/collections/operations/local/find.ts index d36548aa1..db2a25fa3 100644 --- a/packages/payload/src/collections/operations/local/find.ts +++ b/packages/payload/src/collections/operations/local/find.ts @@ -56,7 +56,7 @@ export default async function findLocal(args: Arguments): Prom // ///////////////////////////////////// result = await afterRead({ + collection: collectionConfig, context: req.context, depth, doc: result, - entityConfig: collectionConfig, + global: null, overrideAccess, req, showHiddenFields, @@ -153,6 +154,7 @@ async function restoreVersion(args: Arguments): Prom result = (await hook({ + collection: collectionConfig, context: req.context, doc: result, req, @@ -164,10 +166,11 @@ async function restoreVersion(args: Arguments): Prom // ///////////////////////////////////// result = await afterChange({ + collection: collectionConfig, context: req.context, data: result, doc: result, - entityConfig: collectionConfig, + global: null, operation: 'update', previousDoc: prevDocWithLocales, req, @@ -182,6 +185,7 @@ async function restoreVersion(args: Arguments): Prom result = (await hook({ + collection: collectionConfig, context: req.context, doc: result, operation: 'update', diff --git a/packages/payload/src/collections/operations/update.ts b/packages/payload/src/collections/operations/update.ts index c99c57150..460d3785e 100644 --- a/packages/payload/src/collections/operations/update.ts +++ b/packages/payload/src/collections/operations/update.ts @@ -56,6 +56,7 @@ async function update( args = (await hook({ args, + collection: args.collection.config, context: args.req.context, operation: 'update', })) || args @@ -169,10 +170,11 @@ async function update( try { const originalDoc = await afterRead({ + collection: collectionConfig, context: req.context, depth: 0, doc, - entityConfig: collectionConfig, + global: null, overrideAccess: true, req, showHiddenFields: true, @@ -193,10 +195,11 @@ async function update( data = await beforeValidate>({ id, + collection: collectionConfig, context: req.context, data, doc: originalDoc, - entityConfig: collectionConfig, + global: null, operation: 'update', overrideAccess, req, @@ -211,6 +214,7 @@ async function update( data = (await hook({ + collection: collectionConfig, context: req.context, data, operation: 'update', @@ -236,6 +240,7 @@ async function update( data = (await hook({ + collection: collectionConfig, context: req.context, data, operation: 'update', @@ -250,11 +255,12 @@ async function update( let result = await beforeChange({ id, + collection: collectionConfig, context: req.context, data, doc: originalDoc, docWithLocales: doc, - entityConfig: collectionConfig, + global: null, operation: 'update', req, skipValidation: shouldSaveDraft || data._status === 'draft', @@ -297,10 +303,11 @@ async function update( // ///////////////////////////////////// result = await afterRead({ + collection: collectionConfig, context: req.context, depth, doc: result, - entityConfig: collectionConfig, + global: null, overrideAccess, req, showHiddenFields, @@ -315,6 +322,7 @@ async function update( result = (await hook({ + collection: collectionConfig, context: req.context, doc: result, req, @@ -326,10 +334,11 @@ async function update( // ///////////////////////////////////// result = await afterChange({ + collection: collectionConfig, context: req.context, data, doc: result, - entityConfig: collectionConfig, + global: null, operation: 'update', previousDoc: originalDoc, req, @@ -344,6 +353,7 @@ async function update( result = (await hook({ + collection: collectionConfig, context: req.context, doc: result, operation: 'update', @@ -385,6 +395,7 @@ async function update( result = await buildAfterOperation({ args, + collection: collectionConfig, operation: 'update', result, }) diff --git a/packages/payload/src/collections/operations/updateByID.ts b/packages/payload/src/collections/operations/updateByID.ts index e3aeb69ee..3fe3346d1 100644 --- a/packages/payload/src/collections/operations/updateByID.ts +++ b/packages/payload/src/collections/operations/updateByID.ts @@ -55,6 +55,7 @@ async function updateByID( args = (await hook({ args, + collection: args.collection.config, context: args.req.context, operation: 'update', })) || args @@ -123,10 +124,11 @@ async function updateByID( if (!docWithLocales && hasWherePolicy) throw new Forbidden(t) const originalDoc = await afterRead({ + collection: collectionConfig, context: req.context, depth: 0, doc: docWithLocales, - entityConfig: collectionConfig, + global: null, overrideAccess: true, req, showHiddenFields: true, @@ -166,10 +168,11 @@ async function updateByID( data = await beforeValidate>({ id, + collection: collectionConfig, context: req.context, data, doc: originalDoc, - entityConfig: collectionConfig, + global: null, operation: 'update', overrideAccess, req, @@ -184,6 +187,7 @@ async function updateByID( data = (await hook({ + collection: collectionConfig, context: req.context, data, operation: 'update', @@ -209,6 +213,7 @@ async function updateByID( data = (await hook({ + collection: collectionConfig, context: req.context, data, operation: 'update', @@ -223,11 +228,12 @@ async function updateByID( let result = await beforeChange({ id, + collection: collectionConfig, context: req.context, data, doc: originalDoc, docWithLocales, - entityConfig: collectionConfig, + global: null, operation: 'update', req, skipValidation: shouldSaveDraft || data._status === 'draft', @@ -285,10 +291,11 @@ async function updateByID( // ///////////////////////////////////// result = await afterRead({ + collection: collectionConfig, context: req.context, depth, doc: result, - entityConfig: collectionConfig, + global: null, overrideAccess, req, showHiddenFields, @@ -303,6 +310,7 @@ async function updateByID( result = (await hook({ + collection: collectionConfig, context: req.context, doc: result, req, @@ -314,10 +322,11 @@ async function updateByID( // ///////////////////////////////////// result = await afterChange({ + collection: collectionConfig, context: req.context, data, doc: result, - entityConfig: collectionConfig, + global: null, operation: 'update', previousDoc: originalDoc, req, @@ -332,6 +341,7 @@ async function updateByID( result = (await hook({ + collection: collectionConfig, context: req.context, doc: result, operation: 'update', @@ -346,6 +356,7 @@ async function updateByID( result = await buildAfterOperation({ args, + collection: collectionConfig, operation: 'updateByID', result, }) diff --git a/packages/payload/src/collections/operations/utils.ts b/packages/payload/src/collections/operations/utils.ts index ab0c37816..1abda80a3 100644 --- a/packages/payload/src/collections/operations/utils.ts +++ b/packages/payload/src/collections/operations/utils.ts @@ -1,7 +1,7 @@ import type forgotPassword from '../../auth/operations/forgotPassword' import type login from '../../auth/operations/login' import type refresh from '../../auth/operations/refresh' -import type { AfterOperationHook, TypeWithID } from '../config/types' +import type { AfterOperationHook, SanitizedCollectionConfig, TypeWithID } from '../config/types' import type create from './create' import type deleteOperation from './delete' import type deleteByID from './deleteByID' @@ -25,51 +25,71 @@ export type AfterOperationMap = { export type AfterOperationArg = | { args: Parameters['create']>[0] + /** The collection which this hook is being run on */ + collection: SanitizedCollectionConfig operation: 'create' result: Awaited['create']>> } | { args: Parameters['delete']>[0] + /** The collection which this hook is being run on */ + collection: SanitizedCollectionConfig operation: 'delete' result: Awaited['delete']>> } | { args: Parameters['deleteByID']>[0] + /** The collection which this hook is being run on */ + collection: SanitizedCollectionConfig operation: 'deleteByID' result: Awaited['deleteByID']>> } | { args: Parameters['find']>[0] + /** The collection which this hook is being run on */ + collection: SanitizedCollectionConfig operation: 'find' result: Awaited['find']>> } | { args: Parameters['findByID']>[0] + /** The collection which this hook is being run on */ + collection: SanitizedCollectionConfig operation: 'findByID' result: Awaited['findByID']>> } | { args: Parameters['forgotPassword']>[0] + /** The collection which this hook is being run on */ + collection: SanitizedCollectionConfig operation: 'forgotPassword' result: Awaited['forgotPassword']>> } | { args: Parameters['login']>[0] + /** The collection which this hook is being run on */ + collection: SanitizedCollectionConfig operation: 'login' result: Awaited['login']>> } | { args: Parameters['refresh']>[0] + /** The collection which this hook is being run on */ + collection: SanitizedCollectionConfig operation: 'refresh' result: Awaited['refresh']>> } | { args: Parameters['update']>[0] + /** The collection which this hook is being run on */ + collection: SanitizedCollectionConfig operation: 'update' result: Awaited['update']>> } | { args: Parameters['updateByID']>[0] + /** The collection which this hook is being run on */ + collection: SanitizedCollectionConfig operation: 'updateByID' result: Awaited['updateByID']>> } @@ -82,7 +102,7 @@ export const buildAfterOperation = async < >( operationArgs: AfterOperationArg & { operation: O }, ): Promise[O]>>> => { - const { args, operation, result } = operationArgs + const { args, collection, operation, result } = operationArgs let newResult = result @@ -92,6 +112,7 @@ export const buildAfterOperation = async < const hookResult = await hook({ args, + collection, operation, result: newResult, } as AfterOperationArg) diff --git a/packages/payload/src/express/middleware/errorHandler.ts b/packages/payload/src/express/middleware/errorHandler.ts index 262197679..4790b79cb 100644 --- a/packages/payload/src/express/middleware/errorHandler.ts +++ b/packages/payload/src/express/middleware/errorHandler.ts @@ -47,11 +47,17 @@ const errorHandler = err, response, req.context, + req.collection.config, )) || { response, status }) } if (typeof config.hooks.afterError === 'function') { - ;({ response, status } = (await config.hooks.afterError(err, response, req.context)) || { + ;({ response, status } = (await config.hooks.afterError( + err, + response, + req.context, + req.collection.config, + )) || { response, status, }) diff --git a/packages/payload/src/fields/config/types.ts b/packages/payload/src/fields/config/types.ts index 15c9cf017..3dad19a97 100644 --- a/packages/payload/src/fields/config/types.ts +++ b/packages/payload/src/fields/config/types.ts @@ -9,18 +9,25 @@ import type { Description } from '../../admin/components/forms/FieldDescription/ import type { RowLabel } from '../../admin/components/forms/RowLabel/types' import type { RichTextAdapter } from '../../admin/components/forms/field-types/RichText/types' import type { User } from '../../auth' -import type { TypeWithID } from '../../collections/config/types' +import type { SanitizedCollectionConfig, TypeWithID } from '../../collections/config/types' import type { SanitizedConfig } from '../../config/types' import type { PayloadRequest, RequestContext } from '../../express/types' +import type { SanitizedGlobalConfig } from '../../globals/config/types' import type { Payload } from '../../payload' import type { Operation, Where } from '../../types' export type FieldHookArgs = { + /** The collection which the field belongs to. If the field belongs to a global, this will be null. */ + collection: SanitizedCollectionConfig | null context: RequestContext /** The data passed to update the document within create and update operations, and the full document itself in the afterRead hook. */ data?: Partial + /** The field which the hook is running against. */ + field: FieldAffectingData /** Boolean to denote if this hook is running against finding one, or finding many within the afterRead hook. */ findMany?: boolean + /** The global which the field belongs to. If the field belongs to a collection, this will be null. */ + global: SanitizedGlobalConfig | null /** A string relating to which operation the field type is currently executing within. Useful within beforeValidate, beforeChange, and afterChange hooks to differentiate between create and update operations. */ operation?: 'create' | 'delete' | 'read' | 'update' /** The full original document in `update` operations. In the `afterChange` hook, this is the resulting document of the operation. */ diff --git a/packages/payload/src/fields/hooks/afterChange/index.ts b/packages/payload/src/fields/hooks/afterChange/index.ts index 36b3ac519..90ec776c6 100644 --- a/packages/payload/src/fields/hooks/afterChange/index.ts +++ b/packages/payload/src/fields/hooks/afterChange/index.ts @@ -6,20 +6,23 @@ import { deepCopyObject } from '../../../utilities/deepCopyObject' import { traverseFields } from './traverseFields' type Args = { + collection: SanitizedCollectionConfig | null context: RequestContext data: Record | T doc: Record | T - entityConfig: SanitizedCollectionConfig | SanitizedGlobalConfig + global: SanitizedGlobalConfig | null operation: 'create' | 'update' previousDoc: Record | T req: PayloadRequest } export const afterChange = async >({ + collection, context, data, + doc: incomingDoc, - entityConfig, + global, operation, previousDoc, req, @@ -27,10 +30,12 @@ export const afterChange = async >({ const doc = deepCopyObject(incomingDoc) await traverseFields({ + collection, context, data, doc, - fields: entityConfig.fields, + fields: collection?.fields || global?.fields, + global, operation, previousDoc, previousSiblingDoc: previousDoc, diff --git a/packages/payload/src/fields/hooks/afterChange/promise.ts b/packages/payload/src/fields/hooks/afterChange/promise.ts index 2d483d0ba..70cb5e8dc 100644 --- a/packages/payload/src/fields/hooks/afterChange/promise.ts +++ b/packages/payload/src/fields/hooks/afterChange/promise.ts @@ -1,15 +1,19 @@ /* eslint-disable no-param-reassign */ +import type { SanitizedCollectionConfig } from '../../../collections/config/types' import type { PayloadRequest, RequestContext } from '../../../express/types' +import type { SanitizedGlobalConfig } from '../../../globals/config/types' import type { Field, TabAsField } from '../../config/types' import { fieldAffectsData, tabHasName } from '../../config/types' import { traverseFields } from './traverseFields' type Args = { + collection: SanitizedCollectionConfig | null context: RequestContext data: Record doc: Record field: Field | TabAsField + global: SanitizedGlobalConfig | null operation: 'create' | 'update' previousDoc: Record previousSiblingDoc: Record @@ -22,10 +26,12 @@ type Args = { // - Execute field hooks export const promise = async ({ + collection, context, data, doc, field, + global, operation, previousDoc, previousSiblingDoc, @@ -40,8 +46,11 @@ export const promise = async ({ await priorHook const hookedValue = await currentHook({ + collection, context, data, + field, + global, operation, originalDoc: doc, previousDoc, @@ -63,10 +72,12 @@ export const promise = async ({ switch (field.type) { case 'group': { await traverseFields({ + collection, context, data, doc, fields: field.fields, + global, operation, previousDoc, previousSiblingDoc: previousDoc[field.name] as Record, @@ -86,10 +97,12 @@ export const promise = async ({ rows.forEach((row, i) => { promises.push( traverseFields({ + collection, context, data, doc, fields: field.fields, + global, operation, previousDoc, previousSiblingDoc: previousDoc?.[field.name]?.[i] || ({} as Record), @@ -115,10 +128,12 @@ export const promise = async ({ if (block) { promises.push( traverseFields({ + collection, context, data, doc, fields: block.fields, + global, operation, previousDoc, previousSiblingDoc: @@ -139,10 +154,12 @@ export const promise = async ({ case 'row': case 'collapsible': { await traverseFields({ + collection, context, data, doc, fields: field.fields, + global, operation, previousDoc, previousSiblingDoc: { ...previousSiblingDoc }, @@ -166,10 +183,12 @@ export const promise = async ({ } await traverseFields({ + collection, context, data, doc, fields: field.fields, + global, operation, previousDoc, previousSiblingDoc: tabPreviousSiblingDoc, @@ -183,10 +202,12 @@ export const promise = async ({ case 'tabs': { await traverseFields({ + collection, context, data, doc, fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })), + global, operation, previousDoc, previousSiblingDoc: { ...previousSiblingDoc }, diff --git a/packages/payload/src/fields/hooks/afterChange/traverseFields.ts b/packages/payload/src/fields/hooks/afterChange/traverseFields.ts index 37392f7e0..d3e019785 100644 --- a/packages/payload/src/fields/hooks/afterChange/traverseFields.ts +++ b/packages/payload/src/fields/hooks/afterChange/traverseFields.ts @@ -1,13 +1,17 @@ +import type { SanitizedCollectionConfig } from '../../../collections/config/types' import type { PayloadRequest, RequestContext } from '../../../express/types' +import type { SanitizedGlobalConfig } from '../../../globals/config/types' import type { Field, TabAsField } from '../../config/types' import { promise } from './promise' type Args = { + collection: SanitizedCollectionConfig | null context: RequestContext data: Record doc: Record fields: (Field | TabAsField)[] + global: SanitizedGlobalConfig | null operation: 'create' | 'update' previousDoc: Record previousSiblingDoc: Record @@ -17,10 +21,12 @@ type Args = { } export const traverseFields = async ({ + collection, context, data, doc, fields, + global, operation, previousDoc, previousSiblingDoc, @@ -33,10 +39,12 @@ export const traverseFields = async ({ fields.forEach((field) => { promises.push( promise({ + collection, context, data, doc, field, + global, operation, previousDoc, previousSiblingDoc, diff --git a/packages/payload/src/fields/hooks/afterRead/index.ts b/packages/payload/src/fields/hooks/afterRead/index.ts index fa2a1ad5b..594475f66 100644 --- a/packages/payload/src/fields/hooks/afterRead/index.ts +++ b/packages/payload/src/fields/hooks/afterRead/index.ts @@ -6,13 +6,14 @@ import { deepCopyObject } from '../../../utilities/deepCopyObject' import { traverseFields } from './traverseFields' type Args = { + collection: SanitizedCollectionConfig | null context: RequestContext currentDepth?: number depth: number doc: Record - entityConfig: SanitizedCollectionConfig | SanitizedGlobalConfig findMany?: boolean flattenLocales?: boolean + global: SanitizedGlobalConfig | null overrideAccess: boolean req: PayloadRequest showHiddenFields: boolean @@ -20,13 +21,14 @@ type Args = { export async function afterRead(args: Args): Promise { const { + collection, context, currentDepth: incomingCurrentDepth, depth: incomingDepth, doc: incomingDoc, - entityConfig, findMany, flattenLocales = true, + global, overrideAccess, req, showHiddenFields, @@ -45,14 +47,16 @@ export async function afterRead(args: Args): Promise { const currentDepth = incomingCurrentDepth || 1 traverseFields({ + collection, context, currentDepth, depth, doc, fieldPromises, - fields: entityConfig.fields, + fields: collection?.fields || global?.fields, findMany, flattenLocales, + global, overrideAccess, populationPromises, req, diff --git a/packages/payload/src/fields/hooks/afterRead/promise.ts b/packages/payload/src/fields/hooks/afterRead/promise.ts index 90def94a9..5a813a3cb 100644 --- a/packages/payload/src/fields/hooks/afterRead/promise.ts +++ b/packages/payload/src/fields/hooks/afterRead/promise.ts @@ -1,6 +1,8 @@ /* eslint-disable no-param-reassign */ import type { RichTextAdapter } from '../../../admin/components/forms/field-types/RichText/types' +import type { SanitizedCollectionConfig } from '../../../collections/config/types' import type { PayloadRequest, RequestContext } from '../../../express/types' +import type { SanitizedGlobalConfig } from '../../../globals/config/types' import type { Field, TabAsField } from '../../config/types' import { fieldAffectsData, tabHasName } from '../../config/types' @@ -8,6 +10,7 @@ import relationshipPopulationPromise from './relationshipPopulationPromise' import { traverseFields } from './traverseFields' type Args = { + collection: SanitizedCollectionConfig | null context: RequestContext currentDepth: number depth: number @@ -16,6 +19,7 @@ type Args = { fieldPromises: Promise[] findMany: boolean flattenLocales: boolean + global: SanitizedGlobalConfig | null overrideAccess: boolean populationPromises: Promise[] req: PayloadRequest @@ -32,6 +36,7 @@ type Args = { // - Populate relationships export const promise = async ({ + collection, context, currentDepth, depth, @@ -40,6 +45,7 @@ export const promise = async ({ fieldPromises, findMany, flattenLocales, + global, overrideAccess, populationPromises, req, @@ -179,8 +185,11 @@ export const promise = async ({ const hookPromises = Object.entries(siblingDoc[field.name]).map(([locale, value]) => (async () => { const hookedValue = await currentHook({ + collection, context, data: doc, + field, + global, operation: 'read', originalDoc: doc, req, @@ -197,9 +206,12 @@ export const promise = async ({ await Promise.all(hookPromises) } else { const hookedValue = await currentHook({ + collection, context, data: doc, + field, findMany, + global, operation: 'read', originalDoc: doc, req, @@ -252,6 +264,7 @@ export const promise = async ({ if (typeof siblingDoc[field.name] !== 'object') groupDoc = {} traverseFields({ + collection, context, currentDepth, depth, @@ -260,6 +273,7 @@ export const promise = async ({ fields: field.fields, findMany, flattenLocales, + global, overrideAccess, populationPromises, req, @@ -276,6 +290,7 @@ export const promise = async ({ if (Array.isArray(rows)) { rows.forEach((row) => { traverseFields({ + collection, context, currentDepth, depth, @@ -284,6 +299,7 @@ export const promise = async ({ fields: field.fields, findMany, flattenLocales, + global, overrideAccess, populationPromises, req, @@ -296,6 +312,7 @@ export const promise = async ({ if (Array.isArray(localeRows)) { localeRows.forEach((row) => { traverseFields({ + collection, context, currentDepth, depth, @@ -304,6 +321,7 @@ export const promise = async ({ fields: field.fields, findMany, flattenLocales, + global, overrideAccess, populationPromises, req, @@ -328,6 +346,7 @@ export const promise = async ({ if (block) { traverseFields({ + collection, context, currentDepth, depth, @@ -336,6 +355,7 @@ export const promise = async ({ fields: block.fields, findMany, flattenLocales, + global, overrideAccess, populationPromises, req, @@ -352,6 +372,7 @@ export const promise = async ({ if (block) { traverseFields({ + collection, context, currentDepth, depth, @@ -360,6 +381,7 @@ export const promise = async ({ fields: block.fields, findMany, flattenLocales, + global, overrideAccess, populationPromises, req, @@ -380,6 +402,7 @@ export const promise = async ({ case 'row': case 'collapsible': { traverseFields({ + collection, context, currentDepth, depth, @@ -388,6 +411,7 @@ export const promise = async ({ fields: field.fields, findMany, flattenLocales, + global, overrideAccess, populationPromises, req, @@ -406,6 +430,7 @@ export const promise = async ({ } await traverseFields({ + collection, context, currentDepth, depth, @@ -414,6 +439,7 @@ export const promise = async ({ fields: field.fields, findMany, flattenLocales, + global, overrideAccess, populationPromises, req, @@ -426,6 +452,7 @@ export const promise = async ({ case 'tabs': { traverseFields({ + collection, context, currentDepth, depth, @@ -434,6 +461,7 @@ export const promise = async ({ fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })), findMany, flattenLocales, + global, overrideAccess, populationPromises, req, diff --git a/packages/payload/src/fields/hooks/afterRead/traverseFields.ts b/packages/payload/src/fields/hooks/afterRead/traverseFields.ts index 8b7d5f0ad..638ebcf69 100644 --- a/packages/payload/src/fields/hooks/afterRead/traverseFields.ts +++ b/packages/payload/src/fields/hooks/afterRead/traverseFields.ts @@ -1,9 +1,12 @@ +import type { SanitizedCollectionConfig } from '../../../collections/config/types' import type { PayloadRequest, RequestContext } from '../../../express/types' +import type { SanitizedGlobalConfig } from '../../../globals/config/types' import type { Field, TabAsField } from '../../config/types' import { promise } from './promise' type Args = { + collection: SanitizedCollectionConfig | null context: RequestContext currentDepth: number depth: number @@ -12,6 +15,7 @@ type Args = { fields: (Field | TabAsField)[] findMany: boolean flattenLocales: boolean + global: SanitizedGlobalConfig | null overrideAccess: boolean populationPromises: Promise[] req: PayloadRequest @@ -20,6 +24,7 @@ type Args = { } export const traverseFields = ({ + collection, context, currentDepth, depth, @@ -28,6 +33,7 @@ export const traverseFields = ({ fields, findMany, flattenLocales, + global, overrideAccess, populationPromises, req, @@ -37,6 +43,7 @@ export const traverseFields = ({ fields.forEach((field) => { fieldPromises.push( promise({ + collection, context, currentDepth, depth, @@ -45,6 +52,7 @@ export const traverseFields = ({ fieldPromises, findMany, flattenLocales, + global, overrideAccess, populationPromises, req, diff --git a/packages/payload/src/fields/hooks/beforeChange/index.ts b/packages/payload/src/fields/hooks/beforeChange/index.ts index 1054ba740..c5ba434dd 100644 --- a/packages/payload/src/fields/hooks/beforeChange/index.ts +++ b/packages/payload/src/fields/hooks/beforeChange/index.ts @@ -8,11 +8,12 @@ import { deepCopyObject } from '../../../utilities/deepCopyObject' import { traverseFields } from './traverseFields' type Args = { + collection: SanitizedCollectionConfig | null context: RequestContext data: Record | T doc: Record | T docWithLocales: Record - entityConfig: SanitizedCollectionConfig | SanitizedGlobalConfig + global: SanitizedGlobalConfig | null id?: number | string operation: Operation req: PayloadRequest @@ -21,11 +22,12 @@ type Args = { export const beforeChange = async >({ id, + collection, context, data: incomingData, doc, docWithLocales, - entityConfig, + global, operation, req, skipValidation, @@ -36,12 +38,14 @@ export const beforeChange = async >({ await traverseFields({ id, + collection, context, data, doc, docWithLocales, errors, - fields: entityConfig.fields, + fields: collection?.fields || global?.fields, + global, mergeLocaleActions, operation, path: '', diff --git a/packages/payload/src/fields/hooks/beforeChange/promise.ts b/packages/payload/src/fields/hooks/beforeChange/promise.ts index 29cadda60..0272592f1 100644 --- a/packages/payload/src/fields/hooks/beforeChange/promise.ts +++ b/packages/payload/src/fields/hooks/beforeChange/promise.ts @@ -1,7 +1,9 @@ /* eslint-disable no-param-reassign */ import merge from 'deepmerge' +import type { SanitizedCollectionConfig } from '../../../collections/config/types' import type { PayloadRequest, RequestContext } from '../../../express/types' +import type { SanitizedGlobalConfig } from '../../../globals/config/types' import type { Operation } from '../../../types' import type { Field, TabAsField } from '../../config/types' @@ -10,12 +12,14 @@ import { getExistingRowDoc } from './getExistingRowDoc' import { traverseFields } from './traverseFields' type Args = { + collection: SanitizedCollectionConfig | null context: RequestContext data: Record doc: Record docWithLocales: Record errors: { field: string; message: string }[] field: Field | TabAsField + global: SanitizedGlobalConfig | null id?: number | string mergeLocaleActions: (() => void)[] operation: Operation @@ -36,12 +40,14 @@ type Args = { export const promise = async ({ id, + collection, context, data, doc, docWithLocales, errors, field, + global, mergeLocaleActions, operation, path, @@ -75,8 +81,11 @@ export const promise = async ({ await priorHook const hookedValue = await currentHook({ + collection, context, data, + field, + global, operation, originalDoc: doc, req, @@ -183,12 +192,14 @@ export const promise = async ({ await traverseFields({ id, + collection, context, data, doc, docWithLocales, errors, fields: field.fields, + global, mergeLocaleActions, operation, path: `${path}${field.name}.`, @@ -211,12 +222,14 @@ export const promise = async ({ promises.push( traverseFields({ id, + collection, context, data, doc, docWithLocales, errors, fields: field.fields, + global, mergeLocaleActions, operation, path: `${path}${field.name}.${i}.`, @@ -251,12 +264,14 @@ export const promise = async ({ promises.push( traverseFields({ id, + collection, context, data, doc, docWithLocales, errors, fields: block.fields, + global, mergeLocaleActions, operation, path: `${path}${field.name}.${i}.`, @@ -280,12 +295,14 @@ export const promise = async ({ case 'collapsible': { await traverseFields({ id, + collection, context, data, doc, docWithLocales, errors, fields: field.fields, + global, mergeLocaleActions, operation, path, @@ -319,12 +336,14 @@ export const promise = async ({ await traverseFields({ id, + collection, context, data, doc, docWithLocales, errors, fields: field.fields, + global, mergeLocaleActions, operation, path: tabPath, @@ -341,12 +360,14 @@ export const promise = async ({ case 'tabs': { await traverseFields({ id, + collection, context, data, doc, docWithLocales, errors, fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })), + global, mergeLocaleActions, operation, path, diff --git a/packages/payload/src/fields/hooks/beforeChange/traverseFields.ts b/packages/payload/src/fields/hooks/beforeChange/traverseFields.ts index dab77379f..faabb3b37 100644 --- a/packages/payload/src/fields/hooks/beforeChange/traverseFields.ts +++ b/packages/payload/src/fields/hooks/beforeChange/traverseFields.ts @@ -1,16 +1,20 @@ +import type { SanitizedCollectionConfig } from '../../../collections/config/types' import type { PayloadRequest, RequestContext } from '../../../express/types' +import type { SanitizedGlobalConfig } from '../../../globals/config/types' import type { Operation } from '../../../types' import type { Field, TabAsField } from '../../config/types' import { promise } from './promise' type Args = { + collection: SanitizedCollectionConfig | null context: RequestContext data: Record doc: Record docWithLocales: Record errors: { field: string; message: string }[] fields: (Field | TabAsField)[] + global: SanitizedGlobalConfig | null id?: number | string mergeLocaleActions: (() => void)[] operation: Operation @@ -24,12 +28,14 @@ type Args = { export const traverseFields = async ({ id, + collection, context, data, doc, docWithLocales, errors, fields, + global, mergeLocaleActions, operation, path, @@ -45,12 +51,14 @@ export const traverseFields = async ({ promises.push( promise({ id, + collection, context, data, doc, docWithLocales, errors, field, + global, mergeLocaleActions, operation, path, diff --git a/packages/payload/src/fields/hooks/beforeValidate/index.ts b/packages/payload/src/fields/hooks/beforeValidate/index.ts index 057a80f69..24c97a00f 100644 --- a/packages/payload/src/fields/hooks/beforeValidate/index.ts +++ b/packages/payload/src/fields/hooks/beforeValidate/index.ts @@ -6,10 +6,11 @@ import { deepCopyObject } from '../../../utilities/deepCopyObject' import { traverseFields } from './traverseFields' type Args = { + collection: SanitizedCollectionConfig | null context: RequestContext data: Record | T doc?: Record | T - entityConfig: SanitizedCollectionConfig | SanitizedGlobalConfig + global: SanitizedGlobalConfig | null id?: number | string operation: 'create' | 'update' overrideAccess: boolean @@ -18,10 +19,11 @@ type Args = { export const beforeValidate = async >({ id, + collection, context, data: incomingData, doc, - entityConfig, + global, operation, overrideAccess, req, @@ -30,10 +32,12 @@ export const beforeValidate = async >({ await traverseFields({ id, + collection, context, data, doc, - fields: entityConfig.fields, + fields: collection?.fields || global?.fields, + global, operation, overrideAccess, req, diff --git a/packages/payload/src/fields/hooks/beforeValidate/promise.ts b/packages/payload/src/fields/hooks/beforeValidate/promise.ts index 9a12c2b0b..557064e90 100644 --- a/packages/payload/src/fields/hooks/beforeValidate/promise.ts +++ b/packages/payload/src/fields/hooks/beforeValidate/promise.ts @@ -1,5 +1,7 @@ /* eslint-disable no-param-reassign */ +import type { SanitizedCollectionConfig } from '../../../collections/config/types' import type { PayloadRequest, RequestContext } from '../../../express/types' +import type { SanitizedGlobalConfig } from '../../../globals/config/types' import type { Field, TabAsField } from '../../config/types' import { fieldAffectsData, tabHasName, valueIsValueWithRelation } from '../../config/types' @@ -9,10 +11,12 @@ import { getExistingRowDoc } from '../beforeChange/getExistingRowDoc' import { traverseFields } from './traverseFields' type Args = { + collection: SanitizedCollectionConfig | null context: RequestContext data: T doc: T field: Field | TabAsField + global: SanitizedGlobalConfig | null id?: number | string operation: 'create' | 'update' overrideAccess: boolean @@ -30,10 +34,12 @@ type Args = { export const promise = async ({ id, + collection, context, data, doc, field, + global, operation, overrideAccess, req, @@ -209,8 +215,11 @@ export const promise = async ({ await priorHook const hookedValue = await currentHook({ + collection, context, data, + field, + global, operation, originalDoc: doc, req, @@ -263,10 +272,12 @@ export const promise = async ({ await traverseFields({ id, + collection, context, data, doc, fields: field.fields, + global, operation, overrideAccess, req, @@ -282,14 +293,16 @@ export const promise = async ({ if (Array.isArray(rows)) { const promises = [] - rows.forEach((row, i) => { + rows.forEach((row) => { promises.push( traverseFields({ id, + collection, context, data, doc, fields: field.fields, + global, operation, overrideAccess, req, @@ -308,7 +321,7 @@ export const promise = async ({ if (Array.isArray(rows)) { const promises = [] - rows.forEach((row, i) => { + rows.forEach((row) => { const rowSiblingDoc = getExistingRowDoc(row, siblingDoc[field.name]) const blockTypeToMatch = row.blockType || rowSiblingDoc.blockType const block = field.blocks.find((blockType) => blockType.slug === blockTypeToMatch) @@ -319,10 +332,12 @@ export const promise = async ({ promises.push( traverseFields({ id, + collection, context, data, doc, fields: block.fields, + global, operation, overrideAccess, req, @@ -342,10 +357,12 @@ export const promise = async ({ case 'collapsible': { await traverseFields({ id, + collection, context, data, doc, fields: field.fields, + global, operation, overrideAccess, req, @@ -372,10 +389,12 @@ export const promise = async ({ await traverseFields({ id, + collection, context, data, doc, fields: field.fields, + global, operation, overrideAccess, req, @@ -389,10 +408,12 @@ export const promise = async ({ case 'tabs': { await traverseFields({ id, + collection, context, data, doc, fields: field.tabs.map((tab) => ({ ...tab, type: 'tab' })), + global, operation, overrideAccess, req, diff --git a/packages/payload/src/fields/hooks/beforeValidate/traverseFields.ts b/packages/payload/src/fields/hooks/beforeValidate/traverseFields.ts index ffd2b8cdf..1e72bf9c1 100644 --- a/packages/payload/src/fields/hooks/beforeValidate/traverseFields.ts +++ b/packages/payload/src/fields/hooks/beforeValidate/traverseFields.ts @@ -1,13 +1,17 @@ +import type { SanitizedCollectionConfig } from '../../../collections/config/types' import type { PayloadRequest, RequestContext } from '../../../express/types' +import type { SanitizedGlobalConfig } from '../../../globals/config/types' import type { Field, TabAsField } from '../../config/types' import { promise } from './promise' type Args = { + collection: SanitizedCollectionConfig | null context: RequestContext data: T doc: T fields: (Field | TabAsField)[] + global: SanitizedGlobalConfig | null id?: number | string operation: 'create' | 'update' overrideAccess: boolean @@ -18,10 +22,12 @@ type Args = { export const traverseFields = async ({ id, + collection, context, data, doc, fields, + global, operation, overrideAccess, req, @@ -33,10 +39,12 @@ export const traverseFields = async ({ promises.push( promise({ id, + collection, context, data, doc, field, + global, operation, overrideAccess, req, diff --git a/packages/payload/src/globals/config/types.ts b/packages/payload/src/globals/config/types.ts index c22cda64b..0ba621d27 100644 --- a/packages/payload/src/globals/config/types.ts +++ b/packages/payload/src/globals/config/types.ts @@ -18,6 +18,7 @@ import type { LivePreviewConfig, } from '../../config/types' import type { PayloadRequest } from '../../express/types' +import type { RequestContext } from '../../express/types' import type { Field } from '../../fields/config/types' import type { Where } from '../../types' import type { IncomingGlobalVersions, SanitizedGlobalVersions } from '../../versions/types' @@ -27,20 +28,46 @@ export type TypeWithID = { } export type BeforeValidateHook = (args: { + context: RequestContext data?: any + /** The global which this hook is being run on */ + global: SanitizedGlobalConfig originalDoc?: any req?: PayloadRequest }) => any -export type BeforeChangeHook = (args: { data: any; originalDoc?: any; req: PayloadRequest }) => any +export type BeforeChangeHook = (args: { + context: RequestContext + data: any + /** The global which this hook is being run on */ + global: SanitizedGlobalConfig + originalDoc?: any + req: PayloadRequest +}) => any -export type AfterChangeHook = (args: { doc: any; previousDoc: any; req: PayloadRequest }) => any +export type AfterChangeHook = (args: { + context: RequestContext + doc: any + /** The global which this hook is being run on */ + global: SanitizedGlobalConfig + previousDoc: any + req: PayloadRequest +}) => any -export type BeforeReadHook = (args: { doc: any; req: PayloadRequest }) => any +export type BeforeReadHook = (args: { + context: RequestContext + doc: any + /** The global which this hook is being run on */ + global: SanitizedGlobalConfig + req: PayloadRequest +}) => any export type AfterReadHook = (args: { + context: RequestContext doc: any findMany?: boolean + /** The global which this hook is being run on */ + global: SanitizedGlobalConfig query?: Where req: PayloadRequest }) => any diff --git a/packages/payload/src/globals/operations/findOne.ts b/packages/payload/src/globals/operations/findOne.ts index 4039097c9..32fe8a87e 100644 --- a/packages/payload/src/globals/operations/findOne.ts +++ b/packages/payload/src/globals/operations/findOne.ts @@ -83,7 +83,9 @@ async function findOne>(args: Args): Promise>(args: Args): Promise>(args: Args): Promise = any>(args: Argumen result = (await hook({ + context: req.context, doc: result.version, + global: globalConfig, req, })) || result.version }, Promise.resolve()) @@ -97,11 +99,12 @@ async function findVersionByID = any>(args: Argumen // ///////////////////////////////////// result.version = await afterRead({ + collection: null, context: req.context, currentDepth, depth, doc: result.version, - entityConfig: globalConfig, + global: globalConfig, overrideAccess, req, showHiddenFields, @@ -116,7 +119,9 @@ async function findVersionByID = any>(args: Argumen result.version = (await hook({ + context: req.context, doc: result.version, + global: globalConfig, query: findGlobalVersionsArgs.where, req, })) || result.version diff --git a/packages/payload/src/globals/operations/findVersions.ts b/packages/payload/src/globals/operations/findVersions.ts index e61dee530..e8d69d9a0 100644 --- a/packages/payload/src/globals/operations/findVersions.ts +++ b/packages/payload/src/globals/operations/findVersions.ts @@ -88,11 +88,12 @@ async function findVersions>( paginatedDocs.docs.map(async (data) => ({ ...data, version: await afterRead({ + collection: null, context: req.context, depth, doc: data.version, - entityConfig: globalConfig, findMany: true, + global: globalConfig, overrideAccess, req, showHiddenFields, @@ -115,8 +116,14 @@ async function findVersions>( await priorHook docRef.version = - (await hook({ doc: doc.version, findMany: true, query: fullWhere, req })) || - doc.version + (await hook({ + context: req.context, + doc: doc.version, + findMany: true, + global: globalConfig, + query: fullWhere, + req, + })) || doc.version }, Promise.resolve()) return docRef diff --git a/packages/payload/src/globals/operations/restoreVersion.ts b/packages/payload/src/globals/operations/restoreVersion.ts index 9b257e1e5..4794f340a 100644 --- a/packages/payload/src/globals/operations/restoreVersion.ts +++ b/packages/payload/src/globals/operations/restoreVersion.ts @@ -97,10 +97,11 @@ async function restoreVersion = any>(args: Argument // ///////////////////////////////////// result = await afterRead({ + collection: null, context: req.context, depth, doc: result, - entityConfig: globalConfig, + global: globalConfig, overrideAccess, req, showHiddenFields, @@ -115,7 +116,9 @@ async function restoreVersion = any>(args: Argument result = (await hook({ + context: req.context, doc: result, + global: globalConfig, req, })) || result }, Promise.resolve()) @@ -125,10 +128,11 @@ async function restoreVersion = any>(args: Argument // ///////////////////////////////////// result = await afterChange({ + collection: null, context: req.context, data: result, doc: result, - entityConfig: globalConfig, + global: globalConfig, operation: 'update', previousDoc, req, @@ -143,7 +147,9 @@ async function restoreVersion = any>(args: Argument result = (await hook({ + context: req.context, doc: result, + global: globalConfig, previousDoc, req, })) || result diff --git a/packages/payload/src/globals/operations/update.ts b/packages/payload/src/globals/operations/update.ts index eb0a28309..ddb5dca1e 100644 --- a/packages/payload/src/globals/operations/update.ts +++ b/packages/payload/src/globals/operations/update.ts @@ -92,10 +92,11 @@ async function update( } const originalDoc = await afterRead({ + collection: null, context: req.context, depth: 0, doc: globalJSON, - entityConfig: globalConfig, + global: globalConfig, overrideAccess: true, req, showHiddenFields, @@ -106,10 +107,11 @@ async function update( // ///////////////////////////////////// data = await beforeValidate({ + collection: null, context: req.context, data, doc: originalDoc, - entityConfig: globalConfig, + global: globalConfig, operation: 'update', overrideAccess, req, @@ -124,7 +126,9 @@ async function update( data = (await hook({ + context: req.context, data, + global: globalConfig, originalDoc, req, })) || data @@ -139,7 +143,9 @@ async function update( data = (await hook({ + context: req.context, data, + global: globalConfig, originalDoc, req, })) || data @@ -150,11 +156,12 @@ async function update( // ///////////////////////////////////// let result = await beforeChange({ + collection: null, context: req.context, data, doc: originalDoc, docWithLocales: globalJSON, - entityConfig: globalConfig, + global: globalConfig, operation: 'update', req, skipValidation: shouldSaveDraft, @@ -204,10 +211,11 @@ async function update( // ///////////////////////////////////// result = await afterRead({ + collection: null, context: req.context, depth, doc: result, - entityConfig: globalConfig, + global: globalConfig, overrideAccess, req, showHiddenFields, @@ -222,7 +230,9 @@ async function update( result = (await hook({ + context: req.context, doc: result, + global: globalConfig, req, })) || result }, Promise.resolve()) @@ -232,10 +242,11 @@ async function update( // ///////////////////////////////////// result = await afterChange({ + collection: null, context: req.context, data, doc: result, - entityConfig: globalConfig, + global: globalConfig, operation: 'update', previousDoc: originalDoc, req, @@ -250,7 +261,9 @@ async function update( result = (await hook({ + context: req.context, doc: result, + global: globalConfig, previousDoc: originalDoc, req, })) || result diff --git a/packages/payload/src/graphql/errorHandler.ts b/packages/payload/src/graphql/errorHandler.ts index ef7f1f25d..602e1eb72 100644 --- a/packages/payload/src/graphql/errorHandler.ts +++ b/packages/payload/src/graphql/errorHandler.ts @@ -35,7 +35,7 @@ const errorHandler = async ( } if (afterErrorHook) { - ;({ response } = (await afterErrorHook(err, response, null)) || { response }) + ;({ response } = (await afterErrorHook(err, response, null, null)) || { response }) } return response diff --git a/test/hooks/collections/Data/index.ts b/test/hooks/collections/Data/index.ts new file mode 100644 index 000000000..e269fdc32 --- /dev/null +++ b/test/hooks/collections/Data/index.ts @@ -0,0 +1,111 @@ +/* eslint-disable no-param-reassign */ + +import type { CollectionConfig } from '../../../../packages/payload/src/collections/config/types' + +export const dataHooksSlug = 'data-hooks' + +export const DataHooks: CollectionConfig = { + slug: dataHooksSlug, + access: { + read: () => true, + create: () => true, + delete: () => true, + update: () => true, + }, + hooks: { + beforeOperation: [ + async ({ context, collection, args }) => { + context['collection_beforeOperation_collection'] = JSON.stringify(collection) + + return args + }, + ], + + beforeChange: [ + ({ context, data, collection }) => { + context['collection_beforeChange_collection'] = JSON.stringify(collection) + + return data + }, + ], + afterChange: [ + async ({ context, collection }) => { + context['collection_afterChange_collection'] = JSON.stringify(collection) + }, + ], + beforeRead: [ + async ({ context, collection }) => { + context['collection_beforeRead_collection'] = JSON.stringify(collection) + }, + ], + afterRead: [ + ({ context, collection, doc }) => { + context['collection_afterRead_collection'] = JSON.stringify(collection) + + return doc + }, + ], + afterOperation: [ + ({ args, result, collection }) => { + args.req.context['collection_afterOperation_collection'] = JSON.stringify(collection) + + for (const contextKey in args.req.context) { + if (contextKey.startsWith('collection_')) { + result[contextKey] = args.req.context[contextKey] + } + } + return result + }, + ], + }, + fields: [ + { + name: 'field_collectionAndField', + type: 'text', + hooks: { + beforeChange: [ + ({ collection, field, context, value }) => { + context['field_beforeChange_CollectionAndField'] = + JSON.stringify(collection) + JSON.stringify(field) + + return value + }, + ], + + afterRead: [ + ({ collection, field, context }) => { + return ( + (context['field_beforeChange_CollectionAndField'] as string) + + JSON.stringify(collection) + + JSON.stringify(field) + ) + }, + ], + }, + }, + { + name: 'collection_beforeOperation_collection', + type: 'text', + }, + { + name: 'collection_beforeChange_collection', + type: 'text', + }, + { + name: 'collection_afterChange_collection', + type: 'text', + }, + { + name: 'collection_beforeRead_collection', + type: 'text', + }, + { + name: 'collection_afterRead_collection', + type: 'text', + }, + { + name: 'collection_afterOperation_collection', + type: 'text', + }, + ], +} diff --git a/test/hooks/config.ts b/test/hooks/config.ts index a1bd682c2..c28fb4ca8 100644 --- a/test/hooks/config.ts +++ b/test/hooks/config.ts @@ -1,14 +1,17 @@ +import type { SanitizedConfig } from '../../packages/payload/src/config/types' + import { buildConfigWithDefaults } from '../buildConfigWithDefaults' import AfterOperation from './collections/AfterOperation' import ChainingHooks from './collections/ChainingHooks' import ContextHooks from './collections/ContextHooks' +import { DataHooks } from './collections/Data' import Hooks, { hooksSlug } from './collections/Hook' import NestedAfterReadHooks from './collections/NestedAfterReadHooks' import Relations from './collections/Relations' import TransformHooks from './collections/Transform' import Users, { seedHooksUsers } from './collections/Users' - -export default buildConfigWithDefaults({ +import { DataHooksGlobal } from './globals/Data' +export const HooksConfig: Promise = buildConfigWithDefaults({ collections: [ AfterOperation, ContextHooks, @@ -18,7 +21,9 @@ export default buildConfigWithDefaults({ ChainingHooks, Relations, Users, + DataHooks, ], + globals: [DataHooksGlobal], onInit: async (payload) => { await seedHooksUsers(payload) await payload.create({ @@ -38,3 +43,5 @@ export default buildConfigWithDefaults({ }) }, }) + +export default HooksConfig diff --git a/test/hooks/globals/Data/index.ts b/test/hooks/globals/Data/index.ts new file mode 100644 index 000000000..68dc6cb42 --- /dev/null +++ b/test/hooks/globals/Data/index.ts @@ -0,0 +1,97 @@ +/* eslint-disable no-param-reassign */ + +import type { GlobalConfig } from '../../../../packages/payload/src/globals/config/types' + +export const dataHooksGlobalSlug = 'data-hooks-global' + +export const DataHooksGlobal: GlobalConfig = { + slug: dataHooksGlobalSlug, + access: { + read: () => true, + update: () => true, + }, + hooks: { + beforeChange: [ + ({ data, global, context }) => { + context['global_beforeChange_global'] = JSON.stringify(global) + + return data + }, + ], + beforeRead: [ + async ({ context, global }) => { + context['global_beforeRead_global'] = JSON.stringify(global) + }, + ], + afterRead: [ + ({ context, global, doc }) => { + context['global_afterRead_global'] = JSON.stringify(global) + + // Needs to be done for both afterRead (for findOne test) and afterChange (for update test) + for (const contextKey in context) { + if (contextKey.startsWith('global_')) { + doc[contextKey] = context[contextKey] + } + } + return doc + }, + ], + afterChange: [ + async ({ context, global, doc }) => { + context['global_afterChange_global'] = JSON.stringify(global) + + // Needs to be done for both afterRead (for findOne test) and afterChange (for update test), as afterChange is called after afterRead + for (const contextKey in context) { + if (contextKey.startsWith('global_')) { + doc[contextKey] = context[contextKey] + } + } + + return doc + }, + ], + }, + fields: [ + { + name: 'field_globalAndField', + type: 'text', + hooks: { + beforeChange: [ + ({ global, field, context, value }) => { + context['field_beforeChange_GlobalAndField'] = + JSON.stringify(global) + JSON.stringify(field) + + return value + }, + ], + + afterRead: [ + ({ global, field, context }) => { + return ( + (context['field_beforeChange_GlobalAndField'] as string) + + JSON.stringify(global) + + JSON.stringify(field) + ) + }, + ], + }, + }, + + { + name: 'global_beforeChange_global', + type: 'text', + }, + { + name: 'global_afterChange_global', + type: 'text', + }, + { + name: 'global_beforeRead_global', + type: 'text', + }, + { + name: 'global_afterRead_global', + type: 'text', + }, + ], +} diff --git a/test/hooks/int.spec.ts b/test/hooks/int.spec.ts index 03b6c60fc..6f3e9e490 100644 --- a/test/hooks/int.spec.ts +++ b/test/hooks/int.spec.ts @@ -8,6 +8,7 @@ import { RESTClient } from '../helpers/rest' import { afterOperationSlug } from './collections/AfterOperation' import { chainingHooksSlug } from './collections/ChainingHooks' import { contextHooksSlug } from './collections/ContextHooks' +import { dataHooksSlug } from './collections/Data' import { hooksSlug } from './collections/Hook' import { generatedAfterReadText, @@ -16,7 +17,8 @@ import { import { relationsSlug } from './collections/Relations' import { transformSlug } from './collections/Transform' import { hooksUsersSlug } from './collections/Users' -import configPromise from './config' +import configPromise, { HooksConfig } from './config' +import { dataHooksGlobalSlug } from './globals/Data' let client: RESTClient let apiUrl @@ -293,4 +295,111 @@ describe('Hooks', () => { ).rejects.toThrow(AuthenticationError) }) }) + + describe('hook parameter data', () => { + it('should pass collection prop to collection hooks', async () => { + const sanitizedConfig = await HooksConfig + const sanitizedHooksCollection = JSON.parse( + JSON.stringify(sanitizedConfig.collections.find(({ slug }) => slug === dataHooksSlug)), + ) + + const doc = await payload.create({ + collection: dataHooksSlug, + data: {}, + }) + + expect(JSON.parse(doc.collection_beforeOperation_collection)).toStrictEqual( + sanitizedHooksCollection, + ) + expect(JSON.parse(doc.collection_beforeChange_collection)).toStrictEqual( + sanitizedHooksCollection, + ) + expect(JSON.parse(doc.collection_afterChange_collection)).toStrictEqual( + sanitizedHooksCollection, + ) + expect(JSON.parse(doc.collection_afterRead_collection)).toStrictEqual( + sanitizedHooksCollection, + ) + expect(JSON.parse(doc.collection_afterOperation_collection)).toStrictEqual( + sanitizedHooksCollection, + ) + + // BeforeRead is only run for find operations + const foundDoc = await payload.findByID({ + collection: dataHooksSlug, + id: doc.id, + }) + + expect(JSON.parse(foundDoc.collection_beforeRead_collection)).toStrictEqual( + sanitizedHooksCollection, + ) + }) + + it('should pass collection and field props to field hooks', async () => { + const sanitizedConfig = await HooksConfig + const sanitizedHooksCollection = sanitizedConfig.collections.find( + ({ slug }) => slug === dataHooksSlug, + ) + + const field = sanitizedHooksCollection.fields.find( + (field) => 'name' in field && field.name === 'field_collectionAndField', + ) + + const doc = await payload.create({ + collection: dataHooksSlug, + data: {}, + }) + + const collectionAndField = JSON.stringify(sanitizedHooksCollection) + JSON.stringify(field) + + expect(doc.field_collectionAndField).toStrictEqual(collectionAndField + collectionAndField) + }) + + it('should pass global prop to global hooks', async () => { + const sanitizedConfig = await HooksConfig + const sanitizedHooksGlobal = JSON.parse( + JSON.stringify(sanitizedConfig.globals.find(({ slug }) => slug === dataHooksGlobalSlug)), + ) + + const doc = await payload.updateGlobal({ + slug: dataHooksGlobalSlug, + data: {}, + }) + + expect(JSON.parse(doc.global_beforeChange_global)).toStrictEqual(sanitizedHooksGlobal) + expect(JSON.parse(doc.global_afterRead_global)).toStrictEqual(sanitizedHooksGlobal) + expect(JSON.parse(doc.global_afterChange_global)).toStrictEqual(sanitizedHooksGlobal) + + // beforeRead is only run for findOne operations + const foundDoc = await payload.findGlobal({ + slug: dataHooksGlobalSlug, + }) + + expect(JSON.parse(foundDoc.global_beforeRead_global)).toStrictEqual(sanitizedHooksGlobal) + }) + + it('should pass global and field props to global hooks', async () => { + const sanitizedConfig = await HooksConfig + const sanitizedHooksGlobal = sanitizedConfig.globals.find( + ({ slug }) => slug === dataHooksGlobalSlug, + ) + + const globalString = JSON.stringify(sanitizedHooksGlobal) + + const fieldString = JSON.stringify( + sanitizedHooksGlobal.fields.find( + (field) => 'name' in field && field.name === 'field_globalAndField', + ), + ) + + const doc = await payload.updateGlobal({ + slug: dataHooksGlobalSlug, + data: {}, + }) + + const globalAndFieldString = globalString + fieldString + + expect(doc.field_globalAndField).toStrictEqual(globalAndFieldString + globalAndFieldString) + }) + }) })