UNPKG

@daiso-tech/core

Version:

The library offers flexible, framework-agnostic solutions for modern web applications, built on adaptable components that integrate seamlessly with popular frameworks like Next Js.

147 lines 5.29 kB
/** * @module Utilities */ import { callInvokable, getInvokableName, resolveInvokable, resolveOneOrMore, } from "../../../utilities/_module-exports.js"; /** * The `AsyncHooks` class provides a convenient way to change and inspect arguments and return value of both async and sync functions. * For example `AsyncHooks` class can be used to log function arguments and return values. Note this class will always return promise and is immutable. * * Middlewares apply left to right: each wraps the next, with the leftmost being the outermost layer and the rightmost wrapping the original function. * * IMPORT_PATH: `"@daiso-tech/core/utilities"` * @group Hooks */ export class AsyncHooks { invokable; middlewares; settings; static defaultAbortSignalBinder() { return { forwardSignal: (args) => args, getSignal: () => new AbortController().signal, }; } static resolveSignalBinder(signalBinder, args) { const outerSignal = callInvokable(signalBinder.getSignal, args) ?? new AbortController().signal; const abortController = new AbortController(); const abort = (reason) => { abortController.abort(reason); }; const mergedSignal = AbortSignal.any([ outerSignal, abortController.signal, ]); callInvokable(signalBinder.forwardSignal, args, mergedSignal); return { abort, changedArgs: args, signal: mergedSignal, }; } static init(invokable, middlewares, { name = getInvokableName(invokable), signalBinder = AsyncHooks.defaultAbortSignalBinder(), context = {}, }) { let func = resolveInvokable(invokable); for (const hook of resolveOneOrMore(middlewares) .map(resolveInvokable) .reverse()) { const prevFunc = func; const next = async (...arguments_) => await prevFunc(...arguments_); func = async (...arguments_) => { const resolvedSignalBinder = AsyncHooks.resolveSignalBinder(signalBinder, arguments_); return await hook(resolvedSignalBinder.changedArgs, next, { name, abort: (error) => { resolvedSignalBinder.abort(error); }, signal: resolvedSignalBinder.signal, context, }); }; } return func; } func; /** * @example * ```ts * import { AsyncHooks, type AsyncMiddlewareFn } from "@daiso-tech/core/utilities"; * * function log<TParameters extends unknown[], TReturn>(): AsyncMiddlewareFn<TParameters, TReturn> { * return async (args, next, { name: funcName }) => { * console.log("FUNCTION_NAME:", funcName); * console.log("ARGUMENTS:", args); * const value = await next(...args); * console.log("RETURN:", value); * return value; * } * } * * function time<TParameters extends unknown[], TReturn>(): AsyncMiddlewareFn<TParameters, TReturn> { * return async (args, next) => { * const start = performance.now(); * const value = await next(...args); * const end = performance.now(); * const time = end - start; * console.log("TIME:", `${String(time)}ms`); * return value; * } * } * * function add(a: number, b: number): number { * return a + b; * } * * const enhancedAdd = new AsyncHooks(add, [ * log(), * time() * ], { * // You can provide addtional data to be used the middleware. * context: {}, * }); * * // Will log the function name, arguments and return value. * // Will also log the execution time. * const result = await enhancedAdd.invoke(1, 2); * * // Will be 3. * console.log(result); * ``` */ constructor(invokable, middlewares, settings = {}) { this.invokable = invokable; this.middlewares = middlewares; this.settings = settings; this.func = AsyncHooks.init(invokable, middlewares, this.settings); } /** * The `pipe` method returns a new `AsyncHooks` instance with the additional `middlewares` applied. */ pipe(middlewares) { return new AsyncHooks(this.invokable, [ ...resolveOneOrMore(this.middlewares), ...resolveOneOrMore(middlewares), ], this.settings); } /** * The `pipeWhen` method conditionally applies additional `middlewares`, returning a new `AsyncHooks` instance only if the specified condition is met. */ pipeWhen(condition, middlewares) { if (condition) { return this.pipe(middlewares); } return this; } /** * The `toFunc` will return the function with all middlewares applied. */ toFunc() { return (...args) => this.invoke(...args); } /** * The `invoke` method executes the constructor's input function, applying all middlewares. */ async invoke(...arguments_) { return await this.func(...arguments_); } } //# sourceMappingURL=async-hooks.js.map