import { assertMethodDecorator, isDecoratorCall, } from "../common/decorators.js"; import type { AsyncMethod } from "../common/types.js"; import { isWeakMapKey, resolveCallable, } from "../common/utils.js"; type DelegateDecorator = ( value: AsyncMethod, context: ClassMethodDecoratorContext>, ) => AsyncMethod; export function createDelegatedMethod( originalMethod: AsyncMethod, keyResolver?: ((...args: Args) => string) | keyof This, ): AsyncMethod { const delegatedByInstance = new WeakMap>>(); const fallbackDelegated = new Map>(); const getPendingMap = (instance: This): Map> => { if (!isWeakMapKey(instance)) { return fallbackDelegated; } const instanceKey = instance as object; const existingMap = delegatedByInstance.get(instanceKey); if (existingMap !== undefined) { return existingMap; } const pendingMap = new Map>(); delegatedByInstance.set(instanceKey, pendingMap); return pendingMap; }; return function(this: This, ...args: Args): Promise { const key = keyResolver === undefined ? JSON.stringify(args) : resolveCallable(this, keyResolver)(...args); const pendingMap = getPendingMap(this); if (!pendingMap.has(key)) { pendingMap.set( key, originalMethod.apply(this, args).finally(() => { pendingMap.delete(key); }), ); } return pendingMap.get(key) as Promise; }; } export function delegate( value: AsyncMethod, context: ClassMethodDecoratorContext>, ): AsyncMethod; export function delegate( keyResolver?: ((...args: Args) => string) | keyof This, ): DelegateDecorator; export function delegate(inputOrValue?: unknown, context?: unknown): unknown { const decorate = ( value: AsyncMethod, decoratorContext: ClassMethodDecoratorContext>, keyResolver?: ((...args: Args) => string) | keyof This, ): AsyncMethod => { assertMethodDecorator("delegate", value, decoratorContext); return createDelegatedMethod(value, keyResolver); }; if (arguments.length === 2 && isDecoratorCall(context)) { return decorate( inputOrValue as AsyncMethod, context as ClassMethodDecoratorContext>, ); } const keyResolver = inputOrValue as ((...args: unknown[]) => string) | PropertyKey | undefined; return ( value: AsyncMethod, decoratorContext: ClassMethodDecoratorContext>, ): AsyncMethod => { return decorate(value, decoratorContext, keyResolver as ((...args: Args) => string) | keyof This | undefined); }; }