diff --git a/docs/content/docs/config/hooks.md b/docs/content/docs/config/hooks.md index 27d0259105e..0068ad744c6 100644 --- a/docs/content/docs/config/hooks.md +++ b/docs/content/docs/config/hooks.md @@ -9,7 +9,7 @@ The differences will be explicitly called out below. For each hook, the fields hooks are applied to **all fields first** in parallel, followed by the list hooks. -Hook functions support `async` and, with the exception of `resolveInput`, do not need a return value. +All hook functions are async and, with the exception of `resolveInput`, do not return a value. When operating on multiple values the hooks are called individually for each item being updated, created or deleted. @@ -27,44 +27,19 @@ export default config({ create: async args => { /* ... */ }, update: async args => { /* ... */ }, }, - validate: { - create: async args => { /* ... */ }, - update: async args => { /* ... */ }, - delete: async args => { /* ... */ }, - }, - beforeOperation: { - create: async args => { /* ... */ }, - update: async args => { /* ... */ }, - delete: async args => { /* ... */ }, - }, - afterOperation: { - create: async args => { /* ... */ }, - update: async args => { /* ... */ }, - delete: async args => { /* ... */ }, - } + validateInput: async args => { /* ... */ }, + validateDelete: async args => { /* ... */ }, + beforeOperation: async args => { /* ... */ }, + afterOperation: async args => { /* ... */ }, }, fields: { someFieldName: text({ hooks: { - resolveInput: { - create: async args => { /* ... */ }, - update: async args => { /* ... */ }, - }, - validate: { - create: async args => { /* ... */ }, - update: async args => { /* ... */ }, - delete: async args => { /* ... */ }, - }, - beforeOperation: { - create: async args => { /* ... */ }, - update: async args => { /* ... */ }, - delete: async args => { /* ... */ }, - }, - afterOperation: { - create: async args => { /* ... */ }, - update: async args => { /* ... */ }, - delete: async args => { /* ... */ }, - } + resolveInput: async args => { /* ... */ }, + validateInput: async args => { /* ... */ }, + validateDelete: async args => { /* ... */ }, + beforeOperation: async args => { /* ... */ }, + afterOperation: async args => { /* ... */ }, }, }), }, @@ -75,13 +50,13 @@ export default config({ ### resolveInput -The `resolveInput` hook is a transform for mutating the input `data` value prior to calling any other successive hooks, as part of the operation. +The `resolveInput` function is used to modify or augment the `data` values passed in to a `create` or `update` operation. This hook is the final stage in the [data resolving process](#resolved-data-stages), and is invoked after access control has been applied. For field hooks, the return value should be an updated value for that specific field. For list hooks, the return value should be a [`resolved data`](#resolved-data-stages) object. -The result of `resolveInput` hooks is accessible as the argument `resolvedData` in the hooks that follow, for the remainder of the operation. +The result of `resolveInput` will be passed as `resolvedData` into the next stages of the operation. | Argument | Description | | :------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | @@ -90,7 +65,7 @@ The result of `resolveInput` hooks is accessible as the argument `resolvedData` | `operation` | The operation being performed (`'create'` or `'update'`). | | `inputData` | The value of `data` passed into the mutation. | | `item` | The currently stored item (`undefined` for `create` operations). This object is an internal database item. [DB API](../context/db-items) for more details on internal database items. | -| `resolvedData` | A [`resolved data`](#resolved-data-stages) object. The resolved data value after default values, relationship resolvers, field resolvers, and `resolveInput` hooks have been applied. | +| `resolvedData` | A [`resolved data`](#resolved-data-stages) object. The resolved data value after default values, relationship resolvers, and field resolvers have been applied. | | `context` | The [`KeystoneContext`](../context/overview) object of the originating GraphQL operation. | ```typescript @@ -101,59 +76,32 @@ export default config({ lists: { SomeListName: list({ hooks: { - resolveInput: { - create: async ({ - listKey, - operation, // always 'create' - inputData, - item, - resolvedData, - context, - }) => { - /* ... */ - return resolvedData; - }, - update: async ({ - listKey, - operation, // always 'update' - inputData, - item, - resolvedData, - context, - }) => { - /* ... */ - return resolvedData; - }, + resolveInput: async ({ + listKey, + operation, + inputData, + item, + resolvedData, + context, + }) => { + /* ... */ + return resolvedData; }, }, fields: { someFieldName: text({ hooks: { - resolveInput: { - create: async ({ - listKey, - fieldKey, - operation, - inputData, - item, - resolvedData, - context, - }) => { - /* ... */ - return resolvedData[fieldKey]; - }, - update: async ({ - listKey, - fieldKey, - operation, - inputData, - item, - resolvedData, - context, - }) => { - /* ... */ - return resolvedData[fieldKey]; - }, + resolveInput: async ({ + listKey, + fieldKey, + operation, + inputData, + item, + resolvedData, + context, + }) => { + /* ... */ + return resolvedData[fieldKey]; }, }, }), @@ -163,26 +111,23 @@ export default config({ }); ``` -### validate - -The `validate` hooks can be used to validate your [`resolvedData`](#resolved-data-stages) before a `create` or `update` operation completes, ensuring your expectations are met. -This hook can additionally be used to check your expectations as part of a `delete` operation. +### validateInput -For `create` and `update` operations, this hook is invoked after the respective `resolveInput` hooks has been run. +The `validateInput` function is used to validate the [`resolvedData`](#resolved-data-stages) that will be saved during a `create` or `update` operation. -This hook should report any validation errors using the `addValidationError(message)` function, which is provided as a parameter. -This is preferred to throwing to easily support more than one error message, if required. +It is invoked after the `resolveInput` hooks have been run. +If the `resolvedData` is invalid then the function should report validation errors with `addValidationError(msg)`. These error messages will be returned as a `ValidationFailureError` from the GraphQL API, and the operation will not be completed. | Argument | Description | | :------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `listKey` | The key of the list being operated on. | | `fieldKey` | The key of the field being operated on (field hooks only). | -| `operation` | The operation being performed (`'create'`, `'update'` or `'delete'`). | -| `inputData` | The value of `data` passed into the mutation (`undefined` for `delete` operations). | +| `operation` | The operation being performed (`'create'` or `'update'`). | +| `inputData` | The value of `data` passed into the mutation. | | `item` | The current value of the item being updated (`undefined` for `create` operations). This object is an internal database item. [DB API](../context/db-items) for more details on internal database items. | -| `resolvedData` | A [`resolved data`](#resolved-data-stages) object (`undefined` for `delete` operations). The resolved data value after all data resolver stages have been completed. | +| `resolvedData` | A [`resolved data`](#resolved-data-stages) object. The resolved data value after all data resolver stages have been completed. | | `context` | The [`KeystoneContext`](../context/overview) object of the originating GraphQL operation. | | `addValidationError(msg)` | Used to set a validation error. | @@ -194,65 +139,82 @@ export default config({ lists: { SomeListName: list({ hooks: { - validate: { - create: async ({ - listKey, - operation, - inputData, - resolvedData, - context, - addValidationError, - }) => { /* ... */ }, - update: async ({ - listKey, - operation, - inputData, - item, - resolvedData, - context, - addValidationError, - }) => { /* ... */ }, - delete: async ({ - listKey, - operation, - item, - context, - addValidationError, - }) => { /* ... */ }, - }, + validateInput: async ({ + listKey, + operation, + inputData, + item, + resolvedData, + context, + addValidationError, + }) => { /* ... */ }, }, fields: { someFieldName: text({ hooks: { - validate: { - create: async ({ - listKey, - fieldKey, - operation, - inputData, - resolvedData, - context, - addValidationError, - }) => { /* ... */ }, - update: async ({ - listKey, - fieldKey, - operation, - inputData, - item, - resolvedData, - context, - addValidationError, - }) => { /* ... */ }, - delete: async ({ - listKey, - fieldKey, - operation, - item, - context, - addValidationError, - }) => { /* ... */ }, - }, + validateInput: async ({ + listKey, + fieldKey, + operation, + inputData, + item, + resolvedData, + context, + addValidationError, + }) => { /* ... */ }, + }, + }), + }, + }), + }, +}); +``` + +### validateDelete + +The `validateDelete` function is used during a `delete` operation to validate that deleting the selected item will not cause an issue in your system. + +It is invoked after access control has been applied. + +If the delete operation is invalid then the function should report validation errors with `addValidationError(msg)`. +These error messages will be returned as a `ValidationFailureError` from the GraphQL API. + +| Argument | Description | +| :------------------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `listKey` | The key of the list being operated on. | +| `fieldKey` | The key of the field being operated on (field hooks only). | +| `operation` | The operation being performed (`'delete'`). | +| `item` | The value of the item to be deleted. This object is an internal database item. [DB API](../context/db-items) for more details on internal database items. | +| `context` | The [`KeystoneContext`](../context/overview) object of the originating GraphQL operation. | +| `addValidationError(msg)` | Used to set a validation error. | + +```typescript +import { config, list } from '@keystone-6/core'; +import { text } from '@keystone-6/core/fields'; + +export default config({ + lists: { + SomeListName: list({ + hooks: { + validateDelete: async ({ + listKey, + operation, + item, + context, + addValidationError, + }) => { /* ... */ }, + }, + fields: { + someFieldName: text({ + hooks: { + validateDelete: async ({ + listKey, + fieldKey, + operation, + item, + context, + addValidationError, + }) => { /* ... */ }, }, }), }, @@ -263,9 +225,9 @@ export default config({ ### beforeOperation -The `beforeOperation` hook is used to perform side effects just before the data is saved to the database (for a `create` or `update` operation), or deleted from the database (for `delete` operations). +The `beforeOperation` function is used to perform side effects just before the data is saved to the database (for a `create` or `update` operation), or deleted from the database (for `delete` operations). -It is invoked after the `resolveInput` and `validate` hooks, but before the database is updated by Prisma. +It is invoked after all `validateInput`/`validateDelete` hooks have been run, but before the database is updated. | Argument | Description | | :------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -285,59 +247,27 @@ export default config({ lists: { SomeListName: list({ hooks: { - beforeOperation: { - create: async ({ - listKey, - operation, - inputData, - resolvedData, - context, - }) => { /* ... */ }, - update: async ({ - listKey, - operation, - inputData, - item, - resolvedData, - context, - }) => { /* ... */ }, - delete: async ({ - listKey, - operation, - item, - context, - }) => { /* ... */ }, - }, + beforeOperation: async ({ + listKey, + operation, + inputData, + item, + resolvedData, + context, + }) => { /* ... */ }, }, fields: { someFieldName: text({ hooks: { - beforeOperation: { - create: async ({ - listKey, - fieldKey, - operation, - inputData, - resolvedData, - context, - }) => { /* ... */ }, - update: async ({ - listKey, - fieldKey, - operation, - inputData, - item, - resolvedData, - context, - }) => { /* ... */ }, - delete: async ({ - listKey, - fieldKey, - operation, - item, - context, - }) => { /* ... */ }, - }, + beforeOperation: async ({ + listKey, + fieldKey, + operation, + inputData, + item, + resolvedData, + context, + }) => { /* ... */ }, }, }), }, @@ -348,7 +278,7 @@ export default config({ ### afterOperation -The `afterOperation` hook is used to perform side effects after the data has been saved to the database (for a `create` or `update` operation), or deleted from the database (for `delete` operations). +The `afterOperation` function is used to perform side effects after the data has been saved to the database (for a `create` or `update` operation), or deleted from the database (for `delete` operations). | Argument | Description | | :------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -369,63 +299,29 @@ export default config({ lists: { SomeListName: list({ hooks: { - afterOperation: { - create: async ({ - listKey, - operation, - inputData, - item, - resolvedData, - context, - }) => { /* ... */ }, - update: async ({ - listKey, - operation, - inputData, - originalItem, - item, - resolvedData, - context, - }) => { /* ... */ }, - delete: async ({ - listKey, - operation, - originalItem, - context, - }) => { /* ... */ }, - }, + afterOperation: async ({ + listKey, + operation, + inputData, + originalItem, + item, + resolvedData, + context, + }) => { /* ... */ }, }, fields: { someFieldName: text({ hooks: { - afterOperation: { - create: async ({ - listKey, - fieldKey, - operation, - inputData, - item, - resolvedData, - context, - }) => { /* ... */ }, - update: async ({ - listKey, - fieldKey, - operation, - inputData, - originalItem, - item, - resolvedData, - context, - }) => { /* ... */ }, - delete: async ({ - listKey, - fieldKey, - operation, - originalItem, - context, - }) => { /* ... */ }, - }, + afterOperation: async ({ + listKey, + fieldKey, + operation, + inputData, + originalItem, + item, + resolvedData, + context, + }) => { /* ... */ }, }, }), },