Skip to content

Commit

Permalink
separate types for field hooks
Browse files Browse the repository at this point in the history
  • Loading branch information
dcousens committed Aug 17, 2022
1 parent ad51fc1 commit d0180fe
Showing 1 changed file with 118 additions and 54 deletions.
172 changes: 118 additions & 54 deletions packages/core/src/types/config/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { KeystoneContextFromListTypeInfo } from '..';
import type { KeystoneContextFromListTypeInfo, MaybePromise } from '..';
import { BaseListTypeInfo } from '../type-info';

type CommonArgs<ListTypeInfo extends BaseListTypeInfo> = {
Expand Down Expand Up @@ -32,45 +32,6 @@ export type ListHooks<ListTypeInfo extends BaseListTypeInfo> = {
afterOperation?: AfterOperationHook<ListTypeInfo>;
};

// TODO: probably maybe don't do this and write it out manually
// (this is also incorrect because the return value is wrong for many of them)
type AddFieldPathToObj<T extends (arg: any) => any> = T extends (args: infer Args) => infer Result
? (args: Args & { fieldKey: string }) => Result
: never;

type AddFieldPathArgToAllPropsOnObj<T extends Record<string, (arg: any) => any>> = {
[Key in keyof T]: AddFieldPathToObj<T[Key]>;
};

type FieldKeysForList<ListTypeInfo extends BaseListTypeInfo> =
keyof ListTypeInfo['inputs']['update']; // TODO: uh

export type FieldHooks<
ListTypeInfo extends BaseListTypeInfo,
FieldKey extends FieldKeysForList<ListTypeInfo> = FieldKeysForList<ListTypeInfo>
> = AddFieldPathArgToAllPropsOnObj<{
/**
* Used to **modify the input** for create and update operations after default values and access control have been applied
*/
resolveInput?: ResolveInputFieldHook<ListTypeInfo, FieldKey>;
/**
* Used to **validate the input** for create and update operations once all resolveInput hooks resolved
*/
validateInput?: ValidateInputHook<ListTypeInfo>;
/**
* Used to **validate** that a delete operation can happen after access control has occurred
*/
validateDelete?: ValidateDeleteHook<ListTypeInfo>;
/**
* Used to **cause side effects** before a create, update, or delete operation once all validateInput hooks have resolved
*/
beforeOperation?: BeforeOperationHook<ListTypeInfo>;
/**
* Used to **cause side effects** after a create, update, or delete operation operation has occurred
*/
afterOperation?: AfterOperationHook<ListTypeInfo>;
}>;

type ArgsForCreateOrUpdateOperation<ListTypeInfo extends BaseListTypeInfo> =
| {
operation: 'create';
Expand Down Expand Up @@ -104,9 +65,7 @@ type ArgsForCreateOrUpdateOperation<ListTypeInfo extends BaseListTypeInfo> =
type ResolveInputListHook<ListTypeInfo extends BaseListTypeInfo> = (
args: ArgsForCreateOrUpdateOperation<ListTypeInfo> & CommonArgs<ListTypeInfo>
) =>
| Promise<ListTypeInfo['inputs']['create'] | ListTypeInfo['inputs']['update']>
| ListTypeInfo['inputs']['create']
| ListTypeInfo['inputs']['update']
| MaybePromise<ListTypeInfo['inputs']['create'] | ListTypeInfo['inputs']['update']>
// TODO: These were here to support field hooks before we created a separate type
// (see ResolveInputFieldHook), check whether they're safe to remove now
| Record<string, any>
Expand All @@ -115,17 +74,6 @@ type ResolveInputListHook<ListTypeInfo extends BaseListTypeInfo> = (
| boolean
| null;

type ResolveInputFieldHook<
ListTypeInfo extends BaseListTypeInfo,
FieldKey extends FieldKeysForList<ListTypeInfo>
> = (
args: ArgsForCreateOrUpdateOperation<ListTypeInfo> & CommonArgs<ListTypeInfo>
) =>
| Promise<ListTypeInfo['inputs']['create'][FieldKey] | ListTypeInfo['inputs']['update'][FieldKey]>
| ListTypeInfo['inputs']['create'][FieldKey]
| ListTypeInfo['inputs']['update'][FieldKey]
| undefined; // undefined represents 'don't do anything'

type ValidateInputHook<ListTypeInfo extends BaseListTypeInfo> = (
args: ArgsForCreateOrUpdateOperation<ListTypeInfo> & {
addValidationError: (error: string) => void;
Expand Down Expand Up @@ -178,3 +126,119 @@ type AfterOperationHook<ListTypeInfo extends BaseListTypeInfo> = (
) &
CommonArgs<ListTypeInfo>
) => Promise<void> | void;

// Field Hooks
type FieldKeysForList<ListTypeInfo extends BaseListTypeInfo> =
keyof ListTypeInfo['inputs']['update']; // TODO: uh

type CommonFieldArgs<ListTypeInfo extends BaseListTypeInfo, FieldKey extends FieldKeysForList<ListTypeInfo>> = {
context: KeystoneContextFromListTypeInfo<ListTypeInfo>;
/**
* The key of the list that the operation is occurring on
*/
listKey: string;

/**
* The key of the field that the operation is occurring on
*/
fieldKey: FieldKey;
};

type ResolveInputFieldHook<
ListTypeInfo extends BaseListTypeInfo,
FieldKey extends FieldKeysForList<ListTypeInfo>
> = (
args: ArgsForCreateOrUpdateOperation<ListTypeInfo> & CommonFieldArgs<ListTypeInfo, FieldKey>
) =>
| MaybePromise<ListTypeInfo['inputs']['create'][FieldKey] | ListTypeInfo['inputs']['update'][FieldKey]>
| undefined; // undefined represents 'don't do anything'

type ValidateInputFieldHook<
ListTypeInfo extends BaseListTypeInfo,
FieldKey extends FieldKeysForList<ListTypeInfo>
> = (
args: ArgsForCreateOrUpdateOperation<ListTypeInfo> & {
addValidationError: (error: string) => void;
} & CommonFieldArgs<ListTypeInfo, FieldKey>
) => Promise<void> | void;

type ValidateDeleteFieldHook<
ListTypeInfo extends BaseListTypeInfo,
FieldKey extends FieldKeysForList<ListTypeInfo>
> = (
args: {
operation: 'delete';
item: ListTypeInfo['item'];
addValidationError: (error: string) => void;
} & CommonFieldArgs<ListTypeInfo, FieldKey>
) => Promise<void> | void;

type BeforeOperationFieldHook<
ListTypeInfo extends BaseListTypeInfo,
FieldKey extends FieldKeysForList<ListTypeInfo>
> = (
args: (
| ArgsForCreateOrUpdateOperation<ListTypeInfo>
| {
operation: 'delete';
item: ListTypeInfo['item'];
inputData: undefined;
resolvedData: undefined;
}
) & CommonFieldArgs<ListTypeInfo, FieldKey>
) => Promise<void> | void;

type AfterOperationFieldHook<
ListTypeInfo extends BaseListTypeInfo,
FieldKey extends FieldKeysForList<ListTypeInfo>
> = (
args: (
| ArgsForCreateOrUpdateOperation<ListTypeInfo>
| {
operation: 'delete';
// technically this will never actually exist for a delete
// but making it optional rather than not here
// makes for a better experience
// because then people will see the right type even if they haven't refined the type of operation to 'delete'
item: undefined;
inputData: undefined;
resolvedData: undefined;
}
) &
({ operation: 'delete' } | { operation: 'create' | 'update'; item: ListTypeInfo['item'] }) &
(
| // technically this will never actually exist for a create
// but making it optional rather than not here
// makes for a better experience
// because then people will see the right type even if they haven't refined the type of operation to 'create'
{ operation: 'create'; originalItem: undefined }
| { operation: 'delete' | 'update'; originalItem: ListTypeInfo['item'] }
) &
CommonFieldArgs<ListTypeInfo, FieldKey>
) => Promise<void> | void;

export type FieldHooks<
ListTypeInfo extends BaseListTypeInfo,
FieldKey extends FieldKeysForList<ListTypeInfo> = FieldKeysForList<ListTypeInfo>
> = {
/**
* Used to **modify the input** for create and update operations after default values and access control have been applied
*/
resolveInput?: ResolveInputFieldHook<ListTypeInfo, FieldKey>;
/**
* Used to **validate the input** for create and update operations once all resolveInput hooks resolved
*/
validateInput?: ValidateInputFieldHook<ListTypeInfo, FieldKey>;
/**
* Used to **validate** that a delete operation can happen after access control has occurred
*/
validateDelete?: ValidateDeleteFieldHook<ListTypeInfo, FieldKey>;
/**
* Used to **cause side effects** before a create, update, or delete operation once all validateInput hooks have resolved
*/
beforeOperation?: BeforeOperationFieldHook<ListTypeInfo, FieldKey>;
/**
* Used to **cause side effects** after a create, update, or delete operation operation has occurred
*/
afterOperation?: AfterOperationFieldHook<ListTypeInfo, FieldKey>;
};

0 comments on commit d0180fe

Please sign in to comment.