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