UNPKG

surrogate

Version:

Object method hooks made easy

699 lines (697 loc) 26.3 kB
type SurrogateContexts = 'instance' | 'surrogate'; type MethodWrappers = 'sync' | 'async'; type Contexts = SurrogateContexts | typeof Object | typeof Function | Object; /** * @description Function run when an Error occurs and will determine if Surrogate should silence any error output */ type ShouldSilence = (error: Error) => boolean; interface RecoverableProvider<T extends object, Arguments extends Array<any> = any, Result = any> extends ProviderParameters<T, Arguments, Result> { shouldRecover: boolean; } export interface RunOnErrorParameters<T extends object, Arguments extends Array<any> = any, Result = any> extends RecoverableProvider<T, Arguments, Result> { error: Error; recoverFromError(recover: boolean): void; } export interface RunOnBailParameters<T extends object, Arguments extends Array<any> = any, Result = any> extends RecoverableProvider<T, Arguments, Result> { bailWith(value: any): void; recoverFromBail(recover: boolean): void; } export type RunOnBail = <T extends object, Arguments extends Array<any> = any, Result = any>(bailParameters: RunOnBailParameters<T, Arguments, Result>) => any; export type RunOnError = <T extends object, Arguments extends Array<any> = any, Result = any>(errorParameters: RunOnErrorParameters<T, Arguments, Result>) => any; /** * Surrogate Options * * @export * @interface SurrogateOptions */ export interface SurrogateOptions extends SurrogateGlobalOptions { /** * @description Instructs Surrogate to operate as a Singleton * @default true * @type {boolean} * @memberof SurrogateOptions */ useSingleton?: boolean; /** * @description Will force Surrogate to run specified methods with or without hooks. * @default false * @Type {boolean|string|string[]} * @memberof SurrogateOptions */ maintainContext?: boolean | string | string[]; } interface SurrogateGlobalOptions { /** * @description Pass Next object to the Surrogate Handler * * @default true */ useNext?: boolean; /** * @description Specify that nothing should be passed to handler * * @default false */ noArgs?: boolean; /** * @description Should errors be thrown or ignored when passed via next()? * * @default false */ ignoreErrors?: boolean; /** * @description Specifies the context in which to call a handler. Method defined contexts take precedence. * * @options * - instance * - surrogate * - user supplied context object * * @default instance * @type {Contexts} * @memberof SurrogateGlobalOptions */ useContext?: Contexts; /** * @description Function to be called when an error is thrown. Will run even if errors are ignored. * @type {RunOnError|RunOnError[]} * @memberof SurrogateGlobalOptions */ runOnError?: RunOnError | RunOnError[]; /** * @description Function to be called when bailing out of a handler. * @type {RunOnBail|RunOnBail[]} * @memberof SurrogateGlobalOptions */ runOnBail?: RunOnBail | RunOnBail[]; /** * @description Provide content for Surrogate to pass to handlers and conditionals. * @type {any} * @memberof SurrogateGlobalOptions */ provide?: any; /** * @description Ignore error output * @note If supplying a function a result of true will silence errors and false will allow output * @default false * @type {(boolean | ShouldSilence)} * @memberof SurrogateGlobalOptions */ silenceErrors?: boolean | ShouldSilence; } interface GlobalHandlerOptions<T extends object> { handler?: SurrogateHandlerOptions<T>; global?: SurrogateOptions; } type CombinedOptions<T extends object> = Required<SurrogateHandlerOptions<T>> & Required<Omit<SurrogateOptions, 'useSingleton'>>; export enum SurrogateContext { Instance = "instance", Surrogate = "surrogate" } export enum MethodWrapper { Sync = "sync", Async = "async" } interface OptionsHandler<T extends object> extends CombinedOptions<T> { } declare class OptionsHandler<T extends object> { protected readonly combinedOptions: GlobalHandlerOptions<T>; constructor(combinedOptions?: GlobalHandlerOptions<T>); } declare const METHOD = "method"; declare const EMPTY = "empty"; export const BOTH = "both"; export const POST = "post"; export const PRE = "pre"; type WhichMethod = Which | typeof METHOD | typeof EMPTY; type Which = typeof PRE | typeof POST; type Whichever = Which | typeof BOTH; export enum HookType { PRE = "pre", POST = "post", BOTH = "both" } declare class EventManager<T extends object = any> implements SurrogateEventManager<T> { readonly globalOptions: SurrogateOptions; constructor(globalOptions?: SurrogateOptions); getEventMap(): EventMap<T>; getEventHandlers(event: string): WhichContainers<T>; getPreEventHandlers(event: string): SurrogateHandlerContainer<T>[]; getPostEventHandlers(event: string): SurrogateHandlerContainer<T>[]; eventIsHandled(event: string): boolean; registerHook(event: string, type: Which, handler: SurrogateHandlers<T>, options: SurrogateHandlerOptions<T>): EventManager<T>; registerPreHook(event: string, handler: SurrogateHandlers<T>, options?: SurrogateHandlerOptions<T>): EventManager<T>; registerPostHook(event: string, handler: SurrogateHandlers<T>, options?: SurrogateHandlerOptions<T>): EventManager<T>; deregisterHooks(): EventManager<T>; deregisterHooksFor(event: string): EventManager<T>; deregisterPreHook(event: string, handler: SurrogateHandlers<T>): EventManager<T>; deregisterPostHook(event: string, handler: SurrogateHandlers<T>): EventManager<T>; deregisterPostHooks(event: string): EventManager<T>; deregisterPreHooks(event: string): EventManager<T>; } declare class Context<T extends object> { readonly target: T; readonly receiver: Surrogate<T>; readonly event: string; readonly original: Function; readonly originalArguments: any[]; constructor(target: T, receiver: Surrogate<T>, event: string, original: Function, originalArguments: any[]); determineContext(options: RequiredHandlerOptions<T>): Contexts; useInstance(context: Contexts): boolean; useSurrogate(context: Contexts): boolean; } declare class SurrogateProxy<T extends object> implements ProxyHandler<T> { constructor(target: T, { useSingleton, ...globalOptions }?: SurrogateOptions); get<K extends keyof T>(target: T, event: string, receiver: Surrogate<T>): T[K] | EventManager<T>; getEventManager(target: T): EventManager<T>; static wrap<T extends object>(object: T, options?: SurrogateOptions): Surrogate<T>; static hasTarget<T extends object>(target: T): target is Surrogate<T>; bindHandler(event: string, target: T, receiver: Surrogate<T>): Function; surrogateHandler(context: Context<T>): any; dispose(target: T): T; } interface TimeTracking extends TimeTracker { setHookStart(): void; setHookEnd(): void; } interface TimeTracker { getDurationSinceLastRun(): number; getLastRunDuration(): number; getHookStartTime(): number; getTotalDuration(): number; getStartTime(): number; } interface SurrogateHandlerRunner { run(args: any[], error?: Error): void; } export interface HandlerContainer<T extends object> { type: WhichMethod; options: OptionsHandler<T>; getHandlerRunner(node: NextNode<T>): SurrogateHandlerRunner; getHandler(context: Context<T>): SurrogateHandler<T> | Function; } declare abstract class BaseContainer<T extends object> implements HandlerContainer<T> { protected readonly handler: SurrogateHandlerTypes<T> | Function; readonly type: WhichMethod; readonly options: OptionsHandler<T>; constructor(handler: SurrogateHandlerTypes<T> | Function, type: WhichMethod, options?: OptionsHandler<T>); getHandler(context: Context<T>): Function | SurrogateHandler<T>; getHandlerRunner(node: NextNode<T>): SurrogateHandlerRunner; } declare class SurrogateHandlerContainer<T extends object> extends BaseContainer<T> { readonly handler: SurrogateHandlerTypes<T>; readonly type: Which; constructor(handler: SurrogateHandlerTypes<T>, type: Which, options: OptionsHandler<T>); } /** * @description Object containing PRE and POST handlers for a method * * @export * @interface WhichContainers * @template T */ interface WhichContainers<T extends object> { [HookType.PRE]: SurrogateHandlerContainer<T>[]; [HookType.POST]: SurrogateHandlerContainer<T>[]; } export interface NextOptions extends BailOptions { bail?: boolean; } interface BailOptions { error?: Error | false | null | undefined; bailWith?: any; replace?: any; using?: any[]; } interface ContextController<T extends object> { start(): any; complete(): void; returnValue: any; currentArgs: any[]; context: Context<T>; correlationId: string; bail(bailWith: any): any; timeTracker: TimeTracking; addNext(next: NextNode<T>): void; setNext(next: NextNode<T>): void; runOriginal(node: NextNode<T>): void; updateLatestArgs(updatedArgs: any): void; handleError(node: NextNode<T>, error?: Error): never | void; setupPipeline(proxy: SurrogateProxy<T>, typeContainers: WhichContainers<T>): ContextController<T>; } export interface INext { skip(skipAmount?: number): void; bail(bailOptions?: BailOptions): void; next(nextOptions?: NextOptions): void; skipWith(skipAmount?: number, ...args: any[]): void; } interface NextNode<T extends object> extends INext { handleNext(options?: NextOptions): void; shouldRun(using: any[]): boolean; addNext(next: NextNode<T>): void; controller: ContextController<T>; instance: SurrogateUnwrapped<T>; container: HandlerContainer<T>; proxy: SurrogateProxy<T>; nextNode: NextNode<T>; prevNode: NextNode<T>; useContext: Contexts; context: Context<T>; hookType: string; didError: Error; } /** * * @deprecated Use NextParameters * @export * @interface NextHandler * @extends {ProviderParameters<T>} * @template T */ export interface NextHandler<T extends object> extends NextParameters<T> { } /** * * @export * @interface NextParameters * @extends {ProviderParameters<T>} * @template T */ export interface NextParameters<T extends object, Arguments extends Array<any> = any, Result = any> extends ProviderParameters<T, Arguments, Result> { surrogate: Surrogate<T>; next: INext; } type RequiredHandlerOptions<T extends object, Arguments extends Array<any> = any, Result = any> = Required<SurrogateHandlerOptions<T, Arguments, Result>>; export interface SurrogateHandlerOptions<T extends object, Arguments extends Array<any> = any, Result = any> extends SurrogateGlobalOptions { /** * @description Specifies the method context wrapper to utilize * * @options * - sync * - async * * @default sync */ wrapper?: MethodWrappers; /** * @description Conditions to determine if a handler should be executed */ runConditions?: RunCondition<T, Arguments, Result> | RunCondition<T, Arguments, Result>[]; /** * @description - Specify the priority of the handler. Higher priority handlers are run first. * * @default 0 */ priority?: number; } interface EventMap<T extends object> { [event: string]: WhichContainers<T>; } /** * @description Class that manages PRE and POST method handlers for Surrogate wrapped instances. * * @export * @interface SurrogateEventManager * @template T */ export interface SurrogateEventManager<T extends object> { /** * @description Removes all handlers for all methods * * @returns {SurrogateEventManager<T>} * @memberof SurrogateEventManager */ deregisterHooks(): SurrogateEventManager<T>; /** * @description Removes all PRE handlers for the provided method * * @param {keyof T | string} event * @returns {SurrogateEventManager<T>} * @memberof SurrogateEventManager */ deregisterPreHooks(event: keyof T | string): SurrogateEventManager<T>; /** * @description Removes all POST handlers for the provided method * * @param {keyof T | string} event * @returns {SurrogateEventManager<T>} * @memberof SurrogateEventManager */ deregisterPostHooks(event: keyof T | string): SurrogateEventManager<T>; /** * @description Removes a specific PRE handler for the provided method * * @param {keyof T | string} event * @param {SurrogateHandlers<T>} handler * @returns {SurrogateEventManager<T>} * @memberof SurrogateEventManager */ deregisterPreHook(event: keyof T | string, handler: SurrogateHandlers<T>): SurrogateEventManager<T>; /** * @description Removes a specific POST handler for the provided method * * @param {keyof T | string} event * @param {SurrogateHandlers<T>} handler * @returns {SurrogateEventManager<T>} * @memberof SurrogateEventManager */ deregisterPostHook(event: keyof T | string, handler: SurrogateHandlers<T>): SurrogateEventManager<T>; /** * @description Retrieves all handlers in an event map * * @returns {EventMap<T>} * @memberof SurrogateEventManager */ getEventMap(): EventMap<T>; /** * @description Retrieves all handlers for the provided method * * @param {keyof T | string} event * @returns {WhichContainers<T>} * @memberof SurrogateEventManager */ getEventHandlers(event: keyof T | string): WhichContainers<T>; /** * @description Retrieves all PRE handlers for the provided method * * @param {keyof T | string} event * @returns {SurrogateHandlerContainer<T>} * @memberof SurrogateEventManager */ getPreEventHandlers(event: keyof T | string): SurrogateHandlerContainer<T>[]; /** * @description Retrieves all POST handlers for the provided method * * @param {keyof T | string} event * @returns {SurrogateHandlerContainer<T>} * @memberof SurrogateEventManager */ getPostEventHandlers(event: keyof T | string): SurrogateHandlerContainer<T>[]; /** * @description Registers a PRE handler or array of handlers for the provided method * * @param {keyof T | string} event * @param {(SurrogateHandlers<T>)} handler * @param {SurrogateHandlerOptions<T>} [options] * @returns {SurrogateEventManager<T>} * @memberof SurrogateEventManager */ registerPreHook(event: keyof T | string, handler: SurrogateHandlers<T>, options?: SurrogateHandlerOptions<T>): SurrogateEventManager<T>; /** * @description Registers a POST handler or array of handlers for the provided method * * @param {keyof T | string} event * @param {(SurrogateHandlers<T>)} handler * @param {SurrogateHandlerOptions<T>} [options] * @returns {SurrogateEventManager<T>} * @memberof SurrogateEventManager */ registerPostHook(event: keyof T | string, handler: SurrogateHandlers<T>, options?: SurrogateHandlerOptions<T>): SurrogateEventManager<T>; /** * @description Registers a PRE or POST handler for the provided method * * @param {keyof T | string} event * @param {Which} type * @param {(SurrogateHandlers<T>)} handler * @param {SurrogateHandlerOptions<T>} options * @returns {SurrogateEventManager<T>} * @memberof SurrogateEventManager */ registerHook(event: keyof T | string, type: Which, handler: SurrogateHandlers<T>, options: SurrogateHandlerOptions<T>): SurrogateEventManager<T>; } export interface SurrogateMethods<T extends object> { disposeSurrogate(): T; getSurrogate(): SurrogateEventManager<T>; bypassSurrogate(): SurrogateUnwrapped<T>; } export type Surrogate<T extends object> = SurrogateMethods<T> & T; export type SurrogateUnwrapped<T extends object> = Omit<Surrogate<T>, keyof SurrogateMethods<T>>; /** * @description Surrogate handler types */ export type SurrogateHandler<T extends object, Arguments extends Array<any> = any, Result = any> = (nextParameters: NextParameters<T, Arguments, Result>) => unknown; export type SurrogateHandlerTypes<T extends object, Arguments extends Array<any> = any, Result = any> = SurrogateHandler<T, Arguments, Result> | keyof T | string; export type SurrogateHandlers<T extends object, Arguments extends Array<any> = any, Result = any> = SurrogateHandlerTypes<T, Arguments, Result> | SurrogateHandlerTypes<T>[]; interface ProviderParameters<T extends object, Arguments, Result> { /** * @description The current, unwrapped instance * * @note Being unwrapped it does not have access to Surrogate methods and no hooks will run * * @type {SurrogateUnwrapped<T>} * @memberof ProviderParameters */ instance: SurrogateUnwrapped<T>; /** * @description The current hook pipeline time tracker * * @type {TimeTracker} * @memberof ProviderParameters */ timeTracker: TimeTracker; /** * @description Arguments passed to called method * * @type {Arguments} * @memberof ProviderParameters */ originalArgs: Arguments; /** * @description Hook pipeline correlation id * * @note Sub hook pipelines receive a new correlation id * * @type {string} * @memberof ProviderParameters */ correlationId: string; /** * @description The method name of the current hook pipeline * * @type {string} * @memberof ProviderParameters */ action: string; receivedArgs: any[]; currentArgs: any[]; /** * @description The hook type of the pipeline (pre|post) * * @type {string} * @memberof ProviderParameters */ hookType: string; /** * @description The error thrown by the hook pipeline method invocation * * @type {Error} * @memberof ProviderParameters */ error?: Error | undefined; /** * @description Any additional provided values to the hook pipeline * * @type {*} * @memberof ProviderParameters */ provide: any; /** * @description The result of the current hook pipeline method invocation * * @type {T[keyof T]} * @memberof ProviderParameters */ result: Result; } export interface RunConditionParameters<T extends object, Arguments extends Array<any> = any, Result = any> extends ProviderParameters<T, Arguments, Result> { didError: boolean; valueFromCondition: any; didReceiveFromLastCondition: boolean; passToNextCondition(value: any): void; } export type RunCondition<T extends object, Arguments extends Array<any> = any, Result = any> = (parameters: RunConditionParameters<T, Arguments, Result>) => boolean; /** * Helper function to create Surrogate wrapped objects * * @export * @template T * @param {T} object * @param {SurrogateOptions} [options={}] * @returns {Surrogate<T>} */ export const wrapSurrogate: <T extends object>(object: T, options?: SurrogateOptions) => Surrogate<T>; type Constructor<T> = { new (...args: any[]): T; }; export interface SurrogateDecorateOptions<T extends object> extends SurrogateOptions { locateWith?: Constructor<T>; } export interface SurrogateDecoratorOptions<T extends object, Arguments extends Array<any> = any, Result = any> { options?: SurrogateHandlerOptions<T, Arguments, Result>; handler: SurrogateHandlers<T, Arguments, Result>; } export type SurrogateDelegateOptions<T extends object, Arguments extends Array<any> = any, Result = any> = SurrogateHandlers<T, Arguments, Result> | SurrogateDecoratorOptions<T, Arguments, Result> | SurrogateDecoratorOptions<T, Arguments, Result>[]; export interface SurrogateForOptions<T extends object, Arguments extends Array<any> = any, Result = any> { options: SurrogateDelegateOptions<T, Arguments, Result>; type: Whichever; } type Action<T extends object> = keyof SurrogateUnwrapped<T> | (keyof SurrogateUnwrapped<T>)[] | string | string[]; export interface NextDecoratorOptions<T extends object> { options?: SurrogateHandlerOptions<T>; action: Action<T>; } export interface NextForOptions<T extends object> extends NextDecoratorOptions<T> { type: Whichever; } type PropertyDecorator<T extends object> = (target: T, property: Action<T>) => void; /** * @description Register class for automatic surrogate wrapping * * @export * @decorator * @template T * @param {SurrogateDecorateOptions<T>} [delegateOptions={}] */ export const SurrogateDelegate: <T extends object>(delegateOptions?: SurrogateDecorateOptions<T>) => <K extends Function>(klass: K) => K; /** * @description Registers hooks for decorated methods. Handler type must be assigned. * * @export * @decorator * @template T * @param {(SurrogateForOptions<T> | SurrogateForOptions<T>[])} forOptions * @returns {PropertyDecorator<T>} */ export const SurrogateFor: <T extends object>(forOptions: SurrogateForOptions<T> | SurrogateForOptions<T>[]) => PropertyDecorator<T>; /** * @description Registers pre and post hooks for decorated methods * * @export * @decorator * @template T * @param {(SurrogateDelegateOptions<T> | SurrogateDelegateOptions<T>[])} decoratorOptions * @returns {PropertyDecorator<T>} */ export const SurrogatePreAndPost: <T extends object>(decoratorOptions: SurrogateDelegateOptions<T> | SurrogateDelegateOptions<T>[]) => PropertyDecorator<T>; /** * @description Registers pre hooks for decorated methods * * @export * @decorator * @template T * @param {(SurrogateDelegateOptions<T> | SurrogateDelegateOptions<T>[])} decoratorOptions * @returns {PropertyDecorator<T>} */ export const SurrogatePre: <T extends object>(decoratorOptions: SurrogateDelegateOptions<T> | SurrogateDelegateOptions<T>[]) => PropertyDecorator<T>; /** * @description Registers post hooks for decorated methods * * @export * @decorator * @template T * @param {(SurrogateDelegateOptions<T> | SurrogateDelegateOptions<T>[])} decoratorOptions * @returns {PropertyDecorator<T>} */ export const SurrogatePost: <T extends object>(decoratorOptions: SurrogateDelegateOptions<T> | SurrogateDelegateOptions<T>[]) => PropertyDecorator<T>; /** * @description Registers async pre and post hooks for decorated methods * * @export * @decorator * @template T * @param {(SurrogateDelegateOptions<T> | SurrogateDelegateOptions<T>[])} decoratorOptions * @returns {PropertyDecorator<T>} */ export const SurrogateAsyncPreAndPost: <T extends object>(decoratorOptions: SurrogateDelegateOptions<T> | SurrogateDelegateOptions<T>[]) => PropertyDecorator<T>; /** * @description Registers async post hooks for decorated methods * * @export * @decorator * @template T * @param {(SurrogateDelegateOptions<T> | SurrogateDelegateOptions<T>[])} decoratorOptions * @returns {PropertyDecorator<T>} */ export const SurrogateAsyncPost: <T extends object, Arguments extends Array<any> = any, Result = any>(decoratorOptions: SurrogateDelegateOptions<T, Arguments, Result> | SurrogateDelegateOptions<T, Arguments, Result>[]) => PropertyDecorator<T>; /** * @description Registers async pre hooks for decorated methods * * @export * @decorator * @template T * @param {(SurrogateDelegateOptions<T> | SurrogateDelegateOptions<T>[])} decoratorOptions * @returns {PropertyDecorator<T>} */ export const SurrogateAsyncPre: <T extends object>(decoratorOptions: SurrogateDelegateOptions<T> | SurrogateDelegateOptions<T>[]) => PropertyDecorator<T>; type _PropertyDecorator1<T extends object> = (target: T, property: string, descriptor: PropertyDescriptor) => void; /** * @description Designate decorated methods as next handler. Handler type must be assigned * * @export * @decorator * @template T * @param {(NextForOptions<T> | NextForOptions<T>[])} nextOptions * @returns {PropertyDecorator<T>} */ export const NextFor: <T extends object>(nextOptions: NextForOptions<T> | NextForOptions<T>[]) => _PropertyDecorator1<T>; /** * Designate decorated methods as async pre hooks. * * @export * @decorator * @template T * @param {(NextDecoratorOptions<T> | NextDecoratorOptions<T>[])} nextOptions * @returns {PropertyDecorator<T>} */ export const NextAsyncPre: <T extends object>(nextOptions: NextDecoratorOptions<T> | NextDecoratorOptions<T>[]) => _PropertyDecorator1<T>; /** * Designate decorated methods as async post hooks. * * @export * @decorator * @template T * @param {(NextDecoratorOptions<T> | NextDecoratorOptions<T>[])} nextOptions * @returns {PropertyDecorator<T>} */ export const NextAsyncPost: <T extends object>(nextOptions: NextDecoratorOptions<T> | NextDecoratorOptions<T>[]) => _PropertyDecorator1<T>; /** * Designate decorated methods as async pre and post hooks. * * @export * @decorator * @template T * @param {(NextDecoratorOptions<T> | NextDecoratorOptions<T>[])} nextOptions * @returns {PropertyDecorator<T>} */ export const NextAsyncPreAndPost: <T extends object>(nextOptions: NextDecoratorOptions<T> | NextDecoratorOptions<T>[]) => _PropertyDecorator1<T>; /** * Designate decorated methods as pre hooks. * * @export * @decorator * @template T * @param {(NextDecoratorOptions<T> | NextDecoratorOptions<T>[])} nextOptions * @returns {PropertyDecorator<T>} */ export const NextPre: <T extends object>(nextOptions: NextDecoratorOptions<T> | NextDecoratorOptions<T>[]) => _PropertyDecorator1<T>; /** * Designate decorated methods as post hook * * @export * @decorator * @template T * @param {(NextDecoratorOptions<T> | NextDecoratorOptions<T>[])} nextOptions * @returns {PropertyDecorator<T>} */ export const NextPost: <T extends object>(nextOptions: NextDecoratorOptions<T> | NextDecoratorOptions<T>[]) => _PropertyDecorator1<T>; /** * Designate decorated methods as pre and post hooks. * * @export * @decorator * @template T * @param {(NextDecoratorOptions<T> | NextDecoratorOptions<T>[])} nextOptions * @returns {PropertyDecorator<T>} */ export const NextPreAndPost: <T extends object>(nextOptions: NextDecoratorOptions<T> | NextDecoratorOptions<T>[]) => _PropertyDecorator1<T>; //# sourceMappingURL=types.d.ts.map