Skip to content

Commit

Permalink
update field hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
gautamsi committed Jul 18, 2024
1 parent c7d3f8c commit f5e3425
Show file tree
Hide file tree
Showing 14 changed files with 150 additions and 127 deletions.
62 changes: 2 additions & 60 deletions packages/core/src/fields/resolve-hooks.ts
Original file line number Diff line number Diff line change
@@ -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<ListTypeInfo extends BaseListTypeInfo> =
Omit<FieldHooks<ListTypeInfo>, 'validateInput' | 'validateDelete' | 'resolveInput'>

/** @deprecated, TODO: remove in breaking change */
function resolveValidateHooks <ListTypeInfo extends BaseListTypeInfo> ({
validate,
validateInput,
validateDelete
}: FieldHooks<ListTypeInfo>): Exclude<FieldHooks<ListTypeInfo>["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<void>,
B extends (r: R) => MaybePromise<void>
Expand All @@ -46,26 +11,3 @@ function merge <
await b?.(args)
}
}

export function mergeFieldHooks <ListTypeInfo extends BaseListTypeInfo> (
builtin?: InternalFieldHooks<ListTypeInfo>,
hooks?: FieldHooks<ListTypeInfo>,
) {
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<ListTypeInfo>
}
10 changes: 8 additions & 2 deletions packages/core/src/fields/types/bigInt/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
resolveDbNullable,
makeValidateHook
} from '../../non-null-graphql'
import { mergeFieldHooks } from '../../resolve-hooks'
import { merge } from '../../resolve-hooks'

export type BigIntFieldConfig<ListTypeInfo extends BaseListTypeInfo> =
CommonFieldConfig<ListTypeInfo> & {
Expand Down Expand Up @@ -124,7 +124,13 @@ export function bigInt <ListTypeInfo extends BaseListTypeInfo> (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: {
Expand Down
13 changes: 10 additions & 3 deletions packages/core/src/fields/types/calendarDay/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ListTypeInfo extends BaseListTypeInfo> =
CommonFieldConfig<ListTypeInfo> & {
Expand All @@ -25,7 +25,7 @@ export type CalendarDayFieldConfig<ListTypeInfo extends BaseListTypeInfo> =
}
}

export function calendarDay <ListTypeInfo extends BaseListTypeInfo>(config: CalendarDayFieldConfig<ListTypeInfo> = {}): FieldTypeFunc<ListTypeInfo> {
export function calendarDay <ListTypeInfo extends BaseListTypeInfo> (config: CalendarDayFieldConfig<ListTypeInfo> = {}): FieldTypeFunc<ListTypeInfo> {
const {
isIndexed,
validation,
Expand Down Expand Up @@ -74,7 +74,14 @@ export function calendarDay <ListTypeInfo extends BaseListTypeInfo>(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'
Expand Down
13 changes: 10 additions & 3 deletions packages/core/src/fields/types/decimal/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ListTypeInfo extends BaseListTypeInfo> =
CommonFieldConfig<ListTypeInfo> & {
Expand Down Expand Up @@ -48,7 +48,7 @@ function parseDecimalValueOption (meta: FieldData, value: string, name: string)
return decimal
}

export function decimal <ListTypeInfo extends BaseListTypeInfo>(config: DecimalFieldConfig<ListTypeInfo> = {}): FieldTypeFunc<ListTypeInfo> {
export function decimal <ListTypeInfo extends BaseListTypeInfo> (config: DecimalFieldConfig<ListTypeInfo> = {}): FieldTypeFunc<ListTypeInfo> {
const {
isIndexed,
precision = 18,
Expand Down Expand Up @@ -134,7 +134,14 @@ export function decimal <ListTypeInfo extends BaseListTypeInfo>(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,
Expand Down
42 changes: 24 additions & 18 deletions packages/core/src/fields/types/file/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ListTypeInfo extends BaseListTypeInfo> =
CommonFieldConfig<ListTypeInfo> & {
Expand Down Expand Up @@ -63,23 +63,20 @@ export function file <ListTypeInfo extends BaseListTypeInfo> (config: FileFieldC
throw Error("isIndexed: 'unique' is not a supported option for field type file")
}

const hooks: InternalFieldHooks<ListTypeInfo> = {}
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)
}
}
}
Expand All @@ -93,7 +90,16 @@ export function file <ListTypeInfo extends BaseListTypeInfo> (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,
Expand Down
13 changes: 10 additions & 3 deletions packages/core/src/fields/types/float/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ListTypeInfo extends BaseListTypeInfo> =
CommonFieldConfig<ListTypeInfo> & {
Expand All @@ -27,7 +27,7 @@ export type FloatFieldConfig<ListTypeInfo extends BaseListTypeInfo> =
}
}

export function float <ListTypeInfo extends BaseListTypeInfo>(config: FloatFieldConfig<ListTypeInfo> = {}): FieldTypeFunc<ListTypeInfo> {
export function float <ListTypeInfo extends BaseListTypeInfo> (config: FloatFieldConfig<ListTypeInfo> = {}): FieldTypeFunc<ListTypeInfo> {
const {
defaultValue,
isIndexed,
Expand Down Expand Up @@ -87,7 +87,14 @@ export function float <ListTypeInfo extends BaseListTypeInfo>(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: {
Expand Down
52 changes: 29 additions & 23 deletions packages/core/src/fields/types/image/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ListTypeInfo extends BaseListTypeInfo> =
CommonFieldConfig<ListTypeInfo> & {
Expand Down Expand Up @@ -88,27 +88,24 @@ export function image <ListTypeInfo extends BaseListTypeInfo> (config: ImageFiel
throw Error("isIndexed: 'unique' is not a supported option for field type image")
}

const hooks: InternalFieldHooks<ListTypeInfo> = {}
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)
}
}
}
Expand All @@ -125,7 +122,16 @@ export function image <ListTypeInfo extends BaseListTypeInfo> (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,
Expand Down
11 changes: 9 additions & 2 deletions packages/core/src/fields/types/integer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
resolveDbNullable,
makeValidateHook
} from '../../non-null-graphql'
import { mergeFieldHooks } from '../../resolve-hooks'
import { merge } from '../../resolve-hooks'

export type IntegerFieldConfig<ListTypeInfo extends BaseListTypeInfo> =
CommonFieldConfig<ListTypeInfo> & {
Expand Down Expand Up @@ -121,7 +121,14 @@ export function integer <ListTypeInfo extends BaseListTypeInfo> (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: {
Expand Down
11 changes: 9 additions & 2 deletions packages/core/src/fields/types/multiselect/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ListTypeInfo extends BaseListTypeInfo> =
CommonFieldConfig<ListTypeInfo> &
Expand Down Expand Up @@ -108,7 +108,14 @@ export function multiselect <ListTypeInfo extends BaseListTypeInfo> (
{
...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,
Expand Down
Loading

0 comments on commit f5e3425

Please sign in to comment.