From 78f59f3a653550fbc6ea18365d429301b9437962 Mon Sep 17 00:00:00 2001 From: Gautam Singh <5769869+gautamsi@users.noreply.github.com> Date: Wed, 24 Jul 2024 19:23:30 -0500 Subject: [PATCH 1/5] update hooks documentation --- docs/content/docs/config/hooks.md | 398 +++++++++++++++++++----------- 1 file changed, 249 insertions(+), 149 deletions(-) diff --git a/docs/content/docs/config/hooks.md b/docs/content/docs/config/hooks.md index 0068ad744c6..2689211761b 100644 --- a/docs/content/docs/config/hooks.md +++ b/docs/content/docs/config/hooks.md @@ -27,19 +27,44 @@ export default config({ create: async args => { /* ... */ }, update: async args => { /* ... */ }, }, - validateInput: async args => { /* ... */ }, - validateDelete: async args => { /* ... */ }, - beforeOperation: async args => { /* ... */ }, - afterOperation: 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 => { /* ... */ }, + } }, fields: { someFieldName: text({ hooks: { - resolveInput: async args => { /* ... */ }, - validateInput: async args => { /* ... */ }, - validateDelete: async args => { /* ... */ }, - beforeOperation: async args => { /* ... */ }, - afterOperation: async args => { /* ... */ }, + 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 => { /* ... */ }, + } }, }), }, @@ -50,13 +75,13 @@ export default config({ ### resolveInput -The `resolveInput` function is used to modify or augment the `data` values passed in to a `create` or `update` operation. +The `resolveInput` hook is used to modify or augment the `data` values passed in to a `create` or `update` operation functions. 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` will be passed as `resolvedData` into the next stages of the operation. +The result of `resolveInput` hooks will be passed as `resolvedData` into the next stages of the operation. | Argument | Description | | :------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | @@ -76,32 +101,59 @@ export default config({ lists: { SomeListName: list({ hooks: { - resolveInput: async ({ - listKey, - operation, - inputData, - item, - resolvedData, - context, - }) => { - /* ... */ - return resolvedData; + 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; + }, }, }, fields: { someFieldName: text({ hooks: { - resolveInput: async ({ - listKey, - fieldKey, - operation, - inputData, - item, - resolvedData, - context, - }) => { - /* ... */ - return resolvedData[fieldKey]; + 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]; + }, }, }, }), @@ -111,9 +163,9 @@ export default config({ }); ``` -### validateInput +### validate -The `validateInput` function is used to validate the [`resolvedData`](#resolved-data-stages) that will be saved during a `create` or `update` operation. +The `validate` hooks are used to validate the [`resolvedData`](#resolved-data-stages) that will be saved during a `create` or `update` operation, or validate that deleting the selected item will not cause an issue in your system during `delete` operation. It is invoked after the `resolveInput` hooks have been run. @@ -124,10 +176,10 @@ These error messages will be returned as a `ValidationFailureError` from the Gra | :------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `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'` or `'update'`). | -| `inputData` | The value of `data` passed into the mutation. | +| `operation` | The operation being performed (`'create'`, `'update'` or `'delete'`). | +| `inputData` | The value of `data` passed into the mutation (`undefined` for `delete` operations). | | `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. The resolved data value after all data resolver stages have been completed. | +| `resolvedData` | A [`resolved data`](#resolved-data-stages) object (`undefined` for `delete` operations). 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. | @@ -139,82 +191,65 @@ export default config({ lists: { SomeListName: list({ hooks: { - validateInput: async ({ - listKey, - operation, - inputData, - item, - resolvedData, - context, - addValidationError, - }) => { /* ... */ }, - }, - fields: { - someFieldName: text({ - hooks: { - 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, - }) => { /* ... */ }, + validateInput: { + create: async ({ + listKey, + operation, + inputData, + resolvedData, + context, + addValidationError, + }) => { /* ... */ }, + update: async ({ + listKey, + operation, + inputData, + item, + resolvedData, + context, + addValidationError, + }) => { /* ... */ }, + delete: async ({ + listKey, + operation, + item, + context, + addValidationError, + }) => { /* ... */ }, + }, }, fields: { someFieldName: text({ hooks: { - validateDelete: async ({ - listKey, - fieldKey, - operation, - item, - context, - addValidationError, - }) => { /* ... */ }, + validateInput: { + 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, + }) => { /* ... */ }, + }, }, }), }, @@ -222,12 +257,11 @@ export default config({ }, }); ``` - ### beforeOperation -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). +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). -It is invoked after all `validateInput`/`validateDelete` hooks have been run, but before the database is updated. +It is invoked after all `validate` hooks have been run, but before the database is updated. | Argument | Description | | :------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -247,27 +281,59 @@ export default config({ lists: { SomeListName: list({ hooks: { - beforeOperation: async ({ - listKey, - operation, - inputData, - item, - resolvedData, - context, - }) => { /* ... */ }, + beforeOperation: { + create: async ({ + listKey, + operation, + inputData, + resolvedData, + context, + }) => { /* ... */ }, + update: async ({ + listKey, + operation, + inputData, + item, + resolvedData, + context, + }) => { /* ... */ }, + delete: async ({ + listKey, + operation, + item, + context, + }) => { /* ... */ }, + }, }, fields: { someFieldName: text({ hooks: { - beforeOperation: async ({ - listKey, - fieldKey, - operation, - inputData, - item, - resolvedData, - context, - }) => { /* ... */ }, + 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, + }) => { /* ... */ }, + }, }, }), }, @@ -278,7 +344,7 @@ export default config({ ### afterOperation -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). +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). | Argument | Description | | :------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -299,29 +365,63 @@ export default config({ lists: { SomeListName: list({ hooks: { - afterOperation: async ({ - listKey, - operation, - inputData, - originalItem, - item, - resolvedData, - context, - }) => { /* ... */ }, + afterOperation: { + create: async ({ + listKey, + operation, + inputData, + item, + resolvedData, + context, + }) => { /* ... */ }, + update: async ({ + listKey, + operation, + inputData, + originalItem, + item, + resolvedData, + context, + }) => { /* ... */ }, + delete: async ({ + listKey, + operation, + originalItem, + context, + }) => { /* ... */ }, + }, }, fields: { someFieldName: text({ hooks: { - afterOperation: async ({ - listKey, - fieldKey, - operation, - inputData, - originalItem, - item, - resolvedData, - context, - }) => { /* ... */ }, + 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, + }) => { /* ... */ }, + }, }, }), }, From 1bf33ce9adca4e73d28f0a30c2a34baea22bd1be Mon Sep 17 00:00:00 2001 From: Daniel Cousens <413395+dcousens@users.noreply.github.com> Date: Thu, 25 Jul 2024 14:38:33 +1000 Subject: [PATCH 2/5] tidy up resolveInput copy --- docs/content/docs/config/hooks.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/content/docs/config/hooks.md b/docs/content/docs/config/hooks.md index 2689211761b..77bcc650a63 100644 --- a/docs/content/docs/config/hooks.md +++ b/docs/content/docs/config/hooks.md @@ -75,13 +75,13 @@ export default config({ ### resolveInput -The `resolveInput` hook is used to modify or augment the `data` values passed in to a `create` or `update` operation functions. +The `resolveInput` hook is a transform for mutating the input `data` value prior to calling any other successive hooks, as part of the 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 will be passed as `resolvedData` into the next stages of the operation. +The result of `resolveInput` hooks is accessible as the argument `resolvedData` in the hooks that follow, for the remainder of the operation. | Argument | Description | | :------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | @@ -90,7 +90,7 @@ The result of `resolveInput` hooks will be passed as `resolvedData` into the nex | `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, and field resolvers have been applied. | +| `resolvedData` | A [`resolved data`](#resolved-data-stages) object. The resolved data value after default values, relationship resolvers, field resolvers, and hooks have been applied. | | `context` | The [`KeystoneContext`](../context/overview) object of the originating GraphQL operation. | ```typescript From a319fdca3066ecf6c28106a381192e65428acda2 Mon Sep 17 00:00:00 2001 From: Daniel Cousens <413395+dcousens@users.noreply.github.com> Date: Thu, 25 Jul 2024 14:42:59 +1000 Subject: [PATCH 3/5] update validate copy --- docs/content/docs/config/hooks.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/content/docs/config/hooks.md b/docs/content/docs/config/hooks.md index 77bcc650a63..fb2d222544d 100644 --- a/docs/content/docs/config/hooks.md +++ b/docs/content/docs/config/hooks.md @@ -165,11 +165,14 @@ export default config({ ### validate -The `validate` hooks are used to validate the [`resolvedData`](#resolved-data-stages) that will be saved during a `create` or `update` operation, or validate that deleting the selected item will not cause an issue in your system during `delete` operation. +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. -It is invoked after the `resolveInput` hooks have been run. +For `create` and `update` operations, this hook is invoked after the respective `resolveInput` hooks has been run. + +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. -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 | From ca289bed5ab59263d043bdc6ac9e4bb576f44650 Mon Sep 17 00:00:00 2001 From: Daniel Cousens <413395+dcousens@users.noreply.github.com> Date: Thu, 25 Jul 2024 14:44:11 +1000 Subject: [PATCH 4/5] fix hooks naming --- docs/content/docs/config/hooks.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/content/docs/config/hooks.md b/docs/content/docs/config/hooks.md index fb2d222544d..5d806540269 100644 --- a/docs/content/docs/config/hooks.md +++ b/docs/content/docs/config/hooks.md @@ -90,7 +90,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 hooks have been applied. | +| `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. | | `context` | The [`KeystoneContext`](../context/overview) object of the originating GraphQL operation. | ```typescript @@ -194,7 +194,7 @@ export default config({ lists: { SomeListName: list({ hooks: { - validateInput: { + validate: { create: async ({ listKey, operation, @@ -224,7 +224,7 @@ export default config({ fields: { someFieldName: text({ hooks: { - validateInput: { + validate: { create: async ({ listKey, fieldKey, @@ -260,6 +260,7 @@ 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). From c174d1f60e9c1e1eeb367b23c1de4a6fbbbbf96c Mon Sep 17 00:00:00 2001 From: Daniel Cousens <413395+dcousens@users.noreply.github.com> Date: Thu, 25 Jul 2024 14:46:44 +1000 Subject: [PATCH 5/5] update copy for beforeOperation --- docs/content/docs/config/hooks.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/content/docs/config/hooks.md b/docs/content/docs/config/hooks.md index 5d806540269..27d0259105e 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. -All hook functions are async and, with the exception of `resolveInput`, do not return a value. +Hook functions support `async` and, with the exception of `resolveInput`, do not need a return value. When operating on multiple values the hooks are called individually for each item being updated, created or deleted. @@ -265,7 +265,7 @@ export default config({ 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). -It is invoked after all `validate` hooks have been run, but before the database is updated. +It is invoked after the `resolveInput` and `validate` hooks, but before the database is updated by Prisma. | Argument | Description | | :------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |