hookified 1.15.0 → 2.0.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 +958 -707
- package/dist/browser/index.global.js +283 -155
- package/dist/browser/index.global.js.map +1 -1
- package/dist/browser/index.js +286 -156
- package/dist/browser/index.js.map +1 -1
- package/dist/node/index.cjs +289 -157
- package/dist/node/index.d.cts +206 -79
- package/dist/node/index.d.ts +206 -79
- package/dist/node/index.js +286 -156
- package/package.json +10 -9
|
@@ -9,13 +9,13 @@
|
|
|
9
9
|
constructor(options) {
|
|
10
10
|
__publicField(this, "_eventListeners");
|
|
11
11
|
__publicField(this, "_maxListeners");
|
|
12
|
-
__publicField(this, "
|
|
12
|
+
__publicField(this, "_eventLogger");
|
|
13
13
|
__publicField(this, "_throwOnEmitError", false);
|
|
14
|
-
__publicField(this, "_throwOnEmptyListeners",
|
|
14
|
+
__publicField(this, "_throwOnEmptyListeners", true);
|
|
15
15
|
__publicField(this, "_errorEvent", "error");
|
|
16
16
|
this._eventListeners = /* @__PURE__ */ new Map();
|
|
17
|
-
this._maxListeners =
|
|
18
|
-
this.
|
|
17
|
+
this._maxListeners = 0;
|
|
18
|
+
this._eventLogger = options?.eventLogger;
|
|
19
19
|
if (options?.throwOnEmitError !== void 0) {
|
|
20
20
|
this._throwOnEmitError = options.throwOnEmitError;
|
|
21
21
|
}
|
|
@@ -24,18 +24,18 @@
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
|
-
* Gets the logger
|
|
27
|
+
* Gets the event logger
|
|
28
28
|
* @returns {Logger}
|
|
29
29
|
*/
|
|
30
|
-
get
|
|
31
|
-
return this.
|
|
30
|
+
get eventLogger() {
|
|
31
|
+
return this._eventLogger;
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
|
-
* Sets the logger
|
|
35
|
-
* @param {Logger}
|
|
34
|
+
* Sets the event logger
|
|
35
|
+
* @param {Logger} eventLogger
|
|
36
36
|
*/
|
|
37
|
-
set
|
|
38
|
-
this.
|
|
37
|
+
set eventLogger(eventLogger) {
|
|
38
|
+
this._eventLogger = eventLogger;
|
|
39
39
|
}
|
|
40
40
|
/**
|
|
41
41
|
* Gets whether an error should be thrown when an emit throws an error. Default is false and only emits an error event.
|
|
@@ -164,7 +164,7 @@
|
|
|
164
164
|
}
|
|
165
165
|
const listeners = this._eventListeners.get(event);
|
|
166
166
|
if (listeners) {
|
|
167
|
-
if (listeners.length >= this._maxListeners) {
|
|
167
|
+
if (this._maxListeners > 0 && listeners.length >= this._maxListeners) {
|
|
168
168
|
console.warn(
|
|
169
169
|
`MaxListenersExceededWarning: Possible event memory leak detected. ${listeners.length + 1} ${event} listeners added. Use setMaxListeners() to increase limit.`
|
|
170
170
|
);
|
|
@@ -215,6 +215,7 @@
|
|
|
215
215
|
result = true;
|
|
216
216
|
}
|
|
217
217
|
}
|
|
218
|
+
this.sendToEventLogger(event, arguments_);
|
|
218
219
|
if (event === this._errorEvent) {
|
|
219
220
|
const error = arguments_[0] instanceof Error ? arguments_[0] : new Error(`${arguments_[0]}`);
|
|
220
221
|
if (this._throwOnEmitError && !result) {
|
|
@@ -225,7 +226,6 @@
|
|
|
225
226
|
}
|
|
226
227
|
}
|
|
227
228
|
}
|
|
228
|
-
this.sendLog(event, arguments_);
|
|
229
229
|
return result;
|
|
230
230
|
}
|
|
231
231
|
/**
|
|
@@ -255,12 +255,7 @@
|
|
|
255
255
|
* @returns {void}
|
|
256
256
|
*/
|
|
257
257
|
setMaxListeners(n) {
|
|
258
|
-
this._maxListeners = n;
|
|
259
|
-
for (const listeners of this._eventListeners.values()) {
|
|
260
|
-
if (listeners.length > n) {
|
|
261
|
-
listeners.splice(n);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
258
|
+
this._maxListeners = n < 0 ? 0 : n;
|
|
264
259
|
}
|
|
265
260
|
/**
|
|
266
261
|
* Gets all listeners
|
|
@@ -278,8 +273,8 @@
|
|
|
278
273
|
* @param {string | symbol} eventName - The event name that determines the log level
|
|
279
274
|
* @param {unknown} data - The data to log
|
|
280
275
|
*/
|
|
281
|
-
|
|
282
|
-
if (!this.
|
|
276
|
+
sendToEventLogger(eventName, data) {
|
|
277
|
+
if (!this._eventLogger) {
|
|
283
278
|
return;
|
|
284
279
|
}
|
|
285
280
|
let message;
|
|
@@ -296,38 +291,106 @@
|
|
|
296
291
|
}
|
|
297
292
|
switch (eventName) {
|
|
298
293
|
case "error": {
|
|
299
|
-
this.
|
|
294
|
+
this._eventLogger.error?.(message, { event: eventName, data });
|
|
300
295
|
break;
|
|
301
296
|
}
|
|
302
297
|
case "warn": {
|
|
303
|
-
this.
|
|
298
|
+
this._eventLogger.warn?.(message, { event: eventName, data });
|
|
304
299
|
break;
|
|
305
300
|
}
|
|
306
301
|
case "trace": {
|
|
307
|
-
this.
|
|
302
|
+
this._eventLogger.trace?.(message, { event: eventName, data });
|
|
308
303
|
break;
|
|
309
304
|
}
|
|
310
305
|
case "debug": {
|
|
311
|
-
this.
|
|
306
|
+
this._eventLogger.debug?.(message, { event: eventName, data });
|
|
312
307
|
break;
|
|
313
308
|
}
|
|
314
309
|
case "fatal": {
|
|
315
|
-
this.
|
|
310
|
+
this._eventLogger.fatal?.(message, { event: eventName, data });
|
|
316
311
|
break;
|
|
317
312
|
}
|
|
318
313
|
default: {
|
|
319
|
-
this.
|
|
314
|
+
this._eventLogger.info?.(message, { event: eventName, data });
|
|
320
315
|
break;
|
|
321
316
|
}
|
|
322
317
|
}
|
|
323
318
|
}
|
|
324
319
|
};
|
|
325
320
|
|
|
321
|
+
// src/hooks/hook.ts
|
|
322
|
+
var Hook = class {
|
|
323
|
+
/**
|
|
324
|
+
* Creates a new Hook instance
|
|
325
|
+
* @param {string} event - The event name for the hook
|
|
326
|
+
* @param {HookFn} handler - The handler function for the hook
|
|
327
|
+
* @param {string} [id] - Optional unique identifier for the hook
|
|
328
|
+
*/
|
|
329
|
+
constructor(event, handler, id) {
|
|
330
|
+
__publicField(this, "id");
|
|
331
|
+
__publicField(this, "event");
|
|
332
|
+
__publicField(this, "handler");
|
|
333
|
+
this.id = id;
|
|
334
|
+
this.event = event;
|
|
335
|
+
this.handler = handler;
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
// src/hooks/waterfall-hook.ts
|
|
340
|
+
var WaterfallHook = class {
|
|
341
|
+
/**
|
|
342
|
+
* Creates a new WaterfallHook instance
|
|
343
|
+
* @param {string} event - The event name for the hook
|
|
344
|
+
* @param {WaterfallHookFn} finalHandler - The final handler function that receives the transformed result
|
|
345
|
+
* @param {string} [id] - Optional unique identifier for the hook
|
|
346
|
+
*/
|
|
347
|
+
constructor(event, finalHandler, id) {
|
|
348
|
+
__publicField(this, "id");
|
|
349
|
+
__publicField(this, "event");
|
|
350
|
+
__publicField(this, "handler");
|
|
351
|
+
__publicField(this, "hooks");
|
|
352
|
+
__publicField(this, "_finalHandler");
|
|
353
|
+
this.id = id;
|
|
354
|
+
this.event = event;
|
|
355
|
+
this.hooks = [];
|
|
356
|
+
this._finalHandler = finalHandler;
|
|
357
|
+
this.handler = async (...arguments_) => {
|
|
358
|
+
const initialArgs = arguments_.length === 1 ? arguments_[0] : arguments_;
|
|
359
|
+
const results = [];
|
|
360
|
+
for (const hook of this.hooks) {
|
|
361
|
+
const result = await hook({ initialArgs, results: [...results] });
|
|
362
|
+
results.push({ hook, result });
|
|
363
|
+
}
|
|
364
|
+
await this._finalHandler({ initialArgs, results: [...results] });
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Adds a hook function to the end of the waterfall chain
|
|
369
|
+
* @param {WaterfallHookFn} hook - The hook function to add
|
|
370
|
+
*/
|
|
371
|
+
addHook(hook) {
|
|
372
|
+
this.hooks.push(hook);
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Removes a specific hook function from the waterfall chain
|
|
376
|
+
* @param {WaterfallHookFn} hook - The hook function to remove
|
|
377
|
+
* @returns {boolean} true if the hook was found and removed
|
|
378
|
+
*/
|
|
379
|
+
removeHook(hook) {
|
|
380
|
+
const index = this.hooks.indexOf(hook);
|
|
381
|
+
if (index !== -1) {
|
|
382
|
+
this.hooks.splice(index, 1);
|
|
383
|
+
return true;
|
|
384
|
+
}
|
|
385
|
+
return false;
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
|
|
326
389
|
// src/index.ts
|
|
327
390
|
var Hookified = class extends Eventified {
|
|
328
391
|
constructor(options) {
|
|
329
392
|
super({
|
|
330
|
-
|
|
393
|
+
eventLogger: options?.eventLogger,
|
|
331
394
|
throwOnEmitError: options?.throwOnEmitError,
|
|
332
395
|
throwOnEmptyListeners: options?.throwOnEmptyListeners
|
|
333
396
|
});
|
|
@@ -336,12 +399,11 @@
|
|
|
336
399
|
__publicField(this, "_enforceBeforeAfter", false);
|
|
337
400
|
__publicField(this, "_deprecatedHooks");
|
|
338
401
|
__publicField(this, "_allowDeprecated", true);
|
|
402
|
+
__publicField(this, "_useHookClone", true);
|
|
339
403
|
this._hooks = /* @__PURE__ */ new Map();
|
|
340
404
|
this._deprecatedHooks = options?.deprecatedHooks ? new Map(options.deprecatedHooks) : /* @__PURE__ */ new Map();
|
|
341
405
|
if (options?.throwOnHookError !== void 0) {
|
|
342
406
|
this._throwOnHookError = options.throwOnHookError;
|
|
343
|
-
} else if (options?.throwHookErrors !== void 0) {
|
|
344
|
-
this._throwOnHookError = options.throwHookErrors;
|
|
345
407
|
}
|
|
346
408
|
if (options?.enforceBeforeAfter !== void 0) {
|
|
347
409
|
this._enforceBeforeAfter = options.enforceBeforeAfter;
|
|
@@ -349,30 +411,17 @@
|
|
|
349
411
|
if (options?.allowDeprecated !== void 0) {
|
|
350
412
|
this._allowDeprecated = options.allowDeprecated;
|
|
351
413
|
}
|
|
414
|
+
if (options?.useHookClone !== void 0) {
|
|
415
|
+
this._useHookClone = options.useHookClone;
|
|
416
|
+
}
|
|
352
417
|
}
|
|
353
418
|
/**
|
|
354
419
|
* Gets all hooks
|
|
355
|
-
* @returns {Map<string,
|
|
420
|
+
* @returns {Map<string, IHook[]>}
|
|
356
421
|
*/
|
|
357
422
|
get hooks() {
|
|
358
423
|
return this._hooks;
|
|
359
424
|
}
|
|
360
|
-
/**
|
|
361
|
-
* Gets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.
|
|
362
|
-
* @returns {boolean}
|
|
363
|
-
* @deprecated - this will be deprecated in version 2. Please use throwOnHookError.
|
|
364
|
-
*/
|
|
365
|
-
get throwHookErrors() {
|
|
366
|
-
return this._throwOnHookError;
|
|
367
|
-
}
|
|
368
|
-
/**
|
|
369
|
-
* Sets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.
|
|
370
|
-
* @param {boolean} value
|
|
371
|
-
* @deprecated - this will be deprecated in version 2. Please use throwOnHookError.
|
|
372
|
-
*/
|
|
373
|
-
set throwHookErrors(value) {
|
|
374
|
-
this._throwOnHookError = value;
|
|
375
|
-
}
|
|
376
425
|
/**
|
|
377
426
|
* Gets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.
|
|
378
427
|
* @returns {boolean}
|
|
@@ -431,157 +480,153 @@
|
|
|
431
480
|
this._allowDeprecated = value;
|
|
432
481
|
}
|
|
433
482
|
/**
|
|
434
|
-
*
|
|
435
|
-
* @
|
|
436
|
-
* @throws {Error} If enforceBeforeAfter is true and event doesn't start with 'before' or 'after'
|
|
437
|
-
*/
|
|
438
|
-
validateHookName(event) {
|
|
439
|
-
if (this._enforceBeforeAfter) {
|
|
440
|
-
const eventValue = event.trim().toLocaleLowerCase();
|
|
441
|
-
if (!eventValue.startsWith("before") && !eventValue.startsWith("after")) {
|
|
442
|
-
throw new Error(
|
|
443
|
-
`Hook event "${event}" must start with "before" or "after" when enforceBeforeAfter is enabled`
|
|
444
|
-
);
|
|
445
|
-
}
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
/**
|
|
449
|
-
* Checks if a hook is deprecated and emits a warning if it is
|
|
450
|
-
* @param {string} event - The event name to check
|
|
451
|
-
* @returns {boolean} - Returns true if the hook should proceed, false if it should be blocked
|
|
483
|
+
* Gets whether hook objects are cloned before storing. Default is true.
|
|
484
|
+
* @returns {boolean}
|
|
452
485
|
*/
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
const message = this._deprecatedHooks.get(event);
|
|
456
|
-
const warningMessage = `Hook "${event}" is deprecated${message ? `: ${message}` : ""}`;
|
|
457
|
-
this.emit("warn", { hook: event, message: warningMessage });
|
|
458
|
-
return this._allowDeprecated;
|
|
459
|
-
}
|
|
460
|
-
return true;
|
|
486
|
+
get useHookClone() {
|
|
487
|
+
return this._useHookClone;
|
|
461
488
|
}
|
|
462
489
|
/**
|
|
463
|
-
*
|
|
464
|
-
*
|
|
465
|
-
* @param {
|
|
466
|
-
* @returns {void}
|
|
490
|
+
* Sets whether hook objects are cloned before storing. Default is true.
|
|
491
|
+
* When false, the original IHook reference is stored directly.
|
|
492
|
+
* @param {boolean} value
|
|
467
493
|
*/
|
|
468
|
-
|
|
469
|
-
this.
|
|
494
|
+
set useHookClone(value) {
|
|
495
|
+
this._useHookClone = value;
|
|
470
496
|
}
|
|
471
497
|
/**
|
|
472
|
-
* Adds a handler function for a specific event
|
|
473
|
-
* @
|
|
474
|
-
* @
|
|
498
|
+
* Adds a handler function for a specific event.
|
|
499
|
+
* If you prefer the legacy `(event, handler)` signature, use {@link addHook} instead.
|
|
500
|
+
* To register multiple hooks at once, use {@link onHooks}.
|
|
501
|
+
* @param {IHook} hook - the hook containing event name and handler
|
|
502
|
+
* @param {OnHookOptions} [options] - optional per-call options (e.g., useHookClone override, position)
|
|
503
|
+
* @returns {IHook | undefined} the stored hook, or undefined if blocked by deprecation
|
|
475
504
|
*/
|
|
476
|
-
|
|
477
|
-
this.validateHookName(
|
|
478
|
-
if (!this.checkDeprecatedHook(
|
|
479
|
-
return;
|
|
505
|
+
onHook(hook, options) {
|
|
506
|
+
this.validateHookName(hook.event);
|
|
507
|
+
if (!this.checkDeprecatedHook(hook.event)) {
|
|
508
|
+
return void 0;
|
|
480
509
|
}
|
|
481
|
-
const
|
|
510
|
+
const shouldClone = options?.useHookClone ?? this._useHookClone;
|
|
511
|
+
const entry = shouldClone ? { id: hook.id, event: hook.event, handler: hook.handler } : hook;
|
|
512
|
+
entry.id = entry.id ?? crypto.randomUUID();
|
|
513
|
+
const eventHandlers = this._hooks.get(hook.event);
|
|
482
514
|
if (eventHandlers) {
|
|
483
|
-
eventHandlers.
|
|
515
|
+
const existingIndex = eventHandlers.findIndex((h) => h.id === entry.id);
|
|
516
|
+
if (existingIndex !== -1) {
|
|
517
|
+
eventHandlers[existingIndex] = entry;
|
|
518
|
+
} else {
|
|
519
|
+
const position = options?.position ?? "Bottom";
|
|
520
|
+
if (position === "Top") {
|
|
521
|
+
eventHandlers.unshift(entry);
|
|
522
|
+
} else if (position === "Bottom") {
|
|
523
|
+
eventHandlers.push(entry);
|
|
524
|
+
} else {
|
|
525
|
+
const index = Math.max(0, Math.min(position, eventHandlers.length));
|
|
526
|
+
eventHandlers.splice(index, 0, entry);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
484
529
|
} else {
|
|
485
|
-
this._hooks.set(
|
|
530
|
+
this._hooks.set(hook.event, [entry]);
|
|
486
531
|
}
|
|
532
|
+
return entry;
|
|
487
533
|
}
|
|
488
534
|
/**
|
|
489
535
|
* Alias for onHook. This is provided for compatibility with other libraries that use the `addHook` method.
|
|
490
|
-
* @param {string} event
|
|
491
|
-
* @param {
|
|
536
|
+
* @param {string} event - the event name
|
|
537
|
+
* @param {HookFn} handler - the handler function
|
|
492
538
|
* @returns {void}
|
|
493
539
|
*/
|
|
494
540
|
addHook(event, handler) {
|
|
495
|
-
this.
|
|
541
|
+
this.onHook({ event, handler });
|
|
496
542
|
}
|
|
497
543
|
/**
|
|
498
|
-
* Adds
|
|
499
|
-
* @param {Array<
|
|
544
|
+
* Adds handler functions for specific events
|
|
545
|
+
* @param {Array<IHook>} hooks
|
|
546
|
+
* @param {OnHookOptions} [options] - optional per-call options (e.g., useHookClone override, position)
|
|
500
547
|
* @returns {void}
|
|
501
548
|
*/
|
|
502
|
-
onHooks(hooks) {
|
|
549
|
+
onHooks(hooks, options) {
|
|
503
550
|
for (const hook of hooks) {
|
|
504
|
-
this.onHook(hook
|
|
551
|
+
this.onHook(hook, options);
|
|
505
552
|
}
|
|
506
553
|
}
|
|
507
554
|
/**
|
|
508
|
-
* Adds a handler function for a specific event that runs before all other handlers
|
|
509
|
-
*
|
|
510
|
-
* @param {
|
|
511
|
-
* @
|
|
555
|
+
* Adds a handler function for a specific event that runs before all other handlers.
|
|
556
|
+
* Equivalent to calling `onHook(hook, { position: "Top" })`.
|
|
557
|
+
* @param {IHook} hook - the hook containing event name and handler
|
|
558
|
+
* @param {PrependHookOptions} [options] - optional per-call options (e.g., useHookClone override)
|
|
559
|
+
* @returns {IHook | undefined} the stored hook, or undefined if blocked by deprecation
|
|
512
560
|
*/
|
|
513
|
-
prependHook(
|
|
514
|
-
this.
|
|
515
|
-
if (!this.checkDeprecatedHook(event)) {
|
|
516
|
-
return;
|
|
517
|
-
}
|
|
518
|
-
const eventHandlers = this._hooks.get(event);
|
|
519
|
-
if (eventHandlers) {
|
|
520
|
-
eventHandlers.unshift(handler);
|
|
521
|
-
} else {
|
|
522
|
-
this._hooks.set(event, [handler]);
|
|
523
|
-
}
|
|
561
|
+
prependHook(hook, options) {
|
|
562
|
+
return this.onHook(hook, { ...options, position: "Top" });
|
|
524
563
|
}
|
|
525
564
|
/**
|
|
526
|
-
* Adds a handler that only executes once for a specific event before all other handlers
|
|
527
|
-
*
|
|
528
|
-
* @param handler
|
|
565
|
+
* Adds a handler that only executes once for a specific event before all other handlers.
|
|
566
|
+
* Equivalent to calling `onHook` with a self-removing wrapper and `{ position: "Top" }`.
|
|
567
|
+
* @param {IHook} hook - the hook containing event name and handler
|
|
568
|
+
* @param {PrependHookOptions} [options] - optional per-call options (e.g., useHookClone override)
|
|
569
|
+
* @returns {IHook | undefined} the stored hook, or undefined if blocked by deprecation
|
|
529
570
|
*/
|
|
530
|
-
prependOnceHook(
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
return;
|
|
534
|
-
}
|
|
535
|
-
const hook = async (...arguments_) => {
|
|
536
|
-
this.removeHook(event, hook);
|
|
537
|
-
return handler(...arguments_);
|
|
571
|
+
prependOnceHook(hook, options) {
|
|
572
|
+
const wrappedHandler = async (...arguments_) => {
|
|
573
|
+
this.removeHook({ event: hook.event, handler: wrappedHandler });
|
|
574
|
+
return hook.handler(...arguments_);
|
|
538
575
|
};
|
|
539
|
-
this.
|
|
576
|
+
return this.onHook(
|
|
577
|
+
{ id: hook.id, event: hook.event, handler: wrappedHandler },
|
|
578
|
+
{ ...options, position: "Top" }
|
|
579
|
+
);
|
|
540
580
|
}
|
|
541
581
|
/**
|
|
542
582
|
* Adds a handler that only executes once for a specific event
|
|
543
|
-
* @param event
|
|
544
|
-
* @param handler
|
|
583
|
+
* @param {IHook} hook - the hook containing event name and handler
|
|
545
584
|
*/
|
|
546
|
-
onceHook(
|
|
547
|
-
this.validateHookName(event);
|
|
548
|
-
if (!this.checkDeprecatedHook(event)) {
|
|
585
|
+
onceHook(hook) {
|
|
586
|
+
this.validateHookName(hook.event);
|
|
587
|
+
if (!this.checkDeprecatedHook(hook.event)) {
|
|
549
588
|
return;
|
|
550
589
|
}
|
|
551
|
-
const
|
|
552
|
-
this.removeHook(event,
|
|
553
|
-
return handler(...arguments_);
|
|
590
|
+
const wrappedHandler = async (...arguments_) => {
|
|
591
|
+
this.removeHook({ event: hook.event, handler: wrappedHandler });
|
|
592
|
+
return hook.handler(...arguments_);
|
|
554
593
|
};
|
|
555
|
-
this.onHook(event,
|
|
594
|
+
this.onHook({ id: hook.id, event: hook.event, handler: wrappedHandler });
|
|
556
595
|
}
|
|
557
596
|
/**
|
|
558
597
|
* Removes a handler function for a specific event
|
|
559
|
-
* @param {
|
|
560
|
-
* @
|
|
561
|
-
* @returns {void}
|
|
598
|
+
* @param {IHook} hook - the hook containing event name and handler to remove
|
|
599
|
+
* @returns {IHook | undefined} the removed hook, or undefined if not found
|
|
562
600
|
*/
|
|
563
|
-
removeHook(
|
|
564
|
-
this.validateHookName(event);
|
|
565
|
-
|
|
566
|
-
return;
|
|
567
|
-
}
|
|
568
|
-
const eventHandlers = this._hooks.get(event);
|
|
601
|
+
removeHook(hook) {
|
|
602
|
+
this.validateHookName(hook.event);
|
|
603
|
+
const eventHandlers = this._hooks.get(hook.event);
|
|
569
604
|
if (eventHandlers) {
|
|
570
|
-
const index = eventHandlers.
|
|
605
|
+
const index = eventHandlers.findIndex((h) => h.handler === hook.handler);
|
|
571
606
|
if (index !== -1) {
|
|
572
607
|
eventHandlers.splice(index, 1);
|
|
608
|
+
if (eventHandlers.length === 0) {
|
|
609
|
+
this._hooks.delete(hook.event);
|
|
610
|
+
}
|
|
611
|
+
return { event: hook.event, handler: hook.handler };
|
|
573
612
|
}
|
|
574
613
|
}
|
|
614
|
+
return void 0;
|
|
575
615
|
}
|
|
576
616
|
/**
|
|
577
|
-
* Removes
|
|
578
|
-
* @param {Array<
|
|
579
|
-
* @returns {
|
|
617
|
+
* Removes multiple hook handlers
|
|
618
|
+
* @param {Array<IHook>} hooks
|
|
619
|
+
* @returns {IHook[]} the hooks that were successfully removed
|
|
580
620
|
*/
|
|
581
621
|
removeHooks(hooks) {
|
|
622
|
+
const removed = [];
|
|
582
623
|
for (const hook of hooks) {
|
|
583
|
-
this.removeHook(hook
|
|
624
|
+
const result = this.removeHook(hook);
|
|
625
|
+
if (result) {
|
|
626
|
+
removed.push(result);
|
|
627
|
+
}
|
|
584
628
|
}
|
|
629
|
+
return removed;
|
|
585
630
|
}
|
|
586
631
|
/**
|
|
587
632
|
* Calls all handlers for a specific event
|
|
@@ -596,9 +641,9 @@
|
|
|
596
641
|
}
|
|
597
642
|
const eventHandlers = this._hooks.get(event);
|
|
598
643
|
if (eventHandlers) {
|
|
599
|
-
for (const
|
|
644
|
+
for (const hook of [...eventHandlers]) {
|
|
600
645
|
try {
|
|
601
|
-
await handler(...arguments_);
|
|
646
|
+
await hook.handler(...arguments_);
|
|
602
647
|
} catch (error) {
|
|
603
648
|
const message = `${event}: ${error.message}`;
|
|
604
649
|
this.emit("error", new Error(message));
|
|
@@ -626,12 +671,12 @@
|
|
|
626
671
|
}
|
|
627
672
|
const eventHandlers = this._hooks.get(event);
|
|
628
673
|
if (eventHandlers) {
|
|
629
|
-
for (const
|
|
630
|
-
if (handler.constructor.name === "AsyncFunction") {
|
|
674
|
+
for (const hook of [...eventHandlers]) {
|
|
675
|
+
if (hook.handler.constructor.name === "AsyncFunction") {
|
|
631
676
|
continue;
|
|
632
677
|
}
|
|
633
678
|
try {
|
|
634
|
-
handler(...arguments_);
|
|
679
|
+
hook.handler(...arguments_);
|
|
635
680
|
} catch (error) {
|
|
636
681
|
const message = `${event}: ${error.message}`;
|
|
637
682
|
this.emit("error", new Error(message));
|
|
@@ -671,15 +716,54 @@
|
|
|
671
716
|
/**
|
|
672
717
|
* Gets all hooks for a specific event
|
|
673
718
|
* @param {string} event
|
|
674
|
-
* @returns {
|
|
719
|
+
* @returns {IHook[]}
|
|
675
720
|
*/
|
|
676
721
|
getHooks(event) {
|
|
677
722
|
this.validateHookName(event);
|
|
678
|
-
if (!this.checkDeprecatedHook(event)) {
|
|
679
|
-
return void 0;
|
|
680
|
-
}
|
|
681
723
|
return this._hooks.get(event);
|
|
682
724
|
}
|
|
725
|
+
/**
|
|
726
|
+
* Gets a specific hook by id, searching across all events
|
|
727
|
+
* @param {string} id - the hook id
|
|
728
|
+
* @returns {IHook | undefined} the hook if found, or undefined
|
|
729
|
+
*/
|
|
730
|
+
getHook(id) {
|
|
731
|
+
for (const eventHandlers of this._hooks.values()) {
|
|
732
|
+
const found = eventHandlers.find((h) => h.id === id);
|
|
733
|
+
if (found) {
|
|
734
|
+
return found;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
return void 0;
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* Removes one or more hooks by id, searching across all events
|
|
741
|
+
* @param {string | string[]} id - the hook id or array of hook ids to remove
|
|
742
|
+
* @returns {IHook | IHook[] | undefined} the removed hook(s), or undefined/empty array if not found
|
|
743
|
+
*/
|
|
744
|
+
removeHookById(id) {
|
|
745
|
+
if (Array.isArray(id)) {
|
|
746
|
+
const removed = [];
|
|
747
|
+
for (const singleId of id) {
|
|
748
|
+
const result = this.removeHookById(singleId);
|
|
749
|
+
if (result && !Array.isArray(result)) {
|
|
750
|
+
removed.push(result);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
return removed;
|
|
754
|
+
}
|
|
755
|
+
for (const [event, eventHandlers] of this._hooks.entries()) {
|
|
756
|
+
const index = eventHandlers.findIndex((h) => h.id === id);
|
|
757
|
+
if (index !== -1) {
|
|
758
|
+
const [removed] = eventHandlers.splice(index, 1);
|
|
759
|
+
if (eventHandlers.length === 0) {
|
|
760
|
+
this._hooks.delete(event);
|
|
761
|
+
}
|
|
762
|
+
return removed;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
return void 0;
|
|
766
|
+
}
|
|
683
767
|
/**
|
|
684
768
|
* Removes all hooks
|
|
685
769
|
* @returns {void}
|
|
@@ -687,6 +771,50 @@
|
|
|
687
771
|
clearHooks() {
|
|
688
772
|
this._hooks.clear();
|
|
689
773
|
}
|
|
774
|
+
/**
|
|
775
|
+
* Removes all hooks for a specific event and returns the removed hooks.
|
|
776
|
+
* @param {string} event - The event name to remove hooks for.
|
|
777
|
+
* @returns {IHook[]} the hooks that were removed
|
|
778
|
+
*/
|
|
779
|
+
removeEventHooks(event) {
|
|
780
|
+
this.validateHookName(event);
|
|
781
|
+
const eventHandlers = this._hooks.get(event);
|
|
782
|
+
if (eventHandlers) {
|
|
783
|
+
const removed = [...eventHandlers];
|
|
784
|
+
this._hooks.delete(event);
|
|
785
|
+
return removed;
|
|
786
|
+
}
|
|
787
|
+
return [];
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Validates hook event name if enforceBeforeAfter is enabled
|
|
791
|
+
* @param {string} event - The event name to validate
|
|
792
|
+
* @throws {Error} If enforceBeforeAfter is true and event doesn't start with 'before' or 'after'
|
|
793
|
+
*/
|
|
794
|
+
validateHookName(event) {
|
|
795
|
+
if (this._enforceBeforeAfter) {
|
|
796
|
+
const eventValue = event.trim().toLocaleLowerCase();
|
|
797
|
+
if (!eventValue.startsWith("before") && !eventValue.startsWith("after")) {
|
|
798
|
+
throw new Error(
|
|
799
|
+
`Hook event "${event}" must start with "before" or "after" when enforceBeforeAfter is enabled`
|
|
800
|
+
);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Checks if a hook is deprecated and emits a warning if it is
|
|
806
|
+
* @param {string} event - The event name to check
|
|
807
|
+
* @returns {boolean} - Returns true if the hook should proceed, false if it should be blocked
|
|
808
|
+
*/
|
|
809
|
+
checkDeprecatedHook(event) {
|
|
810
|
+
if (this._deprecatedHooks.has(event)) {
|
|
811
|
+
const message = this._deprecatedHooks.get(event);
|
|
812
|
+
const warningMessage = `Hook "${event}" is deprecated${message ? `: ${message}` : ""}`;
|
|
813
|
+
this.emit("warn", { hook: event, message: warningMessage });
|
|
814
|
+
return this._allowDeprecated;
|
|
815
|
+
}
|
|
816
|
+
return true;
|
|
817
|
+
}
|
|
690
818
|
};
|
|
691
819
|
})();
|
|
692
820
|
/* v8 ignore next -- @preserve */
|