import { flatHooks , parallelCaller , serialCaller } from './utils' ; import type { DeprecatedHook , NestedHooks , HookCallback , HookKeys } from './types' ; type InferCallback < HT , HN extends keyof HT > = HT [ HN ] extends HookCallback ? HT [ HN ] : never ; export class Hookable < HooksT = Record < string , HookCallback > , HookNameT extends HookKeys < HooksT > = HookKeys < HooksT > > { private _hooks : { [ key ] : HookCallback [ ] ; } ; private _deprecatedHooks : Record < string , DeprecatedHook < HooksT > > ; constructor ( ) { this . _hooks = { } ; this . _deprecatedHooks = { } ; this . hook = this . hook . bind ( this ) ; this . callHook = this . callHook . bind ( this ) ; this . callHookWith = this . callHookWith . bind ( this ) ; } hook < NameT extends HookNameT > ( name , fn ) { if ( ! name || typeof fn !== 'function' ) { return ( ) => { } ; } const originalName = name ; let deprecatedHookObj ; while ( this . _deprecatedHooks [ name ] ) { const deprecatedHook = this . _deprecatedHooks [ name ] ; if ( typeof deprecatedHook === 'string' ) { deprecatedHookObj = { to : deprecatedHook as NameT } ; } else { deprecatedHookObj = deprecatedHook ; } name = deprecatedHookObj . to as NameT ; } if ( deprecatedHookObj ) { if ( ! deprecatedHookObj . message ) { console . warn ( `${ originalName } hook has been deprecated` + ( deprecatedHookObj . to ? `, please use ${ deprecatedHookObj . to }` : '' ) ) ; } else { console . warn ( deprecatedHookObj . message ) ; } } this . _hooks [ name ] = this . _hooks [ name ] || [ ] ; this . _hooks [ name ] . push ( fn ) ; return ( ) => { if ( fn ) { this . removeHook ( name , fn ) ; fn = null ; } } ; } hookOnce < NameT extends HookNameT > ( name , fn ) { let _unreg ; let _fn = ( ... args ) => { _unreg ( ) ; _unreg = null ; _fn = null ; return fn ( ... args ) ; } ; _unreg = this . hook ( name , _fn as typeof fn ) ; return _unreg ; } removeHook < NameT extends HookNameT > ( name , fn ) { if ( this . _hooks [ name ] ) { const idx = this . _hooks [ name ] . indexOf ( fn ) ; if ( idx !== - 1 ) { this . _hooks [ name ] . splice ( idx , 1 ) ; } if ( this . _hooks [ name ] . length === 0 ) { delete this . _hooks [ name ] ; } } } deprecateHook < NameT extends HookNameT > ( name , deprecated ) { this . _deprecatedHooks [ name ] = deprecated ; } deprecateHooks ( deprecatedHooks ) { Object . assign ( this . _deprecatedHooks , deprecatedHooks ) ; } addHooks ( configHooks ) { const hooks = flatHooks < HooksT > ( configHooks ) ; const removeFns = Object . keys ( hooks ) . map ( key => this . hook ( key , hooks [ key ] ) ) ; return ( ) => { removeFns . splice ( 0 , removeFns . length ) . forEach ( unreg => unreg ( ) ) ; } ; } removeHooks ( configHooks ) { const hooks = flatHooks < HooksT > ( configHooks ) ; for ( const key in hooks ) { this . removeHook ( key , hooks [ key ] ) ; } } callHook < NameT extends HookNameT > ( name , ... args ) { return serialCaller ( this . _hooks [ name ] || [ ] , args ) ; } callHookParallel < NameT extends HookNameT > ( name , ... args ) { return parallelCaller ( this . _hooks [ name ] || [ ] , args ) ; } callHookWith < NameT extends HookNameT , CallFunction extends ( hooks : HookCallback [ ] , args : Parameters < InferCallback < HooksT , NameT > > ) => any > ( caller , name , ... args ) { return caller ( this . _hooks [ name ] || [ ] , args ) ; } } export function createHooks < T > ( ) { return new Hookable < T > ( ) ; }