@lucidcms/core
Version:
The core of the Lucid CMS. It's responsible for spinning up the API and serving the CMS.
1 lines • 8.11 kB
Source Map (JSON)
{"version":3,"file":"execute-hooks.mjs","names":[],"sources":["../../../src/libs/hooks/execute-hooks.ts"],"sourcesContent":["import { createDraft, finishDraft } from \"immer\";\nimport type {\n\tServiceContext,\n\tServiceResponse,\n} from \"../../utils/services/types.js\";\nimport { copy } from \"../i18n/index.js\";\nimport { hookExecutionKinds } from \"./hook-map.js\";\nimport type {\n\tArgumentsType,\n\tExecuteHookData,\n\tHookData,\n\tHookExecutionKind,\n\tHookOptions,\n\tHookResponse,\n\tHookServiceHandlers,\n\tTransformHookData,\n} from \"./types.js\";\n\ntype MatchingHook<\n\tS extends keyof HookServiceHandlers,\n\tE extends keyof HookServiceHandlers[S],\n> = {\n\thandler: HookServiceHandlers[S][E];\n\tpriority?: number;\n\torder: number;\n};\n\ntype TransformPayload = {\n\tdata: unknown;\n};\n\ntype HookPayloadArguments<T> = T extends (\n\tcontext: ServiceContext,\n\t...args: infer Args\n) => unknown\n\t? Args\n\t: never;\n\nconst hasTransformPayload = (value: unknown): value is TransformPayload => {\n\tif (typeof value !== \"object\" || value === null) return false;\n\n\treturn \"data\" in value;\n};\n\nconst getMatchingHooks = <\n\tS extends keyof HookServiceHandlers,\n\tE extends keyof HookServiceHandlers[S],\n>(\n\toptions: HookOptions<S, E>,\n) => {\n\tconst hooks: Array<MatchingHook<S, E>> = [];\n\tlet order = 0;\n\n\tfor (let i = 0; i < options.config.hooks.length; i++) {\n\t\tconst hook = options.config.hooks[i];\n\t\tif (hook === undefined) continue;\n\t\tif (hook.service !== options.service || hook.event !== options.event) {\n\t\t\tcontinue;\n\t\t}\n\n\t\thooks.push({\n\t\t\thandler: hook.handler as HookServiceHandlers[S][E],\n\t\t\tpriority: \"priority\" in hook ? hook.priority : undefined,\n\t\t\torder: order++,\n\t\t});\n\t}\n\n\tif (options.collectionInstance?.config.hooks === undefined) return hooks;\n\n\tfor (let i = 0; i < options.collectionInstance.config.hooks.length; i++) {\n\t\tconst hook = options.collectionInstance.config.hooks[i];\n\t\tif (hook === undefined) continue;\n\t\tif (hook.service !== options.service) continue;\n\t\tif (hook.event !== options.event) continue;\n\n\t\thooks.push({\n\t\t\thandler: hook.handler as HookServiceHandlers[S][E],\n\t\t\tpriority: \"priority\" in hook ? hook.priority : undefined,\n\t\t\torder: order++,\n\t\t});\n\t}\n\n\treturn hooks;\n};\n\nconst getTransformHooks = <\n\tS extends keyof HookServiceHandlers,\n\tE extends keyof HookServiceHandlers[S],\n>(\n\toptions: HookOptions<S, E>,\n) => {\n\treturn getMatchingHooks(options).sort((a, b) => {\n\t\tconst priorityDiff = (a.priority ?? 0) - (b.priority ?? 0);\n\t\tif (priorityDiff !== 0) return priorityDiff;\n\n\t\treturn a.order - b.order;\n\t});\n};\n\n/**\n * Runs hooks that only perform side effects. Each matching hook receives the\n * same arguments and any returned data is ignored by design.\n */\nconst executeEffectHooks = async <\n\tS extends keyof HookServiceHandlers,\n\tE extends keyof HookServiceHandlers[S],\n>(\n\toptions: HookOptions<S, E>,\n\targs: ArgumentsType<HookServiceHandlers[S][E]>,\n): ServiceResponse<HookData<S, E>> => {\n\tfor (const hook of getMatchingHooks(options)) {\n\t\tconst res = await (\n\t\t\thook.handler as unknown as (\n\t\t\t\t...args: ArgumentsType<HookServiceHandlers[S][E]>\n\t\t\t) => Promise<HookResponse<S, E>>\n\t\t)(...args);\n\t\tif (res.error) return res;\n\t}\n\n\treturn {\n\t\terror: undefined,\n\t\tdata: undefined as HookData<S, E>,\n\t};\n};\n\n/**\n * Runs hooks as ordered transforms. The `data` payload is drafted with Immer for\n * each hook, so handlers can mutate only the parts they care about or return a\n * full replacement value. The finalized data is passed to the next hook.\n */\nconst executeTransformHooks = async <\n\tS extends keyof HookServiceHandlers,\n\tE extends keyof HookServiceHandlers[S],\n>(\n\toptions: HookOptions<S, E>,\n\targs: ArgumentsType<HookServiceHandlers[S][E]>,\n): ServiceResponse<TransformHookData<S, E>> => {\n\tconst payload = args[1];\n\tif (!hasTransformPayload(payload)) {\n\t\treturn {\n\t\t\terror: {\n\t\t\t\ttype: \"basic\",\n\t\t\t\tname: copy(\"server:core.hooks.execution.error.name\"),\n\t\t\t\tmessage: copy(\"server:core.hooks.transform.payload.error.message\", {\n\t\t\t\t\tdata: {\n\t\t\t\t\t\tservice: String(options.service),\n\t\t\t\t\t\tevent: String(options.event),\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\tstatus: 500,\n\t\t\t},\n\t\t\tdata: undefined,\n\t\t};\n\t}\n\n\tlet currentData = payload.data as TransformHookData<S, E>;\n\n\tfor (const hook of getTransformHooks(options)) {\n\t\tconst draft = createDraft(currentData);\n\t\tconst nextArgs = [\n\t\t\targs[0],\n\t\t\t{\n\t\t\t\t...payload,\n\t\t\t\tdata: draft,\n\t\t\t},\n\t\t] as ArgumentsType<HookServiceHandlers[S][E]>;\n\n\t\tconst res = await (\n\t\t\thook.handler as unknown as (\n\t\t\t\t...args: ArgumentsType<HookServiceHandlers[S][E]>\n\t\t\t) => Promise<HookResponse<S, E>>\n\t\t)(...nextArgs);\n\t\tif (res.error) return res;\n\n\t\tif (res.data === undefined || res.data === draft) {\n\t\t\tcurrentData = finishDraft(draft) as TransformHookData<S, E>;\n\t\t\tcontinue;\n\t\t}\n\n\t\tfinishDraft(draft);\n\n\t\tcurrentData = res.data as TransformHookData<S, E>;\n\t}\n\n\treturn {\n\t\terror: undefined,\n\t\tdata: currentData,\n\t};\n};\n\n/**\n * Dispatches hooks through the configured execution kind while keeping one\n * caller interface for both effect and transform events.\n */\nconst executeHooks = async <\n\tS extends keyof HookServiceHandlers,\n\tE extends keyof HookServiceHandlers[S],\n>(\n\tcontext: ServiceContext,\n\toptions: HookOptions<S, E>,\n\t...args: HookPayloadArguments<HookServiceHandlers[S][E]>\n): ServiceResponse<ExecuteHookData<S, E>> => {\n\tconst hookArgs = [context, ...args] as ArgumentsType<\n\t\tHookServiceHandlers[S][E]\n\t>;\n\tconst executionKind =\n\t\t(hookExecutionKinds as Record<string, Record<string, HookExecutionKind>>)[\n\t\t\tString(options.service)\n\t\t]?.[String(options.event)] ?? \"effect\";\n\n\tif (executionKind === \"transform\") {\n\t\treturn executeTransformHooks(options, hookArgs) as ServiceResponse<\n\t\t\tExecuteHookData<S, E>\n\t\t>;\n\t}\n\n\treturn executeEffectHooks(options, hookArgs) as ServiceResponse<\n\t\tExecuteHookData<S, E>\n\t>;\n};\n\nexport default executeHooks;\n"],"mappings":"iJAsCA,MAAM,EAAuB,GACxB,OAAO,GAAU,WAAY,EAAuB,GAEjD,SAAU,EAGZ,EAIL,GACI,CACJ,IAAM,EAAmC,CAAC,EACtC,EAAQ,EAEZ,IAAK,IAAI,EAAI,EAAG,EAAI,EAAQ,OAAO,MAAM,OAAQ,IAAK,CACrD,IAAM,EAAO,EAAQ,OAAO,MAAM,GAC9B,IAAS,IAAA,KACT,EAAK,UAAY,EAAQ,SAAW,EAAK,QAAU,EAAQ,OAI/D,EAAM,KAAK,CACV,QAAS,EAAK,QACd,SAAU,aAAc,EAAO,EAAK,SAAW,IAAA,GAC/C,MAAO,GACR,CAAC,EACF,CAEA,GAAI,EAAQ,oBAAoB,OAAO,QAAU,IAAA,GAAW,OAAO,EAEnE,IAAK,IAAI,EAAI,EAAG,EAAI,EAAQ,mBAAmB,OAAO,MAAM,OAAQ,IAAK,CACxE,IAAM,EAAO,EAAQ,mBAAmB,OAAO,MAAM,GACjD,IAAS,IAAA,IACT,EAAK,UAAY,EAAQ,SACzB,EAAK,QAAU,EAAQ,OAE3B,EAAM,KAAK,CACV,QAAS,EAAK,QACd,SAAU,aAAc,EAAO,EAAK,SAAW,IAAA,GAC/C,MAAO,GACR,CAAC,CACF,CAEA,OAAO,CACR,EAEM,EAIL,GAEO,EAAiB,CAAO,CAAC,CAAC,MAAM,EAAG,IAAM,CAC/C,IAAM,GAAgB,EAAE,UAAY,IAAM,EAAE,UAAY,GAGxD,OAFI,IAAiB,EAEd,EAAE,MAAQ,EAAE,MAFY,CAGhC,CAAC,EAOI,EAAqB,MAI1B,EACA,IACqC,CACrC,IAAK,IAAM,KAAQ,EAAiB,CAAO,EAAG,CAC7C,IAAM,EAAM,MACX,EAAK,QAGJ,GAAG,CAAI,EACT,GAAI,EAAI,MAAO,OAAO,CACvB,CAEA,MAAO,CACN,MAAO,IAAA,GACP,KAAM,IAAA,EACP,CACD,EAOM,EAAwB,MAI7B,EACA,IAC8C,CAC9C,IAAM,EAAU,EAAK,GACrB,GAAI,CAAC,EAAoB,CAAO,EAC/B,MAAO,CACN,MAAO,CACN,KAAM,QACN,KAAM,EAAK,wCAAwC,EACnD,QAAS,EAAK,oDAAqD,CAClE,KAAM,CACL,QAAS,OAAO,EAAQ,OAAO,EAC/B,MAAO,OAAO,EAAQ,KAAK,CAC5B,CACD,CAAC,EACD,OAAQ,GACT,EACA,KAAM,IAAA,EACP,EAGD,IAAI,EAAc,EAAQ,KAE1B,IAAK,IAAM,KAAQ,EAAkB,CAAO,EAAG,CAC9C,IAAM,EAAQ,EAAY,CAAW,EAC/B,EAAW,CAChB,EAAK,GACL,CACC,GAAG,EACH,KAAM,CACP,CACD,EAEM,EAAM,MACX,EAAK,QAGJ,GAAG,CAAQ,EACb,GAAI,EAAI,MAAO,OAAO,EAEtB,GAAI,EAAI,OAAS,IAAA,IAAa,EAAI,OAAS,EAAO,CACjD,EAAc,EAAY,CAAK,EAC/B,QACD,CAEA,EAAY,CAAK,EAEjB,EAAc,EAAI,IACnB,CAEA,MAAO,CACN,MAAO,IAAA,GACP,KAAM,CACP,CACD,EAMM,EAAe,MAIpB,EACA,EACA,GAAG,IACyC,CAC5C,IAAM,EAAW,CAAC,EAAS,GAAG,CAAI,EAclC,OAVE,EACA,OAAO,EAAQ,OAAO,EACtB,GAAG,OAAO,EAAQ,KAAK,IAAM,YAET,YACd,EAAsB,EAAS,CAAQ,EAKxC,EAAmB,EAAS,CAAQ,CAG5C"}