You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

hookified

Package Overview
Dependencies
Maintainers
1
Versions
32
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

hookified - npm Package Compare versions

Comparing version
1.15.1
to
2.0.0
+283
-155
dist/browser/index.global.js

@@ -12,9 +12,9 @@ "use strict";

__publicField(this, "_maxListeners");
__publicField(this, "_logger");
__publicField(this, "_eventLogger");
__publicField(this, "_throwOnEmitError", false);
__publicField(this, "_throwOnEmptyListeners", false);
__publicField(this, "_throwOnEmptyListeners", true);
__publicField(this, "_errorEvent", "error");
this._eventListeners = /* @__PURE__ */ new Map();
this._maxListeners = 100;
this._logger = options?.logger;
this._maxListeners = 0;
this._eventLogger = options?.eventLogger;
if (options?.throwOnEmitError !== void 0) {

@@ -28,14 +28,14 @@ this._throwOnEmitError = options.throwOnEmitError;

/**
* Gets the logger
* Gets the event logger
* @returns {Logger}
*/
get logger() {
return this._logger;
get eventLogger() {
return this._eventLogger;
}
/**
* Sets the logger
* @param {Logger} logger
* Sets the event logger
* @param {Logger} eventLogger
*/
set logger(logger) {
this._logger = logger;
set eventLogger(eventLogger) {
this._eventLogger = eventLogger;
}

@@ -169,3 +169,3 @@ /**

if (listeners) {
if (listeners.length >= this._maxListeners) {
if (this._maxListeners > 0 && listeners.length >= this._maxListeners) {
console.warn(

@@ -221,2 +221,3 @@ `MaxListenersExceededWarning: Possible event memory leak detected. ${listeners.length + 1} ${event} listeners added. Use setMaxListeners() to increase limit.`

}
this.sendToEventLogger(event, arguments_);
if (event === this._errorEvent) {

@@ -232,3 +233,2 @@ const error = arguments_[0] instanceof Error ? arguments_[0] : new Error(`${arguments_[0]}`);

}
this.sendLog(event, arguments_);
return result;

@@ -263,8 +263,3 @@ }

setMaxListeners(n) {
this._maxListeners = n;
for (const listeners of this._eventListeners.values()) {
if (listeners.length > n) {
listeners.splice(n);
}
}
this._maxListeners = n < 0 ? 0 : n;
}

@@ -287,4 +282,4 @@ /**

*/
sendLog(eventName, data) {
if (!this._logger) {
sendToEventLogger(eventName, data) {
if (!this._eventLogger) {
return;

@@ -306,23 +301,23 @@ }

case "error": {
this._logger.error?.(message, { event: eventName, data });
this._eventLogger.error?.(message, { event: eventName, data });
break;
}
case "warn": {
this._logger.warn?.(message, { event: eventName, data });
this._eventLogger.warn?.(message, { event: eventName, data });
break;
}
case "trace": {
this._logger.trace?.(message, { event: eventName, data });
this._eventLogger.trace?.(message, { event: eventName, data });
break;
}
case "debug": {
this._logger.debug?.(message, { event: eventName, data });
this._eventLogger.debug?.(message, { event: eventName, data });
break;
}
case "fatal": {
this._logger.fatal?.(message, { event: eventName, data });
this._eventLogger.fatal?.(message, { event: eventName, data });
break;
}
default: {
this._logger.info?.(message, { event: eventName, data });
this._eventLogger.info?.(message, { event: eventName, data });
break;

@@ -334,2 +329,70 @@ }

// src/hooks/hook.ts
var Hook = class {
/**
* Creates a new Hook instance
* @param {string} event - The event name for the hook
* @param {HookFn} handler - The handler function for the hook
* @param {string} [id] - Optional unique identifier for the hook
*/
constructor(event, handler, id) {
__publicField(this, "id");
__publicField(this, "event");
__publicField(this, "handler");
this.id = id;
this.event = event;
this.handler = handler;
}
};
// src/hooks/waterfall-hook.ts
var WaterfallHook = class {
/**
* Creates a new WaterfallHook instance
* @param {string} event - The event name for the hook
* @param {WaterfallHookFn} finalHandler - The final handler function that receives the transformed result
* @param {string} [id] - Optional unique identifier for the hook
*/
constructor(event, finalHandler, id) {
__publicField(this, "id");
__publicField(this, "event");
__publicField(this, "handler");
__publicField(this, "hooks");
__publicField(this, "_finalHandler");
this.id = id;
this.event = event;
this.hooks = [];
this._finalHandler = finalHandler;
this.handler = async (...arguments_) => {
const initialArgs = arguments_.length === 1 ? arguments_[0] : arguments_;
const results = [];
for (const hook of this.hooks) {
const result = await hook({ initialArgs, results: [...results] });
results.push({ hook, result });
}
await this._finalHandler({ initialArgs, results: [...results] });
};
}
/**
* Adds a hook function to the end of the waterfall chain
* @param {WaterfallHookFn} hook - The hook function to add
*/
addHook(hook) {
this.hooks.push(hook);
}
/**
* Removes a specific hook function from the waterfall chain
* @param {WaterfallHookFn} hook - The hook function to remove
* @returns {boolean} true if the hook was found and removed
*/
removeHook(hook) {
const index = this.hooks.indexOf(hook);
if (index !== -1) {
this.hooks.splice(index, 1);
return true;
}
return false;
}
};
// src/index.ts

@@ -339,3 +402,3 @@ var Hookified = class extends Eventified {

super({
logger: options?.logger,
eventLogger: options?.eventLogger,
throwOnEmitError: options?.throwOnEmitError,

@@ -349,2 +412,3 @@ throwOnEmptyListeners: options?.throwOnEmptyListeners

__publicField(this, "_allowDeprecated", true);
__publicField(this, "_useHookClone", true);
this._hooks = /* @__PURE__ */ new Map();

@@ -354,4 +418,2 @@ this._deprecatedHooks = options?.deprecatedHooks ? new Map(options.deprecatedHooks) : /* @__PURE__ */ new Map();

this._throwOnHookError = options.throwOnHookError;
} else if (options?.throwHookErrors !== void 0) {
this._throwOnHookError = options.throwHookErrors;
}

@@ -364,6 +426,9 @@ if (options?.enforceBeforeAfter !== void 0) {

}
if (options?.useHookClone !== void 0) {
this._useHookClone = options.useHookClone;
}
}
/**
* Gets all hooks
* @returns {Map<string, Hook[]>}
* @returns {Map<string, IHook[]>}
*/

@@ -376,19 +441,3 @@ get hooks() {

* @returns {boolean}
* @deprecated - this will be deprecated in version 2. Please use throwOnHookError.
*/
get throwHookErrors() {
return this._throwOnHookError;
}
/**
* Sets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.
* @param {boolean} value
* @deprecated - this will be deprecated in version 2. Please use throwOnHookError.
*/
set throwHookErrors(value) {
this._throwOnHookError = value;
}
/**
* Gets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.
* @returns {boolean}
*/
get throwOnHookError() {

@@ -448,153 +497,149 @@ return this._throwOnHookError;

/**
* Validates hook event name if enforceBeforeAfter is enabled
* @param {string} event - The event name to validate
* @throws {Error} If enforceBeforeAfter is true and event doesn't start with 'before' or 'after'
* Gets whether hook objects are cloned before storing. Default is true.
* @returns {boolean}
*/
validateHookName(event) {
if (this._enforceBeforeAfter) {
const eventValue = event.trim().toLocaleLowerCase();
if (!eventValue.startsWith("before") && !eventValue.startsWith("after")) {
throw new Error(
`Hook event "${event}" must start with "before" or "after" when enforceBeforeAfter is enabled`
);
}
}
get useHookClone() {
return this._useHookClone;
}
/**
* Checks if a hook is deprecated and emits a warning if it is
* @param {string} event - The event name to check
* @returns {boolean} - Returns true if the hook should proceed, false if it should be blocked
* Sets whether hook objects are cloned before storing. Default is true.
* When false, the original IHook reference is stored directly.
* @param {boolean} value
*/
checkDeprecatedHook(event) {
if (this._deprecatedHooks.has(event)) {
const message = this._deprecatedHooks.get(event);
const warningMessage = `Hook "${event}" is deprecated${message ? `: ${message}` : ""}`;
this.emit("warn", { hook: event, message: warningMessage });
return this._allowDeprecated;
}
return true;
set useHookClone(value) {
this._useHookClone = value;
}
/**
* Adds a handler function for a specific event
* @param {string} event
* @param {Hook} handler - this can be async or sync
* @returns {void}
* Adds a handler function for a specific event.
* If you prefer the legacy `(event, handler)` signature, use {@link addHook} instead.
* To register multiple hooks at once, use {@link onHooks}.
* @param {IHook} hook - the hook containing event name and handler
* @param {OnHookOptions} [options] - optional per-call options (e.g., useHookClone override, position)
* @returns {IHook | undefined} the stored hook, or undefined if blocked by deprecation
*/
onHook(event, handler) {
this.onHookEntry({ event, handler });
}
/**
* Adds a handler function for a specific event
* @param {HookEntry} hookEntry
* @returns {void}
*/
onHookEntry(hookEntry) {
this.validateHookName(hookEntry.event);
if (!this.checkDeprecatedHook(hookEntry.event)) {
return;
onHook(hook, options) {
this.validateHookName(hook.event);
if (!this.checkDeprecatedHook(hook.event)) {
return void 0;
}
const eventHandlers = this._hooks.get(hookEntry.event);
const shouldClone = options?.useHookClone ?? this._useHookClone;
const entry = shouldClone ? { id: hook.id, event: hook.event, handler: hook.handler } : hook;
entry.id = entry.id ?? crypto.randomUUID();
const eventHandlers = this._hooks.get(hook.event);
if (eventHandlers) {
eventHandlers.push(hookEntry.handler);
const existingIndex = eventHandlers.findIndex((h) => h.id === entry.id);
if (existingIndex !== -1) {
eventHandlers[existingIndex] = entry;
} else {
const position = options?.position ?? "Bottom";
if (position === "Top") {
eventHandlers.unshift(entry);
} else if (position === "Bottom") {
eventHandlers.push(entry);
} else {
const index = Math.max(0, Math.min(position, eventHandlers.length));
eventHandlers.splice(index, 0, entry);
}
}
} else {
this._hooks.set(hookEntry.event, [hookEntry.handler]);
this._hooks.set(hook.event, [entry]);
}
return entry;
}
/**
* Alias for onHook. This is provided for compatibility with other libraries that use the `addHook` method.
* @param {string} event
* @param {Hook} handler - this can be async or sync
* @param {string} event - the event name
* @param {HookFn} handler - the handler function
* @returns {void}
*/
addHook(event, handler) {
this.onHookEntry({ event, handler });
this.onHook({ event, handler });
}
/**
* Adds a handler function for a specific event
* @param {Array<HookEntry>} hooks
* Adds handler functions for specific events
* @param {Array<IHook>} hooks
* @param {OnHookOptions} [options] - optional per-call options (e.g., useHookClone override, position)
* @returns {void}
*/
onHooks(hooks) {
onHooks(hooks, options) {
for (const hook of hooks) {
this.onHook(hook.event, hook.handler);
this.onHook(hook, options);
}
}
/**
* Adds a handler function for a specific event that runs before all other handlers
* @param {string} event
* @param {Hook} handler - this can be async or sync
* @returns {void}
* Adds a handler function for a specific event that runs before all other handlers.
* Equivalent to calling `onHook(hook, { position: "Top" })`.
* @param {IHook} hook - the hook containing event name and handler
* @param {PrependHookOptions} [options] - optional per-call options (e.g., useHookClone override)
* @returns {IHook | undefined} the stored hook, or undefined if blocked by deprecation
*/
prependHook(event, handler) {
this.validateHookName(event);
if (!this.checkDeprecatedHook(event)) {
return;
}
const eventHandlers = this._hooks.get(event);
if (eventHandlers) {
eventHandlers.unshift(handler);
} else {
this._hooks.set(event, [handler]);
}
prependHook(hook, options) {
return this.onHook(hook, { ...options, position: "Top" });
}
/**
* Adds a handler that only executes once for a specific event before all other handlers
* @param event
* @param handler
* Adds a handler that only executes once for a specific event before all other handlers.
* Equivalent to calling `onHook` with a self-removing wrapper and `{ position: "Top" }`.
* @param {IHook} hook - the hook containing event name and handler
* @param {PrependHookOptions} [options] - optional per-call options (e.g., useHookClone override)
* @returns {IHook | undefined} the stored hook, or undefined if blocked by deprecation
*/
prependOnceHook(event, handler) {
this.validateHookName(event);
if (!this.checkDeprecatedHook(event)) {
return;
}
const hook = async (...arguments_) => {
this.removeHook(event, hook);
return handler(...arguments_);
prependOnceHook(hook, options) {
const wrappedHandler = async (...arguments_) => {
this.removeHook({ event: hook.event, handler: wrappedHandler });
return hook.handler(...arguments_);
};
this.prependHook(event, hook);
return this.onHook(
{ id: hook.id, event: hook.event, handler: wrappedHandler },
{ ...options, position: "Top" }
);
}
/**
* Adds a handler that only executes once for a specific event
* @param event
* @param handler
* @param {IHook} hook - the hook containing event name and handler
*/
onceHook(event, handler) {
this.validateHookName(event);
if (!this.checkDeprecatedHook(event)) {
onceHook(hook) {
this.validateHookName(hook.event);
if (!this.checkDeprecatedHook(hook.event)) {
return;
}
const hook = async (...arguments_) => {
this.removeHook(event, hook);
return handler(...arguments_);
const wrappedHandler = async (...arguments_) => {
this.removeHook({ event: hook.event, handler: wrappedHandler });
return hook.handler(...arguments_);
};
this.onHook(event, hook);
this.onHook({ id: hook.id, event: hook.event, handler: wrappedHandler });
}
/**
* Removes a handler function for a specific event
* @param {string} event
* @param {Hook} handler
* @returns {void}
* @param {IHook} hook - the hook containing event name and handler to remove
* @returns {IHook | undefined} the removed hook, or undefined if not found
*/
removeHook(event, handler) {
this.validateHookName(event);
if (!this.checkDeprecatedHook(event)) {
return;
}
const eventHandlers = this._hooks.get(event);
removeHook(hook) {
this.validateHookName(hook.event);
const eventHandlers = this._hooks.get(hook.event);
if (eventHandlers) {
const index = eventHandlers.indexOf(handler);
const index = eventHandlers.findIndex((h) => h.handler === hook.handler);
if (index !== -1) {
eventHandlers.splice(index, 1);
if (eventHandlers.length === 0) {
this._hooks.delete(hook.event);
}
return { event: hook.event, handler: hook.handler };
}
}
return void 0;
}
/**
* Removes all handlers for a specific event
* @param {Array<HookEntry>} hooks
* @returns {void}
* Removes multiple hook handlers
* @param {Array<IHook>} hooks
* @returns {IHook[]} the hooks that were successfully removed
*/
removeHooks(hooks) {
const removed = [];
for (const hook of hooks) {
this.removeHook(hook.event, hook.handler);
const result = this.removeHook(hook);
if (result) {
removed.push(result);
}
}
return removed;
}

@@ -614,5 +659,5 @@ /**

if (eventHandlers) {
for (const handler of eventHandlers) {
for (const hook of [...eventHandlers]) {
try {
await handler(...arguments_);
await hook.handler(...arguments_);
} catch (error) {

@@ -645,8 +690,8 @@ const message = `${event}: ${error.message}`;

if (eventHandlers) {
for (const handler of eventHandlers) {
if (handler.constructor.name === "AsyncFunction") {
for (const hook of [...eventHandlers]) {
if (hook.handler.constructor.name === "AsyncFunction") {
continue;
}
try {
handler(...arguments_);
hook.handler(...arguments_);
} catch (error) {

@@ -691,12 +736,51 @@ const message = `${event}: ${error.message}`;

* @param {string} event
* @returns {Hook[]}
* @returns {IHook[]}
*/
getHooks(event) {
this.validateHookName(event);
if (!this.checkDeprecatedHook(event)) {
return void 0;
}
return this._hooks.get(event);
}
/**
* Gets a specific hook by id, searching across all events
* @param {string} id - the hook id
* @returns {IHook | undefined} the hook if found, or undefined
*/
getHook(id) {
for (const eventHandlers of this._hooks.values()) {
const found = eventHandlers.find((h) => h.id === id);
if (found) {
return found;
}
}
return void 0;
}
/**
* Removes one or more hooks by id, searching across all events
* @param {string | string[]} id - the hook id or array of hook ids to remove
* @returns {IHook | IHook[] | undefined} the removed hook(s), or undefined/empty array if not found
*/
removeHookById(id) {
if (Array.isArray(id)) {
const removed = [];
for (const singleId of id) {
const result = this.removeHookById(singleId);
if (result && !Array.isArray(result)) {
removed.push(result);
}
}
return removed;
}
for (const [event, eventHandlers] of this._hooks.entries()) {
const index = eventHandlers.findIndex((h) => h.id === id);
if (index !== -1) {
const [removed] = eventHandlers.splice(index, 1);
if (eventHandlers.length === 0) {
this._hooks.delete(event);
}
return removed;
}
}
return void 0;
}
/**
* Removes all hooks

@@ -708,2 +792,46 @@ * @returns {void}

}
/**
* Removes all hooks for a specific event and returns the removed hooks.
* @param {string} event - The event name to remove hooks for.
* @returns {IHook[]} the hooks that were removed
*/
removeEventHooks(event) {
this.validateHookName(event);
const eventHandlers = this._hooks.get(event);
if (eventHandlers) {
const removed = [...eventHandlers];
this._hooks.delete(event);
return removed;
}
return [];
}
/**
* Validates hook event name if enforceBeforeAfter is enabled
* @param {string} event - The event name to validate
* @throws {Error} If enforceBeforeAfter is true and event doesn't start with 'before' or 'after'
*/
validateHookName(event) {
if (this._enforceBeforeAfter) {
const eventValue = event.trim().toLocaleLowerCase();
if (!eventValue.startsWith("before") && !eventValue.startsWith("after")) {
throw new Error(
`Hook event "${event}" must start with "before" or "after" when enforceBeforeAfter is enabled`
);
}
}
}
/**
* Checks if a hook is deprecated and emits a warning if it is
* @param {string} event - The event name to check
* @returns {boolean} - Returns true if the hook should proceed, false if it should be blocked
*/
checkDeprecatedHook(event) {
if (this._deprecatedHooks.has(event)) {
const message = this._deprecatedHooks.get(event);
const warningMessage = `Hook "${event}" is deprecated${message ? `: ${message}` : ""}`;
this.emit("warn", { hook: event, message: warningMessage });
return this._allowDeprecated;
}
return true;
}
};

@@ -710,0 +838,0 @@ })();

@@ -1,1 +0,1 @@

{"version":3,"sources":["../../src/eventified.ts","../../src/index.ts"],"sourcesContent":["// biome-ignore-all lint/suspicious/noExplicitAny: this is for event emitter compatibility\nimport type {\n\tEventEmitterOptions,\n\tEventListener,\n\tIEventEmitter,\n\tLogger,\n} from \"./types.js\";\n\nexport type { EventEmitterOptions, EventListener, IEventEmitter };\n\nexport class Eventified implements IEventEmitter {\n\tprivate readonly _eventListeners: Map<string | symbol, EventListener[]>;\n\tprivate _maxListeners: number;\n\tprivate _logger?: Logger;\n\tprivate _throwOnEmitError = false;\n\tprivate _throwOnEmptyListeners = false;\n\tprivate _errorEvent = \"error\";\n\n\tconstructor(options?: EventEmitterOptions) {\n\t\tthis._eventListeners = new Map<string | symbol, EventListener[]>();\n\t\tthis._maxListeners = 100; // Default maximum number of listeners\n\n\t\tthis._logger = options?.logger;\n\n\t\tif (options?.throwOnEmitError !== undefined) {\n\t\t\tthis._throwOnEmitError = options.throwOnEmitError;\n\t\t}\n\n\t\tif (options?.throwOnEmptyListeners !== undefined) {\n\t\t\tthis._throwOnEmptyListeners = options.throwOnEmptyListeners;\n\t\t}\n\t}\n\n\t/**\n\t * Gets the logger\n\t * @returns {Logger}\n\t */\n\tpublic get logger(): Logger | undefined {\n\t\treturn this._logger;\n\t}\n\n\t/**\n\t * Sets the logger\n\t * @param {Logger} logger\n\t */\n\tpublic set logger(logger: Logger | undefined) {\n\t\tthis._logger = logger;\n\t}\n\n\t/**\n\t * Gets whether an error should be thrown when an emit throws an error. Default is false and only emits an error event.\n\t * @returns {boolean}\n\t */\n\tpublic get throwOnEmitError(): boolean {\n\t\treturn this._throwOnEmitError;\n\t}\n\n\t/**\n\t * Sets whether an error should be thrown when an emit throws an error. Default is false and only emits an error event.\n\t * @param {boolean} value\n\t */\n\tpublic set throwOnEmitError(value: boolean) {\n\t\tthis._throwOnEmitError = value;\n\t}\n\n\t/**\n\t * Gets whether an error should be thrown when emitting 'error' event with no listeners. Default is false.\n\t * @returns {boolean}\n\t */\n\tpublic get throwOnEmptyListeners(): boolean {\n\t\treturn this._throwOnEmptyListeners;\n\t}\n\n\t/**\n\t * Sets whether an error should be thrown when emitting 'error' event with no listeners. Default is false.\n\t * @param {boolean} value\n\t */\n\tpublic set throwOnEmptyListeners(value: boolean) {\n\t\tthis._throwOnEmptyListeners = value;\n\t}\n\n\t/**\n\t * Adds a handler function for a specific event that will run only once\n\t * @param {string | symbol} eventName\n\t * @param {EventListener} listener\n\t * @returns {IEventEmitter} returns the instance of the class for chaining\n\t */\n\tpublic once(\n\t\teventName: string | symbol,\n\t\tlistener: EventListener,\n\t): IEventEmitter {\n\t\tconst onceListener: EventListener = (...arguments_: any[]) => {\n\t\t\tthis.off(eventName as string, onceListener);\n\t\t\tlistener(...arguments_);\n\t\t};\n\n\t\tthis.on(eventName as string, onceListener);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Gets the number of listeners for a specific event. If no event is provided, it returns the total number of listeners\n\t * @param {string} eventName The event name. Not required\n\t * @returns {number} The number of listeners\n\t */\n\tpublic listenerCount(eventName?: string | symbol): number {\n\t\tif (eventName === undefined) {\n\t\t\treturn this.getAllListeners().length;\n\t\t}\n\n\t\tconst listeners = this._eventListeners.get(eventName);\n\t\treturn listeners ? listeners.length : 0;\n\t}\n\n\t/**\n\t * Gets an array of event names\n\t * @returns {Array<string | symbol>} An array of event names\n\t */\n\tpublic eventNames(): Array<string | symbol> {\n\t\treturn [...this._eventListeners.keys()];\n\t}\n\n\t/**\n\t * Gets an array of listeners for a specific event. If no event is provided, it returns all listeners\n\t * @param {string} [event] (Optional) The event name\n\t * @returns {EventListener[]} An array of listeners\n\t */\n\tpublic rawListeners(event?: string | symbol): EventListener[] {\n\t\tif (event === undefined) {\n\t\t\treturn this.getAllListeners();\n\t\t}\n\n\t\treturn this._eventListeners.get(event) ?? [];\n\t}\n\n\t/**\n\t * Prepends a listener to the beginning of the listeners array for the specified event\n\t * @param {string | symbol} eventName\n\t * @param {EventListener} listener\n\t * @returns {IEventEmitter} returns the instance of the class for chaining\n\t */\n\tpublic prependListener(\n\t\teventName: string | symbol,\n\t\tlistener: EventListener,\n\t): IEventEmitter {\n\t\tconst listeners = this._eventListeners.get(eventName) ?? [];\n\t\tlisteners.unshift(listener);\n\t\tthis._eventListeners.set(eventName, listeners);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Prepends a one-time listener to the beginning of the listeners array for the specified event\n\t * @param {string | symbol} eventName\n\t * @param {EventListener} listener\n\t * @returns {IEventEmitter} returns the instance of the class for chaining\n\t */\n\tpublic prependOnceListener(\n\t\teventName: string | symbol,\n\t\tlistener: EventListener,\n\t): IEventEmitter {\n\t\tconst onceListener: EventListener = (...arguments_: any[]) => {\n\t\t\tthis.off(eventName as string, onceListener);\n\t\t\tlistener(...arguments_);\n\t\t};\n\n\t\tthis.prependListener(eventName as string, onceListener);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Gets the maximum number of listeners that can be added for a single event\n\t * @returns {number} The maximum number of listeners\n\t */\n\tpublic maxListeners(): number {\n\t\treturn this._maxListeners;\n\t}\n\n\t/**\n\t * Adds a listener for a specific event. It is an alias for the on() method\n\t * @param {string | symbol} event\n\t * @param {EventListener} listener\n\t * @returns {IEventEmitter} returns the instance of the class for chaining\n\t */\n\tpublic addListener(\n\t\tevent: string | symbol,\n\t\tlistener: EventListener,\n\t): IEventEmitter {\n\t\tthis.on(event, listener);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Adds a listener for a specific event\n\t * @param {string | symbol} event\n\t * @param {EventListener} listener\n\t * @returns {IEventEmitter} returns the instance of the class for chaining\n\t */\n\tpublic on(event: string | symbol, listener: EventListener): IEventEmitter {\n\t\tif (!this._eventListeners.has(event)) {\n\t\t\tthis._eventListeners.set(event, []);\n\t\t}\n\n\t\tconst listeners = this._eventListeners.get(event);\n\n\t\tif (listeners) {\n\t\t\tif (listeners.length >= this._maxListeners) {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`MaxListenersExceededWarning: Possible event memory leak detected. ${listeners.length + 1} ${event as string} listeners added. Use setMaxListeners() to increase limit.`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tlisteners.push(listener);\n\t\t}\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Removes a listener for a specific event. It is an alias for the off() method\n\t * @param {string | symbol} event\n\t * @param {EventListener} listener\n\t * @returns {IEventEmitter} returns the instance of the class for chaining\n\t */\n\tpublic removeListener(event: string, listener: EventListener): IEventEmitter {\n\t\tthis.off(event, listener);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Removes a listener for a specific event\n\t * @param {string | symbol} event\n\t * @param {EventListener} listener\n\t * @returns {IEventEmitter} returns the instance of the class for chaining\n\t */\n\tpublic off(event: string | symbol, listener: EventListener): IEventEmitter {\n\t\tconst listeners = this._eventListeners.get(event) ?? [];\n\t\tconst index = listeners.indexOf(listener);\n\t\tif (index !== -1) {\n\t\t\tlisteners.splice(index, 1);\n\t\t}\n\n\t\tif (listeners.length === 0) {\n\t\t\tthis._eventListeners.delete(event);\n\t\t}\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Calls all listeners for a specific event\n\t * @param {string | symbol} event\n\t * @param arguments_ The arguments to pass to the listeners\n\t * @returns {boolean} Returns true if the event had listeners, false otherwise\n\t */\n\tpublic emit(event: string | symbol, ...arguments_: any[]): boolean {\n\t\tlet result = false;\n\t\tconst listeners = this._eventListeners.get(event);\n\n\t\tif (listeners && listeners.length > 0) {\n\t\t\tfor (const listener of listeners) {\n\t\t\t\tlistener(...arguments_);\n\t\t\t\tresult = true;\n\t\t\t}\n\t\t}\n\n\t\tif (event === this._errorEvent) {\n\t\t\tconst error =\n\t\t\t\targuments_[0] instanceof Error\n\t\t\t\t\t? arguments_[0]\n\t\t\t\t\t: new Error(`${arguments_[0]}`);\n\n\t\t\tif (this._throwOnEmitError && !result) {\n\t\t\t\tthrow error;\n\t\t\t} else {\n\t\t\t\tif (\n\t\t\t\t\tthis.listeners(this._errorEvent).length === 0 &&\n\t\t\t\t\tthis._throwOnEmptyListeners === true\n\t\t\t\t) {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// send it to the logger\n\t\tthis.sendLog(event, arguments_);\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Gets all listeners for a specific event. If no event is provided, it returns all listeners\n\t * @param {string} [event] (Optional) The event name\n\t * @returns {EventListener[]} An array of listeners\n\t */\n\tpublic listeners(event: string | symbol): EventListener[] {\n\t\treturn this._eventListeners.get(event) ?? [];\n\t}\n\n\t/**\n\t * Removes all listeners for a specific event. If no event is provided, it removes all listeners\n\t * @param {string} [event] (Optional) The event name\n\t * @returns {IEventEmitter} returns the instance of the class for chaining\n\t */\n\tpublic removeAllListeners(event?: string | symbol): IEventEmitter {\n\t\tif (event !== undefined) {\n\t\t\tthis._eventListeners.delete(event);\n\t\t} else {\n\t\t\tthis._eventListeners.clear();\n\t\t}\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Sets the maximum number of listeners that can be added for a single event\n\t * @param {number} n The maximum number of listeners\n\t * @returns {void}\n\t */\n\tpublic setMaxListeners(n: number): void {\n\t\tthis._maxListeners = n;\n\t\tfor (const listeners of this._eventListeners.values()) {\n\t\t\tif (listeners.length > n) {\n\t\t\t\tlisteners.splice(n);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Gets all listeners\n\t * @returns {EventListener[]} An array of listeners\n\t */\n\tpublic getAllListeners(): EventListener[] {\n\t\tlet result: EventListener[] = [];\n\t\tfor (const listeners of this._eventListeners.values()) {\n\t\t\tresult = [...result, ...listeners];\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Sends a log message using the configured logger based on the event name\n\t * @param {string | symbol} eventName - The event name that determines the log level\n\t * @param {unknown} data - The data to log\n\t */\n\tprivate sendLog(eventName: string | symbol, data: any): void {\n\t\tif (!this._logger) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet message: string;\n\t\t/* v8 ignore next -- @preserve */\n\t\tif (typeof data === \"string\") {\n\t\t\tmessage = data;\n\t\t} else if (\n\t\t\tArray.isArray(data) &&\n\t\t\tdata.length > 0 &&\n\t\t\tdata[0] instanceof Error\n\t\t) {\n\t\t\tmessage = data[0].message;\n\t\t\t/* v8 ignore next -- @preserve */\n\t\t} else if (data instanceof Error) {\n\t\t\tmessage = data.message;\n\t\t} else if (\n\t\t\tArray.isArray(data) &&\n\t\t\tdata.length > 0 &&\n\t\t\ttypeof data[0]?.message === \"string\"\n\t\t) {\n\t\t\tmessage = data[0].message;\n\t\t} else {\n\t\t\tmessage = JSON.stringify(data);\n\t\t}\n\n\t\tswitch (eventName) {\n\t\t\tcase \"error\": {\n\t\t\t\tthis._logger.error?.(message, { event: eventName, data });\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"warn\": {\n\t\t\t\tthis._logger.warn?.(message, { event: eventName, data });\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"trace\": {\n\t\t\t\tthis._logger.trace?.(message, { event: eventName, data });\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"debug\": {\n\t\t\t\tthis._logger.debug?.(message, { event: eventName, data });\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"fatal\": {\n\t\t\t\tthis._logger.fatal?.(message, { event: eventName, data });\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault: {\n\t\t\t\tthis._logger.info?.(message, { event: eventName, data });\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n}\n","import { Eventified } from \"./eventified.js\";\nimport type { Hook, HookEntry, HookifiedOptions } from \"./types.js\";\n\nexport type { Hook, HookEntry, HookifiedOptions };\n\nexport class Hookified extends Eventified {\n\tprivate readonly _hooks: Map<string, Hook[]>;\n\tprivate _throwOnHookError = false;\n\tprivate _enforceBeforeAfter = false;\n\tprivate _deprecatedHooks: Map<string, string>;\n\tprivate _allowDeprecated = true;\n\n\tconstructor(options?: HookifiedOptions) {\n\t\tsuper({\n\t\t\tlogger: options?.logger,\n\t\t\tthrowOnEmitError: options?.throwOnEmitError,\n\t\t\tthrowOnEmptyListeners: options?.throwOnEmptyListeners,\n\t\t});\n\t\tthis._hooks = new Map();\n\t\tthis._deprecatedHooks = options?.deprecatedHooks\n\t\t\t? new Map(options.deprecatedHooks)\n\t\t\t: new Map();\n\n\t\tif (options?.throwOnHookError !== undefined) {\n\t\t\tthis._throwOnHookError = options.throwOnHookError;\n\t\t} else if (options?.throwHookErrors !== undefined) {\n\t\t\tthis._throwOnHookError = options.throwHookErrors;\n\t\t}\n\n\t\tif (options?.enforceBeforeAfter !== undefined) {\n\t\t\tthis._enforceBeforeAfter = options.enforceBeforeAfter;\n\t\t}\n\n\t\tif (options?.allowDeprecated !== undefined) {\n\t\t\tthis._allowDeprecated = options.allowDeprecated;\n\t\t}\n\t}\n\n\t/**\n\t * Gets all hooks\n\t * @returns {Map<string, Hook[]>}\n\t */\n\tpublic get hooks() {\n\t\treturn this._hooks;\n\t}\n\n\t/**\n\t * Gets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.\n\t * @returns {boolean}\n\t * @deprecated - this will be deprecated in version 2. Please use throwOnHookError.\n\t */\n\tpublic get throwHookErrors() {\n\t\treturn this._throwOnHookError;\n\t}\n\n\t/**\n\t * Sets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.\n\t * @param {boolean} value\n\t * @deprecated - this will be deprecated in version 2. Please use throwOnHookError.\n\t */\n\tpublic set throwHookErrors(value) {\n\t\tthis._throwOnHookError = value;\n\t}\n\n\t/**\n\t * Gets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.\n\t * @returns {boolean}\n\t */\n\tpublic get throwOnHookError() {\n\t\treturn this._throwOnHookError;\n\t}\n\n\t/**\n\t * Sets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.\n\t * @param {boolean} value\n\t */\n\tpublic set throwOnHookError(value) {\n\t\tthis._throwOnHookError = value;\n\t}\n\n\t/**\n\t * Gets whether to enforce that all hook names start with 'before' or 'after'. Default is false.\n\t * @returns {boolean}\n\t * @default false\n\t */\n\tpublic get enforceBeforeAfter() {\n\t\treturn this._enforceBeforeAfter;\n\t}\n\n\t/**\n\t * Sets whether to enforce that all hook names start with 'before' or 'after'. Default is false.\n\t * @param {boolean} value\n\t */\n\tpublic set enforceBeforeAfter(value) {\n\t\tthis._enforceBeforeAfter = value;\n\t}\n\n\t/**\n\t * Gets the map of deprecated hook names to deprecation messages.\n\t * @returns {Map<string, string>}\n\t */\n\tpublic get deprecatedHooks() {\n\t\treturn this._deprecatedHooks;\n\t}\n\n\t/**\n\t * Sets the map of deprecated hook names to deprecation messages.\n\t * @param {Map<string, string>} value\n\t */\n\tpublic set deprecatedHooks(value) {\n\t\tthis._deprecatedHooks = value;\n\t}\n\n\t/**\n\t * Gets whether deprecated hooks are allowed to be registered and executed. Default is true.\n\t * @returns {boolean}\n\t */\n\tpublic get allowDeprecated() {\n\t\treturn this._allowDeprecated;\n\t}\n\n\t/**\n\t * Sets whether deprecated hooks are allowed to be registered and executed. Default is true.\n\t * @param {boolean} value\n\t */\n\tpublic set allowDeprecated(value) {\n\t\tthis._allowDeprecated = value;\n\t}\n\n\t/**\n\t * Validates hook event name if enforceBeforeAfter is enabled\n\t * @param {string} event - The event name to validate\n\t * @throws {Error} If enforceBeforeAfter is true and event doesn't start with 'before' or 'after'\n\t */\n\tprivate validateHookName(event: string): void {\n\t\tif (this._enforceBeforeAfter) {\n\t\t\tconst eventValue = event.trim().toLocaleLowerCase();\n\t\t\tif (!eventValue.startsWith(\"before\") && !eventValue.startsWith(\"after\")) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Hook event \"${event}\" must start with \"before\" or \"after\" when enforceBeforeAfter is enabled`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Checks if a hook is deprecated and emits a warning if it is\n\t * @param {string} event - The event name to check\n\t * @returns {boolean} - Returns true if the hook should proceed, false if it should be blocked\n\t */\n\tprivate checkDeprecatedHook(event: string): boolean {\n\t\tif (this._deprecatedHooks.has(event)) {\n\t\t\tconst message = this._deprecatedHooks.get(event);\n\t\t\tconst warningMessage = `Hook \"${event}\" is deprecated${message ? `: ${message}` : \"\"}`;\n\n\t\t\t// Emit deprecation warning event\n\t\t\tthis.emit(\"warn\", { hook: event, message: warningMessage });\n\n\t\t\t// Return false if deprecated hooks are not allowed\n\t\t\treturn this._allowDeprecated;\n\t\t}\n\t\treturn true;\n\t}\n\n\t/**\n\t * Adds a handler function for a specific event\n\t * @param {string} event\n\t * @param {Hook} handler - this can be async or sync\n\t * @returns {void}\n\t */\n\tpublic onHook(event: string, handler: Hook) {\n\t\tthis.onHookEntry({ event, handler });\n\t}\n\n\t/**\n\t * Adds a handler function for a specific event\n\t * @param {HookEntry} hookEntry\n\t * @returns {void}\n\t */\n\tpublic onHookEntry(hookEntry: HookEntry) {\n\t\tthis.validateHookName(hookEntry.event);\n\t\tif (!this.checkDeprecatedHook(hookEntry.event)) {\n\t\t\treturn; // Skip registration if deprecated hooks are not allowed\n\t\t}\n\t\tconst eventHandlers = this._hooks.get(hookEntry.event);\n\t\tif (eventHandlers) {\n\t\t\teventHandlers.push(hookEntry.handler);\n\t\t} else {\n\t\t\tthis._hooks.set(hookEntry.event, [hookEntry.handler]);\n\t\t}\n\t}\n\n\t/**\n\t * Alias for onHook. This is provided for compatibility with other libraries that use the `addHook` method.\n\t * @param {string} event\n\t * @param {Hook} handler - this can be async or sync\n\t * @returns {void}\n\t */\n\tpublic addHook(event: string, handler: Hook) {\n\t\tthis.onHookEntry({ event, handler });\n\t}\n\n\t/**\n\t * Adds a handler function for a specific event\n\t * @param {Array<HookEntry>} hooks\n\t * @returns {void}\n\t */\n\tpublic onHooks(hooks: HookEntry[]) {\n\t\tfor (const hook of hooks) {\n\t\t\tthis.onHook(hook.event, hook.handler);\n\t\t}\n\t}\n\n\t/**\n\t * Adds a handler function for a specific event that runs before all other handlers\n\t * @param {string} event\n\t * @param {Hook} handler - this can be async or sync\n\t * @returns {void}\n\t */\n\tpublic prependHook(event: string, handler: Hook) {\n\t\tthis.validateHookName(event);\n\t\tif (!this.checkDeprecatedHook(event)) {\n\t\t\treturn; // Skip registration if deprecated hooks are not allowed\n\t\t}\n\t\tconst eventHandlers = this._hooks.get(event);\n\t\tif (eventHandlers) {\n\t\t\teventHandlers.unshift(handler);\n\t\t} else {\n\t\t\tthis._hooks.set(event, [handler]);\n\t\t}\n\t}\n\n\t/**\n\t * Adds a handler that only executes once for a specific event before all other handlers\n\t * @param event\n\t * @param handler\n\t */\n\tpublic prependOnceHook(event: string, handler: Hook) {\n\t\tthis.validateHookName(event);\n\t\tif (!this.checkDeprecatedHook(event)) {\n\t\t\treturn; // Skip registration if deprecated hooks are not allowed\n\t\t}\n\t\t// biome-ignore lint/suspicious/noExplicitAny: this is for any parameter compatibility\n\t\tconst hook = async (...arguments_: any[]) => {\n\t\t\tthis.removeHook(event, hook);\n\t\t\treturn handler(...arguments_);\n\t\t};\n\n\t\tthis.prependHook(event, hook);\n\t}\n\n\t/**\n\t * Adds a handler that only executes once for a specific event\n\t * @param event\n\t * @param handler\n\t */\n\tpublic onceHook(event: string, handler: Hook) {\n\t\tthis.validateHookName(event);\n\t\tif (!this.checkDeprecatedHook(event)) {\n\t\t\treturn; // Skip registration if deprecated hooks are not allowed\n\t\t}\n\t\t// biome-ignore lint/suspicious/noExplicitAny: this is for any parameter compatibility\n\t\tconst hook = async (...arguments_: any[]) => {\n\t\t\tthis.removeHook(event, hook);\n\t\t\treturn handler(...arguments_);\n\t\t};\n\n\t\tthis.onHook(event, hook);\n\t}\n\n\t/**\n\t * Removes a handler function for a specific event\n\t * @param {string} event\n\t * @param {Hook} handler\n\t * @returns {void}\n\t */\n\tpublic removeHook(event: string, handler: Hook) {\n\t\tthis.validateHookName(event);\n\t\tif (!this.checkDeprecatedHook(event)) {\n\t\t\treturn; // Skip removal if deprecated hooks are not allowed\n\t\t}\n\t\tconst eventHandlers = this._hooks.get(event);\n\t\tif (eventHandlers) {\n\t\t\tconst index = eventHandlers.indexOf(handler);\n\t\t\tif (index !== -1) {\n\t\t\t\teventHandlers.splice(index, 1);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Removes all handlers for a specific event\n\t * @param {Array<HookEntry>} hooks\n\t * @returns {void}\n\t */\n\tpublic removeHooks(hooks: HookEntry[]) {\n\t\tfor (const hook of hooks) {\n\t\t\tthis.removeHook(hook.event, hook.handler);\n\t\t}\n\t}\n\n\t/**\n\t * Calls all handlers for a specific event\n\t * @param {string} event\n\t * @param {T[]} arguments_\n\t * @returns {Promise<void>}\n\t */\n\tpublic async hook<T>(event: string, ...arguments_: T[]) {\n\t\tthis.validateHookName(event);\n\t\tif (!this.checkDeprecatedHook(event)) {\n\t\t\treturn; // Skip execution if deprecated hooks are not allowed\n\t\t}\n\t\tconst eventHandlers = this._hooks.get(event);\n\t\tif (eventHandlers) {\n\t\t\tfor (const handler of eventHandlers) {\n\t\t\t\ttry {\n\t\t\t\t\tawait handler(...arguments_);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst message = `${event}: ${(error as Error).message}`;\n\t\t\t\t\tthis.emit(\"error\", new Error(message));\n\n\t\t\t\t\tif (this._throwOnHookError) {\n\t\t\t\t\t\tthrow new Error(message);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Calls all synchronous handlers for a specific event.\n\t * Async handlers (declared with `async` keyword) are silently skipped.\n\t *\n\t * Note: The `hook` method is preferred as it executes both sync and async functions.\n\t * Use `hookSync` only when you specifically need synchronous execution.\n\t * @param {string} event\n\t * @param {T[]} arguments_\n\t * @returns {void}\n\t */\n\tpublic hookSync<T>(event: string, ...arguments_: T[]): void {\n\t\tthis.validateHookName(event);\n\t\tif (!this.checkDeprecatedHook(event)) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst eventHandlers = this._hooks.get(event);\n\t\tif (eventHandlers) {\n\t\t\tfor (const handler of eventHandlers) {\n\t\t\t\t// Skip async functions silently\n\t\t\t\tif (handler.constructor.name === \"AsyncFunction\") {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\thandler(...arguments_);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst message = `${event}: ${(error as Error).message}`;\n\t\t\t\t\tthis.emit(\"error\", new Error(message));\n\n\t\t\t\t\tif (this._throwOnHookError) {\n\t\t\t\t\t\tthrow new Error(message);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Prepends the word `before` to your hook. Example is event is `test`, the before hook is `before:test`.\n\t * @param {string} event - The event name\n\t * @param {T[]} arguments_ - The arguments to pass to the hook\n\t */\n\tpublic async beforeHook<T>(event: string, ...arguments_: T[]) {\n\t\tawait this.hook(`before:${event}`, ...arguments_);\n\t}\n\n\t/**\n\t * Prepends the word `after` to your hook. Example is event is `test`, the after hook is `after:test`.\n\t * @param {string} event - The event name\n\t * @param {T[]} arguments_ - The arguments to pass to the hook\n\t */\n\tpublic async afterHook<T>(event: string, ...arguments_: T[]) {\n\t\tawait this.hook(`after:${event}`, ...arguments_);\n\t}\n\n\t/**\n\t * Calls all handlers for a specific event. This is an alias for `hook` and is provided for\n\t * compatibility with other libraries that use the `callHook` method.\n\t * @param {string} event\n\t * @param {T[]} arguments_\n\t * @returns {Promise<void>}\n\t */\n\tpublic async callHook<T>(event: string, ...arguments_: T[]) {\n\t\tawait this.hook(event, ...arguments_);\n\t}\n\n\t/**\n\t * Gets all hooks for a specific event\n\t * @param {string} event\n\t * @returns {Hook[]}\n\t */\n\tpublic getHooks(event: string) {\n\t\tthis.validateHookName(event);\n\t\tif (!this.checkDeprecatedHook(event)) {\n\t\t\treturn undefined; // Return undefined if deprecated hooks are not allowed\n\t\t}\n\t\treturn this._hooks.get(event);\n\t}\n\n\t/**\n\t * Removes all hooks\n\t * @returns {void}\n\t */\n\tpublic clearHooks() {\n\t\tthis._hooks.clear();\n\t}\n}\n\nexport { Eventified } from \"./eventified.js\";\nexport type {\n\tEventEmitterOptions,\n\tEventListener,\n\tIEventEmitter,\n\tLogger,\n} from \"./types.js\";\n"],"mappings":";;;;;;;AAUO,MAAM,aAAN,MAA0C;AAAA,IAQhD,YAAY,SAA+B;AAP3C,0BAAiB;AACjB,0BAAQ;AACR,0BAAQ;AACR,0BAAQ,qBAAoB;AAC5B,0BAAQ,0BAAyB;AACjC,0BAAQ,eAAc;AAGrB,WAAK,kBAAkB,oBAAI,IAAsC;AACjE,WAAK,gBAAgB;AAErB,WAAK,UAAU,SAAS;AAExB,UAAI,SAAS,qBAAqB,QAAW;AAC5C,aAAK,oBAAoB,QAAQ;AAAA,MAClC;AAEA,UAAI,SAAS,0BAA0B,QAAW;AACjD,aAAK,yBAAyB,QAAQ;AAAA,MACvC;AAAA,IACD;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,IAAW,SAA6B;AACvC,aAAO,KAAK;AAAA,IACb;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,IAAW,OAAO,QAA4B;AAC7C,WAAK,UAAU;AAAA,IAChB;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,IAAW,mBAA4B;AACtC,aAAO,KAAK;AAAA,IACb;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,IAAW,iBAAiB,OAAgB;AAC3C,WAAK,oBAAoB;AAAA,IAC1B;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,IAAW,wBAAiC;AAC3C,aAAO,KAAK;AAAA,IACb;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,IAAW,sBAAsB,OAAgB;AAChD,WAAK,yBAAyB;AAAA,IAC/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQO,KACN,WACA,UACgB;AAChB,YAAM,eAA8B,IAAI,eAAsB;AAC7D,aAAK,IAAI,WAAqB,YAAY;AAC1C,iBAAS,GAAG,UAAU;AAAA,MACvB;AAEA,WAAK,GAAG,WAAqB,YAAY;AACzC,aAAO;AAAA,IACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOO,cAAc,WAAqC;AACzD,UAAI,cAAc,QAAW;AAC5B,eAAO,KAAK,gBAAgB,EAAE;AAAA,MAC/B;AAEA,YAAM,YAAY,KAAK,gBAAgB,IAAI,SAAS;AACpD,aAAO,YAAY,UAAU,SAAS;AAAA,IACvC;AAAA;AAAA;AAAA;AAAA;AAAA,IAMO,aAAqC;AAC3C,aAAO,CAAC,GAAG,KAAK,gBAAgB,KAAK,CAAC;AAAA,IACvC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOO,aAAa,OAA0C;AAC7D,UAAI,UAAU,QAAW;AACxB,eAAO,KAAK,gBAAgB;AAAA,MAC7B;AAEA,aAAO,KAAK,gBAAgB,IAAI,KAAK,KAAK,CAAC;AAAA,IAC5C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQO,gBACN,WACA,UACgB;AAChB,YAAM,YAAY,KAAK,gBAAgB,IAAI,SAAS,KAAK,CAAC;AAC1D,gBAAU,QAAQ,QAAQ;AAC1B,WAAK,gBAAgB,IAAI,WAAW,SAAS;AAC7C,aAAO;AAAA,IACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQO,oBACN,WACA,UACgB;AAChB,YAAM,eAA8B,IAAI,eAAsB;AAC7D,aAAK,IAAI,WAAqB,YAAY;AAC1C,iBAAS,GAAG,UAAU;AAAA,MACvB;AAEA,WAAK,gBAAgB,WAAqB,YAAY;AACtD,aAAO;AAAA,IACR;AAAA;AAAA;AAAA;AAAA;AAAA,IAMO,eAAuB;AAC7B,aAAO,KAAK;AAAA,IACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQO,YACN,OACA,UACgB;AAChB,WAAK,GAAG,OAAO,QAAQ;AACvB,aAAO;AAAA,IACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQO,GAAG,OAAwB,UAAwC;AACzE,UAAI,CAAC,KAAK,gBAAgB,IAAI,KAAK,GAAG;AACrC,aAAK,gBAAgB,IAAI,OAAO,CAAC,CAAC;AAAA,MACnC;AAEA,YAAM,YAAY,KAAK,gBAAgB,IAAI,KAAK;AAEhD,UAAI,WAAW;AACd,YAAI,UAAU,UAAU,KAAK,eAAe;AAC3C,kBAAQ;AAAA,YACP,qEAAqE,UAAU,SAAS,CAAC,IAAI,KAAe;AAAA,UAC7G;AAAA,QACD;AAEA,kBAAU,KAAK,QAAQ;AAAA,MACxB;AAEA,aAAO;AAAA,IACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQO,eAAe,OAAe,UAAwC;AAC5E,WAAK,IAAI,OAAO,QAAQ;AACxB,aAAO;AAAA,IACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQO,IAAI,OAAwB,UAAwC;AAC1E,YAAM,YAAY,KAAK,gBAAgB,IAAI,KAAK,KAAK,CAAC;AACtD,YAAM,QAAQ,UAAU,QAAQ,QAAQ;AACxC,UAAI,UAAU,IAAI;AACjB,kBAAU,OAAO,OAAO,CAAC;AAAA,MAC1B;AAEA,UAAI,UAAU,WAAW,GAAG;AAC3B,aAAK,gBAAgB,OAAO,KAAK;AAAA,MAClC;AAEA,aAAO;AAAA,IACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQO,KAAK,UAA2B,YAA4B;AAClE,UAAI,SAAS;AACb,YAAM,YAAY,KAAK,gBAAgB,IAAI,KAAK;AAEhD,UAAI,aAAa,UAAU,SAAS,GAAG;AACtC,mBAAW,YAAY,WAAW;AACjC,mBAAS,GAAG,UAAU;AACtB,mBAAS;AAAA,QACV;AAAA,MACD;AAEA,UAAI,UAAU,KAAK,aAAa;AAC/B,cAAM,QACL,WAAW,CAAC,aAAa,QACtB,WAAW,CAAC,IACZ,IAAI,MAAM,GAAG,WAAW,CAAC,CAAC,EAAE;AAEhC,YAAI,KAAK,qBAAqB,CAAC,QAAQ;AACtC,gBAAM;AAAA,QACP,OAAO;AACN,cACC,KAAK,UAAU,KAAK,WAAW,EAAE,WAAW,KAC5C,KAAK,2BAA2B,MAC/B;AACD,kBAAM;AAAA,UACP;AAAA,QACD;AAAA,MACD;AAGA,WAAK,QAAQ,OAAO,UAAU;AAE9B,aAAO;AAAA,IACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOO,UAAU,OAAyC;AACzD,aAAO,KAAK,gBAAgB,IAAI,KAAK,KAAK,CAAC;AAAA,IAC5C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOO,mBAAmB,OAAwC;AACjE,UAAI,UAAU,QAAW;AACxB,aAAK,gBAAgB,OAAO,KAAK;AAAA,MAClC,OAAO;AACN,aAAK,gBAAgB,MAAM;AAAA,MAC5B;AAEA,aAAO;AAAA,IACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOO,gBAAgB,GAAiB;AACvC,WAAK,gBAAgB;AACrB,iBAAW,aAAa,KAAK,gBAAgB,OAAO,GAAG;AACtD,YAAI,UAAU,SAAS,GAAG;AACzB,oBAAU,OAAO,CAAC;AAAA,QACnB;AAAA,MACD;AAAA,IACD;AAAA;AAAA;AAAA;AAAA;AAAA,IAMO,kBAAmC;AACzC,UAAI,SAA0B,CAAC;AAC/B,iBAAW,aAAa,KAAK,gBAAgB,OAAO,GAAG;AACtD,iBAAS,CAAC,GAAG,QAAQ,GAAG,SAAS;AAAA,MAClC;AAEA,aAAO;AAAA,IACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOQ,QAAQ,WAA4B,MAAiB;AAC5D,UAAI,CAAC,KAAK,SAAS;AAClB;AAAA,MACD;AAEA,UAAI;AAEJ,UAAI,OAAO,SAAS,UAAU;AAC7B,kBAAU;AAAA,MACX,WACC,MAAM,QAAQ,IAAI,KAClB,KAAK,SAAS,KACd,KAAK,CAAC,aAAa,OAClB;AACD,kBAAU,KAAK,CAAC,EAAE;AAAA,MAEnB,WAAW,gBAAgB,OAAO;AACjC,kBAAU,KAAK;AAAA,MAChB,WACC,MAAM,QAAQ,IAAI,KAClB,KAAK,SAAS,KACd,OAAO,KAAK,CAAC,GAAG,YAAY,UAC3B;AACD,kBAAU,KAAK,CAAC,EAAE;AAAA,MACnB,OAAO;AACN,kBAAU,KAAK,UAAU,IAAI;AAAA,MAC9B;AAEA,cAAQ,WAAW;AAAA,QAClB,KAAK,SAAS;AACb,eAAK,QAAQ,QAAQ,SAAS,EAAE,OAAO,WAAW,KAAK,CAAC;AACxD;AAAA,QACD;AAAA,QAEA,KAAK,QAAQ;AACZ,eAAK,QAAQ,OAAO,SAAS,EAAE,OAAO,WAAW,KAAK,CAAC;AACvD;AAAA,QACD;AAAA,QAEA,KAAK,SAAS;AACb,eAAK,QAAQ,QAAQ,SAAS,EAAE,OAAO,WAAW,KAAK,CAAC;AACxD;AAAA,QACD;AAAA,QAEA,KAAK,SAAS;AACb,eAAK,QAAQ,QAAQ,SAAS,EAAE,OAAO,WAAW,KAAK,CAAC;AACxD;AAAA,QACD;AAAA,QAEA,KAAK,SAAS;AACb,eAAK,QAAQ,QAAQ,SAAS,EAAE,OAAO,WAAW,KAAK,CAAC;AACxD;AAAA,QACD;AAAA,QAEA,SAAS;AACR,eAAK,QAAQ,OAAO,SAAS,EAAE,OAAO,WAAW,KAAK,CAAC;AACvD;AAAA,QACD;AAAA,MACD;AAAA,IACD;AAAA,EACD;;;ACjZO,MAAM,YAAN,cAAwB,WAAW;AAAA,IAOzC,YAAY,SAA4B;AACvC,YAAM;AAAA,QACL,QAAQ,SAAS;AAAA,QACjB,kBAAkB,SAAS;AAAA,QAC3B,uBAAuB,SAAS;AAAA,MACjC,CAAC;AAXF,0BAAiB;AACjB,0BAAQ,qBAAoB;AAC5B,0BAAQ,uBAAsB;AAC9B,0BAAQ;AACR,0BAAQ,oBAAmB;AAQ1B,WAAK,SAAS,oBAAI,IAAI;AACtB,WAAK,mBAAmB,SAAS,kBAC9B,IAAI,IAAI,QAAQ,eAAe,IAC/B,oBAAI,IAAI;AAEX,UAAI,SAAS,qBAAqB,QAAW;AAC5C,aAAK,oBAAoB,QAAQ;AAAA,MAClC,WAAW,SAAS,oBAAoB,QAAW;AAClD,aAAK,oBAAoB,QAAQ;AAAA,MAClC;AAEA,UAAI,SAAS,uBAAuB,QAAW;AAC9C,aAAK,sBAAsB,QAAQ;AAAA,MACpC;AAEA,UAAI,SAAS,oBAAoB,QAAW;AAC3C,aAAK,mBAAmB,QAAQ;AAAA,MACjC;AAAA,IACD;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,IAAW,QAAQ;AAClB,aAAO,KAAK;AAAA,IACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOA,IAAW,kBAAkB;AAC5B,aAAO,KAAK;AAAA,IACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOA,IAAW,gBAAgB,OAAO;AACjC,WAAK,oBAAoB;AAAA,IAC1B;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,IAAW,mBAAmB;AAC7B,aAAO,KAAK;AAAA,IACb;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,IAAW,iBAAiB,OAAO;AAClC,WAAK,oBAAoB;AAAA,IAC1B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOA,IAAW,qBAAqB;AAC/B,aAAO,KAAK;AAAA,IACb;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,IAAW,mBAAmB,OAAO;AACpC,WAAK,sBAAsB;AAAA,IAC5B;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,IAAW,kBAAkB;AAC5B,aAAO,KAAK;AAAA,IACb;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,IAAW,gBAAgB,OAAO;AACjC,WAAK,mBAAmB;AAAA,IACzB;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,IAAW,kBAAkB;AAC5B,aAAO,KAAK;AAAA,IACb;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,IAAW,gBAAgB,OAAO;AACjC,WAAK,mBAAmB;AAAA,IACzB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOQ,iBAAiB,OAAqB;AAC7C,UAAI,KAAK,qBAAqB;AAC7B,cAAM,aAAa,MAAM,KAAK,EAAE,kBAAkB;AAClD,YAAI,CAAC,WAAW,WAAW,QAAQ,KAAK,CAAC,WAAW,WAAW,OAAO,GAAG;AACxE,gBAAM,IAAI;AAAA,YACT,eAAe,KAAK;AAAA,UACrB;AAAA,QACD;AAAA,MACD;AAAA,IACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOQ,oBAAoB,OAAwB;AACnD,UAAI,KAAK,iBAAiB,IAAI,KAAK,GAAG;AACrC,cAAM,UAAU,KAAK,iBAAiB,IAAI,KAAK;AAC/C,cAAM,iBAAiB,SAAS,KAAK,kBAAkB,UAAU,KAAK,OAAO,KAAK,EAAE;AAGpF,aAAK,KAAK,QAAQ,EAAE,MAAM,OAAO,SAAS,eAAe,CAAC;AAG1D,eAAO,KAAK;AAAA,MACb;AACA,aAAO;AAAA,IACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQO,OAAO,OAAe,SAAe;AAC3C,WAAK,YAAY,EAAE,OAAO,QAAQ,CAAC;AAAA,IACpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOO,YAAY,WAAsB;AACxC,WAAK,iBAAiB,UAAU,KAAK;AACrC,UAAI,CAAC,KAAK,oBAAoB,UAAU,KAAK,GAAG;AAC/C;AAAA,MACD;AACA,YAAM,gBAAgB,KAAK,OAAO,IAAI,UAAU,KAAK;AACrD,UAAI,eAAe;AAClB,sBAAc,KAAK,UAAU,OAAO;AAAA,MACrC,OAAO;AACN,aAAK,OAAO,IAAI,UAAU,OAAO,CAAC,UAAU,OAAO,CAAC;AAAA,MACrD;AAAA,IACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQO,QAAQ,OAAe,SAAe;AAC5C,WAAK,YAAY,EAAE,OAAO,QAAQ,CAAC;AAAA,IACpC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOO,QAAQ,OAAoB;AAClC,iBAAW,QAAQ,OAAO;AACzB,aAAK,OAAO,KAAK,OAAO,KAAK,OAAO;AAAA,MACrC;AAAA,IACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQO,YAAY,OAAe,SAAe;AAChD,WAAK,iBAAiB,KAAK;AAC3B,UAAI,CAAC,KAAK,oBAAoB,KAAK,GAAG;AACrC;AAAA,MACD;AACA,YAAM,gBAAgB,KAAK,OAAO,IAAI,KAAK;AAC3C,UAAI,eAAe;AAClB,sBAAc,QAAQ,OAAO;AAAA,MAC9B,OAAO;AACN,aAAK,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;AAAA,MACjC;AAAA,IACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOO,gBAAgB,OAAe,SAAe;AACpD,WAAK,iBAAiB,KAAK;AAC3B,UAAI,CAAC,KAAK,oBAAoB,KAAK,GAAG;AACrC;AAAA,MACD;AAEA,YAAM,OAAO,UAAU,eAAsB;AAC5C,aAAK,WAAW,OAAO,IAAI;AAC3B,eAAO,QAAQ,GAAG,UAAU;AAAA,MAC7B;AAEA,WAAK,YAAY,OAAO,IAAI;AAAA,IAC7B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOO,SAAS,OAAe,SAAe;AAC7C,WAAK,iBAAiB,KAAK;AAC3B,UAAI,CAAC,KAAK,oBAAoB,KAAK,GAAG;AACrC;AAAA,MACD;AAEA,YAAM,OAAO,UAAU,eAAsB;AAC5C,aAAK,WAAW,OAAO,IAAI;AAC3B,eAAO,QAAQ,GAAG,UAAU;AAAA,MAC7B;AAEA,WAAK,OAAO,OAAO,IAAI;AAAA,IACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQO,WAAW,OAAe,SAAe;AAC/C,WAAK,iBAAiB,KAAK;AAC3B,UAAI,CAAC,KAAK,oBAAoB,KAAK,GAAG;AACrC;AAAA,MACD;AACA,YAAM,gBAAgB,KAAK,OAAO,IAAI,KAAK;AAC3C,UAAI,eAAe;AAClB,cAAM,QAAQ,cAAc,QAAQ,OAAO;AAC3C,YAAI,UAAU,IAAI;AACjB,wBAAc,OAAO,OAAO,CAAC;AAAA,QAC9B;AAAA,MACD;AAAA,IACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOO,YAAY,OAAoB;AACtC,iBAAW,QAAQ,OAAO;AACzB,aAAK,WAAW,KAAK,OAAO,KAAK,OAAO;AAAA,MACzC;AAAA,IACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQA,MAAa,KAAQ,UAAkB,YAAiB;AACvD,WAAK,iBAAiB,KAAK;AAC3B,UAAI,CAAC,KAAK,oBAAoB,KAAK,GAAG;AACrC;AAAA,MACD;AACA,YAAM,gBAAgB,KAAK,OAAO,IAAI,KAAK;AAC3C,UAAI,eAAe;AAClB,mBAAW,WAAW,eAAe;AACpC,cAAI;AACH,kBAAM,QAAQ,GAAG,UAAU;AAAA,UAC5B,SAAS,OAAO;AACf,kBAAM,UAAU,GAAG,KAAK,KAAM,MAAgB,OAAO;AACrD,iBAAK,KAAK,SAAS,IAAI,MAAM,OAAO,CAAC;AAErC,gBAAI,KAAK,mBAAmB;AAC3B,oBAAM,IAAI,MAAM,OAAO;AAAA,YACxB;AAAA,UACD;AAAA,QACD;AAAA,MACD;AAAA,IACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAYO,SAAY,UAAkB,YAAuB;AAC3D,WAAK,iBAAiB,KAAK;AAC3B,UAAI,CAAC,KAAK,oBAAoB,KAAK,GAAG;AACrC;AAAA,MACD;AAEA,YAAM,gBAAgB,KAAK,OAAO,IAAI,KAAK;AAC3C,UAAI,eAAe;AAClB,mBAAW,WAAW,eAAe;AAEpC,cAAI,QAAQ,YAAY,SAAS,iBAAiB;AACjD;AAAA,UACD;AAEA,cAAI;AACH,oBAAQ,GAAG,UAAU;AAAA,UACtB,SAAS,OAAO;AACf,kBAAM,UAAU,GAAG,KAAK,KAAM,MAAgB,OAAO;AACrD,iBAAK,KAAK,SAAS,IAAI,MAAM,OAAO,CAAC;AAErC,gBAAI,KAAK,mBAAmB;AAC3B,oBAAM,IAAI,MAAM,OAAO;AAAA,YACxB;AAAA,UACD;AAAA,QACD;AAAA,MACD;AAAA,IACD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOA,MAAa,WAAc,UAAkB,YAAiB;AAC7D,YAAM,KAAK,KAAK,UAAU,KAAK,IAAI,GAAG,UAAU;AAAA,IACjD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOA,MAAa,UAAa,UAAkB,YAAiB;AAC5D,YAAM,KAAK,KAAK,SAAS,KAAK,IAAI,GAAG,UAAU;AAAA,IAChD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IASA,MAAa,SAAY,UAAkB,YAAiB;AAC3D,YAAM,KAAK,KAAK,OAAO,GAAG,UAAU;AAAA,IACrC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOO,SAAS,OAAe;AAC9B,WAAK,iBAAiB,KAAK;AAC3B,UAAI,CAAC,KAAK,oBAAoB,KAAK,GAAG;AACrC,eAAO;AAAA,MACR;AACA,aAAO,KAAK,OAAO,IAAI,KAAK;AAAA,IAC7B;AAAA;AAAA;AAAA;AAAA;AAAA,IAMO,aAAa;AACnB,WAAK,OAAO,MAAM;AAAA,IACnB;AAAA,EACD;","names":[]}
{"version":3,"sources":["../../src/eventified.ts","../../src/hooks/hook.ts","../../src/hooks/waterfall-hook.ts","../../src/index.ts"],"sourcesContent":["// biome-ignore-all lint/suspicious/noExplicitAny: this is for event emitter compatibility\nimport type {\n\tEventEmitterOptions,\n\tEventListener,\n\tIEventEmitter,\n\tLogger,\n} from \"./types.js\";\n\nexport type { EventEmitterOptions, EventListener, IEventEmitter };\n\nexport class Eventified implements IEventEmitter {\n\tprivate readonly _eventListeners: Map<string | symbol, EventListener[]>;\n\tprivate _maxListeners: number;\n\tprivate _eventLogger?: Logger;\n\tprivate _throwOnEmitError = false;\n\tprivate _throwOnEmptyListeners = true;\n\tprivate _errorEvent = \"error\";\n\n\tconstructor(options?: EventEmitterOptions) {\n\t\tthis._eventListeners = new Map<string | symbol, EventListener[]>();\n\t\tthis._maxListeners = 0; // Default is 0 (unlimited)\n\n\t\tthis._eventLogger = options?.eventLogger;\n\n\t\tif (options?.throwOnEmitError !== undefined) {\n\t\t\tthis._throwOnEmitError = options.throwOnEmitError;\n\t\t}\n\n\t\tif (options?.throwOnEmptyListeners !== undefined) {\n\t\t\tthis._throwOnEmptyListeners = options.throwOnEmptyListeners;\n\t\t}\n\t}\n\n\t/**\n\t * Gets the event logger\n\t * @returns {Logger}\n\t */\n\tpublic get eventLogger(): Logger | undefined {\n\t\treturn this._eventLogger;\n\t}\n\n\t/**\n\t * Sets the event logger\n\t * @param {Logger} eventLogger\n\t */\n\tpublic set eventLogger(eventLogger: Logger | undefined) {\n\t\tthis._eventLogger = eventLogger;\n\t}\n\n\t/**\n\t * Gets whether an error should be thrown when an emit throws an error. Default is false and only emits an error event.\n\t * @returns {boolean}\n\t */\n\tpublic get throwOnEmitError(): boolean {\n\t\treturn this._throwOnEmitError;\n\t}\n\n\t/**\n\t * Sets whether an error should be thrown when an emit throws an error. Default is false and only emits an error event.\n\t * @param {boolean} value\n\t */\n\tpublic set throwOnEmitError(value: boolean) {\n\t\tthis._throwOnEmitError = value;\n\t}\n\n\t/**\n\t * Gets whether an error should be thrown when emitting 'error' event with no listeners. Default is false.\n\t * @returns {boolean}\n\t */\n\tpublic get throwOnEmptyListeners(): boolean {\n\t\treturn this._throwOnEmptyListeners;\n\t}\n\n\t/**\n\t * Sets whether an error should be thrown when emitting 'error' event with no listeners. Default is false.\n\t * @param {boolean} value\n\t */\n\tpublic set throwOnEmptyListeners(value: boolean) {\n\t\tthis._throwOnEmptyListeners = value;\n\t}\n\n\t/**\n\t * Adds a handler function for a specific event that will run only once\n\t * @param {string | symbol} eventName\n\t * @param {EventListener} listener\n\t * @returns {IEventEmitter} returns the instance of the class for chaining\n\t */\n\tpublic once(\n\t\teventName: string | symbol,\n\t\tlistener: EventListener,\n\t): IEventEmitter {\n\t\tconst onceListener: EventListener = (...arguments_: any[]) => {\n\t\t\tthis.off(eventName as string, onceListener);\n\t\t\tlistener(...arguments_);\n\t\t};\n\n\t\tthis.on(eventName as string, onceListener);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Gets the number of listeners for a specific event. If no event is provided, it returns the total number of listeners\n\t * @param {string} eventName The event name. Not required\n\t * @returns {number} The number of listeners\n\t */\n\tpublic listenerCount(eventName?: string | symbol): number {\n\t\tif (eventName === undefined) {\n\t\t\treturn this.getAllListeners().length;\n\t\t}\n\n\t\tconst listeners = this._eventListeners.get(eventName);\n\t\treturn listeners ? listeners.length : 0;\n\t}\n\n\t/**\n\t * Gets an array of event names\n\t * @returns {Array<string | symbol>} An array of event names\n\t */\n\tpublic eventNames(): Array<string | symbol> {\n\t\treturn [...this._eventListeners.keys()];\n\t}\n\n\t/**\n\t * Gets an array of listeners for a specific event. If no event is provided, it returns all listeners\n\t * @param {string} [event] (Optional) The event name\n\t * @returns {EventListener[]} An array of listeners\n\t */\n\tpublic rawListeners(event?: string | symbol): EventListener[] {\n\t\tif (event === undefined) {\n\t\t\treturn this.getAllListeners();\n\t\t}\n\n\t\treturn this._eventListeners.get(event) ?? [];\n\t}\n\n\t/**\n\t * Prepends a listener to the beginning of the listeners array for the specified event\n\t * @param {string | symbol} eventName\n\t * @param {EventListener} listener\n\t * @returns {IEventEmitter} returns the instance of the class for chaining\n\t */\n\tpublic prependListener(\n\t\teventName: string | symbol,\n\t\tlistener: EventListener,\n\t): IEventEmitter {\n\t\tconst listeners = this._eventListeners.get(eventName) ?? [];\n\t\tlisteners.unshift(listener);\n\t\tthis._eventListeners.set(eventName, listeners);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Prepends a one-time listener to the beginning of the listeners array for the specified event\n\t * @param {string | symbol} eventName\n\t * @param {EventListener} listener\n\t * @returns {IEventEmitter} returns the instance of the class for chaining\n\t */\n\tpublic prependOnceListener(\n\t\teventName: string | symbol,\n\t\tlistener: EventListener,\n\t): IEventEmitter {\n\t\tconst onceListener: EventListener = (...arguments_: any[]) => {\n\t\t\tthis.off(eventName as string, onceListener);\n\t\t\tlistener(...arguments_);\n\t\t};\n\n\t\tthis.prependListener(eventName as string, onceListener);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Gets the maximum number of listeners that can be added for a single event\n\t * @returns {number} The maximum number of listeners\n\t */\n\tpublic maxListeners(): number {\n\t\treturn this._maxListeners;\n\t}\n\n\t/**\n\t * Adds a listener for a specific event. It is an alias for the on() method\n\t * @param {string | symbol} event\n\t * @param {EventListener} listener\n\t * @returns {IEventEmitter} returns the instance of the class for chaining\n\t */\n\tpublic addListener(\n\t\tevent: string | symbol,\n\t\tlistener: EventListener,\n\t): IEventEmitter {\n\t\tthis.on(event, listener);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Adds a listener for a specific event\n\t * @param {string | symbol} event\n\t * @param {EventListener} listener\n\t * @returns {IEventEmitter} returns the instance of the class for chaining\n\t */\n\tpublic on(event: string | symbol, listener: EventListener): IEventEmitter {\n\t\tif (!this._eventListeners.has(event)) {\n\t\t\tthis._eventListeners.set(event, []);\n\t\t}\n\n\t\tconst listeners = this._eventListeners.get(event);\n\n\t\tif (listeners) {\n\t\t\tif (this._maxListeners > 0 && listeners.length >= this._maxListeners) {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`MaxListenersExceededWarning: Possible event memory leak detected. ${listeners.length + 1} ${event as string} listeners added. Use setMaxListeners() to increase limit.`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tlisteners.push(listener);\n\t\t}\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Removes a listener for a specific event. It is an alias for the off() method\n\t * @param {string | symbol} event\n\t * @param {EventListener} listener\n\t * @returns {IEventEmitter} returns the instance of the class for chaining\n\t */\n\tpublic removeListener(event: string, listener: EventListener): IEventEmitter {\n\t\tthis.off(event, listener);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Removes a listener for a specific event\n\t * @param {string | symbol} event\n\t * @param {EventListener} listener\n\t * @returns {IEventEmitter} returns the instance of the class for chaining\n\t */\n\tpublic off(event: string | symbol, listener: EventListener): IEventEmitter {\n\t\tconst listeners = this._eventListeners.get(event) ?? [];\n\t\tconst index = listeners.indexOf(listener);\n\t\tif (index !== -1) {\n\t\t\tlisteners.splice(index, 1);\n\t\t}\n\n\t\tif (listeners.length === 0) {\n\t\t\tthis._eventListeners.delete(event);\n\t\t}\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Calls all listeners for a specific event\n\t * @param {string | symbol} event\n\t * @param arguments_ The arguments to pass to the listeners\n\t * @returns {boolean} Returns true if the event had listeners, false otherwise\n\t */\n\tpublic emit(event: string | symbol, ...arguments_: any[]): boolean {\n\t\tlet result = false;\n\t\tconst listeners = this._eventListeners.get(event);\n\n\t\tif (listeners && listeners.length > 0) {\n\t\t\tfor (const listener of listeners) {\n\t\t\t\tlistener(...arguments_);\n\t\t\t\tresult = true;\n\t\t\t}\n\t\t}\n\n\t\t// send it to the logger\n\t\tthis.sendToEventLogger(event, arguments_);\n\n\t\tif (event === this._errorEvent) {\n\t\t\tconst error =\n\t\t\t\targuments_[0] instanceof Error\n\t\t\t\t\t? arguments_[0]\n\t\t\t\t\t: new Error(`${arguments_[0]}`);\n\n\t\t\tif (this._throwOnEmitError && !result) {\n\t\t\t\tthrow error;\n\t\t\t} else {\n\t\t\t\tif (\n\t\t\t\t\tthis.listeners(this._errorEvent).length === 0 &&\n\t\t\t\t\tthis._throwOnEmptyListeners === true\n\t\t\t\t) {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Gets all listeners for a specific event. If no event is provided, it returns all listeners\n\t * @param {string} [event] (Optional) The event name\n\t * @returns {EventListener[]} An array of listeners\n\t */\n\tpublic listeners(event: string | symbol): EventListener[] {\n\t\treturn this._eventListeners.get(event) ?? [];\n\t}\n\n\t/**\n\t * Removes all listeners for a specific event. If no event is provided, it removes all listeners\n\t * @param {string} [event] (Optional) The event name\n\t * @returns {IEventEmitter} returns the instance of the class for chaining\n\t */\n\tpublic removeAllListeners(event?: string | symbol): IEventEmitter {\n\t\tif (event !== undefined) {\n\t\t\tthis._eventListeners.delete(event);\n\t\t} else {\n\t\t\tthis._eventListeners.clear();\n\t\t}\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Sets the maximum number of listeners that can be added for a single event\n\t * @param {number} n The maximum number of listeners\n\t * @returns {void}\n\t */\n\tpublic setMaxListeners(n: number): void {\n\t\tthis._maxListeners = n < 0 ? 0 : n;\n\t}\n\n\t/**\n\t * Gets all listeners\n\t * @returns {EventListener[]} An array of listeners\n\t */\n\tpublic getAllListeners(): EventListener[] {\n\t\tlet result: EventListener[] = [];\n\t\tfor (const listeners of this._eventListeners.values()) {\n\t\t\tresult = [...result, ...listeners];\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Sends a log message using the configured logger based on the event name\n\t * @param {string | symbol} eventName - The event name that determines the log level\n\t * @param {unknown} data - The data to log\n\t */\n\tprivate sendToEventLogger(eventName: string | symbol, data: any): void {\n\t\tif (!this._eventLogger) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet message: string;\n\t\t/* v8 ignore next -- @preserve */\n\t\tif (typeof data === \"string\") {\n\t\t\tmessage = data;\n\t\t} else if (\n\t\t\tArray.isArray(data) &&\n\t\t\tdata.length > 0 &&\n\t\t\tdata[0] instanceof Error\n\t\t) {\n\t\t\tmessage = data[0].message;\n\t\t\t/* v8 ignore next -- @preserve */\n\t\t} else if (data instanceof Error) {\n\t\t\tmessage = data.message;\n\t\t} else if (\n\t\t\tArray.isArray(data) &&\n\t\t\tdata.length > 0 &&\n\t\t\ttypeof data[0]?.message === \"string\"\n\t\t) {\n\t\t\tmessage = data[0].message;\n\t\t} else {\n\t\t\tmessage = JSON.stringify(data);\n\t\t}\n\n\t\tswitch (eventName) {\n\t\t\tcase \"error\": {\n\t\t\t\tthis._eventLogger.error?.(message, { event: eventName, data });\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"warn\": {\n\t\t\t\tthis._eventLogger.warn?.(message, { event: eventName, data });\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"trace\": {\n\t\t\t\tthis._eventLogger.trace?.(message, { event: eventName, data });\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"debug\": {\n\t\t\t\tthis._eventLogger.debug?.(message, { event: eventName, data });\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"fatal\": {\n\t\t\t\tthis._eventLogger.fatal?.(message, { event: eventName, data });\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault: {\n\t\t\t\tthis._eventLogger.info?.(message, { event: eventName, data });\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n}\n","import type { HookFn, IHook } from \"../types.js\";\n\n/**\n * Concrete implementation of the IHook interface.\n * Provides a convenient class-based way to create hook entries.\n */\nexport class Hook implements IHook {\n\tpublic id?: string;\n\tpublic event: string;\n\tpublic handler: HookFn;\n\n\t/**\n\t * Creates a new Hook instance\n\t * @param {string} event - The event name for the hook\n\t * @param {HookFn} handler - The handler function for the hook\n\t * @param {string} [id] - Optional unique identifier for the hook\n\t */\n\tconstructor(event: string, handler: HookFn, id?: string) {\n\t\tthis.id = id;\n\t\tthis.event = event;\n\t\tthis.handler = handler;\n\t}\n}\n","import type {\n\tHookFn,\n\tIWaterfallHook,\n\tWaterfallHookFn,\n\tWaterfallHookResult,\n} from \"../types.js\";\n\n/**\n * A WaterfallHook chains multiple hook functions sequentially,\n * where each hook receives a context with the previous result,\n * initial arguments, and accumulated results. After all hooks\n * have executed, the final handler receives the transformed result.\n * Implements IHook for compatibility with Hookified.onHook().\n */\nexport class WaterfallHook implements IWaterfallHook {\n\tpublic id?: string;\n\tpublic event: string;\n\tpublic handler: HookFn;\n\tpublic hooks: WaterfallHookFn[];\n\n\tprivate readonly _finalHandler: WaterfallHookFn;\n\n\t/**\n\t * Creates a new WaterfallHook instance\n\t * @param {string} event - The event name for the hook\n\t * @param {WaterfallHookFn} finalHandler - The final handler function that receives the transformed result\n\t * @param {string} [id] - Optional unique identifier for the hook\n\t */\n\tconstructor(event: string, finalHandler: WaterfallHookFn, id?: string) {\n\t\tthis.id = id;\n\t\tthis.event = event;\n\t\tthis.hooks = [];\n\t\tthis._finalHandler = finalHandler;\n\n\t\t// biome-ignore lint/suspicious/noExplicitAny: this is for any parameter compatibility\n\t\tthis.handler = async (...arguments_: any[]) => {\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: waterfall result type varies through the chain\n\t\t\tconst initialArgs: any =\n\t\t\t\targuments_.length === 1 ? arguments_[0] : arguments_;\n\t\t\tconst results: WaterfallHookResult[] = [];\n\n\t\t\tfor (const hook of this.hooks) {\n\t\t\t\tconst result = await hook({ initialArgs, results: [...results] });\n\t\t\t\tresults.push({ hook, result });\n\t\t\t}\n\n\t\t\tawait this._finalHandler({ initialArgs, results: [...results] });\n\t\t};\n\t}\n\n\t/**\n\t * Adds a hook function to the end of the waterfall chain\n\t * @param {WaterfallHookFn} hook - The hook function to add\n\t */\n\tpublic addHook(hook: WaterfallHookFn): void {\n\t\tthis.hooks.push(hook);\n\t}\n\n\t/**\n\t * Removes a specific hook function from the waterfall chain\n\t * @param {WaterfallHookFn} hook - The hook function to remove\n\t * @returns {boolean} true if the hook was found and removed\n\t */\n\tpublic removeHook(hook: WaterfallHookFn): boolean {\n\t\tconst index = this.hooks.indexOf(hook);\n\t\tif (index !== -1) {\n\t\t\tthis.hooks.splice(index, 1);\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n}\n","import { Eventified } from \"./eventified.js\";\nimport type {\n\tHookFn,\n\tHookifiedOptions,\n\tIHook,\n\tIWaterfallHook,\n\tOnHookOptions,\n\tPrependHookOptions,\n\tWaterfallHookContext,\n\tWaterfallHookFn,\n\tWaterfallHookResult,\n} from \"./types.js\";\n\nexport type {\n\tHookFn,\n\tHookifiedOptions,\n\tIHook,\n\tIWaterfallHook,\n\tOnHookOptions,\n\tPrependHookOptions,\n\tWaterfallHookContext,\n\tWaterfallHookFn,\n\tWaterfallHookResult,\n};\n\nexport class Hookified extends Eventified {\n\tprivate readonly _hooks: Map<string, IHook[]>;\n\tprivate _throwOnHookError = false;\n\tprivate _enforceBeforeAfter = false;\n\tprivate _deprecatedHooks: Map<string, string>;\n\tprivate _allowDeprecated = true;\n\tprivate _useHookClone = true;\n\n\tconstructor(options?: HookifiedOptions) {\n\t\tsuper({\n\t\t\teventLogger: options?.eventLogger,\n\t\t\tthrowOnEmitError: options?.throwOnEmitError,\n\t\t\tthrowOnEmptyListeners: options?.throwOnEmptyListeners,\n\t\t});\n\t\tthis._hooks = new Map();\n\t\tthis._deprecatedHooks = options?.deprecatedHooks\n\t\t\t? new Map(options.deprecatedHooks)\n\t\t\t: new Map();\n\n\t\tif (options?.throwOnHookError !== undefined) {\n\t\t\tthis._throwOnHookError = options.throwOnHookError;\n\t\t}\n\n\t\tif (options?.enforceBeforeAfter !== undefined) {\n\t\t\tthis._enforceBeforeAfter = options.enforceBeforeAfter;\n\t\t}\n\n\t\tif (options?.allowDeprecated !== undefined) {\n\t\t\tthis._allowDeprecated = options.allowDeprecated;\n\t\t}\n\n\t\tif (options?.useHookClone !== undefined) {\n\t\t\tthis._useHookClone = options.useHookClone;\n\t\t}\n\t}\n\n\t/**\n\t * Gets all hooks\n\t * @returns {Map<string, IHook[]>}\n\t */\n\tpublic get hooks() {\n\t\treturn this._hooks;\n\t}\n\n\t/**\n\t * Gets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.\n\t * @returns {boolean}\n\t */\n\tpublic get throwOnHookError() {\n\t\treturn this._throwOnHookError;\n\t}\n\n\t/**\n\t * Sets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.\n\t * @param {boolean} value\n\t */\n\tpublic set throwOnHookError(value) {\n\t\tthis._throwOnHookError = value;\n\t}\n\n\t/**\n\t * Gets whether to enforce that all hook names start with 'before' or 'after'. Default is false.\n\t * @returns {boolean}\n\t * @default false\n\t */\n\tpublic get enforceBeforeAfter() {\n\t\treturn this._enforceBeforeAfter;\n\t}\n\n\t/**\n\t * Sets whether to enforce that all hook names start with 'before' or 'after'. Default is false.\n\t * @param {boolean} value\n\t */\n\tpublic set enforceBeforeAfter(value) {\n\t\tthis._enforceBeforeAfter = value;\n\t}\n\n\t/**\n\t * Gets the map of deprecated hook names to deprecation messages.\n\t * @returns {Map<string, string>}\n\t */\n\tpublic get deprecatedHooks() {\n\t\treturn this._deprecatedHooks;\n\t}\n\n\t/**\n\t * Sets the map of deprecated hook names to deprecation messages.\n\t * @param {Map<string, string>} value\n\t */\n\tpublic set deprecatedHooks(value) {\n\t\tthis._deprecatedHooks = value;\n\t}\n\n\t/**\n\t * Gets whether deprecated hooks are allowed to be registered and executed. Default is true.\n\t * @returns {boolean}\n\t */\n\tpublic get allowDeprecated() {\n\t\treturn this._allowDeprecated;\n\t}\n\n\t/**\n\t * Sets whether deprecated hooks are allowed to be registered and executed. Default is true.\n\t * @param {boolean} value\n\t */\n\tpublic set allowDeprecated(value) {\n\t\tthis._allowDeprecated = value;\n\t}\n\n\t/**\n\t * Gets whether hook objects are cloned before storing. Default is true.\n\t * @returns {boolean}\n\t */\n\tpublic get useHookClone() {\n\t\treturn this._useHookClone;\n\t}\n\n\t/**\n\t * Sets whether hook objects are cloned before storing. Default is true.\n\t * When false, the original IHook reference is stored directly.\n\t * @param {boolean} value\n\t */\n\tpublic set useHookClone(value) {\n\t\tthis._useHookClone = value;\n\t}\n\n\t/**\n\t * Adds a handler function for a specific event.\n\t * If you prefer the legacy `(event, handler)` signature, use {@link addHook} instead.\n\t * To register multiple hooks at once, use {@link onHooks}.\n\t * @param {IHook} hook - the hook containing event name and handler\n\t * @param {OnHookOptions} [options] - optional per-call options (e.g., useHookClone override, position)\n\t * @returns {IHook | undefined} the stored hook, or undefined if blocked by deprecation\n\t */\n\tpublic onHook(hook: IHook, options?: OnHookOptions): IHook | undefined {\n\t\tthis.validateHookName(hook.event);\n\t\tif (!this.checkDeprecatedHook(hook.event)) {\n\t\t\treturn undefined; // Skip registration if deprecated hooks are not allowed\n\t\t}\n\n\t\tconst shouldClone = options?.useHookClone ?? this._useHookClone;\n\t\tconst entry: IHook = shouldClone\n\t\t\t? { id: hook.id, event: hook.event, handler: hook.handler }\n\t\t\t: hook;\n\n\t\tentry.id = entry.id ?? crypto.randomUUID();\n\n\t\tconst eventHandlers = this._hooks.get(hook.event);\n\t\tif (eventHandlers) {\n\t\t\t// Check for duplicate id — replace in-place if found\n\t\t\tconst existingIndex = eventHandlers.findIndex((h) => h.id === entry.id);\n\t\t\tif (existingIndex !== -1) {\n\t\t\t\teventHandlers[existingIndex] = entry;\n\t\t\t} else {\n\t\t\t\tconst position = options?.position ?? \"Bottom\";\n\t\t\t\tif (position === \"Top\") {\n\t\t\t\t\teventHandlers.unshift(entry);\n\t\t\t\t} else if (position === \"Bottom\") {\n\t\t\t\t\teventHandlers.push(entry);\n\t\t\t\t} else {\n\t\t\t\t\tconst index = Math.max(0, Math.min(position, eventHandlers.length));\n\t\t\t\t\teventHandlers.splice(index, 0, entry);\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tthis._hooks.set(hook.event, [entry]);\n\t\t}\n\n\t\treturn entry;\n\t}\n\n\t/**\n\t * Alias for onHook. This is provided for compatibility with other libraries that use the `addHook` method.\n\t * @param {string} event - the event name\n\t * @param {HookFn} handler - the handler function\n\t * @returns {void}\n\t */\n\tpublic addHook(event: string, handler: HookFn) {\n\t\tthis.onHook({ event, handler });\n\t}\n\n\t/**\n\t * Adds handler functions for specific events\n\t * @param {Array<IHook>} hooks\n\t * @param {OnHookOptions} [options] - optional per-call options (e.g., useHookClone override, position)\n\t * @returns {void}\n\t */\n\tpublic onHooks(hooks: IHook[], options?: OnHookOptions) {\n\t\tfor (const hook of hooks) {\n\t\t\tthis.onHook(hook, options);\n\t\t}\n\t}\n\n\t/**\n\t * Adds a handler function for a specific event that runs before all other handlers.\n\t * Equivalent to calling `onHook(hook, { position: \"Top\" })`.\n\t * @param {IHook} hook - the hook containing event name and handler\n\t * @param {PrependHookOptions} [options] - optional per-call options (e.g., useHookClone override)\n\t * @returns {IHook | undefined} the stored hook, or undefined if blocked by deprecation\n\t */\n\tpublic prependHook(\n\t\thook: IHook,\n\t\toptions?: PrependHookOptions,\n\t): IHook | undefined {\n\t\treturn this.onHook(hook, { ...options, position: \"Top\" });\n\t}\n\n\t/**\n\t * Adds a handler that only executes once for a specific event before all other handlers.\n\t * Equivalent to calling `onHook` with a self-removing wrapper and `{ position: \"Top\" }`.\n\t * @param {IHook} hook - the hook containing event name and handler\n\t * @param {PrependHookOptions} [options] - optional per-call options (e.g., useHookClone override)\n\t * @returns {IHook | undefined} the stored hook, or undefined if blocked by deprecation\n\t */\n\tpublic prependOnceHook(\n\t\thook: IHook,\n\t\toptions?: PrependHookOptions,\n\t): IHook | undefined {\n\t\t// biome-ignore lint/suspicious/noExplicitAny: this is for any parameter compatibility\n\t\tconst wrappedHandler = async (...arguments_: any[]) => {\n\t\t\tthis.removeHook({ event: hook.event, handler: wrappedHandler });\n\t\t\treturn hook.handler(...arguments_);\n\t\t};\n\n\t\treturn this.onHook(\n\t\t\t{ id: hook.id, event: hook.event, handler: wrappedHandler },\n\t\t\t{ ...options, position: \"Top\" },\n\t\t);\n\t}\n\n\t/**\n\t * Adds a handler that only executes once for a specific event\n\t * @param {IHook} hook - the hook containing event name and handler\n\t */\n\tpublic onceHook(hook: IHook) {\n\t\tthis.validateHookName(hook.event);\n\t\tif (!this.checkDeprecatedHook(hook.event)) {\n\t\t\treturn; // Skip registration if deprecated hooks are not allowed\n\t\t}\n\t\t// biome-ignore lint/suspicious/noExplicitAny: this is for any parameter compatibility\n\t\tconst wrappedHandler = async (...arguments_: any[]) => {\n\t\t\tthis.removeHook({ event: hook.event, handler: wrappedHandler });\n\t\t\treturn hook.handler(...arguments_);\n\t\t};\n\n\t\tthis.onHook({ id: hook.id, event: hook.event, handler: wrappedHandler });\n\t}\n\n\t/**\n\t * Removes a handler function for a specific event\n\t * @param {IHook} hook - the hook containing event name and handler to remove\n\t * @returns {IHook | undefined} the removed hook, or undefined if not found\n\t */\n\tpublic removeHook(hook: IHook): IHook | undefined {\n\t\tthis.validateHookName(hook.event);\n\t\tconst eventHandlers = this._hooks.get(hook.event);\n\t\tif (eventHandlers) {\n\t\t\tconst index = eventHandlers.findIndex((h) => h.handler === hook.handler);\n\t\t\tif (index !== -1) {\n\t\t\t\teventHandlers.splice(index, 1);\n\t\t\t\tif (eventHandlers.length === 0) {\n\t\t\t\t\tthis._hooks.delete(hook.event);\n\t\t\t\t}\n\n\t\t\t\treturn { event: hook.event, handler: hook.handler };\n\t\t\t}\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Removes multiple hook handlers\n\t * @param {Array<IHook>} hooks\n\t * @returns {IHook[]} the hooks that were successfully removed\n\t */\n\tpublic removeHooks(hooks: IHook[]): IHook[] {\n\t\tconst removed: IHook[] = [];\n\t\tfor (const hook of hooks) {\n\t\t\tconst result = this.removeHook(hook);\n\t\t\tif (result) {\n\t\t\t\tremoved.push(result);\n\t\t\t}\n\t\t}\n\n\t\treturn removed;\n\t}\n\n\t/**\n\t * Calls all handlers for a specific event\n\t * @param {string} event\n\t * @param {T[]} arguments_\n\t * @returns {Promise<void>}\n\t */\n\tpublic async hook<T>(event: string, ...arguments_: T[]) {\n\t\tthis.validateHookName(event);\n\t\tif (!this.checkDeprecatedHook(event)) {\n\t\t\treturn; // Skip execution if deprecated hooks are not allowed\n\t\t}\n\t\tconst eventHandlers = this._hooks.get(event);\n\t\tif (eventHandlers) {\n\t\t\tfor (const hook of [...eventHandlers]) {\n\t\t\t\ttry {\n\t\t\t\t\tawait hook.handler(...arguments_);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst message = `${event}: ${(error as Error).message}`;\n\t\t\t\t\tthis.emit(\"error\", new Error(message));\n\n\t\t\t\t\tif (this._throwOnHookError) {\n\t\t\t\t\t\tthrow new Error(message);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Calls all synchronous handlers for a specific event.\n\t * Async handlers (declared with `async` keyword) are silently skipped.\n\t *\n\t * Note: The `hook` method is preferred as it executes both sync and async functions.\n\t * Use `hookSync` only when you specifically need synchronous execution.\n\t * @param {string} event\n\t * @param {T[]} arguments_\n\t * @returns {void}\n\t */\n\tpublic hookSync<T>(event: string, ...arguments_: T[]): void {\n\t\tthis.validateHookName(event);\n\t\tif (!this.checkDeprecatedHook(event)) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst eventHandlers = this._hooks.get(event);\n\t\tif (eventHandlers) {\n\t\t\tfor (const hook of [...eventHandlers]) {\n\t\t\t\t// Skip async functions silently\n\t\t\t\tif (hook.handler.constructor.name === \"AsyncFunction\") {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\thook.handler(...arguments_);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst message = `${event}: ${(error as Error).message}`;\n\t\t\t\t\tthis.emit(\"error\", new Error(message));\n\n\t\t\t\t\tif (this._throwOnHookError) {\n\t\t\t\t\t\tthrow new Error(message);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Prepends the word `before` to your hook. Example is event is `test`, the before hook is `before:test`.\n\t * @param {string} event - The event name\n\t * @param {T[]} arguments_ - The arguments to pass to the hook\n\t */\n\tpublic async beforeHook<T>(event: string, ...arguments_: T[]) {\n\t\tawait this.hook(`before:${event}`, ...arguments_);\n\t}\n\n\t/**\n\t * Prepends the word `after` to your hook. Example is event is `test`, the after hook is `after:test`.\n\t * @param {string} event - The event name\n\t * @param {T[]} arguments_ - The arguments to pass to the hook\n\t */\n\tpublic async afterHook<T>(event: string, ...arguments_: T[]) {\n\t\tawait this.hook(`after:${event}`, ...arguments_);\n\t}\n\n\t/**\n\t * Calls all handlers for a specific event. This is an alias for `hook` and is provided for\n\t * compatibility with other libraries that use the `callHook` method.\n\t * @param {string} event\n\t * @param {T[]} arguments_\n\t * @returns {Promise<void>}\n\t */\n\tpublic async callHook<T>(event: string, ...arguments_: T[]) {\n\t\tawait this.hook(event, ...arguments_);\n\t}\n\n\t/**\n\t * Gets all hooks for a specific event\n\t * @param {string} event\n\t * @returns {IHook[]}\n\t */\n\tpublic getHooks(event: string) {\n\t\tthis.validateHookName(event);\n\t\treturn this._hooks.get(event);\n\t}\n\n\t/**\n\t * Gets a specific hook by id, searching across all events\n\t * @param {string} id - the hook id\n\t * @returns {IHook | undefined} the hook if found, or undefined\n\t */\n\tpublic getHook(id: string): IHook | undefined {\n\t\tfor (const eventHandlers of this._hooks.values()) {\n\t\t\tconst found = eventHandlers.find((h) => h.id === id);\n\t\t\tif (found) {\n\t\t\t\treturn found;\n\t\t\t}\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Removes one or more hooks by id, searching across all events\n\t * @param {string | string[]} id - the hook id or array of hook ids to remove\n\t * @returns {IHook | IHook[] | undefined} the removed hook(s), or undefined/empty array if not found\n\t */\n\tpublic removeHookById(id: string | string[]): IHook | IHook[] | undefined {\n\t\tif (Array.isArray(id)) {\n\t\t\tconst removed: IHook[] = [];\n\t\t\tfor (const singleId of id) {\n\t\t\t\tconst result = this.removeHookById(singleId);\n\t\t\t\tif (result && !Array.isArray(result)) {\n\t\t\t\t\tremoved.push(result);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn removed;\n\t\t}\n\n\t\tfor (const [event, eventHandlers] of this._hooks.entries()) {\n\t\t\tconst index = eventHandlers.findIndex((h) => h.id === id);\n\t\t\tif (index !== -1) {\n\t\t\t\tconst [removed] = eventHandlers.splice(index, 1);\n\t\t\t\tif (eventHandlers.length === 0) {\n\t\t\t\t\tthis._hooks.delete(event);\n\t\t\t\t}\n\n\t\t\t\treturn removed;\n\t\t\t}\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Removes all hooks\n\t * @returns {void}\n\t */\n\tpublic clearHooks() {\n\t\tthis._hooks.clear();\n\t}\n\n\t/**\n\t * Removes all hooks for a specific event and returns the removed hooks.\n\t * @param {string} event - The event name to remove hooks for.\n\t * @returns {IHook[]} the hooks that were removed\n\t */\n\tpublic removeEventHooks(event: string): IHook[] {\n\t\tthis.validateHookName(event);\n\t\tconst eventHandlers = this._hooks.get(event);\n\t\tif (eventHandlers) {\n\t\t\tconst removed = [...eventHandlers];\n\t\t\tthis._hooks.delete(event);\n\t\t\treturn removed;\n\t\t}\n\n\t\treturn [];\n\t}\n\n\t/**\n\t * Validates hook event name if enforceBeforeAfter is enabled\n\t * @param {string} event - The event name to validate\n\t * @throws {Error} If enforceBeforeAfter is true and event doesn't start with 'before' or 'after'\n\t */\n\tprivate validateHookName(event: string): void {\n\t\tif (this._enforceBeforeAfter) {\n\t\t\tconst eventValue = event.trim().toLocaleLowerCase();\n\t\t\tif (!eventValue.startsWith(\"before\") && !eventValue.startsWith(\"after\")) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Hook event \"${event}\" must start with \"before\" or \"after\" when enforceBeforeAfter is enabled`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Checks if a hook is deprecated and emits a warning if it is\n\t * @param {string} event - The event name to check\n\t * @returns {boolean} - Returns true if the hook should proceed, false if it should be blocked\n\t */\n\tprivate checkDeprecatedHook(event: string): boolean {\n\t\tif (this._deprecatedHooks.has(event)) {\n\t\t\tconst message = this._deprecatedHooks.get(event);\n\t\t\tconst warningMessage = `Hook \"${event}\" is deprecated${message ? `: ${message}` : \"\"}`;\n\n\t\t\t// Emit deprecation warning event\n\t\t\tthis.emit(\"warn\", { hook: event, message: warningMessage });\n\n\t\t\t// Return false if deprecated hooks are not allowed\n\t\t\treturn this._allowDeprecated;\n\t\t}\n\t\treturn true;\n\t}\n}\n\nexport { Eventified } from \"./eventified.js\";\nexport { Hook } from \"./hooks/hook.js\";\nexport { WaterfallHook } from \"./hooks/waterfall-hook.js\";\nexport type {\n\tEventEmitterOptions,\n\tEventListener,\n\tIEventEmitter,\n\tLogger,\n} from \"./types.js\";\n"],"mappings":"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","names":[]}

@@ -10,9 +10,9 @@ var __defProp = Object.defineProperty;

__publicField(this, "_maxListeners");
__publicField(this, "_logger");
__publicField(this, "_eventLogger");
__publicField(this, "_throwOnEmitError", false);
__publicField(this, "_throwOnEmptyListeners", false);
__publicField(this, "_throwOnEmptyListeners", true);
__publicField(this, "_errorEvent", "error");
this._eventListeners = /* @__PURE__ */ new Map();
this._maxListeners = 100;
this._logger = options?.logger;
this._maxListeners = 0;
this._eventLogger = options?.eventLogger;
if (options?.throwOnEmitError !== void 0) {

@@ -26,14 +26,14 @@ this._throwOnEmitError = options.throwOnEmitError;

/**
* Gets the logger
* Gets the event logger
* @returns {Logger}
*/
get logger() {
return this._logger;
get eventLogger() {
return this._eventLogger;
}
/**
* Sets the logger
* @param {Logger} logger
* Sets the event logger
* @param {Logger} eventLogger
*/
set logger(logger) {
this._logger = logger;
set eventLogger(eventLogger) {
this._eventLogger = eventLogger;
}

@@ -167,3 +167,3 @@ /**

if (listeners) {
if (listeners.length >= this._maxListeners) {
if (this._maxListeners > 0 && listeners.length >= this._maxListeners) {
console.warn(

@@ -219,2 +219,3 @@ `MaxListenersExceededWarning: Possible event memory leak detected. ${listeners.length + 1} ${event} listeners added. Use setMaxListeners() to increase limit.`

}
this.sendToEventLogger(event, arguments_);
if (event === this._errorEvent) {

@@ -230,3 +231,2 @@ const error = arguments_[0] instanceof Error ? arguments_[0] : new Error(`${arguments_[0]}`);

}
this.sendLog(event, arguments_);
return result;

@@ -261,8 +261,3 @@ }

setMaxListeners(n) {
this._maxListeners = n;
for (const listeners of this._eventListeners.values()) {
if (listeners.length > n) {
listeners.splice(n);
}
}
this._maxListeners = n < 0 ? 0 : n;
}

@@ -285,4 +280,4 @@ /**

*/
sendLog(eventName, data) {
if (!this._logger) {
sendToEventLogger(eventName, data) {
if (!this._eventLogger) {
return;

@@ -304,23 +299,23 @@ }

case "error": {
this._logger.error?.(message, { event: eventName, data });
this._eventLogger.error?.(message, { event: eventName, data });
break;
}
case "warn": {
this._logger.warn?.(message, { event: eventName, data });
this._eventLogger.warn?.(message, { event: eventName, data });
break;
}
case "trace": {
this._logger.trace?.(message, { event: eventName, data });
this._eventLogger.trace?.(message, { event: eventName, data });
break;
}
case "debug": {
this._logger.debug?.(message, { event: eventName, data });
this._eventLogger.debug?.(message, { event: eventName, data });
break;
}
case "fatal": {
this._logger.fatal?.(message, { event: eventName, data });
this._eventLogger.fatal?.(message, { event: eventName, data });
break;
}
default: {
this._logger.info?.(message, { event: eventName, data });
this._eventLogger.info?.(message, { event: eventName, data });
break;

@@ -332,2 +327,70 @@ }

// src/hooks/hook.ts
var Hook = class {
/**
* Creates a new Hook instance
* @param {string} event - The event name for the hook
* @param {HookFn} handler - The handler function for the hook
* @param {string} [id] - Optional unique identifier for the hook
*/
constructor(event, handler, id) {
__publicField(this, "id");
__publicField(this, "event");
__publicField(this, "handler");
this.id = id;
this.event = event;
this.handler = handler;
}
};
// src/hooks/waterfall-hook.ts
var WaterfallHook = class {
/**
* Creates a new WaterfallHook instance
* @param {string} event - The event name for the hook
* @param {WaterfallHookFn} finalHandler - The final handler function that receives the transformed result
* @param {string} [id] - Optional unique identifier for the hook
*/
constructor(event, finalHandler, id) {
__publicField(this, "id");
__publicField(this, "event");
__publicField(this, "handler");
__publicField(this, "hooks");
__publicField(this, "_finalHandler");
this.id = id;
this.event = event;
this.hooks = [];
this._finalHandler = finalHandler;
this.handler = async (...arguments_) => {
const initialArgs = arguments_.length === 1 ? arguments_[0] : arguments_;
const results = [];
for (const hook of this.hooks) {
const result = await hook({ initialArgs, results: [...results] });
results.push({ hook, result });
}
await this._finalHandler({ initialArgs, results: [...results] });
};
}
/**
* Adds a hook function to the end of the waterfall chain
* @param {WaterfallHookFn} hook - The hook function to add
*/
addHook(hook) {
this.hooks.push(hook);
}
/**
* Removes a specific hook function from the waterfall chain
* @param {WaterfallHookFn} hook - The hook function to remove
* @returns {boolean} true if the hook was found and removed
*/
removeHook(hook) {
const index = this.hooks.indexOf(hook);
if (index !== -1) {
this.hooks.splice(index, 1);
return true;
}
return false;
}
};
// src/index.ts

@@ -337,3 +400,3 @@ var Hookified = class extends Eventified {

super({
logger: options?.logger,
eventLogger: options?.eventLogger,
throwOnEmitError: options?.throwOnEmitError,

@@ -347,2 +410,3 @@ throwOnEmptyListeners: options?.throwOnEmptyListeners

__publicField(this, "_allowDeprecated", true);
__publicField(this, "_useHookClone", true);
this._hooks = /* @__PURE__ */ new Map();

@@ -352,4 +416,2 @@ this._deprecatedHooks = options?.deprecatedHooks ? new Map(options.deprecatedHooks) : /* @__PURE__ */ new Map();

this._throwOnHookError = options.throwOnHookError;
} else if (options?.throwHookErrors !== void 0) {
this._throwOnHookError = options.throwHookErrors;
}

@@ -362,6 +424,9 @@ if (options?.enforceBeforeAfter !== void 0) {

}
if (options?.useHookClone !== void 0) {
this._useHookClone = options.useHookClone;
}
}
/**
* Gets all hooks
* @returns {Map<string, Hook[]>}
* @returns {Map<string, IHook[]>}
*/

@@ -374,19 +439,3 @@ get hooks() {

* @returns {boolean}
* @deprecated - this will be deprecated in version 2. Please use throwOnHookError.
*/
get throwHookErrors() {
return this._throwOnHookError;
}
/**
* Sets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.
* @param {boolean} value
* @deprecated - this will be deprecated in version 2. Please use throwOnHookError.
*/
set throwHookErrors(value) {
this._throwOnHookError = value;
}
/**
* Gets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.
* @returns {boolean}
*/
get throwOnHookError() {

@@ -446,153 +495,149 @@ return this._throwOnHookError;

/**
* Validates hook event name if enforceBeforeAfter is enabled
* @param {string} event - The event name to validate
* @throws {Error} If enforceBeforeAfter is true and event doesn't start with 'before' or 'after'
* Gets whether hook objects are cloned before storing. Default is true.
* @returns {boolean}
*/
validateHookName(event) {
if (this._enforceBeforeAfter) {
const eventValue = event.trim().toLocaleLowerCase();
if (!eventValue.startsWith("before") && !eventValue.startsWith("after")) {
throw new Error(
`Hook event "${event}" must start with "before" or "after" when enforceBeforeAfter is enabled`
);
}
}
get useHookClone() {
return this._useHookClone;
}
/**
* Checks if a hook is deprecated and emits a warning if it is
* @param {string} event - The event name to check
* @returns {boolean} - Returns true if the hook should proceed, false if it should be blocked
* Sets whether hook objects are cloned before storing. Default is true.
* When false, the original IHook reference is stored directly.
* @param {boolean} value
*/
checkDeprecatedHook(event) {
if (this._deprecatedHooks.has(event)) {
const message = this._deprecatedHooks.get(event);
const warningMessage = `Hook "${event}" is deprecated${message ? `: ${message}` : ""}`;
this.emit("warn", { hook: event, message: warningMessage });
return this._allowDeprecated;
}
return true;
set useHookClone(value) {
this._useHookClone = value;
}
/**
* Adds a handler function for a specific event
* @param {string} event
* @param {Hook} handler - this can be async or sync
* @returns {void}
* Adds a handler function for a specific event.
* If you prefer the legacy `(event, handler)` signature, use {@link addHook} instead.
* To register multiple hooks at once, use {@link onHooks}.
* @param {IHook} hook - the hook containing event name and handler
* @param {OnHookOptions} [options] - optional per-call options (e.g., useHookClone override, position)
* @returns {IHook | undefined} the stored hook, or undefined if blocked by deprecation
*/
onHook(event, handler) {
this.onHookEntry({ event, handler });
}
/**
* Adds a handler function for a specific event
* @param {HookEntry} hookEntry
* @returns {void}
*/
onHookEntry(hookEntry) {
this.validateHookName(hookEntry.event);
if (!this.checkDeprecatedHook(hookEntry.event)) {
return;
onHook(hook, options) {
this.validateHookName(hook.event);
if (!this.checkDeprecatedHook(hook.event)) {
return void 0;
}
const eventHandlers = this._hooks.get(hookEntry.event);
const shouldClone = options?.useHookClone ?? this._useHookClone;
const entry = shouldClone ? { id: hook.id, event: hook.event, handler: hook.handler } : hook;
entry.id = entry.id ?? crypto.randomUUID();
const eventHandlers = this._hooks.get(hook.event);
if (eventHandlers) {
eventHandlers.push(hookEntry.handler);
const existingIndex = eventHandlers.findIndex((h) => h.id === entry.id);
if (existingIndex !== -1) {
eventHandlers[existingIndex] = entry;
} else {
const position = options?.position ?? "Bottom";
if (position === "Top") {
eventHandlers.unshift(entry);
} else if (position === "Bottom") {
eventHandlers.push(entry);
} else {
const index = Math.max(0, Math.min(position, eventHandlers.length));
eventHandlers.splice(index, 0, entry);
}
}
} else {
this._hooks.set(hookEntry.event, [hookEntry.handler]);
this._hooks.set(hook.event, [entry]);
}
return entry;
}
/**
* Alias for onHook. This is provided for compatibility with other libraries that use the `addHook` method.
* @param {string} event
* @param {Hook} handler - this can be async or sync
* @param {string} event - the event name
* @param {HookFn} handler - the handler function
* @returns {void}
*/
addHook(event, handler) {
this.onHookEntry({ event, handler });
this.onHook({ event, handler });
}
/**
* Adds a handler function for a specific event
* @param {Array<HookEntry>} hooks
* Adds handler functions for specific events
* @param {Array<IHook>} hooks
* @param {OnHookOptions} [options] - optional per-call options (e.g., useHookClone override, position)
* @returns {void}
*/
onHooks(hooks) {
onHooks(hooks, options) {
for (const hook of hooks) {
this.onHook(hook.event, hook.handler);
this.onHook(hook, options);
}
}
/**
* Adds a handler function for a specific event that runs before all other handlers
* @param {string} event
* @param {Hook} handler - this can be async or sync
* @returns {void}
* Adds a handler function for a specific event that runs before all other handlers.
* Equivalent to calling `onHook(hook, { position: "Top" })`.
* @param {IHook} hook - the hook containing event name and handler
* @param {PrependHookOptions} [options] - optional per-call options (e.g., useHookClone override)
* @returns {IHook | undefined} the stored hook, or undefined if blocked by deprecation
*/
prependHook(event, handler) {
this.validateHookName(event);
if (!this.checkDeprecatedHook(event)) {
return;
}
const eventHandlers = this._hooks.get(event);
if (eventHandlers) {
eventHandlers.unshift(handler);
} else {
this._hooks.set(event, [handler]);
}
prependHook(hook, options) {
return this.onHook(hook, { ...options, position: "Top" });
}
/**
* Adds a handler that only executes once for a specific event before all other handlers
* @param event
* @param handler
* Adds a handler that only executes once for a specific event before all other handlers.
* Equivalent to calling `onHook` with a self-removing wrapper and `{ position: "Top" }`.
* @param {IHook} hook - the hook containing event name and handler
* @param {PrependHookOptions} [options] - optional per-call options (e.g., useHookClone override)
* @returns {IHook | undefined} the stored hook, or undefined if blocked by deprecation
*/
prependOnceHook(event, handler) {
this.validateHookName(event);
if (!this.checkDeprecatedHook(event)) {
return;
}
const hook = async (...arguments_) => {
this.removeHook(event, hook);
return handler(...arguments_);
prependOnceHook(hook, options) {
const wrappedHandler = async (...arguments_) => {
this.removeHook({ event: hook.event, handler: wrappedHandler });
return hook.handler(...arguments_);
};
this.prependHook(event, hook);
return this.onHook(
{ id: hook.id, event: hook.event, handler: wrappedHandler },
{ ...options, position: "Top" }
);
}
/**
* Adds a handler that only executes once for a specific event
* @param event
* @param handler
* @param {IHook} hook - the hook containing event name and handler
*/
onceHook(event, handler) {
this.validateHookName(event);
if (!this.checkDeprecatedHook(event)) {
onceHook(hook) {
this.validateHookName(hook.event);
if (!this.checkDeprecatedHook(hook.event)) {
return;
}
const hook = async (...arguments_) => {
this.removeHook(event, hook);
return handler(...arguments_);
const wrappedHandler = async (...arguments_) => {
this.removeHook({ event: hook.event, handler: wrappedHandler });
return hook.handler(...arguments_);
};
this.onHook(event, hook);
this.onHook({ id: hook.id, event: hook.event, handler: wrappedHandler });
}
/**
* Removes a handler function for a specific event
* @param {string} event
* @param {Hook} handler
* @returns {void}
* @param {IHook} hook - the hook containing event name and handler to remove
* @returns {IHook | undefined} the removed hook, or undefined if not found
*/
removeHook(event, handler) {
this.validateHookName(event);
if (!this.checkDeprecatedHook(event)) {
return;
}
const eventHandlers = this._hooks.get(event);
removeHook(hook) {
this.validateHookName(hook.event);
const eventHandlers = this._hooks.get(hook.event);
if (eventHandlers) {
const index = eventHandlers.indexOf(handler);
const index = eventHandlers.findIndex((h) => h.handler === hook.handler);
if (index !== -1) {
eventHandlers.splice(index, 1);
if (eventHandlers.length === 0) {
this._hooks.delete(hook.event);
}
return { event: hook.event, handler: hook.handler };
}
}
return void 0;
}
/**
* Removes all handlers for a specific event
* @param {Array<HookEntry>} hooks
* @returns {void}
* Removes multiple hook handlers
* @param {Array<IHook>} hooks
* @returns {IHook[]} the hooks that were successfully removed
*/
removeHooks(hooks) {
const removed = [];
for (const hook of hooks) {
this.removeHook(hook.event, hook.handler);
const result = this.removeHook(hook);
if (result) {
removed.push(result);
}
}
return removed;
}

@@ -612,5 +657,5 @@ /**

if (eventHandlers) {
for (const handler of eventHandlers) {
for (const hook of [...eventHandlers]) {
try {
await handler(...arguments_);
await hook.handler(...arguments_);
} catch (error) {

@@ -643,8 +688,8 @@ const message = `${event}: ${error.message}`;

if (eventHandlers) {
for (const handler of eventHandlers) {
if (handler.constructor.name === "AsyncFunction") {
for (const hook of [...eventHandlers]) {
if (hook.handler.constructor.name === "AsyncFunction") {
continue;
}
try {
handler(...arguments_);
hook.handler(...arguments_);
} catch (error) {

@@ -689,12 +734,51 @@ const message = `${event}: ${error.message}`;

* @param {string} event
* @returns {Hook[]}
* @returns {IHook[]}
*/
getHooks(event) {
this.validateHookName(event);
if (!this.checkDeprecatedHook(event)) {
return void 0;
}
return this._hooks.get(event);
}
/**
* Gets a specific hook by id, searching across all events
* @param {string} id - the hook id
* @returns {IHook | undefined} the hook if found, or undefined
*/
getHook(id) {
for (const eventHandlers of this._hooks.values()) {
const found = eventHandlers.find((h) => h.id === id);
if (found) {
return found;
}
}
return void 0;
}
/**
* Removes one or more hooks by id, searching across all events
* @param {string | string[]} id - the hook id or array of hook ids to remove
* @returns {IHook | IHook[] | undefined} the removed hook(s), or undefined/empty array if not found
*/
removeHookById(id) {
if (Array.isArray(id)) {
const removed = [];
for (const singleId of id) {
const result = this.removeHookById(singleId);
if (result && !Array.isArray(result)) {
removed.push(result);
}
}
return removed;
}
for (const [event, eventHandlers] of this._hooks.entries()) {
const index = eventHandlers.findIndex((h) => h.id === id);
if (index !== -1) {
const [removed] = eventHandlers.splice(index, 1);
if (eventHandlers.length === 0) {
this._hooks.delete(event);
}
return removed;
}
}
return void 0;
}
/**
* Removes all hooks

@@ -706,8 +790,54 @@ * @returns {void}

}
/**
* Removes all hooks for a specific event and returns the removed hooks.
* @param {string} event - The event name to remove hooks for.
* @returns {IHook[]} the hooks that were removed
*/
removeEventHooks(event) {
this.validateHookName(event);
const eventHandlers = this._hooks.get(event);
if (eventHandlers) {
const removed = [...eventHandlers];
this._hooks.delete(event);
return removed;
}
return [];
}
/**
* Validates hook event name if enforceBeforeAfter is enabled
* @param {string} event - The event name to validate
* @throws {Error} If enforceBeforeAfter is true and event doesn't start with 'before' or 'after'
*/
validateHookName(event) {
if (this._enforceBeforeAfter) {
const eventValue = event.trim().toLocaleLowerCase();
if (!eventValue.startsWith("before") && !eventValue.startsWith("after")) {
throw new Error(
`Hook event "${event}" must start with "before" or "after" when enforceBeforeAfter is enabled`
);
}
}
}
/**
* Checks if a hook is deprecated and emits a warning if it is
* @param {string} event - The event name to check
* @returns {boolean} - Returns true if the hook should proceed, false if it should be blocked
*/
checkDeprecatedHook(event) {
if (this._deprecatedHooks.has(event)) {
const message = this._deprecatedHooks.get(event);
const warningMessage = `Hook "${event}" is deprecated${message ? `: ${message}` : ""}`;
this.emit("warn", { hook: event, message: warningMessage });
return this._allowDeprecated;
}
return true;
}
};
export {
Eventified,
Hookified
Hook,
Hookified,
WaterfallHook
};
/* v8 ignore next -- @preserve */
//# sourceMappingURL=index.js.map

@@ -1,1 +0,1 @@

{"version":3,"sources":["../../src/eventified.ts","../../src/index.ts"],"sourcesContent":["// biome-ignore-all lint/suspicious/noExplicitAny: this is for event emitter compatibility\nimport type {\n\tEventEmitterOptions,\n\tEventListener,\n\tIEventEmitter,\n\tLogger,\n} from \"./types.js\";\n\nexport type { EventEmitterOptions, EventListener, IEventEmitter };\n\nexport class Eventified implements IEventEmitter {\n\tprivate readonly _eventListeners: Map<string | symbol, EventListener[]>;\n\tprivate _maxListeners: number;\n\tprivate _logger?: Logger;\n\tprivate _throwOnEmitError = false;\n\tprivate _throwOnEmptyListeners = false;\n\tprivate _errorEvent = \"error\";\n\n\tconstructor(options?: EventEmitterOptions) {\n\t\tthis._eventListeners = new Map<string | symbol, EventListener[]>();\n\t\tthis._maxListeners = 100; // Default maximum number of listeners\n\n\t\tthis._logger = options?.logger;\n\n\t\tif (options?.throwOnEmitError !== undefined) {\n\t\t\tthis._throwOnEmitError = options.throwOnEmitError;\n\t\t}\n\n\t\tif (options?.throwOnEmptyListeners !== undefined) {\n\t\t\tthis._throwOnEmptyListeners = options.throwOnEmptyListeners;\n\t\t}\n\t}\n\n\t/**\n\t * Gets the logger\n\t * @returns {Logger}\n\t */\n\tpublic get logger(): Logger | undefined {\n\t\treturn this._logger;\n\t}\n\n\t/**\n\t * Sets the logger\n\t * @param {Logger} logger\n\t */\n\tpublic set logger(logger: Logger | undefined) {\n\t\tthis._logger = logger;\n\t}\n\n\t/**\n\t * Gets whether an error should be thrown when an emit throws an error. Default is false and only emits an error event.\n\t * @returns {boolean}\n\t */\n\tpublic get throwOnEmitError(): boolean {\n\t\treturn this._throwOnEmitError;\n\t}\n\n\t/**\n\t * Sets whether an error should be thrown when an emit throws an error. Default is false and only emits an error event.\n\t * @param {boolean} value\n\t */\n\tpublic set throwOnEmitError(value: boolean) {\n\t\tthis._throwOnEmitError = value;\n\t}\n\n\t/**\n\t * Gets whether an error should be thrown when emitting 'error' event with no listeners. Default is false.\n\t * @returns {boolean}\n\t */\n\tpublic get throwOnEmptyListeners(): boolean {\n\t\treturn this._throwOnEmptyListeners;\n\t}\n\n\t/**\n\t * Sets whether an error should be thrown when emitting 'error' event with no listeners. Default is false.\n\t * @param {boolean} value\n\t */\n\tpublic set throwOnEmptyListeners(value: boolean) {\n\t\tthis._throwOnEmptyListeners = value;\n\t}\n\n\t/**\n\t * Adds a handler function for a specific event that will run only once\n\t * @param {string | symbol} eventName\n\t * @param {EventListener} listener\n\t * @returns {IEventEmitter} returns the instance of the class for chaining\n\t */\n\tpublic once(\n\t\teventName: string | symbol,\n\t\tlistener: EventListener,\n\t): IEventEmitter {\n\t\tconst onceListener: EventListener = (...arguments_: any[]) => {\n\t\t\tthis.off(eventName as string, onceListener);\n\t\t\tlistener(...arguments_);\n\t\t};\n\n\t\tthis.on(eventName as string, onceListener);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Gets the number of listeners for a specific event. If no event is provided, it returns the total number of listeners\n\t * @param {string} eventName The event name. Not required\n\t * @returns {number} The number of listeners\n\t */\n\tpublic listenerCount(eventName?: string | symbol): number {\n\t\tif (eventName === undefined) {\n\t\t\treturn this.getAllListeners().length;\n\t\t}\n\n\t\tconst listeners = this._eventListeners.get(eventName);\n\t\treturn listeners ? listeners.length : 0;\n\t}\n\n\t/**\n\t * Gets an array of event names\n\t * @returns {Array<string | symbol>} An array of event names\n\t */\n\tpublic eventNames(): Array<string | symbol> {\n\t\treturn [...this._eventListeners.keys()];\n\t}\n\n\t/**\n\t * Gets an array of listeners for a specific event. If no event is provided, it returns all listeners\n\t * @param {string} [event] (Optional) The event name\n\t * @returns {EventListener[]} An array of listeners\n\t */\n\tpublic rawListeners(event?: string | symbol): EventListener[] {\n\t\tif (event === undefined) {\n\t\t\treturn this.getAllListeners();\n\t\t}\n\n\t\treturn this._eventListeners.get(event) ?? [];\n\t}\n\n\t/**\n\t * Prepends a listener to the beginning of the listeners array for the specified event\n\t * @param {string | symbol} eventName\n\t * @param {EventListener} listener\n\t * @returns {IEventEmitter} returns the instance of the class for chaining\n\t */\n\tpublic prependListener(\n\t\teventName: string | symbol,\n\t\tlistener: EventListener,\n\t): IEventEmitter {\n\t\tconst listeners = this._eventListeners.get(eventName) ?? [];\n\t\tlisteners.unshift(listener);\n\t\tthis._eventListeners.set(eventName, listeners);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Prepends a one-time listener to the beginning of the listeners array for the specified event\n\t * @param {string | symbol} eventName\n\t * @param {EventListener} listener\n\t * @returns {IEventEmitter} returns the instance of the class for chaining\n\t */\n\tpublic prependOnceListener(\n\t\teventName: string | symbol,\n\t\tlistener: EventListener,\n\t): IEventEmitter {\n\t\tconst onceListener: EventListener = (...arguments_: any[]) => {\n\t\t\tthis.off(eventName as string, onceListener);\n\t\t\tlistener(...arguments_);\n\t\t};\n\n\t\tthis.prependListener(eventName as string, onceListener);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Gets the maximum number of listeners that can be added for a single event\n\t * @returns {number} The maximum number of listeners\n\t */\n\tpublic maxListeners(): number {\n\t\treturn this._maxListeners;\n\t}\n\n\t/**\n\t * Adds a listener for a specific event. It is an alias for the on() method\n\t * @param {string | symbol} event\n\t * @param {EventListener} listener\n\t * @returns {IEventEmitter} returns the instance of the class for chaining\n\t */\n\tpublic addListener(\n\t\tevent: string | symbol,\n\t\tlistener: EventListener,\n\t): IEventEmitter {\n\t\tthis.on(event, listener);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Adds a listener for a specific event\n\t * @param {string | symbol} event\n\t * @param {EventListener} listener\n\t * @returns {IEventEmitter} returns the instance of the class for chaining\n\t */\n\tpublic on(event: string | symbol, listener: EventListener): IEventEmitter {\n\t\tif (!this._eventListeners.has(event)) {\n\t\t\tthis._eventListeners.set(event, []);\n\t\t}\n\n\t\tconst listeners = this._eventListeners.get(event);\n\n\t\tif (listeners) {\n\t\t\tif (listeners.length >= this._maxListeners) {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`MaxListenersExceededWarning: Possible event memory leak detected. ${listeners.length + 1} ${event as string} listeners added. Use setMaxListeners() to increase limit.`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tlisteners.push(listener);\n\t\t}\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Removes a listener for a specific event. It is an alias for the off() method\n\t * @param {string | symbol} event\n\t * @param {EventListener} listener\n\t * @returns {IEventEmitter} returns the instance of the class for chaining\n\t */\n\tpublic removeListener(event: string, listener: EventListener): IEventEmitter {\n\t\tthis.off(event, listener);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Removes a listener for a specific event\n\t * @param {string | symbol} event\n\t * @param {EventListener} listener\n\t * @returns {IEventEmitter} returns the instance of the class for chaining\n\t */\n\tpublic off(event: string | symbol, listener: EventListener): IEventEmitter {\n\t\tconst listeners = this._eventListeners.get(event) ?? [];\n\t\tconst index = listeners.indexOf(listener);\n\t\tif (index !== -1) {\n\t\t\tlisteners.splice(index, 1);\n\t\t}\n\n\t\tif (listeners.length === 0) {\n\t\t\tthis._eventListeners.delete(event);\n\t\t}\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Calls all listeners for a specific event\n\t * @param {string | symbol} event\n\t * @param arguments_ The arguments to pass to the listeners\n\t * @returns {boolean} Returns true if the event had listeners, false otherwise\n\t */\n\tpublic emit(event: string | symbol, ...arguments_: any[]): boolean {\n\t\tlet result = false;\n\t\tconst listeners = this._eventListeners.get(event);\n\n\t\tif (listeners && listeners.length > 0) {\n\t\t\tfor (const listener of listeners) {\n\t\t\t\tlistener(...arguments_);\n\t\t\t\tresult = true;\n\t\t\t}\n\t\t}\n\n\t\tif (event === this._errorEvent) {\n\t\t\tconst error =\n\t\t\t\targuments_[0] instanceof Error\n\t\t\t\t\t? arguments_[0]\n\t\t\t\t\t: new Error(`${arguments_[0]}`);\n\n\t\t\tif (this._throwOnEmitError && !result) {\n\t\t\t\tthrow error;\n\t\t\t} else {\n\t\t\t\tif (\n\t\t\t\t\tthis.listeners(this._errorEvent).length === 0 &&\n\t\t\t\t\tthis._throwOnEmptyListeners === true\n\t\t\t\t) {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// send it to the logger\n\t\tthis.sendLog(event, arguments_);\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Gets all listeners for a specific event. If no event is provided, it returns all listeners\n\t * @param {string} [event] (Optional) The event name\n\t * @returns {EventListener[]} An array of listeners\n\t */\n\tpublic listeners(event: string | symbol): EventListener[] {\n\t\treturn this._eventListeners.get(event) ?? [];\n\t}\n\n\t/**\n\t * Removes all listeners for a specific event. If no event is provided, it removes all listeners\n\t * @param {string} [event] (Optional) The event name\n\t * @returns {IEventEmitter} returns the instance of the class for chaining\n\t */\n\tpublic removeAllListeners(event?: string | symbol): IEventEmitter {\n\t\tif (event !== undefined) {\n\t\t\tthis._eventListeners.delete(event);\n\t\t} else {\n\t\t\tthis._eventListeners.clear();\n\t\t}\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Sets the maximum number of listeners that can be added for a single event\n\t * @param {number} n The maximum number of listeners\n\t * @returns {void}\n\t */\n\tpublic setMaxListeners(n: number): void {\n\t\tthis._maxListeners = n;\n\t\tfor (const listeners of this._eventListeners.values()) {\n\t\t\tif (listeners.length > n) {\n\t\t\t\tlisteners.splice(n);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Gets all listeners\n\t * @returns {EventListener[]} An array of listeners\n\t */\n\tpublic getAllListeners(): EventListener[] {\n\t\tlet result: EventListener[] = [];\n\t\tfor (const listeners of this._eventListeners.values()) {\n\t\t\tresult = [...result, ...listeners];\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Sends a log message using the configured logger based on the event name\n\t * @param {string | symbol} eventName - The event name that determines the log level\n\t * @param {unknown} data - The data to log\n\t */\n\tprivate sendLog(eventName: string | symbol, data: any): void {\n\t\tif (!this._logger) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet message: string;\n\t\t/* v8 ignore next -- @preserve */\n\t\tif (typeof data === \"string\") {\n\t\t\tmessage = data;\n\t\t} else if (\n\t\t\tArray.isArray(data) &&\n\t\t\tdata.length > 0 &&\n\t\t\tdata[0] instanceof Error\n\t\t) {\n\t\t\tmessage = data[0].message;\n\t\t\t/* v8 ignore next -- @preserve */\n\t\t} else if (data instanceof Error) {\n\t\t\tmessage = data.message;\n\t\t} else if (\n\t\t\tArray.isArray(data) &&\n\t\t\tdata.length > 0 &&\n\t\t\ttypeof data[0]?.message === \"string\"\n\t\t) {\n\t\t\tmessage = data[0].message;\n\t\t} else {\n\t\t\tmessage = JSON.stringify(data);\n\t\t}\n\n\t\tswitch (eventName) {\n\t\t\tcase \"error\": {\n\t\t\t\tthis._logger.error?.(message, { event: eventName, data });\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"warn\": {\n\t\t\t\tthis._logger.warn?.(message, { event: eventName, data });\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"trace\": {\n\t\t\t\tthis._logger.trace?.(message, { event: eventName, data });\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"debug\": {\n\t\t\t\tthis._logger.debug?.(message, { event: eventName, data });\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"fatal\": {\n\t\t\t\tthis._logger.fatal?.(message, { event: eventName, data });\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault: {\n\t\t\t\tthis._logger.info?.(message, { event: eventName, data });\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n}\n","import { Eventified } from \"./eventified.js\";\nimport type { Hook, HookEntry, HookifiedOptions } from \"./types.js\";\n\nexport type { Hook, HookEntry, HookifiedOptions };\n\nexport class Hookified extends Eventified {\n\tprivate readonly _hooks: Map<string, Hook[]>;\n\tprivate _throwOnHookError = false;\n\tprivate _enforceBeforeAfter = false;\n\tprivate _deprecatedHooks: Map<string, string>;\n\tprivate _allowDeprecated = true;\n\n\tconstructor(options?: HookifiedOptions) {\n\t\tsuper({\n\t\t\tlogger: options?.logger,\n\t\t\tthrowOnEmitError: options?.throwOnEmitError,\n\t\t\tthrowOnEmptyListeners: options?.throwOnEmptyListeners,\n\t\t});\n\t\tthis._hooks = new Map();\n\t\tthis._deprecatedHooks = options?.deprecatedHooks\n\t\t\t? new Map(options.deprecatedHooks)\n\t\t\t: new Map();\n\n\t\tif (options?.throwOnHookError !== undefined) {\n\t\t\tthis._throwOnHookError = options.throwOnHookError;\n\t\t} else if (options?.throwHookErrors !== undefined) {\n\t\t\tthis._throwOnHookError = options.throwHookErrors;\n\t\t}\n\n\t\tif (options?.enforceBeforeAfter !== undefined) {\n\t\t\tthis._enforceBeforeAfter = options.enforceBeforeAfter;\n\t\t}\n\n\t\tif (options?.allowDeprecated !== undefined) {\n\t\t\tthis._allowDeprecated = options.allowDeprecated;\n\t\t}\n\t}\n\n\t/**\n\t * Gets all hooks\n\t * @returns {Map<string, Hook[]>}\n\t */\n\tpublic get hooks() {\n\t\treturn this._hooks;\n\t}\n\n\t/**\n\t * Gets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.\n\t * @returns {boolean}\n\t * @deprecated - this will be deprecated in version 2. Please use throwOnHookError.\n\t */\n\tpublic get throwHookErrors() {\n\t\treturn this._throwOnHookError;\n\t}\n\n\t/**\n\t * Sets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.\n\t * @param {boolean} value\n\t * @deprecated - this will be deprecated in version 2. Please use throwOnHookError.\n\t */\n\tpublic set throwHookErrors(value) {\n\t\tthis._throwOnHookError = value;\n\t}\n\n\t/**\n\t * Gets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.\n\t * @returns {boolean}\n\t */\n\tpublic get throwOnHookError() {\n\t\treturn this._throwOnHookError;\n\t}\n\n\t/**\n\t * Sets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.\n\t * @param {boolean} value\n\t */\n\tpublic set throwOnHookError(value) {\n\t\tthis._throwOnHookError = value;\n\t}\n\n\t/**\n\t * Gets whether to enforce that all hook names start with 'before' or 'after'. Default is false.\n\t * @returns {boolean}\n\t * @default false\n\t */\n\tpublic get enforceBeforeAfter() {\n\t\treturn this._enforceBeforeAfter;\n\t}\n\n\t/**\n\t * Sets whether to enforce that all hook names start with 'before' or 'after'. Default is false.\n\t * @param {boolean} value\n\t */\n\tpublic set enforceBeforeAfter(value) {\n\t\tthis._enforceBeforeAfter = value;\n\t}\n\n\t/**\n\t * Gets the map of deprecated hook names to deprecation messages.\n\t * @returns {Map<string, string>}\n\t */\n\tpublic get deprecatedHooks() {\n\t\treturn this._deprecatedHooks;\n\t}\n\n\t/**\n\t * Sets the map of deprecated hook names to deprecation messages.\n\t * @param {Map<string, string>} value\n\t */\n\tpublic set deprecatedHooks(value) {\n\t\tthis._deprecatedHooks = value;\n\t}\n\n\t/**\n\t * Gets whether deprecated hooks are allowed to be registered and executed. Default is true.\n\t * @returns {boolean}\n\t */\n\tpublic get allowDeprecated() {\n\t\treturn this._allowDeprecated;\n\t}\n\n\t/**\n\t * Sets whether deprecated hooks are allowed to be registered and executed. Default is true.\n\t * @param {boolean} value\n\t */\n\tpublic set allowDeprecated(value) {\n\t\tthis._allowDeprecated = value;\n\t}\n\n\t/**\n\t * Validates hook event name if enforceBeforeAfter is enabled\n\t * @param {string} event - The event name to validate\n\t * @throws {Error} If enforceBeforeAfter is true and event doesn't start with 'before' or 'after'\n\t */\n\tprivate validateHookName(event: string): void {\n\t\tif (this._enforceBeforeAfter) {\n\t\t\tconst eventValue = event.trim().toLocaleLowerCase();\n\t\t\tif (!eventValue.startsWith(\"before\") && !eventValue.startsWith(\"after\")) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Hook event \"${event}\" must start with \"before\" or \"after\" when enforceBeforeAfter is enabled`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Checks if a hook is deprecated and emits a warning if it is\n\t * @param {string} event - The event name to check\n\t * @returns {boolean} - Returns true if the hook should proceed, false if it should be blocked\n\t */\n\tprivate checkDeprecatedHook(event: string): boolean {\n\t\tif (this._deprecatedHooks.has(event)) {\n\t\t\tconst message = this._deprecatedHooks.get(event);\n\t\t\tconst warningMessage = `Hook \"${event}\" is deprecated${message ? `: ${message}` : \"\"}`;\n\n\t\t\t// Emit deprecation warning event\n\t\t\tthis.emit(\"warn\", { hook: event, message: warningMessage });\n\n\t\t\t// Return false if deprecated hooks are not allowed\n\t\t\treturn this._allowDeprecated;\n\t\t}\n\t\treturn true;\n\t}\n\n\t/**\n\t * Adds a handler function for a specific event\n\t * @param {string} event\n\t * @param {Hook} handler - this can be async or sync\n\t * @returns {void}\n\t */\n\tpublic onHook(event: string, handler: Hook) {\n\t\tthis.onHookEntry({ event, handler });\n\t}\n\n\t/**\n\t * Adds a handler function for a specific event\n\t * @param {HookEntry} hookEntry\n\t * @returns {void}\n\t */\n\tpublic onHookEntry(hookEntry: HookEntry) {\n\t\tthis.validateHookName(hookEntry.event);\n\t\tif (!this.checkDeprecatedHook(hookEntry.event)) {\n\t\t\treturn; // Skip registration if deprecated hooks are not allowed\n\t\t}\n\t\tconst eventHandlers = this._hooks.get(hookEntry.event);\n\t\tif (eventHandlers) {\n\t\t\teventHandlers.push(hookEntry.handler);\n\t\t} else {\n\t\t\tthis._hooks.set(hookEntry.event, [hookEntry.handler]);\n\t\t}\n\t}\n\n\t/**\n\t * Alias for onHook. This is provided for compatibility with other libraries that use the `addHook` method.\n\t * @param {string} event\n\t * @param {Hook} handler - this can be async or sync\n\t * @returns {void}\n\t */\n\tpublic addHook(event: string, handler: Hook) {\n\t\tthis.onHookEntry({ event, handler });\n\t}\n\n\t/**\n\t * Adds a handler function for a specific event\n\t * @param {Array<HookEntry>} hooks\n\t * @returns {void}\n\t */\n\tpublic onHooks(hooks: HookEntry[]) {\n\t\tfor (const hook of hooks) {\n\t\t\tthis.onHook(hook.event, hook.handler);\n\t\t}\n\t}\n\n\t/**\n\t * Adds a handler function for a specific event that runs before all other handlers\n\t * @param {string} event\n\t * @param {Hook} handler - this can be async or sync\n\t * @returns {void}\n\t */\n\tpublic prependHook(event: string, handler: Hook) {\n\t\tthis.validateHookName(event);\n\t\tif (!this.checkDeprecatedHook(event)) {\n\t\t\treturn; // Skip registration if deprecated hooks are not allowed\n\t\t}\n\t\tconst eventHandlers = this._hooks.get(event);\n\t\tif (eventHandlers) {\n\t\t\teventHandlers.unshift(handler);\n\t\t} else {\n\t\t\tthis._hooks.set(event, [handler]);\n\t\t}\n\t}\n\n\t/**\n\t * Adds a handler that only executes once for a specific event before all other handlers\n\t * @param event\n\t * @param handler\n\t */\n\tpublic prependOnceHook(event: string, handler: Hook) {\n\t\tthis.validateHookName(event);\n\t\tif (!this.checkDeprecatedHook(event)) {\n\t\t\treturn; // Skip registration if deprecated hooks are not allowed\n\t\t}\n\t\t// biome-ignore lint/suspicious/noExplicitAny: this is for any parameter compatibility\n\t\tconst hook = async (...arguments_: any[]) => {\n\t\t\tthis.removeHook(event, hook);\n\t\t\treturn handler(...arguments_);\n\t\t};\n\n\t\tthis.prependHook(event, hook);\n\t}\n\n\t/**\n\t * Adds a handler that only executes once for a specific event\n\t * @param event\n\t * @param handler\n\t */\n\tpublic onceHook(event: string, handler: Hook) {\n\t\tthis.validateHookName(event);\n\t\tif (!this.checkDeprecatedHook(event)) {\n\t\t\treturn; // Skip registration if deprecated hooks are not allowed\n\t\t}\n\t\t// biome-ignore lint/suspicious/noExplicitAny: this is for any parameter compatibility\n\t\tconst hook = async (...arguments_: any[]) => {\n\t\t\tthis.removeHook(event, hook);\n\t\t\treturn handler(...arguments_);\n\t\t};\n\n\t\tthis.onHook(event, hook);\n\t}\n\n\t/**\n\t * Removes a handler function for a specific event\n\t * @param {string} event\n\t * @param {Hook} handler\n\t * @returns {void}\n\t */\n\tpublic removeHook(event: string, handler: Hook) {\n\t\tthis.validateHookName(event);\n\t\tif (!this.checkDeprecatedHook(event)) {\n\t\t\treturn; // Skip removal if deprecated hooks are not allowed\n\t\t}\n\t\tconst eventHandlers = this._hooks.get(event);\n\t\tif (eventHandlers) {\n\t\t\tconst index = eventHandlers.indexOf(handler);\n\t\t\tif (index !== -1) {\n\t\t\t\teventHandlers.splice(index, 1);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Removes all handlers for a specific event\n\t * @param {Array<HookEntry>} hooks\n\t * @returns {void}\n\t */\n\tpublic removeHooks(hooks: HookEntry[]) {\n\t\tfor (const hook of hooks) {\n\t\t\tthis.removeHook(hook.event, hook.handler);\n\t\t}\n\t}\n\n\t/**\n\t * Calls all handlers for a specific event\n\t * @param {string} event\n\t * @param {T[]} arguments_\n\t * @returns {Promise<void>}\n\t */\n\tpublic async hook<T>(event: string, ...arguments_: T[]) {\n\t\tthis.validateHookName(event);\n\t\tif (!this.checkDeprecatedHook(event)) {\n\t\t\treturn; // Skip execution if deprecated hooks are not allowed\n\t\t}\n\t\tconst eventHandlers = this._hooks.get(event);\n\t\tif (eventHandlers) {\n\t\t\tfor (const handler of eventHandlers) {\n\t\t\t\ttry {\n\t\t\t\t\tawait handler(...arguments_);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst message = `${event}: ${(error as Error).message}`;\n\t\t\t\t\tthis.emit(\"error\", new Error(message));\n\n\t\t\t\t\tif (this._throwOnHookError) {\n\t\t\t\t\t\tthrow new Error(message);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Calls all synchronous handlers for a specific event.\n\t * Async handlers (declared with `async` keyword) are silently skipped.\n\t *\n\t * Note: The `hook` method is preferred as it executes both sync and async functions.\n\t * Use `hookSync` only when you specifically need synchronous execution.\n\t * @param {string} event\n\t * @param {T[]} arguments_\n\t * @returns {void}\n\t */\n\tpublic hookSync<T>(event: string, ...arguments_: T[]): void {\n\t\tthis.validateHookName(event);\n\t\tif (!this.checkDeprecatedHook(event)) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst eventHandlers = this._hooks.get(event);\n\t\tif (eventHandlers) {\n\t\t\tfor (const handler of eventHandlers) {\n\t\t\t\t// Skip async functions silently\n\t\t\t\tif (handler.constructor.name === \"AsyncFunction\") {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\thandler(...arguments_);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst message = `${event}: ${(error as Error).message}`;\n\t\t\t\t\tthis.emit(\"error\", new Error(message));\n\n\t\t\t\t\tif (this._throwOnHookError) {\n\t\t\t\t\t\tthrow new Error(message);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Prepends the word `before` to your hook. Example is event is `test`, the before hook is `before:test`.\n\t * @param {string} event - The event name\n\t * @param {T[]} arguments_ - The arguments to pass to the hook\n\t */\n\tpublic async beforeHook<T>(event: string, ...arguments_: T[]) {\n\t\tawait this.hook(`before:${event}`, ...arguments_);\n\t}\n\n\t/**\n\t * Prepends the word `after` to your hook. Example is event is `test`, the after hook is `after:test`.\n\t * @param {string} event - The event name\n\t * @param {T[]} arguments_ - The arguments to pass to the hook\n\t */\n\tpublic async afterHook<T>(event: string, ...arguments_: T[]) {\n\t\tawait this.hook(`after:${event}`, ...arguments_);\n\t}\n\n\t/**\n\t * Calls all handlers for a specific event. This is an alias for `hook` and is provided for\n\t * compatibility with other libraries that use the `callHook` method.\n\t * @param {string} event\n\t * @param {T[]} arguments_\n\t * @returns {Promise<void>}\n\t */\n\tpublic async callHook<T>(event: string, ...arguments_: T[]) {\n\t\tawait this.hook(event, ...arguments_);\n\t}\n\n\t/**\n\t * Gets all hooks for a specific event\n\t * @param {string} event\n\t * @returns {Hook[]}\n\t */\n\tpublic getHooks(event: string) {\n\t\tthis.validateHookName(event);\n\t\tif (!this.checkDeprecatedHook(event)) {\n\t\t\treturn undefined; // Return undefined if deprecated hooks are not allowed\n\t\t}\n\t\treturn this._hooks.get(event);\n\t}\n\n\t/**\n\t * Removes all hooks\n\t * @returns {void}\n\t */\n\tpublic clearHooks() {\n\t\tthis._hooks.clear();\n\t}\n}\n\nexport { Eventified } from \"./eventified.js\";\nexport type {\n\tEventEmitterOptions,\n\tEventListener,\n\tIEventEmitter,\n\tLogger,\n} from \"./types.js\";\n"],"mappings":"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","names":[]}
{"version":3,"sources":["../../src/eventified.ts","../../src/hooks/hook.ts","../../src/hooks/waterfall-hook.ts","../../src/index.ts"],"sourcesContent":["// biome-ignore-all lint/suspicious/noExplicitAny: this is for event emitter compatibility\nimport type {\n\tEventEmitterOptions,\n\tEventListener,\n\tIEventEmitter,\n\tLogger,\n} from \"./types.js\";\n\nexport type { EventEmitterOptions, EventListener, IEventEmitter };\n\nexport class Eventified implements IEventEmitter {\n\tprivate readonly _eventListeners: Map<string | symbol, EventListener[]>;\n\tprivate _maxListeners: number;\n\tprivate _eventLogger?: Logger;\n\tprivate _throwOnEmitError = false;\n\tprivate _throwOnEmptyListeners = true;\n\tprivate _errorEvent = \"error\";\n\n\tconstructor(options?: EventEmitterOptions) {\n\t\tthis._eventListeners = new Map<string | symbol, EventListener[]>();\n\t\tthis._maxListeners = 0; // Default is 0 (unlimited)\n\n\t\tthis._eventLogger = options?.eventLogger;\n\n\t\tif (options?.throwOnEmitError !== undefined) {\n\t\t\tthis._throwOnEmitError = options.throwOnEmitError;\n\t\t}\n\n\t\tif (options?.throwOnEmptyListeners !== undefined) {\n\t\t\tthis._throwOnEmptyListeners = options.throwOnEmptyListeners;\n\t\t}\n\t}\n\n\t/**\n\t * Gets the event logger\n\t * @returns {Logger}\n\t */\n\tpublic get eventLogger(): Logger | undefined {\n\t\treturn this._eventLogger;\n\t}\n\n\t/**\n\t * Sets the event logger\n\t * @param {Logger} eventLogger\n\t */\n\tpublic set eventLogger(eventLogger: Logger | undefined) {\n\t\tthis._eventLogger = eventLogger;\n\t}\n\n\t/**\n\t * Gets whether an error should be thrown when an emit throws an error. Default is false and only emits an error event.\n\t * @returns {boolean}\n\t */\n\tpublic get throwOnEmitError(): boolean {\n\t\treturn this._throwOnEmitError;\n\t}\n\n\t/**\n\t * Sets whether an error should be thrown when an emit throws an error. Default is false and only emits an error event.\n\t * @param {boolean} value\n\t */\n\tpublic set throwOnEmitError(value: boolean) {\n\t\tthis._throwOnEmitError = value;\n\t}\n\n\t/**\n\t * Gets whether an error should be thrown when emitting 'error' event with no listeners. Default is false.\n\t * @returns {boolean}\n\t */\n\tpublic get throwOnEmptyListeners(): boolean {\n\t\treturn this._throwOnEmptyListeners;\n\t}\n\n\t/**\n\t * Sets whether an error should be thrown when emitting 'error' event with no listeners. Default is false.\n\t * @param {boolean} value\n\t */\n\tpublic set throwOnEmptyListeners(value: boolean) {\n\t\tthis._throwOnEmptyListeners = value;\n\t}\n\n\t/**\n\t * Adds a handler function for a specific event that will run only once\n\t * @param {string | symbol} eventName\n\t * @param {EventListener} listener\n\t * @returns {IEventEmitter} returns the instance of the class for chaining\n\t */\n\tpublic once(\n\t\teventName: string | symbol,\n\t\tlistener: EventListener,\n\t): IEventEmitter {\n\t\tconst onceListener: EventListener = (...arguments_: any[]) => {\n\t\t\tthis.off(eventName as string, onceListener);\n\t\t\tlistener(...arguments_);\n\t\t};\n\n\t\tthis.on(eventName as string, onceListener);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Gets the number of listeners for a specific event. If no event is provided, it returns the total number of listeners\n\t * @param {string} eventName The event name. Not required\n\t * @returns {number} The number of listeners\n\t */\n\tpublic listenerCount(eventName?: string | symbol): number {\n\t\tif (eventName === undefined) {\n\t\t\treturn this.getAllListeners().length;\n\t\t}\n\n\t\tconst listeners = this._eventListeners.get(eventName);\n\t\treturn listeners ? listeners.length : 0;\n\t}\n\n\t/**\n\t * Gets an array of event names\n\t * @returns {Array<string | symbol>} An array of event names\n\t */\n\tpublic eventNames(): Array<string | symbol> {\n\t\treturn [...this._eventListeners.keys()];\n\t}\n\n\t/**\n\t * Gets an array of listeners for a specific event. If no event is provided, it returns all listeners\n\t * @param {string} [event] (Optional) The event name\n\t * @returns {EventListener[]} An array of listeners\n\t */\n\tpublic rawListeners(event?: string | symbol): EventListener[] {\n\t\tif (event === undefined) {\n\t\t\treturn this.getAllListeners();\n\t\t}\n\n\t\treturn this._eventListeners.get(event) ?? [];\n\t}\n\n\t/**\n\t * Prepends a listener to the beginning of the listeners array for the specified event\n\t * @param {string | symbol} eventName\n\t * @param {EventListener} listener\n\t * @returns {IEventEmitter} returns the instance of the class for chaining\n\t */\n\tpublic prependListener(\n\t\teventName: string | symbol,\n\t\tlistener: EventListener,\n\t): IEventEmitter {\n\t\tconst listeners = this._eventListeners.get(eventName) ?? [];\n\t\tlisteners.unshift(listener);\n\t\tthis._eventListeners.set(eventName, listeners);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Prepends a one-time listener to the beginning of the listeners array for the specified event\n\t * @param {string | symbol} eventName\n\t * @param {EventListener} listener\n\t * @returns {IEventEmitter} returns the instance of the class for chaining\n\t */\n\tpublic prependOnceListener(\n\t\teventName: string | symbol,\n\t\tlistener: EventListener,\n\t): IEventEmitter {\n\t\tconst onceListener: EventListener = (...arguments_: any[]) => {\n\t\t\tthis.off(eventName as string, onceListener);\n\t\t\tlistener(...arguments_);\n\t\t};\n\n\t\tthis.prependListener(eventName as string, onceListener);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Gets the maximum number of listeners that can be added for a single event\n\t * @returns {number} The maximum number of listeners\n\t */\n\tpublic maxListeners(): number {\n\t\treturn this._maxListeners;\n\t}\n\n\t/**\n\t * Adds a listener for a specific event. It is an alias for the on() method\n\t * @param {string | symbol} event\n\t * @param {EventListener} listener\n\t * @returns {IEventEmitter} returns the instance of the class for chaining\n\t */\n\tpublic addListener(\n\t\tevent: string | symbol,\n\t\tlistener: EventListener,\n\t): IEventEmitter {\n\t\tthis.on(event, listener);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Adds a listener for a specific event\n\t * @param {string | symbol} event\n\t * @param {EventListener} listener\n\t * @returns {IEventEmitter} returns the instance of the class for chaining\n\t */\n\tpublic on(event: string | symbol, listener: EventListener): IEventEmitter {\n\t\tif (!this._eventListeners.has(event)) {\n\t\t\tthis._eventListeners.set(event, []);\n\t\t}\n\n\t\tconst listeners = this._eventListeners.get(event);\n\n\t\tif (listeners) {\n\t\t\tif (this._maxListeners > 0 && listeners.length >= this._maxListeners) {\n\t\t\t\tconsole.warn(\n\t\t\t\t\t`MaxListenersExceededWarning: Possible event memory leak detected. ${listeners.length + 1} ${event as string} listeners added. Use setMaxListeners() to increase limit.`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tlisteners.push(listener);\n\t\t}\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Removes a listener for a specific event. It is an alias for the off() method\n\t * @param {string | symbol} event\n\t * @param {EventListener} listener\n\t * @returns {IEventEmitter} returns the instance of the class for chaining\n\t */\n\tpublic removeListener(event: string, listener: EventListener): IEventEmitter {\n\t\tthis.off(event, listener);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Removes a listener for a specific event\n\t * @param {string | symbol} event\n\t * @param {EventListener} listener\n\t * @returns {IEventEmitter} returns the instance of the class for chaining\n\t */\n\tpublic off(event: string | symbol, listener: EventListener): IEventEmitter {\n\t\tconst listeners = this._eventListeners.get(event) ?? [];\n\t\tconst index = listeners.indexOf(listener);\n\t\tif (index !== -1) {\n\t\t\tlisteners.splice(index, 1);\n\t\t}\n\n\t\tif (listeners.length === 0) {\n\t\t\tthis._eventListeners.delete(event);\n\t\t}\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Calls all listeners for a specific event\n\t * @param {string | symbol} event\n\t * @param arguments_ The arguments to pass to the listeners\n\t * @returns {boolean} Returns true if the event had listeners, false otherwise\n\t */\n\tpublic emit(event: string | symbol, ...arguments_: any[]): boolean {\n\t\tlet result = false;\n\t\tconst listeners = this._eventListeners.get(event);\n\n\t\tif (listeners && listeners.length > 0) {\n\t\t\tfor (const listener of listeners) {\n\t\t\t\tlistener(...arguments_);\n\t\t\t\tresult = true;\n\t\t\t}\n\t\t}\n\n\t\t// send it to the logger\n\t\tthis.sendToEventLogger(event, arguments_);\n\n\t\tif (event === this._errorEvent) {\n\t\t\tconst error =\n\t\t\t\targuments_[0] instanceof Error\n\t\t\t\t\t? arguments_[0]\n\t\t\t\t\t: new Error(`${arguments_[0]}`);\n\n\t\t\tif (this._throwOnEmitError && !result) {\n\t\t\t\tthrow error;\n\t\t\t} else {\n\t\t\t\tif (\n\t\t\t\t\tthis.listeners(this._errorEvent).length === 0 &&\n\t\t\t\t\tthis._throwOnEmptyListeners === true\n\t\t\t\t) {\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Gets all listeners for a specific event. If no event is provided, it returns all listeners\n\t * @param {string} [event] (Optional) The event name\n\t * @returns {EventListener[]} An array of listeners\n\t */\n\tpublic listeners(event: string | symbol): EventListener[] {\n\t\treturn this._eventListeners.get(event) ?? [];\n\t}\n\n\t/**\n\t * Removes all listeners for a specific event. If no event is provided, it removes all listeners\n\t * @param {string} [event] (Optional) The event name\n\t * @returns {IEventEmitter} returns the instance of the class for chaining\n\t */\n\tpublic removeAllListeners(event?: string | symbol): IEventEmitter {\n\t\tif (event !== undefined) {\n\t\t\tthis._eventListeners.delete(event);\n\t\t} else {\n\t\t\tthis._eventListeners.clear();\n\t\t}\n\n\t\treturn this;\n\t}\n\n\t/**\n\t * Sets the maximum number of listeners that can be added for a single event\n\t * @param {number} n The maximum number of listeners\n\t * @returns {void}\n\t */\n\tpublic setMaxListeners(n: number): void {\n\t\tthis._maxListeners = n < 0 ? 0 : n;\n\t}\n\n\t/**\n\t * Gets all listeners\n\t * @returns {EventListener[]} An array of listeners\n\t */\n\tpublic getAllListeners(): EventListener[] {\n\t\tlet result: EventListener[] = [];\n\t\tfor (const listeners of this._eventListeners.values()) {\n\t\t\tresult = [...result, ...listeners];\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Sends a log message using the configured logger based on the event name\n\t * @param {string | symbol} eventName - The event name that determines the log level\n\t * @param {unknown} data - The data to log\n\t */\n\tprivate sendToEventLogger(eventName: string | symbol, data: any): void {\n\t\tif (!this._eventLogger) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet message: string;\n\t\t/* v8 ignore next -- @preserve */\n\t\tif (typeof data === \"string\") {\n\t\t\tmessage = data;\n\t\t} else if (\n\t\t\tArray.isArray(data) &&\n\t\t\tdata.length > 0 &&\n\t\t\tdata[0] instanceof Error\n\t\t) {\n\t\t\tmessage = data[0].message;\n\t\t\t/* v8 ignore next -- @preserve */\n\t\t} else if (data instanceof Error) {\n\t\t\tmessage = data.message;\n\t\t} else if (\n\t\t\tArray.isArray(data) &&\n\t\t\tdata.length > 0 &&\n\t\t\ttypeof data[0]?.message === \"string\"\n\t\t) {\n\t\t\tmessage = data[0].message;\n\t\t} else {\n\t\t\tmessage = JSON.stringify(data);\n\t\t}\n\n\t\tswitch (eventName) {\n\t\t\tcase \"error\": {\n\t\t\t\tthis._eventLogger.error?.(message, { event: eventName, data });\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"warn\": {\n\t\t\t\tthis._eventLogger.warn?.(message, { event: eventName, data });\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"trace\": {\n\t\t\t\tthis._eventLogger.trace?.(message, { event: eventName, data });\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"debug\": {\n\t\t\t\tthis._eventLogger.debug?.(message, { event: eventName, data });\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tcase \"fatal\": {\n\t\t\t\tthis._eventLogger.fatal?.(message, { event: eventName, data });\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tdefault: {\n\t\t\t\tthis._eventLogger.info?.(message, { event: eventName, data });\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n}\n","import type { HookFn, IHook } from \"../types.js\";\n\n/**\n * Concrete implementation of the IHook interface.\n * Provides a convenient class-based way to create hook entries.\n */\nexport class Hook implements IHook {\n\tpublic id?: string;\n\tpublic event: string;\n\tpublic handler: HookFn;\n\n\t/**\n\t * Creates a new Hook instance\n\t * @param {string} event - The event name for the hook\n\t * @param {HookFn} handler - The handler function for the hook\n\t * @param {string} [id] - Optional unique identifier for the hook\n\t */\n\tconstructor(event: string, handler: HookFn, id?: string) {\n\t\tthis.id = id;\n\t\tthis.event = event;\n\t\tthis.handler = handler;\n\t}\n}\n","import type {\n\tHookFn,\n\tIWaterfallHook,\n\tWaterfallHookFn,\n\tWaterfallHookResult,\n} from \"../types.js\";\n\n/**\n * A WaterfallHook chains multiple hook functions sequentially,\n * where each hook receives a context with the previous result,\n * initial arguments, and accumulated results. After all hooks\n * have executed, the final handler receives the transformed result.\n * Implements IHook for compatibility with Hookified.onHook().\n */\nexport class WaterfallHook implements IWaterfallHook {\n\tpublic id?: string;\n\tpublic event: string;\n\tpublic handler: HookFn;\n\tpublic hooks: WaterfallHookFn[];\n\n\tprivate readonly _finalHandler: WaterfallHookFn;\n\n\t/**\n\t * Creates a new WaterfallHook instance\n\t * @param {string} event - The event name for the hook\n\t * @param {WaterfallHookFn} finalHandler - The final handler function that receives the transformed result\n\t * @param {string} [id] - Optional unique identifier for the hook\n\t */\n\tconstructor(event: string, finalHandler: WaterfallHookFn, id?: string) {\n\t\tthis.id = id;\n\t\tthis.event = event;\n\t\tthis.hooks = [];\n\t\tthis._finalHandler = finalHandler;\n\n\t\t// biome-ignore lint/suspicious/noExplicitAny: this is for any parameter compatibility\n\t\tthis.handler = async (...arguments_: any[]) => {\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: waterfall result type varies through the chain\n\t\t\tconst initialArgs: any =\n\t\t\t\targuments_.length === 1 ? arguments_[0] : arguments_;\n\t\t\tconst results: WaterfallHookResult[] = [];\n\n\t\t\tfor (const hook of this.hooks) {\n\t\t\t\tconst result = await hook({ initialArgs, results: [...results] });\n\t\t\t\tresults.push({ hook, result });\n\t\t\t}\n\n\t\t\tawait this._finalHandler({ initialArgs, results: [...results] });\n\t\t};\n\t}\n\n\t/**\n\t * Adds a hook function to the end of the waterfall chain\n\t * @param {WaterfallHookFn} hook - The hook function to add\n\t */\n\tpublic addHook(hook: WaterfallHookFn): void {\n\t\tthis.hooks.push(hook);\n\t}\n\n\t/**\n\t * Removes a specific hook function from the waterfall chain\n\t * @param {WaterfallHookFn} hook - The hook function to remove\n\t * @returns {boolean} true if the hook was found and removed\n\t */\n\tpublic removeHook(hook: WaterfallHookFn): boolean {\n\t\tconst index = this.hooks.indexOf(hook);\n\t\tif (index !== -1) {\n\t\t\tthis.hooks.splice(index, 1);\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t}\n}\n","import { Eventified } from \"./eventified.js\";\nimport type {\n\tHookFn,\n\tHookifiedOptions,\n\tIHook,\n\tIWaterfallHook,\n\tOnHookOptions,\n\tPrependHookOptions,\n\tWaterfallHookContext,\n\tWaterfallHookFn,\n\tWaterfallHookResult,\n} from \"./types.js\";\n\nexport type {\n\tHookFn,\n\tHookifiedOptions,\n\tIHook,\n\tIWaterfallHook,\n\tOnHookOptions,\n\tPrependHookOptions,\n\tWaterfallHookContext,\n\tWaterfallHookFn,\n\tWaterfallHookResult,\n};\n\nexport class Hookified extends Eventified {\n\tprivate readonly _hooks: Map<string, IHook[]>;\n\tprivate _throwOnHookError = false;\n\tprivate _enforceBeforeAfter = false;\n\tprivate _deprecatedHooks: Map<string, string>;\n\tprivate _allowDeprecated = true;\n\tprivate _useHookClone = true;\n\n\tconstructor(options?: HookifiedOptions) {\n\t\tsuper({\n\t\t\teventLogger: options?.eventLogger,\n\t\t\tthrowOnEmitError: options?.throwOnEmitError,\n\t\t\tthrowOnEmptyListeners: options?.throwOnEmptyListeners,\n\t\t});\n\t\tthis._hooks = new Map();\n\t\tthis._deprecatedHooks = options?.deprecatedHooks\n\t\t\t? new Map(options.deprecatedHooks)\n\t\t\t: new Map();\n\n\t\tif (options?.throwOnHookError !== undefined) {\n\t\t\tthis._throwOnHookError = options.throwOnHookError;\n\t\t}\n\n\t\tif (options?.enforceBeforeAfter !== undefined) {\n\t\t\tthis._enforceBeforeAfter = options.enforceBeforeAfter;\n\t\t}\n\n\t\tif (options?.allowDeprecated !== undefined) {\n\t\t\tthis._allowDeprecated = options.allowDeprecated;\n\t\t}\n\n\t\tif (options?.useHookClone !== undefined) {\n\t\t\tthis._useHookClone = options.useHookClone;\n\t\t}\n\t}\n\n\t/**\n\t * Gets all hooks\n\t * @returns {Map<string, IHook[]>}\n\t */\n\tpublic get hooks() {\n\t\treturn this._hooks;\n\t}\n\n\t/**\n\t * Gets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.\n\t * @returns {boolean}\n\t */\n\tpublic get throwOnHookError() {\n\t\treturn this._throwOnHookError;\n\t}\n\n\t/**\n\t * Sets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.\n\t * @param {boolean} value\n\t */\n\tpublic set throwOnHookError(value) {\n\t\tthis._throwOnHookError = value;\n\t}\n\n\t/**\n\t * Gets whether to enforce that all hook names start with 'before' or 'after'. Default is false.\n\t * @returns {boolean}\n\t * @default false\n\t */\n\tpublic get enforceBeforeAfter() {\n\t\treturn this._enforceBeforeAfter;\n\t}\n\n\t/**\n\t * Sets whether to enforce that all hook names start with 'before' or 'after'. Default is false.\n\t * @param {boolean} value\n\t */\n\tpublic set enforceBeforeAfter(value) {\n\t\tthis._enforceBeforeAfter = value;\n\t}\n\n\t/**\n\t * Gets the map of deprecated hook names to deprecation messages.\n\t * @returns {Map<string, string>}\n\t */\n\tpublic get deprecatedHooks() {\n\t\treturn this._deprecatedHooks;\n\t}\n\n\t/**\n\t * Sets the map of deprecated hook names to deprecation messages.\n\t * @param {Map<string, string>} value\n\t */\n\tpublic set deprecatedHooks(value) {\n\t\tthis._deprecatedHooks = value;\n\t}\n\n\t/**\n\t * Gets whether deprecated hooks are allowed to be registered and executed. Default is true.\n\t * @returns {boolean}\n\t */\n\tpublic get allowDeprecated() {\n\t\treturn this._allowDeprecated;\n\t}\n\n\t/**\n\t * Sets whether deprecated hooks are allowed to be registered and executed. Default is true.\n\t * @param {boolean} value\n\t */\n\tpublic set allowDeprecated(value) {\n\t\tthis._allowDeprecated = value;\n\t}\n\n\t/**\n\t * Gets whether hook objects are cloned before storing. Default is true.\n\t * @returns {boolean}\n\t */\n\tpublic get useHookClone() {\n\t\treturn this._useHookClone;\n\t}\n\n\t/**\n\t * Sets whether hook objects are cloned before storing. Default is true.\n\t * When false, the original IHook reference is stored directly.\n\t * @param {boolean} value\n\t */\n\tpublic set useHookClone(value) {\n\t\tthis._useHookClone = value;\n\t}\n\n\t/**\n\t * Adds a handler function for a specific event.\n\t * If you prefer the legacy `(event, handler)` signature, use {@link addHook} instead.\n\t * To register multiple hooks at once, use {@link onHooks}.\n\t * @param {IHook} hook - the hook containing event name and handler\n\t * @param {OnHookOptions} [options] - optional per-call options (e.g., useHookClone override, position)\n\t * @returns {IHook | undefined} the stored hook, or undefined if blocked by deprecation\n\t */\n\tpublic onHook(hook: IHook, options?: OnHookOptions): IHook | undefined {\n\t\tthis.validateHookName(hook.event);\n\t\tif (!this.checkDeprecatedHook(hook.event)) {\n\t\t\treturn undefined; // Skip registration if deprecated hooks are not allowed\n\t\t}\n\n\t\tconst shouldClone = options?.useHookClone ?? this._useHookClone;\n\t\tconst entry: IHook = shouldClone\n\t\t\t? { id: hook.id, event: hook.event, handler: hook.handler }\n\t\t\t: hook;\n\n\t\tentry.id = entry.id ?? crypto.randomUUID();\n\n\t\tconst eventHandlers = this._hooks.get(hook.event);\n\t\tif (eventHandlers) {\n\t\t\t// Check for duplicate id — replace in-place if found\n\t\t\tconst existingIndex = eventHandlers.findIndex((h) => h.id === entry.id);\n\t\t\tif (existingIndex !== -1) {\n\t\t\t\teventHandlers[existingIndex] = entry;\n\t\t\t} else {\n\t\t\t\tconst position = options?.position ?? \"Bottom\";\n\t\t\t\tif (position === \"Top\") {\n\t\t\t\t\teventHandlers.unshift(entry);\n\t\t\t\t} else if (position === \"Bottom\") {\n\t\t\t\t\teventHandlers.push(entry);\n\t\t\t\t} else {\n\t\t\t\t\tconst index = Math.max(0, Math.min(position, eventHandlers.length));\n\t\t\t\t\teventHandlers.splice(index, 0, entry);\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tthis._hooks.set(hook.event, [entry]);\n\t\t}\n\n\t\treturn entry;\n\t}\n\n\t/**\n\t * Alias for onHook. This is provided for compatibility with other libraries that use the `addHook` method.\n\t * @param {string} event - the event name\n\t * @param {HookFn} handler - the handler function\n\t * @returns {void}\n\t */\n\tpublic addHook(event: string, handler: HookFn) {\n\t\tthis.onHook({ event, handler });\n\t}\n\n\t/**\n\t * Adds handler functions for specific events\n\t * @param {Array<IHook>} hooks\n\t * @param {OnHookOptions} [options] - optional per-call options (e.g., useHookClone override, position)\n\t * @returns {void}\n\t */\n\tpublic onHooks(hooks: IHook[], options?: OnHookOptions) {\n\t\tfor (const hook of hooks) {\n\t\t\tthis.onHook(hook, options);\n\t\t}\n\t}\n\n\t/**\n\t * Adds a handler function for a specific event that runs before all other handlers.\n\t * Equivalent to calling `onHook(hook, { position: \"Top\" })`.\n\t * @param {IHook} hook - the hook containing event name and handler\n\t * @param {PrependHookOptions} [options] - optional per-call options (e.g., useHookClone override)\n\t * @returns {IHook | undefined} the stored hook, or undefined if blocked by deprecation\n\t */\n\tpublic prependHook(\n\t\thook: IHook,\n\t\toptions?: PrependHookOptions,\n\t): IHook | undefined {\n\t\treturn this.onHook(hook, { ...options, position: \"Top\" });\n\t}\n\n\t/**\n\t * Adds a handler that only executes once for a specific event before all other handlers.\n\t * Equivalent to calling `onHook` with a self-removing wrapper and `{ position: \"Top\" }`.\n\t * @param {IHook} hook - the hook containing event name and handler\n\t * @param {PrependHookOptions} [options] - optional per-call options (e.g., useHookClone override)\n\t * @returns {IHook | undefined} the stored hook, or undefined if blocked by deprecation\n\t */\n\tpublic prependOnceHook(\n\t\thook: IHook,\n\t\toptions?: PrependHookOptions,\n\t): IHook | undefined {\n\t\t// biome-ignore lint/suspicious/noExplicitAny: this is for any parameter compatibility\n\t\tconst wrappedHandler = async (...arguments_: any[]) => {\n\t\t\tthis.removeHook({ event: hook.event, handler: wrappedHandler });\n\t\t\treturn hook.handler(...arguments_);\n\t\t};\n\n\t\treturn this.onHook(\n\t\t\t{ id: hook.id, event: hook.event, handler: wrappedHandler },\n\t\t\t{ ...options, position: \"Top\" },\n\t\t);\n\t}\n\n\t/**\n\t * Adds a handler that only executes once for a specific event\n\t * @param {IHook} hook - the hook containing event name and handler\n\t */\n\tpublic onceHook(hook: IHook) {\n\t\tthis.validateHookName(hook.event);\n\t\tif (!this.checkDeprecatedHook(hook.event)) {\n\t\t\treturn; // Skip registration if deprecated hooks are not allowed\n\t\t}\n\t\t// biome-ignore lint/suspicious/noExplicitAny: this is for any parameter compatibility\n\t\tconst wrappedHandler = async (...arguments_: any[]) => {\n\t\t\tthis.removeHook({ event: hook.event, handler: wrappedHandler });\n\t\t\treturn hook.handler(...arguments_);\n\t\t};\n\n\t\tthis.onHook({ id: hook.id, event: hook.event, handler: wrappedHandler });\n\t}\n\n\t/**\n\t * Removes a handler function for a specific event\n\t * @param {IHook} hook - the hook containing event name and handler to remove\n\t * @returns {IHook | undefined} the removed hook, or undefined if not found\n\t */\n\tpublic removeHook(hook: IHook): IHook | undefined {\n\t\tthis.validateHookName(hook.event);\n\t\tconst eventHandlers = this._hooks.get(hook.event);\n\t\tif (eventHandlers) {\n\t\t\tconst index = eventHandlers.findIndex((h) => h.handler === hook.handler);\n\t\t\tif (index !== -1) {\n\t\t\t\teventHandlers.splice(index, 1);\n\t\t\t\tif (eventHandlers.length === 0) {\n\t\t\t\t\tthis._hooks.delete(hook.event);\n\t\t\t\t}\n\n\t\t\t\treturn { event: hook.event, handler: hook.handler };\n\t\t\t}\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Removes multiple hook handlers\n\t * @param {Array<IHook>} hooks\n\t * @returns {IHook[]} the hooks that were successfully removed\n\t */\n\tpublic removeHooks(hooks: IHook[]): IHook[] {\n\t\tconst removed: IHook[] = [];\n\t\tfor (const hook of hooks) {\n\t\t\tconst result = this.removeHook(hook);\n\t\t\tif (result) {\n\t\t\t\tremoved.push(result);\n\t\t\t}\n\t\t}\n\n\t\treturn removed;\n\t}\n\n\t/**\n\t * Calls all handlers for a specific event\n\t * @param {string} event\n\t * @param {T[]} arguments_\n\t * @returns {Promise<void>}\n\t */\n\tpublic async hook<T>(event: string, ...arguments_: T[]) {\n\t\tthis.validateHookName(event);\n\t\tif (!this.checkDeprecatedHook(event)) {\n\t\t\treturn; // Skip execution if deprecated hooks are not allowed\n\t\t}\n\t\tconst eventHandlers = this._hooks.get(event);\n\t\tif (eventHandlers) {\n\t\t\tfor (const hook of [...eventHandlers]) {\n\t\t\t\ttry {\n\t\t\t\t\tawait hook.handler(...arguments_);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst message = `${event}: ${(error as Error).message}`;\n\t\t\t\t\tthis.emit(\"error\", new Error(message));\n\n\t\t\t\t\tif (this._throwOnHookError) {\n\t\t\t\t\t\tthrow new Error(message);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Calls all synchronous handlers for a specific event.\n\t * Async handlers (declared with `async` keyword) are silently skipped.\n\t *\n\t * Note: The `hook` method is preferred as it executes both sync and async functions.\n\t * Use `hookSync` only when you specifically need synchronous execution.\n\t * @param {string} event\n\t * @param {T[]} arguments_\n\t * @returns {void}\n\t */\n\tpublic hookSync<T>(event: string, ...arguments_: T[]): void {\n\t\tthis.validateHookName(event);\n\t\tif (!this.checkDeprecatedHook(event)) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst eventHandlers = this._hooks.get(event);\n\t\tif (eventHandlers) {\n\t\t\tfor (const hook of [...eventHandlers]) {\n\t\t\t\t// Skip async functions silently\n\t\t\t\tif (hook.handler.constructor.name === \"AsyncFunction\") {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\thook.handler(...arguments_);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconst message = `${event}: ${(error as Error).message}`;\n\t\t\t\t\tthis.emit(\"error\", new Error(message));\n\n\t\t\t\t\tif (this._throwOnHookError) {\n\t\t\t\t\t\tthrow new Error(message);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Prepends the word `before` to your hook. Example is event is `test`, the before hook is `before:test`.\n\t * @param {string} event - The event name\n\t * @param {T[]} arguments_ - The arguments to pass to the hook\n\t */\n\tpublic async beforeHook<T>(event: string, ...arguments_: T[]) {\n\t\tawait this.hook(`before:${event}`, ...arguments_);\n\t}\n\n\t/**\n\t * Prepends the word `after` to your hook. Example is event is `test`, the after hook is `after:test`.\n\t * @param {string} event - The event name\n\t * @param {T[]} arguments_ - The arguments to pass to the hook\n\t */\n\tpublic async afterHook<T>(event: string, ...arguments_: T[]) {\n\t\tawait this.hook(`after:${event}`, ...arguments_);\n\t}\n\n\t/**\n\t * Calls all handlers for a specific event. This is an alias for `hook` and is provided for\n\t * compatibility with other libraries that use the `callHook` method.\n\t * @param {string} event\n\t * @param {T[]} arguments_\n\t * @returns {Promise<void>}\n\t */\n\tpublic async callHook<T>(event: string, ...arguments_: T[]) {\n\t\tawait this.hook(event, ...arguments_);\n\t}\n\n\t/**\n\t * Gets all hooks for a specific event\n\t * @param {string} event\n\t * @returns {IHook[]}\n\t */\n\tpublic getHooks(event: string) {\n\t\tthis.validateHookName(event);\n\t\treturn this._hooks.get(event);\n\t}\n\n\t/**\n\t * Gets a specific hook by id, searching across all events\n\t * @param {string} id - the hook id\n\t * @returns {IHook | undefined} the hook if found, or undefined\n\t */\n\tpublic getHook(id: string): IHook | undefined {\n\t\tfor (const eventHandlers of this._hooks.values()) {\n\t\t\tconst found = eventHandlers.find((h) => h.id === id);\n\t\t\tif (found) {\n\t\t\t\treturn found;\n\t\t\t}\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Removes one or more hooks by id, searching across all events\n\t * @param {string | string[]} id - the hook id or array of hook ids to remove\n\t * @returns {IHook | IHook[] | undefined} the removed hook(s), or undefined/empty array if not found\n\t */\n\tpublic removeHookById(id: string | string[]): IHook | IHook[] | undefined {\n\t\tif (Array.isArray(id)) {\n\t\t\tconst removed: IHook[] = [];\n\t\t\tfor (const singleId of id) {\n\t\t\t\tconst result = this.removeHookById(singleId);\n\t\t\t\tif (result && !Array.isArray(result)) {\n\t\t\t\t\tremoved.push(result);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn removed;\n\t\t}\n\n\t\tfor (const [event, eventHandlers] of this._hooks.entries()) {\n\t\t\tconst index = eventHandlers.findIndex((h) => h.id === id);\n\t\t\tif (index !== -1) {\n\t\t\t\tconst [removed] = eventHandlers.splice(index, 1);\n\t\t\t\tif (eventHandlers.length === 0) {\n\t\t\t\t\tthis._hooks.delete(event);\n\t\t\t\t}\n\n\t\t\t\treturn removed;\n\t\t\t}\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Removes all hooks\n\t * @returns {void}\n\t */\n\tpublic clearHooks() {\n\t\tthis._hooks.clear();\n\t}\n\n\t/**\n\t * Removes all hooks for a specific event and returns the removed hooks.\n\t * @param {string} event - The event name to remove hooks for.\n\t * @returns {IHook[]} the hooks that were removed\n\t */\n\tpublic removeEventHooks(event: string): IHook[] {\n\t\tthis.validateHookName(event);\n\t\tconst eventHandlers = this._hooks.get(event);\n\t\tif (eventHandlers) {\n\t\t\tconst removed = [...eventHandlers];\n\t\t\tthis._hooks.delete(event);\n\t\t\treturn removed;\n\t\t}\n\n\t\treturn [];\n\t}\n\n\t/**\n\t * Validates hook event name if enforceBeforeAfter is enabled\n\t * @param {string} event - The event name to validate\n\t * @throws {Error} If enforceBeforeAfter is true and event doesn't start with 'before' or 'after'\n\t */\n\tprivate validateHookName(event: string): void {\n\t\tif (this._enforceBeforeAfter) {\n\t\t\tconst eventValue = event.trim().toLocaleLowerCase();\n\t\t\tif (!eventValue.startsWith(\"before\") && !eventValue.startsWith(\"after\")) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Hook event \"${event}\" must start with \"before\" or \"after\" when enforceBeforeAfter is enabled`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Checks if a hook is deprecated and emits a warning if it is\n\t * @param {string} event - The event name to check\n\t * @returns {boolean} - Returns true if the hook should proceed, false if it should be blocked\n\t */\n\tprivate checkDeprecatedHook(event: string): boolean {\n\t\tif (this._deprecatedHooks.has(event)) {\n\t\t\tconst message = this._deprecatedHooks.get(event);\n\t\t\tconst warningMessage = `Hook \"${event}\" is deprecated${message ? `: ${message}` : \"\"}`;\n\n\t\t\t// Emit deprecation warning event\n\t\t\tthis.emit(\"warn\", { hook: event, message: warningMessage });\n\n\t\t\t// Return false if deprecated hooks are not allowed\n\t\t\treturn this._allowDeprecated;\n\t\t}\n\t\treturn true;\n\t}\n}\n\nexport { Eventified } from \"./eventified.js\";\nexport { Hook } from \"./hooks/hook.js\";\nexport { WaterfallHook } from \"./hooks/waterfall-hook.js\";\nexport type {\n\tEventEmitterOptions,\n\tEventListener,\n\tIEventEmitter,\n\tLogger,\n} from \"./types.js\";\n"],"mappings":"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","names":[]}

@@ -24,3 +24,5 @@ "use strict";

Eventified: () => Eventified,
Hookified: () => Hookified
Hook: () => Hook,
Hookified: () => Hookified,
WaterfallHook: () => WaterfallHook
});

@@ -33,10 +35,10 @@ module.exports = __toCommonJS(index_exports);

_maxListeners;
_logger;
_eventLogger;
_throwOnEmitError = false;
_throwOnEmptyListeners = false;
_throwOnEmptyListeners = true;
_errorEvent = "error";
constructor(options) {
this._eventListeners = /* @__PURE__ */ new Map();
this._maxListeners = 100;
this._logger = options?.logger;
this._maxListeners = 0;
this._eventLogger = options?.eventLogger;
if (options?.throwOnEmitError !== void 0) {

@@ -50,14 +52,14 @@ this._throwOnEmitError = options.throwOnEmitError;

/**
* Gets the logger
* Gets the event logger
* @returns {Logger}
*/
get logger() {
return this._logger;
get eventLogger() {
return this._eventLogger;
}
/**
* Sets the logger
* @param {Logger} logger
* Sets the event logger
* @param {Logger} eventLogger
*/
set logger(logger) {
this._logger = logger;
set eventLogger(eventLogger) {
this._eventLogger = eventLogger;
}

@@ -191,3 +193,3 @@ /**

if (listeners) {
if (listeners.length >= this._maxListeners) {
if (this._maxListeners > 0 && listeners.length >= this._maxListeners) {
console.warn(

@@ -243,2 +245,3 @@ `MaxListenersExceededWarning: Possible event memory leak detected. ${listeners.length + 1} ${event} listeners added. Use setMaxListeners() to increase limit.`

}
this.sendToEventLogger(event, arguments_);
if (event === this._errorEvent) {

@@ -254,3 +257,2 @@ const error = arguments_[0] instanceof Error ? arguments_[0] : new Error(`${arguments_[0]}`);

}
this.sendLog(event, arguments_);
return result;

@@ -285,8 +287,3 @@ }

setMaxListeners(n) {
this._maxListeners = n;
for (const listeners of this._eventListeners.values()) {
if (listeners.length > n) {
listeners.splice(n);
}
}
this._maxListeners = n < 0 ? 0 : n;
}

@@ -309,4 +306,4 @@ /**

*/
sendLog(eventName, data) {
if (!this._logger) {
sendToEventLogger(eventName, data) {
if (!this._eventLogger) {
return;

@@ -328,23 +325,23 @@ }

case "error": {
this._logger.error?.(message, { event: eventName, data });
this._eventLogger.error?.(message, { event: eventName, data });
break;
}
case "warn": {
this._logger.warn?.(message, { event: eventName, data });
this._eventLogger.warn?.(message, { event: eventName, data });
break;
}
case "trace": {
this._logger.trace?.(message, { event: eventName, data });
this._eventLogger.trace?.(message, { event: eventName, data });
break;
}
case "debug": {
this._logger.debug?.(message, { event: eventName, data });
this._eventLogger.debug?.(message, { event: eventName, data });
break;
}
case "fatal": {
this._logger.fatal?.(message, { event: eventName, data });
this._eventLogger.fatal?.(message, { event: eventName, data });
break;
}
default: {
this._logger.info?.(message, { event: eventName, data });
this._eventLogger.info?.(message, { event: eventName, data });
break;

@@ -356,2 +353,70 @@ }

// src/hooks/hook.ts
var Hook = class {
id;
event;
handler;
/**
* Creates a new Hook instance
* @param {string} event - The event name for the hook
* @param {HookFn} handler - The handler function for the hook
* @param {string} [id] - Optional unique identifier for the hook
*/
constructor(event, handler, id) {
this.id = id;
this.event = event;
this.handler = handler;
}
};
// src/hooks/waterfall-hook.ts
var WaterfallHook = class {
id;
event;
handler;
hooks;
_finalHandler;
/**
* Creates a new WaterfallHook instance
* @param {string} event - The event name for the hook
* @param {WaterfallHookFn} finalHandler - The final handler function that receives the transformed result
* @param {string} [id] - Optional unique identifier for the hook
*/
constructor(event, finalHandler, id) {
this.id = id;
this.event = event;
this.hooks = [];
this._finalHandler = finalHandler;
this.handler = async (...arguments_) => {
const initialArgs = arguments_.length === 1 ? arguments_[0] : arguments_;
const results = [];
for (const hook of this.hooks) {
const result = await hook({ initialArgs, results: [...results] });
results.push({ hook, result });
}
await this._finalHandler({ initialArgs, results: [...results] });
};
}
/**
* Adds a hook function to the end of the waterfall chain
* @param {WaterfallHookFn} hook - The hook function to add
*/
addHook(hook) {
this.hooks.push(hook);
}
/**
* Removes a specific hook function from the waterfall chain
* @param {WaterfallHookFn} hook - The hook function to remove
* @returns {boolean} true if the hook was found and removed
*/
removeHook(hook) {
const index = this.hooks.indexOf(hook);
if (index !== -1) {
this.hooks.splice(index, 1);
return true;
}
return false;
}
};
// src/index.ts

@@ -364,5 +429,6 @@ var Hookified = class extends Eventified {

_allowDeprecated = true;
_useHookClone = true;
constructor(options) {
super({
logger: options?.logger,
eventLogger: options?.eventLogger,
throwOnEmitError: options?.throwOnEmitError,

@@ -375,4 +441,2 @@ throwOnEmptyListeners: options?.throwOnEmptyListeners

this._throwOnHookError = options.throwOnHookError;
} else if (options?.throwHookErrors !== void 0) {
this._throwOnHookError = options.throwHookErrors;
}

@@ -385,6 +449,9 @@ if (options?.enforceBeforeAfter !== void 0) {

}
if (options?.useHookClone !== void 0) {
this._useHookClone = options.useHookClone;
}
}
/**
* Gets all hooks
* @returns {Map<string, Hook[]>}
* @returns {Map<string, IHook[]>}
*/

@@ -397,19 +464,3 @@ get hooks() {

* @returns {boolean}
* @deprecated - this will be deprecated in version 2. Please use throwOnHookError.
*/
get throwHookErrors() {
return this._throwOnHookError;
}
/**
* Sets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.
* @param {boolean} value
* @deprecated - this will be deprecated in version 2. Please use throwOnHookError.
*/
set throwHookErrors(value) {
this._throwOnHookError = value;
}
/**
* Gets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.
* @returns {boolean}
*/
get throwOnHookError() {

@@ -469,153 +520,149 @@ return this._throwOnHookError;

/**
* Validates hook event name if enforceBeforeAfter is enabled
* @param {string} event - The event name to validate
* @throws {Error} If enforceBeforeAfter is true and event doesn't start with 'before' or 'after'
* Gets whether hook objects are cloned before storing. Default is true.
* @returns {boolean}
*/
validateHookName(event) {
if (this._enforceBeforeAfter) {
const eventValue = event.trim().toLocaleLowerCase();
if (!eventValue.startsWith("before") && !eventValue.startsWith("after")) {
throw new Error(
`Hook event "${event}" must start with "before" or "after" when enforceBeforeAfter is enabled`
);
}
}
get useHookClone() {
return this._useHookClone;
}
/**
* Checks if a hook is deprecated and emits a warning if it is
* @param {string} event - The event name to check
* @returns {boolean} - Returns true if the hook should proceed, false if it should be blocked
* Sets whether hook objects are cloned before storing. Default is true.
* When false, the original IHook reference is stored directly.
* @param {boolean} value
*/
checkDeprecatedHook(event) {
if (this._deprecatedHooks.has(event)) {
const message = this._deprecatedHooks.get(event);
const warningMessage = `Hook "${event}" is deprecated${message ? `: ${message}` : ""}`;
this.emit("warn", { hook: event, message: warningMessage });
return this._allowDeprecated;
}
return true;
set useHookClone(value) {
this._useHookClone = value;
}
/**
* Adds a handler function for a specific event
* @param {string} event
* @param {Hook} handler - this can be async or sync
* @returns {void}
* Adds a handler function for a specific event.
* If you prefer the legacy `(event, handler)` signature, use {@link addHook} instead.
* To register multiple hooks at once, use {@link onHooks}.
* @param {IHook} hook - the hook containing event name and handler
* @param {OnHookOptions} [options] - optional per-call options (e.g., useHookClone override, position)
* @returns {IHook | undefined} the stored hook, or undefined if blocked by deprecation
*/
onHook(event, handler) {
this.onHookEntry({ event, handler });
}
/**
* Adds a handler function for a specific event
* @param {HookEntry} hookEntry
* @returns {void}
*/
onHookEntry(hookEntry) {
this.validateHookName(hookEntry.event);
if (!this.checkDeprecatedHook(hookEntry.event)) {
return;
onHook(hook, options) {
this.validateHookName(hook.event);
if (!this.checkDeprecatedHook(hook.event)) {
return void 0;
}
const eventHandlers = this._hooks.get(hookEntry.event);
const shouldClone = options?.useHookClone ?? this._useHookClone;
const entry = shouldClone ? { id: hook.id, event: hook.event, handler: hook.handler } : hook;
entry.id = entry.id ?? crypto.randomUUID();
const eventHandlers = this._hooks.get(hook.event);
if (eventHandlers) {
eventHandlers.push(hookEntry.handler);
const existingIndex = eventHandlers.findIndex((h) => h.id === entry.id);
if (existingIndex !== -1) {
eventHandlers[existingIndex] = entry;
} else {
const position = options?.position ?? "Bottom";
if (position === "Top") {
eventHandlers.unshift(entry);
} else if (position === "Bottom") {
eventHandlers.push(entry);
} else {
const index = Math.max(0, Math.min(position, eventHandlers.length));
eventHandlers.splice(index, 0, entry);
}
}
} else {
this._hooks.set(hookEntry.event, [hookEntry.handler]);
this._hooks.set(hook.event, [entry]);
}
return entry;
}
/**
* Alias for onHook. This is provided for compatibility with other libraries that use the `addHook` method.
* @param {string} event
* @param {Hook} handler - this can be async or sync
* @param {string} event - the event name
* @param {HookFn} handler - the handler function
* @returns {void}
*/
addHook(event, handler) {
this.onHookEntry({ event, handler });
this.onHook({ event, handler });
}
/**
* Adds a handler function for a specific event
* @param {Array<HookEntry>} hooks
* Adds handler functions for specific events
* @param {Array<IHook>} hooks
* @param {OnHookOptions} [options] - optional per-call options (e.g., useHookClone override, position)
* @returns {void}
*/
onHooks(hooks) {
onHooks(hooks, options) {
for (const hook of hooks) {
this.onHook(hook.event, hook.handler);
this.onHook(hook, options);
}
}
/**
* Adds a handler function for a specific event that runs before all other handlers
* @param {string} event
* @param {Hook} handler - this can be async or sync
* @returns {void}
* Adds a handler function for a specific event that runs before all other handlers.
* Equivalent to calling `onHook(hook, { position: "Top" })`.
* @param {IHook} hook - the hook containing event name and handler
* @param {PrependHookOptions} [options] - optional per-call options (e.g., useHookClone override)
* @returns {IHook | undefined} the stored hook, or undefined if blocked by deprecation
*/
prependHook(event, handler) {
this.validateHookName(event);
if (!this.checkDeprecatedHook(event)) {
return;
}
const eventHandlers = this._hooks.get(event);
if (eventHandlers) {
eventHandlers.unshift(handler);
} else {
this._hooks.set(event, [handler]);
}
prependHook(hook, options) {
return this.onHook(hook, { ...options, position: "Top" });
}
/**
* Adds a handler that only executes once for a specific event before all other handlers
* @param event
* @param handler
* Adds a handler that only executes once for a specific event before all other handlers.
* Equivalent to calling `onHook` with a self-removing wrapper and `{ position: "Top" }`.
* @param {IHook} hook - the hook containing event name and handler
* @param {PrependHookOptions} [options] - optional per-call options (e.g., useHookClone override)
* @returns {IHook | undefined} the stored hook, or undefined if blocked by deprecation
*/
prependOnceHook(event, handler) {
this.validateHookName(event);
if (!this.checkDeprecatedHook(event)) {
return;
}
const hook = async (...arguments_) => {
this.removeHook(event, hook);
return handler(...arguments_);
prependOnceHook(hook, options) {
const wrappedHandler = async (...arguments_) => {
this.removeHook({ event: hook.event, handler: wrappedHandler });
return hook.handler(...arguments_);
};
this.prependHook(event, hook);
return this.onHook(
{ id: hook.id, event: hook.event, handler: wrappedHandler },
{ ...options, position: "Top" }
);
}
/**
* Adds a handler that only executes once for a specific event
* @param event
* @param handler
* @param {IHook} hook - the hook containing event name and handler
*/
onceHook(event, handler) {
this.validateHookName(event);
if (!this.checkDeprecatedHook(event)) {
onceHook(hook) {
this.validateHookName(hook.event);
if (!this.checkDeprecatedHook(hook.event)) {
return;
}
const hook = async (...arguments_) => {
this.removeHook(event, hook);
return handler(...arguments_);
const wrappedHandler = async (...arguments_) => {
this.removeHook({ event: hook.event, handler: wrappedHandler });
return hook.handler(...arguments_);
};
this.onHook(event, hook);
this.onHook({ id: hook.id, event: hook.event, handler: wrappedHandler });
}
/**
* Removes a handler function for a specific event
* @param {string} event
* @param {Hook} handler
* @returns {void}
* @param {IHook} hook - the hook containing event name and handler to remove
* @returns {IHook | undefined} the removed hook, or undefined if not found
*/
removeHook(event, handler) {
this.validateHookName(event);
if (!this.checkDeprecatedHook(event)) {
return;
}
const eventHandlers = this._hooks.get(event);
removeHook(hook) {
this.validateHookName(hook.event);
const eventHandlers = this._hooks.get(hook.event);
if (eventHandlers) {
const index = eventHandlers.indexOf(handler);
const index = eventHandlers.findIndex((h) => h.handler === hook.handler);
if (index !== -1) {
eventHandlers.splice(index, 1);
if (eventHandlers.length === 0) {
this._hooks.delete(hook.event);
}
return { event: hook.event, handler: hook.handler };
}
}
return void 0;
}
/**
* Removes all handlers for a specific event
* @param {Array<HookEntry>} hooks
* @returns {void}
* Removes multiple hook handlers
* @param {Array<IHook>} hooks
* @returns {IHook[]} the hooks that were successfully removed
*/
removeHooks(hooks) {
const removed = [];
for (const hook of hooks) {
this.removeHook(hook.event, hook.handler);
const result = this.removeHook(hook);
if (result) {
removed.push(result);
}
}
return removed;
}

@@ -635,5 +682,5 @@ /**

if (eventHandlers) {
for (const handler of eventHandlers) {
for (const hook of [...eventHandlers]) {
try {
await handler(...arguments_);
await hook.handler(...arguments_);
} catch (error) {

@@ -666,8 +713,8 @@ const message = `${event}: ${error.message}`;

if (eventHandlers) {
for (const handler of eventHandlers) {
if (handler.constructor.name === "AsyncFunction") {
for (const hook of [...eventHandlers]) {
if (hook.handler.constructor.name === "AsyncFunction") {
continue;
}
try {
handler(...arguments_);
hook.handler(...arguments_);
} catch (error) {

@@ -712,12 +759,51 @@ const message = `${event}: ${error.message}`;

* @param {string} event
* @returns {Hook[]}
* @returns {IHook[]}
*/
getHooks(event) {
this.validateHookName(event);
if (!this.checkDeprecatedHook(event)) {
return void 0;
}
return this._hooks.get(event);
}
/**
* Gets a specific hook by id, searching across all events
* @param {string} id - the hook id
* @returns {IHook | undefined} the hook if found, or undefined
*/
getHook(id) {
for (const eventHandlers of this._hooks.values()) {
const found = eventHandlers.find((h) => h.id === id);
if (found) {
return found;
}
}
return void 0;
}
/**
* Removes one or more hooks by id, searching across all events
* @param {string | string[]} id - the hook id or array of hook ids to remove
* @returns {IHook | IHook[] | undefined} the removed hook(s), or undefined/empty array if not found
*/
removeHookById(id) {
if (Array.isArray(id)) {
const removed = [];
for (const singleId of id) {
const result = this.removeHookById(singleId);
if (result && !Array.isArray(result)) {
removed.push(result);
}
}
return removed;
}
for (const [event, eventHandlers] of this._hooks.entries()) {
const index = eventHandlers.findIndex((h) => h.id === id);
if (index !== -1) {
const [removed] = eventHandlers.splice(index, 1);
if (eventHandlers.length === 0) {
this._hooks.delete(event);
}
return removed;
}
}
return void 0;
}
/**
* Removes all hooks

@@ -729,2 +815,46 @@ * @returns {void}

}
/**
* Removes all hooks for a specific event and returns the removed hooks.
* @param {string} event - The event name to remove hooks for.
* @returns {IHook[]} the hooks that were removed
*/
removeEventHooks(event) {
this.validateHookName(event);
const eventHandlers = this._hooks.get(event);
if (eventHandlers) {
const removed = [...eventHandlers];
this._hooks.delete(event);
return removed;
}
return [];
}
/**
* Validates hook event name if enforceBeforeAfter is enabled
* @param {string} event - The event name to validate
* @throws {Error} If enforceBeforeAfter is true and event doesn't start with 'before' or 'after'
*/
validateHookName(event) {
if (this._enforceBeforeAfter) {
const eventValue = event.trim().toLocaleLowerCase();
if (!eventValue.startsWith("before") && !eventValue.startsWith("after")) {
throw new Error(
`Hook event "${event}" must start with "before" or "after" when enforceBeforeAfter is enabled`
);
}
}
}
/**
* Checks if a hook is deprecated and emits a warning if it is
* @param {string} event - The event name to check
* @returns {boolean} - Returns true if the hook should proceed, false if it should be blocked
*/
checkDeprecatedHook(event) {
if (this._deprecatedHooks.has(event)) {
const message = this._deprecatedHooks.get(event);
const warningMessage = `Hook "${event}" is deprecated${message ? `: ${message}` : ""}`;
this.emit("warn", { hook: event, message: warningMessage });
return this._allowDeprecated;
}
return true;
}
};

@@ -734,4 +864,6 @@ // Annotate the CommonJS export names for ESM import in node:

Eventified,
Hookified
Hook,
Hookified,
WaterfallHook
});
/* v8 ignore next -- @preserve */

@@ -11,5 +11,5 @@ type Logger = {

/**
* Logger instance for logging errors.
* Logger instance for logging events.
*/
logger?: Logger;
eventLogger?: Logger;
/**

@@ -21,3 +21,3 @@ * Whether to throw an error when emit 'error' and there are no listeners. Default is false and only emits an error event.

* Whether to throw on 'error' when there are no listeners. This is the standard functionality in EventEmitter
* @default false - in v2 this will be set to true by default
* @default true
*/

@@ -171,5 +171,9 @@ throwOnEmptyListeners?: boolean;

type EventListener = (...arguments_: any[]) => void;
type Hook = (...arguments_: any[]) => Promise<void> | void;
type HookEntry = {
type HookFn = (...arguments_: any[]) => Promise<void> | void;
interface IHook {
/**
* Unique identifier for the hook. Auto-generated via crypto.randomUUID() if not provided.
*/
id?: string;
/**
* The event name for the hook

@@ -181,11 +185,61 @@ */

*/
handler: Hook;
handler: HookFn;
}
type WaterfallHookResult = {
/**
* The hook function that produced this result
*/
hook: WaterfallHookFn;
/**
* The value returned by the hook
*/
result: any;
};
type HookifiedOptions = {
type WaterfallHookContext = {
/**
* Whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.
* @deprecated - this will be deprecated in version 2. Please use throwOnHookError.
* The original arguments passed to the waterfall execution, before any hooks processed them.
*/
throwHookErrors?: boolean;
initialArgs: any;
/**
* Array of results from previous hooks in execution order, each containing the hook and its result.
* Empty for the first hook.
*/
results: WaterfallHookResult[];
};
type WaterfallHookFn = (context: WaterfallHookContext) => Promise<any> | any;
interface IWaterfallHook extends IHook {
/**
* Array of hook functions that are called sequentially.
* Each hook receives a WaterfallHookContext with initialArgs and results.
*/
hooks: WaterfallHookFn[];
/**
* Adds a hook function to the end of the waterfall chain
*/
addHook(hook: WaterfallHookFn): void;
/**
* Removes a specific hook function from the waterfall chain
*/
removeHook(hook: WaterfallHookFn): boolean;
}
type OnHookOptions = {
/**
* Per-call override for useHookClone.
* When true, hook objects are cloned before storing.
* When false, the original IHook reference is stored directly.
* When undefined, falls back to the instance-level useHookClone setting.
* @type {boolean}
*/
useHookClone?: boolean;
/**
* Controls where the hook is inserted in the handlers array.
* - "Top": Insert at the beginning (index 0), before all existing handlers.
* - "Bottom": Append to the end, after all existing handlers. This is the default.
* - number: Insert at a specific index. Values are clamped to the array bounds.
*/
position?: "Top" | "Bottom" | number;
};
type PrependHookOptions = Omit<OnHookOptions, "position">;
type HookifiedOptions = {
/**
* Whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.

@@ -212,2 +266,9 @@ */

allowDeprecated?: boolean;
/**
* Whether to clone hook objects before storing. Default is true.
* When false, the original IHook reference is stored directly.
* @type {boolean}
* @default true
*/
useHookClone?: boolean;
} & EventEmitterOptions;

@@ -218,3 +279,3 @@

private _maxListeners;
private _logger?;
private _eventLogger?;
private _throwOnEmitError;

@@ -225,11 +286,11 @@ private _throwOnEmptyListeners;

/**
* Gets the logger
* Gets the event logger
* @returns {Logger}
*/
get logger(): Logger | undefined;
get eventLogger(): Logger | undefined;
/**
* Sets the logger
* @param {Logger} logger
* Sets the event logger
* @param {Logger} eventLogger
*/
set logger(logger: Logger | undefined);
set eventLogger(eventLogger: Logger | undefined);
/**

@@ -361,5 +422,55 @@ * Gets whether an error should be thrown when an emit throws an error. Default is false and only emits an error event.

*/
private sendLog;
private sendToEventLogger;
}
/**
* Concrete implementation of the IHook interface.
* Provides a convenient class-based way to create hook entries.
*/
declare class Hook implements IHook {
id?: string;
event: string;
handler: HookFn;
/**
* Creates a new Hook instance
* @param {string} event - The event name for the hook
* @param {HookFn} handler - The handler function for the hook
* @param {string} [id] - Optional unique identifier for the hook
*/
constructor(event: string, handler: HookFn, id?: string);
}
/**
* A WaterfallHook chains multiple hook functions sequentially,
* where each hook receives a context with the previous result,
* initial arguments, and accumulated results. After all hooks
* have executed, the final handler receives the transformed result.
* Implements IHook for compatibility with Hookified.onHook().
*/
declare class WaterfallHook implements IWaterfallHook {
id?: string;
event: string;
handler: HookFn;
hooks: WaterfallHookFn[];
private readonly _finalHandler;
/**
* Creates a new WaterfallHook instance
* @param {string} event - The event name for the hook
* @param {WaterfallHookFn} finalHandler - The final handler function that receives the transformed result
* @param {string} [id] - Optional unique identifier for the hook
*/
constructor(event: string, finalHandler: WaterfallHookFn, id?: string);
/**
* Adds a hook function to the end of the waterfall chain
* @param {WaterfallHookFn} hook - The hook function to add
*/
addHook(hook: WaterfallHookFn): void;
/**
* Removes a specific hook function from the waterfall chain
* @param {WaterfallHookFn} hook - The hook function to remove
* @returns {boolean} true if the hook was found and removed
*/
removeHook(hook: WaterfallHookFn): boolean;
}
declare class Hookified extends Eventified {

@@ -371,24 +482,13 @@ private readonly _hooks;

private _allowDeprecated;
private _useHookClone;
constructor(options?: HookifiedOptions);
/**
* Gets all hooks
* @returns {Map<string, Hook[]>}
* @returns {Map<string, IHook[]>}
*/
get hooks(): Map<string, Hook[]>;
get hooks(): Map<string, IHook[]>;
/**
* Gets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.
* @returns {boolean}
* @deprecated - this will be deprecated in version 2. Please use throwOnHookError.
*/
get throwHookErrors(): boolean;
/**
* Sets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.
* @param {boolean} value
* @deprecated - this will be deprecated in version 2. Please use throwOnHookError.
*/
set throwHookErrors(value: boolean);
/**
* Gets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.
* @returns {boolean}
*/
get throwOnHookError(): boolean;

@@ -432,71 +532,68 @@ /**

/**
* Validates hook event name if enforceBeforeAfter is enabled
* @param {string} event - The event name to validate
* @throws {Error} If enforceBeforeAfter is true and event doesn't start with 'before' or 'after'
* Gets whether hook objects are cloned before storing. Default is true.
* @returns {boolean}
*/
private validateHookName;
get useHookClone(): boolean;
/**
* Checks if a hook is deprecated and emits a warning if it is
* @param {string} event - The event name to check
* @returns {boolean} - Returns true if the hook should proceed, false if it should be blocked
* Sets whether hook objects are cloned before storing. Default is true.
* When false, the original IHook reference is stored directly.
* @param {boolean} value
*/
private checkDeprecatedHook;
set useHookClone(value: boolean);
/**
* Adds a handler function for a specific event
* @param {string} event
* @param {Hook} handler - this can be async or sync
* @returns {void}
* Adds a handler function for a specific event.
* If you prefer the legacy `(event, handler)` signature, use {@link addHook} instead.
* To register multiple hooks at once, use {@link onHooks}.
* @param {IHook} hook - the hook containing event name and handler
* @param {OnHookOptions} [options] - optional per-call options (e.g., useHookClone override, position)
* @returns {IHook | undefined} the stored hook, or undefined if blocked by deprecation
*/
onHook(event: string, handler: Hook): void;
onHook(hook: IHook, options?: OnHookOptions): IHook | undefined;
/**
* Adds a handler function for a specific event
* @param {HookEntry} hookEntry
* @returns {void}
*/
onHookEntry(hookEntry: HookEntry): void;
/**
* Alias for onHook. This is provided for compatibility with other libraries that use the `addHook` method.
* @param {string} event
* @param {Hook} handler - this can be async or sync
* @param {string} event - the event name
* @param {HookFn} handler - the handler function
* @returns {void}
*/
addHook(event: string, handler: Hook): void;
addHook(event: string, handler: HookFn): void;
/**
* Adds a handler function for a specific event
* @param {Array<HookEntry>} hooks
* Adds handler functions for specific events
* @param {Array<IHook>} hooks
* @param {OnHookOptions} [options] - optional per-call options (e.g., useHookClone override, position)
* @returns {void}
*/
onHooks(hooks: HookEntry[]): void;
onHooks(hooks: IHook[], options?: OnHookOptions): void;
/**
* Adds a handler function for a specific event that runs before all other handlers
* @param {string} event
* @param {Hook} handler - this can be async or sync
* @returns {void}
* Adds a handler function for a specific event that runs before all other handlers.
* Equivalent to calling `onHook(hook, { position: "Top" })`.
* @param {IHook} hook - the hook containing event name and handler
* @param {PrependHookOptions} [options] - optional per-call options (e.g., useHookClone override)
* @returns {IHook | undefined} the stored hook, or undefined if blocked by deprecation
*/
prependHook(event: string, handler: Hook): void;
prependHook(hook: IHook, options?: PrependHookOptions): IHook | undefined;
/**
* Adds a handler that only executes once for a specific event before all other handlers
* @param event
* @param handler
* Adds a handler that only executes once for a specific event before all other handlers.
* Equivalent to calling `onHook` with a self-removing wrapper and `{ position: "Top" }`.
* @param {IHook} hook - the hook containing event name and handler
* @param {PrependHookOptions} [options] - optional per-call options (e.g., useHookClone override)
* @returns {IHook | undefined} the stored hook, or undefined if blocked by deprecation
*/
prependOnceHook(event: string, handler: Hook): void;
prependOnceHook(hook: IHook, options?: PrependHookOptions): IHook | undefined;
/**
* Adds a handler that only executes once for a specific event
* @param event
* @param handler
* @param {IHook} hook - the hook containing event name and handler
*/
onceHook(event: string, handler: Hook): void;
onceHook(hook: IHook): void;
/**
* Removes a handler function for a specific event
* @param {string} event
* @param {Hook} handler
* @returns {void}
* @param {IHook} hook - the hook containing event name and handler to remove
* @returns {IHook | undefined} the removed hook, or undefined if not found
*/
removeHook(event: string, handler: Hook): void;
removeHook(hook: IHook): IHook | undefined;
/**
* Removes all handlers for a specific event
* @param {Array<HookEntry>} hooks
* @returns {void}
* Removes multiple hook handlers
* @param {Array<IHook>} hooks
* @returns {IHook[]} the hooks that were successfully removed
*/
removeHooks(hooks: HookEntry[]): void;
removeHooks(hooks: IHook[]): IHook[];
/**

@@ -543,6 +640,18 @@ * Calls all handlers for a specific event

* @param {string} event
* @returns {Hook[]}
* @returns {IHook[]}
*/
getHooks(event: string): Hook[] | undefined;
getHooks(event: string): IHook[] | undefined;
/**
* Gets a specific hook by id, searching across all events
* @param {string} id - the hook id
* @returns {IHook | undefined} the hook if found, or undefined
*/
getHook(id: string): IHook | undefined;
/**
* Removes one or more hooks by id, searching across all events
* @param {string | string[]} id - the hook id or array of hook ids to remove
* @returns {IHook | IHook[] | undefined} the removed hook(s), or undefined/empty array if not found
*/
removeHookById(id: string | string[]): IHook | IHook[] | undefined;
/**
* Removes all hooks

@@ -552,4 +661,22 @@ * @returns {void}

clearHooks(): void;
/**
* Removes all hooks for a specific event and returns the removed hooks.
* @param {string} event - The event name to remove hooks for.
* @returns {IHook[]} the hooks that were removed
*/
removeEventHooks(event: string): IHook[];
/**
* Validates hook event name if enforceBeforeAfter is enabled
* @param {string} event - The event name to validate
* @throws {Error} If enforceBeforeAfter is true and event doesn't start with 'before' or 'after'
*/
private validateHookName;
/**
* Checks if a hook is deprecated and emits a warning if it is
* @param {string} event - The event name to check
* @returns {boolean} - Returns true if the hook should proceed, false if it should be blocked
*/
private checkDeprecatedHook;
}
export { type EventEmitterOptions, type EventListener, Eventified, type Hook, type HookEntry, Hookified, type HookifiedOptions, type IEventEmitter, type Logger };
export { type EventEmitterOptions, type EventListener, Eventified, Hook, type HookFn, Hookified, type HookifiedOptions, type IEventEmitter, type IHook, type IWaterfallHook, type Logger, type OnHookOptions, type PrependHookOptions, WaterfallHook, type WaterfallHookContext, type WaterfallHookFn, type WaterfallHookResult };

@@ -11,5 +11,5 @@ type Logger = {

/**
* Logger instance for logging errors.
* Logger instance for logging events.
*/
logger?: Logger;
eventLogger?: Logger;
/**

@@ -21,3 +21,3 @@ * Whether to throw an error when emit 'error' and there are no listeners. Default is false and only emits an error event.

* Whether to throw on 'error' when there are no listeners. This is the standard functionality in EventEmitter
* @default false - in v2 this will be set to true by default
* @default true
*/

@@ -171,5 +171,9 @@ throwOnEmptyListeners?: boolean;

type EventListener = (...arguments_: any[]) => void;
type Hook = (...arguments_: any[]) => Promise<void> | void;
type HookEntry = {
type HookFn = (...arguments_: any[]) => Promise<void> | void;
interface IHook {
/**
* Unique identifier for the hook. Auto-generated via crypto.randomUUID() if not provided.
*/
id?: string;
/**
* The event name for the hook

@@ -181,11 +185,61 @@ */

*/
handler: Hook;
handler: HookFn;
}
type WaterfallHookResult = {
/**
* The hook function that produced this result
*/
hook: WaterfallHookFn;
/**
* The value returned by the hook
*/
result: any;
};
type HookifiedOptions = {
type WaterfallHookContext = {
/**
* Whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.
* @deprecated - this will be deprecated in version 2. Please use throwOnHookError.
* The original arguments passed to the waterfall execution, before any hooks processed them.
*/
throwHookErrors?: boolean;
initialArgs: any;
/**
* Array of results from previous hooks in execution order, each containing the hook and its result.
* Empty for the first hook.
*/
results: WaterfallHookResult[];
};
type WaterfallHookFn = (context: WaterfallHookContext) => Promise<any> | any;
interface IWaterfallHook extends IHook {
/**
* Array of hook functions that are called sequentially.
* Each hook receives a WaterfallHookContext with initialArgs and results.
*/
hooks: WaterfallHookFn[];
/**
* Adds a hook function to the end of the waterfall chain
*/
addHook(hook: WaterfallHookFn): void;
/**
* Removes a specific hook function from the waterfall chain
*/
removeHook(hook: WaterfallHookFn): boolean;
}
type OnHookOptions = {
/**
* Per-call override for useHookClone.
* When true, hook objects are cloned before storing.
* When false, the original IHook reference is stored directly.
* When undefined, falls back to the instance-level useHookClone setting.
* @type {boolean}
*/
useHookClone?: boolean;
/**
* Controls where the hook is inserted in the handlers array.
* - "Top": Insert at the beginning (index 0), before all existing handlers.
* - "Bottom": Append to the end, after all existing handlers. This is the default.
* - number: Insert at a specific index. Values are clamped to the array bounds.
*/
position?: "Top" | "Bottom" | number;
};
type PrependHookOptions = Omit<OnHookOptions, "position">;
type HookifiedOptions = {
/**
* Whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.

@@ -212,2 +266,9 @@ */

allowDeprecated?: boolean;
/**
* Whether to clone hook objects before storing. Default is true.
* When false, the original IHook reference is stored directly.
* @type {boolean}
* @default true
*/
useHookClone?: boolean;
} & EventEmitterOptions;

@@ -218,3 +279,3 @@

private _maxListeners;
private _logger?;
private _eventLogger?;
private _throwOnEmitError;

@@ -225,11 +286,11 @@ private _throwOnEmptyListeners;

/**
* Gets the logger
* Gets the event logger
* @returns {Logger}
*/
get logger(): Logger | undefined;
get eventLogger(): Logger | undefined;
/**
* Sets the logger
* @param {Logger} logger
* Sets the event logger
* @param {Logger} eventLogger
*/
set logger(logger: Logger | undefined);
set eventLogger(eventLogger: Logger | undefined);
/**

@@ -361,5 +422,55 @@ * Gets whether an error should be thrown when an emit throws an error. Default is false and only emits an error event.

*/
private sendLog;
private sendToEventLogger;
}
/**
* Concrete implementation of the IHook interface.
* Provides a convenient class-based way to create hook entries.
*/
declare class Hook implements IHook {
id?: string;
event: string;
handler: HookFn;
/**
* Creates a new Hook instance
* @param {string} event - The event name for the hook
* @param {HookFn} handler - The handler function for the hook
* @param {string} [id] - Optional unique identifier for the hook
*/
constructor(event: string, handler: HookFn, id?: string);
}
/**
* A WaterfallHook chains multiple hook functions sequentially,
* where each hook receives a context with the previous result,
* initial arguments, and accumulated results. After all hooks
* have executed, the final handler receives the transformed result.
* Implements IHook for compatibility with Hookified.onHook().
*/
declare class WaterfallHook implements IWaterfallHook {
id?: string;
event: string;
handler: HookFn;
hooks: WaterfallHookFn[];
private readonly _finalHandler;
/**
* Creates a new WaterfallHook instance
* @param {string} event - The event name for the hook
* @param {WaterfallHookFn} finalHandler - The final handler function that receives the transformed result
* @param {string} [id] - Optional unique identifier for the hook
*/
constructor(event: string, finalHandler: WaterfallHookFn, id?: string);
/**
* Adds a hook function to the end of the waterfall chain
* @param {WaterfallHookFn} hook - The hook function to add
*/
addHook(hook: WaterfallHookFn): void;
/**
* Removes a specific hook function from the waterfall chain
* @param {WaterfallHookFn} hook - The hook function to remove
* @returns {boolean} true if the hook was found and removed
*/
removeHook(hook: WaterfallHookFn): boolean;
}
declare class Hookified extends Eventified {

@@ -371,24 +482,13 @@ private readonly _hooks;

private _allowDeprecated;
private _useHookClone;
constructor(options?: HookifiedOptions);
/**
* Gets all hooks
* @returns {Map<string, Hook[]>}
* @returns {Map<string, IHook[]>}
*/
get hooks(): Map<string, Hook[]>;
get hooks(): Map<string, IHook[]>;
/**
* Gets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.
* @returns {boolean}
* @deprecated - this will be deprecated in version 2. Please use throwOnHookError.
*/
get throwHookErrors(): boolean;
/**
* Sets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.
* @param {boolean} value
* @deprecated - this will be deprecated in version 2. Please use throwOnHookError.
*/
set throwHookErrors(value: boolean);
/**
* Gets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.
* @returns {boolean}
*/
get throwOnHookError(): boolean;

@@ -432,71 +532,68 @@ /**

/**
* Validates hook event name if enforceBeforeAfter is enabled
* @param {string} event - The event name to validate
* @throws {Error} If enforceBeforeAfter is true and event doesn't start with 'before' or 'after'
* Gets whether hook objects are cloned before storing. Default is true.
* @returns {boolean}
*/
private validateHookName;
get useHookClone(): boolean;
/**
* Checks if a hook is deprecated and emits a warning if it is
* @param {string} event - The event name to check
* @returns {boolean} - Returns true if the hook should proceed, false if it should be blocked
* Sets whether hook objects are cloned before storing. Default is true.
* When false, the original IHook reference is stored directly.
* @param {boolean} value
*/
private checkDeprecatedHook;
set useHookClone(value: boolean);
/**
* Adds a handler function for a specific event
* @param {string} event
* @param {Hook} handler - this can be async or sync
* @returns {void}
* Adds a handler function for a specific event.
* If you prefer the legacy `(event, handler)` signature, use {@link addHook} instead.
* To register multiple hooks at once, use {@link onHooks}.
* @param {IHook} hook - the hook containing event name and handler
* @param {OnHookOptions} [options] - optional per-call options (e.g., useHookClone override, position)
* @returns {IHook | undefined} the stored hook, or undefined if blocked by deprecation
*/
onHook(event: string, handler: Hook): void;
onHook(hook: IHook, options?: OnHookOptions): IHook | undefined;
/**
* Adds a handler function for a specific event
* @param {HookEntry} hookEntry
* @returns {void}
*/
onHookEntry(hookEntry: HookEntry): void;
/**
* Alias for onHook. This is provided for compatibility with other libraries that use the `addHook` method.
* @param {string} event
* @param {Hook} handler - this can be async or sync
* @param {string} event - the event name
* @param {HookFn} handler - the handler function
* @returns {void}
*/
addHook(event: string, handler: Hook): void;
addHook(event: string, handler: HookFn): void;
/**
* Adds a handler function for a specific event
* @param {Array<HookEntry>} hooks
* Adds handler functions for specific events
* @param {Array<IHook>} hooks
* @param {OnHookOptions} [options] - optional per-call options (e.g., useHookClone override, position)
* @returns {void}
*/
onHooks(hooks: HookEntry[]): void;
onHooks(hooks: IHook[], options?: OnHookOptions): void;
/**
* Adds a handler function for a specific event that runs before all other handlers
* @param {string} event
* @param {Hook} handler - this can be async or sync
* @returns {void}
* Adds a handler function for a specific event that runs before all other handlers.
* Equivalent to calling `onHook(hook, { position: "Top" })`.
* @param {IHook} hook - the hook containing event name and handler
* @param {PrependHookOptions} [options] - optional per-call options (e.g., useHookClone override)
* @returns {IHook | undefined} the stored hook, or undefined if blocked by deprecation
*/
prependHook(event: string, handler: Hook): void;
prependHook(hook: IHook, options?: PrependHookOptions): IHook | undefined;
/**
* Adds a handler that only executes once for a specific event before all other handlers
* @param event
* @param handler
* Adds a handler that only executes once for a specific event before all other handlers.
* Equivalent to calling `onHook` with a self-removing wrapper and `{ position: "Top" }`.
* @param {IHook} hook - the hook containing event name and handler
* @param {PrependHookOptions} [options] - optional per-call options (e.g., useHookClone override)
* @returns {IHook | undefined} the stored hook, or undefined if blocked by deprecation
*/
prependOnceHook(event: string, handler: Hook): void;
prependOnceHook(hook: IHook, options?: PrependHookOptions): IHook | undefined;
/**
* Adds a handler that only executes once for a specific event
* @param event
* @param handler
* @param {IHook} hook - the hook containing event name and handler
*/
onceHook(event: string, handler: Hook): void;
onceHook(hook: IHook): void;
/**
* Removes a handler function for a specific event
* @param {string} event
* @param {Hook} handler
* @returns {void}
* @param {IHook} hook - the hook containing event name and handler to remove
* @returns {IHook | undefined} the removed hook, or undefined if not found
*/
removeHook(event: string, handler: Hook): void;
removeHook(hook: IHook): IHook | undefined;
/**
* Removes all handlers for a specific event
* @param {Array<HookEntry>} hooks
* @returns {void}
* Removes multiple hook handlers
* @param {Array<IHook>} hooks
* @returns {IHook[]} the hooks that were successfully removed
*/
removeHooks(hooks: HookEntry[]): void;
removeHooks(hooks: IHook[]): IHook[];
/**

@@ -543,6 +640,18 @@ * Calls all handlers for a specific event

* @param {string} event
* @returns {Hook[]}
* @returns {IHook[]}
*/
getHooks(event: string): Hook[] | undefined;
getHooks(event: string): IHook[] | undefined;
/**
* Gets a specific hook by id, searching across all events
* @param {string} id - the hook id
* @returns {IHook | undefined} the hook if found, or undefined
*/
getHook(id: string): IHook | undefined;
/**
* Removes one or more hooks by id, searching across all events
* @param {string | string[]} id - the hook id or array of hook ids to remove
* @returns {IHook | IHook[] | undefined} the removed hook(s), or undefined/empty array if not found
*/
removeHookById(id: string | string[]): IHook | IHook[] | undefined;
/**
* Removes all hooks

@@ -552,4 +661,22 @@ * @returns {void}

clearHooks(): void;
/**
* Removes all hooks for a specific event and returns the removed hooks.
* @param {string} event - The event name to remove hooks for.
* @returns {IHook[]} the hooks that were removed
*/
removeEventHooks(event: string): IHook[];
/**
* Validates hook event name if enforceBeforeAfter is enabled
* @param {string} event - The event name to validate
* @throws {Error} If enforceBeforeAfter is true and event doesn't start with 'before' or 'after'
*/
private validateHookName;
/**
* Checks if a hook is deprecated and emits a warning if it is
* @param {string} event - The event name to check
* @returns {boolean} - Returns true if the hook should proceed, false if it should be blocked
*/
private checkDeprecatedHook;
}
export { type EventEmitterOptions, type EventListener, Eventified, type Hook, type HookEntry, Hookified, type HookifiedOptions, type IEventEmitter, type Logger };
export { type EventEmitterOptions, type EventListener, Eventified, Hook, type HookFn, Hookified, type HookifiedOptions, type IEventEmitter, type IHook, type IWaterfallHook, type Logger, type OnHookOptions, type PrependHookOptions, WaterfallHook, type WaterfallHookContext, type WaterfallHookFn, type WaterfallHookResult };

@@ -5,10 +5,10 @@ // src/eventified.ts

_maxListeners;
_logger;
_eventLogger;
_throwOnEmitError = false;
_throwOnEmptyListeners = false;
_throwOnEmptyListeners = true;
_errorEvent = "error";
constructor(options) {
this._eventListeners = /* @__PURE__ */ new Map();
this._maxListeners = 100;
this._logger = options?.logger;
this._maxListeners = 0;
this._eventLogger = options?.eventLogger;
if (options?.throwOnEmitError !== void 0) {

@@ -22,14 +22,14 @@ this._throwOnEmitError = options.throwOnEmitError;

/**
* Gets the logger
* Gets the event logger
* @returns {Logger}
*/
get logger() {
return this._logger;
get eventLogger() {
return this._eventLogger;
}
/**
* Sets the logger
* @param {Logger} logger
* Sets the event logger
* @param {Logger} eventLogger
*/
set logger(logger) {
this._logger = logger;
set eventLogger(eventLogger) {
this._eventLogger = eventLogger;
}

@@ -163,3 +163,3 @@ /**

if (listeners) {
if (listeners.length >= this._maxListeners) {
if (this._maxListeners > 0 && listeners.length >= this._maxListeners) {
console.warn(

@@ -215,2 +215,3 @@ `MaxListenersExceededWarning: Possible event memory leak detected. ${listeners.length + 1} ${event} listeners added. Use setMaxListeners() to increase limit.`

}
this.sendToEventLogger(event, arguments_);
if (event === this._errorEvent) {

@@ -226,3 +227,2 @@ const error = arguments_[0] instanceof Error ? arguments_[0] : new Error(`${arguments_[0]}`);

}
this.sendLog(event, arguments_);
return result;

@@ -257,8 +257,3 @@ }

setMaxListeners(n) {
this._maxListeners = n;
for (const listeners of this._eventListeners.values()) {
if (listeners.length > n) {
listeners.splice(n);
}
}
this._maxListeners = n < 0 ? 0 : n;
}

@@ -281,4 +276,4 @@ /**

*/
sendLog(eventName, data) {
if (!this._logger) {
sendToEventLogger(eventName, data) {
if (!this._eventLogger) {
return;

@@ -300,23 +295,23 @@ }

case "error": {
this._logger.error?.(message, { event: eventName, data });
this._eventLogger.error?.(message, { event: eventName, data });
break;
}
case "warn": {
this._logger.warn?.(message, { event: eventName, data });
this._eventLogger.warn?.(message, { event: eventName, data });
break;
}
case "trace": {
this._logger.trace?.(message, { event: eventName, data });
this._eventLogger.trace?.(message, { event: eventName, data });
break;
}
case "debug": {
this._logger.debug?.(message, { event: eventName, data });
this._eventLogger.debug?.(message, { event: eventName, data });
break;
}
case "fatal": {
this._logger.fatal?.(message, { event: eventName, data });
this._eventLogger.fatal?.(message, { event: eventName, data });
break;
}
default: {
this._logger.info?.(message, { event: eventName, data });
this._eventLogger.info?.(message, { event: eventName, data });
break;

@@ -328,2 +323,70 @@ }

// src/hooks/hook.ts
var Hook = class {
id;
event;
handler;
/**
* Creates a new Hook instance
* @param {string} event - The event name for the hook
* @param {HookFn} handler - The handler function for the hook
* @param {string} [id] - Optional unique identifier for the hook
*/
constructor(event, handler, id) {
this.id = id;
this.event = event;
this.handler = handler;
}
};
// src/hooks/waterfall-hook.ts
var WaterfallHook = class {
id;
event;
handler;
hooks;
_finalHandler;
/**
* Creates a new WaterfallHook instance
* @param {string} event - The event name for the hook
* @param {WaterfallHookFn} finalHandler - The final handler function that receives the transformed result
* @param {string} [id] - Optional unique identifier for the hook
*/
constructor(event, finalHandler, id) {
this.id = id;
this.event = event;
this.hooks = [];
this._finalHandler = finalHandler;
this.handler = async (...arguments_) => {
const initialArgs = arguments_.length === 1 ? arguments_[0] : arguments_;
const results = [];
for (const hook of this.hooks) {
const result = await hook({ initialArgs, results: [...results] });
results.push({ hook, result });
}
await this._finalHandler({ initialArgs, results: [...results] });
};
}
/**
* Adds a hook function to the end of the waterfall chain
* @param {WaterfallHookFn} hook - The hook function to add
*/
addHook(hook) {
this.hooks.push(hook);
}
/**
* Removes a specific hook function from the waterfall chain
* @param {WaterfallHookFn} hook - The hook function to remove
* @returns {boolean} true if the hook was found and removed
*/
removeHook(hook) {
const index = this.hooks.indexOf(hook);
if (index !== -1) {
this.hooks.splice(index, 1);
return true;
}
return false;
}
};
// src/index.ts

@@ -336,5 +399,6 @@ var Hookified = class extends Eventified {

_allowDeprecated = true;
_useHookClone = true;
constructor(options) {
super({
logger: options?.logger,
eventLogger: options?.eventLogger,
throwOnEmitError: options?.throwOnEmitError,

@@ -347,4 +411,2 @@ throwOnEmptyListeners: options?.throwOnEmptyListeners

this._throwOnHookError = options.throwOnHookError;
} else if (options?.throwHookErrors !== void 0) {
this._throwOnHookError = options.throwHookErrors;
}

@@ -357,6 +419,9 @@ if (options?.enforceBeforeAfter !== void 0) {

}
if (options?.useHookClone !== void 0) {
this._useHookClone = options.useHookClone;
}
}
/**
* Gets all hooks
* @returns {Map<string, Hook[]>}
* @returns {Map<string, IHook[]>}
*/

@@ -369,19 +434,3 @@ get hooks() {

* @returns {boolean}
* @deprecated - this will be deprecated in version 2. Please use throwOnHookError.
*/
get throwHookErrors() {
return this._throwOnHookError;
}
/**
* Sets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.
* @param {boolean} value
* @deprecated - this will be deprecated in version 2. Please use throwOnHookError.
*/
set throwHookErrors(value) {
this._throwOnHookError = value;
}
/**
* Gets whether an error should be thrown when a hook throws an error. Default is false and only emits an error event.
* @returns {boolean}
*/
get throwOnHookError() {

@@ -441,153 +490,149 @@ return this._throwOnHookError;

/**
* Validates hook event name if enforceBeforeAfter is enabled
* @param {string} event - The event name to validate
* @throws {Error} If enforceBeforeAfter is true and event doesn't start with 'before' or 'after'
* Gets whether hook objects are cloned before storing. Default is true.
* @returns {boolean}
*/
validateHookName(event) {
if (this._enforceBeforeAfter) {
const eventValue = event.trim().toLocaleLowerCase();
if (!eventValue.startsWith("before") && !eventValue.startsWith("after")) {
throw new Error(
`Hook event "${event}" must start with "before" or "after" when enforceBeforeAfter is enabled`
);
}
}
get useHookClone() {
return this._useHookClone;
}
/**
* Checks if a hook is deprecated and emits a warning if it is
* @param {string} event - The event name to check
* @returns {boolean} - Returns true if the hook should proceed, false if it should be blocked
* Sets whether hook objects are cloned before storing. Default is true.
* When false, the original IHook reference is stored directly.
* @param {boolean} value
*/
checkDeprecatedHook(event) {
if (this._deprecatedHooks.has(event)) {
const message = this._deprecatedHooks.get(event);
const warningMessage = `Hook "${event}" is deprecated${message ? `: ${message}` : ""}`;
this.emit("warn", { hook: event, message: warningMessage });
return this._allowDeprecated;
}
return true;
set useHookClone(value) {
this._useHookClone = value;
}
/**
* Adds a handler function for a specific event
* @param {string} event
* @param {Hook} handler - this can be async or sync
* @returns {void}
* Adds a handler function for a specific event.
* If you prefer the legacy `(event, handler)` signature, use {@link addHook} instead.
* To register multiple hooks at once, use {@link onHooks}.
* @param {IHook} hook - the hook containing event name and handler
* @param {OnHookOptions} [options] - optional per-call options (e.g., useHookClone override, position)
* @returns {IHook | undefined} the stored hook, or undefined if blocked by deprecation
*/
onHook(event, handler) {
this.onHookEntry({ event, handler });
}
/**
* Adds a handler function for a specific event
* @param {HookEntry} hookEntry
* @returns {void}
*/
onHookEntry(hookEntry) {
this.validateHookName(hookEntry.event);
if (!this.checkDeprecatedHook(hookEntry.event)) {
return;
onHook(hook, options) {
this.validateHookName(hook.event);
if (!this.checkDeprecatedHook(hook.event)) {
return void 0;
}
const eventHandlers = this._hooks.get(hookEntry.event);
const shouldClone = options?.useHookClone ?? this._useHookClone;
const entry = shouldClone ? { id: hook.id, event: hook.event, handler: hook.handler } : hook;
entry.id = entry.id ?? crypto.randomUUID();
const eventHandlers = this._hooks.get(hook.event);
if (eventHandlers) {
eventHandlers.push(hookEntry.handler);
const existingIndex = eventHandlers.findIndex((h) => h.id === entry.id);
if (existingIndex !== -1) {
eventHandlers[existingIndex] = entry;
} else {
const position = options?.position ?? "Bottom";
if (position === "Top") {
eventHandlers.unshift(entry);
} else if (position === "Bottom") {
eventHandlers.push(entry);
} else {
const index = Math.max(0, Math.min(position, eventHandlers.length));
eventHandlers.splice(index, 0, entry);
}
}
} else {
this._hooks.set(hookEntry.event, [hookEntry.handler]);
this._hooks.set(hook.event, [entry]);
}
return entry;
}
/**
* Alias for onHook. This is provided for compatibility with other libraries that use the `addHook` method.
* @param {string} event
* @param {Hook} handler - this can be async or sync
* @param {string} event - the event name
* @param {HookFn} handler - the handler function
* @returns {void}
*/
addHook(event, handler) {
this.onHookEntry({ event, handler });
this.onHook({ event, handler });
}
/**
* Adds a handler function for a specific event
* @param {Array<HookEntry>} hooks
* Adds handler functions for specific events
* @param {Array<IHook>} hooks
* @param {OnHookOptions} [options] - optional per-call options (e.g., useHookClone override, position)
* @returns {void}
*/
onHooks(hooks) {
onHooks(hooks, options) {
for (const hook of hooks) {
this.onHook(hook.event, hook.handler);
this.onHook(hook, options);
}
}
/**
* Adds a handler function for a specific event that runs before all other handlers
* @param {string} event
* @param {Hook} handler - this can be async or sync
* @returns {void}
* Adds a handler function for a specific event that runs before all other handlers.
* Equivalent to calling `onHook(hook, { position: "Top" })`.
* @param {IHook} hook - the hook containing event name and handler
* @param {PrependHookOptions} [options] - optional per-call options (e.g., useHookClone override)
* @returns {IHook | undefined} the stored hook, or undefined if blocked by deprecation
*/
prependHook(event, handler) {
this.validateHookName(event);
if (!this.checkDeprecatedHook(event)) {
return;
}
const eventHandlers = this._hooks.get(event);
if (eventHandlers) {
eventHandlers.unshift(handler);
} else {
this._hooks.set(event, [handler]);
}
prependHook(hook, options) {
return this.onHook(hook, { ...options, position: "Top" });
}
/**
* Adds a handler that only executes once for a specific event before all other handlers
* @param event
* @param handler
* Adds a handler that only executes once for a specific event before all other handlers.
* Equivalent to calling `onHook` with a self-removing wrapper and `{ position: "Top" }`.
* @param {IHook} hook - the hook containing event name and handler
* @param {PrependHookOptions} [options] - optional per-call options (e.g., useHookClone override)
* @returns {IHook | undefined} the stored hook, or undefined if blocked by deprecation
*/
prependOnceHook(event, handler) {
this.validateHookName(event);
if (!this.checkDeprecatedHook(event)) {
return;
}
const hook = async (...arguments_) => {
this.removeHook(event, hook);
return handler(...arguments_);
prependOnceHook(hook, options) {
const wrappedHandler = async (...arguments_) => {
this.removeHook({ event: hook.event, handler: wrappedHandler });
return hook.handler(...arguments_);
};
this.prependHook(event, hook);
return this.onHook(
{ id: hook.id, event: hook.event, handler: wrappedHandler },
{ ...options, position: "Top" }
);
}
/**
* Adds a handler that only executes once for a specific event
* @param event
* @param handler
* @param {IHook} hook - the hook containing event name and handler
*/
onceHook(event, handler) {
this.validateHookName(event);
if (!this.checkDeprecatedHook(event)) {
onceHook(hook) {
this.validateHookName(hook.event);
if (!this.checkDeprecatedHook(hook.event)) {
return;
}
const hook = async (...arguments_) => {
this.removeHook(event, hook);
return handler(...arguments_);
const wrappedHandler = async (...arguments_) => {
this.removeHook({ event: hook.event, handler: wrappedHandler });
return hook.handler(...arguments_);
};
this.onHook(event, hook);
this.onHook({ id: hook.id, event: hook.event, handler: wrappedHandler });
}
/**
* Removes a handler function for a specific event
* @param {string} event
* @param {Hook} handler
* @returns {void}
* @param {IHook} hook - the hook containing event name and handler to remove
* @returns {IHook | undefined} the removed hook, or undefined if not found
*/
removeHook(event, handler) {
this.validateHookName(event);
if (!this.checkDeprecatedHook(event)) {
return;
}
const eventHandlers = this._hooks.get(event);
removeHook(hook) {
this.validateHookName(hook.event);
const eventHandlers = this._hooks.get(hook.event);
if (eventHandlers) {
const index = eventHandlers.indexOf(handler);
const index = eventHandlers.findIndex((h) => h.handler === hook.handler);
if (index !== -1) {
eventHandlers.splice(index, 1);
if (eventHandlers.length === 0) {
this._hooks.delete(hook.event);
}
return { event: hook.event, handler: hook.handler };
}
}
return void 0;
}
/**
* Removes all handlers for a specific event
* @param {Array<HookEntry>} hooks
* @returns {void}
* Removes multiple hook handlers
* @param {Array<IHook>} hooks
* @returns {IHook[]} the hooks that were successfully removed
*/
removeHooks(hooks) {
const removed = [];
for (const hook of hooks) {
this.removeHook(hook.event, hook.handler);
const result = this.removeHook(hook);
if (result) {
removed.push(result);
}
}
return removed;
}

@@ -607,5 +652,5 @@ /**

if (eventHandlers) {
for (const handler of eventHandlers) {
for (const hook of [...eventHandlers]) {
try {
await handler(...arguments_);
await hook.handler(...arguments_);
} catch (error) {

@@ -638,8 +683,8 @@ const message = `${event}: ${error.message}`;

if (eventHandlers) {
for (const handler of eventHandlers) {
if (handler.constructor.name === "AsyncFunction") {
for (const hook of [...eventHandlers]) {
if (hook.handler.constructor.name === "AsyncFunction") {
continue;
}
try {
handler(...arguments_);
hook.handler(...arguments_);
} catch (error) {

@@ -684,12 +729,51 @@ const message = `${event}: ${error.message}`;

* @param {string} event
* @returns {Hook[]}
* @returns {IHook[]}
*/
getHooks(event) {
this.validateHookName(event);
if (!this.checkDeprecatedHook(event)) {
return void 0;
}
return this._hooks.get(event);
}
/**
* Gets a specific hook by id, searching across all events
* @param {string} id - the hook id
* @returns {IHook | undefined} the hook if found, or undefined
*/
getHook(id) {
for (const eventHandlers of this._hooks.values()) {
const found = eventHandlers.find((h) => h.id === id);
if (found) {
return found;
}
}
return void 0;
}
/**
* Removes one or more hooks by id, searching across all events
* @param {string | string[]} id - the hook id or array of hook ids to remove
* @returns {IHook | IHook[] | undefined} the removed hook(s), or undefined/empty array if not found
*/
removeHookById(id) {
if (Array.isArray(id)) {
const removed = [];
for (const singleId of id) {
const result = this.removeHookById(singleId);
if (result && !Array.isArray(result)) {
removed.push(result);
}
}
return removed;
}
for (const [event, eventHandlers] of this._hooks.entries()) {
const index = eventHandlers.findIndex((h) => h.id === id);
if (index !== -1) {
const [removed] = eventHandlers.splice(index, 1);
if (eventHandlers.length === 0) {
this._hooks.delete(event);
}
return removed;
}
}
return void 0;
}
/**
* Removes all hooks

@@ -701,7 +785,53 @@ * @returns {void}

}
/**
* Removes all hooks for a specific event and returns the removed hooks.
* @param {string} event - The event name to remove hooks for.
* @returns {IHook[]} the hooks that were removed
*/
removeEventHooks(event) {
this.validateHookName(event);
const eventHandlers = this._hooks.get(event);
if (eventHandlers) {
const removed = [...eventHandlers];
this._hooks.delete(event);
return removed;
}
return [];
}
/**
* Validates hook event name if enforceBeforeAfter is enabled
* @param {string} event - The event name to validate
* @throws {Error} If enforceBeforeAfter is true and event doesn't start with 'before' or 'after'
*/
validateHookName(event) {
if (this._enforceBeforeAfter) {
const eventValue = event.trim().toLocaleLowerCase();
if (!eventValue.startsWith("before") && !eventValue.startsWith("after")) {
throw new Error(
`Hook event "${event}" must start with "before" or "after" when enforceBeforeAfter is enabled`
);
}
}
}
/**
* Checks if a hook is deprecated and emits a warning if it is
* @param {string} event - The event name to check
* @returns {boolean} - Returns true if the hook should proceed, false if it should be blocked
*/
checkDeprecatedHook(event) {
if (this._deprecatedHooks.has(event)) {
const message = this._deprecatedHooks.get(event);
const warningMessage = `Hook "${event}" is deprecated${message ? `: ${message}` : ""}`;
this.emit("warn", { hook: event, message: warningMessage });
return this._allowDeprecated;
}
return true;
}
};
export {
Eventified,
Hookified
Hook,
Hookified,
WaterfallHook
};
/* v8 ignore next -- @preserve */
{
"name": "hookified",
"version": "1.15.1",
"version": "2.0.0",
"description": "Event Emitting and Middleware Hooks",

@@ -72,12 +72,12 @@ "type": "module",

"devDependencies": {
"@biomejs/biome": "^2.3.13",
"@biomejs/biome": "^2.4.4",
"@monstermann/tinybench-pretty-printer": "^0.3.0",
"@types/node": "^25.0.3",
"@types/node": "^25.3.0",
"@vitest/coverage-v8": "^4.0.18",
"docula": "^0.40.0",
"docula": "^0.41.1",
"emittery": "^1.2.0",
"eventemitter3": "^5.0.4",
"hookable": "^6.0.1",
"pino": "^10.3.0",
"rimraf": "^6.1.2",
"pino": "^10.3.1",
"rimraf": "^6.1.3",
"tinybench": "^6.0.0",

@@ -84,0 +84,0 @@ "tsup": "^8.5.1",

+956
-705

@@ -7,3 +7,3 @@ ![Hookified](site/logo.svg)

[![GitHub license](https://img.shields.io/github/license/jaredwray/hookified)](https://github.com/jaredwray/hookified/blob/master/LICENSE)
[![codecov](https://codecov.io/gh/jaredwray/hookified/graph/badge.svg?token=nKkVklTFdA)](https://codecov.io/gh/jaredwray/hookified)
[![codecov](https://codecov.io/gh/jaredwray/hookified/branch/main/graph/badge.svg?token=nKkVklTFdA)](https://codecov.io/gh/jaredwray/hookified)
[![npm](https://img.shields.io/npm/dm/hookified)](https://npmjs.com/package/hookified)

@@ -23,3 +23,4 @@ [![jsDelivr](https://data.jsdelivr.com/v1/package/npm/hookified/badge)](https://www.jsdelivr.com/package/npm/hookified)

- Control deprecated hook execution with `allowDeprecated`
- No package dependencies and only 200KB in size
- WaterfallHook for sequential data transformation pipelines
- No package dependencies and only 250KB in size
- Fast and Efficient with [Benchmarks](#benchmarks)

@@ -31,26 +32,33 @@ - Maintained on a regular basis!

- [Usage](#usage)
- [Migrating from v1 to v2](#migrating-from-v1-to-v2)
- [Using it in the Browser](#using-it-in-the-browser)
- [Hooks](#hooks)
- [Standard Hook](#standard-hook)
- [Waterfall Hook](#waterfallhook)
- [API - Hooks](#api---hooks)
- [.throwOnHookError](#throwhookerror)
- [.logger](#logger)
- [.allowDeprecated](#allowdeprecated)
- [.deprecatedHooks](#deprecatedhooks)
- [.enforceBeforeAfter](#enforcebeforeafter)
- [.deprecatedHooks](#deprecatedhooks)
- [.allowDeprecated](#allowdeprecated)
- [.onHook(eventName, handler)](#onhookeventname-handler)
- [.onHookEntry(hookEntry)](#onhookentryhookentry)
- [.addHook(eventName, handler)](#addhookeventname-handler)
- [.onHooks(Array)](#onhooksarray)
- [.onceHook(eventName, handler)](#oncehookeventname-handler)
- [.prependHook(eventName, handler)](#prependhookeventname-handler)
- [.prependOnceHook(eventName, handler)](#prependoncehookeventname-handler)
- [.removeHook(eventName)](#removehookeventname)
- [.removeHooks(Array)](#removehooksarray)
- [.eventLogger](#eventlogger)
- [.hooks](#hooks-1)
- [.throwOnHookError](#throwOnHookError)
- [.useHookClone](#usehookclone)
- [.addHook(event, handler)](#addhookevent-handler)
- [.afterHook(eventName, ...args)](#afterhookeventname-args)
- [.beforeHook(eventName, ...args)](#beforehookeventname-args)
- [.callHook(eventName, ...args)](#callhookeventname-args)
- [.clearHooks()](#clearhooks)
- [.getHook(id)](#gethookid)
- [.getHooks(eventName)](#gethookseventname)
- [.hook(eventName, ...args)](#hookeventname-args)
- [.callHook(eventName, ...args)](#callhookeventname-args)
- [.beforeHook(eventName, ...args)](#beforehookeventname-args)
- [.afterHook(eventName, ...args)](#afterhookeventname-args)
- [.hookSync(eventName, ...args)](#hooksync-eventname-args)
- [.hooks](#hooks)
- [.getHooks(eventName)](#gethookseventname)
- [.clearHooks(eventName)](#clearhookeventname)
- [.onHook(hook, options?)](#onhookhook-options)
- [.onHooks(Array, options?)](#onhooksarray-options)
- [.onceHook(hook)](#oncehookhook)
- [.prependHook(hook, options?)](#prependhookhook-options)
- [.prependOnceHook(hook, options?)](#prependoncehookhook-options)
- [.removeEventHooks(eventName)](#removeeventhookseventname)
- [.removeHook(hook)](#removehookhook)
- [.removeHookById(id)](#removehookbyidid)
- [.removeHooks(Array)](#removehooksarray)
- [API - Events](#api---events)

@@ -93,7 +101,7 @@ - [.throwOnEmitError](#throwonemitterror)

async myMethodEmittingEvent() {
this.emit('message', 'Hello World'); //using Emittery
this.emit('message', 'Hello World');
}
//with hooks you can pass data in and if they are subscribed via onHook they can modify the data
async myMethodWithHooks() Promise<any> {
async myMethodWithHooks(): Promise<any> {
let data = { some: 'data' };

@@ -118,3 +126,3 @@ // do something

async myMethodWithHooks() Promise<any> {
async myMethodWithHooks(): Promise<any> {
let data = { some: 'data' };

@@ -142,7 +150,7 @@ let data2 = { some: 'data2' };

async myMethodEmittingEvent() {
this.emit('message', 'Hello World'); //using Emittery
this.emit('message', 'Hello World');
}
//with hooks you can pass data in and if they are subscribed via onHook they can modify the data
async myMethodWithHooks() Promise<any> {
async myMethodWithHooks(): Promise<any> {
let data = { some: 'data' };

@@ -169,7 +177,7 @@ // do something

async myMethodEmittingEvent() {
this.emit('message', 'Hello World'); //using Emittery
this.emit('message', 'Hello World');
}
//with hooks you can pass data in and if they are subscribed via onHook they can modify the data
async myMethodWithHooks() Promise<any> {
async myMethodWithHooks(): Promise<any> {
let data = { some: 'data' };

@@ -185,15 +193,25 @@ // do something

# API - Hooks
# Hooks
## .throwOnHookError
## Standard Hook
If set to true, errors thrown in hooks will be thrown. If set to false, errors will be only emitted.
The `Hook` class provides a convenient way to create hook entries. It implements the `IHook` interface.
The `IHook` interface has the following properties:
| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `id` | `string` | No | Unique identifier for the hook. Auto-generated via `crypto.randomUUID()` if not provided. |
| `event` | `string` | Yes | The event name for the hook. |
| `handler` | `HookFn` | Yes | The handler function for the hook. |
When a hook is registered, it is assigned an `id` (auto-generated if not provided). The `id` can be used to look up or remove hooks via `getHook` and `removeHookById`. If you register a hook with the same `id` on the same event, it will replace the existing hook in-place (preserving its position).
**Using the `Hook` class:**
```javascript
import { Hookified } from 'hookified';
import { Hook, Hookified } from 'hookified';
class MyClass extends Hookified {
constructor() {
super({ throwOnHookError: true });
}
constructor() { super(); }
}

@@ -203,122 +221,94 @@

console.log(myClass.throwOnHookError); // true. because it is set in super
// Without id (auto-generated)
const hook = new Hook('before:save', async (data) => {
data.validated = true;
});
try {
myClass.onHook('error-event', async () => {
throw new Error('error');
});
// With id
const hook2 = new Hook('after:save', async (data) => {
console.log('saved');
}, 'my-after-save-hook');
await myClass.hook('error-event');
} catch (error) {
console.log(error.message); // error
}
// Register with onHook
myClass.onHook(hook);
myClass.throwOnHookError = false;
console.log(myClass.throwOnHookError); // false
// Or register multiple hooks with onHooks
const hooks = [
new Hook('before:save', async (data) => { data.validated = true; }),
new Hook('after:save', async (data) => { console.log('saved'); }),
];
myClass.onHooks(hooks);
// Remove hooks
myClass.removeHooks(hooks);
```
## .logger
If set, errors thrown in hooks will be logged to the logger. If not set, errors will be only emitted.
**Using plain TypeScript with the `IHook` interface:**
```javascript
import { Hookified } from 'hookified';
import pino from 'pino';
```typescript
import { Hookified, type IHook } from 'hookified';
const logger = pino(); // create a logger instance that is compatible with Logger type
class MyClass extends Hookified {
constructor() {
super({ logger });
}
async myMethodWithHooks() Promise<any> {
let data = { some: 'data' };
// do something
await this.hook('before:myMethod2', data);
return data;
}
constructor() { super(); }
}
const myClass = new MyClass();
myClass.onHook('before:myMethod2', async () => {
throw new Error('error');
});
// when you call before:myMethod2 it will log the error to the logger
await myClass.hook('before:myMethod2');
const hook: IHook = {
id: 'my-validation-hook', // optional — auto-generated if omitted
event: 'before:save',
handler: async (data) => {
data.validated = true;
},
};
const stored = myClass.onHook(hook);
console.log(stored?.id); // 'my-validation-hook'
// Later, remove by id
myClass.removeHookById('my-validation-hook');
```
## .enforceBeforeAfter
## Waterfall Hook
If set to true, enforces that all hook names must start with 'before' or 'after'. This is useful for maintaining consistent hook naming conventions in your application. Default is false.
The `WaterfallHook` class chains multiple hook functions sequentially in a waterfall pipeline. Each hook receives a context containing the original arguments and the accumulated results from all previous hooks. It implements the `IHook` interface, so it integrates directly with `Hookified.onHook()`.
```javascript
import { Hookified } from 'hookified';
The `WaterfallHookContext` has the following properties:
class MyClass extends Hookified {
constructor() {
super({ enforceBeforeAfter: true });
}
}
| Property | Type | Description |
|----------|------|-------------|
| `initialArgs` | `any` | The original arguments passed to the waterfall execution. |
| `results` | `WaterfallHookResult[]` | Array of `{ hook, result }` entries from previous hooks. Empty for the first hook. |
const myClass = new MyClass();
**Basic usage:**
console.log(myClass.enforceBeforeAfter); // true
```javascript
import { WaterfallHook } from 'hookified';
// These will work fine
myClass.onHook('beforeSave', async () => {
console.log('Before save hook');
const wh = new WaterfallHook('process', ({ results, initialArgs }) => {
// Final handler receives all accumulated results
const lastResult = results[results.length - 1].result;
console.log('Final:', lastResult);
});
myClass.onHook('afterSave', async () => {
console.log('After save hook');
// Add transformation hooks to the pipeline
wh.addHook(({ initialArgs }) => {
return initialArgs + 1; // 5 -> 6
});
myClass.onHook('before:validation', async () => {
console.log('Before validation hook');
wh.addHook(({ results }) => {
return results[results.length - 1].result * 2; // 6 -> 12
});
// This will throw an error
try {
myClass.onHook('customEvent', async () => {
console.log('This will not work');
});
} catch (error) {
console.log(error.message); // Hook event "customEvent" must start with "before" or "after" when enforceBeforeAfter is enabled
}
// You can also change it dynamically
myClass.enforceBeforeAfter = false;
myClass.onHook('customEvent', async () => {
console.log('This will work now');
});
// Execute the waterfall by calling handler directly
await wh.handler(5); // Final: 12
```
The validation applies to all hook-related methods:
- `onHook()`, `addHook()`, `onHookEntry()`, `onHooks()`
- `prependHook()`, `onceHook()`, `prependOnceHook()`
- `hook()`, `callHook()`
- `getHooks()`, `removeHook()`, `removeHooks()`
**Integrating with Hookified via `onHook()`:**
Note: The `beforeHook()` and `afterHook()` helper methods automatically generate proper hook names and work regardless of the `enforceBeforeAfter` setting.
## .deprecatedHooks
A Map of deprecated hook names to deprecation messages. When a deprecated hook is used, a warning will be emitted via the 'warn' event and logged to the logger (if available). Default is an empty Map.
```javascript
import { Hookified } from 'hookified';
import { Hookified, WaterfallHook } from 'hookified';
// Define deprecated hooks with custom messages
const deprecatedHooks = new Map([
['oldHook', 'Use newHook instead'],
['legacyMethod', 'This hook will be removed in v2.0'],
['deprecatedFeature', ''] // Empty message - will just say "deprecated"
]);
class MyClass extends Hookified {
constructor() {
super({ deprecatedHooks });
}
constructor() { super(); }
}

@@ -328,46 +318,48 @@

console.log(myClass.deprecatedHooks); // Map with deprecated hooks
// Listen for deprecation warnings
myClass.on('warn', (event) => {
console.log(`Deprecation warning: ${event.message}`);
// event.hook contains the hook name
// event.message contains the full warning message
const wh = new WaterfallHook('save', ({ results }) => {
const data = results[results.length - 1].result;
console.log('Saved:', data);
});
// Using a deprecated hook will emit warnings
myClass.onHook('oldHook', () => {
console.log('This hook is deprecated');
wh.addHook(({ initialArgs }) => {
return { ...initialArgs, validated: true };
});
// Output: Hook "oldHook" is deprecated: Use newHook instead
// Using a deprecated hook with empty message
myClass.onHook('deprecatedFeature', () => {
console.log('This hook is deprecated');
wh.addHook(({ results }) => {
return { ...results[results.length - 1].result, timestamp: Date.now() };
});
// Output: Hook "deprecatedFeature" is deprecated
// You can also set deprecated hooks dynamically
myClass.deprecatedHooks.set('anotherOldHook', 'Please migrate to the new API');
// Register with Hookified — works because WaterfallHook implements IHook
myClass.onHook(wh);
// Works with logger if provided
import pino from 'pino';
const logger = pino();
// When hook() fires, the full waterfall pipeline executes
await myClass.hook('save', { name: 'test' });
// Saved: { name: 'test', validated: true, timestamp: ... }
```
const myClassWithLogger = new Hookified({
deprecatedHooks,
logger
});
**Managing hooks:**
// Deprecation warnings will be logged to logger.warn
```javascript
const wh = new WaterfallHook('process', ({ results }) => results);
const myHook = ({ initialArgs }) => initialArgs + 1;
wh.addHook(myHook);
// Remove a hook by reference
wh.removeHook(myHook); // returns true
// Access the hooks array
console.log(wh.hooks.length); // 0
```
The deprecation warning system applies to all hook-related methods:
- Registration: `onHook()`, `addHook()`, `onHookEntry()`, `onHooks()`, `prependHook()`, `onceHook()`, `prependOnceHook()`
- Execution: `hook()`, `callHook()`
- Management: `getHooks()`, `removeHook()`, `removeHooks()`
# API - Hooks
Deprecation warnings are emitted in two ways:
1. **Event**: A 'warn' event is emitted with `{ hook: string, message: string }`
2. **Logger**: Logged to `logger.warn()` if a logger is configured and has a `warn` method
> All examples below assume the following setup unless otherwise noted:
> ```javascript
> import { Hookified } from 'hookified';
> class MyClass extends Hookified {
> constructor(options) { super(options); }
> }
> const myClass = new MyClass();
> ```

@@ -401,5 +393,5 @@ ## .allowDeprecated

// Try to register a deprecated hook - will emit warning but not register
myClass.onHook('oldHook', () => {
myClass.onHook({ event: 'oldHook', handler: () => {
console.log('This will never execute');
});
}});
// Output: Warning: Hook "oldHook" is deprecated: Use newHook instead

@@ -416,5 +408,5 @@

// Non-deprecated hooks work normally
myClass.onHook('validHook', () => {
myClass.onHook({ event: 'validHook', handler: () => {
console.log('This works fine');
});
}});

@@ -427,5 +419,5 @@ console.log(myClass.getHooks('validHook')); // [handler function]

// Now deprecated hooks can be registered and executed
myClass.onHook('oldHook', () => {
myClass.onHook({ event: 'oldHook', handler: () => {
console.log('Now this works');
});
}});

@@ -437,4 +429,4 @@ console.log(myClass.getHooks('oldHook')); // [handler function]

- **Registration**: All hook registration methods (`onHook`, `addHook`, `prependHook`, etc.) will emit warnings but skip registration
- **Execution**: Hook execution methods (`hook`, `callHook`) will emit warnings but skip execution
- **Management**: Hook management methods (`getHooks`, `removeHook`) will emit warnings and return undefined/skip operations
- **Execution**: Hook execution methods (`hook`, `callHook`) will emit warnings but skip execution
- **Removal/Reading**: `removeHook`, `removeHooks`, and `getHooks` always work regardless of deprecation status
- **Warnings**: Deprecation warnings are always emitted regardless of `allowDeprecated` setting

@@ -448,5 +440,5 @@

## .onHook(eventName, handler)
## .deprecatedHooks
Subscribe to a hook event.
A Map of deprecated hook names to deprecation messages. When a deprecated hook is used, a warning will be emitted via the 'warn' event and logged to the logger (if available). Default is an empty Map.

@@ -456,60 +448,67 @@ ```javascript

// Define deprecated hooks with custom messages
const deprecatedHooks = new Map([
['oldHook', 'Use newHook instead'],
['legacyMethod', 'This hook will be removed in v2.0'],
['deprecatedFeature', ''] // Empty message - will just say "deprecated"
]);
class MyClass extends Hookified {
constructor() {
super();
super({ deprecatedHooks });
}
async myMethodWithHooks() Promise<any> {
let data = { some: 'data' };
// do something
await this.hook('before:myMethod2', data);
return data;
}
}
const myClass = new MyClass();
myClass.onHook('before:myMethod2', async (data) => {
data.some = 'new data';
});
```
## .onHookEntry(hookEntry)
console.log(myClass.deprecatedHooks); // Map with deprecated hooks
This allows you to create a hook with the `HookEntry` type which includes the event and handler. This is useful for creating hooks with a single object.
// Listen for deprecation warnings
myClass.on('warn', (event) => {
console.log(`Deprecation warning: ${event.message}`);
// event.hook contains the hook name
// event.message contains the full warning message
});
```javascript
import { Hookified, HookEntry } from 'hookified';
// Using a deprecated hook will emit warnings
myClass.onHook({ event: 'oldHook', handler: () => {
console.log('This hook is deprecated');
}});
// Output: Hook "oldHook" is deprecated: Use newHook instead
class MyClass extends Hookified {
constructor() {
super();
}
// Using a deprecated hook with empty message
myClass.onHook({ event: 'deprecatedFeature', handler: () => {
console.log('This hook is deprecated');
}});
// Output: Hook "deprecatedFeature" is deprecated
async myMethodWithHooks() Promise<any> {
let data = { some: 'data' };
// do something
await this.hook('before:myMethod2', data);
// You can also set deprecated hooks dynamically
myClass.deprecatedHooks.set('anotherOldHook', 'Please migrate to the new API');
return data;
}
}
// Works with logger if provided
import pino from 'pino';
const logger = pino();
const myClass = new MyClass();
myClass.onHookEntry({
event: 'before:myMethod2',
handler: async (data) => {
data.some = 'new data';
},
const myClassWithLogger = new Hookified({
deprecatedHooks,
eventLogger: logger
});
// Deprecation warnings will be logged to logger.warn
```
## .addHook(eventName, handler)
The deprecation warning system applies to the following hook-related methods:
- Registration: `onHook()`, `addHook()`, `onHooks()`, `prependHook()`, `onceHook()`, `prependOnceHook()`
- Execution: `hook()`, `callHook()`
This is an alias for `.onHook(eventName, handler)` for backwards compatibility.
Note: `getHooks()`, `removeHook()`, and `removeHooks()` do not check for deprecated hooks and always operate normally.
## .onHooks(Array)
Deprecation warnings are emitted in two ways:
1. **Event**: A 'warn' event is emitted with `{ hook: string, message: string }`
2. **Logger**: Logged to `eventLogger.warn()` if an `eventLogger` is configured and has a `warn` method
Subscribe to multiple hook events at once
## .enforceBeforeAfter
If set to true, enforces that all hook names must start with 'before' or 'after'. This is useful for maintaining consistent hook naming conventions in your application. Default is false.
```javascript

@@ -520,427 +519,430 @@ import { Hookified } from 'hookified';

constructor() {
super();
super({ enforceBeforeAfter: true });
}
}
async myMethodWithHooks() Promise<any> {
let data = { some: 'data' };
await this.hook('before:myMethodWithHooks', data);
// do something here with the data
data.some = 'new data';
const myClass = new MyClass();
await this.hook('after:myMethodWithHooks', data);
console.log(myClass.enforceBeforeAfter); // true
return data;
}
// These will work fine
myClass.onHook({ event: 'beforeSave', handler: async () => {
console.log('Before save hook');
}});
myClass.onHook({ event: 'afterSave', handler: async () => {
console.log('After save hook');
}});
myClass.onHook({ event: 'before:validation', handler: async () => {
console.log('Before validation hook');
}});
// This will throw an error
try {
myClass.onHook({ event: 'customEvent', handler: async () => {
console.log('This will not work');
}});
} catch (error) {
console.log(error.message); // Hook event "customEvent" must start with "before" or "after" when enforceBeforeAfter is enabled
}
const myClass = new MyClass();
const hooks = [
{
event: 'before:myMethodWithHooks',
handler: async (data) => {
data.some = 'new data1';
},
},
{
event: 'after:myMethodWithHooks',
handler: async (data) => {
data.some = 'new data2';
},
},
];
// You can also change it dynamically
myClass.enforceBeforeAfter = false;
myClass.onHook({ event: 'customEvent', handler: async () => {
console.log('This will work now');
}});
```
## .onceHook(eventName, handler)
The validation applies to all hook-related methods:
- `onHook()`, `addHook()`, `onHooks()`
- `prependHook()`, `onceHook()`, `prependOnceHook()`
- `hook()`, `callHook()`
- `getHooks()`, `removeHook()`, `removeHooks()`
Subscribe to a hook event once.
Note: The `beforeHook()` and `afterHook()` helper methods automatically generate proper hook names and work regardless of the `enforceBeforeAfter` setting.
## .eventLogger
If set, errors thrown in hooks will be logged to the logger. If not set, errors will be only emitted.
```javascript
import { Hookified } from 'hookified';
import pino from 'pino';
class MyClass extends Hookified {
constructor() {
super();
}
const myClass = new MyClass({ eventLogger: pino() });
async myMethodWithHooks() Promise<any> {
let data = { some: 'data' };
// do something
await this.hook('before:myMethod2', data);
myClass.onHook({ event: 'before:myMethod2', handler: async () => {
throw new Error('error');
}});
return data;
}
}
// when you call before:myMethod2 it will log the error to the logger
await myClass.hook('before:myMethod2');
```
const myClass = new MyClass();
## .hooks
myClass.onHookOnce('before:myMethod2', async (data) => {
Get all hooks. Returns a `Map<string, IHook[]>` where each key is an event name and the value is an array of `IHook` objects.
```javascript
myClass.onHook({ event: 'before:myMethod2', handler: async (data) => {
data.some = 'new data';
});
}});
myClass.myMethodWithHooks();
console.log(myClass.hooks.length); // 0
console.log(myClass.hooks); // Map { 'before:myMethod2' => [{ event: 'before:myMethod2', handler: [Function] }] }
```
## .prependHook(eventName, handler)
## .throwOnHookError
Subscribe to a hook event before all other hooks.
If set to true, errors thrown in hooks will be thrown. If set to false, errors will be only emitted.
```javascript
import { Hookified } from 'hookified';
const myClass = new MyClass({ throwOnHookError: true });
class MyClass extends Hookified {
constructor() {
super();
}
console.log(myClass.throwOnHookError); // true
async myMethodWithHooks() Promise<any> {
let data = { some: 'data' };
// do something
await this.hook('before:myMethod2', data);
try {
myClass.onHook({ event: 'error-event', handler: async () => {
throw new Error('error');
}});
return data;
}
await myClass.hook('error-event');
} catch (error) {
console.log(error.message); // error
}
const myClass = new MyClass();
myClass.onHook('before:myMethod2', async (data) => {
data.some = 'new data';
});
myClass.preHook('before:myMethod2', async (data) => {
data.some = 'will run before new data';
});
myClass.throwOnHookError = false;
console.log(myClass.throwOnHookError); // false
```
## .prependOnceHook(eventName, handler)
## .useHookClone
Subscribe to a hook event before all other hooks. After it is used once it will be removed.
Controls whether hook objects are cloned before storing internally. Default is `true`. When `true`, a shallow copy of the `IHook` object is stored, preventing external mutation from affecting registered hooks. When `false`, the original reference is stored directly.
```javascript
import { Hookified } from 'hookified';
const myClass = new MyClass({ useHookClone: false });
class MyClass extends Hookified {
constructor() {
super();
}
const hook = { event: 'before:save', handler: async (data) => {} };
myClass.onHook(hook);
async myMethodWithHooks() Promise<any> {
let data = { some: 'data' };
// do something
await this.hook('before:myMethod2', data);
// With useHookClone: false, the stored hook is the same reference
const storedHooks = myClass.getHooks('before:save');
console.log(storedHooks[0] === hook); // true
return data;
}
}
// You can dynamically change the setting
myClass.useHookClone = true;
```
const myClass = new MyClass();
myClass.onHook('before:myMethod2', async (data) => {
## .addHook(event, handler)
This is an alias for `.onHook()` that takes an event name and handler function directly.
```javascript
myClass.addHook('before:myMethod2', async (data) => {
data.some = 'new data';
});
myClass.preHook('before:myMethod2', async (data) => {
data.some = 'will run before new data';
});
```
## .removeHook(eventName)
## .afterHook(eventName, ...args)
Unsubscribe from a hook event.
This is a helper function that will prepend a hook name with `after:`.
```javascript
import { Hookified } from 'hookified';
// Inside your class method — the event name will be `after:myMethod2`
await this.afterHook('myMethod2', data);
```
class MyClass extends Hookified {
constructor() {
super();
}
## .beforeHook(eventName, ...args)
async myMethodWithHooks() Promise<any> {
let data = { some: 'data' };
// do something
await this.hook('before:myMethod2', data);
This is a helper function that will prepend a hook name with `before:`.
return data;
}
}
```javascript
// Inside your class method — the event name will be `before:myMethod2`
await this.beforeHook('myMethod2', data);
```
const myClass = new MyClass();
const handler = async (data) => {
data.some = 'new data';
};
## .callHook(eventName, ...args)
myClass.onHook('before:myMethod2', handler);
This is an alias for `.hook(eventName, ...args)` for backwards compatibility.
myClass.removeHook('before:myMethod2', handler);
```
## .clearHooks()
## .removeHooks(Array)
Unsubscribe from multiple hooks.
Clear all hooks across all events.
```javascript
import { Hookified } from 'hookified';
myClass.onHook({ event: 'before:myMethod2', handler: async (data) => {
data.some = 'new data';
}});
class MyClass extends Hookified {
constructor() {
super();
}
myClass.clearHooks();
```
async myMethodWithHooks() Promise<any> {
let data = { some: 'data' };
await this.hook('before:myMethodWithHooks', data);
// do something
data.some = 'new data';
await this.hook('after:myMethodWithHooks', data);
## .getHook(id)
return data;
}
}
Get a specific hook by `id`, searching across all events. Returns the `IHook` if found, or `undefined`.
```javascript
const myClass = new MyClass();
const hooks = [
{
event: 'before:myMethodWithHooks',
handler: async (data) => {
data.some = 'new data1';
},
},
{
event: 'after:myMethodWithHooks',
handler: async (data) => {
data.some = 'new data2';
},
},
];
myClass.onHooks(hooks);
myClass.onHook({
id: 'my-hook',
event: 'before:save',
handler: async (data) => { data.validated = true; },
});
// remove all hooks
myClass.removeHook(hooks);
const hook = myClass.getHook('my-hook');
console.log(hook?.id); // 'my-hook'
console.log(hook?.event); // 'before:save'
console.log(hook?.handler); // [Function]
```
## .hook(eventName, ...args)
## .getHooks(eventName)
Run a hook event.
Get all hooks for an event. Returns an `IHook[]` array, or `undefined` if no hooks are registered for the event.
```javascript
import { Hookified } from 'hookified';
myClass.onHook({ event: 'before:myMethod2', handler: async (data) => {
data.some = 'new data';
}});
class MyClass extends Hookified {
constructor() {
super();
}
console.log(myClass.getHooks('before:myMethod2')); // [{ event: 'before:myMethod2', handler: [Function] }]
```
async myMethodWithHooks() Promise<any> {
let data = { some: 'data' };
// do something
await this.hook('before:myMethod2', data);
## .hook(eventName, ...args)
return data;
}
}
Run a hook event.
```javascript
// Inside your class method
await this.hook('before:myMethod2', data);
```
in this example we are passing multiple arguments to the hook:
You can pass multiple arguments to the hook:
```javascript
import { Hookified } from 'hookified';
// Inside your class method
await this.hook('before:myMethod2', data, data2);
class MyClass extends Hookified {
constructor() {
super();
}
// The handler receives all arguments
myClass.onHook({ event: 'before:myMethod2', handler: async (data, data2) => {
data.some = 'new data';
data2.some = 'new data2';
}});
```
async myMethodWithHooks() Promise<any> {
let data = { some: 'data' };
let data2 = { some: 'data2' };
// do something
await this.hook('before:myMethod2', data, data2);
## .hookSync(eventName, ...args)
return data;
}
}
Run a hook event synchronously. Async handlers (functions declared with `async` keyword) are silently skipped and only synchronous handlers are executed.
const myClass = new MyClass();
> **Note:** The `.hook()` method is preferred as it executes both sync and async functions. Use `.hookSync()` only when you specifically need synchronous execution.
myClass.onHook('before:myMethod2', async (data, data2) => {
data.some = 'new data';
data2.some = 'new data2';
});
```javascript
// This sync handler will execute
myClass.onHook({ event: 'before:myMethod', handler: (data) => {
data.some = 'modified';
}});
await myClass.myMethodWithHooks();
// This async handler will be silently skipped
myClass.onHook({ event: 'before:myMethod', handler: async (data) => {
data.some = 'will not run';
}});
// Inside your class method
this.hookSync('before:myMethod', data); // Only sync handler runs
```
## .callHook(eventName, ...args)
## .onHook(hook, options?)
This is an alias for `.hook(eventName, ...args)` for backwards compatibility.
Subscribe to a hook event. Takes an `IHook` object and an optional `OnHookOptions` object. Returns the stored `IHook` (with `id` assigned), or `undefined` if the hook was blocked by deprecation. The returned reference is the exact object stored internally, which is useful for later removal with `.removeHook()` or `.removeHookById()`. To register multiple hooks at once, use `.onHooks()`.
## .beforeHook(eventName, ...args)
If the hook has an `id`, it will be used as-is. If not, a UUID is auto-generated via `crypto.randomUUID()`. If a hook with the same `id` already exists on the same event, it will be **replaced in-place** (preserving its position in the array).
This is a helper function that will prepend a hook name with `before:`.
**Options (`OnHookOptions`)**:
- `useHookClone` (boolean, optional) — Per-call override for the instance-level `useHookClone` setting. When `true`, the hook object is cloned before storing. When `false`, the original reference is stored directly. When omitted, falls back to the instance-level setting.
- `position` (`"Top"` | `"Bottom"` | `number`, optional) — Controls where the hook is inserted in the handlers array. `"Top"` inserts at the beginning, `"Bottom"` appends to the end (default). A number inserts at that index, clamped to the array bounds.
```javascript
import { Hookified } from 'hookified';
// Single hook — returns the stored IHook with id
const stored = myClass.onHook({
event: 'before:myMethod2',
handler: async (data) => {
data.some = 'new data';
},
});
console.log(stored.id); // auto-generated UUID
class MyClass extends Hookified {
constructor() {
super();
}
// With a custom id
const stored2 = myClass.onHook({
id: 'my-validation',
event: 'before:save',
handler: async (data) => { data.validated = true; },
});
async myMethodWithHooks() Promise<any> {
let data = { some: 'data' };
// the event name will be `before:myMethod2`
await this.beforeHook('myMethod2', data);
// Replace hook by registering with the same id
myClass.onHook({
id: 'my-validation',
event: 'before:save',
handler: async (data) => { data.validated = true; data.extra = true; },
});
// Only one hook with id 'my-validation' exists, at the same position
return data;
}
}
```
// Remove by id
myClass.removeHookById('my-validation');
## .afterHook(eventName, ...args)
// Use the returned reference to remove the hook later
myClass.removeHook(stored);
This is a helper function that will prepend a hook name with `after:`.
// Override useHookClone per-call — store original reference even though instance default is true
const hook = { event: 'before:save', handler: async (data) => {} };
myClass.onHook(hook, { useHookClone: false });
console.log(myClass.getHooks('before:save')[0] === hook); // true
```javascript
import { Hookified } from 'hookified';
// Insert at the top of the handlers array
myClass.onHook({ event: 'before:save', handler: async (data) => {} }, { position: 'Top' });
class MyClass extends Hookified {
constructor() {
super();
}
async myMethodWithHooks() Promise<any> {
let data = { some: 'data' };
// the event name will be `after:myMethod2`
await this.afterHook('myMethod2', data);
return data;
}
}
// Insert at a specific index
myClass.onHook({ event: 'before:save', handler: async (data) => {} }, { position: 1 });
```
## .hookSync(eventName, ...args)
## .onHooks(Array, options?)
Run a hook event synchronously. Async handlers (functions declared with `async` keyword) are silently skipped and only synchronous handlers are executed.
Subscribe to multiple hook events at once. Takes an array of `IHook` objects and an optional `OnHookOptions` object that is applied to each hook.
> **Note:** The `.hook()` method is preferred as it executes both sync and async functions. Use `.hookSync()` only when you specifically need synchronous execution.
```javascript
import { Hookified } from 'hookified';
const hooks = [
{
event: 'before:myMethodWithHooks',
handler: async (data) => {
data.some = 'new data1';
},
},
{
event: 'after:myMethodWithHooks',
handler: async (data) => {
data.some = 'new data2';
},
},
];
myClass.onHooks(hooks);
class MyClass extends Hookified {
constructor() {
super();
}
// With options — insert all hooks at the top
myClass.onHooks(hooks, { position: 'Top' });
myMethodWithSyncHooks() {
let data = { some: 'data' };
// Only synchronous handlers will execute
this.hookSync('before:myMethod', data);
// With options — skip cloning for all hooks in this batch
myClass.onHooks(hooks, { useHookClone: false });
```
return data;
}
}
## .onceHook(hook)
const myClass = new MyClass();
Subscribe to a hook event once. Takes an `IHook` object with `event` and `handler` properties. After the handler is called once, it is automatically removed.
// This sync handler will execute
myClass.onHook('before:myMethod', (data) => {
data.some = 'modified';
});
```javascript
myClass.onceHook({ event: 'before:myMethod2', handler: async (data) => {
data.some = 'new data';
}});
// This async handler will be silently skipped
myClass.onHook('before:myMethod', async (data) => {
data.some = 'will not run';
});
myClass.myMethodWithSyncHooks(); // Only sync handler runs
await myClass.hook('before:myMethod2', data); // handler runs once then is removed
console.log(myClass.hooks.size); // 0
```
## .hooks
## .prependHook(hook, options?)
Get all hooks.
Subscribe to a hook event before all other hooks. Takes an `IHook` object with `event` and `handler` properties. Returns the stored `IHook` (with generated `id`), or `undefined` if blocked by deprecation. Equivalent to calling `onHook(hook, { position: "Top" })`.
An optional `PrependHookOptions` object can be passed with:
- `useHookClone` (boolean) — per-call override for hook cloning behavior
```javascript
import { Hookified } from 'hookified';
myClass.onHook({ event: 'before:myMethod2', handler: async (data) => {
data.some = 'new data';
}});
myClass.prependHook({ event: 'before:myMethod2', handler: async (data) => {
data.some = 'will run before new data';
}});
```
class MyClass extends Hookified {
constructor() {
super();
}
## .prependOnceHook(hook, options?)
async myMethodWithHooks() Promise<any> {
let data = { some: 'data' };
// do something
await this.hook('before:myMethod2', data);
Subscribe to a hook event before all other hooks. Takes an `IHook` object with `event` and `handler` properties. After the handler is called once, it is automatically removed. Returns the stored `IHook` (with generated `id`), or `undefined` if blocked by deprecation.
return data;
}
}
An optional `PrependHookOptions` object can be passed with:
- `useHookClone` (boolean) — per-call override for hook cloning behavior
const myClass = new MyClass();
myClass.onHook('before:myMethod2', async (data) => {
```javascript
myClass.onHook({ event: 'before:myMethod2', handler: async (data) => {
data.some = 'new data';
});
console.log(myClass.hooks);
}});
myClass.prependOnceHook({ event: 'before:myMethod2', handler: async (data) => {
data.some = 'will run before new data';
}});
```
## .getHooks(eventName)
## .removeEventHooks(eventName)
Get all hooks for an event.
Removes all hooks for a specific event and returns the removed hooks as an `IHook[]` array. Returns an empty array if no hooks are registered for the event.
```javascript
import { Hookified } from 'hookified';
myClass.onHook({ event: 'before:myMethod2', handler: async (data) => {
data.some = 'new data';
}});
myClass.onHook({ event: 'before:myMethod2', handler: async (data) => {
data.some = 'more data';
}});
class MyClass extends Hookified {
constructor() {
super();
}
// Remove all hooks for a specific event
const removed = myClass.removeEventHooks('before:myMethod2');
console.log(removed.length); // 2
```
async myMethodWithHooks() Promise<any> {
let data = { some: 'data' };
// do something
await this.hook('before:myMethod2', data);
## .removeHook(hook)
return data;
}
}
Unsubscribe a handler from a hook event. Takes an `IHook` object with `event` and `handler` properties. Returns the removed hook as an `IHook` object, or `undefined` if the handler was not found.
const myClass = new MyClass();
myClass.onHook('before:myMethod2', async (data) => {
```javascript
const handler = async (data) => {
data.some = 'new data';
});
};
console.log(myClass.getHooks('before:myMethod2'));
myClass.onHook({ event: 'before:myMethod2', handler });
const removed = myClass.removeHook({ event: 'before:myMethod2', handler });
console.log(removed); // { event: 'before:myMethod2', handler: [Function] }
```
## .clearHooks(eventName)
## .removeHookById(id)
Clear all hooks for an event.
Remove one or more hooks by `id`, searching across all events. Accepts a single `string` or an array of `string` ids.
- **Single id**: Returns the removed `IHook`, or `undefined` if not found.
- **Array of ids**: Returns an `IHook[]` array of the hooks that were successfully removed.
When the last hook for an event is removed, the event key is cleaned up.
```javascript
import { Hookified } from 'hookified';
const myClass = new MyClass();
class MyClass extends Hookified {
constructor() {
super();
}
myClass.onHook({ id: 'hook-a', event: 'before:save', handler: async () => {} });
myClass.onHook({ id: 'hook-b', event: 'after:save', handler: async () => {} });
myClass.onHook({ id: 'hook-c', event: 'before:save', handler: async () => {} });
async myMethodWithHooks() Promise<any> {
let data = { some: 'data' };
// do something
await this.hook('before:myMethod2', data);
// Remove a single hook by id
const removed = myClass.removeHookById('hook-a');
console.log(removed?.id); // 'hook-a'
return data;
}
}
// Remove multiple hooks by ids
const removedMany = myClass.removeHookById(['hook-b', 'hook-c']);
console.log(removedMany.length); // 2
```
const myClass = new MyClass();
## .removeHooks(Array)
myClass.onHook('before:myMethod2', async (data) => {
data.some = 'new data';
});
Unsubscribe from multiple hooks. Returns an array of the hooks that were successfully removed.
myClass.clearHooks('before:myMethod2');
```javascript
const hooks = [
{ event: 'before:save', handler: async (data) => { data.some = 'new data1'; } },
{ event: 'after:save', handler: async (data) => { data.some = 'new data2'; } },
];
myClass.onHooks(hooks);
const removed = myClass.removeHooks(hooks);
console.log(removed.length); // 2
```

@@ -950,21 +952,26 @@

> All examples below assume the following setup unless otherwise noted:
> ```javascript
> import { Hookified } from 'hookified';
> class MyClass extends Hookified {
> constructor(options) { super(options); }
> }
> const myClass = new MyClass();
> ```
## .throwOnEmitError
If set to true, errors emitted as `error` will be thrown if there are no listeners. If set to false, errors will be only emitted.
If set to true, errors emitted as `error` will always be thrown, even if there are listeners. If set to false (default), errors will only be emitted to listeners.
```javascript
import { Hookified } from 'hookified';
const myClass = new MyClass({ throwOnEmitError: true });
class MyClass extends Hookified {
constructor() {
super();
}
myClass.on('error', (err) => {
console.log('listener received:', err.message);
});
async myMethodWithHooks() Promise<any> {
let data = { some: 'data' };
// do something
await this.hook('before:myMethod2', data);
return data;
}
try {
myClass.emit('error', new Error('This will throw despite having a listener'));
} catch (error) {
console.log(error.message); // This will throw despite having a listener
}

@@ -975,17 +982,9 @@ ```

If set to true, errors will be thrown when emitting an `error` event with no listeners. This follows the standard Node.js EventEmitter behavior. Default is false. In version 2, this will be set to true by default.
If set to true, errors will be thrown when emitting an `error` event with no listeners. This follows the standard Node.js EventEmitter behavior. Default is `true`.
```javascript
import { Hookified } from 'hookified';
const myClass = new MyClass({ throwOnEmptyListeners: true });
class MyClass extends Hookified {
constructor() {
super({ throwOnEmptyListeners: true });
}
}
console.log(myClass.throwOnEmptyListeners); // true (default)
const myClass = new MyClass();
console.log(myClass.throwOnEmptyListeners); // true
// This will throw because there are no error listeners

@@ -1021,16 +1020,2 @@ try {

```javascript
import { Hookified } from 'hookified';
class MyClass extends Hookified {
constructor() {
super();
}
async myMethodEmittingEvent() {
this.emit('message', 'Hello World');
}
}
const myClass = new MyClass();
myClass.on('message', (message) => {

@@ -1046,22 +1031,8 @@ console.log(message);

```javascript
import { Hookified } from 'hookified';
class MyClass extends Hookified {
constructor() {
super();
}
async myMethodEmittingEvent() {
this.emit('message', 'Hello World');
}
}
const myClass = new MyClass();
myClass.on('message', (message) => {
const handler = (message) => {
console.log(message);
});
};
myClass.off('message', (message) => {
console.log(message);
});
myClass.on('message', handler);
myClass.off('message', handler);
```

@@ -1074,13 +1045,3 @@

```javascript
import { Hookified } from 'hookified';
class MyClass extends Hookified {
constructor() {
super();
}
async myMethodEmittingEvent() {
this.emit('message', 'Hello World');
}
}
myClass.emit('message', 'Hello World');
```

@@ -1093,16 +1054,2 @@

```javascript
import { Hookified } from 'hookified';
class MyClass extends Hookified {
constructor() {
super();
}
async myMethodEmittingEvent() {
this.emit('message', 'Hello World');
}
}
const myClass = new MyClass();
myClass.on('message', (message) => {

@@ -1120,16 +1067,2 @@ console.log(message);

```javascript
import { Hookified } from 'hookified';
class MyClass extends Hookified {
constructor() {
super();
}
async myMethodEmittingEvent() {
this.emit('message', 'Hello World');
}
}
const myClass = new MyClass();
myClass.on('message', (message) => {

@@ -1144,19 +1077,5 @@ console.log(message);

Set the maximum number of listeners and will truncate if there are already too many.
Set the maximum number of listeners for a single event. Default is `0` (unlimited). Negative values are treated as `0`. Setting to `0` disables the limit and the warning. When the limit is exceeded, a `MaxListenersExceededWarning` is emitted via `console.warn` but the listener is still added. This matches standard Node.js EventEmitter behavior.
```javascript
import { Hookified } from 'hookified';
class MyClass extends Hookified {
constructor() {
super();
}
async myMethodEmittingEvent() {
this.emit('message', 'Hello World');
}
}
const myClass = new MyClass();
myClass.setMaxListeners(1);

@@ -1170,5 +1089,5 @@

console.log(message);
}); // this will not be added and console warning
}); // warning emitted but listener is still added
console.log(myClass.listenerCount('message')); // 1
console.log(myClass.listenerCount('message')); // 2
```

@@ -1181,12 +1100,2 @@

```javascript
import { Hookified } from 'hookified';
class MyClass extends Hookified {
constructor() {
super();
}
}
const myClass = new MyClass();
myClass.once('message', (message) => {

@@ -1196,5 +1105,4 @@ console.log(message);

myClass.emit('message', 'Hello World');
myClass.emit('message', 'Hello World'); // this will not be called
myClass.emit('message', 'Hello World'); // handler runs
myClass.emit('message', 'Hello World'); // handler does not run
```

@@ -1207,12 +1115,2 @@

```javascript
import { Hookified } from 'hookified';
class MyClass extends Hookified {
constructor() {
super();
}
}
const myClass = new MyClass();
myClass.prependListener('message', (message) => {

@@ -1228,12 +1126,2 @@ console.log(message);

```javascript
import { Hookified } from 'hookified';
class MyClass extends Hookified {
constructor() {
super();
}
}
const myClass = new MyClass();
myClass.prependOnceListener('message', (message) => {

@@ -1251,12 +1139,2 @@ console.log(message);

```javascript
import { Hookified } from 'hookified';
class MyClass extends Hookified {
constructor() {
super();
}
}
const myClass = new MyClass();
myClass.on('message', (message) => {

@@ -1266,3 +1144,3 @@ console.log(message);

console.log(myClass.eventNames());
console.log(myClass.eventNames()); // ['message']
```

@@ -1272,15 +1150,5 @@

Get the count of listeners for an event or all events if evenName not provided.
Get the count of listeners for an event or all events if eventName not provided.
```javascript
import { Hookified } from 'hookified';
class MyClass extends Hookified {
constructor() {
super();
}
}
const myClass = new MyClass();
myClass.on('message', (message) => {

@@ -1295,15 +1163,5 @@ console.log(message);

Get all listeners for an event or all events if evenName not provided.
Get all listeners for an event or all events if eventName not provided.
```javascript
import { Hookified } from 'hookified';
class MyClass extends Hookified {
constructor() {
super();
}
}
const myClass = new MyClass();
myClass.on('message', (message) => {

@@ -1318,16 +1176,16 @@ console.log(message);

Hookified integrates logging directly into the event system. When a logger is configured, all emitted events are automatically logged to the appropriate log level based on the event name.
Hookified integrates logging directly into the event system. When an `eventLogger` is configured, all emitted events are automatically logged to the appropriate log level based on the event name.
## How It Works
When you emit an event, Hookified automatically sends the event data to the configured logger using the appropriate log method:
When you emit an event, Hookified automatically sends the event data to the configured `eventLogger` using the appropriate log method:
| Event Name | Logger Method |
|------------|---------------|
| `error` | `logger.error()` |
| `warn` | `logger.warn()` |
| `debug` | `logger.debug()` |
| `trace` | `logger.trace()` |
| `fatal` | `logger.fatal()` |
| Any other | `logger.info()` |
| `error` | `eventLogger.error()` |
| `warn` | `eventLogger.warn()` |
| `debug` | `eventLogger.debug()` |
| `trace` | `eventLogger.trace()` |
| `fatal` | `eventLogger.fatal()` |
| Any other | `eventLogger.info()` |

@@ -1363,3 +1221,3 @@ The logger receives two arguments:

constructor() {
super({ logger });
super({ eventLogger: logger });
}

@@ -1390,10 +1248,10 @@

You can also set or change the logger after instantiation:
You can also set or change the eventLogger after instantiation:
```javascript
const service = new MyService();
service.logger = pino({ level: 'debug' });
service.eventLogger = pino({ level: 'debug' });
// Or remove the logger
service.logger = undefined;
// Or remove the eventLogger
service.eventLogger = undefined;
```

@@ -1407,6 +1265,6 @@

| name | summary | ops/sec | time/op | margin | samples |
|-----------------------|:---------:|----------:|----------:|:--------:|----------:|
| Hookified (v1.15.1) | 🥇 | 5M | 199ns | ±0.01% | 5M |
| Hookable (v6.0.1) | -62% | 2M | 578ns | ±0.01% | 2M |
| name | summary | ops/sec | time/op | margin | samples |
|----------------------|:---------:|----------:|----------:|:--------:|----------:|
| Hookified (v2.0.0) | 🥇 | 5M | 214ns | ±0.01% | 5M |
| Hookable (v6.0.1) | -59% | 2M | 567ns | ±0.01% | 2M |

@@ -1419,27 +1277,424 @@ ## Emits

|---------------------------|:---------:|----------:|----------:|:--------:|----------:|
| EventEmitter3 (v5.0.4) | 🥇 | 14M | 85ns | ±0.02% | 12M |
| Hookified (v1.15.1) | -6.9% | 13M | 88ns | ±0.02% | 11M |
| EventEmitter (v24.11.1) | -9.5% | 13M | 89ns | ±0.02% | 11M |
| Emittery (v1.2.0) | -92% | 1M | 993ns | ±0.01% | 1M |
| EventEmitter3 (v5.0.4) | 🥇 | 14M | 82ns | ±0.02% | 12M |
| Hookified (v2.0.0) | -6.9% | 13M | 97ns | ±0.02% | 10M |
| EventEmitter (v24.11.1) | -7.2% | 13M | 83ns | ±0.02% | 12M |
| Emittery (v1.2.0) | -92% | 1M | 1µs | ±0.01% | 979K ||
_Note: the `EventEmitter` version is Nodejs versioning._
# How to Contribute
# Migrating from v1 to v2
Hookified is written in TypeScript and tests are written in `vitest`. To run the tests, use the following command:
## Quick Guide
To setup the environment and run the tests:
v2 overhauls hook storage to use `IHook` objects instead of raw functions. This enables hook IDs, ordering via position, cloning control, and new hook types like `WaterfallHook`. The main change most users will notice is that `onHook` now takes an `IHook` object instead of positional arguments:
```bash
pnpm i && pnpm test
```typescript
// v1 — positional arguments
hookified.onHook('before:save', async (data) => {});
// v2 — IHook object (or use addHook for positional args)
hookified.onHook({ event: 'before:save', handler: async (data) => {} });
hookified.addHook('before:save', async (data) => {}); // still works
```
Note that we are using `pnpm` as our package manager. If you don't have it installed, you can install it globally with:
**Other common changes:**
```bash
npm install -g pnpm
| v1 | v2 |
|---|---|
| `throwHookErrors` | `throwOnHookError` |
| `logger` | `eventLogger` |
| `onHookEntry(hook)` | `onHook(hook)` |
| `HookEntry` type | `IHook` interface |
| `Hook` type (fn) | `HookFn` type |
| `getHooks()` returns `HookFn[]` | `getHooks()` returns `IHook[]` |
| `removeHook(event, handler)` | `removeHook({ event, handler })` |
See below for full details on each change.
**[Breaking Changes](#breaking-changes)**
- [`throwHookErrors` removed — use `throwOnHookError` instead](#throwhookerrors-removed--use-throwonhookerror-instead)
- [`throwOnEmptyListeners` now defaults to `true`](#throwonemptylisteners-now-defaults-to-true)
- [`logger` renamed to `eventLogger`](#logger-renamed-to-eventlogger)
- [`maxListeners` default changed from `100` to `0` (unlimited) and no longer truncates](#maxlisteners-default-changed-from-100-to-0-unlimited-and-no-longer-truncates)
- [`onHookEntry` removed — use `onHook` instead](#onhookentry-removed--use-onhook-instead)
- [`onHook` signature changed](#onhook-signature-changed)
- [`HookEntry` type and `Hook` type removed](#hookentry-type-and-hook-type-removed)
- [`removeHook` and `removeHooks` now return removed hooks](#removehook-and-removehooks-now-return-removed-hooks)
- [`removeHook`, `removeHooks`, and `getHooks` no longer check for deprecated hooks](#removehook-removehooks-and-gethooks-no-longer-check-for-deprecated-hooks)
- [Internal hook storage now uses `IHook` objects](#internal-hook-storage-now-uses-ihook-objects)
- [`onceHook`, `prependHook`, `prependOnceHook`, and `removeHook` now take `IHook`](#oncehook-prependhook-prependoncehook-and-removehook-now-take-ihook)
- [`onHook` now returns the stored hook](#onhook-now-returns-the-stored-hook)
**[New Features](#new-features)**
- [standard `Hook` class now available](#standard-hook)
- [`WaterfallHook` class for sequential data transformation pipelines](#waterfallhook-class)
- [`useHookClone` option](#usehookclone-option)
- [`onHook` now accepts `OnHookOptions`](#onhook-now-accepts-onhookoptions)
- [`IHook` now has an `id` property](#ihook-now-has-an-id-property)
- [`removeEventHooks` method](#removeeventhooks-method)
## Breaking Changes
| Change | Summary |
|---|---|
| `throwHookErrors` | Renamed to `throwOnHookError` |
| `throwOnEmptyListeners` | Default changed from `false` to `true` |
| `logger` | Renamed to `eventLogger` |
| `maxListeners` | Default changed from `100` to `0` (unlimited), no longer truncates |
| `onHookEntry` | Removed — use `onHook` instead |
| `onHook` signature | Now takes `IHook` object instead of `(event, handler)` |
| `HookEntry` / `Hook` types | Replaced with `IHook` / `HookFn` |
| `removeHook` / `removeHooks` | Now return removed hooks; no longer check deprecated status |
| Internal hook storage | Uses `IHook` objects instead of raw functions |
| `onceHook`, `prependHook`, etc. | Now take `IHook` instead of `(event, handler)` |
| `onHook` return | Now returns stored `IHook` (was `void`) |
### `throwHookErrors` removed — use `throwOnHookError` instead
The deprecated `throwHookErrors` option and property has been removed. Use `throwOnHookError` instead.
**Before (v1):**
```javascript
super({ throwHookErrors: true });
myClass.throwHookErrors = false;
```
To contribute follow the [Contributing Guidelines](CONTRIBUTING.md) and [Code of Conduct](CODE_OF_CONDUCT.md).
**After (v2):**
```javascript
super({ throwOnHookError: true });
myClass.throwOnHookError = false;
```
### `throwOnEmptyListeners` now defaults to `true`
The `throwOnEmptyListeners` option now defaults to `true`, matching standard Node.js EventEmitter behavior. Previously it defaulted to `false`. If you emit an `error` event with no listeners registered, an error will now be thrown by default.
**Before (v1):**
```javascript
const myClass = new MyClass(); // throwOnEmptyListeners defaults to false
myClass.emit('error', new Error('No throw')); // silently ignored
```
**After (v2):**
```javascript
const myClass = new MyClass(); // throwOnEmptyListeners defaults to true
myClass.emit('error', new Error('This will throw')); // throws!
// To restore v1 behavior:
const myClass2 = new MyClass({ throwOnEmptyListeners: false });
```
### `logger` renamed to `eventLogger`
The `logger` option and property has been renamed to `eventLogger` to avoid conflicts with other logger properties in your classes.
**Before (v1):**
```javascript
super({ logger });
myClass.logger = pino({ level: 'debug' });
```
**After (v2):**
```javascript
super({ eventLogger: logger });
myClass.eventLogger = pino({ level: 'debug' });
```
### `maxListeners` default changed from `100` to `0` (unlimited) and no longer truncates
The default maximum number of listeners has changed from `100` to `0` (unlimited). The `MaxListenersExceededWarning` will no longer be emitted unless you explicitly set a limit via `setMaxListeners()`. Additionally, `setMaxListeners()` no longer truncates existing listeners — it only sets the warning threshold, matching standard Node.js EventEmitter behavior.
**Before (v1):**
```javascript
const myClass = new MyClass(); // maxListeners defaults to 100
// Warning emitted after adding 100+ listeners to the same event
// setMaxListeners() would truncate existing listeners exceeding the limit
```
**After (v2):**
```javascript
const myClass = new MyClass(); // maxListeners defaults to 0 (unlimited)
// No warning — unlimited listeners allowed
// setMaxListeners() only sets warning threshold, never removes listeners
// To restore v1 warning behavior:
myClass.setMaxListeners(100);
```
### `onHookEntry` removed — use `onHook` instead
The `onHookEntry` method has been removed. Use `onHook` which now accepts an `IHook` object (or array of `IHook`) directly.
**Before (v1):**
```typescript
hookified.onHookEntry({ event: 'before:save', handler: async (data) => {} });
```
**After (v2):**
```typescript
hookified.onHook({ event: 'before:save', handler: async (data) => {} });
```
### `onHook` signature changed
`onHook` no longer accepts positional `(event, handler)` arguments. It now takes a single `IHook` object or `Hook` class instance. Use `addHook(event, handler)` if you prefer positional arguments. Use `onHooks()` for bulk registration.
**Before (v1):**
```typescript
hookified.onHook('before:save', async (data) => {});
```
**After (v2):**
```typescript
// Using IHook object
hookified.onHook({ event: 'before:save', handler: async (data) => {} });
// For multiple hooks, use onHooks
hookified.onHooks([
{ event: 'before:save', handler: async (data) => {} },
{ event: 'after:save', handler: async (data) => {} },
]);
// Or use addHook for positional args
hookified.addHook('before:save', async (data) => {});
```
### `HookEntry` type and `Hook` type removed
The `HookEntry` type has been removed and replaced with the `IHook` interface. The `Hook` type (function type) has been renamed to `HookFn`.
**Before (v1):**
```typescript
import type { HookEntry, Hook } from 'hookified';
const hook: HookEntry = { event: 'before:save', handler: async () => {} };
const myHook: Hook = async (data) => {};
```
**After (v2):**
```typescript
import type { IHook, HookFn } from 'hookified';
const hook: IHook = { event: 'before:save', handler: async () => {} };
const myHook: HookFn = async (data) => {};
```
### `removeHook` and `removeHooks` now return removed hooks
`removeHook` now returns the removed hook as an `IHook` object (or `undefined` if not found). `removeHooks` now returns an `IHook[]` array of the hooks that were successfully removed. Previously both returned `void`.
**Before (v1):**
```typescript
hookified.removeHook('before:save', handler); // void
hookified.removeHooks(hooks); // void
```
**After (v2):**
```typescript
const removed = hookified.removeHook({ event: 'before:save', handler }); // IHook | undefined
const removedHooks = hookified.removeHooks(hooks); // IHook[]
```
### `removeHook`, `removeHooks`, and `getHooks` no longer check for deprecated hooks
Previously, `removeHook`, `removeHooks`, and `getHooks` would skip their operation and emit a deprecation warning when called with a deprecated hook name and `allowDeprecated` was `false`. This made it impossible to clean up or inspect deprecated hooks. These methods now always operate regardless of deprecation status.
### Internal hook storage now uses `IHook` objects
The internal `_hooks` map now stores full `IHook` objects (`Map<string, IHook[]>`) instead of raw handler functions (`Map<string, HookFn[]>`). This means `.hooks` returns `Map<string, IHook[]>` and `.getHooks()` returns `IHook[] | undefined`.
**Before (v1):**
```typescript
const hooks = myClass.getHooks('before:save'); // HookFn[]
hooks[0](data); // direct function call
```
**After (v2):**
```typescript
const hooks = myClass.getHooks('before:save'); // IHook[]
hooks[0].handler(data); // access .handler property
hooks[0].event; // 'before:save'
```
### `onceHook`, `prependHook`, `prependOnceHook`, and `removeHook` now take `IHook`
These methods now accept an `IHook` object instead of separate `(event, handler)` arguments.
**Before (v1):**
```typescript
hookified.onceHook('before:save', async (data) => {});
hookified.prependHook('before:save', async (data) => {});
hookified.prependOnceHook('before:save', async (data) => {});
hookified.removeHook('before:save', handler);
```
**After (v2):**
```typescript
hookified.onceHook({ event: 'before:save', handler: async (data) => {} });
hookified.prependHook({ event: 'before:save', handler: async (data) => {} });
hookified.prependOnceHook({ event: 'before:save', handler: async (data) => {} });
hookified.removeHook({ event: 'before:save', handler });
```
### `onHook` now returns the stored hook
`onHook` now returns the stored `IHook` object (or `undefined` if blocked by deprecation). Previously it returned `void`. The returned reference is the exact object stored internally, making it easy to later remove with `removeHook()`.
**Before (v1):**
```typescript
hookified.onHook({ event: 'before:save', handler }); // void
```
**After (v2):**
```typescript
const stored = hookified.onHook({ event: 'before:save', handler }); // IHook | undefined
hookified.removeHook(stored); // exact reference match
```
## New Features
### `Hook` class
A new `Hook` class is available for creating hook entries. It implements the `IHook` interface and can be used anywhere `IHook` is accepted.
```typescript
import { Hook } from 'hookified';
const hook = new Hook('before:save', async (data) => {
data.validated = true;
});
myClass.onHook(hook);
```
### `WaterfallHook` class
A new `WaterfallHook` class is available for creating sequential data transformation pipelines. It implements the `IHook` interface and integrates directly with `Hookified.onHook()`. Each hook in the chain receives a `WaterfallHookContext` with `initialArgs` (the original arguments) and `results` (an array of `{ hook, result }` entries from all previous hooks).
```typescript
import { Hookified, WaterfallHook } from 'hookified';
class MyClass extends Hookified {
constructor() { super(); }
}
const myClass = new MyClass();
const wh = new WaterfallHook('save', ({ results }) => {
const data = results[results.length - 1].result;
console.log('Saved:', data);
});
wh.addHook(({ initialArgs }) => {
return { ...initialArgs, validated: true };
});
wh.addHook(({ results }) => {
return { ...results[results.length - 1].result, timestamp: Date.now() };
});
myClass.onHook(wh);
await myClass.hook('save', { name: 'test' });
// Saved: { name: 'test', validated: true, timestamp: ... }
```
See the [Waterfall Hook](#waterfallhook) section for full documentation.
### `useHookClone` option
A new `useHookClone` option (default `true`) controls whether hook objects are shallow-cloned before storing. When enabled, external mutation of a registered hook object won't affect the internal state. Set to `false` to store the original reference for performance or when you need reference equality.
```typescript
class MyClass extends Hookified {
constructor() { super({ useHookClone: false }); }
}
```
### `onHook` now accepts `OnHookOptions`
`onHook` now accepts an optional second parameter of type `OnHookOptions`. This allows you to override the instance-level `useHookClone` setting and control hook positioning on a per-call basis.
```typescript
// Override useHookClone for this specific call
hookified.onHook({ event: 'before:save', handler }, { useHookClone: false });
// Insert at the top of the handlers array instead of the end
hookified.onHook({ event: 'before:save', handler }, { position: 'Top' });
// Insert at a specific index
hookified.onHook({ event: 'before:save', handler }, { position: 1 });
```
### `IHook` now has an `id` property
Every hook now has an optional `id` property. If not provided, a UUID is auto-generated via `crypto.randomUUID()`. The `id` enables easier lookups and removal via the new `getHook(id)` and `removeHookById(id)` methods, which search across all events.
Registering a hook with the same `id` on the same event replaces the existing hook in-place (preserving its position).
```typescript
// With custom id
const stored = hookified.onHook({
id: 'my-validation',
event: 'before:save',
handler: async (data) => { data.validated = true; },
});
// Without id — auto-generated
const stored2 = hookified.onHook({
event: 'before:save',
handler: async (data) => {},
});
console.log(stored2.id); // e.g. '550e8400-e29b-41d4-a716-446655440000'
// Look up by id (searches all events)
const hook = hookified.getHook('my-validation');
// Remove by id (searches all events)
hookified.removeHookById('my-validation');
// Remove multiple by ids
hookified.removeHookById(['hook-a', 'hook-b']);
```
The `Hook` class also accepts an optional `id` parameter:
```typescript
const hook = new Hook('before:save', handler, 'my-custom-id');
```
### `removeEventHooks` method
A new `removeEventHooks(event)` method removes all hooks for a specific event and returns the removed hooks as an `IHook[]` array.
```typescript
const removed = hookified.removeEventHooks('before:save');
console.log(removed.length); // number of hooks removed
```
# How to Contribute
Hookified is written in TypeScript and tests are written with `vitest`. To setup the environment and run the tests:
```bash

@@ -1460,5 +1715,1 @@ pnpm i && pnpm test

[MIT & © Jared Wray](LICENSE)