UNPKG

hookar

Version:

small hook utility with support for hooks and pipelines

158 lines (143 loc) 3.56 kB
/** * @template H * @callback AddHookToCollection * @param {H} hook * @returns {function} unhook **/ import { runOnce } from "./util.mjs"; /** * @template H * @typedef {AddHookToCollection<H> & HooksCollectionProps<H>} HooksCollection */ /** * @template H hook * @typedef {Object} HooksCollectionProps * @prop {H} run * @prop {H} runOnce * @prop {HookCb<H>} next * @prop {H[]} hooks */ /** * @template V * @callback Runner * @param {HookCb<V>[]} value * @param {...any} rest */ /** * @template T * @callback HookCb * @param {T} value * @param {...any} rest */ /** * creates a hook collection * @template T * @param {Runner<T>} runner * @return {HooksCollection<HookCb<T>>} * @example * const hooksCollection = createHook() * const unhookFn = hooksCollection(x => console.log('hello', x)) * const unhookFn2 = hooksCollection(x => console.log('goodbye', x)) * * // call hooks * hooksCollection.hooks.forEach(hook => hook('Jake')) * // logs "hello Jake" and "goodbye Jake" * * // unregister * unhookFn() * unhookFn2() */ const createHooksCollection = (runner) => { /** @type {HookCb<T>[]} */ const hooks = []; /** *@type {HooksCollection<HookCb<T>>} */ const hooksCollection = (hook) => { hooks.push(hook); return () => hooks.splice(hooks.indexOf(hook), 1); }; hooksCollection.hooks = hooks; hooksCollection.run = runner(hooks); hooksCollection.runOnce = runOnce(runner(hooks)); hooksCollection.next = (hook) => { const unhook = hooksCollection((...payload) => { hook(...payload); unhook(); }); }; return hooksCollection; }; /** * @template P * @typedef {HooksCollection<(pri: P, ...rest)=>P>} CollectionSync */ /** * @template P * @typedef {HooksCollection<(pri: P, ...rest)=>P|Promise<P>>} CollectionAsync */ /** * @template P * @typedef {HooksCollection<((pri: P, ...rest)=>void)>} CollectionSyncVoid */ /** * @template P * @typedef {HooksCollection<(pri: P, ...rest)=>void|Promise<void>>} CollectionAsyncVoid */ /** * @template T * @param {T=} type * @returns {HooksCollection<T>} */ export const createPipelineCollection = (type) => // @ts-ignore createHooksCollection( (hooks) => (value, ...rest) => hooks.reduce( (pipedValue, hook) => pipedValue?.then ? pipedValue.then((r) => hook(r, ...rest)) : hook(pipedValue, ...rest), value ) ); /** * @template T * @param {T=} type * @returns {CollectionSyncVoid<T>|CollectionAsyncVoid<T>} */ export const createSequenceHooksCollection = (type) => createHooksCollection( (hooks) => (value, ...rest) => hooks.reduce( (last, hook) => (last?.then ? last.then((_) => hook(value, ...rest)) : hook(value, ...rest)), value ) ); /** * @template T * @param {T=} type * @returns {CollectionSyncVoid<T>|CollectionAsyncVoid<T>} */ export const createParallelHooksCollection = (type) => createHooksCollection( (hooks) => (value, ...rest) => Promise.all(hooks.map((hook) => hook(value, ...rest))).then((r) => value) ); /** * @template T * @param {T=} type * @returns {CollectionSync<T>|CollectionAsync<T>} */ export const createGuardsCollection = (type) => // @ts-ignore createHooksCollection( (hooks) => (value, ...rest) => hooks.reduce( (pipedValue, hook) => pipedValue?.then ? pipedValue.then((r) => r && hook(value, ...rest)) : pipedValue && hook(value, ...rest), value || true ) );