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