hookified 1.15.1 → 2.0.1
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 +960 -709
- package/dist/browser/index.global.js +384 -191
- package/dist/browser/index.global.js.map +1 -1
- package/dist/browser/index.js +387 -192
- package/dist/browser/index.js.map +1 -1
- package/dist/node/index.cjs +390 -193
- package/dist/node/index.d.cts +206 -80
- package/dist/node/index.d.ts +206 -80
- package/dist/node/index.js +387 -192
- package/package.json +6 -6
package/dist/node/index.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
// src/eventified.ts
|
|
2
|
+
var ERROR_EVENT = "error";
|
|
2
3
|
var Eventified = class {
|
|
3
4
|
_eventListeners;
|
|
4
5
|
_maxListeners;
|
|
5
|
-
|
|
6
|
+
_eventLogger;
|
|
6
7
|
_throwOnEmitError = false;
|
|
7
|
-
_throwOnEmptyListeners =
|
|
8
|
-
_errorEvent = "error";
|
|
8
|
+
_throwOnEmptyListeners = true;
|
|
9
9
|
constructor(options) {
|
|
10
10
|
this._eventListeners = /* @__PURE__ */ new Map();
|
|
11
|
-
this._maxListeners =
|
|
12
|
-
this.
|
|
11
|
+
this._maxListeners = 0;
|
|
12
|
+
this._eventLogger = options?.eventLogger;
|
|
13
13
|
if (options?.throwOnEmitError !== void 0) {
|
|
14
14
|
this._throwOnEmitError = options.throwOnEmitError;
|
|
15
15
|
}
|
|
@@ -18,18 +18,18 @@ var Eventified = class {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
/**
|
|
21
|
-
* Gets the logger
|
|
21
|
+
* Gets the event logger
|
|
22
22
|
* @returns {Logger}
|
|
23
23
|
*/
|
|
24
|
-
get
|
|
25
|
-
return this.
|
|
24
|
+
get eventLogger() {
|
|
25
|
+
return this._eventLogger;
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
|
-
* Sets the logger
|
|
29
|
-
* @param {Logger}
|
|
28
|
+
* Sets the event logger
|
|
29
|
+
* @param {Logger} eventLogger
|
|
30
30
|
*/
|
|
31
|
-
set
|
|
32
|
-
this.
|
|
31
|
+
set eventLogger(eventLogger) {
|
|
32
|
+
this._eventLogger = eventLogger;
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
35
|
* Gets whether an error should be thrown when an emit throws an error. Default is false and only emits an error event.
|
|
@@ -80,10 +80,17 @@ var Eventified = class {
|
|
|
80
80
|
*/
|
|
81
81
|
listenerCount(eventName) {
|
|
82
82
|
if (eventName === void 0) {
|
|
83
|
-
|
|
83
|
+
let count = 0;
|
|
84
|
+
for (const entry2 of this._eventListeners.values()) {
|
|
85
|
+
count += typeof entry2 === "function" ? 1 : entry2.length;
|
|
86
|
+
}
|
|
87
|
+
return count;
|
|
88
|
+
}
|
|
89
|
+
const entry = this._eventListeners.get(eventName);
|
|
90
|
+
if (entry === void 0) {
|
|
91
|
+
return 0;
|
|
84
92
|
}
|
|
85
|
-
|
|
86
|
-
return listeners ? listeners.length : 0;
|
|
93
|
+
return typeof entry === "function" ? 1 : entry.length;
|
|
87
94
|
}
|
|
88
95
|
/**
|
|
89
96
|
* Gets an array of event names
|
|
@@ -101,7 +108,11 @@ var Eventified = class {
|
|
|
101
108
|
if (event === void 0) {
|
|
102
109
|
return this.getAllListeners();
|
|
103
110
|
}
|
|
104
|
-
|
|
111
|
+
const entry = this._eventListeners.get(event);
|
|
112
|
+
if (entry === void 0) {
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
return typeof entry === "function" ? [entry] : entry;
|
|
105
116
|
}
|
|
106
117
|
/**
|
|
107
118
|
* Prepends a listener to the beginning of the listeners array for the specified event
|
|
@@ -110,9 +121,14 @@ var Eventified = class {
|
|
|
110
121
|
* @returns {IEventEmitter} returns the instance of the class for chaining
|
|
111
122
|
*/
|
|
112
123
|
prependListener(eventName, listener) {
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
124
|
+
const existing = this._eventListeners.get(eventName);
|
|
125
|
+
if (existing === void 0) {
|
|
126
|
+
this._eventListeners.set(eventName, listener);
|
|
127
|
+
} else if (typeof existing === "function") {
|
|
128
|
+
this._eventListeners.set(eventName, [listener, existing]);
|
|
129
|
+
} else {
|
|
130
|
+
existing.unshift(listener);
|
|
131
|
+
}
|
|
116
132
|
return this;
|
|
117
133
|
}
|
|
118
134
|
/**
|
|
@@ -153,17 +169,26 @@ var Eventified = class {
|
|
|
153
169
|
* @returns {IEventEmitter} returns the instance of the class for chaining
|
|
154
170
|
*/
|
|
155
171
|
on(event, listener) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
172
|
+
const existing = this._eventListeners.get(event);
|
|
173
|
+
if (existing === void 0) {
|
|
174
|
+
this._eventListeners.set(event, listener);
|
|
175
|
+
return this;
|
|
176
|
+
}
|
|
177
|
+
if (typeof existing === "function") {
|
|
178
|
+
const arr = [existing, listener];
|
|
179
|
+
this._eventListeners.set(event, arr);
|
|
180
|
+
if (this._maxListeners > 0 && arr.length > this._maxListeners) {
|
|
162
181
|
console.warn(
|
|
163
|
-
`MaxListenersExceededWarning: Possible event memory leak detected. ${
|
|
182
|
+
`MaxListenersExceededWarning: Possible event memory leak detected. ${arr.length} ${event} listeners added. Use setMaxListeners() to increase limit.`
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
} else {
|
|
186
|
+
existing.push(listener);
|
|
187
|
+
if (this._maxListeners > 0 && existing.length > this._maxListeners) {
|
|
188
|
+
console.warn(
|
|
189
|
+
`MaxListenersExceededWarning: Possible event memory leak detected. ${existing.length} ${event} listeners added. Use setMaxListeners() to increase limit.`
|
|
164
190
|
);
|
|
165
191
|
}
|
|
166
|
-
listeners.push(listener);
|
|
167
192
|
}
|
|
168
193
|
return this;
|
|
169
194
|
}
|
|
@@ -184,13 +209,25 @@ var Eventified = class {
|
|
|
184
209
|
* @returns {IEventEmitter} returns the instance of the class for chaining
|
|
185
210
|
*/
|
|
186
211
|
off(event, listener) {
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
listeners.splice(index, 1);
|
|
212
|
+
const entry = this._eventListeners.get(event);
|
|
213
|
+
if (entry === void 0) {
|
|
214
|
+
return this;
|
|
191
215
|
}
|
|
192
|
-
if (
|
|
193
|
-
|
|
216
|
+
if (typeof entry === "function") {
|
|
217
|
+
if (entry === listener) {
|
|
218
|
+
this._eventListeners.delete(event);
|
|
219
|
+
}
|
|
220
|
+
return this;
|
|
221
|
+
}
|
|
222
|
+
const index = entry.indexOf(listener);
|
|
223
|
+
if (index !== -1) {
|
|
224
|
+
if (entry.length === 2) {
|
|
225
|
+
this._eventListeners.set(event, entry[1 - index]);
|
|
226
|
+
} else if (entry.length === 1) {
|
|
227
|
+
this._eventListeners.delete(event);
|
|
228
|
+
} else {
|
|
229
|
+
entry.splice(index, 1);
|
|
230
|
+
}
|
|
194
231
|
}
|
|
195
232
|
return this;
|
|
196
233
|
}
|
|
@@ -202,24 +239,40 @@ var Eventified = class {
|
|
|
202
239
|
*/
|
|
203
240
|
emit(event, ...arguments_) {
|
|
204
241
|
let result = false;
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
242
|
+
const entry = this._eventListeners.get(event);
|
|
243
|
+
const argumentLength = arguments_.length;
|
|
244
|
+
if (entry !== void 0) {
|
|
245
|
+
if (typeof entry === "function") {
|
|
246
|
+
if (argumentLength === 1) {
|
|
247
|
+
entry(arguments_[0]);
|
|
248
|
+
} else if (argumentLength === 2) {
|
|
249
|
+
entry(arguments_[0], arguments_[1]);
|
|
250
|
+
} else {
|
|
251
|
+
entry(...arguments_);
|
|
252
|
+
}
|
|
253
|
+
} else {
|
|
254
|
+
const snapshot = [...entry];
|
|
255
|
+
for (let i = 0; i < snapshot.length; i++) {
|
|
256
|
+
if (argumentLength === 1) {
|
|
257
|
+
snapshot[i](arguments_[0]);
|
|
258
|
+
} else if (argumentLength === 2) {
|
|
259
|
+
snapshot[i](arguments_[0], arguments_[1]);
|
|
260
|
+
} else {
|
|
261
|
+
snapshot[i](...arguments_);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
210
264
|
}
|
|
265
|
+
result = true;
|
|
266
|
+
}
|
|
267
|
+
if (this._eventLogger) {
|
|
268
|
+
this.sendToEventLogger(event, arguments_);
|
|
211
269
|
}
|
|
212
|
-
if (event ===
|
|
270
|
+
if (event === ERROR_EVENT && !result) {
|
|
213
271
|
const error = arguments_[0] instanceof Error ? arguments_[0] : new Error(`${arguments_[0]}`);
|
|
214
|
-
if (this._throwOnEmitError
|
|
272
|
+
if (this._throwOnEmitError || this._throwOnEmptyListeners) {
|
|
215
273
|
throw error;
|
|
216
|
-
} else {
|
|
217
|
-
if (this.listeners(this._errorEvent).length === 0 && this._throwOnEmptyListeners === true) {
|
|
218
|
-
throw error;
|
|
219
|
-
}
|
|
220
274
|
}
|
|
221
275
|
}
|
|
222
|
-
this.sendLog(event, arguments_);
|
|
223
276
|
return result;
|
|
224
277
|
}
|
|
225
278
|
/**
|
|
@@ -228,7 +281,11 @@ var Eventified = class {
|
|
|
228
281
|
* @returns {EventListener[]} An array of listeners
|
|
229
282
|
*/
|
|
230
283
|
listeners(event) {
|
|
231
|
-
|
|
284
|
+
const entry = this._eventListeners.get(event);
|
|
285
|
+
if (entry === void 0) {
|
|
286
|
+
return [];
|
|
287
|
+
}
|
|
288
|
+
return typeof entry === "function" ? [entry] : entry;
|
|
232
289
|
}
|
|
233
290
|
/**
|
|
234
291
|
* Removes all listeners for a specific event. If no event is provided, it removes all listeners
|
|
@@ -249,21 +306,22 @@ var Eventified = class {
|
|
|
249
306
|
* @returns {void}
|
|
250
307
|
*/
|
|
251
308
|
setMaxListeners(n) {
|
|
252
|
-
this._maxListeners = n;
|
|
253
|
-
for (const listeners of this._eventListeners.values()) {
|
|
254
|
-
if (listeners.length > n) {
|
|
255
|
-
listeners.splice(n);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
309
|
+
this._maxListeners = n < 0 ? 0 : n;
|
|
258
310
|
}
|
|
259
311
|
/**
|
|
260
312
|
* Gets all listeners
|
|
261
313
|
* @returns {EventListener[]} An array of listeners
|
|
262
314
|
*/
|
|
263
315
|
getAllListeners() {
|
|
264
|
-
|
|
265
|
-
for (const
|
|
266
|
-
|
|
316
|
+
const result = [];
|
|
317
|
+
for (const entry of this._eventListeners.values()) {
|
|
318
|
+
if (typeof entry === "function") {
|
|
319
|
+
result.push(entry);
|
|
320
|
+
} else {
|
|
321
|
+
for (let i = 0; i < entry.length; i++) {
|
|
322
|
+
result.push(entry[i]);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
267
325
|
}
|
|
268
326
|
return result;
|
|
269
327
|
}
|
|
@@ -272,8 +330,8 @@ var Eventified = class {
|
|
|
272
330
|
* @param {string | symbol} eventName - The event name that determines the log level
|
|
273
331
|
* @param {unknown} data - The data to log
|
|
274
332
|
*/
|
|
275
|
-
|
|
276
|
-
if (!this.
|
|
333
|
+
sendToEventLogger(eventName, data) {
|
|
334
|
+
if (!this._eventLogger) {
|
|
277
335
|
return;
|
|
278
336
|
}
|
|
279
337
|
let message;
|
|
@@ -290,33 +348,101 @@ var Eventified = class {
|
|
|
290
348
|
}
|
|
291
349
|
switch (eventName) {
|
|
292
350
|
case "error": {
|
|
293
|
-
this.
|
|
351
|
+
this._eventLogger.error?.(message, { event: eventName, data });
|
|
294
352
|
break;
|
|
295
353
|
}
|
|
296
354
|
case "warn": {
|
|
297
|
-
this.
|
|
355
|
+
this._eventLogger.warn?.(message, { event: eventName, data });
|
|
298
356
|
break;
|
|
299
357
|
}
|
|
300
358
|
case "trace": {
|
|
301
|
-
this.
|
|
359
|
+
this._eventLogger.trace?.(message, { event: eventName, data });
|
|
302
360
|
break;
|
|
303
361
|
}
|
|
304
362
|
case "debug": {
|
|
305
|
-
this.
|
|
363
|
+
this._eventLogger.debug?.(message, { event: eventName, data });
|
|
306
364
|
break;
|
|
307
365
|
}
|
|
308
366
|
case "fatal": {
|
|
309
|
-
this.
|
|
367
|
+
this._eventLogger.fatal?.(message, { event: eventName, data });
|
|
310
368
|
break;
|
|
311
369
|
}
|
|
312
370
|
default: {
|
|
313
|
-
this.
|
|
371
|
+
this._eventLogger.info?.(message, { event: eventName, data });
|
|
314
372
|
break;
|
|
315
373
|
}
|
|
316
374
|
}
|
|
317
375
|
}
|
|
318
376
|
};
|
|
319
377
|
|
|
378
|
+
// src/hooks/hook.ts
|
|
379
|
+
var Hook = class {
|
|
380
|
+
id;
|
|
381
|
+
event;
|
|
382
|
+
handler;
|
|
383
|
+
/**
|
|
384
|
+
* Creates a new Hook instance
|
|
385
|
+
* @param {string} event - The event name for the hook
|
|
386
|
+
* @param {HookFn} handler - The handler function for the hook
|
|
387
|
+
* @param {string} [id] - Optional unique identifier for the hook
|
|
388
|
+
*/
|
|
389
|
+
constructor(event, handler, id) {
|
|
390
|
+
this.id = id;
|
|
391
|
+
this.event = event;
|
|
392
|
+
this.handler = handler;
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
// src/hooks/waterfall-hook.ts
|
|
397
|
+
var WaterfallHook = class {
|
|
398
|
+
id;
|
|
399
|
+
event;
|
|
400
|
+
handler;
|
|
401
|
+
hooks;
|
|
402
|
+
_finalHandler;
|
|
403
|
+
/**
|
|
404
|
+
* Creates a new WaterfallHook instance
|
|
405
|
+
* @param {string} event - The event name for the hook
|
|
406
|
+
* @param {WaterfallHookFn} finalHandler - The final handler function that receives the transformed result
|
|
407
|
+
* @param {string} [id] - Optional unique identifier for the hook
|
|
408
|
+
*/
|
|
409
|
+
constructor(event, finalHandler, id) {
|
|
410
|
+
this.id = id;
|
|
411
|
+
this.event = event;
|
|
412
|
+
this.hooks = [];
|
|
413
|
+
this._finalHandler = finalHandler;
|
|
414
|
+
this.handler = async (...arguments_) => {
|
|
415
|
+
const initialArgs = arguments_.length === 1 ? arguments_[0] : arguments_;
|
|
416
|
+
const results = [];
|
|
417
|
+
for (const hook of this.hooks) {
|
|
418
|
+
const result = await hook({ initialArgs, results: [...results] });
|
|
419
|
+
results.push({ hook, result });
|
|
420
|
+
}
|
|
421
|
+
await this._finalHandler({ initialArgs, results: [...results] });
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Adds a hook function to the end of the waterfall chain
|
|
426
|
+
* @param {WaterfallHookFn} hook - The hook function to add
|
|
427
|
+
*/
|
|
428
|
+
addHook(hook) {
|
|
429
|
+
this.hooks.push(hook);
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Removes a specific hook function from the waterfall chain
|
|
433
|
+
* @param {WaterfallHookFn} hook - The hook function to remove
|
|
434
|
+
* @returns {boolean} true if the hook was found and removed
|
|
435
|
+
*/
|
|
436
|
+
removeHook(hook) {
|
|
437
|
+
const index = this.hooks.indexOf(hook);
|
|
438
|
+
if (index !== -1) {
|
|
439
|
+
this.hooks.splice(index, 1);
|
|
440
|
+
return true;
|
|
441
|
+
}
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
|
|
320
446
|
// src/index.ts
|
|
321
447
|
var Hookified = class extends Eventified {
|
|
322
448
|
_hooks;
|
|
@@ -324,9 +450,10 @@ var Hookified = class extends Eventified {
|
|
|
324
450
|
_enforceBeforeAfter = false;
|
|
325
451
|
_deprecatedHooks;
|
|
326
452
|
_allowDeprecated = true;
|
|
453
|
+
_useHookClone = true;
|
|
327
454
|
constructor(options) {
|
|
328
455
|
super({
|
|
329
|
-
|
|
456
|
+
eventLogger: options?.eventLogger,
|
|
330
457
|
throwOnEmitError: options?.throwOnEmitError,
|
|
331
458
|
throwOnEmptyListeners: options?.throwOnEmptyListeners
|
|
332
459
|
});
|
|
@@ -334,8 +461,6 @@ var Hookified = class extends Eventified {
|
|
|
334
461
|
this._deprecatedHooks = options?.deprecatedHooks ? new Map(options.deprecatedHooks) : /* @__PURE__ */ new Map();
|
|
335
462
|
if (options?.throwOnHookError !== void 0) {
|
|
336
463
|
this._throwOnHookError = options.throwOnHookError;
|
|
337
|
-
} else if (options?.throwHookErrors !== void 0) {
|
|
338
|
-
this._throwOnHookError = options.throwHookErrors;
|
|
339
464
|
}
|
|
340
465
|
if (options?.enforceBeforeAfter !== void 0) {
|
|
341
466
|
this._enforceBeforeAfter = options.enforceBeforeAfter;
|
|
@@ -343,30 +468,17 @@ var Hookified = class extends Eventified {
|
|
|
343
468
|
if (options?.allowDeprecated !== void 0) {
|
|
344
469
|
this._allowDeprecated = options.allowDeprecated;
|
|
345
470
|
}
|
|
471
|
+
if (options?.useHookClone !== void 0) {
|
|
472
|
+
this._useHookClone = options.useHookClone;
|
|
473
|
+
}
|
|
346
474
|
}
|
|
347
475
|
/**
|
|
348
476
|
* Gets all hooks
|
|
349
|
-
* @returns {Map<string,
|
|
477
|
+
* @returns {Map<string, IHook[]>}
|
|
350
478
|
*/
|
|
351
479
|
get hooks() {
|
|
352
480
|
return this._hooks;
|
|
353
481
|
}
|
|
354
|
-
/**
|
|
355
|
-
* Gets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.
|
|
356
|
-
* @returns {boolean}
|
|
357
|
-
* @deprecated - this will be deprecated in version 2. Please use throwOnHookError.
|
|
358
|
-
*/
|
|
359
|
-
get throwHookErrors() {
|
|
360
|
-
return this._throwOnHookError;
|
|
361
|
-
}
|
|
362
|
-
/**
|
|
363
|
-
* Sets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.
|
|
364
|
-
* @param {boolean} value
|
|
365
|
-
* @deprecated - this will be deprecated in version 2. Please use throwOnHookError.
|
|
366
|
-
*/
|
|
367
|
-
set throwHookErrors(value) {
|
|
368
|
-
this._throwOnHookError = value;
|
|
369
|
-
}
|
|
370
482
|
/**
|
|
371
483
|
* Gets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.
|
|
372
484
|
* @returns {boolean}
|
|
@@ -425,157 +537,153 @@ var Hookified = class extends Eventified {
|
|
|
425
537
|
this._allowDeprecated = value;
|
|
426
538
|
}
|
|
427
539
|
/**
|
|
428
|
-
*
|
|
429
|
-
* @
|
|
430
|
-
* @throws {Error} If enforceBeforeAfter is true and event doesn't start with 'before' or 'after'
|
|
431
|
-
*/
|
|
432
|
-
validateHookName(event) {
|
|
433
|
-
if (this._enforceBeforeAfter) {
|
|
434
|
-
const eventValue = event.trim().toLocaleLowerCase();
|
|
435
|
-
if (!eventValue.startsWith("before") && !eventValue.startsWith("after")) {
|
|
436
|
-
throw new Error(
|
|
437
|
-
`Hook event "${event}" must start with "before" or "after" when enforceBeforeAfter is enabled`
|
|
438
|
-
);
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
/**
|
|
443
|
-
* Checks if a hook is deprecated and emits a warning if it is
|
|
444
|
-
* @param {string} event - The event name to check
|
|
445
|
-
* @returns {boolean} - Returns true if the hook should proceed, false if it should be blocked
|
|
540
|
+
* Gets whether hook objects are cloned before storing. Default is true.
|
|
541
|
+
* @returns {boolean}
|
|
446
542
|
*/
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
const message = this._deprecatedHooks.get(event);
|
|
450
|
-
const warningMessage = `Hook "${event}" is deprecated${message ? `: ${message}` : ""}`;
|
|
451
|
-
this.emit("warn", { hook: event, message: warningMessage });
|
|
452
|
-
return this._allowDeprecated;
|
|
453
|
-
}
|
|
454
|
-
return true;
|
|
543
|
+
get useHookClone() {
|
|
544
|
+
return this._useHookClone;
|
|
455
545
|
}
|
|
456
546
|
/**
|
|
457
|
-
*
|
|
458
|
-
*
|
|
459
|
-
* @param {
|
|
460
|
-
* @returns {void}
|
|
547
|
+
* Sets whether hook objects are cloned before storing. Default is true.
|
|
548
|
+
* When false, the original IHook reference is stored directly.
|
|
549
|
+
* @param {boolean} value
|
|
461
550
|
*/
|
|
462
|
-
|
|
463
|
-
this.
|
|
551
|
+
set useHookClone(value) {
|
|
552
|
+
this._useHookClone = value;
|
|
464
553
|
}
|
|
465
554
|
/**
|
|
466
|
-
* Adds a handler function for a specific event
|
|
467
|
-
* @
|
|
468
|
-
* @
|
|
555
|
+
* Adds a handler function for a specific event.
|
|
556
|
+
* If you prefer the legacy `(event, handler)` signature, use {@link addHook} instead.
|
|
557
|
+
* To register multiple hooks at once, use {@link onHooks}.
|
|
558
|
+
* @param {IHook} hook - the hook containing event name and handler
|
|
559
|
+
* @param {OnHookOptions} [options] - optional per-call options (e.g., useHookClone override, position)
|
|
560
|
+
* @returns {IHook | undefined} the stored hook, or undefined if blocked by deprecation
|
|
469
561
|
*/
|
|
470
|
-
|
|
471
|
-
this.validateHookName(
|
|
472
|
-
if (!this.checkDeprecatedHook(
|
|
473
|
-
return;
|
|
562
|
+
onHook(hook, options) {
|
|
563
|
+
this.validateHookName(hook.event);
|
|
564
|
+
if (!this.checkDeprecatedHook(hook.event)) {
|
|
565
|
+
return void 0;
|
|
474
566
|
}
|
|
475
|
-
const
|
|
567
|
+
const shouldClone = options?.useHookClone ?? this._useHookClone;
|
|
568
|
+
const entry = shouldClone ? { id: hook.id, event: hook.event, handler: hook.handler } : hook;
|
|
569
|
+
entry.id = entry.id ?? crypto.randomUUID();
|
|
570
|
+
const eventHandlers = this._hooks.get(hook.event);
|
|
476
571
|
if (eventHandlers) {
|
|
477
|
-
eventHandlers.
|
|
572
|
+
const existingIndex = eventHandlers.findIndex((h) => h.id === entry.id);
|
|
573
|
+
if (existingIndex !== -1) {
|
|
574
|
+
eventHandlers[existingIndex] = entry;
|
|
575
|
+
} else {
|
|
576
|
+
const position = options?.position ?? "Bottom";
|
|
577
|
+
if (position === "Top") {
|
|
578
|
+
eventHandlers.unshift(entry);
|
|
579
|
+
} else if (position === "Bottom") {
|
|
580
|
+
eventHandlers.push(entry);
|
|
581
|
+
} else {
|
|
582
|
+
const index = Math.max(0, Math.min(position, eventHandlers.length));
|
|
583
|
+
eventHandlers.splice(index, 0, entry);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
478
586
|
} else {
|
|
479
|
-
this._hooks.set(
|
|
587
|
+
this._hooks.set(hook.event, [entry]);
|
|
480
588
|
}
|
|
589
|
+
return entry;
|
|
481
590
|
}
|
|
482
591
|
/**
|
|
483
592
|
* Alias for onHook. This is provided for compatibility with other libraries that use the `addHook` method.
|
|
484
|
-
* @param {string} event
|
|
485
|
-
* @param {
|
|
593
|
+
* @param {string} event - the event name
|
|
594
|
+
* @param {HookFn} handler - the handler function
|
|
486
595
|
* @returns {void}
|
|
487
596
|
*/
|
|
488
597
|
addHook(event, handler) {
|
|
489
|
-
this.
|
|
598
|
+
this.onHook({ event, handler });
|
|
490
599
|
}
|
|
491
600
|
/**
|
|
492
|
-
* Adds
|
|
493
|
-
* @param {Array<
|
|
601
|
+
* Adds handler functions for specific events
|
|
602
|
+
* @param {Array<IHook>} hooks
|
|
603
|
+
* @param {OnHookOptions} [options] - optional per-call options (e.g., useHookClone override, position)
|
|
494
604
|
* @returns {void}
|
|
495
605
|
*/
|
|
496
|
-
onHooks(hooks) {
|
|
606
|
+
onHooks(hooks, options) {
|
|
497
607
|
for (const hook of hooks) {
|
|
498
|
-
this.onHook(hook
|
|
608
|
+
this.onHook(hook, options);
|
|
499
609
|
}
|
|
500
610
|
}
|
|
501
611
|
/**
|
|
502
|
-
* Adds a handler function for a specific event that runs before all other handlers
|
|
503
|
-
*
|
|
504
|
-
* @param {
|
|
505
|
-
* @
|
|
612
|
+
* Adds a handler function for a specific event that runs before all other handlers.
|
|
613
|
+
* Equivalent to calling `onHook(hook, { position: "Top" })`.
|
|
614
|
+
* @param {IHook} hook - the hook containing event name and handler
|
|
615
|
+
* @param {PrependHookOptions} [options] - optional per-call options (e.g., useHookClone override)
|
|
616
|
+
* @returns {IHook | undefined} the stored hook, or undefined if blocked by deprecation
|
|
506
617
|
*/
|
|
507
|
-
prependHook(
|
|
508
|
-
this.
|
|
509
|
-
if (!this.checkDeprecatedHook(event)) {
|
|
510
|
-
return;
|
|
511
|
-
}
|
|
512
|
-
const eventHandlers = this._hooks.get(event);
|
|
513
|
-
if (eventHandlers) {
|
|
514
|
-
eventHandlers.unshift(handler);
|
|
515
|
-
} else {
|
|
516
|
-
this._hooks.set(event, [handler]);
|
|
517
|
-
}
|
|
618
|
+
prependHook(hook, options) {
|
|
619
|
+
return this.onHook(hook, { ...options, position: "Top" });
|
|
518
620
|
}
|
|
519
621
|
/**
|
|
520
|
-
* Adds a handler that only executes once for a specific event before all other handlers
|
|
521
|
-
*
|
|
522
|
-
* @param handler
|
|
622
|
+
* Adds a handler that only executes once for a specific event before all other handlers.
|
|
623
|
+
* Equivalent to calling `onHook` with a self-removing wrapper and `{ position: "Top" }`.
|
|
624
|
+
* @param {IHook} hook - the hook containing event name and handler
|
|
625
|
+
* @param {PrependHookOptions} [options] - optional per-call options (e.g., useHookClone override)
|
|
626
|
+
* @returns {IHook | undefined} the stored hook, or undefined if blocked by deprecation
|
|
523
627
|
*/
|
|
524
|
-
prependOnceHook(
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
return;
|
|
528
|
-
}
|
|
529
|
-
const hook = async (...arguments_) => {
|
|
530
|
-
this.removeHook(event, hook);
|
|
531
|
-
return handler(...arguments_);
|
|
628
|
+
prependOnceHook(hook, options) {
|
|
629
|
+
const wrappedHandler = async (...arguments_) => {
|
|
630
|
+
this.removeHook({ event: hook.event, handler: wrappedHandler });
|
|
631
|
+
return hook.handler(...arguments_);
|
|
532
632
|
};
|
|
533
|
-
this.
|
|
633
|
+
return this.onHook(
|
|
634
|
+
{ id: hook.id, event: hook.event, handler: wrappedHandler },
|
|
635
|
+
{ ...options, position: "Top" }
|
|
636
|
+
);
|
|
534
637
|
}
|
|
535
638
|
/**
|
|
536
639
|
* Adds a handler that only executes once for a specific event
|
|
537
|
-
* @param event
|
|
538
|
-
* @param handler
|
|
640
|
+
* @param {IHook} hook - the hook containing event name and handler
|
|
539
641
|
*/
|
|
540
|
-
onceHook(
|
|
541
|
-
this.validateHookName(event);
|
|
542
|
-
if (!this.checkDeprecatedHook(event)) {
|
|
642
|
+
onceHook(hook) {
|
|
643
|
+
this.validateHookName(hook.event);
|
|
644
|
+
if (!this.checkDeprecatedHook(hook.event)) {
|
|
543
645
|
return;
|
|
544
646
|
}
|
|
545
|
-
const
|
|
546
|
-
this.removeHook(event,
|
|
547
|
-
return handler(...arguments_);
|
|
647
|
+
const wrappedHandler = async (...arguments_) => {
|
|
648
|
+
this.removeHook({ event: hook.event, handler: wrappedHandler });
|
|
649
|
+
return hook.handler(...arguments_);
|
|
548
650
|
};
|
|
549
|
-
this.onHook(event,
|
|
651
|
+
this.onHook({ id: hook.id, event: hook.event, handler: wrappedHandler });
|
|
550
652
|
}
|
|
551
653
|
/**
|
|
552
654
|
* Removes a handler function for a specific event
|
|
553
|
-
* @param {
|
|
554
|
-
* @
|
|
555
|
-
* @returns {void}
|
|
655
|
+
* @param {IHook} hook - the hook containing event name and handler to remove
|
|
656
|
+
* @returns {IHook | undefined} the removed hook, or undefined if not found
|
|
556
657
|
*/
|
|
557
|
-
removeHook(
|
|
558
|
-
this.validateHookName(event);
|
|
559
|
-
|
|
560
|
-
return;
|
|
561
|
-
}
|
|
562
|
-
const eventHandlers = this._hooks.get(event);
|
|
658
|
+
removeHook(hook) {
|
|
659
|
+
this.validateHookName(hook.event);
|
|
660
|
+
const eventHandlers = this._hooks.get(hook.event);
|
|
563
661
|
if (eventHandlers) {
|
|
564
|
-
const index = eventHandlers.
|
|
662
|
+
const index = eventHandlers.findIndex((h) => h.handler === hook.handler);
|
|
565
663
|
if (index !== -1) {
|
|
566
664
|
eventHandlers.splice(index, 1);
|
|
665
|
+
if (eventHandlers.length === 0) {
|
|
666
|
+
this._hooks.delete(hook.event);
|
|
667
|
+
}
|
|
668
|
+
return { event: hook.event, handler: hook.handler };
|
|
567
669
|
}
|
|
568
670
|
}
|
|
671
|
+
return void 0;
|
|
569
672
|
}
|
|
570
673
|
/**
|
|
571
|
-
* Removes
|
|
572
|
-
* @param {Array<
|
|
573
|
-
* @returns {
|
|
674
|
+
* Removes multiple hook handlers
|
|
675
|
+
* @param {Array<IHook>} hooks
|
|
676
|
+
* @returns {IHook[]} the hooks that were successfully removed
|
|
574
677
|
*/
|
|
575
678
|
removeHooks(hooks) {
|
|
679
|
+
const removed = [];
|
|
576
680
|
for (const hook of hooks) {
|
|
577
|
-
this.removeHook(hook
|
|
681
|
+
const result = this.removeHook(hook);
|
|
682
|
+
if (result) {
|
|
683
|
+
removed.push(result);
|
|
684
|
+
}
|
|
578
685
|
}
|
|
686
|
+
return removed;
|
|
579
687
|
}
|
|
580
688
|
/**
|
|
581
689
|
* Calls all handlers for a specific event
|
|
@@ -590,9 +698,9 @@ var Hookified = class extends Eventified {
|
|
|
590
698
|
}
|
|
591
699
|
const eventHandlers = this._hooks.get(event);
|
|
592
700
|
if (eventHandlers) {
|
|
593
|
-
for (const
|
|
701
|
+
for (const hook of [...eventHandlers]) {
|
|
594
702
|
try {
|
|
595
|
-
await handler(...arguments_);
|
|
703
|
+
await hook.handler(...arguments_);
|
|
596
704
|
} catch (error) {
|
|
597
705
|
const message = `${event}: ${error.message}`;
|
|
598
706
|
this.emit("error", new Error(message));
|
|
@@ -620,12 +728,12 @@ var Hookified = class extends Eventified {
|
|
|
620
728
|
}
|
|
621
729
|
const eventHandlers = this._hooks.get(event);
|
|
622
730
|
if (eventHandlers) {
|
|
623
|
-
for (const
|
|
624
|
-
if (handler.constructor.name === "AsyncFunction") {
|
|
731
|
+
for (const hook of [...eventHandlers]) {
|
|
732
|
+
if (hook.handler.constructor.name === "AsyncFunction") {
|
|
625
733
|
continue;
|
|
626
734
|
}
|
|
627
735
|
try {
|
|
628
|
-
handler(...arguments_);
|
|
736
|
+
hook.handler(...arguments_);
|
|
629
737
|
} catch (error) {
|
|
630
738
|
const message = `${event}: ${error.message}`;
|
|
631
739
|
this.emit("error", new Error(message));
|
|
@@ -665,15 +773,54 @@ var Hookified = class extends Eventified {
|
|
|
665
773
|
/**
|
|
666
774
|
* Gets all hooks for a specific event
|
|
667
775
|
* @param {string} event
|
|
668
|
-
* @returns {
|
|
776
|
+
* @returns {IHook[]}
|
|
669
777
|
*/
|
|
670
778
|
getHooks(event) {
|
|
671
779
|
this.validateHookName(event);
|
|
672
|
-
if (!this.checkDeprecatedHook(event)) {
|
|
673
|
-
return void 0;
|
|
674
|
-
}
|
|
675
780
|
return this._hooks.get(event);
|
|
676
781
|
}
|
|
782
|
+
/**
|
|
783
|
+
* Gets a specific hook by id, searching across all events
|
|
784
|
+
* @param {string} id - the hook id
|
|
785
|
+
* @returns {IHook | undefined} the hook if found, or undefined
|
|
786
|
+
*/
|
|
787
|
+
getHook(id) {
|
|
788
|
+
for (const eventHandlers of this._hooks.values()) {
|
|
789
|
+
const found = eventHandlers.find((h) => h.id === id);
|
|
790
|
+
if (found) {
|
|
791
|
+
return found;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
return void 0;
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Removes one or more hooks by id, searching across all events
|
|
798
|
+
* @param {string | string[]} id - the hook id or array of hook ids to remove
|
|
799
|
+
* @returns {IHook | IHook[] | undefined} the removed hook(s), or undefined/empty array if not found
|
|
800
|
+
*/
|
|
801
|
+
removeHookById(id) {
|
|
802
|
+
if (Array.isArray(id)) {
|
|
803
|
+
const removed = [];
|
|
804
|
+
for (const singleId of id) {
|
|
805
|
+
const result = this.removeHookById(singleId);
|
|
806
|
+
if (result && !Array.isArray(result)) {
|
|
807
|
+
removed.push(result);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
return removed;
|
|
811
|
+
}
|
|
812
|
+
for (const [event, eventHandlers] of this._hooks.entries()) {
|
|
813
|
+
const index = eventHandlers.findIndex((h) => h.id === id);
|
|
814
|
+
if (index !== -1) {
|
|
815
|
+
const [removed] = eventHandlers.splice(index, 1);
|
|
816
|
+
if (eventHandlers.length === 0) {
|
|
817
|
+
this._hooks.delete(event);
|
|
818
|
+
}
|
|
819
|
+
return removed;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
return void 0;
|
|
823
|
+
}
|
|
677
824
|
/**
|
|
678
825
|
* Removes all hooks
|
|
679
826
|
* @returns {void}
|
|
@@ -681,9 +828,57 @@ var Hookified = class extends Eventified {
|
|
|
681
828
|
clearHooks() {
|
|
682
829
|
this._hooks.clear();
|
|
683
830
|
}
|
|
831
|
+
/**
|
|
832
|
+
* Removes all hooks for a specific event and returns the removed hooks.
|
|
833
|
+
* @param {string} event - The event name to remove hooks for.
|
|
834
|
+
* @returns {IHook[]} the hooks that were removed
|
|
835
|
+
*/
|
|
836
|
+
removeEventHooks(event) {
|
|
837
|
+
this.validateHookName(event);
|
|
838
|
+
const eventHandlers = this._hooks.get(event);
|
|
839
|
+
if (eventHandlers) {
|
|
840
|
+
const removed = [...eventHandlers];
|
|
841
|
+
this._hooks.delete(event);
|
|
842
|
+
return removed;
|
|
843
|
+
}
|
|
844
|
+
return [];
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Validates hook event name if enforceBeforeAfter is enabled
|
|
848
|
+
* @param {string} event - The event name to validate
|
|
849
|
+
* @throws {Error} If enforceBeforeAfter is true and event doesn't start with 'before' or 'after'
|
|
850
|
+
*/
|
|
851
|
+
validateHookName(event) {
|
|
852
|
+
if (this._enforceBeforeAfter) {
|
|
853
|
+
const eventValue = event.trim().toLocaleLowerCase();
|
|
854
|
+
if (!eventValue.startsWith("before") && !eventValue.startsWith("after")) {
|
|
855
|
+
throw new Error(
|
|
856
|
+
`Hook event "${event}" must start with "before" or "after" when enforceBeforeAfter is enabled`
|
|
857
|
+
);
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
/**
|
|
862
|
+
* Checks if a hook is deprecated and emits a warning if it is
|
|
863
|
+
* @param {string} event - The event name to check
|
|
864
|
+
* @returns {boolean} - Returns true if the hook should proceed, false if it should be blocked
|
|
865
|
+
*/
|
|
866
|
+
checkDeprecatedHook(event) {
|
|
867
|
+
if (this._deprecatedHooks.has(event)) {
|
|
868
|
+
const message = this._deprecatedHooks.get(event);
|
|
869
|
+
const warningMessage = `Hook "${event}" is deprecated${message ? `: ${message}` : ""}`;
|
|
870
|
+
this.emit("warn", { hook: event, message: warningMessage });
|
|
871
|
+
return this._allowDeprecated;
|
|
872
|
+
}
|
|
873
|
+
return true;
|
|
874
|
+
}
|
|
684
875
|
};
|
|
685
876
|
export {
|
|
686
877
|
Eventified,
|
|
687
|
-
|
|
878
|
+
Hook,
|
|
879
|
+
Hookified,
|
|
880
|
+
WaterfallHook
|
|
688
881
|
};
|
|
882
|
+
/* v8 ignore start -- @preserve: single-element arrays are stored as functions */
|
|
883
|
+
/* v8 ignore next 3 -- @preserve: guarded by caller */
|
|
689
884
|
/* v8 ignore next -- @preserve */
|