diff --git a/dist/cjs/index.cjs b/dist/cjs/index.cjs index 4e85b55..592dadb 100644 --- a/dist/cjs/index.cjs +++ b/dist/cjs/index.cjs @@ -1,370 +1,2 @@ -'use strict'; - -var mixme = require('mixme'); -var toposort = require('toposort'); - -const PlugableError = class PlugableError extends Error { - constructor(code, message, ...contexts) { - if (Array.isArray(message)) { - message = message.filter(function (line) { - return !!line; - }).join(' '); - } - message = `${code}: ${message}`; - super(message); - if (Error.captureStackTrace) { - Error.captureStackTrace(this, PlugableError); - } - this.code = code; - for (let i = 0; i < contexts.length; i++) { - const context = contexts[i]; - for (const key in context) { - if (key === 'code') { - continue; - } - const value = context[key]; - if (value === void 0) { - continue; - } - this[key] = Buffer.isBuffer(value) ? value.toString() : value === null ? value : JSON.parse(JSON.stringify(value)); - } - } - } -}; -var error = (function () { - return new PlugableError(...arguments); -}); - -const array_flatten = function (items, depth = -1) { - const result = []; - for (const item of items) { - if (Array.isArray(item)) { - if (depth === 0) { - result.push(...item); - } - else { - result.push(...array_flatten(item, depth - 1)); - } - } - else { - result.push(item); - } - } - return result; -}; - -const normalize_hook = function (name, hook) { - if (!Array.isArray(hook)) { - hook = [hook]; - } - return hook.map(function (hook) { - if (typeof hook === "function") { - hook = { - handler: hook, - }; - } else if (!mixme.is_object(hook)) { - throw error("PLUGINS_HOOK_INVALID_HANDLER", [ - "no hook handler function could be found,", - "a hook must be defined as a function", - "or as an object with an handler property,", - `got ${JSON.stringify(hook)} instead.`, - ]); - } - hook.name = name; - if (typeof hook.after === "string") { - hook.after = [hook.after]; - } - if (typeof hook.before === "string") { - hook.before = [hook.before]; - } - return hook; - }); -}; - -const errors = { - PLUGINS_HOOK_AFTER_INVALID: function ({ name, plugin, after }) { - throw error("PLUGINS_HOOK_AFTER_INVALID", [ - `the hook ${JSON.stringify(name)}`, - plugin ? `in plugin ${JSON.stringify(plugin)}` : void 0, - "references an after dependency", - `in plugin ${JSON.stringify(after)} which does not exists.`, - ]); - }, - PLUGINS_HOOK_BEFORE_INVALID: function ({ name, plugin, before }) { - throw error("PLUGINS_HOOK_BEFORE_INVALID", [ - `the hook ${JSON.stringify(name)}`, - plugin ? `in plugin ${JSON.stringify(plugin)}` : void 0, - "references a before dependency", - `in plugin ${JSON.stringify(before)} which does not exists.`, - ]); - }, - REQUIRED_PLUGIN: function ({ plugin, require }) { - throw error("REQUIRED_PLUGIN", [ - `the plugin ${JSON.stringify(plugin)}`, - "requires a plugin", - `named ${JSON.stringify(require)} which is not unregistered.`, - ]); - }, - PLUGINS_REGISTER_INVALID_REQUIRE: function ({ name, require }) { - throw error("PLUGINS_REGISTER_INVALID_REQUIRE", [ - "the `require` property", - name ? `in plugin ${JSON.stringify(name)}` : void 0, - "must be a string or an array,", - `got ${JSON.stringify(require)}.`, - ]); - }, -}; - -const plugandplay = function ({ args, chain, parent, plugins = [] } = {}) { - // Internal plugin store - const store = []; - // Public API definition - const registry = { - // Register new plugins - register: function (plugin) { - if (typeof plugin === "function") { - plugin = plugin.apply(null, args); - } - if (!mixme.is_object_literal(plugin)) { - throw error("PLUGINS_REGISTER_INVALID_ARGUMENT", [ - "a plugin must be an object literal or a function returning an object literal", - "with keys such as `name`, `required` and `hooks`,", - `got ${JSON.stringify(plugin)} instead.`, - ]); - } - if (plugin.hooks == null) { - plugin.hooks = {}; - } - for (const name in plugin.hooks) { - plugin.hooks[name] = normalize_hook(name, plugin.hooks[name]); - } - if (plugin.require == null) { - plugin.require = []; - } else if (typeof plugin.require === "string") { - plugin.require = [plugin.require]; - } - if (!Array.isArray(plugin.require)) { - throw errors.PLUGINS_REGISTER_INVALID_REQUIRE({ - name: plugin.name, - require: plugin.require, - }); - } - store.push(plugin); - return chain || this; - }, - registered: function (name) { - for (const plugin of store) { - if (plugin.name === name) { - return true; - } - } - if (parent != null && parent.registered(name)) { - return true; - } - return false; - }, - get: function ({ name, hooks = [], sort = true }) { - hooks = [ - // Merge hooks provided by the user - ...normalize_hook(name, hooks), - // With hooks present in the store - ...array_flatten( - store - .map(function (plugin) { - // Only filter plugins with the requested hook - if (!plugin.hooks[name]) return; - // Validate plugin requirements - for (const require of plugin.require) { - if (!registry.registered(require)) { - throw errors.REQUIRED_PLUGIN({ - plugin: plugin.name, - require: require, - }); - } - } - return plugin.hooks[name].map(function (hook) { - return mixme.merge( - { - plugin: plugin.name, - require: plugin.require, - }, - hook - ); - }); - }) - .filter(function (hook) { - return hook !== undefined; - }) - ), - ...(parent ? parent.get({ name: name, sort: false }) : []), - ]; - if (!sort) { - return hooks; - } - // Topological sort - const index = {}; - for (const hook of hooks) { - if (hook.plugin) index[hook.plugin] = hook; - } - const edges_after = hooks - .map(function (hook) { - if (!hook.after) return; - return hook.after - .map(function (after) { - // This check assume the plugin has the same hooks which is not always the case - if (!index[after]) { - if (registry.registered(after)) { - throw errors.PLUGINS_HOOK_AFTER_INVALID({ - name: name, - plugin: hook.plugin, - after: after, - }); - } else { - return undefined; - } - } - return [index[after], hook]; - }) - .filter(function (hook) { - return hook !== undefined; - }); - }) - .filter(function (hook) { - return hook !== undefined; - }); - const edges_before = hooks - .map(function (hook) { - if (!hook.before) return; - return hook.before - .map(function (before) { - if (!index[before]) { - if (registry.registered(before)) { - throw errors.PLUGINS_HOOK_BEFORE_INVALID({ - name: name, - plugin: hook.plugin, - before: before, - }); - } else { - return undefined; - } - } - return [hook, index[before]]; - }) - .filter(function (hook) { - return hook !== undefined; - }); - }) - .filter(function (hook) { - return hook !== undefined; - }); - const edges = array_flatten([...edges_after, ...edges_before], 0); - return toposort.array(hooks, edges); - }, - // Call a hook against each registered plugin matching the hook name - call: async function ({ args = [], handler, hooks = [], name }) { - if (arguments.length !== 1) { - throw error("PLUGINS_INVALID_ARGUMENTS_NUMBER", [ - "function `call` expect 1 object argument,", - `got ${arguments.length} arguments.`, - ]); - } else if (!mixme.is_object_literal(arguments[0])) { - throw error("PLUGINS_INVALID_ARGUMENT_PROPERTIES", [ - "function `call` expect argument to be a literal object", - "with the properties `name`, `args`, `hooks` and `handler`,", - `got ${JSON.stringify(arguments[0])} arguments.`, - ]); - } else if (typeof name !== "string") { - throw error("PLUGINS_INVALID_ARGUMENT_NAME", [ - "function `call` requires a property `name` in its first argument,", - `got ${JSON.stringify(arguments[0])} argument.`, - ]); - } - // Retrieve the name hooks - hooks = this.get({ - hooks: hooks, - name: name, - }); - // Call the hooks - for (const hook of hooks) { - switch (hook.handler.length) { - case 0: - case 1: - await hook.handler.call(this, args); - break; - case 2: - handler = await hook.handler.call(this, args, handler); - if (handler === null) { - return null; - } - break; - default: - throw error("PLUGINS_INVALID_HOOK_HANDLER", [ - "hook handlers must have 0 to 2 arguments", - `got ${hook.handler.length}`, - ]); - } - } - if (handler) { - // Call the final handler - return handler.call(this, args); - } - }, - // Call a hook against each registered plugin matching the hook name - call_sync: function ({ args = [], handler, hooks = [], name }) { - if (arguments.length !== 1) { - throw error("PLUGINS_INVALID_ARGUMENTS_NUMBER", [ - "function `call` expect 1 object argument,", - `got ${arguments.length} arguments.`, - ]); - } else if (!mixme.is_object_literal(arguments[0])) { - throw error("PLUGINS_INVALID_ARGUMENT_PROPERTIES", [ - "function `call` expect argument to be a literal object", - "with the properties `name`, `args`, `hooks` and `handler`,", - `got ${JSON.stringify(arguments[0])} arguments.`, - ]); - } else if (typeof name !== "string") { - throw error("PLUGINS_INVALID_ARGUMENT_NAME", [ - "function `call` requires a property `name` in its first argument,", - `got ${JSON.stringify(arguments[0])} argument.`, - ]); - } - // Retrieve the name hooks - hooks = this.get({ - hooks: hooks, - name: name, - }); - // Call the hooks - for (const hook of hooks) { - switch (hook.handler.length) { - case 0: - case 1: - hook.handler.call(this, args); - break; - case 2: - handler = hook.handler.call(this, args, handler); - if (handler === null) { - return null; - } - break; - default: - throw error("PLUGINS_INVALID_HOOK_HANDLER", [ - "hook handlers must have 0 to 2 arguments", - `got ${hook.handler.length}`, - ]); - } - } - if (handler) { - // Call the final handler - return handler.call(this, args); - } - }, - }; - // Register initial plugins - for (const plugin of plugins) { - registry.register(plugin); - } - // return the object - return registry; -}; - -exports.plugandplay = plugandplay; +"use strict";var e=require("mixme"),r=require("toposort");function n(e,r,n,t){return new(n||(n=Promise))((function(i,o){function a(e){try{s(t.next(e))}catch(e){o(e)}}function u(e){try{s(t.throw(e))}catch(e){o(e)}}function s(e){var r;e.done?i(e.value):(r=e.value,r instanceof n?r:new n((function(e){e(r)}))).then(a,u)}s((t=t.apply(e,r||[])).next())}))}"function"==typeof SuppressedError&&SuppressedError;const t=class e extends Error{constructor(r,n,...t){Array.isArray(n)&&(n=n.filter((function(e){return!!e})).join(" ")),super(n=`${r}: ${n}`),Error.captureStackTrace&&Error.captureStackTrace(this,e),this.code=r;for(let e=0;ee.call(this,r)))},get:function({name:n,hooks:t=[],sort:i=!0}){const s=[...o(n,t),...h.map((function(r){if(r.hooks[n]){if(r.require)for(const e of r.require)if(!p.registered(e))throw f({plugin:r.name,require:e});return r.hooks[n].map((function(n){return e.merge({plugin:r.name,require:r.require},n)}))}})).filter((function(e){return void 0!==e})).flat(),...c?c.get({name:n,sort:!1}):[]];if(!i)return s;const l={};for(const e of s)e&&"plugin"in e&&e.plugin&&(l[e.plugin]=e);const g=[...s.map((function(e){if("after"in e&&Array.isArray(e.after))return e.after.map((function(r){if(l[r]||!("plugin"in e)||!e.plugin)return[l[r],e];if(p.registered(r))throw a({after:r,name:n,plugin:e.plugin})})).filter((function(e){return void 0!==e}))})).filter((function(e){return void 0!==e})),...s.map((function(e){if("before"in e&&Array.isArray(e.before))return e.before.map((function(r){if(l[r]||!("plugin"in e)||!e.plugin)return[e,l[r]];if(p.registered(r))throw u({before:r,name:n,plugin:e.plugin})})).filter((function(e){return void 0!==e}))})).filter((function(e){return void 0!==e}))].flat(1);return r.array(s,g).map((e=>(e&&("require"in e&&delete e.require,"plugin"in e&&delete e.plugin),e)))}};for(const e of g)p.register(e);return p}; +//# sourceMappingURL=index.cjs.map diff --git a/dist/cjs/index.cjs.map b/dist/cjs/index.cjs.map new file mode 100644 index 0000000..93fc062 --- /dev/null +++ b/dist/cjs/index.cjs.map @@ -0,0 +1 @@ +{"version":3,"file":"index.cjs","sources":["../../lib/error.ts","../../lib/index.ts"],"sourcesContent":["const PlugableError = class PlugableError extends Error {\n public code: string;\n [index: string]: unknown; // required to allow adding class props dynamically\n constructor(\n code: string,\n message: string | (string | undefined)[],\n ...contexts: Record[]\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] = Buffer.isBuffer(value)\n ? value.toString()\n : value === null\n ? value\n : JSON.parse(JSON.stringify(value));\n }\n }\n }\n};\nexport default (function (\n ...args: ConstructorParameters\n) {\n return new PlugableError(...args);\n});\n","import { is_object, is_object_literal, merge } from 'mixme';\nimport toposort from 'toposort';\n\nimport error 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 */\nexport type HookHandler = (\n args: T,\n handler?: HookHandler\n) => null | void | HookHandler | Promise>;\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 */\nexport interface Hook {\n /**\n * List of plugin names with hooks of the same name that should be executed after this hook.\n * If a string is provided, it is coerced to an array.\n */\n after?: string | string[];\n\n /**\n * List of plugin names with hooks of the same name that should be executed before this hook.\n * If a string is provided, it is coerced to an array.\n */\n before?: string | string[];\n\n /**\n * The hook handler to be executed.\n */\n handler: HookHandler;\n\n /**\n * Name to identify the hook.\n */\n name?: PropertyKey;\n\n /**\n * Name of the plugin that defines this hook.\n */\n plugin?: string;\n\n /**\n * List of plugin names that this hook requires.\n */\n require?: string | 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 */\nexport interface Plugin {\n /**\n * List of hooks identified by hook names.\n *\n * Each hook can be an array of hooks, a single hook, or a handler function.\n */\n hooks: {\n [name in keyof T]: Hook[] | Hook | HookHandler;\n };\n\n /**\n * Name of the plugin.\n */\n name: PropertyKey;\n\n /**\n * Names of the required plugins.\n *\n * If a required plugin is not registered, an error will be thrown when the plugin is registered.\n */\n require?: string[];\n}\n\ninterface callArguments {\n /**\n * Argument passed to the handler function as well as all hook handlers.\n */\n args: T[K];\n\n /**\n * Function to decorate, receive the value associated with the `args` property.\n */\n handler: HookHandler;\n\n /**\n * List of completary hooks from the end user.\n */\n hooks?: Hook[];\n\n /**\n * Name of the hook to execute.\n */\n name: K;\n}\ninterface getArguments {\n /**\n * List of complementary hooks from the end user.\n */\n hooks?: Hook[];\n\n /**\n * Name of the hook to retrieve.\n */\n name: K;\n\n /**\n * Sort the hooks relatively to each other using the after and before properties.\n */\n sort?: boolean;\n}\n\ntype CallFunction = (\n args: callArguments\n) => Promise;\n\ntype CallSyncFunction = (\n args: callArguments\n) => unknown;\n\ntype GetFunction = (\n args: getArguments\n) => Hook[];\n\ninterface Registry {\n /**\n * Execute a hander function and its associated hooks.\n */\n call: CallFunction;\n /**\n * Execute a hander function and its associated hooks, synchronously.\n */\n call_sync: CallSyncFunction;\n /**\n * Retrieves registered hooks.\n */\n get: GetFunction;\n /**\n * Registers a plugin\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 register: (\n userPlugin: Plugin | ((...args: unknown[]) => Plugin)\n ) => Registry;\n /**\n * Check if a plugin is registered.\n */\n registered: (name: PropertyKey) => boolean;\n}\n\ninterface plugangplayArguments {\n args?: unknown[];\n chain?: Registry;\n parent?: Registry;\n plugins?: Plugin[];\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 (\n name: K,\n hook: Hook | Hook[] | HookHandler\n): Hook[] {\n const hookArray = Array.isArray(hook) ? hook : [hook];\n\n return hookArray.map(function (hook) {\n let normalizedHook = structuredClone(hook);\n\n if (typeof normalizedHook === 'function') {\n normalizedHook = {\n handler: normalizedHook,\n };\n } else if (!is_object(normalizedHook)) {\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(normalizedHook)} instead.`,\n ]);\n }\n\n normalizedHook.name = name;\n\n normalizedHook.after =\n 'after' in normalizedHook && typeof normalizedHook.after === 'string'\n ? [normalizedHook.after]\n : normalizedHook?.after;\n normalizedHook.before =\n 'before' in normalizedHook && typeof normalizedHook.before === 'string'\n ? [normalizedHook.before]\n : normalizedHook?.before;\n\n return normalizedHook;\n });\n};\n\nconst errors = {\n PLUGINS_HOOK_AFTER_INVALID: function ({\n name,\n plugin,\n after,\n }: {\n after: string;\n name: PropertyKey;\n plugin: string;\n }) {\n throw error('PLUGINS_HOOK_AFTER_INVALID', [\n `the hook ${JSON.stringify(name)}`,\n plugin ? `in plugin ${JSON.stringify(plugin)}` : void 0,\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 before: string;\n name: PropertyKey;\n plugin: string;\n }) {\n throw error('PLUGINS_HOOK_BEFORE_INVALID', [\n `the hook ${JSON.stringify(name)}`,\n plugin ? `in plugin ${JSON.stringify(plugin)}` : void 0,\n 'references a before dependency',\n `in plugin ${JSON.stringify(before)} which does not exists.`,\n ]);\n },\n PLUGINS_REGISTER_INVALID_REQUIRE: function ({\n name,\n require,\n }: {\n name: PropertyKey;\n require: string;\n }) {\n throw error('PLUGINS_REGISTER_INVALID_REQUIRE', [\n 'the `require` property',\n name ? `in plugin ${JSON.stringify(name)}` : void 0,\n 'must be a string or an array,',\n `got ${JSON.stringify(require)}.`,\n ]);\n },\n REQUIRED_PLUGIN: function ({\n plugin,\n require,\n }: {\n plugin: PropertyKey;\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 registered.`,\n ]);\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 */\ninterface NormalizedPlugin {\n /**\n * The hooks associated with the plugin, normalized to a standardized format.\n */\n hooks: {\n [name in keyof T]: Hook[];\n };\n\n /**\n * Name of the plugin.\n */\n name: PropertyKey;\n\n /**\n * Names of the required plugins.\n */\n require?: string[];\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 args - The arguments to pass to the registered plugins.\n * @param chain - The chain of plugins to call the hooks on.\n * @param parent - The parent plugin system to call the hooks on.\n * @param plugins - The initial plugins to register.\n * @returns - An object representing the plugin system.\n */\nconst plugandplay = function <\n T extends Record = Record,\n>({\n args = [],\n chain,\n parent,\n plugins = [],\n}: plugangplayArguments = {}): Registry {\n // Internal plugin store\n const store: NormalizedPlugin[] = [];\n\n // Public API definition\n const registry: Registry = {\n /**\n * Registers a plugin with the plugin system.\n *\n * @param plugin - The plugin to register.\n * @returns - The plugin system.\n */\n register: function (plugin) {\n const normalizedPlugin: NormalizedPlugin =\n typeof plugin === 'function'\n ? (plugin(...args) as NormalizedPlugin)\n : (structuredClone(plugin) as NormalizedPlugin);\n\n // Validate the plugin\n if (\n !(\n is_object_literal(normalizedPlugin) &&\n 'name' in normalizedPlugin &&\n typeof normalizedPlugin.name === 'string'\n )\n ) {\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(normalizedPlugin)} instead.`,\n ]);\n }\n\n // Normalize the plugin hooks\n if (normalizedPlugin.hooks == null) {\n normalizedPlugin.hooks = {} as { [name in keyof T]: Hook[] };\n }\n\n for (const name in normalizedPlugin.hooks) {\n normalizedPlugin.hooks[name] = normalize_hook(\n name,\n normalizedPlugin.hooks[name]\n );\n }\n\n // Normalize the plugin requirements\n normalizedPlugin.require = [];\n if ('require' in plugin && plugin.require) {\n if (typeof plugin.require === 'string') {\n normalizedPlugin.require = [plugin.require];\n }\n if (!Array.isArray(plugin.require)) {\n throw errors.PLUGINS_REGISTER_INVALID_REQUIRE({\n name: plugin.name,\n require: plugin.require,\n });\n }\n }\n\n // Add the plugin to the store\n store.push(normalizedPlugin);\n\n // Return the plugin system\n return chain ?? this;\n },\n\n /**\n * Checks 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: function (name) {\n for (const plugin of store) {\n if (plugin.name === name) {\n return true;\n }\n }\n return !!parent?.registered(name);\n },\n\n /**\n * Calls the hooks with the given name on all registered plugins, in the order they were registered.\n *\n * @param args - The arguments to pass to the hooks.\n * @param handler - The handler to pass to the hooks.\n * @param hooks - The hooks to call.\n * @param name - The name of the hooks to call.\n * @returns - A promise that resolves to the result of the final handler.\n */\n call: async function ({ args, handler, hooks, name }) {\n // Validate the arguments\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 hooks\n hooks = this.get({\n hooks: hooks,\n name: name,\n });\n\n // Call the hooks\n let maybeHandler;\n for (const hook of hooks) {\n switch (hook.handler.length) {\n case 0:\n case 1: {\n await hook.handler.call(this, args);\n break;\n }\n case 2: {\n maybeHandler = await hook.handler.call(this, args, handler);\n if (maybeHandler === null) {\n return null;\n }\n break;\n }\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 }\n if (maybeHandler) {\n // Call the final handler\n return maybeHandler.call(this, args);\n }\n },\n\n /**\n * Calls the hooks with the given name on all registered plugins, in the order they were registered.\n *\n * @param args - The arguments to pass to the hooks.\n * @param handler - The handler to pass to the hooks.\n * @param hooks - The hooks to call.\n * @param name - The name of the hooks to call.\n * @returns - The result of the final handler.\n */\n call_sync: function ({ args, handler, hooks, name }) {\n // Validate the arguments\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 hooks\n hooks = this.get({\n hooks: hooks,\n name: name,\n });\n\n // Call the hooks\n let maybeHandler;\n for (const hook of hooks) {\n switch (hook.handler.length) {\n case 0:\n case 1: {\n hook.handler.call(this, args);\n break;\n }\n case 2: {\n maybeHandler = hook.handler.call(this, args, handler);\n if (maybeHandler === null) {\n return null;\n }\n break;\n }\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 }\n if (maybeHandler) {\n return Promise.resolve(maybeHandler).then((handler) =>\n handler.call(this, args)\n );\n }\n },\n\n /**\n * Retrieves the hooks with the given name from all registered plugins, in the order they were registered.\n *\n * @param hooks - The hooks to retrieve.\n * @param name - The name of the hooks to retrieve.\n * @param sort - Whether to sort the hooks topologically.\n * @returns - The retrieved hooks.\n */\n get: function ({\n name,\n hooks = [],\n sort = true,\n }: getArguments) {\n const mergedHooks = [\n ...normalize_hook(name, hooks),\n ...store\n .map(function (plugin) {\n // Only filter plugins with the requested hook\n if (!plugin.hooks[name]) return;\n // Validate plugin requirements\n if (plugin.require) {\n for (const require of plugin.require) {\n if (!registry.registered(require)) {\n throw errors.REQUIRED_PLUGIN({\n plugin: plugin.name,\n require: require,\n });\n }\n }\n }\n\n // Normalize the plugin hooks\n return plugin.hooks[name].map(function (hook) {\n return merge(\n {\n plugin: plugin.name,\n require: plugin.require,\n },\n hook\n ) as Hook;\n });\n })\n .filter(function (hook) {\n return hook !== undefined;\n })\n .flat(),\n ...(parent\n ? parent.get({\n name: name,\n sort: false,\n })\n : []),\n ];\n\n if (!sort) {\n return mergedHooks;\n }\n\n // Topological sort\n const index: Record> = {};\n for (const hook of mergedHooks) {\n if (hook && 'plugin' in hook && hook.plugin) index[hook.plugin] = hook;\n }\n const edges_after = mergedHooks\n .map(function (hook) {\n if (!('after' in hook && Array.isArray(hook.after))) return;\n return hook.after\n .map(function (after) {\n // This check assume the plugin has the same hooks which is not always the case\n if (!index[after] && 'plugin' in hook && hook.plugin) {\n if (registry.registered(after)) {\n throw errors.PLUGINS_HOOK_AFTER_INVALID({\n after: after,\n name: name,\n plugin: hook.plugin,\n });\n } else {\n return;\n }\n }\n return [index[after], hook];\n })\n .filter(function (hook) {\n return hook !== undefined;\n }) as [Hook, Hook][];\n })\n .filter(function (hook) {\n return hook !== undefined;\n });\n const edges_before = mergedHooks\n .map(function (hook) {\n if (!('before' in hook && Array.isArray(hook.before))) return;\n return hook.before\n .map(function (before) {\n if (!index[before] && 'plugin' in hook && hook.plugin) {\n if (registry.registered(before)) {\n throw errors.PLUGINS_HOOK_BEFORE_INVALID({\n before: before,\n name: name,\n plugin: hook.plugin,\n });\n } else {\n return;\n }\n }\n return [hook, index[before]];\n })\n .filter(function (hook) {\n return hook !== undefined;\n }) as [Hook, Hook][];\n })\n .filter(function (hook) {\n return hook !== undefined;\n });\n const edges = [...edges_after, ...edges_before].flat(1);\n return toposort.array(mergedHooks, edges).map((hook) => {\n if (hook) {\n if ('require' in hook) delete hook.require;\n if ('plugin' in hook) delete hook.plugin;\n }\n return hook;\n });\n },\n };\n // Register initial plugins\n for (const plugin of plugins) {\n registry.register(plugin);\n }\n // return the plugin system\n return registry;\n};\n\nexport { plugandplay };\n"],"names":["PlugableError","Error","constructor","code","message","contexts","Array","isArray","filter","line","join","super","captureStackTrace","this","i","length","context","key","value","Buffer","isBuffer","toString","JSON","parse","stringify","error","args","normalize_hook","name","hook","map","normalizedHook","structuredClone","handler","is_object","after","before","errors","plugin","require","chain","parent","plugins","store","registry","register","normalizedPlugin","is_object_literal","hooks","push","registered","call","_a","__awaiter","arguments","arguments_1","maybeHandler","get","call_sync","Promise","resolve","then","sort","mergedHooks","merge","undefined","flat","index","edges","toposort","array"],"mappings":"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"} \ No newline at end of file diff --git a/dist/dts/error.d.ts b/dist/dts/error.d.ts new file mode 100644 index 0000000..1a5179c --- /dev/null +++ b/dist/dts/error.d.ts @@ -0,0 +1,8 @@ +declare const _default: (code: string, message: string | (string | undefined)[], ...contexts: Record[]) => { + [index: string]: unknown; + code: string; + name: string; + message: string; + stack?: string; +}; +export default _default; diff --git a/dist/dts/index.d.ts b/dist/dts/index.d.ts new file mode 100644 index 0000000..2d15f4c --- /dev/null +++ b/dist/dts/index.d.ts @@ -0,0 +1,45 @@ +export type HookHandler = (args: T, handler?: HookHandler) => null | void | HookHandler | Promise>; +export interface Hook { + after?: string | string[]; + before?: string | string[]; + handler: HookHandler; + name?: PropertyKey; + plugin?: string; + require?: string | string[]; +} +export interface Plugin { + hooks: { + [name in keyof T]: Hook[] | Hook | HookHandler; + }; + name: PropertyKey; + require?: string[]; +} +interface callArguments { + args: T[K]; + handler: HookHandler; + hooks?: Hook[]; + name: K; +} +interface getArguments { + hooks?: Hook[]; + name: K; + sort?: boolean; +} +type CallFunction = (args: callArguments) => Promise; +type CallSyncFunction = (args: callArguments) => unknown; +type GetFunction = (args: getArguments) => Hook[]; +interface Registry { + call: CallFunction; + call_sync: CallSyncFunction; + get: GetFunction; + register: (userPlugin: Plugin | ((...args: unknown[]) => Plugin)) => Registry; + registered: (name: PropertyKey) => boolean; +} +interface plugangplayArguments { + args?: unknown[]; + chain?: Registry; + parent?: Registry; + plugins?: Plugin[]; +} +declare const plugandplay: = Record>({ args, chain, parent, plugins, }?: plugangplayArguments) => Registry; +export { plugandplay }; diff --git a/dist/esm/index.js b/dist/esm/index.js index 31c1f25..ba28a3a 100644 --- a/dist/esm/index.js +++ b/dist/esm/index.js @@ -1,368 +1,2 @@ -import { is_object_literal, merge, is_object } from 'mixme'; -import toposort from 'toposort'; - -const PlugableError = class PlugableError extends Error { - constructor(code, message, ...contexts) { - if (Array.isArray(message)) { - message = message.filter(function (line) { - return !!line; - }).join(' '); - } - message = `${code}: ${message}`; - super(message); - if (Error.captureStackTrace) { - Error.captureStackTrace(this, PlugableError); - } - this.code = code; - for (let i = 0; i < contexts.length; i++) { - const context = contexts[i]; - for (const key in context) { - if (key === 'code') { - continue; - } - const value = context[key]; - if (value === void 0) { - continue; - } - this[key] = Buffer.isBuffer(value) ? value.toString() : value === null ? value : JSON.parse(JSON.stringify(value)); - } - } - } -}; -var error = (function () { - return new PlugableError(...arguments); -}); - -const array_flatten = function (items, depth = -1) { - const result = []; - for (const item of items) { - if (Array.isArray(item)) { - if (depth === 0) { - result.push(...item); - } - else { - result.push(...array_flatten(item, depth - 1)); - } - } - else { - result.push(item); - } - } - return result; -}; - -const normalize_hook = function (name, hook) { - if (!Array.isArray(hook)) { - hook = [hook]; - } - return hook.map(function (hook) { - if (typeof hook === "function") { - hook = { - handler: hook, - }; - } else if (!is_object(hook)) { - throw error("PLUGINS_HOOK_INVALID_HANDLER", [ - "no hook handler function could be found,", - "a hook must be defined as a function", - "or as an object with an handler property,", - `got ${JSON.stringify(hook)} instead.`, - ]); - } - hook.name = name; - if (typeof hook.after === "string") { - hook.after = [hook.after]; - } - if (typeof hook.before === "string") { - hook.before = [hook.before]; - } - return hook; - }); -}; - -const errors = { - PLUGINS_HOOK_AFTER_INVALID: function ({ name, plugin, after }) { - throw error("PLUGINS_HOOK_AFTER_INVALID", [ - `the hook ${JSON.stringify(name)}`, - plugin ? `in plugin ${JSON.stringify(plugin)}` : void 0, - "references an after dependency", - `in plugin ${JSON.stringify(after)} which does not exists.`, - ]); - }, - PLUGINS_HOOK_BEFORE_INVALID: function ({ name, plugin, before }) { - throw error("PLUGINS_HOOK_BEFORE_INVALID", [ - `the hook ${JSON.stringify(name)}`, - plugin ? `in plugin ${JSON.stringify(plugin)}` : void 0, - "references a before dependency", - `in plugin ${JSON.stringify(before)} which does not exists.`, - ]); - }, - REQUIRED_PLUGIN: function ({ plugin, require }) { - throw error("REQUIRED_PLUGIN", [ - `the plugin ${JSON.stringify(plugin)}`, - "requires a plugin", - `named ${JSON.stringify(require)} which is not unregistered.`, - ]); - }, - PLUGINS_REGISTER_INVALID_REQUIRE: function ({ name, require }) { - throw error("PLUGINS_REGISTER_INVALID_REQUIRE", [ - "the `require` property", - name ? `in plugin ${JSON.stringify(name)}` : void 0, - "must be a string or an array,", - `got ${JSON.stringify(require)}.`, - ]); - }, -}; - -const plugandplay = function ({ args, chain, parent, plugins = [] } = {}) { - // Internal plugin store - const store = []; - // Public API definition - const registry = { - // Register new plugins - register: function (plugin) { - if (typeof plugin === "function") { - plugin = plugin.apply(null, args); - } - if (!is_object_literal(plugin)) { - throw error("PLUGINS_REGISTER_INVALID_ARGUMENT", [ - "a plugin must be an object literal or a function returning an object literal", - "with keys such as `name`, `required` and `hooks`,", - `got ${JSON.stringify(plugin)} instead.`, - ]); - } - if (plugin.hooks == null) { - plugin.hooks = {}; - } - for (const name in plugin.hooks) { - plugin.hooks[name] = normalize_hook(name, plugin.hooks[name]); - } - if (plugin.require == null) { - plugin.require = []; - } else if (typeof plugin.require === "string") { - plugin.require = [plugin.require]; - } - if (!Array.isArray(plugin.require)) { - throw errors.PLUGINS_REGISTER_INVALID_REQUIRE({ - name: plugin.name, - require: plugin.require, - }); - } - store.push(plugin); - return chain || this; - }, - registered: function (name) { - for (const plugin of store) { - if (plugin.name === name) { - return true; - } - } - if (parent != null && parent.registered(name)) { - return true; - } - return false; - }, - get: function ({ name, hooks = [], sort = true }) { - hooks = [ - // Merge hooks provided by the user - ...normalize_hook(name, hooks), - // With hooks present in the store - ...array_flatten( - store - .map(function (plugin) { - // Only filter plugins with the requested hook - if (!plugin.hooks[name]) return; - // Validate plugin requirements - for (const require of plugin.require) { - if (!registry.registered(require)) { - throw errors.REQUIRED_PLUGIN({ - plugin: plugin.name, - require: require, - }); - } - } - return plugin.hooks[name].map(function (hook) { - return merge( - { - plugin: plugin.name, - require: plugin.require, - }, - hook - ); - }); - }) - .filter(function (hook) { - return hook !== undefined; - }) - ), - ...(parent ? parent.get({ name: name, sort: false }) : []), - ]; - if (!sort) { - return hooks; - } - // Topological sort - const index = {}; - for (const hook of hooks) { - if (hook.plugin) index[hook.plugin] = hook; - } - const edges_after = hooks - .map(function (hook) { - if (!hook.after) return; - return hook.after - .map(function (after) { - // This check assume the plugin has the same hooks which is not always the case - if (!index[after]) { - if (registry.registered(after)) { - throw errors.PLUGINS_HOOK_AFTER_INVALID({ - name: name, - plugin: hook.plugin, - after: after, - }); - } else { - return undefined; - } - } - return [index[after], hook]; - }) - .filter(function (hook) { - return hook !== undefined; - }); - }) - .filter(function (hook) { - return hook !== undefined; - }); - const edges_before = hooks - .map(function (hook) { - if (!hook.before) return; - return hook.before - .map(function (before) { - if (!index[before]) { - if (registry.registered(before)) { - throw errors.PLUGINS_HOOK_BEFORE_INVALID({ - name: name, - plugin: hook.plugin, - before: before, - }); - } else { - return undefined; - } - } - return [hook, index[before]]; - }) - .filter(function (hook) { - return hook !== undefined; - }); - }) - .filter(function (hook) { - return hook !== undefined; - }); - const edges = array_flatten([...edges_after, ...edges_before], 0); - return toposort.array(hooks, edges); - }, - // Call a hook against each registered plugin matching the hook name - call: async function ({ args = [], handler, hooks = [], name }) { - if (arguments.length !== 1) { - throw error("PLUGINS_INVALID_ARGUMENTS_NUMBER", [ - "function `call` expect 1 object argument,", - `got ${arguments.length} arguments.`, - ]); - } else if (!is_object_literal(arguments[0])) { - throw error("PLUGINS_INVALID_ARGUMENT_PROPERTIES", [ - "function `call` expect argument to be a literal object", - "with the properties `name`, `args`, `hooks` and `handler`,", - `got ${JSON.stringify(arguments[0])} arguments.`, - ]); - } else if (typeof name !== "string") { - throw error("PLUGINS_INVALID_ARGUMENT_NAME", [ - "function `call` requires a property `name` in its first argument,", - `got ${JSON.stringify(arguments[0])} argument.`, - ]); - } - // Retrieve the name hooks - hooks = this.get({ - hooks: hooks, - name: name, - }); - // Call the hooks - for (const hook of hooks) { - switch (hook.handler.length) { - case 0: - case 1: - await hook.handler.call(this, args); - break; - case 2: - handler = await hook.handler.call(this, args, handler); - if (handler === null) { - return null; - } - break; - default: - throw error("PLUGINS_INVALID_HOOK_HANDLER", [ - "hook handlers must have 0 to 2 arguments", - `got ${hook.handler.length}`, - ]); - } - } - if (handler) { - // Call the final handler - return handler.call(this, args); - } - }, - // Call a hook against each registered plugin matching the hook name - call_sync: function ({ args = [], handler, hooks = [], name }) { - if (arguments.length !== 1) { - throw error("PLUGINS_INVALID_ARGUMENTS_NUMBER", [ - "function `call` expect 1 object argument,", - `got ${arguments.length} arguments.`, - ]); - } else if (!is_object_literal(arguments[0])) { - throw error("PLUGINS_INVALID_ARGUMENT_PROPERTIES", [ - "function `call` expect argument to be a literal object", - "with the properties `name`, `args`, `hooks` and `handler`,", - `got ${JSON.stringify(arguments[0])} arguments.`, - ]); - } else if (typeof name !== "string") { - throw error("PLUGINS_INVALID_ARGUMENT_NAME", [ - "function `call` requires a property `name` in its first argument,", - `got ${JSON.stringify(arguments[0])} argument.`, - ]); - } - // Retrieve the name hooks - hooks = this.get({ - hooks: hooks, - name: name, - }); - // Call the hooks - for (const hook of hooks) { - switch (hook.handler.length) { - case 0: - case 1: - hook.handler.call(this, args); - break; - case 2: - handler = hook.handler.call(this, args, handler); - if (handler === null) { - return null; - } - break; - default: - throw error("PLUGINS_INVALID_HOOK_HANDLER", [ - "hook handlers must have 0 to 2 arguments", - `got ${hook.handler.length}`, - ]); - } - } - if (handler) { - // Call the final handler - return handler.call(this, args); - } - }, - }; - // Register initial plugins - for (const plugin of plugins) { - registry.register(plugin); - } - // return the object - return registry; -}; - -export { plugandplay }; +import{is_object_literal as e,merge as r,is_object as n}from"mixme";import t from"toposort";function i(e,r,n,t){return new(n||(n=Promise))((function(i,o){function a(e){try{s(t.next(e))}catch(e){o(e)}}function u(e){try{s(t.throw(e))}catch(e){o(e)}}function s(e){var r;e.done?i(e.value):(r=e.value,r instanceof n?r:new n((function(e){e(r)}))).then(a,u)}s((t=t.apply(e,r||[])).next())}))}"function"==typeof SuppressedError&&SuppressedError;const o=class e extends Error{constructor(r,n,...t){Array.isArray(n)&&(n=n.filter((function(e){return!!e})).join(" ")),super(n=`${r}: ${n}`),Error.captureStackTrace&&Error.captureStackTrace(this,e),this.code=r;for(let e=0;ee.call(this,r)))},get:function({name:e,hooks:n=[],sort:i=!0}){const o=[...u(e,n),...p.map((function(n){if(n.hooks[e]){if(n.require)for(const e of n.require)if(!d.registered(e))throw c({plugin:n.name,require:e});return n.hooks[e].map((function(e){return r({plugin:n.name,require:n.require},e)}))}})).filter((function(e){return void 0!==e})).flat(),...h?h.get({name:e,sort:!1}):[]];if(!i)return o;const a={};for(const e of o)e&&"plugin"in e&&e.plugin&&(a[e.plugin]=e);const l=[...o.map((function(r){if("after"in r&&Array.isArray(r.after))return r.after.map((function(n){if(a[n]||!("plugin"in r)||!r.plugin)return[a[n],r];if(d.registered(n))throw s({after:n,name:e,plugin:r.plugin})})).filter((function(e){return void 0!==e}))})).filter((function(e){return void 0!==e})),...o.map((function(r){if("before"in r&&Array.isArray(r.before))return r.before.map((function(n){if(a[n]||!("plugin"in r)||!r.plugin)return[r,a[n]];if(d.registered(n))throw f({before:n,name:e,plugin:r.plugin})})).filter((function(e){return void 0!==e}))})).filter((function(e){return void 0!==e}))].flat(1);return t.array(o,l).map((e=>(e&&("require"in e&&delete e.require,"plugin"in e&&delete e.plugin),e)))}};for(const e of g)d.register(e);return d};export{h as plugandplay}; +//# sourceMappingURL=index.js.map diff --git a/dist/esm/index.js.map b/dist/esm/index.js.map new file mode 100644 index 0000000..e26d28b --- /dev/null +++ b/dist/esm/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sources":["../../lib/error.ts","../../lib/index.ts"],"sourcesContent":["const PlugableError = class PlugableError extends Error {\n public code: string;\n [index: string]: unknown; // required to allow adding class props dynamically\n constructor(\n code: string,\n message: string | (string | undefined)[],\n ...contexts: Record[]\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] = Buffer.isBuffer(value)\n ? value.toString()\n : value === null\n ? value\n : JSON.parse(JSON.stringify(value));\n }\n }\n }\n};\nexport default (function (\n ...args: ConstructorParameters\n) {\n return new PlugableError(...args);\n});\n","import { is_object, is_object_literal, merge } from 'mixme';\nimport toposort from 'toposort';\n\nimport error 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 */\nexport type HookHandler = (\n args: T,\n handler?: HookHandler\n) => null | void | HookHandler | Promise>;\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 */\nexport interface Hook {\n /**\n * List of plugin names with hooks of the same name that should be executed after this hook.\n * If a string is provided, it is coerced to an array.\n */\n after?: string | string[];\n\n /**\n * List of plugin names with hooks of the same name that should be executed before this hook.\n * If a string is provided, it is coerced to an array.\n */\n before?: string | string[];\n\n /**\n * The hook handler to be executed.\n */\n handler: HookHandler;\n\n /**\n * Name to identify the hook.\n */\n name?: PropertyKey;\n\n /**\n * Name of the plugin that defines this hook.\n */\n plugin?: string;\n\n /**\n * List of plugin names that this hook requires.\n */\n require?: string | 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 */\nexport interface Plugin {\n /**\n * List of hooks identified by hook names.\n *\n * Each hook can be an array of hooks, a single hook, or a handler function.\n */\n hooks: {\n [name in keyof T]: Hook[] | Hook | HookHandler;\n };\n\n /**\n * Name of the plugin.\n */\n name: PropertyKey;\n\n /**\n * Names of the required plugins.\n *\n * If a required plugin is not registered, an error will be thrown when the plugin is registered.\n */\n require?: string[];\n}\n\ninterface callArguments {\n /**\n * Argument passed to the handler function as well as all hook handlers.\n */\n args: T[K];\n\n /**\n * Function to decorate, receive the value associated with the `args` property.\n */\n handler: HookHandler;\n\n /**\n * List of completary hooks from the end user.\n */\n hooks?: Hook[];\n\n /**\n * Name of the hook to execute.\n */\n name: K;\n}\ninterface getArguments {\n /**\n * List of complementary hooks from the end user.\n */\n hooks?: Hook[];\n\n /**\n * Name of the hook to retrieve.\n */\n name: K;\n\n /**\n * Sort the hooks relatively to each other using the after and before properties.\n */\n sort?: boolean;\n}\n\ntype CallFunction = (\n args: callArguments\n) => Promise;\n\ntype CallSyncFunction = (\n args: callArguments\n) => unknown;\n\ntype GetFunction = (\n args: getArguments\n) => Hook[];\n\ninterface Registry {\n /**\n * Execute a hander function and its associated hooks.\n */\n call: CallFunction;\n /**\n * Execute a hander function and its associated hooks, synchronously.\n */\n call_sync: CallSyncFunction;\n /**\n * Retrieves registered hooks.\n */\n get: GetFunction;\n /**\n * Registers a plugin\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 register: (\n userPlugin: Plugin | ((...args: unknown[]) => Plugin)\n ) => Registry;\n /**\n * Check if a plugin is registered.\n */\n registered: (name: PropertyKey) => boolean;\n}\n\ninterface plugangplayArguments {\n args?: unknown[];\n chain?: Registry;\n parent?: Registry;\n plugins?: Plugin[];\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 (\n name: K,\n hook: Hook | Hook[] | HookHandler\n): Hook[] {\n const hookArray = Array.isArray(hook) ? hook : [hook];\n\n return hookArray.map(function (hook) {\n let normalizedHook = structuredClone(hook);\n\n if (typeof normalizedHook === 'function') {\n normalizedHook = {\n handler: normalizedHook,\n };\n } else if (!is_object(normalizedHook)) {\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(normalizedHook)} instead.`,\n ]);\n }\n\n normalizedHook.name = name;\n\n normalizedHook.after =\n 'after' in normalizedHook && typeof normalizedHook.after === 'string'\n ? [normalizedHook.after]\n : normalizedHook?.after;\n normalizedHook.before =\n 'before' in normalizedHook && typeof normalizedHook.before === 'string'\n ? [normalizedHook.before]\n : normalizedHook?.before;\n\n return normalizedHook;\n });\n};\n\nconst errors = {\n PLUGINS_HOOK_AFTER_INVALID: function ({\n name,\n plugin,\n after,\n }: {\n after: string;\n name: PropertyKey;\n plugin: string;\n }) {\n throw error('PLUGINS_HOOK_AFTER_INVALID', [\n `the hook ${JSON.stringify(name)}`,\n plugin ? `in plugin ${JSON.stringify(plugin)}` : void 0,\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 before: string;\n name: PropertyKey;\n plugin: string;\n }) {\n throw error('PLUGINS_HOOK_BEFORE_INVALID', [\n `the hook ${JSON.stringify(name)}`,\n plugin ? `in plugin ${JSON.stringify(plugin)}` : void 0,\n 'references a before dependency',\n `in plugin ${JSON.stringify(before)} which does not exists.`,\n ]);\n },\n PLUGINS_REGISTER_INVALID_REQUIRE: function ({\n name,\n require,\n }: {\n name: PropertyKey;\n require: string;\n }) {\n throw error('PLUGINS_REGISTER_INVALID_REQUIRE', [\n 'the `require` property',\n name ? `in plugin ${JSON.stringify(name)}` : void 0,\n 'must be a string or an array,',\n `got ${JSON.stringify(require)}.`,\n ]);\n },\n REQUIRED_PLUGIN: function ({\n plugin,\n require,\n }: {\n plugin: PropertyKey;\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 registered.`,\n ]);\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 */\ninterface NormalizedPlugin {\n /**\n * The hooks associated with the plugin, normalized to a standardized format.\n */\n hooks: {\n [name in keyof T]: Hook[];\n };\n\n /**\n * Name of the plugin.\n */\n name: PropertyKey;\n\n /**\n * Names of the required plugins.\n */\n require?: string[];\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 args - The arguments to pass to the registered plugins.\n * @param chain - The chain of plugins to call the hooks on.\n * @param parent - The parent plugin system to call the hooks on.\n * @param plugins - The initial plugins to register.\n * @returns - An object representing the plugin system.\n */\nconst plugandplay = function <\n T extends Record = Record,\n>({\n args = [],\n chain,\n parent,\n plugins = [],\n}: plugangplayArguments = {}): Registry {\n // Internal plugin store\n const store: NormalizedPlugin[] = [];\n\n // Public API definition\n const registry: Registry = {\n /**\n * Registers a plugin with the plugin system.\n *\n * @param plugin - The plugin to register.\n * @returns - The plugin system.\n */\n register: function (plugin) {\n const normalizedPlugin: NormalizedPlugin =\n typeof plugin === 'function'\n ? (plugin(...args) as NormalizedPlugin)\n : (structuredClone(plugin) as NormalizedPlugin);\n\n // Validate the plugin\n if (\n !(\n is_object_literal(normalizedPlugin) &&\n 'name' in normalizedPlugin &&\n typeof normalizedPlugin.name === 'string'\n )\n ) {\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(normalizedPlugin)} instead.`,\n ]);\n }\n\n // Normalize the plugin hooks\n if (normalizedPlugin.hooks == null) {\n normalizedPlugin.hooks = {} as { [name in keyof T]: Hook[] };\n }\n\n for (const name in normalizedPlugin.hooks) {\n normalizedPlugin.hooks[name] = normalize_hook(\n name,\n normalizedPlugin.hooks[name]\n );\n }\n\n // Normalize the plugin requirements\n normalizedPlugin.require = [];\n if ('require' in plugin && plugin.require) {\n if (typeof plugin.require === 'string') {\n normalizedPlugin.require = [plugin.require];\n }\n if (!Array.isArray(plugin.require)) {\n throw errors.PLUGINS_REGISTER_INVALID_REQUIRE({\n name: plugin.name,\n require: plugin.require,\n });\n }\n }\n\n // Add the plugin to the store\n store.push(normalizedPlugin);\n\n // Return the plugin system\n return chain ?? this;\n },\n\n /**\n * Checks 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: function (name) {\n for (const plugin of store) {\n if (plugin.name === name) {\n return true;\n }\n }\n return !!parent?.registered(name);\n },\n\n /**\n * Calls the hooks with the given name on all registered plugins, in the order they were registered.\n *\n * @param args - The arguments to pass to the hooks.\n * @param handler - The handler to pass to the hooks.\n * @param hooks - The hooks to call.\n * @param name - The name of the hooks to call.\n * @returns - A promise that resolves to the result of the final handler.\n */\n call: async function ({ args, handler, hooks, name }) {\n // Validate the arguments\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 hooks\n hooks = this.get({\n hooks: hooks,\n name: name,\n });\n\n // Call the hooks\n let maybeHandler;\n for (const hook of hooks) {\n switch (hook.handler.length) {\n case 0:\n case 1: {\n await hook.handler.call(this, args);\n break;\n }\n case 2: {\n maybeHandler = await hook.handler.call(this, args, handler);\n if (maybeHandler === null) {\n return null;\n }\n break;\n }\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 }\n if (maybeHandler) {\n // Call the final handler\n return maybeHandler.call(this, args);\n }\n },\n\n /**\n * Calls the hooks with the given name on all registered plugins, in the order they were registered.\n *\n * @param args - The arguments to pass to the hooks.\n * @param handler - The handler to pass to the hooks.\n * @param hooks - The hooks to call.\n * @param name - The name of the hooks to call.\n * @returns - The result of the final handler.\n */\n call_sync: function ({ args, handler, hooks, name }) {\n // Validate the arguments\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 hooks\n hooks = this.get({\n hooks: hooks,\n name: name,\n });\n\n // Call the hooks\n let maybeHandler;\n for (const hook of hooks) {\n switch (hook.handler.length) {\n case 0:\n case 1: {\n hook.handler.call(this, args);\n break;\n }\n case 2: {\n maybeHandler = hook.handler.call(this, args, handler);\n if (maybeHandler === null) {\n return null;\n }\n break;\n }\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 }\n if (maybeHandler) {\n return Promise.resolve(maybeHandler).then((handler) =>\n handler.call(this, args)\n );\n }\n },\n\n /**\n * Retrieves the hooks with the given name from all registered plugins, in the order they were registered.\n *\n * @param hooks - The hooks to retrieve.\n * @param name - The name of the hooks to retrieve.\n * @param sort - Whether to sort the hooks topologically.\n * @returns - The retrieved hooks.\n */\n get: function ({\n name,\n hooks = [],\n sort = true,\n }: getArguments) {\n const mergedHooks = [\n ...normalize_hook(name, hooks),\n ...store\n .map(function (plugin) {\n // Only filter plugins with the requested hook\n if (!plugin.hooks[name]) return;\n // Validate plugin requirements\n if (plugin.require) {\n for (const require of plugin.require) {\n if (!registry.registered(require)) {\n throw errors.REQUIRED_PLUGIN({\n plugin: plugin.name,\n require: require,\n });\n }\n }\n }\n\n // Normalize the plugin hooks\n return plugin.hooks[name].map(function (hook) {\n return merge(\n {\n plugin: plugin.name,\n require: plugin.require,\n },\n hook\n ) as Hook;\n });\n })\n .filter(function (hook) {\n return hook !== undefined;\n })\n .flat(),\n ...(parent\n ? parent.get({\n name: name,\n sort: false,\n })\n : []),\n ];\n\n if (!sort) {\n return mergedHooks;\n }\n\n // Topological sort\n const index: Record> = {};\n for (const hook of mergedHooks) {\n if (hook && 'plugin' in hook && hook.plugin) index[hook.plugin] = hook;\n }\n const edges_after = mergedHooks\n .map(function (hook) {\n if (!('after' in hook && Array.isArray(hook.after))) return;\n return hook.after\n .map(function (after) {\n // This check assume the plugin has the same hooks which is not always the case\n if (!index[after] && 'plugin' in hook && hook.plugin) {\n if (registry.registered(after)) {\n throw errors.PLUGINS_HOOK_AFTER_INVALID({\n after: after,\n name: name,\n plugin: hook.plugin,\n });\n } else {\n return;\n }\n }\n return [index[after], hook];\n })\n .filter(function (hook) {\n return hook !== undefined;\n }) as [Hook, Hook][];\n })\n .filter(function (hook) {\n return hook !== undefined;\n });\n const edges_before = mergedHooks\n .map(function (hook) {\n if (!('before' in hook && Array.isArray(hook.before))) return;\n return hook.before\n .map(function (before) {\n if (!index[before] && 'plugin' in hook && hook.plugin) {\n if (registry.registered(before)) {\n throw errors.PLUGINS_HOOK_BEFORE_INVALID({\n before: before,\n name: name,\n plugin: hook.plugin,\n });\n } else {\n return;\n }\n }\n return [hook, index[before]];\n })\n .filter(function (hook) {\n return hook !== undefined;\n }) as [Hook, Hook][];\n })\n .filter(function (hook) {\n return hook !== undefined;\n });\n const edges = [...edges_after, ...edges_before].flat(1);\n return toposort.array(mergedHooks, edges).map((hook) => {\n if (hook) {\n if ('require' in hook) delete hook.require;\n if ('plugin' in hook) delete hook.plugin;\n }\n return hook;\n });\n },\n };\n // Register initial plugins\n for (const plugin of plugins) {\n registry.register(plugin);\n }\n // return the plugin system\n return registry;\n};\n\nexport { plugandplay };\n"],"names":["PlugableError","Error","constructor","code","message","contexts","Array","isArray","filter","line","join","super","captureStackTrace","this","i","length","context","key","value","Buffer","isBuffer","toString","JSON","parse","stringify","error","args","normalize_hook","name","hook","map","normalizedHook","structuredClone","handler","is_object","after","before","errors","plugin","require","plugandplay","chain","parent","plugins","store","registry","register","normalizedPlugin","is_object_literal","hooks","push","registered","call","_a","__awaiter","arguments","arguments_1","maybeHandler","get","call_sync","Promise","resolve","then","sort","mergedHooks","merge","undefined","flat","index","edges","toposort","array"],"mappings":"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"} \ No newline at end of file diff --git a/dist/index.d.ts b/dist/index.d.ts deleted file mode 100644 index 580e0b5..0000000 --- a/dist/index.d.ts +++ /dev/null @@ -1,43 +0,0 @@ -type HookHandler = (args: T, handler?: HookHandler) => null | void | HookHandler | Promise>; -interface Hook { - after?: string[]; - before?: string[]; - handler: HookHandler; - name?: string; - plugin?: string; - require?: string[]; -} -interface Plugin { - hooks: { - [name: string]: Hook[] | Hook | HookHandler; - }; - name: string; - require?: string[]; -} -interface callArgs { - args: T; - handler: HookHandler; - hooks?: Hook[]; - name: string; -} -interface getArgs { - hooks?: Hook[]; - name: string; - sort?: boolean; -} -interface Registry { - call: (args: callArgs) => Promise; - call_sync: (args: callArgs) => unknown; - get: (args: getArgs) => Hook[]; - register: (userPlugin: Plugin | ((args?: T) => Plugin)) => Registry; - registered: (name: string) => boolean; -} -type plugangplayArgs = { - args?: T; - chain?: Registry; - parent?: Registry; - plugins?: Plugin[]; -}; -declare const plugandplay: ({ args, chain, parent, plugins, }?: plugangplayArgs) => Registry; - -export { Hook, Plugin, plugandplay };