Skip to content

Commit

Permalink
feat!: improve type checking
Browse files Browse the repository at this point in the history
  • Loading branch information
pi0 committed Aug 26, 2021
1 parent ae9e030 commit c2e1e22
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 32 deletions.
38 changes: 23 additions & 15 deletions src/hookable.ts
Original file line number Diff line number Diff line change
@@ -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<string, HookCallback>,
HooksT = _HooksT & { error: (error: Error | any) => void },
HookNameT extends HookKeys<HooksT> = HookKeys<HooksT>
> {
private _hooks: { [key: string]: HookCallback[] }
private _deprecatedHooks: Record<string, DeprecatedHook<HooksT>>
private _logger: LoggerT | false

static mergeHooks: typeof mergeHooks
Expand All @@ -20,7 +24,7 @@ class Hookable {
this.callHook = this.callHook.bind(this)
}

hook (name: string, fn: hookFnT) {
hook <NameT extends HookNameT>(name: NameT, fn: HooksT[NameT] & HookCallback) {
if (!name || typeof fn !== 'function') {
return () => {}
}
Expand Down Expand Up @@ -56,19 +60,19 @@ class Hookable {
}
}

hookOnce (name: string, fn: hookFnT) {
hookOnce <NameT extends HookNameT>(name: NameT, fn: HooksT[NameT] & HookCallback) {
let _unreg
let _fn = (...args) => {
_unreg()
_unreg = null
_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 <NameT extends HookNameT> (name: NameT, fn: HooksT[NameT] & HookCallback) {
if (this._hooks[name]) {
const idx = this._hooks[name].indexOf(fn)

Expand All @@ -82,16 +86,17 @@ class Hookable {
}
}

deprecateHook (name: string, deprecated: deprecatedHookT) {
deprecateHook <NameT extends HookNameT> (name: NameT, deprecated: DeprecatedHook<HooksT>) {
this._deprecatedHooks[name] = deprecated
}

deprecateHooks (deprecatedHooks: deprecatedHooksT) {
deprecateHooks (deprecatedHooks: Record<HookNameT, DeprecatedHook<HooksT>>) {
Object.assign(this._deprecatedHooks, deprecatedHooks)
}

addHooks (configHooks: configHooksT) {
const hooks = flatHooks(configHooks)
addHooks (configHooks: NestedHooks<HooksT>) {
const hooks = flatHooks<HooksT>(configHooks)
// @ts-ignore
const removeFns = Object.keys(hooks).map(key => this.hook(key, hooks[key]))

return () => {
Expand All @@ -101,21 +106,24 @@ class Hookable {
}
}

removeHooks (configHooks: configHooksT) {
const hooks = flatHooks(configHooks)
removeHooks (configHooks: NestedHooks<HooksT>) {
const hooks = flatHooks<HooksT>(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 <NameT extends HookNameT> (name: NameT, ...args: Parameters<HooksT[NameT]>) {
if (!this._hooks[name]) {
return
}
try {
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) {
Expand Down
13 changes: 7 additions & 6 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
export type unregHookT = () => void
export type hookFnT = (...args: any) => Promise<void> | 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> | void

export interface Hooks { [key: string]: HookCallback }
export type HookKeys<T> = keyof T & string
export type NestedHooks<T> = { [name in HookKeys<T>]: NestedHooks<T> | HookCallback }
export type DeprecatedHook<T> = string | { message: string, to: HookKeys<T> }
export type DeprecatedHooks<T> = { [name in HookKeys<T>]: DeprecatedHook<T> }

export interface LoggerT {
error(...args: any): void,
Expand Down
23 changes: 12 additions & 11 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -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<T> (configHooks: NestedHooks<T>, 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<T> (...hooks: NestedHooks<T>[]): 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]]
}
}
}
Expand All @@ -36,7 +37,7 @@ export function mergeHooks (...hooks: configHooksT[]): flatHooksT {
}
}

return finalHooks
return finalHooks as any
}

export function serial<T> (tasks: T[], fn: (task: T) => Promise<any> | any) {
Expand Down

0 comments on commit c2e1e22

Please sign in to comment.