// allow decorating function promise function call // before/after execution, or in case of error type DecoratedSlot = 'before' | 'success' | 'error' type DecoratedFunction = { (...args: any): any before?: (...args: any) => any success?: (...args: any) => any error?: (...args: any) => any original?: (...args: any) => any } export function decorate< Target extends Object, Method extends keyof Target, Slot extends DecoratedSlot, Func = Target[Method] extends (...args: any) => any ? Target[Method] : any, Param = Func extends (...args: any) => any ? Parameters : never, Return = Func extends (...args: any) => any ? Awaited> : never, Callback = Slot extends 'before' ? (this: Target, ...args: Param[]) => any : Slot extends 'success' ? (this: Target, arg: Return) => any : (this: Target, error: Error) => any >(target: Target, type: Slot, method: Method, callback: Callback) { const original = target[method] as unknown as DecoratedFunction if (!original.original) { const decorated: DecoratedFunction = async function (this: Target, ...args: Param[]) { try { await decorated.before?.apply(this, args) const result = await original.apply(this, args) await decorated.success?.call(this, result) return result } catch (e) { if (decorated.error) { await decorated.error.call(this, e) } else { throw e } } } decorated.original = original Object.defineProperty(target, method, { value: decorated, writable: false, enumerable: false }) } const decorated = target[method] as unknown as DecoratedFunction if (typeof callback == 'function') decorated[type] = callback as unknown as (...args: any) => any }