From c2e1e223d16e7bf87117cd8d72ad3ba211a333d8 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Thu, 26 Aug 2021 20:11:55 +0200 Subject: [PATCH] feat!: improve type checking --- src/hookable.ts | 38 +++++++++++++++++++++++--------------- src/types.ts | 13 +++++++------ src/utils.ts | 23 ++++++++++++----------- 3 files changed, 42 insertions(+), 32 deletions(-) diff --git a/src/hookable.ts b/src/hookable.ts index 2e47edf..8973242 100644 --- a/src/hookable.ts +++ b/src/hookable.ts @@ -1,10 +1,14 @@ import { serial, flatHooks, mergeHooks } from './utils' -import { LoggerT, hookFnT, configHooksT, deprecatedHookT, deprecatedHooksT } from './types' +import type { LoggerT, DeprecatedHook, NestedHooks, HookCallback, HookKeys } from './types' export * from './types' -class Hookable { - private _hooks: { [name: string]: hookFnT[] } - private _deprecatedHooks: deprecatedHooksT +class Hookable < + _HooksT = Record, + HooksT = _HooksT & { error: (error: Error | any) => void }, + HookNameT extends HookKeys = HookKeys +> { + private _hooks: { [key: string]: HookCallback[] } + private _deprecatedHooks: Record> private _logger: LoggerT | false static mergeHooks: typeof mergeHooks @@ -20,7 +24,7 @@ class Hookable { this.callHook = this.callHook.bind(this) } - hook (name: string, fn: hookFnT) { + hook (name: NameT, fn: HooksT[NameT] & HookCallback) { if (!name || typeof fn !== 'function') { return () => {} } @@ -56,7 +60,7 @@ class Hookable { } } - hookOnce (name: string, fn: hookFnT) { + hookOnce (name: NameT, fn: HooksT[NameT] & HookCallback) { let _unreg let _fn = (...args) => { _unreg() @@ -64,11 +68,11 @@ class Hookable { _fn = null return fn(...args) } - _unreg = this.hook(name, _fn) + _unreg = this.hook(name, _fn as typeof fn) return _unreg } - removeHook (name: string, fn: hookFnT) { + removeHook (name: NameT, fn: HooksT[NameT] & HookCallback) { if (this._hooks[name]) { const idx = this._hooks[name].indexOf(fn) @@ -82,16 +86,17 @@ class Hookable { } } - deprecateHook (name: string, deprecated: deprecatedHookT) { + deprecateHook (name: NameT, deprecated: DeprecatedHook) { this._deprecatedHooks[name] = deprecated } - deprecateHooks (deprecatedHooks: deprecatedHooksT) { + deprecateHooks (deprecatedHooks: Record>) { Object.assign(this._deprecatedHooks, deprecatedHooks) } - addHooks (configHooks: configHooksT) { - const hooks = flatHooks(configHooks) + addHooks (configHooks: NestedHooks) { + const hooks = flatHooks(configHooks) + // @ts-ignore const removeFns = Object.keys(hooks).map(key => this.hook(key, hooks[key])) return () => { @@ -101,14 +106,16 @@ class Hookable { } } - removeHooks (configHooks: configHooksT) { - const hooks = flatHooks(configHooks) + removeHooks (configHooks: NestedHooks) { + const hooks = flatHooks(configHooks) for (const key in hooks) { + // @ts-ignore this.removeHook(key, hooks[key]) } } - async callHook (name: string, ...args: any) { + // @ts-ignore HooksT[NameT] & HookCallback prevents typechecking + async callHook (name: NameT, ...args: Parameters) { if (!this._hooks[name]) { return } @@ -116,6 +123,7 @@ class Hookable { await serial(this._hooks[name], fn => fn(...args)) } catch (err) { if (name !== 'error') { + // @ts-ignore Stranger Things await this.callHook('error', err) } if (this._logger) { diff --git a/src/types.ts b/src/types.ts index b11b8bb..04adf21 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,9 +1,10 @@ -export type unregHookT = () => void -export type hookFnT = (...args: any) => Promise | void -export type configHooksT = { [name: string]: configHooksT | hookFnT } -export type deprecatedHookT = string | { message: string, to: string } -export type deprecatedHooksT = { [name: string]: deprecatedHookT} -export type flatHooksT = { [name: string]: hookFnT } +export type HookCallback = (...args: any) => Promise | void + +export interface Hooks { [key: string]: HookCallback } +export type HookKeys = keyof T & string +export type NestedHooks = { [name in HookKeys]: NestedHooks | HookCallback } +export type DeprecatedHook = string | { message: string, to: HookKeys } +export type DeprecatedHooks = { [name in HookKeys]: DeprecatedHook } export interface LoggerT { error(...args: any): void, diff --git a/src/utils.ts b/src/utils.ts index b112a47..9d6c904 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,28 +1,29 @@ -import { configHooksT, flatHooksT } from './types' +import { NestedHooks } from './types' -export function flatHooks (configHooks: configHooksT, hooks: flatHooksT = {}, parentName?: string): flatHooksT { +export function flatHooks (configHooks: NestedHooks, hooks: T = {} as T, parentName?: string): T { for (const key in configHooks) { const subHook = configHooks[key] const name = parentName ? `${parentName}:${key}` : key if (typeof subHook === 'object' && subHook !== null) { flatHooks(subHook, hooks, name) } else if (typeof subHook === 'function') { + // @ts-ignore hooks[name] = subHook } } - return hooks + return hooks as any } -export function mergeHooks (...hooks: configHooksT[]): flatHooksT { - const finalHooks: any = {} +export function mergeHooks (...hooks: NestedHooks[]): T { + const finalHooks = {} as any - for (let _hook of hooks) { - _hook = flatHooks(_hook) - for (const key in _hook) { + for (let hook of hooks) { + const flatenHook = flatHooks(hook) + for (const key in flatenHook) { if (finalHooks[key]) { - finalHooks[key].push(_hook[key]) + finalHooks[key].push(flatenHook[key]) } else { - finalHooks[key] = [_hook[key]] + finalHooks[key] = [flatenHook[key]] } } } @@ -36,7 +37,7 @@ export function mergeHooks (...hooks: configHooksT[]): flatHooksT { } } - return finalHooks + return finalHooks as any } export function serial (tasks: T[], fn: (task: T) => Promise | any) {