feat: ability to add context to payload's request object (#2796)

Co-authored-by: Dan Ribbens <dan.ribbens@gmail.com>
This commit is contained in:
Alessio Gravili
2023-07-26 15:07:49 +02:00
committed by GitHub
parent cf9795b8d8
commit 67ba131cc6
63 changed files with 570 additions and 40 deletions

127
docs/hooks/context.mdx Normal file
View File

@@ -0,0 +1,127 @@
---
title: Context
label: Context
order: 50
desc: Context allows you to pass in extra data that can be shared between hooks
keywords: hooks, context, payload context, payloadcontext, data, extra data, shared data, shared, extra
---
The `context` object in hooks is used to share data across different hooks. The persists throughout the entire lifecycle of a request and is available within every hook. This allows you to add logic to your hooks based on the request state by setting properties to `req.context` and using them elsewhere.
## When to use Context
Context gives you a way forward on otherwise difficult problems such as:
1. **Passing data between hooks**: Needing data in multiple hooks from a 3rd party API, it could be retrieved and used in `beforeChange` and later used again in an `afterChange` hook without having to fetch it twice.
2. **Preventing infinite loops**: Calling `payload.update()` on the same document that triggered an `afterChange` hook will create an infinite loop, control the flow by assigning a no-op condition to context
3. **Passing data to local API**: Setting values on the `req.context` and pass it to `payload.create()` you can provide additional data to hooks without adding extraneous fields.
4. **Passing data between hooks and middleware or custom endpoints**: Hooks could set context across multiple collections and then be used in a final `postMiddleware`.
## How to Use Context
Let's see examples on how context can be used in the first two scenarios mentioned above:
### Passing data between hooks
To pass data between hooks, you can assign values to context in an earlier hook in the lifecycle of a request and expect it the context in a later hook.
For example:
```ts
const Customer: CollectionConfig = {
slug: 'customers',
hooks: {
beforeChange: [async ({ context, data }) => {
// assign the customerData to context for use later
context.customerData = await fetchCustomerData(data.customerID);
return {
...data,
// some data we use here
name: context.customerData.name
};
}],
afterChange: [async ({ context, doc, req }) => {
// use context.customerData without needing to fetch it again
if (context.customerData.contacted === false) {
createTodo('Call Customer', context.customerData)
}
}],
},
fields: [ /* ... */ ],
};
```
### Preventing infinite loops
Let's say you have an `afterChange` hook, and you want to do a calculation inside the hook (as the document ID needed for the calculation is available in the `afterChange` hook, but not in the `beforeChange` hook). Once that's done, you want to update the document with the result of the calculation.
Bad example:
```ts
const Customer: CollectionConfig = {
slug: 'customers',
hooks: {
afterChange: [async ({ doc }) => {
await payload.update({
// DANGER: updating the same slug as the collection in an afterChange will create an infinite loop!
collection: 'customers',
id: doc.id,
data: {
...(await fetchCustomerData(data.customerID))
},
});
}],
},
fields: [ /* ... */ ],
};
```
Instead of the above, we need to tell the `afterChange` hook to not run again if it performs the update (and thus not update itself again). We can solve that with context.
Fixed example:
```ts
const MyCollection: CollectionConfig = {
slug: 'slug',
hooks: {
afterChange: [async ({ context, doc }) => {
// return if flag was previously set
if (context.triggerAfterChange === false) {
return;
}
await payload.update({
collection: contextHooksSlug,
id: doc.id,
data: {
...(await fetchCustomerData(data.customerID))
},
context: {
// set a flag to prevent from running again
triggerAfterChange: false,
},
});
}],
},
fields: [ /* ... */ ],
};
```
## Typing context
The default typescript interface for `context` is `{ [key: string]: unknown }`. If you prefer a more strict typing in your project or when authoring plugins for others, you can override this using the `declare` syntax.
This is known as "type augmentation" - a TypeScript feature which allows us to add types to existing objects. Simply put this in any .ts or .d.ts file:
```ts
import { RequestContext as OriginalRequestContext } from 'payload';
declare module 'payload' {
// Create a new interface that merges your additional fields with the original one
export interface RequestContext extends OriginalRequestContext {
myObject?: string;
// ...
}
}
```
This will add a the property `myObject` with a type of string to every context object. Make sure to follow this example correctly, as type augmentation can mess up your types if you do it wrong.

View File

@@ -77,6 +77,7 @@ You can specify more options within the Local API vs. REST or GraphQL due to the
| `user` | If you set `overrideAccess` to `false`, you can pass a user to use against the access control checks. | | `user` | If you set `overrideAccess` to `false`, you can pass a user to use against the access control checks. |
| `showHiddenFields` | Opt-in to receiving hidden fields. By default, they are hidden from returned documents in accordance to your config. | | `showHiddenFields` | Opt-in to receiving hidden fields. By default, they are hidden from returned documents in accordance to your config. |
| `pagination` | Set to false to return all documents and avoid querying for document counts. | | `pagination` | Set to false to return all documents and avoid querying for document counts. |
| `context` | [Context](/docs/hooks/context), which will then be passed to `context` and `req.context`, which can be read by hooks. Useful if you want to pass additional information to the hooks which shouldn't be necessarily part of the document, for example a `triggerBeforeChange` option which can be read by the BeforeChange hook to determine if it should run or not. |
_There are more options available on an operation by operation basis outlined below._ _There are more options available on an operation by operation basis outlined below._

View File

@@ -34,6 +34,7 @@ async function forgotPassword(incomingArgs: Arguments): Promise<string | null> {
args = (await hook({ args = (await hook({
args, args,
operation: 'forgotPassword', operation: 'forgotPassword',
context: args.req.context,
})) || args; })) || args;
}, Promise.resolve()); }, Promise.resolve());
@@ -122,7 +123,7 @@ async function forgotPassword(incomingArgs: Arguments): Promise<string | null> {
await collectionConfig.hooks.afterForgotPassword.reduce(async (priorHook, hook) => { await collectionConfig.hooks.afterForgotPassword.reduce(async (priorHook, hook) => {
await priorHook; await priorHook;
await hook({ args }); await hook({ args, context: req.context });
}, Promise.resolve()); }, Promise.resolve());
return token; return token;

View File

@@ -5,6 +5,7 @@ import { Payload } from '../../../payload';
import { getDataLoader } from '../../../collections/dataloader'; import { getDataLoader } from '../../../collections/dataloader';
import i18n from '../../../translations/init'; import i18n from '../../../translations/init';
import { APIError } from '../../../errors'; import { APIError } from '../../../errors';
import { setRequestContext } from '../../../express/setRequestContext';
export type Options<T extends keyof GeneratedTypes['collections']> = { export type Options<T extends keyof GeneratedTypes['collections']> = {
collection: T collection: T
@@ -27,6 +28,7 @@ async function localForgotPassword<T extends keyof GeneratedTypes['collections']
disableEmail, disableEmail,
req = {} as PayloadRequest, req = {} as PayloadRequest,
} = options; } = options;
setRequestContext(options.req);
const collection = payload.collections[collectionSlug]; const collection = payload.collections[collectionSlug];

View File

@@ -6,6 +6,7 @@ import { Payload } from '../../../payload';
import { getDataLoader } from '../../../collections/dataloader'; import { getDataLoader } from '../../../collections/dataloader';
import i18n from '../../../translations/init'; import i18n from '../../../translations/init';
import { APIError } from '../../../errors'; import { APIError } from '../../../errors';
import { setRequestContext } from '../../../express/setRequestContext';
export type Options<TSlug extends keyof GeneratedTypes['collections']> = { export type Options<TSlug extends keyof GeneratedTypes['collections']> = {
collection: TSlug collection: TSlug
@@ -37,6 +38,8 @@ async function localLogin<TSlug extends keyof GeneratedTypes['collections']>(
overrideAccess = true, overrideAccess = true,
showHiddenFields, showHiddenFields,
} = options; } = options;
setRequestContext(options.req);
const collection = payload.collections[collectionSlug]; const collection = payload.collections[collectionSlug];

View File

@@ -5,6 +5,7 @@ import { PayloadRequest } from '../../../express/types';
import { getDataLoader } from '../../../collections/dataloader'; import { getDataLoader } from '../../../collections/dataloader';
import i18n from '../../../translations/init'; import i18n from '../../../translations/init';
import { APIError } from '../../../errors'; import { APIError } from '../../../errors';
import { setRequestContext } from '../../../express/setRequestContext';
export type Options<T extends keyof GeneratedTypes['collections']> = { export type Options<T extends keyof GeneratedTypes['collections']> = {
collection: T collection: T
@@ -26,6 +27,7 @@ async function localResetPassword<T extends keyof GeneratedTypes['collections']>
overrideAccess, overrideAccess,
req = {} as PayloadRequest, req = {} as PayloadRequest,
} = options; } = options;
setRequestContext(options.req);
const collection = payload.collections[collectionSlug]; const collection = payload.collections[collectionSlug];

View File

@@ -5,6 +5,7 @@ import unlock from '../unlock';
import { getDataLoader } from '../../../collections/dataloader'; import { getDataLoader } from '../../../collections/dataloader';
import i18n from '../../../translations/init'; import i18n from '../../../translations/init';
import { APIError } from '../../../errors'; import { APIError } from '../../../errors';
import { setRequestContext } from '../../../express/setRequestContext';
export type Options<T extends keyof GeneratedTypes['collections']> = { export type Options<T extends keyof GeneratedTypes['collections']> = {
collection: T collection: T
@@ -25,6 +26,7 @@ async function localUnlock<T extends keyof GeneratedTypes['collections']>(
overrideAccess = true, overrideAccess = true,
req = {} as PayloadRequest, req = {} as PayloadRequest,
} = options; } = options;
setRequestContext(options.req);
const collection = payload.collections[collectionSlug]; const collection = payload.collections[collectionSlug];

View File

@@ -48,6 +48,7 @@ async function login<TSlug extends keyof GeneratedTypes['collections']>(
args = (await hook({ args = (await hook({
args, args,
operation: 'login', operation: 'login',
context: args.req.context,
})) || args; })) || args;
}, Promise.resolve()); }, Promise.resolve());
@@ -133,6 +134,7 @@ async function login<TSlug extends keyof GeneratedTypes['collections']>(
user = (await hook({ user = (await hook({
user, user,
req: args.req, req: args.req,
context: req.context,
})) || user; })) || user;
}, Promise.resolve()); }, Promise.resolve());
@@ -172,6 +174,7 @@ async function login<TSlug extends keyof GeneratedTypes['collections']>(
user, user,
req: args.req, req: args.req,
token, token,
context: req.context,
}) || user; }) || user;
}, Promise.resolve()); }, Promise.resolve());
@@ -186,6 +189,7 @@ async function login<TSlug extends keyof GeneratedTypes['collections']>(
overrideAccess, overrideAccess,
req, req,
showHiddenFields, showHiddenFields,
context: req.context,
}); });
// ///////////////////////////////////// // /////////////////////////////////////
@@ -198,6 +202,7 @@ async function login<TSlug extends keyof GeneratedTypes['collections']>(
user = await hook({ user = await hook({
req, req,
doc: user, doc: user,
context: req.context,
}) || user; }) || user;
}, Promise.resolve()); }, Promise.resolve());

View File

@@ -46,6 +46,7 @@ async function logout(incomingArgs: Arguments): Promise<string> {
args = (await hook({ args = (await hook({
req, req,
res, res,
context: req.context,
})) || args; })) || args;
}, Promise.resolve()); }, Promise.resolve());

View File

@@ -60,6 +60,7 @@ async function me({
response = await hook({ response = await hook({
req, req,
response, response,
context: req.context,
}) || response; }) || response;
}, Promise.resolve()); }, Promise.resolve());

View File

@@ -34,6 +34,7 @@ async function refresh(incomingArgs: Arguments): Promise<Result> {
args = (await hook({ args = (await hook({
args, args,
operation: 'refresh', operation: 'refresh',
context: args.req.context,
})) || args; })) || args;
}, Promise.resolve()); }, Promise.resolve());
@@ -114,6 +115,7 @@ async function refresh(incomingArgs: Arguments): Promise<Result> {
res: args.res, res: args.res,
exp, exp,
token: refreshedToken, token: refreshedToken,
context: args.req.context,
})) || response; })) || response;
}, Promise.resolve()); }, Promise.resolve());

View File

@@ -6,7 +6,7 @@ import { Response } from 'express';
import { Config as GeneratedTypes } from 'payload/generated-types'; import { Config as GeneratedTypes } from 'payload/generated-types';
import { Access, Endpoint, EntityDescription, GeneratePreviewURL } from '../../config/types'; import { Access, Endpoint, EntityDescription, GeneratePreviewURL } from '../../config/types';
import { Field } from '../../fields/config/types'; import { Field } from '../../fields/config/types';
import { PayloadRequest } from '../../express/types'; import { PayloadRequest, RequestContext } from '../../express/types';
import { Auth, IncomingAuthType, User } from '../../auth/types'; import { Auth, IncomingAuthType, User } from '../../auth/types';
import { IncomingUploadType, Upload } from '../../uploads/types'; import { IncomingUploadType, Upload } from '../../uploads/types';
import { IncomingCollectionVersions, SanitizedCollectionVersions } from '../../versions/types'; import { IncomingCollectionVersions, SanitizedCollectionVersions } from '../../versions/types';
@@ -49,6 +49,7 @@ export type BeforeOperationHook = (args: {
* Hook operation being performed * Hook operation being performed
*/ */
operation: HookOperationType; operation: HookOperationType;
context: RequestContext;
}) => any; }) => any;
export type BeforeValidateHook<T extends TypeWithID = any> = (args: { export type BeforeValidateHook<T extends TypeWithID = any> = (args: {
@@ -64,6 +65,7 @@ export type BeforeValidateHook<T extends TypeWithID = any> = (args: {
* `undefined` on 'create' operation * `undefined` on 'create' operation
*/ */
originalDoc?: T; originalDoc?: T;
context: RequestContext;
}) => any; }) => any;
export type BeforeChangeHook<T extends TypeWithID = any> = (args: { export type BeforeChangeHook<T extends TypeWithID = any> = (args: {
@@ -79,6 +81,7 @@ export type BeforeChangeHook<T extends TypeWithID = any> = (args: {
* `undefined` on 'create' operation * `undefined` on 'create' operation
*/ */
originalDoc?: T; originalDoc?: T;
context: RequestContext;
}) => any; }) => any;
export type AfterChangeHook<T extends TypeWithID = any> = (args: { export type AfterChangeHook<T extends TypeWithID = any> = (args: {
@@ -89,53 +92,62 @@ export type AfterChangeHook<T extends TypeWithID = any> = (args: {
* Hook operation being performed * Hook operation being performed
*/ */
operation: CreateOrUpdateOperation; operation: CreateOrUpdateOperation;
context: RequestContext;
}) => any; }) => any;
export type BeforeReadHook<T extends TypeWithID = any> = (args: { export type BeforeReadHook<T extends TypeWithID = any> = (args: {
doc: T; doc: T;
req: PayloadRequest; req: PayloadRequest;
query: { [key: string]: any }; query: { [key: string]: any };
context: RequestContext;
}) => any; }) => any;
export type AfterReadHook<T extends TypeWithID = any> = (args: { export type AfterReadHook<T extends TypeWithID = any> = (args: {
doc: T; doc: T;
req: PayloadRequest; req: PayloadRequest;
query?: { [key: string]: any }; query?: { [key: string]: any };
findMany?: boolean findMany?: boolean;
context: RequestContext;
}) => any; }) => any;
export type BeforeDeleteHook = (args: { export type BeforeDeleteHook = (args: {
req: PayloadRequest; req: PayloadRequest;
id: string | number; id: string | number;
context: RequestContext;
}) => any; }) => any;
export type AfterDeleteHook<T extends TypeWithID = any> = (args: { export type AfterDeleteHook<T extends TypeWithID = any> = (args: {
doc: T; doc: T;
req: PayloadRequest; req: PayloadRequest;
id: string | number; id: string | number;
context: RequestContext;
}) => any; }) => any;
export type AfterErrorHook = (err: Error, res: unknown) => { response: any, status: number } | void; export type AfterErrorHook = (err: Error, res: unknown, context: RequestContext) => { response: any, status: number } | void;
export type BeforeLoginHook<T extends TypeWithID = any> = (args: { export type BeforeLoginHook<T extends TypeWithID = any> = (args: {
req: PayloadRequest; req: PayloadRequest;
user: T user: T;
context: RequestContext;
}) => any; }) => any;
export type AfterLoginHook<T extends TypeWithID = any> = (args: { export type AfterLoginHook<T extends TypeWithID = any> = (args: {
req: PayloadRequest; req: PayloadRequest;
user: T; user: T;
token: string; token: string;
context: RequestContext;
}) => any; }) => any;
export type AfterLogoutHook<T extends TypeWithID = any> = (args: { export type AfterLogoutHook<T extends TypeWithID = any> = (args: {
req: PayloadRequest; req: PayloadRequest;
res: Response; res: Response;
context: RequestContext;
}) => any; }) => any;
export type AfterMeHook<T extends TypeWithID = any> = (args: { export type AfterMeHook<T extends TypeWithID = any> = (args: {
req: PayloadRequest; req: PayloadRequest;
response: unknown; response: unknown;
context: RequestContext;
}) => any; }) => any;
export type AfterRefreshHook<T extends TypeWithID = any> = (args: { export type AfterRefreshHook<T extends TypeWithID = any> = (args: {
@@ -143,10 +155,12 @@ export type AfterRefreshHook<T extends TypeWithID = any> = (args: {
res: Response; res: Response;
token: string; token: string;
exp: number; exp: number;
context: RequestContext;
}) => any; }) => any;
export type AfterForgotPasswordHook = (args: { export type AfterForgotPasswordHook = (args: {
args?: any; args?: any;
context: RequestContext;
}) => any; }) => any;
type BeforeDuplicateArgs<T> = { type BeforeDuplicateArgs<T> = {

View File

@@ -54,6 +54,7 @@ async function create<TSlug extends keyof GeneratedTypes['collections']>(
args = (await hook({ args = (await hook({
args, args,
operation: 'create', operation: 'create',
context: args.req.context,
})) || args; })) || args;
}, Promise.resolve()); }, Promise.resolve());
@@ -130,6 +131,7 @@ async function create<TSlug extends keyof GeneratedTypes['collections']>(
operation: 'create', operation: 'create',
overrideAccess, overrideAccess,
req, req,
context: req.context,
}); });
// ///////////////////////////////////// // /////////////////////////////////////
@@ -143,6 +145,7 @@ async function create<TSlug extends keyof GeneratedTypes['collections']>(
data, data,
req, req,
operation: 'create', operation: 'create',
context: req.context,
})) || data; })) || data;
}, Promise.resolve()); }, Promise.resolve());
@@ -165,6 +168,7 @@ async function create<TSlug extends keyof GeneratedTypes['collections']>(
data, data,
req, req,
operation: 'create', operation: 'create',
context: req.context,
})) || data; })) || data;
}, Promise.resolve()); }, Promise.resolve());
@@ -180,6 +184,7 @@ async function create<TSlug extends keyof GeneratedTypes['collections']>(
operation: 'create', operation: 'create',
req, req,
skipValidation: shouldSaveDraft, skipValidation: shouldSaveDraft,
context: req.context,
}); });
// ///////////////////////////////////// // /////////////////////////////////////
@@ -203,7 +208,7 @@ async function create<TSlug extends keyof GeneratedTypes['collections']>(
doc: resultWithLocales, doc: resultWithLocales,
payload: req.payload, payload: req.payload,
password: data.password as string, password: data.password as string,
}) });
} else { } else {
try { try {
doc = await Model.create(resultWithLocales); doc = await Model.create(resultWithLocales);
@@ -266,6 +271,7 @@ async function create<TSlug extends keyof GeneratedTypes['collections']>(
overrideAccess, overrideAccess,
req, req,
showHiddenFields, showHiddenFields,
context: req.context,
}); });
// ///////////////////////////////////// // /////////////////////////////////////
@@ -278,6 +284,7 @@ async function create<TSlug extends keyof GeneratedTypes['collections']>(
result = await hook({ result = await hook({
req, req,
doc: result, doc: result,
context: req.context,
}) || result; }) || result;
}, Promise.resolve()); }, Promise.resolve());
@@ -292,6 +299,7 @@ async function create<TSlug extends keyof GeneratedTypes['collections']>(
entityConfig: collectionConfig, entityConfig: collectionConfig,
operation: 'create', operation: 'create',
req, req,
context: req.context,
}); });
// ///////////////////////////////////// // /////////////////////////////////////
@@ -306,6 +314,7 @@ async function create<TSlug extends keyof GeneratedTypes['collections']>(
previousDoc: {}, previousDoc: {},
req: args.req, req: args.req,
operation: 'create', operation: 'create',
context: req.context,
}) || result; }) || result;
}, Promise.resolve()); }, Promise.resolve());

View File

@@ -41,6 +41,7 @@ async function deleteOperation<TSlug extends keyof GeneratedTypes['collections']
args = (await hook({ args = (await hook({
args, args,
operation: 'delete', operation: 'delete',
context: args.req.context,
})) || args; })) || args;
}, Promise.resolve()); }, Promise.resolve());
@@ -116,6 +117,7 @@ async function deleteOperation<TSlug extends keyof GeneratedTypes['collections']
return hook({ return hook({
req, req,
id, id,
context: req.context,
}); });
}, Promise.resolve()); }, Promise.resolve());
@@ -150,6 +152,7 @@ async function deleteOperation<TSlug extends keyof GeneratedTypes['collections']
overrideAccess, overrideAccess,
req, req,
showHiddenFields, showHiddenFields,
context: req.context,
}); });
// ///////////////////////////////////// // /////////////////////////////////////
@@ -162,6 +165,7 @@ async function deleteOperation<TSlug extends keyof GeneratedTypes['collections']
result = await hook({ result = await hook({
req, req,
doc: result || doc, doc: result || doc,
context: req.context,
}) || result; }) || result;
}, Promise.resolve()); }, Promise.resolve());
@@ -176,6 +180,7 @@ async function deleteOperation<TSlug extends keyof GeneratedTypes['collections']
req, req,
id, id,
doc: result, doc: result,
context: req.context,
}) || result; }) || result;
}, Promise.resolve()); }, Promise.resolve());

View File

@@ -32,6 +32,7 @@ async function deleteByID<TSlug extends keyof GeneratedTypes['collections']>(inc
args = (await hook({ args = (await hook({
args, args,
operation: 'delete', operation: 'delete',
context: args.req.context,
})) || args; })) || args;
}, Promise.resolve()); }, Promise.resolve());
@@ -72,6 +73,7 @@ async function deleteByID<TSlug extends keyof GeneratedTypes['collections']>(inc
return hook({ return hook({
req, req,
id, id,
context: req.context,
}); });
}, Promise.resolve()); }, Promise.resolve());
@@ -145,6 +147,7 @@ async function deleteByID<TSlug extends keyof GeneratedTypes['collections']>(inc
overrideAccess, overrideAccess,
req, req,
showHiddenFields, showHiddenFields,
context: req.context,
}); });
// ///////////////////////////////////// // /////////////////////////////////////
@@ -157,6 +160,7 @@ async function deleteByID<TSlug extends keyof GeneratedTypes['collections']>(inc
result = await hook({ result = await hook({
req, req,
doc: result, doc: result,
context: req.context,
}) || result; }) || result;
}, Promise.resolve()); }, Promise.resolve());
@@ -167,7 +171,7 @@ async function deleteByID<TSlug extends keyof GeneratedTypes['collections']>(inc
await collectionConfig.hooks.afterDelete.reduce(async (priorHook, hook) => { await collectionConfig.hooks.afterDelete.reduce(async (priorHook, hook) => {
await priorHook; await priorHook;
result = await hook({ req, id, doc: result }) || result; result = await hook({ req, id, doc: result, context: req.context }) || result;
}, Promise.resolve()); }, Promise.resolve());
// ///////////////////////////////////// // /////////////////////////////////////

View File

@@ -41,6 +41,7 @@ async function find<T extends TypeWithID & Record<string, unknown>>(
args = (await hook({ args = (await hook({
args, args,
operation: 'read', operation: 'read',
context: args.req.context,
})) || args; })) || args;
}, Promise.resolve()); }, Promise.resolve());
@@ -180,7 +181,7 @@ async function find<T extends TypeWithID & Record<string, unknown>>(
await collectionConfig.hooks.beforeRead.reduce(async (priorHook, hook) => { await collectionConfig.hooks.beforeRead.reduce(async (priorHook, hook) => {
await priorHook; await priorHook;
docRef = await hook({ req, query, doc: docRef }) || docRef; docRef = await hook({ req, query, doc: docRef, context: req.context }) || docRef;
}, Promise.resolve()); }, Promise.resolve());
return docRef; return docRef;
@@ -202,6 +203,7 @@ async function find<T extends TypeWithID & Record<string, unknown>>(
req, req,
showHiddenFields, showHiddenFields,
findMany: true, findMany: true,
context: req.context,
}))), }))),
}; };
@@ -217,7 +219,7 @@ async function find<T extends TypeWithID & Record<string, unknown>>(
await collectionConfig.hooks.afterRead.reduce(async (priorHook, hook) => { await collectionConfig.hooks.afterRead.reduce(async (priorHook, hook) => {
await priorHook; await priorHook;
docRef = await hook({ req, query, doc: docRef, findMany: true }) || doc; docRef = await hook({ req, query, doc: docRef, findMany: true, context: req.context }) || doc;
}, Promise.resolve()); }, Promise.resolve());
return docRef; return docRef;

View File

@@ -35,6 +35,7 @@ async function findByID<T extends TypeWithID>(
args = (await hook({ args = (await hook({
args, args,
operation: 'read', operation: 'read',
context: args.req.context,
})) || args; })) || args;
}, Promise.resolve()); }, Promise.resolve());
@@ -138,6 +139,7 @@ async function findByID<T extends TypeWithID>(
req, req,
query, query,
doc: result, doc: result,
context: req.context,
}) || result; }) || result;
}, Promise.resolve()); }, Promise.resolve());
@@ -153,6 +155,7 @@ async function findByID<T extends TypeWithID>(
overrideAccess, overrideAccess,
req, req,
showHiddenFields, showHiddenFields,
context: req.context,
}); });
// ///////////////////////////////////// // /////////////////////////////////////
@@ -166,6 +169,7 @@ async function findByID<T extends TypeWithID>(
req, req,
query, query,
doc: result, doc: result,
context: req.context,
}) || result; }) || result;
}, Promise.resolve()); }, Promise.resolve());

View File

@@ -98,6 +98,7 @@ async function findVersionByID<T extends TypeWithVersion<T> = any>(args: Argumen
req, req,
query, query,
doc: result.version, doc: result.version,
context: req.context,
}) || result.version; }) || result.version;
}, Promise.resolve()); }, Promise.resolve());
@@ -113,6 +114,7 @@ async function findVersionByID<T extends TypeWithVersion<T> = any>(args: Argumen
overrideAccess, overrideAccess,
req, req,
showHiddenFields, showHiddenFields,
context: req.context,
}); });
// ///////////////////////////////////// // /////////////////////////////////////
@@ -126,6 +128,7 @@ async function findVersionByID<T extends TypeWithVersion<T> = any>(args: Argumen
req, req,
query, query,
doc: result.version, doc: result.version,
context: req.context,
}) || result.version; }) || result.version;
}, Promise.resolve()); }, Promise.resolve());

View File

@@ -110,7 +110,7 @@ async function findVersions<T extends TypeWithVersion<T>>(
await collectionConfig.hooks.beforeRead.reduce(async (priorHook, hook) => { await collectionConfig.hooks.beforeRead.reduce(async (priorHook, hook) => {
await priorHook; await priorHook;
docRef.version = await hook({ req, query, doc: docRef.version }) || docRef.version; docRef.version = await hook({ req, query, doc: docRef.version, context: req.context }) || docRef.version;
}, Promise.resolve()); }, Promise.resolve());
return docRef; return docRef;
@@ -133,6 +133,7 @@ async function findVersions<T extends TypeWithVersion<T>>(
req, req,
showHiddenFields, showHiddenFields,
findMany: true, findMany: true,
context: req.context,
}), }),
}))), }))),
}; };
@@ -149,7 +150,7 @@ async function findVersions<T extends TypeWithVersion<T>>(
await collectionConfig.hooks.afterRead.reduce(async (priorHook, hook) => { await collectionConfig.hooks.afterRead.reduce(async (priorHook, hook) => {
await priorHook; await priorHook;
docRef.version = await hook({ req, query, doc: doc.version, findMany: true }) || doc.version; docRef.version = await hook({ req, query, doc: doc.version, findMany: true, context: req.context }) || doc.version;
}, Promise.resolve()); }, Promise.resolve());
return docRef; return docRef;

View File

@@ -2,7 +2,7 @@ import { Config as GeneratedTypes } from 'payload/generated-types';
import { UploadedFile } from 'express-fileupload'; import { UploadedFile } from 'express-fileupload';
import { MarkOptional } from 'ts-essentials'; import { MarkOptional } from 'ts-essentials';
import { Payload } from '../../../payload'; import { Payload } from '../../../payload';
import { PayloadRequest } from '../../../express/types'; import { PayloadRequest, RequestContext } from '../../../express/types';
import { Document } from '../../../types'; import { Document } from '../../../types';
import getFileByPath from '../../../uploads/getFileByPath'; import getFileByPath from '../../../uploads/getFileByPath';
import create from '../create'; import create from '../create';
@@ -10,6 +10,7 @@ import { getDataLoader } from '../../dataloader';
import { File } from '../../../uploads/types'; import { File } from '../../../uploads/types';
import i18n from '../../../translations/init'; import i18n from '../../../translations/init';
import { APIError } from '../../../errors'; import { APIError } from '../../../errors';
import { setRequestContext } from '../../../express/setRequestContext';
export type Options<TSlug extends keyof GeneratedTypes['collections']> = { export type Options<TSlug extends keyof GeneratedTypes['collections']> = {
collection: TSlug collection: TSlug
@@ -26,6 +27,10 @@ export type Options<TSlug extends keyof GeneratedTypes['collections']> = {
overwriteExistingFiles?: boolean overwriteExistingFiles?: boolean
req?: PayloadRequest req?: PayloadRequest
draft?: boolean draft?: boolean
/**
* context, which will then be passed to req.context, which can be read by hooks
*/
context?: RequestContext
} }
export default async function createLocal<TSlug extends keyof GeneratedTypes['collections']>( export default async function createLocal<TSlug extends keyof GeneratedTypes['collections']>(
@@ -47,7 +52,9 @@ export default async function createLocal<TSlug extends keyof GeneratedTypes['co
overwriteExistingFiles = false, overwriteExistingFiles = false,
req = {} as PayloadRequest, req = {} as PayloadRequest,
draft, draft,
context,
} = options; } = options;
setRequestContext(req, context);
const collection = payload.collections[collectionSlug]; const collection = payload.collections[collectionSlug];
const defaultLocale = payload?.config?.localization ? payload?.config?.localization?.defaultLocale : null; const defaultLocale = payload?.config?.localization ? payload?.config?.localization?.defaultLocale : null;

View File

@@ -1,6 +1,6 @@
import { Config as GeneratedTypes } from '../../../generated-types'; import { Config as GeneratedTypes } from '../../../generated-types';
import { Document, Where } from '../../../types'; import { Document, Where } from '../../../types';
import { PayloadRequest } from '../../../express/types'; import { PayloadRequest, RequestContext } from '../../../express/types';
import { Payload } from '../../../payload'; import { Payload } from '../../../payload';
import deleteOperation from '../delete'; import deleteOperation from '../delete';
import deleteByID from '../deleteByID'; import deleteByID from '../deleteByID';
@@ -8,6 +8,7 @@ import { getDataLoader } from '../../dataloader';
import i18n from '../../../translations/init'; import i18n from '../../../translations/init';
import { APIError } from '../../../errors'; import { APIError } from '../../../errors';
import { BulkOperationResult } from '../../config/types'; import { BulkOperationResult } from '../../config/types';
import { setRequestContext } from '../../../express/setRequestContext';
export type BaseOptions<T extends keyof GeneratedTypes['collections']> = { export type BaseOptions<T extends keyof GeneratedTypes['collections']> = {
collection: T collection: T
@@ -17,6 +18,10 @@ export type BaseOptions<T extends keyof GeneratedTypes['collections']> = {
user?: Document user?: Document
overrideAccess?: boolean overrideAccess?: boolean
showHiddenFields?: boolean showHiddenFields?: boolean
/**
* context, which will then be passed to req.context, which can be read by hooks
*/
context?: RequestContext
} }
export type ByIDOptions<T extends keyof GeneratedTypes['collections']> = BaseOptions<T> & { export type ByIDOptions<T extends keyof GeneratedTypes['collections']> = BaseOptions<T> & {
@@ -45,6 +50,7 @@ async function deleteLocal<TSlug extends keyof GeneratedTypes['collections']>(pa
user, user,
overrideAccess = true, overrideAccess = true,
showHiddenFields, showHiddenFields,
context,
} = options; } = options;
const collection = payload.collections[collectionSlug]; const collection = payload.collections[collectionSlug];
@@ -63,6 +69,7 @@ async function deleteLocal<TSlug extends keyof GeneratedTypes['collections']>(pa
payload, payload,
i18n: i18n(payload.config.i18n), i18n: i18n(payload.config.i18n),
} as PayloadRequest; } as PayloadRequest;
setRequestContext(req, context);
if (!req.t) req.t = req.i18n.t; if (!req.t) req.t = req.i18n.t;
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req); if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req);

View File

@@ -2,11 +2,12 @@ import { Config as GeneratedTypes } from 'payload/generated-types';
import { PaginatedDocs } from '../../../mongoose/types'; import { PaginatedDocs } from '../../../mongoose/types';
import { Document, Where } from '../../../types'; import { Document, Where } from '../../../types';
import { Payload } from '../../../payload'; import { Payload } from '../../../payload';
import { PayloadRequest } from '../../../express/types'; import { PayloadRequest, RequestContext } from '../../../express/types';
import find from '../find'; import find from '../find';
import { getDataLoader } from '../../dataloader'; import { getDataLoader } from '../../dataloader';
import i18n from '../../../translations/init'; import i18n from '../../../translations/init';
import { APIError } from '../../../errors'; import { APIError } from '../../../errors';
import { setRequestContext } from '../../../express/setRequestContext';
export type Options<T extends keyof GeneratedTypes['collections']> = { export type Options<T extends keyof GeneratedTypes['collections']> = {
collection: T collection: T
@@ -25,6 +26,10 @@ export type Options<T extends keyof GeneratedTypes['collections']> = {
where?: Where where?: Where
draft?: boolean draft?: boolean
req?: PayloadRequest req?: PayloadRequest
/**
* context, which will then be passed to req.context, which can be read by hooks
*/
context?: RequestContext
} }
export default async function findLocal<T extends keyof GeneratedTypes['collections']>( export default async function findLocal<T extends keyof GeneratedTypes['collections']>(
@@ -48,7 +53,9 @@ export default async function findLocal<T extends keyof GeneratedTypes['collecti
draft = false, draft = false,
pagination = true, pagination = true,
req = {} as PayloadRequest, req = {} as PayloadRequest,
context,
} = options; } = options;
setRequestContext(options.req, context);
const collection = payload.collections[collectionSlug]; const collection = payload.collections[collectionSlug];
const defaultLocale = payload?.config?.localization ? payload?.config?.localization?.defaultLocale : null; const defaultLocale = payload?.config?.localization ? payload?.config?.localization?.defaultLocale : null;

View File

@@ -1,11 +1,12 @@
import { Config as GeneratedTypes } from 'payload/generated-types'; import { Config as GeneratedTypes } from 'payload/generated-types';
import { PayloadRequest } from '../../../express/types'; import { PayloadRequest, RequestContext } from '../../../express/types';
import { Document } from '../../../types'; import { Document } from '../../../types';
import findByID from '../findByID'; import findByID from '../findByID';
import { Payload } from '../../../payload'; import { Payload } from '../../../payload';
import { getDataLoader } from '../../dataloader'; import { getDataLoader } from '../../dataloader';
import i18n from '../../../translations/init'; import i18n from '../../../translations/init';
import { APIError } from '../../../errors'; import { APIError } from '../../../errors';
import { setRequestContext } from '../../../express/setRequestContext';
export type Options<T extends keyof GeneratedTypes['collections']> = { export type Options<T extends keyof GeneratedTypes['collections']> = {
collection: T collection: T
@@ -20,6 +21,10 @@ export type Options<T extends keyof GeneratedTypes['collections']> = {
disableErrors?: boolean disableErrors?: boolean
req?: PayloadRequest req?: PayloadRequest
draft?: boolean draft?: boolean
/**
* context, which will then be passed to req.context, which can be read by hooks
*/
context?: RequestContext,
} }
export default async function findByIDLocal<T extends keyof GeneratedTypes['collections']>( export default async function findByIDLocal<T extends keyof GeneratedTypes['collections']>(
@@ -39,7 +44,9 @@ export default async function findByIDLocal<T extends keyof GeneratedTypes['coll
showHiddenFields, showHiddenFields,
req = {} as PayloadRequest, req = {} as PayloadRequest,
draft = false, draft = false,
context,
} = options; } = options;
setRequestContext(options.req, context);
const collection = payload.collections[collectionSlug]; const collection = payload.collections[collectionSlug];
const defaultLocale = payload?.config?.localization ? payload?.config?.localization?.defaultLocale : null; const defaultLocale = payload?.config?.localization ? payload?.config?.localization?.defaultLocale : null;

View File

@@ -1,12 +1,13 @@
import { Config as GeneratedTypes } from 'payload/generated-types'; import { Config as GeneratedTypes } from 'payload/generated-types';
import { Payload } from '../../../payload'; import { Payload } from '../../../payload';
import { Document } from '../../../types'; import { Document } from '../../../types';
import { PayloadRequest } from '../../../express/types'; import { PayloadRequest, RequestContext } from '../../../express/types';
import { TypeWithVersion } from '../../../versions/types'; import { TypeWithVersion } from '../../../versions/types';
import findVersionByID from '../findVersionByID'; import findVersionByID from '../findVersionByID';
import { getDataLoader } from '../../dataloader'; import { getDataLoader } from '../../dataloader';
import i18n from '../../../translations/init'; import i18n from '../../../translations/init';
import { APIError } from '../../../errors'; import { APIError } from '../../../errors';
import { setRequestContext } from '../../../express/setRequestContext';
export type Options<T extends keyof GeneratedTypes['collections']> = { export type Options<T extends keyof GeneratedTypes['collections']> = {
collection: T collection: T
@@ -19,6 +20,11 @@ export type Options<T extends keyof GeneratedTypes['collections']> = {
showHiddenFields?: boolean showHiddenFields?: boolean
disableErrors?: boolean disableErrors?: boolean
req?: PayloadRequest req?: PayloadRequest
draft?: boolean
/**
* context, which will then be passed to req.context, which can be read by hooks
*/
context?: RequestContext,
} }
export default async function findVersionByIDLocal<T extends keyof GeneratedTypes['collections']>( export default async function findVersionByIDLocal<T extends keyof GeneratedTypes['collections']>(
@@ -35,7 +41,9 @@ export default async function findVersionByIDLocal<T extends keyof GeneratedType
disableErrors = false, disableErrors = false,
showHiddenFields, showHiddenFields,
req = {} as PayloadRequest, req = {} as PayloadRequest,
context,
} = options; } = options;
setRequestContext(options.req, context);
const collection = payload.collections[collectionSlug]; const collection = payload.collections[collectionSlug];
const defaultLocale = payload?.config?.localization ? payload?.config?.localization?.defaultLocale : null; const defaultLocale = payload?.config?.localization ? payload?.config?.localization?.defaultLocale : null;

View File

@@ -3,11 +3,12 @@ import { Payload } from '../../../payload';
import { Document, Where } from '../../../types'; import { Document, Where } from '../../../types';
import { PaginatedDocs } from '../../../mongoose/types'; import { PaginatedDocs } from '../../../mongoose/types';
import { TypeWithVersion } from '../../../versions/types'; import { TypeWithVersion } from '../../../versions/types';
import { PayloadRequest } from '../../../express/types'; import { PayloadRequest, RequestContext } from '../../../express/types';
import findVersions from '../findVersions'; import findVersions from '../findVersions';
import { getDataLoader } from '../../dataloader'; import { getDataLoader } from '../../dataloader';
import i18nInit from '../../../translations/init'; import i18nInit from '../../../translations/init';
import { APIError } from '../../../errors'; import { APIError } from '../../../errors';
import { setRequestContext } from '../../../express/setRequestContext';
export type Options<T extends keyof GeneratedTypes['collections']> = { export type Options<T extends keyof GeneratedTypes['collections']> = {
collection: T collection: T
@@ -21,6 +22,11 @@ export type Options<T extends keyof GeneratedTypes['collections']> = {
showHiddenFields?: boolean showHiddenFields?: boolean
sort?: string sort?: string
where?: Where where?: Where
draft?: boolean
/**
* context, which will then be passed to req.context, which can be read by hooks
*/
context?: RequestContext,
} }
export default async function findVersionsLocal<T extends keyof GeneratedTypes['collections']>( export default async function findVersionsLocal<T extends keyof GeneratedTypes['collections']>(
@@ -39,6 +45,7 @@ export default async function findVersionsLocal<T extends keyof GeneratedTypes['
overrideAccess = true, overrideAccess = true,
showHiddenFields, showHiddenFields,
sort, sort,
context,
} = options; } = options;
const collection = payload.collections[collectionSlug]; const collection = payload.collections[collectionSlug];
@@ -57,6 +64,7 @@ export default async function findVersionsLocal<T extends keyof GeneratedTypes['
payload, payload,
i18n, i18n,
} as PayloadRequest; } as PayloadRequest;
setRequestContext(req, context);
if (!req.t) req.t = req.i18n.t; if (!req.t) req.t = req.i18n.t;
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req); if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req);

View File

@@ -1,11 +1,12 @@
import { Config as GeneratedTypes } from 'payload/generated-types'; import { Config as GeneratedTypes } from 'payload/generated-types';
import { Payload } from '../../../payload'; import { Payload } from '../../../payload';
import { PayloadRequest } from '../../../express/types'; import { PayloadRequest, RequestContext } from '../../../express/types';
import { Document } from '../../../types'; import { Document } from '../../../types';
import { getDataLoader } from '../../dataloader'; import { getDataLoader } from '../../dataloader';
import restoreVersion from '../restoreVersion'; import restoreVersion from '../restoreVersion';
import i18nInit from '../../../translations/init'; import i18nInit from '../../../translations/init';
import { APIError } from '../../../errors'; import { APIError } from '../../../errors';
import { setRequestContext } from '../../../express/setRequestContext';
export type Options<T extends keyof GeneratedTypes['collections']> = { export type Options<T extends keyof GeneratedTypes['collections']> = {
collection: T collection: T
@@ -16,6 +17,11 @@ export type Options<T extends keyof GeneratedTypes['collections']> = {
user?: Document user?: Document
overrideAccess?: boolean overrideAccess?: boolean
showHiddenFields?: boolean showHiddenFields?: boolean
draft?: boolean
/**
* context, which will then be passed to req.context, which can be read by hooks
*/
context?: RequestContext,
} }
export default async function restoreVersionLocal<T extends keyof GeneratedTypes['collections']>( export default async function restoreVersionLocal<T extends keyof GeneratedTypes['collections']>(
@@ -31,6 +37,7 @@ export default async function restoreVersionLocal<T extends keyof GeneratedTypes
user, user,
overrideAccess = true, overrideAccess = true,
showHiddenFields, showHiddenFields,
context,
} = options; } = options;
const collection = payload.collections[collectionSlug]; const collection = payload.collections[collectionSlug];
@@ -49,6 +56,7 @@ export default async function restoreVersionLocal<T extends keyof GeneratedTypes
i18n, i18n,
t: i18n.t, t: i18n.t,
} as PayloadRequest; } as PayloadRequest;
setRequestContext(req, context);
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req); if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req);

View File

@@ -4,13 +4,14 @@ import { Payload } from '../../../payload';
import { Document, Where } from '../../../types'; import { Document, Where } from '../../../types';
import getFileByPath from '../../../uploads/getFileByPath'; import getFileByPath from '../../../uploads/getFileByPath';
import update from '../update'; import update from '../update';
import { PayloadRequest } from '../../../express/types'; import { PayloadRequest, RequestContext } from '../../../express/types';
import { getDataLoader } from '../../dataloader'; import { getDataLoader } from '../../dataloader';
import { File } from '../../../uploads/types'; import { File } from '../../../uploads/types';
import i18nInit from '../../../translations/init'; import i18nInit from '../../../translations/init';
import { APIError } from '../../../errors'; import { APIError } from '../../../errors';
import updateByID from '../updateByID'; import updateByID from '../updateByID';
import { BulkOperationResult } from '../../config/types'; import { BulkOperationResult } from '../../config/types';
import { setRequestContext } from '../../../express/setRequestContext';
export type BaseOptions<TSlug extends keyof GeneratedTypes['collections']> = { export type BaseOptions<TSlug extends keyof GeneratedTypes['collections']> = {
collection: TSlug collection: TSlug
@@ -26,6 +27,10 @@ export type BaseOptions<TSlug extends keyof GeneratedTypes['collections']> = {
overwriteExistingFiles?: boolean overwriteExistingFiles?: boolean
draft?: boolean draft?: boolean
autosave?: boolean autosave?: boolean
/**
* context, which will then be passed to req.context, which can be read by hooks
*/
context?: RequestContext
} }
export type ByIDOptions<TSlug extends keyof GeneratedTypes['collections']> = BaseOptions<TSlug> & { export type ByIDOptions<TSlug extends keyof GeneratedTypes['collections']> = BaseOptions<TSlug> & {
@@ -60,6 +65,7 @@ async function updateLocal<TSlug extends keyof GeneratedTypes['collections']>(pa
autosave, autosave,
id, id,
where, where,
context,
} = options; } = options;
const collection = payload.collections[collectionSlug]; const collection = payload.collections[collectionSlug];
@@ -82,6 +88,7 @@ async function updateLocal<TSlug extends keyof GeneratedTypes['collections']>(pa
file: file ?? await getFileByPath(filePath), file: file ?? await getFileByPath(filePath),
}, },
} as PayloadRequest; } as PayloadRequest;
setRequestContext(req, context);
if (!req.t) req.t = req.i18n.t; if (!req.t) req.t = req.i18n.t;
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req); if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req);

View File

@@ -143,6 +143,7 @@ async function restoreVersion<T extends TypeWithID = any>(args: Arguments): Prom
req, req,
overrideAccess, overrideAccess,
showHiddenFields, showHiddenFields,
context: req.context,
}); });
// ///////////////////////////////////// // /////////////////////////////////////
@@ -155,6 +156,7 @@ async function restoreVersion<T extends TypeWithID = any>(args: Arguments): Prom
result = await hook({ result = await hook({
req, req,
doc: result, doc: result,
context: req.context,
}) || result; }) || result;
}, Promise.resolve()); }, Promise.resolve());
@@ -168,6 +170,7 @@ async function restoreVersion<T extends TypeWithID = any>(args: Arguments): Prom
previousDoc: prevDocWithLocales, previousDoc: prevDocWithLocales,
entityConfig: collectionConfig, entityConfig: collectionConfig,
operation: 'update', operation: 'update',
context: req.context,
req, req,
}); });
@@ -183,6 +186,7 @@ async function restoreVersion<T extends TypeWithID = any>(args: Arguments): Prom
req, req,
previousDoc: prevDocWithLocales, previousDoc: prevDocWithLocales,
operation: 'update', operation: 'update',
context: req.context,
}) || result; }) || result;
}, Promise.resolve()); }, Promise.resolve());

View File

@@ -46,6 +46,7 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
args = (await hook({ args = (await hook({
args, args,
operation: 'update', operation: 'update',
context: args.req.context,
})) || args; })) || args;
}, Promise.resolve()); }, Promise.resolve());
@@ -149,6 +150,7 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
req, req,
overrideAccess: true, overrideAccess: true,
showHiddenFields: true, showHiddenFields: true,
context: req.context,
}); });
await deleteAssociatedFiles({ config, collectionConfig, files: filesToUpload, doc: docWithLocales, t, overrideDelete: false }); await deleteAssociatedFiles({ config, collectionConfig, files: filesToUpload, doc: docWithLocales, t, overrideDelete: false });
@@ -165,6 +167,7 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
operation: 'update', operation: 'update',
overrideAccess, overrideAccess,
req, req,
context: req.context,
}); });
// ///////////////////////////////////// // /////////////////////////////////////
@@ -179,6 +182,7 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
req, req,
operation: 'update', operation: 'update',
originalDoc, originalDoc,
context: req.context,
})) || data; })) || data;
}, Promise.resolve()); }, Promise.resolve());
@@ -202,6 +206,7 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
req, req,
originalDoc, originalDoc,
operation: 'update', operation: 'update',
context: req.context,
})) || data; })) || data;
}, Promise.resolve()); }, Promise.resolve());
@@ -218,6 +223,7 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
operation: 'update', operation: 'update',
req, req,
skipValidation: shouldSaveDraft || data._status === 'draft', skipValidation: shouldSaveDraft || data._status === 'draft',
context: req.context,
}); });
// ///////////////////////////////////// // /////////////////////////////////////
@@ -275,6 +281,7 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
req, req,
overrideAccess, overrideAccess,
showHiddenFields, showHiddenFields,
context: req.context,
}); });
// ///////////////////////////////////// // /////////////////////////////////////
@@ -287,6 +294,7 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
result = await hook({ result = await hook({
req, req,
doc: result, doc: result,
context: req.context,
}) || result; }) || result;
}, Promise.resolve()); }, Promise.resolve());
@@ -301,6 +309,7 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
entityConfig: collectionConfig, entityConfig: collectionConfig,
operation: 'update', operation: 'update',
req, req,
context: req.context,
}); });
// ///////////////////////////////////// // /////////////////////////////////////
@@ -315,6 +324,7 @@ async function update<TSlug extends keyof GeneratedTypes['collections']>(
previousDoc: originalDoc, previousDoc: originalDoc,
req, req,
operation: 'update', operation: 'update',
context: req.context,
}) || result; }) || result;
}, Promise.resolve()); }, Promise.resolve());

View File

@@ -49,6 +49,7 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
args = (await hook({ args = (await hook({
args, args,
operation: 'update', operation: 'update',
context: args.req.context,
})) || args; })) || args;
}, Promise.resolve()); }, Promise.resolve());
@@ -130,6 +131,7 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
req, req,
overrideAccess: true, overrideAccess: true,
showHiddenFields: true, showHiddenFields: true,
context: req.context,
}); });
// ///////////////////////////////////// // /////////////////////////////////////
@@ -165,6 +167,7 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
operation: 'update', operation: 'update',
overrideAccess, overrideAccess,
req, req,
context: req.context,
}); });
// ///////////////////////////////////// // /////////////////////////////////////
@@ -179,6 +182,7 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
req, req,
operation: 'update', operation: 'update',
originalDoc, originalDoc,
context: req.context,
})) || data; })) || data;
}, Promise.resolve()); }, Promise.resolve());
@@ -202,6 +206,7 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
req, req,
originalDoc, originalDoc,
operation: 'update', operation: 'update',
context: req.context,
})) || data; })) || data;
}, Promise.resolve()); }, Promise.resolve());
@@ -218,18 +223,19 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
operation: 'update', operation: 'update',
req, req,
skipValidation: shouldSaveDraft || data._status === 'draft', skipValidation: shouldSaveDraft || data._status === 'draft',
context: req.context,
}); });
// ///////////////////////////////////// // /////////////////////////////////////
// Handle potential password update // Handle potential password update
// ///////////////////////////////////// // /////////////////////////////////////
const dataToUpdate: Record<string, unknown> = { ...result } const dataToUpdate: Record<string, unknown> = { ...result };
if (shouldSavePassword && typeof password === 'string') { if (shouldSavePassword && typeof password === 'string') {
const { hash, salt } = await generatePasswordSaltHash({ password }) const { hash, salt } = await generatePasswordSaltHash({ password });
dataToUpdate.salt = salt dataToUpdate.salt = salt;
dataToUpdate.hash = hash dataToUpdate.hash = hash;
delete data.password; delete data.password;
delete result.password; delete result.password;
} }
@@ -287,6 +293,7 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
req, req,
overrideAccess, overrideAccess,
showHiddenFields, showHiddenFields,
context: req.context,
}); });
// ///////////////////////////////////// // /////////////////////////////////////
@@ -299,6 +306,7 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
result = await hook({ result = await hook({
req, req,
doc: result, doc: result,
context: req.context,
}) || result; }) || result;
}, Promise.resolve()); }, Promise.resolve());
@@ -313,6 +321,7 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
entityConfig: collectionConfig, entityConfig: collectionConfig,
operation: 'update', operation: 'update',
req, req,
context: req.context,
}); });
// ///////////////////////////////////// // /////////////////////////////////////
@@ -327,6 +336,7 @@ async function updateByID<TSlug extends keyof GeneratedTypes['collections']>(
previousDoc: originalDoc, previousDoc: originalDoc,
req, req,
operation: 'update', operation: 'update',
context: req.context,
}) || result; }) || result;
}, Promise.resolve()); }, Promise.resolve());

View File

@@ -0,0 +1,10 @@
import type { Response, NextFunction } from 'express';
import type { PayloadRequest } from '../types';
import { setRequestContext } from '../setRequestContext';
function defaultPayload(req: PayloadRequest, res: Response, next: NextFunction) {
setRequestContext(req);
next();
}
export default defaultPayload;

View File

@@ -21,11 +21,11 @@ const errorHandler = (config: SanitizedConfig, logger: Logger) => async (err: AP
} }
if (req.collection && typeof req.collection.config.hooks.afterError === 'function') { if (req.collection && typeof req.collection.config.hooks.afterError === 'function') {
({ response, status } = await req.collection.config.hooks.afterError(err, response) || { response, status }); ({ response, status } = await req.collection.config.hooks.afterError(err, response, req.context) || { response, status });
} }
if (typeof config.hooks.afterError === 'function') { if (typeof config.hooks.afterError === 'function') {
({ response, status } = await config.hooks.afterError(err, response) || { response, status }); ({ response, status } = await config.hooks.afterError(err, response, req.context) || { response, status });
} }
res.status(status).send(response); res.status(status).send(response);

View File

@@ -14,6 +14,7 @@ import { PayloadRequest } from '../types';
import corsHeaders from './corsHeaders'; import corsHeaders from './corsHeaders';
import convertPayload from './convertPayload'; import convertPayload from './convertPayload';
import { i18nMiddleware } from './i18n'; import { i18nMiddleware } from './i18n';
import defaultPayload from './defaultPayload';
const middleware = (payload: Payload): any => { const middleware = (payload: Payload): any => {
const rateLimitOptions: { const rateLimitOptions: {
@@ -32,6 +33,7 @@ const middleware = (payload: Payload): any => {
} }
return [ return [
defaultPayload,
...(payload.config.express.preMiddleware || []), ...(payload.config.express.preMiddleware || []),
rateLimit(rateLimitOptions), rateLimit(rateLimitOptions),
passport.initialize(), passport.initialize(),

View File

@@ -0,0 +1,18 @@
/* eslint-disable no-param-reassign */
import type { PayloadRequest, RequestContext } from './types';
/**
* This makes sure that req.context always exists (is {}) and populates it with an optional default context.
* This function mutates directly to avoid copying memory. As payloadRequest is not a primitive, the scope of the mutation is not limited to this function but should also be reflected in the calling function.
*/
export function setRequestContext(req: PayloadRequest = { context: null } as PayloadRequest, context: RequestContext = {}) {
if (req.context) {
if (Object.keys(req.context).length === 0 && req.context.constructor === Object) { // check if req.context is just {}
req.context = context; // Faster - ... is bad for performance
} else {
req.context = { ...req.context, ...context }; // Merge together
}
} else {
req.context = context;
}
}

View File

@@ -24,10 +24,16 @@ export declare type PayloadRequest<U = any> = Request & {
* - Configuration from payload-config.ts * - Configuration from payload-config.ts
* - MongoDB model for this collection * - MongoDB model for this collection
* - GraphQL type metadata * - GraphQL type metadata
* */ */
collection?: Collection; collection?: Collection;
/** What triggered this request */ /** What triggered this request */
payloadAPI?: 'REST' | 'local' | 'GraphQL'; payloadAPI?: 'REST' | 'local' | 'GraphQL';
/** context allows you to pass your own data to the request object as context
* This is useful for, for example, passing data from a beforeChange hook to an afterChange hook.
* payoadContext can also be fully typed using declare module
* {@link https://payloadcms.com/docs/hooks/context More info in the Payload Documentation}.
*/
context: RequestContext;
/** Uploaded files */ /** Uploaded files */
files?: { files?: {
/** /**
@@ -49,3 +55,7 @@ export declare type PayloadRequest<U = any> = Request & {
[slug: string]: (q: unknown) => Document; [slug: string]: (q: unknown) => Document;
}; };
}; };
export interface RequestContext {
[key: string]: unknown;
}

View File

@@ -6,7 +6,7 @@ import type { EditorProps } from '@monaco-editor/react';
import { Operation, Where } from '../../types'; import { Operation, Where } from '../../types';
import { SanitizedConfig } from '../../config/types'; import { SanitizedConfig } from '../../config/types';
import { TypeWithID } from '../../collections/config/types'; import { TypeWithID } from '../../collections/config/types';
import { PayloadRequest } from '../../express/types'; import { PayloadRequest, RequestContext } from '../../express/types';
import { ConditionalDateProps } from '../../admin/components/elements/DatePicker/types'; import { ConditionalDateProps } from '../../admin/components/elements/DatePicker/types';
import { Description } from '../../admin/components/forms/FieldDescription/types'; import { Description } from '../../admin/components/forms/FieldDescription/types';
import { User } from '../../auth'; import { User } from '../../auth';
@@ -33,6 +33,7 @@ export type FieldHookArgs<T extends TypeWithID = any, P = any, S = any> = {
/** The value of the field. */ /** The value of the field. */
value?: P, value?: P,
previousValue?: P, previousValue?: P,
context: RequestContext
} }
export type FieldHook<T extends TypeWithID = any, P = any, S = any> = (args: FieldHookArgs<T, P, S>) => Promise<P> | P; export type FieldHook<T extends TypeWithID = any, P = any, S = any> = (args: FieldHookArgs<T, P, S>) => Promise<P> | P;

View File

@@ -1,6 +1,6 @@
import { SanitizedCollectionConfig } from '../../../collections/config/types'; import { SanitizedCollectionConfig } from '../../../collections/config/types';
import { SanitizedGlobalConfig } from '../../../globals/config/types'; import { SanitizedGlobalConfig } from '../../../globals/config/types';
import { PayloadRequest } from '../../../express/types'; import { PayloadRequest, RequestContext } from '../../../express/types';
import { traverseFields } from './traverseFields'; import { traverseFields } from './traverseFields';
import deepCopyObject from '../../../utilities/deepCopyObject'; import deepCopyObject from '../../../utilities/deepCopyObject';
@@ -11,6 +11,7 @@ type Args<T> = {
entityConfig: SanitizedCollectionConfig | SanitizedGlobalConfig entityConfig: SanitizedCollectionConfig | SanitizedGlobalConfig
operation: 'create' | 'update' operation: 'create' | 'update'
req: PayloadRequest req: PayloadRequest
context: RequestContext
} }
export const afterChange = async <T extends Record<string, unknown>>({ export const afterChange = async <T extends Record<string, unknown>>({
@@ -20,6 +21,7 @@ export const afterChange = async <T extends Record<string, unknown>>({
entityConfig, entityConfig,
operation, operation,
req, req,
context,
}: Args<T>): Promise<T> => { }: Args<T>): Promise<T> => {
const doc = deepCopyObject(incomingDoc); const doc = deepCopyObject(incomingDoc);
@@ -33,6 +35,7 @@ export const afterChange = async <T extends Record<string, unknown>>({
previousSiblingDoc: previousDoc, previousSiblingDoc: previousDoc,
siblingDoc: doc, siblingDoc: doc,
siblingData: data, siblingData: data,
context,
}); });
return doc; return doc;

View File

@@ -1,5 +1,5 @@
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
import { PayloadRequest } from '../../../express/types'; import { PayloadRequest, RequestContext } from '../../../express/types';
import { Field, fieldAffectsData, TabAsField, tabHasName } from '../../config/types'; import { Field, fieldAffectsData, TabAsField, tabHasName } from '../../config/types';
import { traverseFields } from './traverseFields'; import { traverseFields } from './traverseFields';
@@ -13,6 +13,7 @@ type Args = {
req: PayloadRequest req: PayloadRequest
siblingData: Record<string, unknown> siblingData: Record<string, unknown>
siblingDoc: Record<string, unknown> siblingDoc: Record<string, unknown>
context: RequestContext
} }
// This function is responsible for the following actions, in order: // This function is responsible for the following actions, in order:
@@ -28,6 +29,7 @@ export const promise = async ({
req, req,
siblingData, siblingData,
siblingDoc, siblingDoc,
context,
}: Args): Promise<void> => { }: Args): Promise<void> => {
if (fieldAffectsData(field)) { if (fieldAffectsData(field)) {
// Execute hooks // Execute hooks
@@ -45,6 +47,7 @@ export const promise = async ({
siblingData, siblingData,
operation, operation,
req, req,
context,
}); });
if (hookedValue !== undefined) { if (hookedValue !== undefined) {
@@ -67,6 +70,7 @@ export const promise = async ({
req, req,
siblingData: siblingData?.[field.name] as Record<string, unknown> || {}, siblingData: siblingData?.[field.name] as Record<string, unknown> || {},
siblingDoc: siblingDoc[field.name] as Record<string, unknown>, siblingDoc: siblingDoc[field.name] as Record<string, unknown>,
context,
}); });
break; break;
@@ -88,6 +92,7 @@ export const promise = async ({
req, req,
siblingData: siblingData?.[field.name]?.[i] || {}, siblingData: siblingData?.[field.name]?.[i] || {},
siblingDoc: { ...row } || {}, siblingDoc: { ...row } || {},
context,
})); }));
}); });
await Promise.all(promises); await Promise.all(promises);
@@ -114,6 +119,7 @@ export const promise = async ({
req, req,
siblingData: siblingData?.[field.name]?.[i] || {}, siblingData: siblingData?.[field.name]?.[i] || {},
siblingDoc: { ...row } || {}, siblingDoc: { ...row } || {},
context,
})); }));
} }
}); });
@@ -135,6 +141,7 @@ export const promise = async ({
req, req,
siblingData: siblingData || {}, siblingData: siblingData || {},
siblingDoc: { ...siblingDoc }, siblingDoc: { ...siblingDoc },
context,
}); });
break; break;
@@ -161,6 +168,7 @@ export const promise = async ({
previousDoc, previousDoc,
siblingData: tabSiblingData, siblingData: tabSiblingData,
siblingDoc: tabSiblingDoc, siblingDoc: tabSiblingDoc,
context,
}); });
break; break;
@@ -177,6 +185,7 @@ export const promise = async ({
req, req,
siblingData: siblingData || {}, siblingData: siblingData || {},
siblingDoc: { ...siblingDoc }, siblingDoc: { ...siblingDoc },
context,
}); });
break; break;
} }

View File

@@ -1,6 +1,6 @@
import { Field, TabAsField } from '../../config/types'; import { Field, TabAsField } from '../../config/types';
import { promise } from './promise'; import { promise } from './promise';
import { PayloadRequest } from '../../../express/types'; import { PayloadRequest, RequestContext } from '../../../express/types';
type Args = { type Args = {
data: Record<string, unknown> data: Record<string, unknown>
@@ -12,6 +12,7 @@ type Args = {
req: PayloadRequest req: PayloadRequest
siblingData: Record<string, unknown> siblingData: Record<string, unknown>
siblingDoc: Record<string, unknown> siblingDoc: Record<string, unknown>
context: RequestContext
} }
export const traverseFields = async ({ export const traverseFields = async ({
@@ -24,6 +25,7 @@ export const traverseFields = async ({
req, req,
siblingData, siblingData,
siblingDoc, siblingDoc,
context,
}: Args): Promise<void> => { }: Args): Promise<void> => {
const promises = []; const promises = [];
@@ -38,6 +40,7 @@ export const traverseFields = async ({
req, req,
siblingData, siblingData,
siblingDoc, siblingDoc,
context,
})); }));
}); });

View File

@@ -1,6 +1,6 @@
import { SanitizedCollectionConfig } from '../../../collections/config/types'; import { SanitizedCollectionConfig } from '../../../collections/config/types';
import { SanitizedGlobalConfig } from '../../../globals/config/types'; import { SanitizedGlobalConfig } from '../../../globals/config/types';
import { PayloadRequest } from '../../../express/types'; import { PayloadRequest, RequestContext } from '../../../express/types';
import { traverseFields } from './traverseFields'; import { traverseFields } from './traverseFields';
import deepCopyObject from '../../../utilities/deepCopyObject'; import deepCopyObject from '../../../utilities/deepCopyObject';
@@ -14,6 +14,7 @@ type Args = {
req: PayloadRequest req: PayloadRequest
overrideAccess: boolean overrideAccess: boolean
showHiddenFields: boolean showHiddenFields: boolean
context: RequestContext
} }
export async function afterRead<T = any>(args: Args): Promise<T> { export async function afterRead<T = any>(args: Args): Promise<T> {
@@ -27,6 +28,7 @@ export async function afterRead<T = any>(args: Args): Promise<T> {
req, req,
overrideAccess, overrideAccess,
showHiddenFields, showHiddenFields,
context,
} = args; } = args;
const doc = deepCopyObject(incomingDoc); const doc = deepCopyObject(incomingDoc);
@@ -51,6 +53,7 @@ export async function afterRead<T = any>(args: Args): Promise<T> {
req, req,
siblingDoc: doc, siblingDoc: doc,
showHiddenFields, showHiddenFields,
context,
}); });
await Promise.all(fieldPromises); await Promise.all(fieldPromises);

View File

@@ -1,6 +1,6 @@
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
import { Field, fieldAffectsData, TabAsField, tabHasName } from '../../config/types'; import { Field, fieldAffectsData, TabAsField, tabHasName } from '../../config/types';
import { PayloadRequest } from '../../../express/types'; import { PayloadRequest, RequestContext } from '../../../express/types';
import { traverseFields } from './traverseFields'; import { traverseFields } from './traverseFields';
import richTextRelationshipPromise from '../../richText/richTextRelationshipPromise'; import richTextRelationshipPromise from '../../richText/richTextRelationshipPromise';
import relationshipPopulationPromise from './relationshipPopulationPromise'; import relationshipPopulationPromise from './relationshipPopulationPromise';
@@ -18,6 +18,7 @@ type Args = {
overrideAccess: boolean overrideAccess: boolean
siblingDoc: Record<string, unknown> siblingDoc: Record<string, unknown>
showHiddenFields: boolean showHiddenFields: boolean
context: RequestContext
} }
// This function is responsible for the following actions, in order: // This function is responsible for the following actions, in order:
@@ -41,6 +42,7 @@ export const promise = async ({
req, req,
siblingDoc, siblingDoc,
showHiddenFields, showHiddenFields,
context,
}: Args): Promise<void> => { }: Args): Promise<void> => {
if (fieldAffectsData(field) && field.hidden && typeof siblingDoc[field.name] !== 'undefined' && !showHiddenFields) { if (fieldAffectsData(field) && field.hidden && typeof siblingDoc[field.name] !== 'undefined' && !showHiddenFields) {
delete siblingDoc[field.name]; delete siblingDoc[field.name];
@@ -161,6 +163,7 @@ export const promise = async ({
siblingData: siblingDoc, siblingData: siblingDoc,
operation: 'read', operation: 'read',
req, req,
context,
}); });
if (hookedValue !== undefined) { if (hookedValue !== undefined) {
@@ -178,6 +181,7 @@ export const promise = async ({
siblingData: siblingDoc, siblingData: siblingDoc,
req, req,
value: siblingDoc[field.name], value: siblingDoc[field.name],
context,
}); });
if (hookedValue !== undefined) { if (hookedValue !== undefined) {
@@ -227,6 +231,7 @@ export const promise = async ({
req, req,
siblingDoc: groupDoc, siblingDoc: groupDoc,
showHiddenFields, showHiddenFields,
context,
}); });
break; break;
@@ -250,6 +255,7 @@ export const promise = async ({
req, req,
siblingDoc: row || {}, siblingDoc: row || {},
showHiddenFields, showHiddenFields,
context,
}); });
}); });
} else if (!shouldHoistLocalizedValue && typeof rows === 'object' && rows !== null) { } else if (!shouldHoistLocalizedValue && typeof rows === 'object' && rows !== null) {
@@ -269,6 +275,7 @@ export const promise = async ({
req, req,
siblingDoc: row || {}, siblingDoc: row || {},
showHiddenFields, showHiddenFields,
context,
}); });
}); });
} }
@@ -298,6 +305,7 @@ export const promise = async ({
req, req,
siblingDoc: row || {}, siblingDoc: row || {},
showHiddenFields, showHiddenFields,
context,
}); });
} }
}); });
@@ -321,6 +329,7 @@ export const promise = async ({
req, req,
siblingDoc: row || {}, siblingDoc: row || {},
showHiddenFields, showHiddenFields,
context,
}); });
} }
}); });
@@ -346,6 +355,7 @@ export const promise = async ({
req, req,
siblingDoc, siblingDoc,
showHiddenFields, showHiddenFields,
context,
}); });
break; break;
@@ -371,6 +381,7 @@ export const promise = async ({
req, req,
siblingDoc: tabDoc, siblingDoc: tabDoc,
showHiddenFields, showHiddenFields,
context,
}); });
break; break;
@@ -390,6 +401,7 @@ export const promise = async ({
req, req,
siblingDoc, siblingDoc,
showHiddenFields, showHiddenFields,
context,
}); });
break; break;
} }

View File

@@ -1,6 +1,6 @@
import { Field, TabAsField } from '../../config/types'; import { Field, TabAsField } from '../../config/types';
import { promise } from './promise'; import { promise } from './promise';
import { PayloadRequest } from '../../../express/types'; import { PayloadRequest, RequestContext } from '../../../express/types';
type Args = { type Args = {
currentDepth: number currentDepth: number
@@ -15,6 +15,7 @@ type Args = {
overrideAccess: boolean overrideAccess: boolean
siblingDoc: Record<string, unknown> siblingDoc: Record<string, unknown>
showHiddenFields: boolean showHiddenFields: boolean
context: RequestContext
} }
export const traverseFields = ({ export const traverseFields = ({
@@ -30,6 +31,7 @@ export const traverseFields = ({
req, req,
siblingDoc, siblingDoc,
showHiddenFields, showHiddenFields,
context,
}: Args): void => { }: Args): void => {
fields.forEach((field) => { fields.forEach((field) => {
fieldPromises.push(promise({ fieldPromises.push(promise({
@@ -45,6 +47,7 @@ export const traverseFields = ({
req, req,
siblingDoc, siblingDoc,
showHiddenFields, showHiddenFields,
context,
})); }));
}); });
}; };

View File

@@ -1,7 +1,7 @@
import { SanitizedCollectionConfig } from '../../../collections/config/types'; import { SanitizedCollectionConfig } from '../../../collections/config/types';
import { SanitizedGlobalConfig } from '../../../globals/config/types'; import { SanitizedGlobalConfig } from '../../../globals/config/types';
import { Operation } from '../../../types'; import { Operation } from '../../../types';
import { PayloadRequest } from '../../../express/types'; import { PayloadRequest, RequestContext } from '../../../express/types';
import { traverseFields } from './traverseFields'; import { traverseFields } from './traverseFields';
import { ValidationError } from '../../../errors'; import { ValidationError } from '../../../errors';
import deepCopyObject from '../../../utilities/deepCopyObject'; import deepCopyObject from '../../../utilities/deepCopyObject';
@@ -15,6 +15,7 @@ type Args<T> = {
operation: Operation operation: Operation
req: PayloadRequest req: PayloadRequest
skipValidation?: boolean skipValidation?: boolean
context: RequestContext
} }
export const beforeChange = async <T extends Record<string, unknown>>({ export const beforeChange = async <T extends Record<string, unknown>>({
@@ -26,6 +27,7 @@ export const beforeChange = async <T extends Record<string, unknown>>({
operation, operation,
req, req,
skipValidation, skipValidation,
context,
}: Args<T>): Promise<T> => { }: Args<T>): Promise<T> => {
const data = deepCopyObject(incomingData); const data = deepCopyObject(incomingData);
const mergeLocaleActions = []; const mergeLocaleActions = [];
@@ -46,6 +48,7 @@ export const beforeChange = async <T extends Record<string, unknown>>({
siblingDocWithLocales: docWithLocales, siblingDocWithLocales: docWithLocales,
fields: entityConfig.fields, fields: entityConfig.fields,
skipValidation, skipValidation,
context,
}); });
if (errors.length > 0) { if (errors.length > 0) {

View File

@@ -2,7 +2,7 @@
import merge from 'deepmerge'; import merge from 'deepmerge';
import { Field, fieldAffectsData, TabAsField, tabHasName } from '../../config/types'; import { Field, fieldAffectsData, TabAsField, tabHasName } from '../../config/types';
import { Operation } from '../../../types'; import { Operation } from '../../../types';
import { PayloadRequest } from '../../../express/types'; import { PayloadRequest, RequestContext } from '../../../express/types';
import getValueWithDefault from '../../getDefaultValue'; import getValueWithDefault from '../../getDefaultValue';
import { traverseFields } from './traverseFields'; import { traverseFields } from './traverseFields';
import { getExistingRowDoc } from './getExistingRowDoc'; import { getExistingRowDoc } from './getExistingRowDoc';
@@ -23,6 +23,7 @@ type Args = {
siblingDoc: Record<string, unknown> siblingDoc: Record<string, unknown>
siblingDocWithLocales?: Record<string, unknown> siblingDocWithLocales?: Record<string, unknown>
skipValidation: boolean skipValidation: boolean
context: RequestContext
} }
// This function is responsible for the following actions, in order: // This function is responsible for the following actions, in order:
@@ -49,6 +50,7 @@ export const promise = async ({
siblingDoc, siblingDoc,
siblingDocWithLocales, siblingDocWithLocales,
skipValidation, skipValidation,
context,
}: Args): Promise<void> => { }: Args): Promise<void> => {
const passesCondition = (field.admin?.condition) ? field.admin.condition(data, siblingData, { user: req.user }) : true; const passesCondition = (field.admin?.condition) ? field.admin.condition(data, siblingData, { user: req.user }) : true;
let skipValidationFromHere = skipValidation || !passesCondition; let skipValidationFromHere = skipValidation || !passesCondition;
@@ -96,6 +98,7 @@ export const promise = async ({
siblingData, siblingData,
operation, operation,
req, req,
context,
}); });
if (hookedValue !== undefined) { if (hookedValue !== undefined) {
@@ -208,6 +211,7 @@ export const promise = async ({
siblingDoc: siblingDoc[field.name] as Record<string, unknown>, siblingDoc: siblingDoc[field.name] as Record<string, unknown>,
siblingDocWithLocales: siblingDocWithLocales[field.name] as Record<string, unknown>, siblingDocWithLocales: siblingDocWithLocales[field.name] as Record<string, unknown>,
skipValidation: skipValidationFromHere, skipValidation: skipValidationFromHere,
context,
}); });
break; break;
@@ -234,6 +238,7 @@ export const promise = async ({
siblingDoc: getExistingRowDoc(row, siblingDoc[field.name]), siblingDoc: getExistingRowDoc(row, siblingDoc[field.name]),
siblingDocWithLocales: getExistingRowDoc(row, siblingDocWithLocales[field.name]), siblingDocWithLocales: getExistingRowDoc(row, siblingDocWithLocales[field.name]),
skipValidation: skipValidationFromHere, skipValidation: skipValidationFromHere,
context,
})); }));
}); });
@@ -267,6 +272,7 @@ export const promise = async ({
siblingDoc: getExistingRowDoc(row, siblingDoc[field.name]), siblingDoc: getExistingRowDoc(row, siblingDoc[field.name]),
siblingDocWithLocales: getExistingRowDoc(row, siblingDocWithLocales[field.name]), siblingDocWithLocales: getExistingRowDoc(row, siblingDocWithLocales[field.name]),
skipValidation: skipValidationFromHere, skipValidation: skipValidationFromHere,
context,
})); }));
} }
}); });
@@ -294,6 +300,7 @@ export const promise = async ({
siblingDoc, siblingDoc,
siblingDocWithLocales, siblingDocWithLocales,
skipValidation: skipValidationFromHere, skipValidation: skipValidationFromHere,
context,
}); });
break; break;
@@ -331,6 +338,7 @@ export const promise = async ({
siblingDoc: tabSiblingDoc, siblingDoc: tabSiblingDoc,
siblingDocWithLocales: tabSiblingDocWithLocales, siblingDocWithLocales: tabSiblingDocWithLocales,
skipValidation: skipValidationFromHere, skipValidation: skipValidationFromHere,
context,
}); });
break; break;
@@ -352,6 +360,7 @@ export const promise = async ({
siblingDoc, siblingDoc,
siblingDocWithLocales, siblingDocWithLocales,
skipValidation: skipValidationFromHere, skipValidation: skipValidationFromHere,
context,
}); });
break; break;

View File

@@ -1,7 +1,7 @@
import { Field, TabAsField } from '../../config/types'; import { Field, TabAsField } from '../../config/types';
import { promise } from './promise'; import { promise } from './promise';
import { Operation } from '../../../types'; import { Operation } from '../../../types';
import { PayloadRequest } from '../../../express/types'; import { PayloadRequest, RequestContext } from '../../../express/types';
type Args = { type Args = {
data: Record<string, unknown> data: Record<string, unknown>
@@ -18,6 +18,7 @@ type Args = {
siblingDoc: Record<string, unknown> siblingDoc: Record<string, unknown>
siblingDocWithLocales: Record<string, unknown> siblingDocWithLocales: Record<string, unknown>
skipValidation?: boolean skipValidation?: boolean
context: RequestContext
} }
export const traverseFields = async ({ export const traverseFields = async ({
@@ -35,6 +36,7 @@ export const traverseFields = async ({
siblingDoc, siblingDoc,
siblingDocWithLocales, siblingDocWithLocales,
skipValidation, skipValidation,
context,
}: Args): Promise<void> => { }: Args): Promise<void> => {
const promises = []; const promises = [];
@@ -54,6 +56,7 @@ export const traverseFields = async ({
siblingDoc, siblingDoc,
siblingDocWithLocales, siblingDocWithLocales,
skipValidation, skipValidation,
context,
})); }));
}); });

View File

@@ -1,6 +1,6 @@
import { SanitizedCollectionConfig } from '../../../collections/config/types'; import { SanitizedCollectionConfig } from '../../../collections/config/types';
import { SanitizedGlobalConfig } from '../../../globals/config/types'; import { SanitizedGlobalConfig } from '../../../globals/config/types';
import { PayloadRequest } from '../../../express/types'; import { PayloadRequest, RequestContext } from '../../../express/types';
import { traverseFields } from './traverseFields'; import { traverseFields } from './traverseFields';
import deepCopyObject from '../../../utilities/deepCopyObject'; import deepCopyObject from '../../../utilities/deepCopyObject';
@@ -12,6 +12,7 @@ type Args<T> = {
operation: 'create' | 'update' operation: 'create' | 'update'
overrideAccess: boolean overrideAccess: boolean
req: PayloadRequest req: PayloadRequest
context: RequestContext
} }
export const beforeValidate = async <T extends Record<string, unknown>>({ export const beforeValidate = async <T extends Record<string, unknown>>({
@@ -22,6 +23,7 @@ export const beforeValidate = async <T extends Record<string, unknown>>({
operation, operation,
overrideAccess, overrideAccess,
req, req,
context,
}: Args<T>): Promise<T> => { }: Args<T>): Promise<T> => {
const data = deepCopyObject(incomingData); const data = deepCopyObject(incomingData);
@@ -35,6 +37,7 @@ export const beforeValidate = async <T extends Record<string, unknown>>({
req, req,
siblingData: data, siblingData: data,
siblingDoc: doc, siblingDoc: doc,
context,
}); });
return data; return data;

View File

@@ -1,5 +1,5 @@
/* eslint-disable no-param-reassign */ /* eslint-disable no-param-reassign */
import { PayloadRequest } from '../../../express/types'; import { PayloadRequest, RequestContext } from '../../../express/types';
import { Field, fieldAffectsData, TabAsField, tabHasName, valueIsValueWithRelation } from '../../config/types'; import { Field, fieldAffectsData, TabAsField, tabHasName, valueIsValueWithRelation } from '../../config/types';
import { traverseFields } from './traverseFields'; import { traverseFields } from './traverseFields';
@@ -13,6 +13,7 @@ type Args<T> = {
req: PayloadRequest req: PayloadRequest
siblingData: Record<string, unknown> siblingData: Record<string, unknown>
siblingDoc: Record<string, unknown> siblingDoc: Record<string, unknown>
context: RequestContext
} }
// This function is responsible for the following actions, in order: // This function is responsible for the following actions, in order:
@@ -30,6 +31,7 @@ export const promise = async <T>({
req, req,
siblingData, siblingData,
siblingDoc, siblingDoc,
context,
}: Args<T>): Promise<void> => { }: Args<T>): Promise<void> => {
if (fieldAffectsData(field)) { if (fieldAffectsData(field)) {
if (field.name === 'id') { if (field.name === 'id') {
@@ -170,6 +172,7 @@ export const promise = async <T>({
siblingData, siblingData,
operation, operation,
req, req,
context,
}); });
if (hookedValue !== undefined) { if (hookedValue !== undefined) {
@@ -207,6 +210,7 @@ export const promise = async <T>({
req, req,
siblingData: groupData, siblingData: groupData,
siblingDoc: groupDoc, siblingDoc: groupDoc,
context,
}); });
break; break;
@@ -228,6 +232,7 @@ export const promise = async <T>({
req, req,
siblingData: row, siblingData: row,
siblingDoc: siblingDoc[field.name]?.[i] || {}, siblingDoc: siblingDoc[field.name]?.[i] || {},
context,
})); }));
}); });
await Promise.all(promises); await Promise.all(promises);
@@ -254,6 +259,7 @@ export const promise = async <T>({
req, req,
siblingData: row, siblingData: row,
siblingDoc: siblingDoc[field.name]?.[i] || {}, siblingDoc: siblingDoc[field.name]?.[i] || {},
context,
})); }));
} }
}); });
@@ -275,6 +281,7 @@ export const promise = async <T>({
req, req,
siblingData, siblingData,
siblingDoc, siblingDoc,
context,
}); });
break; break;
@@ -301,6 +308,7 @@ export const promise = async <T>({
req, req,
siblingData: tabSiblingData, siblingData: tabSiblingData,
siblingDoc: tabSiblingDoc, siblingDoc: tabSiblingDoc,
context,
}); });
break; break;
@@ -317,6 +325,7 @@ export const promise = async <T>({
req, req,
siblingData, siblingData,
siblingDoc, siblingDoc,
context,
}); });
break; break;

View File

@@ -1,4 +1,4 @@
import { PayloadRequest } from '../../../express/types'; import { PayloadRequest, RequestContext } from '../../../express/types';
import { Field, TabAsField } from '../../config/types'; import { Field, TabAsField } from '../../config/types';
import { promise } from './promise'; import { promise } from './promise';
@@ -12,6 +12,7 @@ type Args<T> = {
req: PayloadRequest req: PayloadRequest
siblingData: Record<string, unknown> siblingData: Record<string, unknown>
siblingDoc: Record<string, unknown> siblingDoc: Record<string, unknown>
context: RequestContext
} }
export const traverseFields = async <T>({ export const traverseFields = async <T>({
@@ -24,6 +25,7 @@ export const traverseFields = async <T>({
req, req,
siblingData, siblingData,
siblingDoc, siblingDoc,
context,
}: Args<T>): Promise<void> => { }: Args<T>): Promise<void> => {
const promises = []; const promises = [];
fields.forEach((field) => { fields.forEach((field) => {
@@ -37,6 +39,7 @@ export const traverseFields = async <T>({
req, req,
siblingData, siblingData,
siblingDoc, siblingDoc,
context,
})); }));
}); });
await Promise.all(promises); await Promise.all(promises);

View File

@@ -112,6 +112,7 @@ async function findOne<T extends Record<string, unknown>>(args: Args): Promise<T
req, req,
overrideAccess, overrideAccess,
showHiddenFields, showHiddenFields,
context: req.context,
}); });
// ///////////////////////////////////// // /////////////////////////////////////

View File

@@ -106,6 +106,7 @@ async function findVersionByID<T extends TypeWithVersion<T> = any>(args: Argumen
req, req,
overrideAccess, overrideAccess,
showHiddenFields, showHiddenFields,
context: req.context,
}); });
// ///////////////////////////////////// // /////////////////////////////////////

View File

@@ -107,6 +107,7 @@ async function findVersions<T extends TypeWithVersion<T>>(
overrideAccess, overrideAccess,
showHiddenFields, showHiddenFields,
findMany: true, findMany: true,
context: req.context,
}), }),
}))), }))),
} as PaginatedDocs<T>; } as PaginatedDocs<T>;

View File

@@ -6,6 +6,7 @@ import { Document } from '../../../types';
import findOne from '../findOne'; import findOne from '../findOne';
import i18nInit from '../../../translations/init'; import i18nInit from '../../../translations/init';
import { APIError } from '../../../errors'; import { APIError } from '../../../errors';
import { setRequestContext } from '../../../express/setRequestContext';
export type Options<T extends keyof GeneratedTypes['globals']> = { export type Options<T extends keyof GeneratedTypes['globals']> = {
slug: T slug: T
@@ -50,6 +51,7 @@ export default async function findOneLocal<T extends keyof GeneratedTypes['globa
i18n, i18n,
t: i18n.t, t: i18n.t,
} as PayloadRequest; } as PayloadRequest;
setRequestContext(req);
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req); if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req);

View File

@@ -7,6 +7,7 @@ import { TypeWithVersion } from '../../../versions/types';
import findVersionByID from '../findVersionByID'; import findVersionByID from '../findVersionByID';
import i18nInit from '../../../translations/init'; import i18nInit from '../../../translations/init';
import { APIError } from '../../../errors'; import { APIError } from '../../../errors';
import { setRequestContext } from '../../../express/setRequestContext';
export type Options<T extends keyof GeneratedTypes['globals']> = { export type Options<T extends keyof GeneratedTypes['globals']> = {
slug: T slug: T
@@ -52,6 +53,7 @@ export default async function findVersionByIDLocal<T extends keyof GeneratedType
i18n, i18n,
t: i18n.t, t: i18n.t,
} as PayloadRequest; } as PayloadRequest;
setRequestContext(req);
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req); if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req);

View File

@@ -8,6 +8,7 @@ import { getDataLoader } from '../../../collections/dataloader';
import i18nInit from '../../../translations/init'; import i18nInit from '../../../translations/init';
import { APIError } from '../../../errors'; import { APIError } from '../../../errors';
import { TypeWithVersion } from '../../../versions/types'; import { TypeWithVersion } from '../../../versions/types';
import { setRequestContext } from '../../../express/setRequestContext';
export type Options<T extends keyof GeneratedTypes['globals']> = { export type Options<T extends keyof GeneratedTypes['globals']> = {
slug: T slug: T
@@ -57,6 +58,7 @@ export default async function findVersionsLocal<T extends keyof GeneratedTypes['
i18n, i18n,
t: i18n.t, t: i18n.t,
} as PayloadRequest; } as PayloadRequest;
setRequestContext(req);
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req); if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req);

View File

@@ -6,6 +6,7 @@ import { Document } from '../../../types';
import restoreVersion from '../restoreVersion'; import restoreVersion from '../restoreVersion';
import i18nInit from '../../../translations/init'; import i18nInit from '../../../translations/init';
import { APIError } from '../../../errors'; import { APIError } from '../../../errors';
import { setRequestContext } from '../../../express/setRequestContext';
export type Options<T extends keyof GeneratedTypes['globals']> = { export type Options<T extends keyof GeneratedTypes['globals']> = {
slug: string slug: string
@@ -49,6 +50,7 @@ export default async function restoreVersionLocal<T extends keyof GeneratedTypes
i18n, i18n,
t: i18n.t, t: i18n.t,
} as PayloadRequest; } as PayloadRequest;
setRequestContext(req);
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req); if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req);

View File

@@ -7,6 +7,7 @@ import update from '../update';
import { getDataLoader } from '../../../collections/dataloader'; import { getDataLoader } from '../../../collections/dataloader';
import i18nInit from '../../../translations/init'; import i18nInit from '../../../translations/init';
import { APIError } from '../../../errors'; import { APIError } from '../../../errors';
import { setRequestContext } from '../../../express/setRequestContext';
export type Options<TSlug extends keyof GeneratedTypes['globals']> = { export type Options<TSlug extends keyof GeneratedTypes['globals']> = {
slug: TSlug slug: TSlug
@@ -52,6 +53,7 @@ export default async function updateLocal<TSlug extends keyof GeneratedTypes['gl
i18n, i18n,
t: i18n.t, t: i18n.t,
} as PayloadRequest; } as PayloadRequest;
setRequestContext(req);
if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req); if (!req.payloadDataLoader) req.payloadDataLoader = getDataLoader(req);

View File

@@ -106,6 +106,7 @@ async function restoreVersion<T extends TypeWithVersion<T> = any>(args: Argument
req, req,
overrideAccess, overrideAccess,
showHiddenFields, showHiddenFields,
context: req.context,
}); });
// ///////////////////////////////////// // /////////////////////////////////////
@@ -132,6 +133,7 @@ async function restoreVersion<T extends TypeWithVersion<T> = any>(args: Argument
entityConfig: globalConfig, entityConfig: globalConfig,
operation: 'update', operation: 'update',
req, req,
context: req.context,
}); });
// ///////////////////////////////////// // /////////////////////////////////////

View File

@@ -100,6 +100,7 @@ async function update<TSlug extends keyof GeneratedTypes['globals']>(
req, req,
overrideAccess: true, overrideAccess: true,
showHiddenFields, showHiddenFields,
context: req.context,
}); });
// ///////////////////////////////////// // /////////////////////////////////////
@@ -113,6 +114,7 @@ async function update<TSlug extends keyof GeneratedTypes['globals']>(
operation: 'update', operation: 'update',
overrideAccess, overrideAccess,
req, req,
context: req.context,
}); });
// ///////////////////////////////////// // /////////////////////////////////////
@@ -155,6 +157,7 @@ async function update<TSlug extends keyof GeneratedTypes['globals']>(
operation: 'update', operation: 'update',
req, req,
skipValidation: shouldSaveDraft, skipValidation: shouldSaveDraft,
context: req.context,
}); });
// ///////////////////////////////////// // /////////////////////////////////////
@@ -207,6 +210,7 @@ async function update<TSlug extends keyof GeneratedTypes['globals']>(
req, req,
overrideAccess, overrideAccess,
showHiddenFields, showHiddenFields,
context: req.context,
}); });
// ///////////////////////////////////// // /////////////////////////////////////
@@ -232,6 +236,7 @@ async function update<TSlug extends keyof GeneratedTypes['globals']>(
previousDoc: originalDoc, previousDoc: originalDoc,
entityConfig: globalConfig, entityConfig: globalConfig,
operation: 'update', operation: 'update',
context: req.context,
req, req,
}); });

View File

@@ -22,7 +22,7 @@ const errorHandler = async (
}; };
if (afterErrorHook) { if (afterErrorHook) {
({ response } = await afterErrorHook(err, response) || { response }); ({ response } = await afterErrorHook(err, response, null) || { response });
} }
return response; return response;

View File

@@ -3,6 +3,8 @@ import { InitOptions } from './config/types';
import { initHTTP } from './initHTTP'; import { initHTTP } from './initHTTP';
import { Payload as LocalPayload, BasePayload } from './payload'; import { Payload as LocalPayload, BasePayload } from './payload';
import type { RequestContext } from './express/types';
export { getPayload } from './payload'; export { getPayload } from './payload';
require('isomorphic-fetch'); require('isomorphic-fetch');
@@ -25,3 +27,5 @@ const payload = new Payload();
export default payload; export default payload;
module.exports = payload; module.exports = payload;
// Export RequestContext type
export type { RequestContext };

View File

@@ -0,0 +1,68 @@
/* eslint-disable no-param-reassign */
import payload from '../../../../src';
import { CollectionConfig } from '../../../../src/collections/config/types';
import type { PayloadRequest } from '../../../../src/types';
export const contextHooksSlug = 'context-hooks';
const ContextHooks: CollectionConfig = {
slug: contextHooksSlug,
access: {
read: () => true,
create: () => true,
delete: () => true,
update: () => true,
},
hooks: {
beforeOperation: [async ({ context, args }) => {
// eslint-disable-next-line prefer-destructuring
const req: PayloadRequest = args.req;
if (!req.query || !Object.keys(req.query).length) {
return args;
}
Object.keys(req.query).forEach((key) => {
if (key.startsWith('context_')) {
// Strip 'context_' from key, add it to context object and remove it from query params
const newKey = key.substring('context_'.length);
context[newKey] = req.query[key];
delete req.query[key];
}
});
return args;
}],
beforeChange: [({ context, data, req }) => {
if (!context.secretValue) {
context.secretValue = 'secret';
}
if (req.context !== context) {
throw new Error('req.context !== context');
}
return data;
}],
afterChange: [async ({ context, doc }) => {
if (context.triggerAfterChange === false) { // Make sure we don't trigger afterChange again and again in an infinite loop
return;
}
await payload.update({
collection: contextHooksSlug,
id: doc.id,
data: {
value: context.secretValue ?? '',
},
context: {
triggerAfterChange: false, // Make sure we don't trigger afterChange again and again in an infinite loop. This should be done via context and not a potential disableHooks property, as we want to specifically test the context functionality here
},
});
}],
},
fields: [
{
name: 'value',
type: 'text',
},
],
};
export default ContextHooks;

View File

@@ -5,9 +5,11 @@ import NestedAfterReadHooks from './collections/NestedAfterReadHooks';
import ChainingHooks from './collections/ChainingHooks'; import ChainingHooks from './collections/ChainingHooks';
import Relations from './collections/Relations'; import Relations from './collections/Relations';
import Users, { seedHooksUsers } from './collections/Users'; import Users, { seedHooksUsers } from './collections/Users';
import ContextHooks from './collections/ContextHooks';
export default buildConfigWithDefaults({ export default buildConfigWithDefaults({
collections: [ collections: [
ContextHooks,
TransformHooks, TransformHooks,
Hooks, Hooks,
NestedAfterReadHooks, NestedAfterReadHooks,

View File

@@ -12,13 +12,16 @@ import type { NestedAfterReadHook } from './payload-types';
import { hooksUsersSlug } from './collections/Users'; import { hooksUsersSlug } from './collections/Users';
import { devUser, regularUser } from '../credentials'; import { devUser, regularUser } from '../credentials';
import { AuthenticationError } from '../../src/errors'; import { AuthenticationError } from '../../src/errors';
import { contextHooksSlug } from './collections/ContextHooks';
let client: RESTClient; let client: RESTClient;
let apiUrl;
describe('Hooks', () => { describe('Hooks', () => {
beforeAll(async () => { beforeAll(async () => {
const { serverURL } = await initPayloadTest({ __dirname, init: { local: false } }); const { serverURL } = await initPayloadTest({ __dirname, init: { local: false } });
client = new RESTClient(config, { serverURL, defaultSlug: transformSlug }); client = new RESTClient(config, { serverURL, defaultSlug: transformSlug });
apiUrl = `${serverURL}/api`;
}); });
afterAll(async () => { afterAll(async () => {
@@ -151,6 +154,63 @@ describe('Hooks', () => {
expect(retrievedDocs[0].text).toEqual('ok!!'); expect(retrievedDocs[0].text).toEqual('ok!!');
}); });
it('should pass context from beforeChange to afterChange', async () => {
const document = await payload.create({
collection: contextHooksSlug,
data: {
value: 'wrongvalue',
},
});
const retrievedDoc = await payload.findByID({
collection: contextHooksSlug,
id: document.id,
});
expect(retrievedDoc.value).toEqual('secret');
});
it('should pass context from local API to hooks', async () => {
const document = await payload.create({
collection: contextHooksSlug,
data: {
value: 'wrongvalue',
},
context: {
secretValue: 'data from local API',
},
});
const retrievedDoc = await payload.findByID({
collection: contextHooksSlug,
id: document.id,
});
expect(retrievedDoc.value).toEqual('data from local API');
});
it('should pass context from rest API to hooks', async () => {
const params = new URLSearchParams({
context_secretValue: 'data from rest API',
});
// send context as query params. It will be parsed by the beforeOperation hook
const response = await fetch(`${apiUrl}/${contextHooksSlug}?${params.toString()}`, {
body: JSON.stringify({
value: 'wrongvalue',
}),
method: 'post',
});
const document = (await response.json()).doc;
const retrievedDoc = await payload.findByID({
collection: contextHooksSlug,
id: document.id,
});
expect(retrievedDoc.value).toEqual('data from rest API');
});
}); });
describe('auth collection hooks', () => { describe('auth collection hooks', () => {