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