From e2f6ad37393a11953047733c7f7b5fabc6bc9347 Mon Sep 17 00:00:00 2001 From: Gautam Singh <5769869+gautamsi@users.noreply.github.com> Date: Wed, 17 Jul 2024 16:38:46 -0500 Subject: [PATCH] update field hooks --- packages/core/src/fields/resolve-hooks.ts | 62 +------------------ .../core/src/fields/types/bigInt/index.ts | 10 ++- .../src/fields/types/calendarDay/index.ts | 11 +++- .../core/src/fields/types/decimal/index.ts | 11 +++- packages/core/src/fields/types/file/index.ts | 42 +++++++------ packages/core/src/fields/types/float/index.ts | 11 +++- packages/core/src/fields/types/image/index.ts | 52 +++++++++------- .../core/src/fields/types/integer/index.ts | 11 +++- .../src/fields/types/multiselect/index.ts | 11 +++- .../core/src/fields/types/password/index.ts | 11 +++- .../core/src/fields/types/select/index.ts | 13 +++- packages/core/src/fields/types/text/index.ts | 11 +++- .../core/src/fields/types/timestamp/index.ts | 11 +++- packages/core/src/types/config/hooks.ts | 2 +- 14 files changed, 146 insertions(+), 123 deletions(-) diff --git a/packages/core/src/fields/resolve-hooks.ts b/packages/core/src/fields/resolve-hooks.ts index 3ab9ede6633..44e47573210 100644 --- a/packages/core/src/fields/resolve-hooks.ts +++ b/packages/core/src/fields/resolve-hooks.ts @@ -1,41 +1,6 @@ -import { - type BaseListTypeInfo, - type FieldHooks, - type MaybePromise -} from '../types' +import { type MaybePromise } from '../types' -// force new syntax for built-in fields -// and block hooks from using resolveInput, they should use GraphQL resolvers -export type InternalFieldHooks = - Omit, 'validateInput' | 'validateDelete' | 'resolveInput'> - -/** @deprecated, TODO: remove in breaking change */ -function resolveValidateHooks ({ - validate, - validateInput, - validateDelete -}: FieldHooks): Exclude["validate"], Function> | undefined { - if (validateInput || validateDelete) { - return { - create: validateInput, - update: validateInput, - delete: validateDelete, - } - } - - if (!validate) return - if (typeof validate === 'function') { - return { - create: validate, - update: validate, - delete: validate - } - } - - return validate -} - -function merge < +export function merge < R, A extends (r: R) => MaybePromise, B extends (r: R) => MaybePromise @@ -46,26 +11,3 @@ function merge < await b?.(args) } } - -export function mergeFieldHooks ( - builtin?: InternalFieldHooks, - hooks?: FieldHooks, -) { - if (hooks === undefined) return builtin - if (builtin === undefined) return hooks - - const builtinValidate = resolveValidateHooks(builtin) - const hooksValidate = resolveValidateHooks(hooks) - return { - ...hooks, - // WARNING: beforeOperation is _after_ a user beforeOperation hook, TODO: this is align with user expectations about when "operations" happen - // our *Operation hooks are built-in, and should happen nearest to the database - beforeOperation: merge(hooks.beforeOperation, builtin.beforeOperation), - afterOperation: merge(builtin.afterOperation, hooks.afterOperation), - validate: (builtinValidate || hooksValidate) ? { - create: merge(builtinValidate?.create, hooksValidate?.create), - update: merge(builtinValidate?.update, hooksValidate?.update), - delete: merge(builtinValidate?.delete, hooksValidate?.delete) - } : undefined, - } satisfies FieldHooks -} diff --git a/packages/core/src/fields/types/bigInt/index.ts b/packages/core/src/fields/types/bigInt/index.ts index ff25772a15b..aa09b490cb3 100644 --- a/packages/core/src/fields/types/bigInt/index.ts +++ b/packages/core/src/fields/types/bigInt/index.ts @@ -11,7 +11,7 @@ import { resolveDbNullable, makeValidateHook } from '../../non-null-graphql' -import { mergeFieldHooks } from '../../resolve-hooks' +import { merge } from '../../resolve-hooks' export type BigIntFieldConfig = CommonFieldConfig & { @@ -124,7 +124,13 @@ export function bigInt (config: BigIntFi extendPrismaSchema: config.db?.extendPrismaSchema, })({ ...config, - hooks: mergeFieldHooks({ validate }, config.hooks), + hooks: { + ...config.hooks, + validate: { + ...config.hooks?.validate, + create: merge(config.hooks?.validate?.create, validate), + } + }, input: { uniqueWhere: isIndexed === 'unique' ? { arg: graphql.arg({ type: graphql.BigInt }) } : undefined, where: { diff --git a/packages/core/src/fields/types/calendarDay/index.ts b/packages/core/src/fields/types/calendarDay/index.ts index 89e37f182c9..7851a6e565d 100644 --- a/packages/core/src/fields/types/calendarDay/index.ts +++ b/packages/core/src/fields/types/calendarDay/index.ts @@ -9,7 +9,7 @@ import { type CalendarDayFieldMeta } from './views' import { graphql } from '../../..' import { filters } from '../../filters' import { makeValidateHook } from '../../non-null-graphql' -import { mergeFieldHooks } from '../../resolve-hooks' +import { merge } from '../../resolve-hooks' export type CalendarDayFieldConfig = CommonFieldConfig & { @@ -74,7 +74,14 @@ export function calendarDay (config: Cale nativeType: usesNativeDateType ? 'Date' : undefined, })({ ...config, - hooks: mergeFieldHooks({ validate }, config.hooks), + hooks: { + ...config.hooks, + validate: { + ...config.hooks?.validate, + create: merge(config.hooks?.validate?.create, validate), + update: merge(config.hooks?.validate?.update, validate), + }, + }, input: { uniqueWhere: isIndexed === 'unique' diff --git a/packages/core/src/fields/types/decimal/index.ts b/packages/core/src/fields/types/decimal/index.ts index b3f9d6f1d5d..aa918635c85 100644 --- a/packages/core/src/fields/types/decimal/index.ts +++ b/packages/core/src/fields/types/decimal/index.ts @@ -11,7 +11,7 @@ import { graphql } from '../../..' import { filters } from '../../filters' import { type DecimalFieldMeta } from './views' import { makeValidateHook } from '../../non-null-graphql' -import { mergeFieldHooks } from '../../resolve-hooks' +import { merge } from '../../resolve-hooks' export type DecimalFieldConfig = CommonFieldConfig & { @@ -134,7 +134,14 @@ export function decimal (config: DecimalF return fieldType(dbField)({ ...config, - hooks: mergeFieldHooks({ validate }, config.hooks), + hooks: { + ...config.hooks, + validate: { + ...config.hooks?.validate, + create: merge(config.hooks?.validate?.create, validate), + update: merge(config.hooks?.validate?.update, validate), + }, + }, input: { uniqueWhere: isIndexed === 'unique' ? { arg: graphql.arg({ type: graphql.Decimal }) } : undefined, diff --git a/packages/core/src/fields/types/file/index.ts b/packages/core/src/fields/types/file/index.ts index 78882b594b1..6cb528a74c8 100644 --- a/packages/core/src/fields/types/file/index.ts +++ b/packages/core/src/fields/types/file/index.ts @@ -7,7 +7,7 @@ import { fieldType, } from '../../../types' import { graphql } from '../../..' -import { mergeFieldHooks, type InternalFieldHooks } from '../../resolve-hooks' +import { merge } from '../../resolve-hooks' export type FileFieldConfig = CommonFieldConfig & { @@ -63,23 +63,20 @@ export function file (config: FileFieldC throw Error("isIndexed: 'unique' is not a supported option for field type file") } - const hooks: InternalFieldHooks = {} - if (!storage.preserve) { - hooks.beforeOperation = async function (args) { - if (args.operation === 'update' || args.operation === 'delete') { - const filenameKey = `${fieldKey}_filename` - const filename = args.item[filenameKey] + async function beforeOperationResolver (args: any) { + if (args.operation === 'update' || args.operation === 'delete') { + const filenameKey = `${fieldKey}_filename` + const filename = args.item[filenameKey] - // this will occur on an update where a file already existed but has been - // changed, or on a delete, where there is no longer an item - if ( - (args.operation === 'delete' || - typeof args.resolvedData[fieldKey].filename === 'string' || - args.resolvedData[fieldKey].filename === null) && - typeof filename === 'string' - ) { - await args.context.files(config.storage).deleteAtSource(filename) - } + // this will occur on an update where a file already existed but has been + // changed, or on a delete, where there is no longer an item + if ( + (args.operation === 'delete' || + typeof args.resolvedData[fieldKey].filename === 'string' || + args.resolvedData[fieldKey].filename === null) && + typeof filename === 'string' + ) { + await args.context.files(config.storage).deleteAtSource(filename) } } } @@ -93,7 +90,16 @@ export function file (config: FileFieldC }, })({ ...config, - hooks: mergeFieldHooks(hooks, config.hooks), + hooks: storage.preserve + ? config.hooks + : { + ...config.hooks, + beforeOperation: { + ...config.hooks?.beforeOperation, + update: merge(config.hooks?.beforeOperation?.update, beforeOperationResolver), + delete: merge(config.hooks?.beforeOperation?.delete, beforeOperationResolver), + }, + }, input: { create: { arg: inputArg, diff --git a/packages/core/src/fields/types/float/index.ts b/packages/core/src/fields/types/float/index.ts index f77048b9051..0de405f6e34 100644 --- a/packages/core/src/fields/types/float/index.ts +++ b/packages/core/src/fields/types/float/index.ts @@ -9,7 +9,7 @@ import { import { graphql } from '../../..' import { filters } from '../../filters' import { makeValidateHook } from '../../non-null-graphql' -import { mergeFieldHooks } from '../../resolve-hooks' +import { merge } from '../../resolve-hooks' export type FloatFieldConfig = CommonFieldConfig & { @@ -87,7 +87,14 @@ export function float (config: FloatField extendPrismaSchema: config.db?.extendPrismaSchema, })({ ...config, - hooks: mergeFieldHooks({ validate }, config.hooks), + hooks: { + ...config.hooks, + validate: { + ...config.hooks?.validate, + create: merge(config.hooks?.validate?.create, validate), + update: merge(config.hooks?.validate?.update, validate), + }, + }, input: { uniqueWhere: isIndexed === 'unique' ? { arg: graphql.arg({ type: graphql.Float }) } : undefined, where: { diff --git a/packages/core/src/fields/types/image/index.ts b/packages/core/src/fields/types/image/index.ts index 7877902c6d9..520139f5a4a 100644 --- a/packages/core/src/fields/types/image/index.ts +++ b/packages/core/src/fields/types/image/index.ts @@ -9,7 +9,7 @@ import { } from '../../../types' import { graphql } from '../../..' import { SUPPORTED_IMAGE_EXTENSIONS } from './utils' -import { mergeFieldHooks, type InternalFieldHooks } from '../../resolve-hooks' +import { merge } from '../../resolve-hooks' export type ImageFieldConfig = CommonFieldConfig & { @@ -88,27 +88,24 @@ export function image (config: ImageFiel throw Error("isIndexed: 'unique' is not a supported option for field type image") } - const hooks: InternalFieldHooks = {} - if (!storage.preserve) { - hooks.beforeOperation = async (args) => { - if (args.operation === 'update' || args.operation === 'delete') { - const idKey = `${fieldKey}_id` - const id = args.item[idKey] - const extensionKey = `${fieldKey}_extension` - const extension = args.item[extensionKey] - - // This will occur on an update where an image already existed but has been - // changed, or on a delete, where there is no longer an item - if ( - (args.operation === 'delete' || - typeof args.resolvedData[fieldKey].id === 'string' || - args.resolvedData[fieldKey].id === null) && - typeof id === 'string' && - typeof extension === 'string' && - isValidImageExtension(extension) - ) { - await args.context.images(config.storage).deleteAtSource(id, extension) - } + async function beforeOperationResolver (args: any) { + if (args.operation === 'update' || args.operation === 'delete') { + const idKey = `${fieldKey}_id` + const id = args.item[idKey] + const extensionKey = `${fieldKey}_extension` + const extension = args.item[extensionKey] + + // This will occur on an update where an image already existed but has been + // changed, or on a delete, where there is no longer an item + if ( + (args.operation === 'delete' || + typeof args.resolvedData[fieldKey].id === 'string' || + args.resolvedData[fieldKey].id === null) && + typeof id === 'string' && + typeof extension === 'string' && + isValidImageExtension(extension) + ) { + await args.context.images(config.storage).deleteAtSource(id, extension) } } } @@ -125,7 +122,16 @@ export function image (config: ImageFiel }, })({ ...config, - hooks: mergeFieldHooks(hooks, config.hooks), + hooks: storage.preserve + ? config.hooks + : { + ...config.hooks, + beforeOperation: { + ...config.hooks?.beforeOperation, + update: merge(config.hooks?.beforeOperation?.update, beforeOperationResolver), + delete: merge(config.hooks?.beforeOperation?.delete, beforeOperationResolver), + }, + }, input: { create: { arg: inputArg, diff --git a/packages/core/src/fields/types/integer/index.ts b/packages/core/src/fields/types/integer/index.ts index 43e6a3499f6..8a552c44388 100644 --- a/packages/core/src/fields/types/integer/index.ts +++ b/packages/core/src/fields/types/integer/index.ts @@ -11,7 +11,7 @@ import { resolveDbNullable, makeValidateHook } from '../../non-null-graphql' -import { mergeFieldHooks } from '../../resolve-hooks' +import { merge } from '../../resolve-hooks' export type IntegerFieldConfig = CommonFieldConfig & { @@ -121,7 +121,14 @@ export function integer (config: Integer extendPrismaSchema: config.db?.extendPrismaSchema, })({ ...config, - hooks: mergeFieldHooks({ validate }, config.hooks), + hooks: { + ...config.hooks, + validate: { + ...config.hooks?.validate, + create: merge(config.hooks?.validate?.create, validate), + update: merge(config.hooks?.validate?.update, validate), + }, + }, input: { uniqueWhere: isIndexed === 'unique' ? { arg: graphql.arg({ type: graphql.Int }) } : undefined, where: { diff --git a/packages/core/src/fields/types/multiselect/index.ts b/packages/core/src/fields/types/multiselect/index.ts index bb5614cb8f7..8c23d096d69 100644 --- a/packages/core/src/fields/types/multiselect/index.ts +++ b/packages/core/src/fields/types/multiselect/index.ts @@ -9,7 +9,7 @@ import { } from '../../../types' import { graphql } from '../../..' import { makeValidateHook } from '../../non-null-graphql' -import { mergeFieldHooks } from '../../resolve-hooks' +import { merge } from '../../resolve-hooks' export type MultiselectFieldConfig = CommonFieldConfig & @@ -108,7 +108,14 @@ export function multiselect ( { ...config, __ksTelemetryFieldTypeName: '@keystone-6/multiselect', - hooks: mergeFieldHooks({ validate }, config.hooks), + hooks: { + ...config.hooks, + validate: { + ...config.hooks?.validate, + create: merge(config.hooks?.validate?.create, validate), + update: merge(config.hooks?.validate?.update, validate), + }, + }, views: '@keystone-6/core/fields/types/multiselect/views', getAdminMeta: () => ({ options: transformedConfig.options, diff --git a/packages/core/src/fields/types/password/index.ts b/packages/core/src/fields/types/password/index.ts index 1324ca89136..e647371c0be 100644 --- a/packages/core/src/fields/types/password/index.ts +++ b/packages/core/src/fields/types/password/index.ts @@ -11,7 +11,7 @@ import { import { graphql } from '../../..' import { type PasswordFieldMeta } from './views' import { makeValidateHook } from '../../non-null-graphql' -import { mergeFieldHooks } from '../../resolve-hooks' +import { merge } from '../../resolve-hooks' export type PasswordFieldConfig = CommonFieldConfig & { @@ -137,7 +137,14 @@ export function password (config: Passwor extendPrismaSchema: config.db?.extendPrismaSchema, })({ ...config, - hooks: mergeFieldHooks({ validate }, config.hooks), + hooks: { + ...config.hooks, + validate: { + ...config.hooks?.validate, + create: merge(config.hooks?.validate?.create, validate), + update: merge(config.hooks?.validate?.update, validate), + }, + }, input: { where: mode === 'required' diff --git a/packages/core/src/fields/types/select/index.ts b/packages/core/src/fields/types/select/index.ts index ad10c77c12f..ed7704c820f 100644 --- a/packages/core/src/fields/types/select/index.ts +++ b/packages/core/src/fields/types/select/index.ts @@ -10,7 +10,7 @@ import { import { graphql } from '../../..' import { filters } from '../../filters' import { makeValidateHook } from '../../non-null-graphql' -import { mergeFieldHooks } from '../../resolve-hooks' +import { merge } from '../../resolve-hooks' export type SelectFieldConfig = CommonFieldConfig & @@ -55,7 +55,7 @@ export type SelectFieldConfig = const MAX_INT = 2147483647 const MIN_INT = -2147483648 -export function select (config: SelectFieldConfig): FieldTypeFunc { +export function select (config: SelectFieldConfig): FieldTypeFunc { const { isIndexed, ui: { displayMode = 'select', ...ui } = {}, @@ -95,7 +95,14 @@ export function select (config: SelectFie ...config, mode, ui, - hooks: mergeFieldHooks({ validate }, config.hooks), + hooks: { + ...config.hooks, + validate: { + ...config.hooks?.validate, + create: merge(config.hooks?.validate?.create, validate), + update: merge(config.hooks?.validate?.update, validate), + }, + }, __ksTelemetryFieldTypeName: '@keystone-6/select', views: '@keystone-6/core/fields/types/select/views', getAdminMeta: () => ({ diff --git a/packages/core/src/fields/types/text/index.ts b/packages/core/src/fields/types/text/index.ts index 426908a2763..19433953456 100644 --- a/packages/core/src/fields/types/text/index.ts +++ b/packages/core/src/fields/types/text/index.ts @@ -8,7 +8,7 @@ import { import { graphql } from '../../..' import { makeValidateHook } from '../../non-null-graphql' import { filters } from '../../filters' -import { mergeFieldHooks } from '../../resolve-hooks' +import { merge } from '../../resolve-hooks' export type TextFieldConfig = CommonFieldConfig & { @@ -142,7 +142,14 @@ export function text ( extendPrismaSchema: config.db?.extendPrismaSchema, })({ ...config, - hooks: mergeFieldHooks({ validate }, config.hooks), + hooks: { + ...config.hooks, + validate: { + ...config.hooks?.validate, + create: merge(config.hooks?.validate?.create, validate), + update: merge(config.hooks?.validate?.update, validate), + }, + }, input: { uniqueWhere: isIndexed === 'unique' ? { arg: graphql.arg({ type: graphql.String }) } : undefined, where: { diff --git a/packages/core/src/fields/types/timestamp/index.ts b/packages/core/src/fields/types/timestamp/index.ts index d2cdd55d8ac..96d7d190891 100644 --- a/packages/core/src/fields/types/timestamp/index.ts +++ b/packages/core/src/fields/types/timestamp/index.ts @@ -8,7 +8,7 @@ import { import { graphql } from '../../..' import { filters } from '../../filters' import { makeValidateHook } from '../../non-null-graphql' -import { mergeFieldHooks } from '../../resolve-hooks' +import { merge } from '../../resolve-hooks' import { type TimestampFieldMeta } from './views' export type TimestampFieldConfig = @@ -73,7 +73,14 @@ export function timestamp ( extendPrismaSchema: config.db?.extendPrismaSchema, })({ ...config, - hooks: mergeFieldHooks({ validate }, config.hooks), + hooks: { + ...config.hooks, + validate: { + ...config.hooks?.validate, + create: merge(config.hooks?.validate?.create, validate), + update: merge(config.hooks?.validate?.update, validate), + }, + }, input: { uniqueWhere: isIndexed === 'unique' ? { arg: graphql.arg({ type: graphql.DateTime }) } : undefined, where: { diff --git a/packages/core/src/types/config/hooks.ts b/packages/core/src/types/config/hooks.ts index a7fd3a89f9c..dd108fab1eb 100644 --- a/packages/core/src/types/config/hooks.ts +++ b/packages/core/src/types/config/hooks.ts @@ -121,7 +121,7 @@ export type FieldHooks< /** * Used to **validate** if a create, update or delete operation is OK */ - validate?:{ + validate?: { create?: ValidateFieldHook update?: ValidateFieldHook delete?: ValidateFieldHook