Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@nasriya/atomix

Package Overview
Dependencies
Maintainers
1
Versions
27
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@nasriya/atomix - npm Package Compare versions

Comparing version
1.0.23
to
1.0.24
+23
-1
dist/@types/tools/events/docs.d.ts

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

import { Prettify } from "../../docs/docs";
export interface EventData {

@@ -42,2 +43,23 @@ name: string;

}
export type EventHandler = (...args: any) => any | Promise<any>;
export type EventHandler = (...args: any[]) => any | Promise<any>;
export type GlobalEventHandler<TMap> = (eventName: EventName<TMap>, ...args: any) => any | Promise<any>;
export type EventName<TMap> = Extract<keyof TMap, string>;
export type IsNever<T> = [T] extends [never] ? true : false;
export type ProcessorEventsConfigs = {
/**
* The name of the emitted event.
*/
emittedBy: string;
/**
* The internal data of the event.
*/
data: EventData;
/**
* Whether the events are global or not.
* @default false
*/
isGlobal?: boolean;
};
export type ProcessorMetaData = Prettify<{
triggeredBy: ProcessorEventsConfigs['emittedBy'];
} & Pick<ProcessorEventsConfigs, 'isGlobal'>>;
+142
-60

@@ -1,11 +0,16 @@

import { EventHandler, AddHandlerOptions } from './docs';
import type { EventHandler, AddHandlerOptions, EventName, GlobalEventHandler } from './docs';
/**
* A flexible and lightweight event emitter class that supports advanced features like:
* - `beforeAll` and `afterAll` handler types
* - One-time (`once`) handlers
* - Handler limits with overflow detection
* - Custom max-handler overflow behavior
* A flexible and lightweight event emitter class with full TypeScript support.
*
* This implementation allows both standard event handlers and lifecycle-style hooks around them.
* Features:
* - Strongly typed event names and handler arguments using generics (`EventsMap`).
* - Global `'*'` handlers that receive the event name as the first argument, followed by the event's arguments.
* - `beforeAll` and `afterAll` handler types for lifecycle-style hooks.
* - One-time handlers via `on` with `{ once: true }` option (removed automatically after first execution).
* - Handler limits with overflow detection via `maxTotalHandlers` and custom overflow behavior.
* - Backward-compatible `maxHandlers` property (deprecated in favor of `maxTotalHandlers`).
*
* This implementation allows both standard event handlers and advanced lifecycle hooks,
* while supporting both typed and untyped usage.
*
* @example

@@ -27,2 +32,5 @@ * ```ts

*
* // Global handler receives the event name first
* emitter.on(`'*'`, (event, ...args) => console.log(`Global: ${event}`, args));
*
* // Emit the event

@@ -34,16 +42,23 @@ * await emitter.emit('data', 42);

* ```ts
* // Custom behavior when handler count exceeds limit
* emitter.maxHandlers = 2;
* emitter.onMaxHandlers((eventName) => {
* throw new Error(`Handler overflow on ${eventName}`);
* });
* const emitter = new atomix.tools.EventEmitter();
*
* // Option 1: Throw an Error immediately when the limit is exceeded
* emitter.maxTotalHandlers = 2;
* emitter.onMaxHandlers(new Error('Handler limit exceeded'));
*
* emitter.on('load', () => {});
* emitter.on('load', () => {});
* emitter.on('load', () => {}); // throws
* emitter.on('load', () => {}); // throws immediately
*
* // Option 2: Use a custom function to handle overflows (debounced)
* emitter.onMaxHandlers((eventName) => {
* console.warn(`Handler overflow on event: ${eventName}`);
* });
* ```
*
* @template EventsMap - Optional type map for event names and handler signatures. Defaults to `{}` for untyped usage.
*
* @since v1.0.8
*/
export declare class EventEmitter {
export declare class EventEmitter<EventsMap extends Record<string, EventHandler> = {}> {
#private;

@@ -53,2 +68,13 @@ /**

*
* This method supports both **typed** and **untyped** emitters:
*
* - In a **typed emitter** (`EventEmitter<{ load: () => void; data: (value: number) => void }>`),
* the `eventName` parameter is restricted to the keys of the provided map,
* and the `args` are automatically typed according to the event's parameters.
*
* - In an **untyped emitter** (`EventEmitter` with default `{}`), `eventName` can be any string,
* and `args` are of type `any[]`.
*
* Global (`'*'`) handlers are triggered on every emit and receive the actual event name as the first argument.
*
* Handlers are invoked in the following order for each `emit` call:

@@ -59,16 +85,11 @@ * 1. The `beforeAll` handler (if set) runs before all `normal` handlers.

*
* `beforeAll` and `afterAll` are special singleton handlers — only one of each
* may be set per event name. They are not removed automatically and are executed
* on every `emit` call.
* `beforeAll` and `afterAll` are singleton handlers — only one of each may be set per event name.
* They are not removed automatically and are executed on every `emit` call.
*
* `normal` handlers may be marked as `once`, in which case they will be removed
* after their first invocation.
* `normal` handlers may be marked as `once`, in which case they are removed after their first invocation.
*
* Global (`'*'`) handlers are triggered on every emit and receive the actual event
* name as their first argument.
* @template E - The event name type; inferred automatically.
* @param eventName - The name of the event to emit, or `'*'` for global handlers. Must be a non-empty string.
* @param args - Arguments to pass to all applicable handlers. Automatically typed for typed emitters.
*
* @async
* @param eventName - The name of the event to emit. Must be a non-empty string.
* @param args - Arguments to pass to all applicable handler functions.
*
* @throws {TypeError} If the event name is not a string.

@@ -78,46 +99,69 @@ * @throws {RangeError} If the event name is an empty string.

* @example
* ```ts
* // Untyped emitter
* const emitter = new EventEmitter();
* emitter.on('*', (event, ...args) => console.log(`Global: ${event}`, args));
* await emitter.emit('load', true);
*
* emitter.on('data', (value) => console.log('Normal:', value));
* emitter.on('data', () => console.log('Before All'), { type: 'beforeAll' });
* emitter.on('data', () => console.log('After All'), { type: 'afterAll' });
*
* await emitter.emit('data', 123);
* // Output:
* // Before All
* // Normal: 123
* // After All
* ```
*
* @example
* ```ts
* emitter.on('*', (event, ...args) => {
* console.log(`Global handler: ${event}`, args);
* });
* // Typed emitter
* type MyEvents = { load: (ok: boolean) => void; data: (value: number) => void };
* const typedEmitter = new EventEmitter<MyEvents>();
* typedEmitter.on('data', (value) => console.log('Data:', value));
* typedEmitter.on('*', (event, ...args) => console.log(`Global: ${event}`, args));
* await typedEmitter.emit('data', 42); // value typed as number
*
* await emitter.emit('load', true);
* // Output:
* // Global handler: load [true]
* ```
*
* @since v1.0.8
*/
emit(eventName: string, ...args: any): Promise<void>;
emit<E extends EventName<EventsMap>>(eventName: [E] extends [never] ? string : E, ...args: [E] extends [never] ? any[] : Parameters<EventsMap[E]>): Promise<void>;
/**
* Adds a handler to an event.
*
* @param eventName - The name of the event to add the handler to.
* @param handler - The handler function to add.
* @param options - (Optional) Additional configuration options for the handler. If not provided, defaults to `{ once: false, type: 'normal' }`.
* @param options.once - (Optional) If true, the handler is removed after being called once. Defaults to false.
* @param options.type - (Optional) The type of event handler to add. One of "before", "after", or "normal". Defaults to "normal".
* This method supports both **typed** and **untyped** event emitters:
*
* @throws {TypeError} Throws if the event name is not a string or the handler is not a function.
* @throws {RangeError} Throws if the event name is an empty string or the type is not a valid event type.
* - In a **typed emitter** (`EventEmitter<{ load: () => void; data: (value: number) => void }>`),
* the `eventName` parameter is restricted to the keys of the provided map,
* and the `handler` function is automatically typed according to the event's parameters.
*
* @returns The EventEmitter instance.
* - In an **untyped emitter** (`EventEmitter` with default `{}`), `eventName` can be any string.
*
* Special support for `'*'` allows registering a **global handler** that listens to all events:
* - The `handler` receives the actual event name as the first argument.
* - This works for both typed and untyped emitters.
*
* Handler types:
* - `"normal"`: Runs on every emit (default)
* - `"beforeAll"`: Runs before all normal handlers for the event
* - `"afterAll"`: Runs after all normal handlers for the event
*
* @template E - The event name type; inferred automatically.
* @param eventName - The name of the event to add the handler to, or `'*'` for a global handler.
* @param handler - The handler function for the event:
* - For specific events, receives the event parameters.
* - For `'*'`, receives the event name followed by its parameters.
* @param options - Optional configuration for the handler. Defaults to `{ once: false, type: 'normal' }`.
* @param options.once - If true, the handler is removed after being called once.
* @param options.type - The type of handler. One of `"normal"`, `"beforeAll"`, or `"afterAll"`.
*
* @throws {TypeError} If `eventName` is not a string, or `handler` is not a function.
* @throws {RangeError} If `eventName` is an empty string, or `options.type` is invalid.
*
* @returns The EventEmitter instance, allowing method chaining.
*
* @example
* // Untyped emitter
* const emitter = new EventEmitter();
* emitter.on('*', (event, ...args) => {
* console.log(`Event "${event}" emitted with args:`, args);
* });
*
* @example
* // Typed emitter
* type MyEvents = { load: (ok: boolean) => void; data: (value: number) => void };
* const typedEmitter = new EventEmitter<MyEvents>();
* typedEmitter.on('data', (value) => console.log('Data:', value));
* typedEmitter.on('*', (event, ...args) => console.log(`Event "${event}"`, args));
*
* @since v1.0.8
*/
on(eventName: string, handler: EventHandler, options?: AddHandlerOptions): this;
on<E extends EventName<EventsMap> | '*'>(eventName: [Exclude<E, '*'>] extends [never] ? string : E, handler: E extends '*' ? GlobalEventHandler<EventsMap> : EventsMap[E], options?: AddHandlerOptions): this;
/**

@@ -128,7 +172,23 @@ * Registers a custom handler for the "max handlers exceeded" event.

*
* The handler is debounced to prevent excessive calls when the maximum number of handlers is exceeded multiple times in quick succession.
* - If a **function** is provided, it is debounced (100ms) to prevent excessive calls during rapid handler additions.
* - If an **Error** object is provided, it will be thrown immediately when the limit is exceeded.
*
* @param handler - A function to call when the maximum number of handlers is exceeded or an error to throw.
* @returns The EventEmitter instance.
* @throws {TypeError} If the handler is not a function or an error.
* @param handler - A function to call when the maximum number of handlers is exceeded, or an Error to throw.
* @returns The EventEmitter instance, allowing method chaining.
*
* @throws {TypeError} If the handler is not a function or an Error instance.
*
* @example
* ```ts
* const emitter = new atomix.tools.EventEmitter();
*
* // Throw an error when the max handlers limit is exceeded
* emitter.onMaxHandlers(new Error('Handler limit exceeded'));
*
* // Or provide a custom function
* emitter.onMaxHandlers((eventName) => {
* console.warn(`Handler overflow on event: ${eventName}`);
* });
* ```
*
* @since v1.0.8

@@ -156,2 +216,23 @@ */

* @returns {number} The maximum number of handlers. A positive integer or Infinity.
* @since v1.0.24
*/
get maxTotalHandlers(): number;
/**
* Sets the maximum number of event handlers allowed for this EventEmitter instance.
*
* The value must be a positive integer or Infinity. If set to Infinity, there is no limit to the number of handlers.
*
* @param {number} value The maximum number of handlers. Must be a positive integer or Infinity.
* @throws {TypeError} If the provided value is not a number or not an integer.
* @throws {RangeError} If the provided value is not greater than 0.
* @since v1.0.24
*/
set maxTotalHandlers(value: number);
/**
* Retrieves the maximum number of event handlers allowed for this EventEmitter instance.
*
* The value is a positive integer or Infinity. If set to Infinity, there is no limit to the number of handlers.
*
* @returns {number} The maximum number of handlers. A positive integer or Infinity.
* @deprecated Use {@link maxTotalHandlers} getter/setter instead.
* @since v1.0.8

@@ -168,2 +249,3 @@ */

* @throws {RangeError} If the provided value is not greater than 0.
* @deprecated Use {@link maxTotalHandlers} getter/setter instead.
* @since v1.0.8

@@ -170,0 +252,0 @@ */

@@ -121,3 +121,3 @@ import AdaptiveTaskQueue from "./queues/AdaptiveTaskQueue";

* // Custom behavior when handler count exceeds limit
* emitter.maxHandlers = 2;
* emitter.maxTotalHandlers = 2;
* emitter.onMaxHandlers((eventName) => {

@@ -124,0 +124,0 @@ * throw new Error(`Handler overflow on ${eventName}`);

@@ -11,10 +11,15 @@ "use strict";

/**
* A flexible and lightweight event emitter class that supports advanced features like:
* - `beforeAll` and `afterAll` handler types
* - One-time (`once`) handlers
* - Handler limits with overflow detection
* - Custom max-handler overflow behavior
* A flexible and lightweight event emitter class with full TypeScript support.
*
* This implementation allows both standard event handlers and lifecycle-style hooks around them.
* Features:
* - Strongly typed event names and handler arguments using generics (`EventsMap`).
* - Global `'*'` handlers that receive the event name as the first argument, followed by the event's arguments.
* - `beforeAll` and `afterAll` handler types for lifecycle-style hooks.
* - One-time handlers via `on` with `{ once: true }` option (removed automatically after first execution).
* - Handler limits with overflow detection via `maxTotalHandlers` and custom overflow behavior.
* - Backward-compatible `maxHandlers` property (deprecated in favor of `maxTotalHandlers`).
*
* This implementation allows both standard event handlers and advanced lifecycle hooks,
* while supporting both typed and untyped usage.
*
* @example

@@ -36,2 +41,5 @@ * ```ts

*
* // Global handler receives the event name first
* emitter.on(`'*'`, (event, ...args) => console.log(`Global: ${event}`, args));
*
* // Emit the event

@@ -43,13 +51,20 @@ * await emitter.emit('data', 42);

* ```ts
* // Custom behavior when handler count exceeds limit
* emitter.maxHandlers = 2;
* emitter.onMaxHandlers((eventName) => {
* throw new Error(`Handler overflow on ${eventName}`);
* });
* const emitter = new atomix.tools.EventEmitter();
*
* // Option 1: Throw an Error immediately when the limit is exceeded
* emitter.maxTotalHandlers = 2;
* emitter.onMaxHandlers(new Error('Handler limit exceeded'));
*
* emitter.on('load', () => {});
* emitter.on('load', () => {});
* emitter.on('load', () => {}); // throws
* emitter.on('load', () => {}); // throws immediately
*
* // Option 2: Use a custom function to handle overflows (debounced)
* emitter.onMaxHandlers((eventName) => {
* console.warn(`Handler overflow on event: ${eventName}`);
* });
* ```
*
* @template EventsMap - Optional type map for event names and handler signatures. Defaults to `{}` for untyped usage.
*
* @since v1.0.8

@@ -115,33 +130,42 @@ */

};
#_runner = {
processEvents: async (events, ...args) => {
if (events.handlers.before) {
await this.#_runner.runHandler(events.handlers.before, events.name, ...args);
#_processor = {
process: async (events, ...args) => {
const { emittedBy, data, isGlobal } = events;
if (data.handlers.before) {
await this.#_processor.processHandler(data.handlers.before, { triggeredBy: emittedBy, isGlobal }, ...args);
}
// Handle normal events
for (let i = 0; i < events.handlers.normal.index; i++) {
const handler = (events.handlers.normal.handlers.get(i) || events.handlers.normal.onceHandlers.get(i));
for (let i = 0; i < data.handlers.normal.index; i++) {
const handler = (data.handlers.normal.handlers.get(i) || data.handlers.normal.onceHandlers.get(i));
if (!handler) {
continue;
}
await this.#_runner.runHandler(handler, events.name, ...args);
await this.#_processor.processHandler(handler, { triggeredBy: emittedBy, isGlobal }, ...args);
}
if (events.handlers.after) {
await this.#_runner.runHandler(events.handlers.after, events.name, ...args);
if (data.handlers.after) {
await this.#_processor.processHandler(data.handlers.after, { triggeredBy: emittedBy, isGlobal }, ...args);
}
const onceHandlersCount = events.handlers.normal.onceHandlers.size;
this.#_helpers.updateEventHandlersNum(events, -onceHandlersCount);
events.handlers.normal.onceHandlers.clear();
const onceHandlersCount = data.handlers.normal.onceHandlers.size;
this.#_helpers.updateEventHandlersNum(data, -onceHandlersCount);
data.handlers.normal.onceHandlers.clear();
},
runHandler: async (handler, eventName, ...args) => {
processHandler: async (handler, meta, ...args) => {
const triggeredBy = meta.triggeredBy;
const isGlobal = meta.isGlobal;
try {
await handler(...args);
if (isGlobal) {
await handler(triggeredBy, ...args);
}
else {
await handler(...args);
}
}
catch (error) {
this.#_runner.onError(error, eventName);
this.#_processor.onError(error, meta);
}
},
onError: (err, eventName) => {
console.error(`[HandlerError] (${eventName})`, err);
},
onError(error, meta) {
const errMsg = `[Atomix][EventEmitter:UserHandlerError]: An error occurred while processing event '${meta.triggeredBy}'.`;
console.error(errMsg, error);
}
};

@@ -151,2 +175,13 @@ /**

*
* This method supports both **typed** and **untyped** emitters:
*
* - In a **typed emitter** (`EventEmitter<{ load: () => void; data: (value: number) => void }>`),
* the `eventName` parameter is restricted to the keys of the provided map,
* and the `args` are automatically typed according to the event's parameters.
*
* - In an **untyped emitter** (`EventEmitter` with default `{}`), `eventName` can be any string,
* and `args` are of type `any[]`.
*
* Global (`'*'`) handlers are triggered on every emit and receive the actual event name as the first argument.
*
* Handlers are invoked in the following order for each `emit` call:

@@ -157,16 +192,11 @@ * 1. The `beforeAll` handler (if set) runs before all `normal` handlers.

*
* `beforeAll` and `afterAll` are special singleton handlers — only one of each
* may be set per event name. They are not removed automatically and are executed
* on every `emit` call.
* `beforeAll` and `afterAll` are singleton handlers — only one of each may be set per event name.
* They are not removed automatically and are executed on every `emit` call.
*
* `normal` handlers may be marked as `once`, in which case they will be removed
* after their first invocation.
* `normal` handlers may be marked as `once`, in which case they are removed after their first invocation.
*
* Global (`'*'`) handlers are triggered on every emit and receive the actual event
* name as their first argument.
* @template E - The event name type; inferred automatically.
* @param eventName - The name of the event to emit, or `'*'` for global handlers. Must be a non-empty string.
* @param args - Arguments to pass to all applicable handlers. Automatically typed for typed emitters.
*
* @async
* @param eventName - The name of the event to emit. Must be a non-empty string.
* @param args - Arguments to pass to all applicable handler functions.
*
* @throws {TypeError} If the event name is not a string.

@@ -176,27 +206,15 @@ * @throws {RangeError} If the event name is an empty string.

* @example
* ```ts
* // Untyped emitter
* const emitter = new EventEmitter();
* emitter.on('*', (event, ...args) => console.log(`Global: ${event}`, args));
* await emitter.emit('load', true);
*
* emitter.on('data', (value) => console.log('Normal:', value));
* emitter.on('data', () => console.log('Before All'), { type: 'beforeAll' });
* emitter.on('data', () => console.log('After All'), { type: 'afterAll' });
*
* await emitter.emit('data', 123);
* // Output:
* // Before All
* // Normal: 123
* // After All
* ```
*
* @example
* ```ts
* emitter.on('*', (event, ...args) => {
* console.log(`Global handler: ${event}`, args);
* });
* // Typed emitter
* type MyEvents = { load: (ok: boolean) => void; data: (value: number) => void };
* const typedEmitter = new EventEmitter<MyEvents>();
* typedEmitter.on('data', (value) => console.log('Data:', value));
* typedEmitter.on('*', (event, ...args) => console.log(`Global: ${event}`, args));
* await typedEmitter.emit('data', 42); // value typed as number
*
* await emitter.emit('load', true);
* // Output:
* // Global handler: load [true]
* ```
*
* @since v1.0.8

@@ -214,6 +232,6 @@ */

if (namedEvents) {
await this.#_runner.processEvents(namedEvents, ...args);
await this.#_processor.process({ emittedBy: eventName, data: namedEvents }, ...args);
}
if (globalEvents) {
await this.#_runner.processEvents(globalEvents, ...args);
await this.#_processor.process({ emittedBy: eventName, data: globalEvents, isGlobal: true }, ...args);
}

@@ -224,12 +242,47 @@ }

*
* @param eventName - The name of the event to add the handler to.
* @param handler - The handler function to add.
* @param options - (Optional) Additional configuration options for the handler. If not provided, defaults to `{ once: false, type: 'normal' }`.
* @param options.once - (Optional) If true, the handler is removed after being called once. Defaults to false.
* @param options.type - (Optional) The type of event handler to add. One of "before", "after", or "normal". Defaults to "normal".
* This method supports both **typed** and **untyped** event emitters:
*
* @throws {TypeError} Throws if the event name is not a string or the handler is not a function.
* @throws {RangeError} Throws if the event name is an empty string or the type is not a valid event type.
* - In a **typed emitter** (`EventEmitter<{ load: () => void; data: (value: number) => void }>`),
* the `eventName` parameter is restricted to the keys of the provided map,
* and the `handler` function is automatically typed according to the event's parameters.
*
* @returns The EventEmitter instance.
* - In an **untyped emitter** (`EventEmitter` with default `{}`), `eventName` can be any string.
*
* Special support for `'*'` allows registering a **global handler** that listens to all events:
* - The `handler` receives the actual event name as the first argument.
* - This works for both typed and untyped emitters.
*
* Handler types:
* - `"normal"`: Runs on every emit (default)
* - `"beforeAll"`: Runs before all normal handlers for the event
* - `"afterAll"`: Runs after all normal handlers for the event
*
* @template E - The event name type; inferred automatically.
* @param eventName - The name of the event to add the handler to, or `'*'` for a global handler.
* @param handler - The handler function for the event:
* - For specific events, receives the event parameters.
* - For `'*'`, receives the event name followed by its parameters.
* @param options - Optional configuration for the handler. Defaults to `{ once: false, type: 'normal' }`.
* @param options.once - If true, the handler is removed after being called once.
* @param options.type - The type of handler. One of `"normal"`, `"beforeAll"`, or `"afterAll"`.
*
* @throws {TypeError} If `eventName` is not a string, or `handler` is not a function.
* @throws {RangeError} If `eventName` is an empty string, or `options.type` is invalid.
*
* @returns The EventEmitter instance, allowing method chaining.
*
* @example
* // Untyped emitter
* const emitter = new EventEmitter();
* emitter.on('*', (event, ...args) => {
* console.log(`Event "${event}" emitted with args:`, args);
* });
*
* @example
* // Typed emitter
* type MyEvents = { load: (ok: boolean) => void; data: (value: number) => void };
* const typedEmitter = new EventEmitter<MyEvents>();
* typedEmitter.on('data', (value) => console.log('Data:', value));
* typedEmitter.on('*', (event, ...args) => console.log(`Event "${event}"`, args));
*
* @since v1.0.8

@@ -293,7 +346,23 @@ */

*
* The handler is debounced to prevent excessive calls when the maximum number of handlers is exceeded multiple times in quick succession.
* - If a **function** is provided, it is debounced (100ms) to prevent excessive calls during rapid handler additions.
* - If an **Error** object is provided, it will be thrown immediately when the limit is exceeded.
*
* @param handler - A function to call when the maximum number of handlers is exceeded or an error to throw.
* @returns The EventEmitter instance.
* @throws {TypeError} If the handler is not a function or an error.
* @param handler - A function to call when the maximum number of handlers is exceeded, or an Error to throw.
* @returns The EventEmitter instance, allowing method chaining.
*
* @throws {TypeError} If the handler is not a function or an Error instance.
*
* @example
* ```ts
* const emitter = new atomix.tools.EventEmitter();
*
* // Throw an error when the max handlers limit is exceeded
* emitter.onMaxHandlers(new Error('Handler limit exceeded'));
*
* // Or provide a custom function
* emitter.onMaxHandlers((eventName) => {
* console.warn(`Handler overflow on event: ${eventName}`);
* });
* ```
*
* @since v1.0.8

@@ -348,5 +417,5 @@ */

* @returns {number} The maximum number of handlers. A positive integer or Infinity.
* @since v1.0.8
* @since v1.0.24
*/
get maxHandlers() { return this.#_stats.handlers.max; }
get maxTotalHandlers() { return this.#_stats.handlers.max; }
/**

@@ -360,7 +429,7 @@ * Sets the maximum number of event handlers allowed for this EventEmitter instance.

* @throws {RangeError} If the provided value is not greater than 0.
* @since v1.0.8
* @since v1.0.24
*/
set maxHandlers(value) {
set maxTotalHandlers(value) {
if (!valueIs_1.default.number(value)) {
throw new TypeError('maxHandlers must be a number');
throw new TypeError('maxTotalHandlers must be a number');
}

@@ -372,6 +441,6 @@ if (value === Infinity) {

if (value <= 0) {
throw new RangeError('maxHandlers must be greater than 0');
throw new RangeError('maxTotalHandlers must be greater than 0');
}
if (!valueIs_1.default.integer(value)) {
throw new TypeError('maxHandlers must be an integer');
throw new TypeError('maxTotalHandlers must be an integer');
}

@@ -381,2 +450,26 @@ this.#_stats.handlers.max = value;

/**
* Retrieves the maximum number of event handlers allowed for this EventEmitter instance.
*
* The value is a positive integer or Infinity. If set to Infinity, there is no limit to the number of handlers.
*
* @returns {number} The maximum number of handlers. A positive integer or Infinity.
* @deprecated Use {@link maxTotalHandlers} getter/setter instead.
* @since v1.0.8
*/
get maxHandlers() { return this.maxTotalHandlers; }
/**
* Sets the maximum number of event handlers allowed for this EventEmitter instance.
*
* The value must be a positive integer or Infinity. If set to Infinity, there is no limit to the number of handlers.
*
* @param {number} value The maximum number of handlers. Must be a positive integer or Infinity.
* @throws {TypeError} If the provided value is not a number or not an integer.
* @throws {RangeError} If the provided value is not greater than 0.
* @deprecated Use {@link maxTotalHandlers} getter/setter instead.
* @since v1.0.8
*/
set maxHandlers(value) {
this.maxTotalHandlers = value;
}
/**
* Retrieves the total number of event handlers registered with this EventEmitter instance.

@@ -383,0 +476,0 @@ * @returns {number} The total number of event handlers.

@@ -126,3 +126,3 @@ "use strict";

* // Custom behavior when handler count exceeds limit
* emitter.maxHandlers = 2;
* emitter.maxTotalHandlers = 2;
* emitter.onMaxHandlers((eventName) => {

@@ -129,0 +129,0 @@ * throw new Error(`Handler overflow on ${eventName}`);

@@ -5,10 +5,15 @@ import valueIs from '../../valueIs.js';

/**
* A flexible and lightweight event emitter class that supports advanced features like:
* - `beforeAll` and `afterAll` handler types
* - One-time (`once`) handlers
* - Handler limits with overflow detection
* - Custom max-handler overflow behavior
* A flexible and lightweight event emitter class with full TypeScript support.
*
* This implementation allows both standard event handlers and lifecycle-style hooks around them.
* Features:
* - Strongly typed event names and handler arguments using generics (`EventsMap`).
* - Global `'*'` handlers that receive the event name as the first argument, followed by the event's arguments.
* - `beforeAll` and `afterAll` handler types for lifecycle-style hooks.
* - One-time handlers via `on` with `{ once: true }` option (removed automatically after first execution).
* - Handler limits with overflow detection via `maxTotalHandlers` and custom overflow behavior.
* - Backward-compatible `maxHandlers` property (deprecated in favor of `maxTotalHandlers`).
*
* This implementation allows both standard event handlers and advanced lifecycle hooks,
* while supporting both typed and untyped usage.
*
* @example

@@ -30,2 +35,5 @@ * ```ts

*
* // Global handler receives the event name first
* emitter.on(`'*'`, (event, ...args) => console.log(`Global: ${event}`, args));
*
* // Emit the event

@@ -37,13 +45,20 @@ * await emitter.emit('data', 42);

* ```ts
* // Custom behavior when handler count exceeds limit
* emitter.maxHandlers = 2;
* emitter.onMaxHandlers((eventName) => {
* throw new Error(`Handler overflow on ${eventName}`);
* });
* const emitter = new atomix.tools.EventEmitter();
*
* // Option 1: Throw an Error immediately when the limit is exceeded
* emitter.maxTotalHandlers = 2;
* emitter.onMaxHandlers(new Error('Handler limit exceeded'));
*
* emitter.on('load', () => {});
* emitter.on('load', () => {});
* emitter.on('load', () => {}); // throws
* emitter.on('load', () => {}); // throws immediately
*
* // Option 2: Use a custom function to handle overflows (debounced)
* emitter.onMaxHandlers((eventName) => {
* console.warn(`Handler overflow on event: ${eventName}`);
* });
* ```
*
* @template EventsMap - Optional type map for event names and handler signatures. Defaults to `{}` for untyped usage.
*
* @since v1.0.8

@@ -109,33 +124,42 @@ */

};
#_runner = {
processEvents: async (events, ...args) => {
if (events.handlers.before) {
await this.#_runner.runHandler(events.handlers.before, events.name, ...args);
#_processor = {
process: async (events, ...args) => {
const { emittedBy, data, isGlobal } = events;
if (data.handlers.before) {
await this.#_processor.processHandler(data.handlers.before, { triggeredBy: emittedBy, isGlobal }, ...args);
}
// Handle normal events
for (let i = 0; i < events.handlers.normal.index; i++) {
const handler = (events.handlers.normal.handlers.get(i) || events.handlers.normal.onceHandlers.get(i));
for (let i = 0; i < data.handlers.normal.index; i++) {
const handler = (data.handlers.normal.handlers.get(i) || data.handlers.normal.onceHandlers.get(i));
if (!handler) {
continue;
}
await this.#_runner.runHandler(handler, events.name, ...args);
await this.#_processor.processHandler(handler, { triggeredBy: emittedBy, isGlobal }, ...args);
}
if (events.handlers.after) {
await this.#_runner.runHandler(events.handlers.after, events.name, ...args);
if (data.handlers.after) {
await this.#_processor.processHandler(data.handlers.after, { triggeredBy: emittedBy, isGlobal }, ...args);
}
const onceHandlersCount = events.handlers.normal.onceHandlers.size;
this.#_helpers.updateEventHandlersNum(events, -onceHandlersCount);
events.handlers.normal.onceHandlers.clear();
const onceHandlersCount = data.handlers.normal.onceHandlers.size;
this.#_helpers.updateEventHandlersNum(data, -onceHandlersCount);
data.handlers.normal.onceHandlers.clear();
},
runHandler: async (handler, eventName, ...args) => {
processHandler: async (handler, meta, ...args) => {
const triggeredBy = meta.triggeredBy;
const isGlobal = meta.isGlobal;
try {
await handler(...args);
if (isGlobal) {
await handler(triggeredBy, ...args);
}
else {
await handler(...args);
}
}
catch (error) {
this.#_runner.onError(error, eventName);
this.#_processor.onError(error, meta);
}
},
onError: (err, eventName) => {
console.error(`[HandlerError] (${eventName})`, err);
},
onError(error, meta) {
const errMsg = `[Atomix][EventEmitter:UserHandlerError]: An error occurred while processing event '${meta.triggeredBy}'.`;
console.error(errMsg, error);
}
};

@@ -145,2 +169,13 @@ /**

*
* This method supports both **typed** and **untyped** emitters:
*
* - In a **typed emitter** (`EventEmitter<{ load: () => void; data: (value: number) => void }>`),
* the `eventName` parameter is restricted to the keys of the provided map,
* and the `args` are automatically typed according to the event's parameters.
*
* - In an **untyped emitter** (`EventEmitter` with default `{}`), `eventName` can be any string,
* and `args` are of type `any[]`.
*
* Global (`'*'`) handlers are triggered on every emit and receive the actual event name as the first argument.
*
* Handlers are invoked in the following order for each `emit` call:

@@ -151,16 +186,11 @@ * 1. The `beforeAll` handler (if set) runs before all `normal` handlers.

*
* `beforeAll` and `afterAll` are special singleton handlers — only one of each
* may be set per event name. They are not removed automatically and are executed
* on every `emit` call.
* `beforeAll` and `afterAll` are singleton handlers — only one of each may be set per event name.
* They are not removed automatically and are executed on every `emit` call.
*
* `normal` handlers may be marked as `once`, in which case they will be removed
* after their first invocation.
* `normal` handlers may be marked as `once`, in which case they are removed after their first invocation.
*
* Global (`'*'`) handlers are triggered on every emit and receive the actual event
* name as their first argument.
* @template E - The event name type; inferred automatically.
* @param eventName - The name of the event to emit, or `'*'` for global handlers. Must be a non-empty string.
* @param args - Arguments to pass to all applicable handlers. Automatically typed for typed emitters.
*
* @async
* @param eventName - The name of the event to emit. Must be a non-empty string.
* @param args - Arguments to pass to all applicable handler functions.
*
* @throws {TypeError} If the event name is not a string.

@@ -170,27 +200,15 @@ * @throws {RangeError} If the event name is an empty string.

* @example
* ```ts
* // Untyped emitter
* const emitter = new EventEmitter();
* emitter.on('*', (event, ...args) => console.log(`Global: ${event}`, args));
* await emitter.emit('load', true);
*
* emitter.on('data', (value) => console.log('Normal:', value));
* emitter.on('data', () => console.log('Before All'), { type: 'beforeAll' });
* emitter.on('data', () => console.log('After All'), { type: 'afterAll' });
*
* await emitter.emit('data', 123);
* // Output:
* // Before All
* // Normal: 123
* // After All
* ```
*
* @example
* ```ts
* emitter.on('*', (event, ...args) => {
* console.log(`Global handler: ${event}`, args);
* });
* // Typed emitter
* type MyEvents = { load: (ok: boolean) => void; data: (value: number) => void };
* const typedEmitter = new EventEmitter<MyEvents>();
* typedEmitter.on('data', (value) => console.log('Data:', value));
* typedEmitter.on('*', (event, ...args) => console.log(`Global: ${event}`, args));
* await typedEmitter.emit('data', 42); // value typed as number
*
* await emitter.emit('load', true);
* // Output:
* // Global handler: load [true]
* ```
*
* @since v1.0.8

@@ -208,6 +226,6 @@ */

if (namedEvents) {
await this.#_runner.processEvents(namedEvents, ...args);
await this.#_processor.process({ emittedBy: eventName, data: namedEvents }, ...args);
}
if (globalEvents) {
await this.#_runner.processEvents(globalEvents, ...args);
await this.#_processor.process({ emittedBy: eventName, data: globalEvents, isGlobal: true }, ...args);
}

@@ -218,12 +236,47 @@ }

*
* @param eventName - The name of the event to add the handler to.
* @param handler - The handler function to add.
* @param options - (Optional) Additional configuration options for the handler. If not provided, defaults to `{ once: false, type: 'normal' }`.
* @param options.once - (Optional) If true, the handler is removed after being called once. Defaults to false.
* @param options.type - (Optional) The type of event handler to add. One of "before", "after", or "normal". Defaults to "normal".
* This method supports both **typed** and **untyped** event emitters:
*
* @throws {TypeError} Throws if the event name is not a string or the handler is not a function.
* @throws {RangeError} Throws if the event name is an empty string or the type is not a valid event type.
* - In a **typed emitter** (`EventEmitter<{ load: () => void; data: (value: number) => void }>`),
* the `eventName` parameter is restricted to the keys of the provided map,
* and the `handler` function is automatically typed according to the event's parameters.
*
* @returns The EventEmitter instance.
* - In an **untyped emitter** (`EventEmitter` with default `{}`), `eventName` can be any string.
*
* Special support for `'*'` allows registering a **global handler** that listens to all events:
* - The `handler` receives the actual event name as the first argument.
* - This works for both typed and untyped emitters.
*
* Handler types:
* - `"normal"`: Runs on every emit (default)
* - `"beforeAll"`: Runs before all normal handlers for the event
* - `"afterAll"`: Runs after all normal handlers for the event
*
* @template E - The event name type; inferred automatically.
* @param eventName - The name of the event to add the handler to, or `'*'` for a global handler.
* @param handler - The handler function for the event:
* - For specific events, receives the event parameters.
* - For `'*'`, receives the event name followed by its parameters.
* @param options - Optional configuration for the handler. Defaults to `{ once: false, type: 'normal' }`.
* @param options.once - If true, the handler is removed after being called once.
* @param options.type - The type of handler. One of `"normal"`, `"beforeAll"`, or `"afterAll"`.
*
* @throws {TypeError} If `eventName` is not a string, or `handler` is not a function.
* @throws {RangeError} If `eventName` is an empty string, or `options.type` is invalid.
*
* @returns The EventEmitter instance, allowing method chaining.
*
* @example
* // Untyped emitter
* const emitter = new EventEmitter();
* emitter.on('*', (event, ...args) => {
* console.log(`Event "${event}" emitted with args:`, args);
* });
*
* @example
* // Typed emitter
* type MyEvents = { load: (ok: boolean) => void; data: (value: number) => void };
* const typedEmitter = new EventEmitter<MyEvents>();
* typedEmitter.on('data', (value) => console.log('Data:', value));
* typedEmitter.on('*', (event, ...args) => console.log(`Event "${event}"`, args));
*
* @since v1.0.8

@@ -287,7 +340,23 @@ */

*
* The handler is debounced to prevent excessive calls when the maximum number of handlers is exceeded multiple times in quick succession.
* - If a **function** is provided, it is debounced (100ms) to prevent excessive calls during rapid handler additions.
* - If an **Error** object is provided, it will be thrown immediately when the limit is exceeded.
*
* @param handler - A function to call when the maximum number of handlers is exceeded or an error to throw.
* @returns The EventEmitter instance.
* @throws {TypeError} If the handler is not a function or an error.
* @param handler - A function to call when the maximum number of handlers is exceeded, or an Error to throw.
* @returns The EventEmitter instance, allowing method chaining.
*
* @throws {TypeError} If the handler is not a function or an Error instance.
*
* @example
* ```ts
* const emitter = new atomix.tools.EventEmitter();
*
* // Throw an error when the max handlers limit is exceeded
* emitter.onMaxHandlers(new Error('Handler limit exceeded'));
*
* // Or provide a custom function
* emitter.onMaxHandlers((eventName) => {
* console.warn(`Handler overflow on event: ${eventName}`);
* });
* ```
*
* @since v1.0.8

@@ -342,5 +411,5 @@ */

* @returns {number} The maximum number of handlers. A positive integer or Infinity.
* @since v1.0.8
* @since v1.0.24
*/
get maxHandlers() { return this.#_stats.handlers.max; }
get maxTotalHandlers() { return this.#_stats.handlers.max; }
/**

@@ -354,7 +423,7 @@ * Sets the maximum number of event handlers allowed for this EventEmitter instance.

* @throws {RangeError} If the provided value is not greater than 0.
* @since v1.0.8
* @since v1.0.24
*/
set maxHandlers(value) {
set maxTotalHandlers(value) {
if (!valueIs.number(value)) {
throw new TypeError('maxHandlers must be a number');
throw new TypeError('maxTotalHandlers must be a number');
}

@@ -366,6 +435,6 @@ if (value === Infinity) {

if (value <= 0) {
throw new RangeError('maxHandlers must be greater than 0');
throw new RangeError('maxTotalHandlers must be greater than 0');
}
if (!valueIs.integer(value)) {
throw new TypeError('maxHandlers must be an integer');
throw new TypeError('maxTotalHandlers must be an integer');
}

@@ -375,2 +444,26 @@ this.#_stats.handlers.max = value;

/**
* Retrieves the maximum number of event handlers allowed for this EventEmitter instance.
*
* The value is a positive integer or Infinity. If set to Infinity, there is no limit to the number of handlers.
*
* @returns {number} The maximum number of handlers. A positive integer or Infinity.
* @deprecated Use {@link maxTotalHandlers} getter/setter instead.
* @since v1.0.8
*/
get maxHandlers() { return this.maxTotalHandlers; }
/**
* Sets the maximum number of event handlers allowed for this EventEmitter instance.
*
* The value must be a positive integer or Infinity. If set to Infinity, there is no limit to the number of handlers.
*
* @param {number} value The maximum number of handlers. Must be a positive integer or Infinity.
* @throws {TypeError} If the provided value is not a number or not an integer.
* @throws {RangeError} If the provided value is not greater than 0.
* @deprecated Use {@link maxTotalHandlers} getter/setter instead.
* @since v1.0.8
*/
set maxHandlers(value) {
this.maxTotalHandlers = value;
}
/**
* Retrieves the total number of event handlers registered with this EventEmitter instance.

@@ -377,0 +470,0 @@ * @returns {number} The total number of event handlers.

@@ -121,3 +121,3 @@ import AdaptiveTaskQueue from "./queues/AdaptiveTaskQueue.js";

* // Custom behavior when handler count exceeds limit
* emitter.maxHandlers = 2;
* emitter.maxTotalHandlers = 2;
* emitter.onMaxHandlers((eventName) => {

@@ -124,0 +124,0 @@ * throw new Error(`Handler overflow on ${eventName}`);

{
"name": "@nasriya/atomix",
"version": "1.0.23",
"version": "1.0.24",
"description": "Composable helper functions for building reliable systems",

@@ -5,0 +5,0 @@ "keywords": [