/** * @module lib/Mixins * Allows for defining and applying mixin functions to allow multiple sources to modify a value in a controlled way. */ import { type Prettify } from "@sv443-network/coreutils"; /** Full mixin object (either sync or async), as it is stored in the instance's mixin array. */ export type MixinObj = Prettify | MixinObjAsync>; /** Synchronous mixin object, as it is stored in the instance's mixin array. */ export type MixinObjSync = Prettify<{ /** The mixin function */ fn: (arg: TArg, ctx?: TCtx) => TArg; } & MixinObjBase>; /** Asynchronous mixin object, as it is stored in the instance's mixin array. */ export type MixinObjAsync = Prettify<{ /** The mixin function */ fn: (arg: TArg, ctx?: TCtx) => TArg | Promise; } & MixinObjBase>; /** Base type for mixin objects */ type MixinObjBase = Prettify<{ /** The public identifier key (purpose) of the mixin */ key: string; } & MixinConfig>; /** Configuration object for a mixin function */ export type MixinConfig = { /** The higher, the earlier the mixin will be applied. Supports floating-point and negative numbers too. 0 by default. */ priority: number; /** If true, no further mixins will be applied after this one. */ stopPropagation: boolean; /** If set, the mixin will only be applied if the given signal is not aborted. */ signal?: AbortSignal; }; /** Configuration object for the {@linkcode Mixins} class */ export type MixinsConstructorConfig = { /** * If true, when no priority is specified, an auto-incrementing integer priority will be used, starting at `defaultPriority` or 0 (unique per mixin key). Defaults to false. * This means that the first mixin added will have the lowest priority of `defaultPriority`, and the last one will have the highest priority and be applied first. * If a priority value is already in use, it will be incremented until a unique value is found. * A finer level of interjection can be achieved by manually setting the priority to a floating point number, while the auto-incrementing priority will always be an integer. */ autoIncrementPriority: boolean; /** The default priority for mixins that do not specify one. Defaults to 0. */ defaultPriority: number; /** The default stopPropagation value for mixins that do not specify one. Defaults to false. */ defaultStopPropagation: boolean; /** The default AbortSignal for mixins that do not specify one. Defaults to undefined. */ defaultSignal?: AbortSignal; }; /** * The mixin class allows for defining and applying mixin functions to allow multiple sources to modify values in a controlled way. * Mixins are identified via their string key and can be added with {@linkcode add()} * When calling {@linkcode resolve()}, all registered mixin functions with the same key will be applied to the input value in the order of their priority, or alternatively the order they were added. * If a mixin function has its `stopPropagation` flag set to true when being added, no further mixin functions will be applied after it. * @template TMixinMap A map of mixin keys to their respective function signatures. The first argument of the function is the input value, the second argument is an optional context object. If it is defined here, it must be passed as the third argument in {@linkcode resolve()}. * @example ```ts * const ac = new AbortController(); * const { abort: removeAllMixins } = ac; * * const mathMixins = new Mixins<{ * // supports sync and async functions: * foo: (val: number, ctx: { baz: string }) => Promise; * // first argument and return value have to be of the same type: * bar: (val: bigint) => bigint; * // ... * }>({ * autoIncrementPriority: true, * defaultPriority: 0, * defaultSignal: ac.signal, * }); * * // will be applied last due to base priority of 0: * mathMixins.add("foo", (val, ctx) => Promise.resolve(val * 2 + ctx.baz.length)); * // will be applied second due to manually set priority of 1: * mathMixins.add("foo", (val) => val + 1, { priority: 1 }); * // will be applied first, even though the above ones were called first, because of the auto-incrementing priority of 2: * mathMixins.add("foo", (val) => val / 2); * * const result = await mathMixins.resolve("foo", 10, { baz: "this has a length of 23" }); * // order of application: * // input value: 10 * // 10 / 2 = 5 * // 5 + 1 = 6 * // 6 * 2 + 23 = 35 * // result = 35 * * // removes all mixins added without a `signal` property: * removeAllMixins(); * ``` */ export declare class Mixins any>, TMixinKey extends Extract = Extract> { /** List of all registered mixins */ protected mixins: MixinObj[]; /** Default configuration object for mixins */ protected readonly defaultMixinCfg: MixinConfig; /** Whether the priorities should auto-increment if not specified */ protected readonly autoIncPrioEnabled: boolean; /** The current auto-increment priority counter */ protected autoIncPrioCounter: Map; /** * Creates a new Mixins instance. * @param config Configuration object to customize the behavior. */ constructor(config?: Partial); /** * Adds a mixin function to the given {@linkcode mixinKey}. * If no priority is specified, it will be calculated via the protected method {@linkcode calcPriority()} based on the constructor configuration, or fall back to the default priority. * @param mixinKey The key to identify the mixin function. * @param mixinFn The function to be called to apply the mixin. The first argument is the input value, the second argument is the context object (if any). * @param config Configuration object to customize the mixin behavior, or just the priority if a number is passed. * @returns Returns a cleanup function, to be called when this mixin is no longer needed. */ add[0], TCtx extends Parameters[1]>(mixinKey: TKey, mixinFn: (arg: TArg, ...ctx: TCtx extends undefined ? [void] : [TCtx]) => ReturnType extends Promise ? ReturnType | Awaited> : ReturnType, config?: Partial | number): () => void; /** Returns a list of all added mixins with their keys and configuration objects, but not their functions */ list(): ({ key: string; } & MixinConfig)[]; /** * Applies all mixins with the given key to the input value, respecting the priority and stopPropagation settings. * If additional context is set in the MixinMap, it will need to be passed as the third argument. * @returns The modified value after all mixins have been applied. The method will return a Promise if at least one of the mixins is async. If all mixins are indicated to be synchronous in TS, but at least one of them turns out to be asynchronous, the return type will be a Promise. With `await`, this will not make a difference, but `.then().catch()` could be affected. */ resolve[0], TCtx extends Parameters[1]>(mixinKey: TKey, inputValue: TArg, ...inputCtx: TCtx extends undefined ? [void] : [TCtx]): ReturnType extends Promise ? ReturnType : ReturnType; /** Calculates the priority for a mixin based on the given configuration and the current auto-increment state of the instance */ protected calcPriority(mixinKey: TMixinKey, config: Partial): number | undefined; /** * Removes all mixins with the given key. * Note: this method is protected to avoid third-party code from removing mixins. If needed, you can extend the Mixins class and expose this method publicly. */ protected removeAll(mixinKey: TMixinKey): void; } export {};