hookified 2.1.1 → 2.2.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.
@@ -1,892 +1,951 @@
1
- "use strict";
2
- (() => {
3
- var __defProp = Object.defineProperty;
4
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
5
- var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
1
+ var Hookified = (function(exports) {
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ //#region \0@oxc-project+runtime@0.127.0/helpers/typeof.js
4
+ function _typeof(o) {
5
+ "@babel/helpers - typeof";
6
+ return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o) {
7
+ return typeof o;
8
+ } : function(o) {
9
+ return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
10
+ }, _typeof(o);
11
+ }
12
+ //#endregion
13
+ //#region \0@oxc-project+runtime@0.127.0/helpers/toPrimitive.js
14
+ function toPrimitive(t, r) {
15
+ if ("object" != _typeof(t) || !t) return t;
16
+ var e = t[Symbol.toPrimitive];
17
+ if (void 0 !== e) {
18
+ var i = e.call(t, r || "default");
19
+ if ("object" != _typeof(i)) return i;
20
+ throw new TypeError("@@toPrimitive must return a primitive value.");
21
+ }
22
+ return ("string" === r ? String : Number)(t);
23
+ }
24
+ //#endregion
25
+ //#region \0@oxc-project+runtime@0.127.0/helpers/toPropertyKey.js
26
+ function toPropertyKey(t) {
27
+ var i = toPrimitive(t, "string");
28
+ return "symbol" == _typeof(i) ? i : i + "";
29
+ }
30
+ //#endregion
31
+ //#region \0@oxc-project+runtime@0.127.0/helpers/defineProperty.js
32
+ function _defineProperty(e, r, t) {
33
+ return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
34
+ value: t,
35
+ enumerable: !0,
36
+ configurable: !0,
37
+ writable: !0
38
+ }) : e[r] = t, e;
39
+ }
40
+ //#endregion
41
+ //#region src/eventified.ts
42
+ const ERROR_EVENT = "error";
43
+ var Eventified = class {
44
+ constructor(options) {
45
+ _defineProperty(this, "_eventListeners", void 0);
46
+ _defineProperty(this, "_maxListeners", void 0);
47
+ _defineProperty(this, "_eventLogger", void 0);
48
+ _defineProperty(this, "_throwOnEmitError", false);
49
+ _defineProperty(this, "_throwOnEmptyListeners", true);
50
+ this._eventListeners = /* @__PURE__ */ new Map();
51
+ this._maxListeners = 0;
52
+ this._eventLogger = options?.eventLogger;
53
+ if (options?.throwOnEmitError !== void 0) this._throwOnEmitError = options.throwOnEmitError;
54
+ if (options?.throwOnEmptyListeners !== void 0) this._throwOnEmptyListeners = options.throwOnEmptyListeners;
55
+ }
56
+ /**
57
+ * Gets the event logger
58
+ * @returns {Logger}
59
+ */
60
+ get eventLogger() {
61
+ return this._eventLogger;
62
+ }
63
+ /**
64
+ * Sets the event logger
65
+ * @param {Logger} eventLogger
66
+ */
67
+ set eventLogger(eventLogger) {
68
+ this._eventLogger = eventLogger;
69
+ }
70
+ /**
71
+ * Gets whether an error should be thrown when an emit throws an error. Default is false and only emits an error event.
72
+ * @returns {boolean}
73
+ */
74
+ get throwOnEmitError() {
75
+ return this._throwOnEmitError;
76
+ }
77
+ /**
78
+ * Sets whether an error should be thrown when an emit throws an error. Default is false and only emits an error event.
79
+ * @param {boolean} value
80
+ */
81
+ set throwOnEmitError(value) {
82
+ this._throwOnEmitError = value;
83
+ }
84
+ /**
85
+ * Gets whether an error should be thrown when emitting 'error' event with no listeners. Default is false.
86
+ * @returns {boolean}
87
+ */
88
+ get throwOnEmptyListeners() {
89
+ return this._throwOnEmptyListeners;
90
+ }
91
+ /**
92
+ * Sets whether an error should be thrown when emitting 'error' event with no listeners. Default is false.
93
+ * @param {boolean} value
94
+ */
95
+ set throwOnEmptyListeners(value) {
96
+ this._throwOnEmptyListeners = value;
97
+ }
98
+ /**
99
+ * Adds a handler function for a specific event that will run only once
100
+ * @param {string | symbol} eventName
101
+ * @param {EventListener} listener
102
+ * @returns {IEventEmitter} returns the instance of the class for chaining
103
+ */
104
+ once(eventName, listener) {
105
+ const onceListener = (...arguments_) => {
106
+ this.off(eventName, onceListener);
107
+ listener(...arguments_);
108
+ };
109
+ this.on(eventName, onceListener);
110
+ return this;
111
+ }
112
+ /**
113
+ * Gets the number of listeners for a specific event. If no event is provided, it returns the total number of listeners
114
+ * @param {string} eventName The event name. Not required
115
+ * @returns {number} The number of listeners
116
+ */
117
+ listenerCount(eventName) {
118
+ if (eventName === void 0) {
119
+ let count = 0;
120
+ for (const entry of this._eventListeners.values()) count += typeof entry === "function" ? 1 : entry.length;
121
+ return count;
122
+ }
123
+ const entry = this._eventListeners.get(eventName);
124
+ if (entry === void 0) return 0;
125
+ return typeof entry === "function" ? 1 : entry.length;
126
+ }
127
+ /**
128
+ * Gets an array of event names
129
+ * @returns {Array<string | symbol>} An array of event names
130
+ */
131
+ eventNames() {
132
+ return [...this._eventListeners.keys()];
133
+ }
134
+ /**
135
+ * Gets an array of listeners for a specific event. If no event is provided, it returns all listeners
136
+ * @param {string} [event] (Optional) The event name
137
+ * @returns {EventListener[]} An array of listeners
138
+ */
139
+ rawListeners(event) {
140
+ if (event === void 0) return this.getAllListeners();
141
+ const entry = this._eventListeners.get(event);
142
+ if (entry === void 0) return [];
143
+ return typeof entry === "function" ? [entry] : entry;
144
+ }
145
+ /**
146
+ * Prepends a listener to the beginning of the listeners array for the specified event
147
+ * @param {string | symbol} eventName
148
+ * @param {EventListener} listener
149
+ * @returns {IEventEmitter} returns the instance of the class for chaining
150
+ */
151
+ prependListener(eventName, listener) {
152
+ const existing = this._eventListeners.get(eventName);
153
+ if (existing === void 0) this._eventListeners.set(eventName, listener);
154
+ else if (typeof existing === "function") this._eventListeners.set(eventName, [listener, existing]);
155
+ else existing.unshift(listener);
156
+ return this;
157
+ }
158
+ /**
159
+ * Prepends a one-time listener to the beginning of the listeners array for the specified event
160
+ * @param {string | symbol} eventName
161
+ * @param {EventListener} listener
162
+ * @returns {IEventEmitter} returns the instance of the class for chaining
163
+ */
164
+ prependOnceListener(eventName, listener) {
165
+ const onceListener = (...arguments_) => {
166
+ this.off(eventName, onceListener);
167
+ listener(...arguments_);
168
+ };
169
+ this.prependListener(eventName, onceListener);
170
+ return this;
171
+ }
172
+ /**
173
+ * Gets the maximum number of listeners that can be added for a single event
174
+ * @returns {number} The maximum number of listeners
175
+ */
176
+ maxListeners() {
177
+ return this._maxListeners;
178
+ }
179
+ /**
180
+ * Adds a listener for a specific event. It is an alias for the on() method
181
+ * @param {string | symbol} event
182
+ * @param {EventListener} listener
183
+ * @returns {IEventEmitter} returns the instance of the class for chaining
184
+ */
185
+ addListener(event, listener) {
186
+ this.on(event, listener);
187
+ return this;
188
+ }
189
+ /**
190
+ * Adds a listener for a specific event
191
+ * @param {string | symbol} event
192
+ * @param {EventListener} listener
193
+ * @returns {IEventEmitter} returns the instance of the class for chaining
194
+ */
195
+ on(event, listener) {
196
+ const existing = this._eventListeners.get(event);
197
+ if (existing === void 0) {
198
+ this._eventListeners.set(event, listener);
199
+ return this;
200
+ }
201
+ if (typeof existing === "function") {
202
+ const arr = [existing, listener];
203
+ this._eventListeners.set(event, arr);
204
+ if (this._maxListeners > 0 && arr.length > this._maxListeners) console.warn(`MaxListenersExceededWarning: Possible event memory leak detected. ${arr.length} ${event} listeners added. Use setMaxListeners() to increase limit.`);
205
+ } else {
206
+ existing.push(listener);
207
+ if (this._maxListeners > 0 && existing.length > this._maxListeners) console.warn(`MaxListenersExceededWarning: Possible event memory leak detected. ${existing.length} ${event} listeners added. Use setMaxListeners() to increase limit.`);
208
+ }
209
+ return this;
210
+ }
211
+ /**
212
+ * Removes a listener for a specific event. It is an alias for the off() method
213
+ * @param {string | symbol} event
214
+ * @param {EventListener} listener
215
+ * @returns {IEventEmitter} returns the instance of the class for chaining
216
+ */
217
+ removeListener(event, listener) {
218
+ this.off(event, listener);
219
+ return this;
220
+ }
221
+ /**
222
+ * Removes a listener for a specific event
223
+ * @param {string | symbol} event
224
+ * @param {EventListener} listener
225
+ * @returns {IEventEmitter} returns the instance of the class for chaining
226
+ */
227
+ off(event, listener) {
228
+ const entry = this._eventListeners.get(event);
229
+ if (entry === void 0) return this;
230
+ if (typeof entry === "function") {
231
+ if (entry === listener) this._eventListeners.delete(event);
232
+ return this;
233
+ }
234
+ const index = entry.indexOf(listener);
235
+ if (index !== -1) if (entry.length === 2) this._eventListeners.set(event, entry[1 - index]);
236
+ else if (entry.length === 1) this._eventListeners.delete(event);
237
+ else entry.splice(index, 1);
238
+ return this;
239
+ }
240
+ /**
241
+ * Calls all listeners for a specific event
242
+ * @param {string | symbol} event
243
+ * @param arguments_ The arguments to pass to the listeners
244
+ * @returns {boolean} Returns true if the event had listeners, false otherwise
245
+ */
246
+ emit(event, ...arguments_) {
247
+ let result = false;
248
+ const entry = this._eventListeners.get(event);
249
+ const argumentLength = arguments_.length;
250
+ if (entry !== void 0) {
251
+ if (typeof entry === "function") if (argumentLength === 1) entry(arguments_[0]);
252
+ else if (argumentLength === 2) entry(arguments_[0], arguments_[1]);
253
+ else entry(...arguments_);
254
+ else {
255
+ const len = entry.length;
256
+ for (let i = 0; i < len; i++) if (argumentLength === 1) entry[i](arguments_[0]);
257
+ else if (argumentLength === 2) entry[i](arguments_[0], arguments_[1]);
258
+ else entry[i](...arguments_);
259
+ }
260
+ result = true;
261
+ }
262
+ if (this._eventLogger) this.sendToEventLogger(event, arguments_);
263
+ if (!result && event === ERROR_EVENT) {
264
+ const error = arguments_[0] instanceof Error ? arguments_[0] : /* @__PURE__ */ new Error(`${arguments_[0]}`);
265
+ if (this._throwOnEmitError || this._throwOnEmptyListeners) throw error;
266
+ }
267
+ return result;
268
+ }
269
+ /**
270
+ * Gets all listeners for a specific event. If no event is provided, it returns all listeners
271
+ * @param {string} [event] (Optional) The event name
272
+ * @returns {EventListener[]} An array of listeners
273
+ */
274
+ listeners(event) {
275
+ const entry = this._eventListeners.get(event);
276
+ if (entry === void 0) return [];
277
+ return typeof entry === "function" ? [entry] : entry;
278
+ }
279
+ /**
280
+ * Removes all listeners for a specific event. If no event is provided, it removes all listeners
281
+ * @param {string} [event] (Optional) The event name
282
+ * @returns {IEventEmitter} returns the instance of the class for chaining
283
+ */
284
+ removeAllListeners(event) {
285
+ if (event !== void 0) this._eventListeners.delete(event);
286
+ else this._eventListeners.clear();
287
+ return this;
288
+ }
289
+ /**
290
+ * Sets the maximum number of listeners that can be added for a single event
291
+ * @param {number} n The maximum number of listeners
292
+ * @returns {void}
293
+ */
294
+ setMaxListeners(n) {
295
+ this._maxListeners = n < 0 ? 0 : n;
296
+ }
297
+ /**
298
+ * Gets all listeners
299
+ * @returns {EventListener[]} An array of listeners
300
+ */
301
+ getAllListeners() {
302
+ const result = [];
303
+ for (const entry of this._eventListeners.values()) if (typeof entry === "function") result.push(entry);
304
+ else for (let i = 0; i < entry.length; i++) result.push(entry[i]);
305
+ return result;
306
+ }
307
+ /**
308
+ * Sends a log message using the configured logger based on the event name
309
+ * @param {string | symbol} eventName - The event name that determines the log level
310
+ * @param {unknown} data - The data to log
311
+ */
312
+ sendToEventLogger(eventName, data) {
313
+ /* v8 ignore next 3 -- @preserve: guarded by caller */
314
+ if (!this._eventLogger) return;
315
+ let message;
316
+ /* v8 ignore next -- @preserve */
317
+ if (typeof data === "string") message = data;
318
+ else if (Array.isArray(data) && data.length > 0 && data[0] instanceof Error) message = data[0].message;
319
+ else if (data instanceof Error) message = data.message;
320
+ else if (Array.isArray(data) && data.length > 0 && typeof data[0]?.message === "string") message = data[0].message;
321
+ else message = JSON.stringify(data);
322
+ switch (eventName) {
323
+ case "error":
324
+ this._eventLogger.error?.(message, {
325
+ event: eventName,
326
+ data
327
+ });
328
+ break;
329
+ case "warn":
330
+ this._eventLogger.warn?.(message, {
331
+ event: eventName,
332
+ data
333
+ });
334
+ break;
335
+ case "trace":
336
+ this._eventLogger.trace?.(message, {
337
+ event: eventName,
338
+ data
339
+ });
340
+ break;
341
+ case "debug":
342
+ this._eventLogger.debug?.(message, {
343
+ event: eventName,
344
+ data
345
+ });
346
+ break;
347
+ case "fatal":
348
+ this._eventLogger.fatal?.(message, {
349
+ event: eventName,
350
+ data
351
+ });
352
+ break;
353
+ default:
354
+ this._eventLogger.info?.(message, {
355
+ event: eventName,
356
+ data
357
+ });
358
+ break;
359
+ }
360
+ }
361
+ };
362
+ //#endregion
363
+ //#region src/hooks/hook.ts
364
+ /**
365
+ * Concrete implementation of the IHook interface.
366
+ * Provides a convenient class-based way to create hook entries.
367
+ */
368
+ var Hook = class {
369
+ /**
370
+ * Creates a new Hook instance
371
+ * @param {string} event - The event name for the hook
372
+ * @param {HookFn} handler - The handler function for the hook
373
+ * @param {string} [id] - Optional unique identifier for the hook
374
+ */
375
+ constructor(event, handler, id) {
376
+ _defineProperty(this, "id", void 0);
377
+ _defineProperty(this, "event", void 0);
378
+ _defineProperty(this, "handler", void 0);
379
+ this.id = id;
380
+ this.event = event;
381
+ this.handler = handler;
382
+ }
383
+ };
384
+ //#endregion
385
+ //#region src/hooks/parallel-hook.ts
386
+ /**
387
+ * A ParallelHook fans a single invocation out to many registered hook
388
+ * functions concurrently via Promise.allSettled, then calls a final
389
+ * handler with the aggregated results — including failures — keyed by
390
+ * the hook function reference for direct lookup. Each hook receives only
391
+ * the original arguments; hooks do not see each other's results.
392
+ *
393
+ * Implements IHook for compatibility with Hookified.onHook(); the final
394
+ * handler is called regardless of whether the parallel hook is invoked
395
+ * directly or through Hookified.hook().
396
+ *
397
+ * Generics tighten the per-hook signature when every hook in the set
398
+ * accepts the same `initialArgs` and returns the same result type.
399
+ * Both default to `any` for backwards compatibility.
400
+ *
401
+ * @template TArgs - The shape of `initialArgs` passed to every hook
402
+ * @template TResult - The result type each hook returns
403
+ */
404
+ var ParallelHook = class {
405
+ /**
406
+ * Creates a new ParallelHook instance
407
+ * @param {string} event - The event name for the hook
408
+ * @param {ParallelHookFinalFn<TArgs, TResult>} finalHandler - Called once with `{ initialArgs, results }` after all parallel hooks settle
409
+ * @param {string} [id] - Optional unique identifier for the hook
410
+ */
411
+ constructor(event, finalHandler, id) {
412
+ _defineProperty(this, "id", void 0);
413
+ _defineProperty(this, "event", void 0);
414
+ _defineProperty(this, "handler", void 0);
415
+ _defineProperty(this, "hooks", void 0);
416
+ _defineProperty(this, "_finalHandler", void 0);
417
+ this.id = id;
418
+ this.event = event;
419
+ this.hooks = [];
420
+ this._finalHandler = finalHandler;
421
+ this.handler = async (...arguments_) => {
422
+ const initialArgs = arguments_.length === 1 ? arguments_[0] : arguments_;
423
+ const snapshot = [...this.hooks];
424
+ const settled = await Promise.allSettled(snapshot.map((hook) => Promise.resolve().then(() => hook({ initialArgs }))));
425
+ const results = /* @__PURE__ */ new Map();
426
+ snapshot.forEach((hook, index) => {
427
+ const outcome = settled[index];
428
+ results.set(hook, outcome.status === "fulfilled" ? {
429
+ status: "fulfilled",
430
+ result: outcome.value
431
+ } : {
432
+ status: "rejected",
433
+ reason: outcome.reason
434
+ });
435
+ });
436
+ await this._finalHandler({
437
+ initialArgs,
438
+ results
439
+ });
440
+ };
441
+ }
442
+ /**
443
+ * Adds a hook function to the parallel set
444
+ * @param {ParallelHookFn<TArgs, TResult>} hook - The hook function to add
445
+ */
446
+ addHook(hook) {
447
+ this.hooks.push(hook);
448
+ }
449
+ /**
450
+ * Removes a specific hook function from the parallel set
451
+ * @param {ParallelHookFn<TArgs, TResult>} hook - The hook function to remove
452
+ * @returns {boolean} true if the hook was found and removed
453
+ */
454
+ removeHook(hook) {
455
+ const index = this.hooks.indexOf(hook);
456
+ if (index !== -1) {
457
+ this.hooks.splice(index, 1);
458
+ return true;
459
+ }
460
+ return false;
461
+ }
462
+ };
463
+ //#endregion
464
+ //#region src/hooks/waterfall-hook.ts
465
+ /**
466
+ * A WaterfallHook chains multiple hook functions sequentially,
467
+ * where each hook receives a context with the previous result,
468
+ * initial arguments, and accumulated results. After all hooks
469
+ * have executed, the final handler receives the transformed result.
470
+ * Implements IHook for compatibility with Hookified.onHook().
471
+ */
472
+ var WaterfallHook = class {
473
+ /**
474
+ * Creates a new WaterfallHook instance
475
+ * @param {string} event - The event name for the hook
476
+ * @param {WaterfallHookFn} finalHandler - The final handler function that receives the transformed result
477
+ * @param {string} [id] - Optional unique identifier for the hook
478
+ */
479
+ constructor(event, finalHandler, id) {
480
+ _defineProperty(this, "id", void 0);
481
+ _defineProperty(this, "event", void 0);
482
+ _defineProperty(this, "handler", void 0);
483
+ _defineProperty(this, "hooks", void 0);
484
+ _defineProperty(this, "_finalHandler", void 0);
485
+ this.id = id;
486
+ this.event = event;
487
+ this.hooks = [];
488
+ this._finalHandler = finalHandler;
489
+ this.handler = async (...arguments_) => {
490
+ const initialArgs = arguments_.length === 1 ? arguments_[0] : arguments_;
491
+ const results = [];
492
+ for (const hook of this.hooks) {
493
+ const result = await hook({
494
+ initialArgs,
495
+ results: [...results]
496
+ });
497
+ results.push({
498
+ hook,
499
+ result
500
+ });
501
+ }
502
+ await this._finalHandler({
503
+ initialArgs,
504
+ results: [...results]
505
+ });
506
+ };
507
+ }
508
+ /**
509
+ * Adds a hook function to the end of the waterfall chain
510
+ * @param {WaterfallHookFn} hook - The hook function to add
511
+ */
512
+ addHook(hook) {
513
+ this.hooks.push(hook);
514
+ }
515
+ /**
516
+ * Removes a specific hook function from the waterfall chain
517
+ * @param {WaterfallHookFn} hook - The hook function to remove
518
+ * @returns {boolean} true if the hook was found and removed
519
+ */
520
+ removeHook(hook) {
521
+ const index = this.hooks.indexOf(hook);
522
+ if (index !== -1) {
523
+ this.hooks.splice(index, 1);
524
+ return true;
525
+ }
526
+ return false;
527
+ }
528
+ };
529
+ //#endregion
530
+ //#region src/index.ts
531
+ var Hookified = class extends Eventified {
532
+ constructor(options) {
533
+ super({
534
+ eventLogger: options?.eventLogger,
535
+ throwOnEmitError: options?.throwOnEmitError,
536
+ throwOnEmptyListeners: options?.throwOnEmptyListeners
537
+ });
538
+ _defineProperty(this, "_hooks", void 0);
539
+ _defineProperty(this, "_throwOnHookError", false);
540
+ _defineProperty(this, "_enforceBeforeAfter", false);
541
+ _defineProperty(this, "_deprecatedHooks", void 0);
542
+ _defineProperty(this, "_allowDeprecated", true);
543
+ _defineProperty(this, "_useHookClone", true);
544
+ this._hooks = /* @__PURE__ */ new Map();
545
+ this._deprecatedHooks = options?.deprecatedHooks ? new Map(options.deprecatedHooks) : /* @__PURE__ */ new Map();
546
+ if (options?.throwOnHookError !== void 0) this._throwOnHookError = options.throwOnHookError;
547
+ if (options?.enforceBeforeAfter !== void 0) this._enforceBeforeAfter = options.enforceBeforeAfter;
548
+ if (options?.allowDeprecated !== void 0) this._allowDeprecated = options.allowDeprecated;
549
+ if (options?.useHookClone !== void 0) this._useHookClone = options.useHookClone;
550
+ }
551
+ /**
552
+ * Gets all hooks
553
+ * @returns {Map<string, IHook[]>}
554
+ */
555
+ get hooks() {
556
+ return this._hooks;
557
+ }
558
+ /**
559
+ * Gets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.
560
+ * @returns {boolean}
561
+ */
562
+ get throwOnHookError() {
563
+ return this._throwOnHookError;
564
+ }
565
+ /**
566
+ * Sets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.
567
+ * @param {boolean} value
568
+ */
569
+ set throwOnHookError(value) {
570
+ this._throwOnHookError = value;
571
+ }
572
+ /**
573
+ * Gets whether to enforce that all hook names start with 'before' or 'after'. Default is false.
574
+ * @returns {boolean}
575
+ * @default false
576
+ */
577
+ get enforceBeforeAfter() {
578
+ return this._enforceBeforeAfter;
579
+ }
580
+ /**
581
+ * Sets whether to enforce that all hook names start with 'before' or 'after'. Default is false.
582
+ * @param {boolean} value
583
+ */
584
+ set enforceBeforeAfter(value) {
585
+ this._enforceBeforeAfter = value;
586
+ }
587
+ /**
588
+ * Gets the map of deprecated hook names to deprecation messages.
589
+ * @returns {Map<string, string>}
590
+ */
591
+ get deprecatedHooks() {
592
+ return this._deprecatedHooks;
593
+ }
594
+ /**
595
+ * Sets the map of deprecated hook names to deprecation messages.
596
+ * @param {Map<string, string>} value
597
+ */
598
+ set deprecatedHooks(value) {
599
+ this._deprecatedHooks = value;
600
+ }
601
+ /**
602
+ * Gets whether deprecated hooks are allowed to be registered and executed. Default is true.
603
+ * @returns {boolean}
604
+ */
605
+ get allowDeprecated() {
606
+ return this._allowDeprecated;
607
+ }
608
+ /**
609
+ * Sets whether deprecated hooks are allowed to be registered and executed. Default is true.
610
+ * @param {boolean} value
611
+ */
612
+ set allowDeprecated(value) {
613
+ this._allowDeprecated = value;
614
+ }
615
+ /**
616
+ * Gets whether hook objects are cloned before storing. Default is true.
617
+ * @returns {boolean}
618
+ */
619
+ get useHookClone() {
620
+ return this._useHookClone;
621
+ }
622
+ /**
623
+ * Sets whether hook objects are cloned before storing. Default is true.
624
+ * When false, the original IHook reference is stored directly.
625
+ * @param {boolean} value
626
+ */
627
+ set useHookClone(value) {
628
+ this._useHookClone = value;
629
+ }
630
+ onHook(hookOrEvent, optionsOrHandler) {
631
+ let hook;
632
+ let options;
633
+ if (typeof hookOrEvent === "string") {
634
+ if (typeof optionsOrHandler !== "function") throw new TypeError("When calling onHook(event, handler), the second argument must be a function");
635
+ hook = {
636
+ event: hookOrEvent,
637
+ handler: optionsOrHandler
638
+ };
639
+ options = void 0;
640
+ } else {
641
+ hook = hookOrEvent;
642
+ options = optionsOrHandler;
643
+ }
644
+ this.validateHookName(hook.event);
645
+ if (!this.checkDeprecatedHook(hook.event)) return;
646
+ const entry = options?.useHookClone ?? this._useHookClone ? {
647
+ id: hook.id,
648
+ event: hook.event,
649
+ handler: hook.handler
650
+ } : hook;
651
+ entry.id = entry.id ?? crypto.randomUUID();
652
+ const eventHandlers = this._hooks.get(hook.event);
653
+ if (eventHandlers) {
654
+ const existingIndex = eventHandlers.findIndex((h) => h.id === entry.id);
655
+ if (existingIndex !== -1) eventHandlers[existingIndex] = entry;
656
+ else {
657
+ const position = options?.position ?? "Bottom";
658
+ if (position === "Top") eventHandlers.unshift(entry);
659
+ else if (position === "Bottom") eventHandlers.push(entry);
660
+ else {
661
+ const index = Math.max(0, Math.min(position, eventHandlers.length));
662
+ eventHandlers.splice(index, 0, entry);
663
+ }
664
+ }
665
+ } else this._hooks.set(hook.event, [entry]);
666
+ return entry;
667
+ }
668
+ /**
669
+ * Alias for onHook. This is provided for compatibility with other libraries that use the `addHook` method.
670
+ * @param {string} event - the event name
671
+ * @param {HookFn} handler - the handler function
672
+ * @returns {void}
673
+ */
674
+ addHook(event, handler) {
675
+ this.onHook({
676
+ event,
677
+ handler
678
+ });
679
+ }
680
+ /**
681
+ * Adds handler functions for specific events
682
+ * @param {Array<IHook>} hooks
683
+ * @param {OnHookOptions} [options] - optional per-call options (e.g., useHookClone override, position)
684
+ * @returns {void}
685
+ */
686
+ onHooks(hooks, options) {
687
+ for (const hook of hooks) this.onHook(hook, options);
688
+ }
689
+ /**
690
+ * Adds a handler function for a specific event that runs before all other handlers.
691
+ * Equivalent to calling `onHook(hook, { position: "Top" })`.
692
+ * @param {IHook} hook - the hook containing event name and handler
693
+ * @param {PrependHookOptions} [options] - optional per-call options (e.g., useHookClone override)
694
+ * @returns {IHook | undefined} the stored hook, or undefined if blocked by deprecation
695
+ */
696
+ prependHook(hook, options) {
697
+ return this.onHook(hook, {
698
+ ...options,
699
+ position: "Top"
700
+ });
701
+ }
702
+ /**
703
+ * Adds a handler that only executes once for a specific event before all other handlers.
704
+ * Equivalent to calling `onHook` with a self-removing wrapper and `{ position: "Top" }`.
705
+ * @param {IHook} hook - the hook containing event name and handler
706
+ * @param {PrependHookOptions} [options] - optional per-call options (e.g., useHookClone override)
707
+ * @returns {IHook | undefined} the stored hook, or undefined if blocked by deprecation
708
+ */
709
+ prependOnceHook(hook, options) {
710
+ const wrappedHandler = async (...arguments_) => {
711
+ this.removeHook({
712
+ event: hook.event,
713
+ handler: wrappedHandler
714
+ });
715
+ return hook.handler(...arguments_);
716
+ };
717
+ return this.onHook({
718
+ id: hook.id,
719
+ event: hook.event,
720
+ handler: wrappedHandler
721
+ }, {
722
+ ...options,
723
+ position: "Top"
724
+ });
725
+ }
726
+ /**
727
+ * Adds a handler that only executes once for a specific event
728
+ * @param {IHook} hook - the hook containing event name and handler
729
+ */
730
+ onceHook(hook) {
731
+ this.validateHookName(hook.event);
732
+ if (!this.checkDeprecatedHook(hook.event)) return;
733
+ const wrappedHandler = async (...arguments_) => {
734
+ this.removeHook({
735
+ event: hook.event,
736
+ handler: wrappedHandler
737
+ });
738
+ return hook.handler(...arguments_);
739
+ };
740
+ this.onHook({
741
+ id: hook.id,
742
+ event: hook.event,
743
+ handler: wrappedHandler
744
+ });
745
+ }
746
+ /**
747
+ * Removes a handler function for a specific event
748
+ * @param {IHook} hook - the hook containing event name and handler to remove
749
+ * @returns {IHook | undefined} the removed hook, or undefined if not found
750
+ */
751
+ removeHook(hook) {
752
+ this.validateHookName(hook.event);
753
+ const eventHandlers = this._hooks.get(hook.event);
754
+ if (eventHandlers) {
755
+ const index = eventHandlers.findIndex((h) => h.handler === hook.handler);
756
+ if (index !== -1) {
757
+ eventHandlers.splice(index, 1);
758
+ if (eventHandlers.length === 0) this._hooks.delete(hook.event);
759
+ return {
760
+ event: hook.event,
761
+ handler: hook.handler
762
+ };
763
+ }
764
+ }
765
+ }
766
+ /**
767
+ * Removes multiple hook handlers
768
+ * @param {Array<IHook>} hooks
769
+ * @returns {IHook[]} the hooks that were successfully removed
770
+ */
771
+ removeHooks(hooks) {
772
+ const removed = [];
773
+ for (const hook of hooks) {
774
+ const result = this.removeHook(hook);
775
+ if (result) removed.push(result);
776
+ }
777
+ return removed;
778
+ }
779
+ /**
780
+ * Calls all handlers for a specific event
781
+ * @param {string} event
782
+ * @param {T[]} arguments_
783
+ * @returns {Promise<void>}
784
+ */
785
+ async hook(event, ...arguments_) {
786
+ this.validateHookName(event);
787
+ if (!this.checkDeprecatedHook(event)) return;
788
+ const eventHandlers = this._hooks.get(event);
789
+ if (eventHandlers) for (const hook of [...eventHandlers]) try {
790
+ await hook.handler(...arguments_);
791
+ } catch (error) {
792
+ const message = `${event}: ${error.message}`;
793
+ this.emit("error", new Error(message));
794
+ if (this._throwOnHookError) throw new Error(message);
795
+ }
796
+ }
797
+ /**
798
+ * Calls all synchronous handlers for a specific event.
799
+ * Async handlers (declared with `async` keyword) are silently skipped.
800
+ *
801
+ * Note: The `hook` method is preferred as it executes both sync and async functions.
802
+ * Use `hookSync` only when you specifically need synchronous execution.
803
+ * @param {string} event
804
+ * @param {T[]} arguments_
805
+ * @returns {void}
806
+ */
807
+ hookSync(event, ...arguments_) {
808
+ this.validateHookName(event);
809
+ if (!this.checkDeprecatedHook(event)) return;
810
+ const eventHandlers = this._hooks.get(event);
811
+ if (eventHandlers) for (const hook of [...eventHandlers]) {
812
+ if (hook.handler.constructor.name === "AsyncFunction") continue;
813
+ try {
814
+ hook.handler(...arguments_);
815
+ } catch (error) {
816
+ const message = `${event}: ${error.message}`;
817
+ this.emit("error", new Error(message));
818
+ if (this._throwOnHookError) throw new Error(message);
819
+ }
820
+ }
821
+ }
822
+ /**
823
+ * Prepends the word `before` to your hook. Example is event is `test`, the before hook is `before:test`.
824
+ * @param {string} event - The event name
825
+ * @param {T[]} arguments_ - The arguments to pass to the hook
826
+ */
827
+ async beforeHook(event, ...arguments_) {
828
+ await this.hook(`before:${event}`, ...arguments_);
829
+ }
830
+ /**
831
+ * Prepends the word `after` to your hook. Example is event is `test`, the after hook is `after:test`.
832
+ * @param {string} event - The event name
833
+ * @param {T[]} arguments_ - The arguments to pass to the hook
834
+ */
835
+ async afterHook(event, ...arguments_) {
836
+ await this.hook(`after:${event}`, ...arguments_);
837
+ }
838
+ /**
839
+ * Calls all handlers for a specific event. This is an alias for `hook` and is provided for
840
+ * compatibility with other libraries that use the `callHook` method.
841
+ * @param {string} event
842
+ * @param {T[]} arguments_
843
+ * @returns {Promise<void>}
844
+ */
845
+ async callHook(event, ...arguments_) {
846
+ await this.hook(event, ...arguments_);
847
+ }
848
+ /**
849
+ * Gets all hooks for a specific event
850
+ * @param {string} event
851
+ * @returns {IHook[]}
852
+ */
853
+ getHooks(event) {
854
+ this.validateHookName(event);
855
+ return this._hooks.get(event);
856
+ }
857
+ /**
858
+ * Gets a specific hook by id, searching across all events
859
+ * @param {string} id - the hook id
860
+ * @returns {IHook | undefined} the hook if found, or undefined
861
+ */
862
+ getHook(id) {
863
+ for (const eventHandlers of this._hooks.values()) {
864
+ const found = eventHandlers.find((h) => h.id === id);
865
+ if (found) return found;
866
+ }
867
+ }
868
+ /**
869
+ * Removes one or more hooks by id, searching across all events
870
+ * @param {string | string[]} id - the hook id or array of hook ids to remove
871
+ * @returns {IHook | IHook[] | undefined} the removed hook(s), or undefined/empty array if not found
872
+ */
873
+ removeHookById(id) {
874
+ if (Array.isArray(id)) {
875
+ const removed = [];
876
+ for (const singleId of id) {
877
+ const result = this.removeHookById(singleId);
878
+ if (result && !Array.isArray(result)) removed.push(result);
879
+ }
880
+ return removed;
881
+ }
882
+ for (const [event, eventHandlers] of this._hooks.entries()) {
883
+ const index = eventHandlers.findIndex((h) => h.id === id);
884
+ if (index !== -1) {
885
+ const [removed] = eventHandlers.splice(index, 1);
886
+ if (eventHandlers.length === 0) this._hooks.delete(event);
887
+ return removed;
888
+ }
889
+ }
890
+ }
891
+ /**
892
+ * Removes all hooks
893
+ * @returns {void}
894
+ */
895
+ clearHooks() {
896
+ this._hooks.clear();
897
+ }
898
+ /**
899
+ * Removes all hooks for a specific event and returns the removed hooks.
900
+ * @param {string} event - The event name to remove hooks for.
901
+ * @returns {IHook[]} the hooks that were removed
902
+ */
903
+ removeEventHooks(event) {
904
+ this.validateHookName(event);
905
+ const eventHandlers = this._hooks.get(event);
906
+ if (eventHandlers) {
907
+ const removed = [...eventHandlers];
908
+ this._hooks.delete(event);
909
+ return removed;
910
+ }
911
+ return [];
912
+ }
913
+ /**
914
+ * Validates hook event name if enforceBeforeAfter is enabled
915
+ * @param {string} event - The event name to validate
916
+ * @throws {Error} If enforceBeforeAfter is true and event doesn't start with 'before' or 'after'
917
+ */
918
+ validateHookName(event) {
919
+ if (this._enforceBeforeAfter) {
920
+ const eventValue = event.trim().toLocaleLowerCase();
921
+ if (!eventValue.startsWith("before") && !eventValue.startsWith("after")) throw new Error(`Hook event "${event}" must start with "before" or "after" when enforceBeforeAfter is enabled`);
922
+ }
923
+ }
924
+ /**
925
+ * Checks if a hook is deprecated and emits a warning if it is
926
+ * @param {string} event - The event name to check
927
+ * @returns {boolean} - Returns true if the hook should proceed, false if it should be blocked
928
+ */
929
+ checkDeprecatedHook(event) {
930
+ if (this._deprecatedHooks.has(event)) {
931
+ const message = this._deprecatedHooks.get(event);
932
+ const warningMessage = `Hook "${event}" is deprecated${message ? `: ${message}` : ""}`;
933
+ this.emit("warn", {
934
+ hook: event,
935
+ message: warningMessage
936
+ });
937
+ return this._allowDeprecated;
938
+ }
939
+ return true;
940
+ }
941
+ };
942
+ //#endregion
943
+ exports.Eventified = Eventified;
944
+ exports.Hook = Hook;
945
+ exports.Hookified = Hookified;
946
+ exports.ParallelHook = ParallelHook;
947
+ exports.WaterfallHook = WaterfallHook;
948
+ return exports;
949
+ })({});
6
950
 
7
- // src/eventified.ts
8
- var ERROR_EVENT = "error";
9
- var Eventified = class {
10
- constructor(options) {
11
- __publicField(this, "_eventListeners");
12
- __publicField(this, "_maxListeners");
13
- __publicField(this, "_eventLogger");
14
- __publicField(this, "_throwOnEmitError", false);
15
- __publicField(this, "_throwOnEmptyListeners", true);
16
- this._eventListeners = /* @__PURE__ */ new Map();
17
- this._maxListeners = 0;
18
- this._eventLogger = options?.eventLogger;
19
- if (options?.throwOnEmitError !== void 0) {
20
- this._throwOnEmitError = options.throwOnEmitError;
21
- }
22
- if (options?.throwOnEmptyListeners !== void 0) {
23
- this._throwOnEmptyListeners = options.throwOnEmptyListeners;
24
- }
25
- }
26
- /**
27
- * Gets the event logger
28
- * @returns {Logger}
29
- */
30
- get eventLogger() {
31
- return this._eventLogger;
32
- }
33
- /**
34
- * Sets the event logger
35
- * @param {Logger} eventLogger
36
- */
37
- set eventLogger(eventLogger) {
38
- this._eventLogger = eventLogger;
39
- }
40
- /**
41
- * Gets whether an error should be thrown when an emit throws an error. Default is false and only emits an error event.
42
- * @returns {boolean}
43
- */
44
- get throwOnEmitError() {
45
- return this._throwOnEmitError;
46
- }
47
- /**
48
- * Sets whether an error should be thrown when an emit throws an error. Default is false and only emits an error event.
49
- * @param {boolean} value
50
- */
51
- set throwOnEmitError(value) {
52
- this._throwOnEmitError = value;
53
- }
54
- /**
55
- * Gets whether an error should be thrown when emitting 'error' event with no listeners. Default is false.
56
- * @returns {boolean}
57
- */
58
- get throwOnEmptyListeners() {
59
- return this._throwOnEmptyListeners;
60
- }
61
- /**
62
- * Sets whether an error should be thrown when emitting 'error' event with no listeners. Default is false.
63
- * @param {boolean} value
64
- */
65
- set throwOnEmptyListeners(value) {
66
- this._throwOnEmptyListeners = value;
67
- }
68
- /**
69
- * Adds a handler function for a specific event that will run only once
70
- * @param {string | symbol} eventName
71
- * @param {EventListener} listener
72
- * @returns {IEventEmitter} returns the instance of the class for chaining
73
- */
74
- once(eventName, listener) {
75
- const onceListener = (...arguments_) => {
76
- this.off(eventName, onceListener);
77
- listener(...arguments_);
78
- };
79
- this.on(eventName, onceListener);
80
- return this;
81
- }
82
- /**
83
- * Gets the number of listeners for a specific event. If no event is provided, it returns the total number of listeners
84
- * @param {string} eventName The event name. Not required
85
- * @returns {number} The number of listeners
86
- */
87
- listenerCount(eventName) {
88
- if (eventName === void 0) {
89
- let count = 0;
90
- for (const entry2 of this._eventListeners.values()) {
91
- count += typeof entry2 === "function" ? 1 : entry2.length;
92
- }
93
- return count;
94
- }
95
- const entry = this._eventListeners.get(eventName);
96
- if (entry === void 0) {
97
- return 0;
98
- }
99
- return typeof entry === "function" ? 1 : entry.length;
100
- }
101
- /**
102
- * Gets an array of event names
103
- * @returns {Array<string | symbol>} An array of event names
104
- */
105
- eventNames() {
106
- return [...this._eventListeners.keys()];
107
- }
108
- /**
109
- * Gets an array of listeners for a specific event. If no event is provided, it returns all listeners
110
- * @param {string} [event] (Optional) The event name
111
- * @returns {EventListener[]} An array of listeners
112
- */
113
- rawListeners(event) {
114
- if (event === void 0) {
115
- return this.getAllListeners();
116
- }
117
- const entry = this._eventListeners.get(event);
118
- if (entry === void 0) {
119
- return [];
120
- }
121
- return typeof entry === "function" ? [entry] : entry;
122
- }
123
- /**
124
- * Prepends a listener to the beginning of the listeners array for the specified event
125
- * @param {string | symbol} eventName
126
- * @param {EventListener} listener
127
- * @returns {IEventEmitter} returns the instance of the class for chaining
128
- */
129
- prependListener(eventName, listener) {
130
- const existing = this._eventListeners.get(eventName);
131
- if (existing === void 0) {
132
- this._eventListeners.set(eventName, listener);
133
- } else if (typeof existing === "function") {
134
- this._eventListeners.set(eventName, [listener, existing]);
135
- } else {
136
- existing.unshift(listener);
137
- }
138
- return this;
139
- }
140
- /**
141
- * Prepends a one-time listener to the beginning of the listeners array for the specified event
142
- * @param {string | symbol} eventName
143
- * @param {EventListener} listener
144
- * @returns {IEventEmitter} returns the instance of the class for chaining
145
- */
146
- prependOnceListener(eventName, listener) {
147
- const onceListener = (...arguments_) => {
148
- this.off(eventName, onceListener);
149
- listener(...arguments_);
150
- };
151
- this.prependListener(eventName, onceListener);
152
- return this;
153
- }
154
- /**
155
- * Gets the maximum number of listeners that can be added for a single event
156
- * @returns {number} The maximum number of listeners
157
- */
158
- maxListeners() {
159
- return this._maxListeners;
160
- }
161
- /**
162
- * Adds a listener for a specific event. It is an alias for the on() method
163
- * @param {string | symbol} event
164
- * @param {EventListener} listener
165
- * @returns {IEventEmitter} returns the instance of the class for chaining
166
- */
167
- addListener(event, listener) {
168
- this.on(event, listener);
169
- return this;
170
- }
171
- /**
172
- * Adds a listener for a specific event
173
- * @param {string | symbol} event
174
- * @param {EventListener} listener
175
- * @returns {IEventEmitter} returns the instance of the class for chaining
176
- */
177
- on(event, listener) {
178
- const existing = this._eventListeners.get(event);
179
- if (existing === void 0) {
180
- this._eventListeners.set(event, listener);
181
- return this;
182
- }
183
- if (typeof existing === "function") {
184
- const arr = [existing, listener];
185
- this._eventListeners.set(event, arr);
186
- if (this._maxListeners > 0 && arr.length > this._maxListeners) {
187
- console.warn(
188
- `MaxListenersExceededWarning: Possible event memory leak detected. ${arr.length} ${event} listeners added. Use setMaxListeners() to increase limit.`
189
- );
190
- }
191
- } else {
192
- existing.push(listener);
193
- if (this._maxListeners > 0 && existing.length > this._maxListeners) {
194
- console.warn(
195
- `MaxListenersExceededWarning: Possible event memory leak detected. ${existing.length} ${event} listeners added. Use setMaxListeners() to increase limit.`
196
- );
197
- }
198
- }
199
- return this;
200
- }
201
- /**
202
- * Removes a listener for a specific event. It is an alias for the off() method
203
- * @param {string | symbol} event
204
- * @param {EventListener} listener
205
- * @returns {IEventEmitter} returns the instance of the class for chaining
206
- */
207
- removeListener(event, listener) {
208
- this.off(event, listener);
209
- return this;
210
- }
211
- /**
212
- * Removes a listener for a specific event
213
- * @param {string | symbol} event
214
- * @param {EventListener} listener
215
- * @returns {IEventEmitter} returns the instance of the class for chaining
216
- */
217
- off(event, listener) {
218
- const entry = this._eventListeners.get(event);
219
- if (entry === void 0) {
220
- return this;
221
- }
222
- if (typeof entry === "function") {
223
- if (entry === listener) {
224
- this._eventListeners.delete(event);
225
- }
226
- return this;
227
- }
228
- const index = entry.indexOf(listener);
229
- if (index !== -1) {
230
- if (entry.length === 2) {
231
- this._eventListeners.set(event, entry[1 - index]);
232
- } else if (entry.length === 1) {
233
- this._eventListeners.delete(event);
234
- } else {
235
- entry.splice(index, 1);
236
- }
237
- }
238
- return this;
239
- }
240
- /**
241
- * Calls all listeners for a specific event
242
- * @param {string | symbol} event
243
- * @param arguments_ The arguments to pass to the listeners
244
- * @returns {boolean} Returns true if the event had listeners, false otherwise
245
- */
246
- emit(event, ...arguments_) {
247
- let result = false;
248
- const entry = this._eventListeners.get(event);
249
- const argumentLength = arguments_.length;
250
- if (entry !== void 0) {
251
- if (typeof entry === "function") {
252
- if (argumentLength === 1) {
253
- entry(arguments_[0]);
254
- } else if (argumentLength === 2) {
255
- entry(arguments_[0], arguments_[1]);
256
- } else {
257
- entry(...arguments_);
258
- }
259
- } else {
260
- const len = entry.length;
261
- for (let i = 0; i < len; i++) {
262
- if (argumentLength === 1) {
263
- entry[i](arguments_[0]);
264
- } else if (argumentLength === 2) {
265
- entry[i](arguments_[0], arguments_[1]);
266
- } else {
267
- entry[i](...arguments_);
268
- }
269
- }
270
- }
271
- result = true;
272
- }
273
- if (this._eventLogger) {
274
- this.sendToEventLogger(event, arguments_);
275
- }
276
- if (!result && event === ERROR_EVENT) {
277
- const error = arguments_[0] instanceof Error ? arguments_[0] : new Error(`${arguments_[0]}`);
278
- if (this._throwOnEmitError || this._throwOnEmptyListeners) {
279
- throw error;
280
- }
281
- }
282
- return result;
283
- }
284
- /**
285
- * Gets all listeners for a specific event. If no event is provided, it returns all listeners
286
- * @param {string} [event] (Optional) The event name
287
- * @returns {EventListener[]} An array of listeners
288
- */
289
- listeners(event) {
290
- const entry = this._eventListeners.get(event);
291
- if (entry === void 0) {
292
- return [];
293
- }
294
- return typeof entry === "function" ? [entry] : entry;
295
- }
296
- /**
297
- * Removes all listeners for a specific event. If no event is provided, it removes all listeners
298
- * @param {string} [event] (Optional) The event name
299
- * @returns {IEventEmitter} returns the instance of the class for chaining
300
- */
301
- removeAllListeners(event) {
302
- if (event !== void 0) {
303
- this._eventListeners.delete(event);
304
- } else {
305
- this._eventListeners.clear();
306
- }
307
- return this;
308
- }
309
- /**
310
- * Sets the maximum number of listeners that can be added for a single event
311
- * @param {number} n The maximum number of listeners
312
- * @returns {void}
313
- */
314
- setMaxListeners(n) {
315
- this._maxListeners = n < 0 ? 0 : n;
316
- }
317
- /**
318
- * Gets all listeners
319
- * @returns {EventListener[]} An array of listeners
320
- */
321
- getAllListeners() {
322
- const result = [];
323
- for (const entry of this._eventListeners.values()) {
324
- if (typeof entry === "function") {
325
- result.push(entry);
326
- } else {
327
- for (let i = 0; i < entry.length; i++) {
328
- result.push(entry[i]);
329
- }
330
- }
331
- }
332
- return result;
333
- }
334
- /**
335
- * Sends a log message using the configured logger based on the event name
336
- * @param {string | symbol} eventName - The event name that determines the log level
337
- * @param {unknown} data - The data to log
338
- */
339
- sendToEventLogger(eventName, data) {
340
- if (!this._eventLogger) {
341
- return;
342
- }
343
- let message;
344
- if (typeof data === "string") {
345
- message = data;
346
- } else if (Array.isArray(data) && data.length > 0 && data[0] instanceof Error) {
347
- message = data[0].message;
348
- } else if (data instanceof Error) {
349
- message = data.message;
350
- } else if (Array.isArray(data) && data.length > 0 && typeof data[0]?.message === "string") {
351
- message = data[0].message;
352
- } else {
353
- message = JSON.stringify(data);
354
- }
355
- switch (eventName) {
356
- case "error": {
357
- this._eventLogger.error?.(message, { event: eventName, data });
358
- break;
359
- }
360
- case "warn": {
361
- this._eventLogger.warn?.(message, { event: eventName, data });
362
- break;
363
- }
364
- case "trace": {
365
- this._eventLogger.trace?.(message, { event: eventName, data });
366
- break;
367
- }
368
- case "debug": {
369
- this._eventLogger.debug?.(message, { event: eventName, data });
370
- break;
371
- }
372
- case "fatal": {
373
- this._eventLogger.fatal?.(message, { event: eventName, data });
374
- break;
375
- }
376
- default: {
377
- this._eventLogger.info?.(message, { event: eventName, data });
378
- break;
379
- }
380
- }
381
- }
382
- };
383
-
384
- // src/hooks/hook.ts
385
- var Hook = class {
386
- /**
387
- * Creates a new Hook instance
388
- * @param {string} event - The event name for the hook
389
- * @param {HookFn} handler - The handler function for the hook
390
- * @param {string} [id] - Optional unique identifier for the hook
391
- */
392
- constructor(event, handler, id) {
393
- __publicField(this, "id");
394
- __publicField(this, "event");
395
- __publicField(this, "handler");
396
- this.id = id;
397
- this.event = event;
398
- this.handler = handler;
399
- }
400
- };
401
-
402
- // src/hooks/waterfall-hook.ts
403
- var WaterfallHook = class {
404
- /**
405
- * Creates a new WaterfallHook instance
406
- * @param {string} event - The event name for the hook
407
- * @param {WaterfallHookFn} finalHandler - The final handler function that receives the transformed result
408
- * @param {string} [id] - Optional unique identifier for the hook
409
- */
410
- constructor(event, finalHandler, id) {
411
- __publicField(this, "id");
412
- __publicField(this, "event");
413
- __publicField(this, "handler");
414
- __publicField(this, "hooks");
415
- __publicField(this, "_finalHandler");
416
- this.id = id;
417
- this.event = event;
418
- this.hooks = [];
419
- this._finalHandler = finalHandler;
420
- this.handler = async (...arguments_) => {
421
- const initialArgs = arguments_.length === 1 ? arguments_[0] : arguments_;
422
- const results = [];
423
- for (const hook of this.hooks) {
424
- const result = await hook({ initialArgs, results: [...results] });
425
- results.push({ hook, result });
426
- }
427
- await this._finalHandler({ initialArgs, results: [...results] });
428
- };
429
- }
430
- /**
431
- * Adds a hook function to the end of the waterfall chain
432
- * @param {WaterfallHookFn} hook - The hook function to add
433
- */
434
- addHook(hook) {
435
- this.hooks.push(hook);
436
- }
437
- /**
438
- * Removes a specific hook function from the waterfall chain
439
- * @param {WaterfallHookFn} hook - The hook function to remove
440
- * @returns {boolean} true if the hook was found and removed
441
- */
442
- removeHook(hook) {
443
- const index = this.hooks.indexOf(hook);
444
- if (index !== -1) {
445
- this.hooks.splice(index, 1);
446
- return true;
447
- }
448
- return false;
449
- }
450
- };
451
-
452
- // src/index.ts
453
- var Hookified = class extends Eventified {
454
- constructor(options) {
455
- super({
456
- eventLogger: options?.eventLogger,
457
- throwOnEmitError: options?.throwOnEmitError,
458
- throwOnEmptyListeners: options?.throwOnEmptyListeners
459
- });
460
- __publicField(this, "_hooks");
461
- __publicField(this, "_throwOnHookError", false);
462
- __publicField(this, "_enforceBeforeAfter", false);
463
- __publicField(this, "_deprecatedHooks");
464
- __publicField(this, "_allowDeprecated", true);
465
- __publicField(this, "_useHookClone", true);
466
- this._hooks = /* @__PURE__ */ new Map();
467
- this._deprecatedHooks = options?.deprecatedHooks ? new Map(options.deprecatedHooks) : /* @__PURE__ */ new Map();
468
- if (options?.throwOnHookError !== void 0) {
469
- this._throwOnHookError = options.throwOnHookError;
470
- }
471
- if (options?.enforceBeforeAfter !== void 0) {
472
- this._enforceBeforeAfter = options.enforceBeforeAfter;
473
- }
474
- if (options?.allowDeprecated !== void 0) {
475
- this._allowDeprecated = options.allowDeprecated;
476
- }
477
- if (options?.useHookClone !== void 0) {
478
- this._useHookClone = options.useHookClone;
479
- }
480
- }
481
- /**
482
- * Gets all hooks
483
- * @returns {Map<string, IHook[]>}
484
- */
485
- get hooks() {
486
- return this._hooks;
487
- }
488
- /**
489
- * Gets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.
490
- * @returns {boolean}
491
- */
492
- get throwOnHookError() {
493
- return this._throwOnHookError;
494
- }
495
- /**
496
- * Sets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.
497
- * @param {boolean} value
498
- */
499
- set throwOnHookError(value) {
500
- this._throwOnHookError = value;
501
- }
502
- /**
503
- * Gets whether to enforce that all hook names start with 'before' or 'after'. Default is false.
504
- * @returns {boolean}
505
- * @default false
506
- */
507
- get enforceBeforeAfter() {
508
- return this._enforceBeforeAfter;
509
- }
510
- /**
511
- * Sets whether to enforce that all hook names start with 'before' or 'after'. Default is false.
512
- * @param {boolean} value
513
- */
514
- set enforceBeforeAfter(value) {
515
- this._enforceBeforeAfter = value;
516
- }
517
- /**
518
- * Gets the map of deprecated hook names to deprecation messages.
519
- * @returns {Map<string, string>}
520
- */
521
- get deprecatedHooks() {
522
- return this._deprecatedHooks;
523
- }
524
- /**
525
- * Sets the map of deprecated hook names to deprecation messages.
526
- * @param {Map<string, string>} value
527
- */
528
- set deprecatedHooks(value) {
529
- this._deprecatedHooks = value;
530
- }
531
- /**
532
- * Gets whether deprecated hooks are allowed to be registered and executed. Default is true.
533
- * @returns {boolean}
534
- */
535
- get allowDeprecated() {
536
- return this._allowDeprecated;
537
- }
538
- /**
539
- * Sets whether deprecated hooks are allowed to be registered and executed. Default is true.
540
- * @param {boolean} value
541
- */
542
- set allowDeprecated(value) {
543
- this._allowDeprecated = value;
544
- }
545
- /**
546
- * Gets whether hook objects are cloned before storing. Default is true.
547
- * @returns {boolean}
548
- */
549
- get useHookClone() {
550
- return this._useHookClone;
551
- }
552
- /**
553
- * Sets whether hook objects are cloned before storing. Default is true.
554
- * When false, the original IHook reference is stored directly.
555
- * @param {boolean} value
556
- */
557
- set useHookClone(value) {
558
- this._useHookClone = value;
559
- }
560
- onHook(hookOrEvent, optionsOrHandler) {
561
- let hook;
562
- let options;
563
- if (typeof hookOrEvent === "string") {
564
- if (typeof optionsOrHandler !== "function") {
565
- throw new TypeError(
566
- "When calling onHook(event, handler), the second argument must be a function"
567
- );
568
- }
569
- hook = { event: hookOrEvent, handler: optionsOrHandler };
570
- options = void 0;
571
- } else {
572
- hook = hookOrEvent;
573
- options = optionsOrHandler;
574
- }
575
- this.validateHookName(hook.event);
576
- if (!this.checkDeprecatedHook(hook.event)) {
577
- return void 0;
578
- }
579
- const shouldClone = options?.useHookClone ?? this._useHookClone;
580
- const entry = shouldClone ? { id: hook.id, event: hook.event, handler: hook.handler } : hook;
581
- entry.id = entry.id ?? crypto.randomUUID();
582
- const eventHandlers = this._hooks.get(hook.event);
583
- if (eventHandlers) {
584
- const existingIndex = eventHandlers.findIndex((h) => h.id === entry.id);
585
- if (existingIndex !== -1) {
586
- eventHandlers[existingIndex] = entry;
587
- } else {
588
- const position = options?.position ?? "Bottom";
589
- if (position === "Top") {
590
- eventHandlers.unshift(entry);
591
- } else if (position === "Bottom") {
592
- eventHandlers.push(entry);
593
- } else {
594
- const index = Math.max(0, Math.min(position, eventHandlers.length));
595
- eventHandlers.splice(index, 0, entry);
596
- }
597
- }
598
- } else {
599
- this._hooks.set(hook.event, [entry]);
600
- }
601
- return entry;
602
- }
603
- /**
604
- * Alias for onHook. This is provided for compatibility with other libraries that use the `addHook` method.
605
- * @param {string} event - the event name
606
- * @param {HookFn} handler - the handler function
607
- * @returns {void}
608
- */
609
- addHook(event, handler) {
610
- this.onHook({ event, handler });
611
- }
612
- /**
613
- * Adds handler functions for specific events
614
- * @param {Array<IHook>} hooks
615
- * @param {OnHookOptions} [options] - optional per-call options (e.g., useHookClone override, position)
616
- * @returns {void}
617
- */
618
- onHooks(hooks, options) {
619
- for (const hook of hooks) {
620
- this.onHook(hook, options);
621
- }
622
- }
623
- /**
624
- * Adds a handler function for a specific event that runs before all other handlers.
625
- * Equivalent to calling `onHook(hook, { position: "Top" })`.
626
- * @param {IHook} hook - the hook containing event name and handler
627
- * @param {PrependHookOptions} [options] - optional per-call options (e.g., useHookClone override)
628
- * @returns {IHook | undefined} the stored hook, or undefined if blocked by deprecation
629
- */
630
- prependHook(hook, options) {
631
- return this.onHook(hook, { ...options, position: "Top" });
632
- }
633
- /**
634
- * Adds a handler that only executes once for a specific event before all other handlers.
635
- * Equivalent to calling `onHook` with a self-removing wrapper and `{ position: "Top" }`.
636
- * @param {IHook} hook - the hook containing event name and handler
637
- * @param {PrependHookOptions} [options] - optional per-call options (e.g., useHookClone override)
638
- * @returns {IHook | undefined} the stored hook, or undefined if blocked by deprecation
639
- */
640
- prependOnceHook(hook, options) {
641
- const wrappedHandler = async (...arguments_) => {
642
- this.removeHook({ event: hook.event, handler: wrappedHandler });
643
- return hook.handler(...arguments_);
644
- };
645
- return this.onHook(
646
- { id: hook.id, event: hook.event, handler: wrappedHandler },
647
- { ...options, position: "Top" }
648
- );
649
- }
650
- /**
651
- * Adds a handler that only executes once for a specific event
652
- * @param {IHook} hook - the hook containing event name and handler
653
- */
654
- onceHook(hook) {
655
- this.validateHookName(hook.event);
656
- if (!this.checkDeprecatedHook(hook.event)) {
657
- return;
658
- }
659
- const wrappedHandler = async (...arguments_) => {
660
- this.removeHook({ event: hook.event, handler: wrappedHandler });
661
- return hook.handler(...arguments_);
662
- };
663
- this.onHook({ id: hook.id, event: hook.event, handler: wrappedHandler });
664
- }
665
- /**
666
- * Removes a handler function for a specific event
667
- * @param {IHook} hook - the hook containing event name and handler to remove
668
- * @returns {IHook | undefined} the removed hook, or undefined if not found
669
- */
670
- removeHook(hook) {
671
- this.validateHookName(hook.event);
672
- const eventHandlers = this._hooks.get(hook.event);
673
- if (eventHandlers) {
674
- const index = eventHandlers.findIndex((h) => h.handler === hook.handler);
675
- if (index !== -1) {
676
- eventHandlers.splice(index, 1);
677
- if (eventHandlers.length === 0) {
678
- this._hooks.delete(hook.event);
679
- }
680
- return { event: hook.event, handler: hook.handler };
681
- }
682
- }
683
- return void 0;
684
- }
685
- /**
686
- * Removes multiple hook handlers
687
- * @param {Array<IHook>} hooks
688
- * @returns {IHook[]} the hooks that were successfully removed
689
- */
690
- removeHooks(hooks) {
691
- const removed = [];
692
- for (const hook of hooks) {
693
- const result = this.removeHook(hook);
694
- if (result) {
695
- removed.push(result);
696
- }
697
- }
698
- return removed;
699
- }
700
- /**
701
- * Calls all handlers for a specific event
702
- * @param {string} event
703
- * @param {T[]} arguments_
704
- * @returns {Promise<void>}
705
- */
706
- async hook(event, ...arguments_) {
707
- this.validateHookName(event);
708
- if (!this.checkDeprecatedHook(event)) {
709
- return;
710
- }
711
- const eventHandlers = this._hooks.get(event);
712
- if (eventHandlers) {
713
- for (const hook of [...eventHandlers]) {
714
- try {
715
- await hook.handler(...arguments_);
716
- } catch (error) {
717
- const message = `${event}: ${error.message}`;
718
- this.emit("error", new Error(message));
719
- if (this._throwOnHookError) {
720
- throw new Error(message);
721
- }
722
- }
723
- }
724
- }
725
- }
726
- /**
727
- * Calls all synchronous handlers for a specific event.
728
- * Async handlers (declared with `async` keyword) are silently skipped.
729
- *
730
- * Note: The `hook` method is preferred as it executes both sync and async functions.
731
- * Use `hookSync` only when you specifically need synchronous execution.
732
- * @param {string} event
733
- * @param {T[]} arguments_
734
- * @returns {void}
735
- */
736
- hookSync(event, ...arguments_) {
737
- this.validateHookName(event);
738
- if (!this.checkDeprecatedHook(event)) {
739
- return;
740
- }
741
- const eventHandlers = this._hooks.get(event);
742
- if (eventHandlers) {
743
- for (const hook of [...eventHandlers]) {
744
- if (hook.handler.constructor.name === "AsyncFunction") {
745
- continue;
746
- }
747
- try {
748
- hook.handler(...arguments_);
749
- } catch (error) {
750
- const message = `${event}: ${error.message}`;
751
- this.emit("error", new Error(message));
752
- if (this._throwOnHookError) {
753
- throw new Error(message);
754
- }
755
- }
756
- }
757
- }
758
- }
759
- /**
760
- * Prepends the word `before` to your hook. Example is event is `test`, the before hook is `before:test`.
761
- * @param {string} event - The event name
762
- * @param {T[]} arguments_ - The arguments to pass to the hook
763
- */
764
- async beforeHook(event, ...arguments_) {
765
- await this.hook(`before:${event}`, ...arguments_);
766
- }
767
- /**
768
- * Prepends the word `after` to your hook. Example is event is `test`, the after hook is `after:test`.
769
- * @param {string} event - The event name
770
- * @param {T[]} arguments_ - The arguments to pass to the hook
771
- */
772
- async afterHook(event, ...arguments_) {
773
- await this.hook(`after:${event}`, ...arguments_);
774
- }
775
- /**
776
- * Calls all handlers for a specific event. This is an alias for `hook` and is provided for
777
- * compatibility with other libraries that use the `callHook` method.
778
- * @param {string} event
779
- * @param {T[]} arguments_
780
- * @returns {Promise<void>}
781
- */
782
- async callHook(event, ...arguments_) {
783
- await this.hook(event, ...arguments_);
784
- }
785
- /**
786
- * Gets all hooks for a specific event
787
- * @param {string} event
788
- * @returns {IHook[]}
789
- */
790
- getHooks(event) {
791
- this.validateHookName(event);
792
- return this._hooks.get(event);
793
- }
794
- /**
795
- * Gets a specific hook by id, searching across all events
796
- * @param {string} id - the hook id
797
- * @returns {IHook | undefined} the hook if found, or undefined
798
- */
799
- getHook(id) {
800
- for (const eventHandlers of this._hooks.values()) {
801
- const found = eventHandlers.find((h) => h.id === id);
802
- if (found) {
803
- return found;
804
- }
805
- }
806
- return void 0;
807
- }
808
- /**
809
- * Removes one or more hooks by id, searching across all events
810
- * @param {string | string[]} id - the hook id or array of hook ids to remove
811
- * @returns {IHook | IHook[] | undefined} the removed hook(s), or undefined/empty array if not found
812
- */
813
- removeHookById(id) {
814
- if (Array.isArray(id)) {
815
- const removed = [];
816
- for (const singleId of id) {
817
- const result = this.removeHookById(singleId);
818
- if (result && !Array.isArray(result)) {
819
- removed.push(result);
820
- }
821
- }
822
- return removed;
823
- }
824
- for (const [event, eventHandlers] of this._hooks.entries()) {
825
- const index = eventHandlers.findIndex((h) => h.id === id);
826
- if (index !== -1) {
827
- const [removed] = eventHandlers.splice(index, 1);
828
- if (eventHandlers.length === 0) {
829
- this._hooks.delete(event);
830
- }
831
- return removed;
832
- }
833
- }
834
- return void 0;
835
- }
836
- /**
837
- * Removes all hooks
838
- * @returns {void}
839
- */
840
- clearHooks() {
841
- this._hooks.clear();
842
- }
843
- /**
844
- * Removes all hooks for a specific event and returns the removed hooks.
845
- * @param {string} event - The event name to remove hooks for.
846
- * @returns {IHook[]} the hooks that were removed
847
- */
848
- removeEventHooks(event) {
849
- this.validateHookName(event);
850
- const eventHandlers = this._hooks.get(event);
851
- if (eventHandlers) {
852
- const removed = [...eventHandlers];
853
- this._hooks.delete(event);
854
- return removed;
855
- }
856
- return [];
857
- }
858
- /**
859
- * Validates hook event name if enforceBeforeAfter is enabled
860
- * @param {string} event - The event name to validate
861
- * @throws {Error} If enforceBeforeAfter is true and event doesn't start with 'before' or 'after'
862
- */
863
- validateHookName(event) {
864
- if (this._enforceBeforeAfter) {
865
- const eventValue = event.trim().toLocaleLowerCase();
866
- if (!eventValue.startsWith("before") && !eventValue.startsWith("after")) {
867
- throw new Error(
868
- `Hook event "${event}" must start with "before" or "after" when enforceBeforeAfter is enabled`
869
- );
870
- }
871
- }
872
- }
873
- /**
874
- * Checks if a hook is deprecated and emits a warning if it is
875
- * @param {string} event - The event name to check
876
- * @returns {boolean} - Returns true if the hook should proceed, false if it should be blocked
877
- */
878
- checkDeprecatedHook(event) {
879
- if (this._deprecatedHooks.has(event)) {
880
- const message = this._deprecatedHooks.get(event);
881
- const warningMessage = `Hook "${event}" is deprecated${message ? `: ${message}` : ""}`;
882
- this.emit("warn", { hook: event, message: warningMessage });
883
- return this._allowDeprecated;
884
- }
885
- return true;
886
- }
887
- };
888
- })();
889
- /* v8 ignore start -- @preserve: single-element arrays are stored as functions */
890
- /* v8 ignore next 3 -- @preserve: guarded by caller */
891
- /* v8 ignore next -- @preserve */
892
951
  //# sourceMappingURL=index.global.js.map