import { Middleware } from './compose.ts'; import { copyToSelf, copyProperties } from './utils.ts'; export const HOOKS: string = Symbol('@feathersjs/hooks') as any; export type HookContextData = { [key: string]: any }; /** * The base hook context. */ export class BaseHookContext { self?: C; [key: string]: any; constructor (data: HookContextData = {}) { Object.assign(this, data); } } export interface HookContext extends BaseHookContext { result?: T; method?: string; arguments: any[]; } export type HookContextConstructor = new (data?: { [key: string]: any }) => BaseHookContext; export type HookDefaultsInitializer = (self?: any, args?: any[], context?: HookContext) => HookContextData; export class HookManager { _parent?: this|null = null; _params: string[]|null = null; _middleware: Middleware[]|null = null; _props: HookContextData|null = null; _defaults?: HookDefaultsInitializer; parent (parent: this|null) { this._parent = parent; return this; } middleware (middleware?: Middleware[]) { this._middleware = middleware?.length ? middleware : null; return this; } getMiddleware (): Middleware[]|null { const previous = this._parent?.getMiddleware(); if (previous && this._middleware) { return previous.concat(this._middleware); } return previous || this._middleware; } collectMiddleware (self: any, _args: any[]): Middleware[] { const otherMiddleware = getMiddleware(self); const middleware = this.getMiddleware(); if (otherMiddleware && middleware) { return otherMiddleware.concat(middleware); } return otherMiddleware || middleware || []; } props (props: HookContextData) { if (!this._props) { this._props = {}; } copyProperties(this._props, props); return this; } getProps (): HookContextData|null { const previous = this._parent?.getProps(); if (previous && this._props) { return copyProperties({}, previous, this._props); } return previous || this._props || null; } params (...params: string[]) { this._params = params; return this; } getParams (): string[]|null { const previous = this._parent?.getParams(); if (previous && this._params) { return previous.concat(this._params); } return previous || this._params; } defaults (defaults: HookDefaultsInitializer) { this._defaults = defaults; return this; } getDefaults (self: any, args: any[], context: HookContext): HookContextData|null { const defaults = typeof this._defaults === 'function' ? this._defaults(self, args, context) : null; const previous = this._parent?.getDefaults(self, args, context); if (previous && defaults) { return Object.assign({}, previous, defaults); } return previous || defaults; } getContextClass (Base: HookContextConstructor = BaseHookContext): HookContextConstructor { const ContextClass = class ContextClass extends Base { constructor (data: any) { super(data); copyToSelf(this); } }; const params = this.getParams(); const props = this.getProps(); if (params) { params.forEach((name, index) => { if (props?.[name] !== undefined) { throw new Error(`Hooks can not have a property and param named '${name}'. Use .defaults instead.`); } Object.defineProperty(ContextClass.prototype, name, { enumerable: true, get () { return this?.arguments[index]; }, set (value: any) { this.arguments[index] = value; } }); }); } if (props) { copyProperties(ContextClass.prototype, props); } return ContextClass; } initializeContext (self: any, args: any[], context: HookContext): HookContext { const ctx = this._parent ? this._parent.initializeContext(self, args, context) : context; const defaults = this.getDefaults(self, args, ctx); if (self) { ctx.self = self; } ctx.arguments = args; if (defaults) { for (const name of Object.keys(defaults)) { if (ctx[name] === undefined) { ctx[name] = defaults[name]; } } } return ctx; } } export type HookOptions = HookManager|Middleware[]|null; export function convertOptions (options: HookOptions = null) { if (!options) { return new HookManager() } return Array.isArray(options) ? new HookManager().middleware(options) : options; } export function getManager (target: any): HookManager|null { return (target && target[HOOKS]) || null; } export function setManager (target: T, manager: HookManager) { const parent = getManager(target); (target as any)[HOOKS] = manager.parent(parent); return target; } export function getMiddleware (target: any): Middleware[]|null { const manager = getManager(target); return manager ? manager.getMiddleware() : null; } export function setMiddleware (target: T, middleware: Middleware[]) { const manager = new HookManager().middleware(middleware); return setManager(target, manager); }