UNPKG

plug-and-play

Version:

Easily create hooks and let users plug their own logic across your code to make it extensible by everyone with new features.

1 lines 26.3 kB
{"version":3,"sources":["/home/david/projects/open-source/plug-and-play/dist/index.cjs","../src/index.ts","../src/error.ts"],"names":["require","hook"],"mappings":"AAAA;ACAA,8BAA6C;AAC7C,uFAA0B;ADE1B;AACA;AEHA,IAAM,cAAA,EAAN,MAAM,eAAA,QAAsB,MAAM;AAAA,EACzB;AAAA,EAEP,WAAA,CACE,IAAA,EACA,OAAA,EAAA,GACG,QAAA,EACH;AACA,IAAA,GAAA,CAAI,KAAA,CAAM,OAAA,CAAQ,OAAO,CAAA,EAAG;AAC1B,MAAA,QAAA,EAAU,OAAA,CACP,MAAA,CAAO,QAAA,CAAU,IAAA,EAAM;AACtB,QAAA,OAAO,CAAC,CAAC,IAAA;AAAA,MACX,CAAC,CAAA,CACA,IAAA,CAAK,GAAG,CAAA;AAAA,IACb;AACA,IAAA,QAAA,EAAU,CAAA,EAAA;AACJ,IAAA;AACI,IAAA;AACF,MAAA;AACR,IAAA;AACK,IAAA;AACI,IAAA;AACD,MAAA;AACN,MAAA;AACM,QAAA;AACF,UAAA;AACF,QAAA;AACM,QAAA;AACF,QAAA;AACF,UAAA;AACF,QAAA;AACK,QAAA;AAIP,MAAA;AACF,IAAA;AACF,EAAA;AACF;AAEO;AACM,EAAA;AACb;AFNe;AACA;AC8JT;AAII,EAAA;AACR,EAAA;AACA,EAAA;AACW,EAAA;AAMU;AAEf,EAAA;AAEqB,EAAA;AAAA;AAEf,IAAA;AACJ,MAAA;AACI,QAAA;AACJ,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACH,MAAA;AAES,MAAA;AAEH,MAAA;AACN,MAAA;AACO,QAAA;AACL,QAAA;AACF,MAAA;AAEI,MAAA;AACK,QAAA;AACT,MAAA;AACMA,MAAAA;AAIA,MAAA;AACA,QAAA;AACF,UAAA;AACE,YAAA;AACA,YAAA;AACD,UAAA;AACIA,QAAAA;AACR,MAAA;AAEK,MAAA;AACG,QAAA;AACP,QAAA;AACM,QAAA;AACR,MAAA;AACM,MAAA;AACE,MAAA;AACV,IAAA;AACA,IAAA;AACE,MAAA;AACM,QAAA;AACF,UAAA;AACF,QAAA;AACF,MAAA;AACI,MAAA;AACK,QAAA;AACT,MAAA;AACO,MAAA;AACT,IAAA;AACK,IAAA;AACG,MAAA;AAAkB;AAEnB,QAAA;AAA0B;AAE1B,QAAA;AAGM,UAAA;AAEL,UAAA;AACE,YAAA;AACE,cAAA;AAA6B,gBAAA;AACZ,gBAAA;AAEhB,cAAA;AACH,YAAA;AACF,UAAA;AACA,UAAA;AACG,YAAA;AAEG,cAAA;AACA,cAAA;AACA,cAAA;AACF,YAAA;AACJ,UAAA;AAED,QAAA;AACC,UAAA;AAED,QAAA;AACC,QAAA;AACN,MAAA;AACK,MAAA;AACI,QAAA;AACT,MAAA;AAMM,MAAA;AACN,MAAA;AACM,QAAA;AACN,MAAA;AAKM,MAAA;AACG,QAAA;AACD,UAAA;AACF,YAAA;AACF,UAAA;AACE,YAAA;AACE,cAAA;AACA,cAAA;AACA,cAAA;AACD,YAAA;AACH,UAAA;AACA,UAAA;AACG,QAAA;AACN,MAAA;AACK,MAAA;AACG,QAAA;AACD,UAAA;AACF,YAAA;AACF,UAAA;AACE,YAAA;AACE,cAAA;AACA,cAAA;AACA,cAAA;AACD,YAAA;AACH,UAAA;AACA,UAAA;AACG,QAAA;AACN,MAAA;AACK,MAAA;AACU,MAAA;AAClB,IAAA;AAAA;AAEM,IAAA;AACA,MAAA;AACI,QAAA;AACJ,UAAA;AACA,UAAA;AACD,QAAA;AACH,MAAA;AACQ,QAAA;AACJ,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACH,MAAA;AACQ,QAAA;AACJ,UAAA;AACA,UAAA;AACD,QAAA;AACH,MAAA;AAEM,MAAA;AACJ,QAAA;AACA,QAAA;AACD,MAAA;AAED,MAAA;AAA6B,MAAA;AAC7B,MAAA;AACE,QAAA;AACO,UAAA;AACA,UAAA;AACH,YAAA;AAAiC,YAAA;AACjC,YAAA;AACG,UAAA;AACH,YAAA;AACA,YAAA;AACE,cAAA;AAKF,YAAA;AAGE,cAAA;AACF,YAAA;AACA,YAAA;AACF,UAAA;AACE,YAAA;AACE,cAAA;AACA,cAAA;AACD,YAAA;AACL,QAAA;AACF,MAAA;AAEO,MAAA;AAAoC,MAAA;AAC7C,IAAA;AAAA;AAEW,IAAA;AACL,MAAA;AACI,QAAA;AACJ,UAAA;AACA,UAAA;AACD,QAAA;AACH,MAAA;AACQ,QAAA;AACJ,UAAA;AACA,UAAA;AACA,UAAA;AACD,QAAA;AACH,MAAA;AACQ,QAAA;AACJ,UAAA;AACA,UAAA;AACD,QAAA;AACH,MAAA;AAEM,MAAA;AACJ,QAAA;AACA,QAAA;AACD,MAAA;AAED,MAAA;AAA6B,MAAA;AAC7B,MAAA;AACE,QAAA;AACO,UAAA;AACA,UAAA;AACH,YAAA;AAA2B,YAAA;AAC3B,YAAA;AACG,UAAA;AACH,YAAA;AACA,YAAA;AACE,cAAA;AAKF,YAAA;AAGE,cAAA;AACF,YAAA;AACA,YAAA;AACF,UAAA;AACE,YAAA;AACE,cAAA;AACA,cAAA;AACD,YAAA;AACL,QAAA;AACF,MAAA;AAEO,MAAA;AAAoC,MAAA;AAC7C,IAAA;AACF,EAAA;AAEW,EAAA;AACL,IAAA;AACN,EAAA;AAEO,EAAA;AACT;AAYM;AAIE,EAAA;AACO,EAAA;AACAC,IAAAA;AACH,MAAA;AACJ,QAAA;AACA,QAAA;AACA,QAAA;AACO,QAAA;AACR,MAAA;AACH,IAAA;AACO,IAAA;AAED,MAAA;AAGJ,MAAA;AAEE,MAAA;AAGO,MAAA;AACX,IAAA;AACD,EAAA;AACH;AAEe;AACb,EAAA;AACE,IAAA;AACA,IAAA;AACA,IAAA;AAKC,EAAA;AACK,IAAA;AACJ,MAAA;AACS,MAAA;AACT,MAAA;AACA,MAAA;AACD,IAAA;AACH,EAAA;AACA,EAAA;AACE,IAAA;AACA,IAAA;AACA,IAAA;AAKC,EAAA;AACK,IAAA;AACJ,MAAA;AACS,MAAA;AACT,MAAA;AACA,MAAA;AACD,IAAA;AACH,EAAA;AACA,EAAA;AACE,IAAA;AACAD,IAAAA;AAIC,EAAA;AACK,IAAA;AACJ,MAAA;AACA,MAAA;AACS,MAAA;AACV,IAAA;AACH,EAAA;AACA,EAAA;AACE,IAAA;AACAA,IAAAA;AAIC,EAAA;AACK,IAAA;AACJ,MAAA;AACO,MAAA;AACP,MAAA;AACO,MAAA;AACR,IAAA;AACH,EAAA;AACF;AD7Oe;AACA;AACA;AACA","file":"/home/david/projects/open-source/plug-and-play/dist/index.cjs","sourcesContent":[null,"import { is_object_literal, is_object } from \"mixme\";\nimport * as toposort from \"toposort\";\nimport error from \"./error.js\";\nexport { PlugableError } from \"./error.js\";\n\n/**\n * Represents a handler function for a hook.\n *\n * @typeParam T - The type of the arguments passed to the hook handler.\n *\n * @param args - The arguments passed to the hook handler.\n * @param handler - The next hook handler in the chain.\n *\n * @returns A Promise or a value that represents the result of the hook handler.\n * @remarks (TODO)\n * Both args and handler are optional by nature.\n * Differentiate the hook handlers from the user handlers\n */\nexport type Handler<T> = (\n args: T,\n handler: Handler<T>,\n) => unknown | void | PromiseLike<unknown> | Handler<T>;\n\n// export type Handler<T> = {\n// (): unknown | void | PromiseLike<unknown>;\n// (args: T): unknown | void | PromiseLike<unknown>;\n// (args: T, handler: Handler<T>): unknown | void | PromiseLike<unknown> | Handler<T>;\n// }\n\n/**\n * Represents a hook in the Plug-and-Play system.\n *\n * @typeParam T - The type of the arguments expected by the hook handlers.\n * @property after - List of plugin names with hooks of the same name that should be executed after this hook. If a string is provided, it is coerced to an array.\n * @property before - List of plugin names with hooks of the same name that should be executed before this hook. If a string is provided, it is coerced to an array.\n * @property handler - The hook handler to be executed.\n * @property name - Name to identify the hook.\n */\nexport interface Hook<T> {\n after?: string | string[];\n before?: string | string[];\n handler: Handler<T>;\n name?: PropertyKey;\n}\n\n/**\n * Represents a normalized hook with standardized format.\n *\n * @typeParam T - The type of the arguments and return values of the hooks.\n * @typeParam K - The type of the key of the hook in the record.\n * @property after - An array of plugin names that this hook should be executed after.\n * @property before - An array of plugin names that this hook should be executed before.\n * @property handler - The handler function of the hook.\n * @property name - The name of the hook.\n * @property plugin - The name of the plugin that defines this hook.\n * @property require - An array of plugin names that this plugin requires.\n */\nexport interface HookNormalized<T, K extends keyof T> {\n after: string[];\n before: string[];\n handler: Handler<T[K]>;\n name: K;\n plugin?: string;\n require?: string[];\n}\n\n/**\n * Represents a plugin for the Plug-and-Play system.\n *\n * @typeParam T - Type of parameters expected by hook handlers.\n * @property hooks - List of hooks identified by hook names. Each hook can be an array of hooks, a single hook, or a handler function.\n * @property name - Name identifying the plugin by other plugin with the `after`, `before` and `require` properties.\n * @property require - Names of the required plugins. If a required plugin is not registered, an error is thrown when the plugin is registered.\n */\nexport interface Plugin<T> {\n hooks: {\n [Name in keyof T]?: Hook<T[Name]>[] | Hook<T[Name]> | Handler<T[Name]>;\n };\n name?: PropertyKey;\n require?: string | string[];\n}\n\n/**\n * Represents a normalized plugin with standardized hooks.\n *\n * @typeParam T - The type of the arguments and return values of the hooks.\n * @property hooks - Hooks associated with the plugin, normalized to a standardized format.\n * @property name - Name identifying the plugin by other plugin with the `after`, `before` and `require` properties.\n * @property require - Names of the required plugins. If a required plugin is not registered, an error is thrown when the plugin is registered.\n */\nexport interface PluginNormalized<T> {\n hooks: {\n [Name in keyof T]: HookNormalized<T, Name>[];\n };\n name: PropertyKey | undefined;\n require: string[];\n}\n\n/**\n * Represents the public API of the plugin system.\n *\n * @typeParam T - The type of the arguments and return values of the hooks.\n */\nexport interface Api<T, Chain = undefined> {\n /**\n * Execute a handler function and its associated hooks.\n *\n * @param options - Options for the synchroneous hook execution.\n * @param options.args - Argument passed to the handler function as well as all hook handlers.\n * @param options.handler - Function to decorate, receive the value associated with the `args` property.\n * @param options.hooks - List of complementary hooks from the end user.\n * @param options.name - Name of the hook to execute.\n * @returns - A promise that resolves to the result of the final handler.\n */\n call: <K extends keyof T>(options: {\n args: T[K];\n handler?: Handler<T[K]>;\n hooks?: Hook<T[K]>[];\n name: K;\n }) => Promise<unknown>;\n /**\n * Execute a handler function and its associated hooks synchronously.\n *\n * @param options - Options for the asynchroneous hook execution.\n * @param options.args - Argument passed to the handler function as well as all hook handlers.\n * @param options.handler - Function to decorate, receive the value associated with the `args` property.\n * @param options.hooks - List of complementary hooks from the end user.\n * @param options.name - Name of the hook to execute.\n * @returns - The result of the final handler.\n */\n call_sync: <K extends keyof T>(options: {\n args: T[K];\n handler?: Handler<T[K]>;\n hooks?: Hook<T[K]>[];\n name: K;\n }) => unknown;\n /**\n * Retrieve the hooks with the given name from all registered plugins, in the order they were registered.\n *\n * @param options - Options used to retrieve the hooks. The default is `[]`.\n * @param options.hooks - List of complementary hooks from the end user. These hooks are merged with the hooks retrieved from the registered plugins.\n * @param options.name - Names of the hook to retrieve.\n * @param options.sort - Topological sorting of the hooks relatively to each other using the `after` and `before` properties. If `sort` is `false`, the hooks are returned in the order they were registered. The default is `true`.\n * @returns - The retrieved hooks.\n */\n get: <K extends keyof T>(options: {\n hooks?: Handler<T[K]> | Hook<T[K]> | Hook<T[K]>[];\n name: K;\n sort?: boolean;\n }) => HookNormalized<T, K>[];\n /**\n * Register a new plugin in the system.\n *\n * @param plugin - The plugin to register.\n * @returns - The plugin system.\n *\n * @remarks Plugin can be provided when instantiating Plug-And-Play by passing the plugins property or they can be provided later on by calling the register function.\n *\n */\n register: (\n plugin: Plugin<T> | ((...Args: unknown[]) => Plugin<T>),\n ) => Chain extends undefined ? this : Chain;\n /**\n * Check if a plugin with the given name is registered with the plugin system.\n *\n * @param name - The name of the plugin to check.\n * @returns - True if the plugin is registered, false otherwise.\n */\n registered: (name: PropertyKey) => boolean;\n}\n\n/**\n * A function to initialize a plugandplay instance. Creates a plugin system with support for hooks and plugin requirements.\n *\n * @typeParam T - The type of the arguments and return values of the hooks. An object representing the type of every hook arguments.\n * @example\n *\n * Loose typing:\n * ```typescript\n * plugandplay();\n * ```\n *\n * Specific typing:\n * ```typescript\n * plugandplay<{\n * \"first-hook\" : { bar: number; foo: string };\n * \"second-hook\" : { baz: object }\n * }>();\n * ```\n * @param options - The options used to initiate the library.\n * @param options.args - The arguments to pass to the registered plugins.\n * @param options.chain - The chain of plugins to call the hooks on.\n * @param options.parent - The parent plugin system to call the hooks on.\n * @param options.plugins - The initial plugins to register.\n * @returns - An object representing the plugin system.\n */\nconst plugandplay = function <\n T extends Record<string, unknown> = Record<string, unknown>,\n Chain = undefined,\n>({\n args = [],\n chain,\n parent,\n plugins = [],\n}: {\n args?: unknown[];\n chain?: Chain;\n parent?: Api<T, Chain>;\n plugins?: (Plugin<T> | (<FnArgs, T>(...Args: FnArgs[]) => Plugin<T>))[];\n} = {}): Api<T, Chain> {\n // Internal plugin store\n const store: PluginNormalized<T>[] = [];\n // Public API definition\n const api: Api<T, Chain> = {\n // Register new plugins\n register: function (plugin) {\n if (typeof plugin !== \"function\" && !is_object_literal(plugin)) {\n throw error(\"PLUGINS_REGISTER_INVALID_ARGUMENT\", [\n \"a plugin must be an object literal or a function returning an object literal\",\n \"with keys such as `name`, `required` and `hooks`,\",\n `got ${JSON.stringify(plugin)} instead.`,\n ]);\n }\n // Obtain plugin from user function\n plugin = typeof plugin === \"function\" ? plugin(...args) : plugin;\n // Hook normalization\n const hooksNormalized = {} as PluginNormalized<T>[\"hooks\"];\n for (const name in plugin.hooks) {\n if (!plugin.hooks[name]) continue;\n hooksNormalized[name] = normalize_hook(name, plugin.hooks[name]);\n }\n // Require normalization\n if (plugin.require && !Array.isArray(plugin.require)) {\n plugin.require = [plugin.require];\n }\n const require =\n !plugin.require ? []\n : !Array.isArray(plugin.require) ? [plugin.require]\n : plugin.require;\n const requireNormalized: string[] = require.map((require) => {\n if (typeof require !== \"string\")\n throw errors.PLUGINS_REGISTER_INVALID_REQUIRE({\n name: plugin.name,\n require: require,\n });\n return require;\n });\n // Plugin normalization\n const normalizedPlugin: PluginNormalized<T> = {\n hooks: hooksNormalized,\n require: requireNormalized,\n name: plugin.name,\n };\n store.push(normalizedPlugin);\n return (chain ?? this) as any;\n },\n registered: function (name) {\n for (const plugin of store) {\n if (plugin.name === name) {\n return true;\n }\n }\n if (parent != null && parent.registered(name)) {\n return true;\n }\n return false;\n },\n get: function ({ name, hooks = [], sort = true }) {\n const normalizedHooks = [\n // Merge hooks provided by the user\n ...normalize_hook(name, hooks),\n // With hooks present in the store\n ...store\n .map(function (plugin) {\n // Only select plugins with the requested hook\n if (!plugin.hooks[name]) return;\n // Validate plugin requirements\n for (const require of plugin.require) {\n if (!api.registered(require)) {\n throw errors.REQUIRED_PLUGIN({\n plugin: plugin.name,\n require: require,\n });\n }\n }\n return plugin.hooks[name]?.map(\n (hook) =>\n ({\n plugin: plugin.name,\n require: plugin.require,\n ...hook,\n }) as HookNormalized<T, typeof name>,\n );\n })\n .filter(function (hook) {\n return hook !== undefined;\n })\n .flat(1),\n ...(parent ? parent.get({ name: name, sort: false }) : []),\n ];\n if (!sort) {\n return normalizedHooks;\n }\n // Topological sort\n // Hook index associates plugin names to the requested hook\n // About PLUGINS_HOOK_[AFTER|BEFORE]_INVALID error:\n // It is ok for a hook to refer to a non-registered and optional plugin\n // However, if the plugin exists, then it must expose a hook of the same name.\n const index: Record<string, HookNormalized<T, typeof name>> = {};\n for (const hook of normalizedHooks) {\n if (hook.plugin) index[hook.plugin] = hook;\n }\n type Test = [\n HookNormalized<T, typeof name>,\n HookNormalized<T, typeof name>,\n ][];\n const edges_after = normalizedHooks.map(function (hook) {\n return hook.after.reduce<Test>(function (result, after) {\n if (index[after]) {\n result.push([index[after], hook]);\n } else if (api.registered(after)) {\n throw errors.PLUGINS_HOOK_AFTER_INVALID({\n name: name,\n plugin: hook.plugin,\n after: after,\n });\n }\n return result;\n }, []);\n });\n const edges_before = normalizedHooks.map(function (hook) {\n return hook.before.reduce<Test>(function (result, before) {\n if (index[before]) {\n result.push([hook, index[before]]);\n } else if (api.registered(before)) {\n throw errors.PLUGINS_HOOK_BEFORE_INVALID({\n name: name,\n plugin: hook.plugin,\n before: before,\n });\n }\n return result;\n }, []);\n });\n const edges = [...edges_after, ...edges_before].flat(1);\n return toposort.array(normalizedHooks, edges);\n },\n // Call a hook against each registered plugin matching the hook name\n call: async function ({ args, handler, hooks = [], name }) {\n if (arguments.length !== 1) {\n throw error(\"PLUGINS_INVALID_ARGUMENTS_NUMBER\", [\n \"function `call` expect 1 object argument,\",\n `got ${arguments.length} arguments.`,\n ]);\n } else if (!is_object_literal(arguments[0])) {\n throw error(\"PLUGINS_INVALID_ARGUMENT_PROPERTIES\", [\n \"function `call` expect argument to be a literal object\",\n \"with the properties `name`, `args`, `hooks` and `handler`,\",\n `got ${JSON.stringify(arguments[0])} arguments.`,\n ]);\n } else if (typeof name !== \"string\") {\n throw error(\"PLUGINS_INVALID_ARGUMENT_NAME\", [\n \"function `call` requires a property `name` in its first argument,\",\n `got ${JSON.stringify(arguments[0])} argument.`,\n ]);\n }\n // Retrieve the name hooks\n const hooksNormalized = this.get({\n hooks: hooks,\n name: name,\n });\n // Call the hooks\n handler = handler || (() => {});\n for (const hook of hooksNormalized) {\n switch (hook.handler.length) {\n case 0:\n case 1:\n await hook.handler(args, () => {});\n break;\n case 2:\n const result = await hook.handler(args, handler);\n if (result === null) {\n return null;\n // Note, this respect the implementation prior the TS migration\n // See test in call.ts # continue with `undefined` # when `undefined` is returned, sync mode\n // Not necessarily a good idea, shall be more strict on what the\n // hook handler might return\n } else {\n // }else if(result !== undefined) {\n // }else if (typeof result === 'function') {\n handler = result as Handler<T[typeof hook.name]>;\n }\n break;\n default:\n throw error(\"PLUGINS_INVALID_HOOK_HANDLER\", [\n \"hook handlers must have 0 to 2 arguments\",\n `got ${hook.handler.length}`,\n ]);\n }\n }\n // Call the final handler\n return handler ? handler(args, () => {}) : undefined;\n },\n // Call a hook against each registered plugin matching the hook name\n call_sync: function ({ args, handler, hooks = [], name }) {\n if (arguments.length !== 1) {\n throw error(\"PLUGINS_INVALID_ARGUMENTS_NUMBER\", [\n \"function `call` expect 1 object argument,\",\n `got ${arguments.length} arguments.`,\n ]);\n } else if (!is_object_literal(arguments[0])) {\n throw error(\"PLUGINS_INVALID_ARGUMENT_PROPERTIES\", [\n \"function `call` expect argument to be a literal object\",\n \"with the properties `name`, `args`, `hooks` and `handler`,\",\n `got ${JSON.stringify(arguments[0])} arguments.`,\n ]);\n } else if (typeof name !== \"string\") {\n throw error(\"PLUGINS_INVALID_ARGUMENT_NAME\", [\n \"function `call` requires a property `name` in its first argument,\",\n `got ${JSON.stringify(arguments[0])} argument.`,\n ]);\n }\n // Retrieve the name hooks\n const hooksNormalized = this.get({\n hooks: hooks,\n name: name,\n });\n // Call the hooks\n handler = handler || (() => {});\n for (const hook of hooksNormalized) {\n switch (hook.handler.length) {\n case 0:\n case 1:\n hook.handler(args, () => {});\n break;\n case 2:\n const result = hook.handler(args, handler);\n if (result === null) {\n return null;\n // Note, this respect the implementation prior the TS migration\n // See test in call.ts # continue with `undefined` # when `undefined` is returned, sync mode\n // Not necessarily a good idea, shall be more strict on what the\n // hook handler might return\n } else {\n // }else if(result !== undefined) {\n // }else if (typeof result === 'function') {\n handler = result as Handler<T[typeof hook.name]>;\n }\n break;\n default:\n throw error(\"PLUGINS_INVALID_HOOK_HANDLER\", [\n \"hook handlers must have 0 to 2 arguments\",\n `got ${hook.handler.length}`,\n ]);\n }\n }\n // Call the final handler\n return handler ? handler(args, () => {}) : undefined;\n },\n };\n // Register initial plugins\n for (const plugin of plugins) {\n api.register(plugin);\n }\n // return the object\n return api;\n};\n\n/**\n * Normalize a hook definition to a standardized format.\n *\n * @typeParam T - Type of parameters expected by hook handlers.\n *\n * @param name - Name of the hook.\n * @param hook - User-defined hook definition.\n *\n * @returns An array of standardized hook definitions.\n */\nconst normalize_hook = function <T, K extends keyof T>(\n name: K,\n hook: Handler<T[K]> | Hook<T[K]> | Hook<T[K]>[],\n): HookNormalized<T, K>[] {\n const hooks = Array.isArray(hook) ? hook : [hook];\n return hooks.map(function (hook) {\n if (typeof hook !== \"function\" && !is_object(hook)) {\n throw error(\"PLUGINS_HOOK_INVALID_HANDLER\", [\n \"no hook handler function could be found,\",\n \"a hook must be defined as a function\",\n \"or as an object with an handler property,\",\n `got ${JSON.stringify(hook)} instead.`,\n ]);\n }\n return {\n after:\n !(typeof hook !== \"function\" && hook.after) ? []\n : typeof hook.after === \"string\" ? [hook.after]\n : hook.after,\n name: name,\n before:\n !(typeof hook !== \"function\" && hook.before) ? []\n : typeof hook.before === \"string\" ? [hook.before]\n : hook.before,\n handler: typeof hook === \"function\" ? hook : hook.handler,\n };\n });\n};\n\nconst errors = {\n PLUGINS_HOOK_AFTER_INVALID: function ({\n name,\n plugin,\n after,\n }: {\n name: PropertyKey;\n plugin: string | undefined;\n after: string;\n }) {\n throw error(\"PLUGINS_HOOK_AFTER_INVALID\", [\n `the hook ${JSON.stringify(name)}`,\n plugin ? `in plugin ${JSON.stringify(plugin)}` : \"\",\n \"references an after dependency\",\n `in plugin ${JSON.stringify(after)} which does not exists.`,\n ]);\n },\n PLUGINS_HOOK_BEFORE_INVALID: function ({\n name,\n plugin,\n before,\n }: {\n name: PropertyKey;\n plugin: string | undefined;\n before: string;\n }) {\n throw error(\"PLUGINS_HOOK_BEFORE_INVALID\", [\n `the hook ${JSON.stringify(name)}`,\n plugin ? `in plugin ${JSON.stringify(plugin)}` : \"\",\n \"references a before dependency\",\n `in plugin ${JSON.stringify(before)} which does not exists.`,\n ]);\n },\n REQUIRED_PLUGIN: function ({\n plugin,\n require,\n }: {\n plugin: PropertyKey | undefined;\n require: string;\n }) {\n throw error(\"REQUIRED_PLUGIN\", [\n `the plugin ${JSON.stringify(plugin)}`,\n \"requires a plugin\",\n `named ${JSON.stringify(require)} which is not unregistered.`,\n ]);\n },\n PLUGINS_REGISTER_INVALID_REQUIRE: function ({\n name,\n require,\n }: {\n name: PropertyKey | undefined;\n require: string;\n }) {\n throw error(\"PLUGINS_REGISTER_INVALID_REQUIRE\", [\n \"the `require` property\",\n name ? `in plugin ${JSON.stringify(name)}` : \"\",\n \"must be a string or an array,\",\n `got ${JSON.stringify(require)}.`,\n ]);\n },\n};\n\nexport { plugandplay };\n","\nclass PlugableError extends Error {\n public code: string;\n [index: string]: unknown;\n constructor(\n code: string,\n message: string | (string | object)[],\n ...contexts: Record<string, unknown>[]\n ) {\n if (Array.isArray(message)) {\n message = message\n .filter(function (line) {\n return !!line;\n })\n .join(\" \");\n }\n message = `${code}: ${message}`;\n super(message);\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, PlugableError);\n }\n this.code = code;\n for (let i = 0; i < contexts.length; i++) {\n const context = contexts[i];\n for (const key in context) {\n if (key === \"code\") {\n continue;\n }\n const value = context[key];\n if (value === void 0) {\n continue;\n }\n this[key] =\n Buffer.isBuffer(value) ? value.toString()\n : value === null ? value\n : JSON.parse(JSON.stringify(value));\n }\n }\n }\n};\nexport { PlugableError };\nexport default (function (...args: ConstructorParameters<typeof PlugableError>) {\n return new PlugableError(...args);\n});\n"]}