hookable 5.2.2 → 5.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -220,6 +220,19 @@ hookable.hook('test', () => { console.log('running test hook') })
220
220
  await hookable.callHook('test')
221
221
  ```
222
222
 
223
+ ### `createDebugger`
224
+
225
+ Automatically logs each hook that is called and how long it takes to run.
226
+
227
+ ```js
228
+ const debug = hookable.createDebugger(hooks, { tag: 'something' })
229
+
230
+ hooks.callHook('some-hook', 'some-arg')
231
+ // [something] some-hook: 0.21ms
232
+
233
+ debug.close()
234
+ ```
235
+
223
236
  ## Migration
224
237
 
225
238
  ### From `4.x` to `5.x`
package/dist/index.cjs CHANGED
@@ -50,40 +50,80 @@ function callEachWith(callbacks, arg0) {
50
50
  cb(arg0);
51
51
  }
52
52
  }
53
+ const isBrowser = typeof window !== "undefined";
54
+ function createDebugger(hooks, _options = {}) {
55
+ const options = {
56
+ inspect: isBrowser,
57
+ group: isBrowser,
58
+ filter: () => true,
59
+ ..._options
60
+ };
61
+ const _filter = options.filter;
62
+ const filter = typeof _filter === "string" ? (name) => name.startsWith(_filter) : _filter;
63
+ const logPrefix = options.tag ? `[${options.tag}] ` : "";
64
+ const unsubscribeBefore = hooks.beforeEach(({ name }) => {
65
+ if (!filter(name)) {
66
+ return;
67
+ }
68
+ console.time(logPrefix + name);
69
+ });
70
+ const unsubscribeAfter = hooks.afterEach(({ name, args }) => {
71
+ if (!filter(name)) {
72
+ return;
73
+ }
74
+ if (options.group) {
75
+ console.groupCollapsed(name);
76
+ }
77
+ if (options.inspect) {
78
+ console.timeLog(logPrefix + name, args);
79
+ } else {
80
+ console.timeEnd(logPrefix + name);
81
+ }
82
+ if (options.group) {
83
+ console.groupEnd();
84
+ }
85
+ });
86
+ return {
87
+ close: () => {
88
+ unsubscribeBefore();
89
+ unsubscribeAfter();
90
+ }
91
+ };
92
+ }
53
93
 
54
94
  class Hookable {
55
95
  constructor() {
56
96
  this._hooks = {};
57
97
  this._before = null;
58
98
  this._after = null;
99
+ this._deprecatedMessages = null;
59
100
  this._deprecatedHooks = {};
60
101
  this.hook = this.hook.bind(this);
61
102
  this.callHook = this.callHook.bind(this);
62
103
  this.callHookWith = this.callHookWith.bind(this);
63
104
  }
64
- hook(name, fn) {
105
+ hook(name, fn, opts = {}) {
65
106
  if (!name || typeof fn !== "function") {
66
107
  return () => {
67
108
  };
68
109
  }
69
110
  const originalName = name;
70
- let deprecatedHookObj;
111
+ let dep;
71
112
  while (this._deprecatedHooks[name]) {
72
- const deprecatedHook = this._deprecatedHooks[name];
73
- if (typeof deprecatedHook === "string") {
74
- deprecatedHookObj = { to: deprecatedHook };
75
- } else {
76
- deprecatedHookObj = deprecatedHook;
77
- }
78
- name = deprecatedHookObj.to;
113
+ dep = this._deprecatedHooks[name];
114
+ name = dep.to;
79
115
  }
80
- if (deprecatedHookObj) {
81
- if (!deprecatedHookObj.message) {
82
- console.warn(
83
- `${originalName} hook has been deprecated` + (deprecatedHookObj.to ? `, please use ${deprecatedHookObj.to}` : "")
84
- );
85
- } else {
86
- console.warn(deprecatedHookObj.message);
116
+ if (dep && !opts.allowDeprecated) {
117
+ let message = dep.message;
118
+ if (!message) {
119
+ message = `${originalName} hook has been deprecated` + (dep.to ? `, please use ${dep.to}` : "");
120
+ }
121
+ if (!this._deprecatedMessages) {
122
+ this._deprecatedMessages = /* @__PURE__ */ new Set();
123
+ }
124
+ if (!this._deprecatedMessages.has(message)) {
125
+ console.warn(message);
126
+ this._deprecatedMessages.add(message);
87
127
  }
88
128
  }
89
129
  this._hooks[name] = this._hooks[name] || [];
@@ -118,7 +158,7 @@ class Hookable {
118
158
  }
119
159
  }
120
160
  deprecateHook(name, deprecated) {
121
- this._deprecatedHooks[name] = deprecated;
161
+ this._deprecatedHooks[name] = typeof deprecated === "string" ? { to: deprecated } : deprecated;
122
162
  const _hooks = this._hooks[name] || [];
123
163
  this._hooks[name] = void 0;
124
164
  for (const hook of _hooks) {
@@ -171,10 +211,22 @@ class Hookable {
171
211
  beforeEach(fn) {
172
212
  this._before = this._before || [];
173
213
  this._before.push(fn);
214
+ return () => {
215
+ const index = this._before.indexOf(fn);
216
+ if (index !== -1) {
217
+ this._before.splice(index, 1);
218
+ }
219
+ };
174
220
  }
175
221
  afterEach(fn) {
176
222
  this._after = this._after || [];
177
223
  this._after.push(fn);
224
+ return () => {
225
+ const index = this._after.indexOf(fn);
226
+ if (index !== -1) {
227
+ this._after.splice(index, 1);
228
+ }
229
+ };
178
230
  }
179
231
  }
180
232
  function createHooks() {
@@ -182,6 +234,7 @@ function createHooks() {
182
234
  }
183
235
 
184
236
  exports.Hookable = Hookable;
237
+ exports.createDebugger = createDebugger;
185
238
  exports.createHooks = createHooks;
186
239
  exports.flatHooks = flatHooks;
187
240
  exports.mergeHooks = mergeHooks;
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ interface Hooks {
3
3
  [key: string]: HookCallback;
4
4
  }
5
5
  declare type HookKeys<T> = keyof T & string;
6
- declare type DeprecatedHook<T> = string | {
6
+ declare type DeprecatedHook<T> = {
7
7
  message?: string;
8
8
  to: HookKeys<T>;
9
9
  };
@@ -34,6 +34,24 @@ declare type NestedHooks<T> = (Partial<StripGeneric<T>> | Partial<OnlyGeneric<T>
34
34
  }> & Partial<{
35
35
  [key in BareHooks<StripGeneric<T>>]: T[key];
36
36
  }>;
37
+ interface CreateDebuggerOptions {
38
+ /** An optional tag to prefix console logs with */
39
+ tag?: string;
40
+ /**
41
+ * Show hook params to the console output
42
+ *
43
+ * Enabled for browsers by default
44
+ */
45
+ inspect?: boolean;
46
+ /**
47
+ * Use group/groupEnd wrapper around logs happening during a specific hook
48
+ *
49
+ * Enabled for browsers by default
50
+ */
51
+ group?: boolean;
52
+ /** Filter which hooks to enable debugger for. Can be a string prefix or fn. */
53
+ filter?: string | ((event: string) => boolean);
54
+ }
37
55
 
38
56
  declare type InferCallback<HT, HN extends keyof HT> = HT[HN] extends HookCallback ? HT[HN] : never;
39
57
  declare type InferSpyEvent<HT extends Record<string, any>> = {
@@ -48,19 +66,22 @@ declare class Hookable<HooksT = Record<string, HookCallback>, HookNameT extends
48
66
  private _before;
49
67
  private _after;
50
68
  private _deprecatedHooks;
69
+ private _deprecatedMessages;
51
70
  constructor();
52
- hook<NameT extends HookNameT>(name: NameT, fn: InferCallback<HooksT, NameT>): () => void;
71
+ hook<NameT extends HookNameT>(name: NameT, fn: InferCallback<HooksT, NameT>, opts?: {
72
+ allowDeprecated?: boolean;
73
+ }): () => void;
53
74
  hookOnce<NameT extends HookNameT>(name: NameT, fn: InferCallback<HooksT, NameT>): () => void;
54
75
  removeHook<NameT extends HookNameT>(name: NameT, fn: InferCallback<HooksT, NameT>): void;
55
- deprecateHook<NameT extends HookNameT>(name: NameT, deprecated: DeprecatedHook<HooksT>): void;
76
+ deprecateHook<NameT extends HookNameT>(name: NameT, deprecated: HookKeys<HooksT> | DeprecatedHook<HooksT>): void;
56
77
  deprecateHooks(deprecatedHooks: Partial<Record<HookNameT, DeprecatedHook<HooksT>>>): void;
57
78
  addHooks(configHooks: NestedHooks<HooksT>): () => void;
58
79
  removeHooks(configHooks: NestedHooks<HooksT>): void;
59
80
  callHook<NameT extends HookNameT>(name: NameT, ...args: Parameters<InferCallback<HooksT, NameT>>): Promise<any>;
60
81
  callHookParallel<NameT extends HookNameT>(name: NameT, ...args: Parameters<InferCallback<HooksT, NameT>>): Promise<any[]>;
61
82
  callHookWith<NameT extends HookNameT, CallFunction extends (hooks: HookCallback[], args: Parameters<InferCallback<HooksT, NameT>>) => any>(caller: CallFunction, name: NameT, ...args: Parameters<InferCallback<HooksT, NameT>>): ReturnType<CallFunction>;
62
- beforeEach(fn: (event: InferSpyEvent<HooksT>) => void): void;
63
- afterEach(fn: (event: InferSpyEvent<HooksT>) => void): void;
83
+ beforeEach(fn: (event: InferSpyEvent<HooksT>) => void): () => void;
84
+ afterEach(fn: (event: InferSpyEvent<HooksT>) => void): () => void;
64
85
  }
65
86
  declare function createHooks<T>(): Hookable<T>;
66
87
 
@@ -69,5 +90,10 @@ declare function mergeHooks<T>(...hooks: NestedHooks<T>[]): T;
69
90
  declare function serial<T>(tasks: T[], fn: (task: T) => Promise<any> | any): Promise<any>;
70
91
  declare function serialCaller(hooks: HookCallback[], args?: any[]): Promise<any>;
71
92
  declare function parallelCaller(hooks: HookCallback[], args?: any[]): Promise<any[]>;
93
+ /** Start debugging hook names and timing in console */
94
+ declare function createDebugger(hooks: Hookable, _options?: CreateDebuggerOptions): {
95
+ /** Stop debugging and remove listeners */
96
+ close: () => void;
97
+ };
72
98
 
73
- export { DeprecatedHook, DeprecatedHooks, HookCallback, HookKeys, Hookable, Hooks, NestedHooks, createHooks, flatHooks, mergeHooks, parallelCaller, serial, serialCaller };
99
+ export { CreateDebuggerOptions, DeprecatedHook, DeprecatedHooks, HookCallback, HookKeys, Hookable, Hooks, NestedHooks, createDebugger, createHooks, flatHooks, mergeHooks, parallelCaller, serial, serialCaller };
package/dist/index.mjs CHANGED
@@ -46,40 +46,80 @@ function callEachWith(callbacks, arg0) {
46
46
  cb(arg0);
47
47
  }
48
48
  }
49
+ const isBrowser = typeof window !== "undefined";
50
+ function createDebugger(hooks, _options = {}) {
51
+ const options = {
52
+ inspect: isBrowser,
53
+ group: isBrowser,
54
+ filter: () => true,
55
+ ..._options
56
+ };
57
+ const _filter = options.filter;
58
+ const filter = typeof _filter === "string" ? (name) => name.startsWith(_filter) : _filter;
59
+ const logPrefix = options.tag ? `[${options.tag}] ` : "";
60
+ const unsubscribeBefore = hooks.beforeEach(({ name }) => {
61
+ if (!filter(name)) {
62
+ return;
63
+ }
64
+ console.time(logPrefix + name);
65
+ });
66
+ const unsubscribeAfter = hooks.afterEach(({ name, args }) => {
67
+ if (!filter(name)) {
68
+ return;
69
+ }
70
+ if (options.group) {
71
+ console.groupCollapsed(name);
72
+ }
73
+ if (options.inspect) {
74
+ console.timeLog(logPrefix + name, args);
75
+ } else {
76
+ console.timeEnd(logPrefix + name);
77
+ }
78
+ if (options.group) {
79
+ console.groupEnd();
80
+ }
81
+ });
82
+ return {
83
+ close: () => {
84
+ unsubscribeBefore();
85
+ unsubscribeAfter();
86
+ }
87
+ };
88
+ }
49
89
 
50
90
  class Hookable {
51
91
  constructor() {
52
92
  this._hooks = {};
53
93
  this._before = null;
54
94
  this._after = null;
95
+ this._deprecatedMessages = null;
55
96
  this._deprecatedHooks = {};
56
97
  this.hook = this.hook.bind(this);
57
98
  this.callHook = this.callHook.bind(this);
58
99
  this.callHookWith = this.callHookWith.bind(this);
59
100
  }
60
- hook(name, fn) {
101
+ hook(name, fn, opts = {}) {
61
102
  if (!name || typeof fn !== "function") {
62
103
  return () => {
63
104
  };
64
105
  }
65
106
  const originalName = name;
66
- let deprecatedHookObj;
107
+ let dep;
67
108
  while (this._deprecatedHooks[name]) {
68
- const deprecatedHook = this._deprecatedHooks[name];
69
- if (typeof deprecatedHook === "string") {
70
- deprecatedHookObj = { to: deprecatedHook };
71
- } else {
72
- deprecatedHookObj = deprecatedHook;
73
- }
74
- name = deprecatedHookObj.to;
109
+ dep = this._deprecatedHooks[name];
110
+ name = dep.to;
75
111
  }
76
- if (deprecatedHookObj) {
77
- if (!deprecatedHookObj.message) {
78
- console.warn(
79
- `${originalName} hook has been deprecated` + (deprecatedHookObj.to ? `, please use ${deprecatedHookObj.to}` : "")
80
- );
81
- } else {
82
- console.warn(deprecatedHookObj.message);
112
+ if (dep && !opts.allowDeprecated) {
113
+ let message = dep.message;
114
+ if (!message) {
115
+ message = `${originalName} hook has been deprecated` + (dep.to ? `, please use ${dep.to}` : "");
116
+ }
117
+ if (!this._deprecatedMessages) {
118
+ this._deprecatedMessages = /* @__PURE__ */ new Set();
119
+ }
120
+ if (!this._deprecatedMessages.has(message)) {
121
+ console.warn(message);
122
+ this._deprecatedMessages.add(message);
83
123
  }
84
124
  }
85
125
  this._hooks[name] = this._hooks[name] || [];
@@ -114,7 +154,7 @@ class Hookable {
114
154
  }
115
155
  }
116
156
  deprecateHook(name, deprecated) {
117
- this._deprecatedHooks[name] = deprecated;
157
+ this._deprecatedHooks[name] = typeof deprecated === "string" ? { to: deprecated } : deprecated;
118
158
  const _hooks = this._hooks[name] || [];
119
159
  this._hooks[name] = void 0;
120
160
  for (const hook of _hooks) {
@@ -167,14 +207,26 @@ class Hookable {
167
207
  beforeEach(fn) {
168
208
  this._before = this._before || [];
169
209
  this._before.push(fn);
210
+ return () => {
211
+ const index = this._before.indexOf(fn);
212
+ if (index !== -1) {
213
+ this._before.splice(index, 1);
214
+ }
215
+ };
170
216
  }
171
217
  afterEach(fn) {
172
218
  this._after = this._after || [];
173
219
  this._after.push(fn);
220
+ return () => {
221
+ const index = this._after.indexOf(fn);
222
+ if (index !== -1) {
223
+ this._after.splice(index, 1);
224
+ }
225
+ };
174
226
  }
175
227
  }
176
228
  function createHooks() {
177
229
  return new Hookable();
178
230
  }
179
231
 
180
- export { Hookable, createHooks, flatHooks, mergeHooks, parallelCaller, serial, serialCaller };
232
+ export { Hookable, createDebugger, createHooks, flatHooks, mergeHooks, parallelCaller, serial, serialCaller };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hookable",
3
- "version": "5.2.2",
3
+ "version": "5.4.0",
4
4
  "description": "Awaitable hook system",
5
5
  "keywords": [
6
6
  "hook",
@@ -23,15 +23,15 @@
23
23
  ],
24
24
  "devDependencies": {
25
25
  "@nuxtjs/eslint-config-typescript": "latest",
26
- "@vitest/coverage-c8": "^0.22.1",
26
+ "@vitest/coverage-c8": "^0.24.1",
27
27
  "eslint": "latest",
28
- "expect-type": "^0.13.0",
28
+ "expect-type": "^0.14.2",
29
29
  "standard-version": "latest",
30
30
  "typescript": "latest",
31
31
  "unbuild": "latest",
32
32
  "vitest": "latest"
33
33
  },
34
- "packageManager": "pnpm@7.9.4",
34
+ "packageManager": "pnpm@7.13.4",
35
35
  "scripts": {
36
36
  "build": "unbuild",
37
37
  "dev": "vitest",