@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
JavaScript
/**
* @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