emitochondria 1.1.0 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
10
10
  [![Bundle Size](https://img.shields.io/bundlephobia/minzip/emitochondria)](https://bundlephobia.com/package/emitochondria)
11
11
 
12
- A tiny, fully-typed event emitter for TypeScript with built-in error handling and memory leak detection. Zero dependencies, under 2KB.
12
+ A tiny, fully-typed event emitter for TypeScript with built-in error handling. Zero dependencies, under 2KB.
13
13
 
14
14
  ## Features
15
15
 
@@ -256,35 +256,6 @@ const events = createEmitochondria<MyEvents>({
256
256
  });
257
257
  ```
258
258
 
259
- ## ⚡ Biological API (Alternative Naming)
260
-
261
- Emitochondria offers biologically-themed aliases for all methods. Use whichever style fits your project:
262
-
263
- | Standard API | Biological Alias | Description |
264
- |--------------|------------------|-------------|
265
- | `.on()` | `.bind()` | Bind a receptor to a signal |
266
- | `.off()` | `.release()` | Release a receptor |
267
- | `.emit()` | `.pulse()` | Pulse energy through the system |
268
- | `.emitAsync()` | `.cascade()` | Trigger a signal cascade |
269
- | `.once()` | `.spike()` | Single spike of energy |
270
- | `.onAny()` | `.membrane()` | Membrane catches all signals |
271
- | `.clear()` | `.apoptosis()` | Programmed cell death |
272
- | `.listenerCount()` | `.receptors()` | Count of receptors |
273
-
274
- ```typescript
275
- // Standard API
276
- events.on('user:login', handler);
277
- events.emit('user:login', data);
278
-
279
- // Biological API - same functionality, different style
280
- events.bind('user:login', handler);
281
- events.pulse('user:login', data);
282
-
283
- // Mix and match freely
284
- events.bind('save', async (data) => await saveToDatabase(data));
285
- await events.cascade('save', { id: '123' });
286
- ```
287
-
288
259
  ## TypeScript Magic
289
260
 
290
261
  The type system prevents mistakes at compile time:
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Import the compiled CLI
4
+ require('../dist/cli/index.cjs');
@@ -0,0 +1,258 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __commonJS = (cb, mod) => function __require() {
10
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+
29
+ // package.json
30
+ var require_package = __commonJS({
31
+ "package.json"(exports2, module2) {
32
+ module2.exports = {
33
+ name: "emitochondria",
34
+ version: "1.2.1",
35
+ type: "module",
36
+ description: "The powerhouse of your events \u2014 A tiny, fully-typed event emitter for TypeScript with built-in error handling and memory leak detection",
37
+ author: "Pablo D\xEDaz A.K.A exudev",
38
+ license: "MIT",
39
+ bin: {
40
+ emitochondria: "./bin/emitochondria.cjs"
41
+ },
42
+ keywords: [
43
+ "events",
44
+ "emitter",
45
+ "event-emitter",
46
+ "typescript",
47
+ "typed",
48
+ "pubsub",
49
+ "pub-sub",
50
+ "subscribe",
51
+ "publish",
52
+ "error-handling",
53
+ "memory-leak-detection",
54
+ "async",
55
+ "type-safe"
56
+ ],
57
+ repository: {
58
+ type: "git",
59
+ url: "https://github.com/Exudev/emitochondria.git"
60
+ },
61
+ main: "./dist/index.js",
62
+ module: "./dist/index.mjs",
63
+ types: "./dist/index.d.ts",
64
+ exports: {
65
+ ".": {
66
+ import: {
67
+ types: "./dist/index.d.mts",
68
+ default: "./dist/index.mjs"
69
+ },
70
+ require: {
71
+ types: "./dist/index.d.ts",
72
+ default: "./dist/index.js"
73
+ }
74
+ },
75
+ "./schema.json": "./schema.json"
76
+ },
77
+ files: [
78
+ "dist",
79
+ "bin",
80
+ "schema.json"
81
+ ],
82
+ sideEffects: false,
83
+ scripts: {
84
+ build: "tsup",
85
+ test: "vitest",
86
+ "test:run": "vitest run",
87
+ "test:coverage": "vitest run --coverage",
88
+ prepublishOnly: "npm run test:run && npm run build"
89
+ },
90
+ devDependencies: {
91
+ "@types/node": "^25.0.3",
92
+ tsup: "^8.5.1",
93
+ typescript: "^5.9.3",
94
+ vitest: "^4.0.16"
95
+ }
96
+ };
97
+ }
98
+ });
99
+
100
+ // src/cli/index.ts
101
+ var fs = __toESM(require("fs"), 1);
102
+ var path = __toESM(require("path"), 1);
103
+ var readline = __toESM(require("readline"), 1);
104
+ var CONFIG_FILENAME = "emitochondria.config.json";
105
+ var SCHEMA_URL = "https://unpkg.com/emitochondria/schema.json";
106
+ var DEFAULT_CONFIG = {
107
+ $schema: SCHEMA_URL,
108
+ maxListeners: 10,
109
+ logging: {
110
+ timestamps: false,
111
+ timestampFormat: "iso",
112
+ timezone: "utc",
113
+ persist: false,
114
+ path: "./logs/emitochondria.log",
115
+ format: "text",
116
+ maxSize: "10MB",
117
+ logEvents: false,
118
+ logErrors: true,
119
+ logWarnings: true
120
+ }
121
+ };
122
+ var c = {
123
+ reset: "\x1B[0m",
124
+ bold: "\x1B[1m",
125
+ green: "\x1B[32m",
126
+ yellow: "\x1B[33m",
127
+ blue: "\x1B[34m",
128
+ cyan: "\x1B[36m",
129
+ red: "\x1B[31m",
130
+ dim: "\x1B[2m"
131
+ };
132
+ function log(icon, color, msg) {
133
+ console.log(`${color}${icon}${c.reset} ${msg}`);
134
+ }
135
+ var info = (msg) => log("\u2139", c.blue, msg);
136
+ var success = (msg) => log("\u2713", c.green, msg);
137
+ var warn = (msg) => log("\u26A0 ", c.yellow, msg);
138
+ var error = (msg) => log("\u2717", c.red, msg);
139
+ function prompt(question) {
140
+ const rl = readline.createInterface({
141
+ input: process.stdin,
142
+ output: process.stdout
143
+ });
144
+ return new Promise((resolve) => {
145
+ rl.question(question, (answer) => {
146
+ rl.close();
147
+ resolve(answer.trim().toLowerCase());
148
+ });
149
+ });
150
+ }
151
+ function printHelp() {
152
+ console.log(`
153
+ ${c.bold}\u{1F9EC} Emitochondria CLI${c.reset}
154
+ ${c.dim}The powerhouse of your events${c.reset}
155
+
156
+ ${c.bold}Usage:${c.reset}
157
+ npx emitochondria <command>
158
+
159
+ ${c.bold}Commands:${c.reset}
160
+ init Create emitochondria.config.json
161
+ init --force Overwrite existing config file
162
+ help Show this help message
163
+
164
+ ${c.bold}Examples:${c.reset}
165
+ ${c.dim}# Create config file${c.reset}
166
+ npx emitochondria init
167
+
168
+ ${c.dim}# Overwrite existing config${c.reset}
169
+ npx emitochondria init --force
170
+
171
+ ${c.bold}Documentation:${c.reset}
172
+ https://github.com/Exudev/emitochondria
173
+ `);
174
+ }
175
+ function printVersion() {
176
+ try {
177
+ const pkg = require_package();
178
+ console.log(`v${pkg.version}`);
179
+ } catch {
180
+ console.log("unknown");
181
+ }
182
+ }
183
+ async function initCommand(force) {
184
+ const configPath = path.join(process.cwd(), CONFIG_FILENAME);
185
+ console.log(`
186
+ ${c.bold}\u{1F9EC} Emitochondria${c.reset}
187
+ `);
188
+ if (fs.existsSync(configPath)) {
189
+ if (!force) {
190
+ warn(`${CONFIG_FILENAME} already exists.`);
191
+ const answer = await prompt(` Overwrite? ${c.dim}(y/N)${c.reset} `);
192
+ if (answer !== "y" && answer !== "yes") {
193
+ info("Aborted. No changes made.");
194
+ process.exit(0);
195
+ }
196
+ } else {
197
+ warn("Overwriting existing config file.");
198
+ }
199
+ }
200
+ try {
201
+ const content = JSON.stringify(DEFAULT_CONFIG, null, 2);
202
+ fs.writeFileSync(configPath, content + "\n", "utf8");
203
+ success(`Created ${c.bold}${CONFIG_FILENAME}${c.reset}`);
204
+ console.log(`
205
+ ${c.dim} Location: ${configPath}${c.reset}`);
206
+ console.log(`
207
+ ${c.bold}Next steps:${c.reset}
208
+
209
+ 1. Edit the config file to match your needs
210
+ 2. Import and use emitochondria:
211
+
212
+ ${c.cyan}import { createEmitochondria } from 'emitochondria';${c.reset}
213
+
214
+ ${c.cyan}type MyEvents = {${c.reset}
215
+ ${c.cyan}'user:login': { userId: string };${c.reset}
216
+ ${c.cyan}};${c.reset}
217
+
218
+ ${c.cyan}const events = createEmitochondria<MyEvents>();${c.reset}
219
+
220
+ ${c.dim}\u{1F4D6} Docs: https://github.com/Exudev/emitochondria${c.reset}
221
+ `);
222
+ } catch (err) {
223
+ error(`Failed to create config file: ${err}`);
224
+ process.exit(1);
225
+ }
226
+ }
227
+ async function main() {
228
+ const args = process.argv.slice(2);
229
+ const command = args[0];
230
+ const flags = args.slice(1);
231
+ const hasForce = flags.includes("--force") || flags.includes("-f");
232
+ switch (command) {
233
+ case "init":
234
+ await initCommand(hasForce);
235
+ break;
236
+ case "help":
237
+ case "--help":
238
+ case "-h":
239
+ case void 0:
240
+ printHelp();
241
+ break;
242
+ case "version":
243
+ case "--version":
244
+ case "-v":
245
+ printVersion();
246
+ break;
247
+ default:
248
+ error(`Unknown command: ${command}`);
249
+ console.log(`
250
+ Run ${c.cyan}npx emitochondria help${c.reset} for usage.
251
+ `);
252
+ process.exit(1);
253
+ }
254
+ }
255
+ main().catch((err) => {
256
+ error(`Unexpected error: ${err}`);
257
+ process.exit(1);
258
+ });
package/dist/index.d.cts CHANGED
@@ -1,34 +1,97 @@
1
- /**
2
- * Base type for event maps.
3
- * Keys are event names, values are payload types.
4
- */
5
1
  type EventMap = Record<string, unknown>;
6
- /**
7
- * Extracts event names from an event map as a string union.
8
- */
9
2
  type EventKey<T extends EventMap> = keyof T & string;
10
- /**
11
- * Handler function for a specific event payload.
12
- * Can be sync or async.
13
- */
14
3
  type EventHandler<T> = (payload: T) => void | Promise<void>;
15
- /**
16
- * Wildcard handler that receives event name and payload.
17
- * Useful for logging/debugging.
18
- */
19
4
  type WildcardHandler<T extends EventMap> = <K extends EventKey<T>>(event: K, payload: T[K]) => void | Promise<void>;
20
- /**
21
- * Options for creating an Emitochondria instance.
22
- */
23
- interface EmitochondriaOptions {
5
+ type ErrorHandler<T extends EventMap> = (error: unknown, event: EventKey<T>, handler: EventHandler<unknown>) => void;
6
+ type MaxListenersHandler<T extends EventMap> = (event: EventKey<T>, count: number, max: number) => void;
7
+ type TimestampFormat = 'iso' | 'unix' | 'human';
8
+ type Timezone = 'utc' | 'local';
9
+ type LogFormat = 'text' | 'json';
10
+ interface LoggingOptions {
11
+ /**
12
+ * Enable timestamps in logs.
13
+ * @default false
14
+ */
15
+ timestamps?: boolean;
16
+ /**
17
+ * Timestamp format.
18
+ * - 'iso': 2025-01-13T14:30:00.000Z
19
+ * - 'unix': 1736776200000
20
+ * - 'human': Jan 13, 2:30:00 PM
21
+ * @default 'iso'
22
+ */
23
+ timestampFormat?: TimestampFormat;
24
+ /**
25
+ * Timezone for timestamps.
26
+ * - 'utc': UTC time
27
+ * - 'local': System local time
28
+ * @default 'utc'
29
+ */
30
+ timezone?: Timezone;
31
+ /**
32
+ * Enable file logging.
33
+ * ⚠️ Only works in Node.js. Browser will show a warning.
34
+ * ⚠️ Payloads may contain sensitive data — they will be written to disk.
35
+ * @default false
36
+ */
37
+ persist?: boolean;
38
+ /**
39
+ * Path to log file.
40
+ * Directory will be created if it doesn't exist.
41
+ * @default './logs/emitochondria.log'
42
+ */
43
+ path?: string;
44
+ /**
45
+ * Log file format.
46
+ * - 'text': Human-readable plain text
47
+ * - 'json': JSON lines (one JSON object per line)
48
+ * @default 'text'
49
+ */
50
+ format?: LogFormat;
51
+ /**
52
+ * Maximum log file size before rotation.
53
+ * Supports: '10MB', '1GB', '500KB'
54
+ * When exceeded, current file is renamed to .1, .2, etc.
55
+ * @default '10MB'
56
+ */
57
+ maxSize?: string;
24
58
  /**
25
- * Custom error handler for errors thrown by event handlers.
26
- *
27
- * @default - Logs to console in development, silent in production
28
- * @example 'throw' - Preserve original throwing behavior
29
- * @example (error, event) => logger.error(error)
59
+ * Log every emitted event.
60
+ * ⚠️ Can be very verbose in production!
61
+ * @default false
30
62
  */
31
- onError?: ((error: Error, event: string, handler: EventHandler<unknown>) => void) | 'throw';
63
+ logEvents?: boolean;
64
+ /**
65
+ * Log handler errors.
66
+ * @default true
67
+ */
68
+ logErrors?: boolean;
69
+ /**
70
+ * Log memory leak warnings.
71
+ * @default true
72
+ */
73
+ logWarnings?: boolean;
74
+ }
75
+ interface EmitochondriaConfig {
76
+ /**
77
+ * Maximum listeners per event before warning.
78
+ * Set to 0 to disable warnings.
79
+ * @default 10
80
+ */
81
+ maxListeners?: number;
82
+ /**
83
+ * Custom error handler for when handlers throw.
84
+ * - 'throw': Re-throw errors
85
+ * - undefined: Default (logs in dev, silent in prod)
86
+ * Note: Function handlers only work in options, not config file.
87
+ */
88
+ onError?: 'throw';
89
+ /**
90
+ * Logging configuration.
91
+ */
92
+ logging?: LoggingOptions;
93
+ }
94
+ interface EmitochondriaOptions<T extends EventMap> {
32
95
  /**
33
96
  * Maximum listeners per event before warning.
34
97
  * Set to 0 to disable warnings.
@@ -36,88 +99,46 @@ interface EmitochondriaOptions {
36
99
  */
37
100
  maxListeners?: number;
38
101
  /**
39
- * Custom handler when max listeners is exceeded.
102
+ * Custom error handler function or 'throw'.
103
+ * - ErrorHandler function: Custom error handling logic
104
+ * - 'throw': Re-throw errors
105
+ * - undefined: Default (logs in dev, silent in prod)
106
+ */
107
+ onError?: ErrorHandler<T> | 'throw';
108
+ /**
109
+ * Custom max listeners exceeded handler.
40
110
  * If not provided, logs a warning to console.
41
111
  */
42
- onMaxListenersExceeded?: (event: string, count: number, max: number) => void;
112
+ onMaxListenersExceeded?: MaxListenersHandler<T>;
113
+ /**
114
+ * Logging configuration.
115
+ */
116
+ logging?: LoggingOptions;
43
117
  }
44
- /**
45
- * The typed emitter interface.
46
- */
47
118
  interface Emitochondria<T extends EventMap> {
48
- /** Subscribe to an event. Returns unsubscribe function. */
49
119
  on<K extends EventKey<T>>(event: K, handler: EventHandler<T[K]>): () => void;
50
- /** Unsubscribe a handler from an event. */
51
120
  off<K extends EventKey<T>>(event: K, handler: EventHandler<T[K]>): void;
52
- /** Subscribe for a single emission only. */
53
121
  once<K extends EventKey<T>>(event: K, handler: EventHandler<T[K]>): () => void;
54
- /** Emit an event synchronously. */
55
122
  emit<K extends EventKey<T>>(event: K, ...payload: T[K] extends void ? [] : [T[K]]): void;
56
- /** Emit an event and await all handlers. */
57
123
  emitAsync<K extends EventKey<T>>(event: K, ...payload: T[K] extends void ? [] : [T[K]]): Promise<void>;
58
- /** Subscribe to all events (wildcard). */
59
124
  onAny(handler: WildcardHandler<T>): () => void;
60
- /** Unsubscribe a wildcard handler. */
61
125
  offAny(handler: WildcardHandler<T>): void;
62
- /** Clear handlers for a specific event or all events. */
63
126
  clear<K extends EventKey<T>>(event?: K): void;
64
- /** Get the number of listeners for an event. */
65
127
  listenerCount<K extends EventKey<T>>(event: K): number;
66
- /** Set a custom error handler at runtime. */
67
- setErrorHandler(handler: EmitochondriaOptions['onError']): void;
68
- /** Set the maximum number of listeners per event before warning. */
128
+ setErrorHandler(handler: ErrorHandler<T> | 'throw'): void;
69
129
  setMaxListeners(n: number): void;
70
- /** Get the current max listener limit. */
71
130
  getMaxListeners(): number;
72
- /** Get all event names that currently have registered listeners. */
73
131
  eventNames(): EventKey<T>[];
74
- /** Get all handlers registered for a specific event. */
75
- listeners<K extends EventKey<T>>(event: K): ReadonlyArray<EventHandler<T[K]>>;
76
- /** Get all wildcard handlers. */
77
- wildcardListeners(): ReadonlyArray<WildcardHandler<T>>;
78
- /** Check if a specific handler is registered for an event. */
132
+ listeners<K extends EventKey<T>>(event: K): EventHandler<T[K]>[];
133
+ wildcardListeners(): WildcardHandler<T>[];
79
134
  hasListener<K extends EventKey<T>>(event: K, handler: EventHandler<T[K]>): boolean;
80
- /** Alias for `on` — Bind a receptor to a signal. */
81
- bind: Emitochondria<T>['on'];
82
- /** Alias for `off` — Release a receptor. */
83
- release: Emitochondria<T>['off'];
84
- /** Alias for `emit` — Pulse energy through the system. */
85
- pulse: Emitochondria<T>['emit'];
86
- /** Alias for `emitAsync` — Trigger a signal cascade. */
87
- cascade: Emitochondria<T>['emitAsync'];
88
- /** Alias for `once` — Single spike of energy. */
89
- spike: Emitochondria<T>['once'];
90
- /** Alias for `onAny` — Membrane catches all signals. */
91
- membrane: Emitochondria<T>['onAny'];
92
- /** Alias for `clear` — Programmed cell death. */
93
- apoptosis: Emitochondria<T>['clear'];
94
- /** Alias for `listenerCount` — Count of receptors. */
95
- receptors: Emitochondria<T>['listenerCount'];
96
135
  }
136
+
137
+ declare function createEmitochondria<T extends EventMap>(options?: EmitochondriaOptions<T>): Emitochondria<T>;
138
+
97
139
  /**
98
- * Create a new typed event emitter.
99
- *
100
- * @example
101
- * ```typescript
102
- * type MyEvents = {
103
- * 'user:login': { userId: string };
104
- * 'app:ready': void;
105
- * };
106
- *
107
- * const mito = createEmitochondria<MyEvents>();
108
- *
109
- * // Standard API
110
- * mito.on('user:login', (data) => {
111
- * console.log(data.userId); // fully typed!
112
- * });
113
- * mito.emit('user:login', { userId: '123' });
114
- * mito.emit('app:ready');
115
- *
116
- * // Biological API
117
- * mito.bind('user:login', (data) => console.log(data.userId));
118
- * mito.pulse('user:login', { userId: '123' });
119
- * ```
140
+ * Clear cached config (useful for testing).
120
141
  */
121
- declare function createEmitochondria<T extends EventMap>(options?: EmitochondriaOptions): Emitochondria<T>;
142
+ declare function clearConfigCache(): void;
122
143
 
123
- export { type Emitochondria, type EmitochondriaOptions, type EventHandler, type EventKey, type EventMap, type WildcardHandler, createEmitochondria, createEmitochondria as default };
144
+ export { type Emitochondria, type EmitochondriaConfig, type EmitochondriaOptions, type ErrorHandler, type EventHandler, type EventKey, type EventMap, type LogFormat, type LoggingOptions, type MaxListenersHandler, type TimestampFormat, type Timezone, type WildcardHandler, clearConfigCache, createEmitochondria, createEmitochondria as default };
package/dist/index.d.ts CHANGED
@@ -1,34 +1,97 @@
1
- /**
2
- * Base type for event maps.
3
- * Keys are event names, values are payload types.
4
- */
5
1
  type EventMap = Record<string, unknown>;
6
- /**
7
- * Extracts event names from an event map as a string union.
8
- */
9
2
  type EventKey<T extends EventMap> = keyof T & string;
10
- /**
11
- * Handler function for a specific event payload.
12
- * Can be sync or async.
13
- */
14
3
  type EventHandler<T> = (payload: T) => void | Promise<void>;
15
- /**
16
- * Wildcard handler that receives event name and payload.
17
- * Useful for logging/debugging.
18
- */
19
4
  type WildcardHandler<T extends EventMap> = <K extends EventKey<T>>(event: K, payload: T[K]) => void | Promise<void>;
20
- /**
21
- * Options for creating an Emitochondria instance.
22
- */
23
- interface EmitochondriaOptions {
5
+ type ErrorHandler<T extends EventMap> = (error: unknown, event: EventKey<T>, handler: EventHandler<unknown>) => void;
6
+ type MaxListenersHandler<T extends EventMap> = (event: EventKey<T>, count: number, max: number) => void;
7
+ type TimestampFormat = 'iso' | 'unix' | 'human';
8
+ type Timezone = 'utc' | 'local';
9
+ type LogFormat = 'text' | 'json';
10
+ interface LoggingOptions {
11
+ /**
12
+ * Enable timestamps in logs.
13
+ * @default false
14
+ */
15
+ timestamps?: boolean;
16
+ /**
17
+ * Timestamp format.
18
+ * - 'iso': 2025-01-13T14:30:00.000Z
19
+ * - 'unix': 1736776200000
20
+ * - 'human': Jan 13, 2:30:00 PM
21
+ * @default 'iso'
22
+ */
23
+ timestampFormat?: TimestampFormat;
24
+ /**
25
+ * Timezone for timestamps.
26
+ * - 'utc': UTC time
27
+ * - 'local': System local time
28
+ * @default 'utc'
29
+ */
30
+ timezone?: Timezone;
31
+ /**
32
+ * Enable file logging.
33
+ * ⚠️ Only works in Node.js. Browser will show a warning.
34
+ * ⚠️ Payloads may contain sensitive data — they will be written to disk.
35
+ * @default false
36
+ */
37
+ persist?: boolean;
38
+ /**
39
+ * Path to log file.
40
+ * Directory will be created if it doesn't exist.
41
+ * @default './logs/emitochondria.log'
42
+ */
43
+ path?: string;
44
+ /**
45
+ * Log file format.
46
+ * - 'text': Human-readable plain text
47
+ * - 'json': JSON lines (one JSON object per line)
48
+ * @default 'text'
49
+ */
50
+ format?: LogFormat;
51
+ /**
52
+ * Maximum log file size before rotation.
53
+ * Supports: '10MB', '1GB', '500KB'
54
+ * When exceeded, current file is renamed to .1, .2, etc.
55
+ * @default '10MB'
56
+ */
57
+ maxSize?: string;
24
58
  /**
25
- * Custom error handler for errors thrown by event handlers.
26
- *
27
- * @default - Logs to console in development, silent in production
28
- * @example 'throw' - Preserve original throwing behavior
29
- * @example (error, event) => logger.error(error)
59
+ * Log every emitted event.
60
+ * ⚠️ Can be very verbose in production!
61
+ * @default false
30
62
  */
31
- onError?: ((error: Error, event: string, handler: EventHandler<unknown>) => void) | 'throw';
63
+ logEvents?: boolean;
64
+ /**
65
+ * Log handler errors.
66
+ * @default true
67
+ */
68
+ logErrors?: boolean;
69
+ /**
70
+ * Log memory leak warnings.
71
+ * @default true
72
+ */
73
+ logWarnings?: boolean;
74
+ }
75
+ interface EmitochondriaConfig {
76
+ /**
77
+ * Maximum listeners per event before warning.
78
+ * Set to 0 to disable warnings.
79
+ * @default 10
80
+ */
81
+ maxListeners?: number;
82
+ /**
83
+ * Custom error handler for when handlers throw.
84
+ * - 'throw': Re-throw errors
85
+ * - undefined: Default (logs in dev, silent in prod)
86
+ * Note: Function handlers only work in options, not config file.
87
+ */
88
+ onError?: 'throw';
89
+ /**
90
+ * Logging configuration.
91
+ */
92
+ logging?: LoggingOptions;
93
+ }
94
+ interface EmitochondriaOptions<T extends EventMap> {
32
95
  /**
33
96
  * Maximum listeners per event before warning.
34
97
  * Set to 0 to disable warnings.
@@ -36,88 +99,46 @@ interface EmitochondriaOptions {
36
99
  */
37
100
  maxListeners?: number;
38
101
  /**
39
- * Custom handler when max listeners is exceeded.
102
+ * Custom error handler function or 'throw'.
103
+ * - ErrorHandler function: Custom error handling logic
104
+ * - 'throw': Re-throw errors
105
+ * - undefined: Default (logs in dev, silent in prod)
106
+ */
107
+ onError?: ErrorHandler<T> | 'throw';
108
+ /**
109
+ * Custom max listeners exceeded handler.
40
110
  * If not provided, logs a warning to console.
41
111
  */
42
- onMaxListenersExceeded?: (event: string, count: number, max: number) => void;
112
+ onMaxListenersExceeded?: MaxListenersHandler<T>;
113
+ /**
114
+ * Logging configuration.
115
+ */
116
+ logging?: LoggingOptions;
43
117
  }
44
- /**
45
- * The typed emitter interface.
46
- */
47
118
  interface Emitochondria<T extends EventMap> {
48
- /** Subscribe to an event. Returns unsubscribe function. */
49
119
  on<K extends EventKey<T>>(event: K, handler: EventHandler<T[K]>): () => void;
50
- /** Unsubscribe a handler from an event. */
51
120
  off<K extends EventKey<T>>(event: K, handler: EventHandler<T[K]>): void;
52
- /** Subscribe for a single emission only. */
53
121
  once<K extends EventKey<T>>(event: K, handler: EventHandler<T[K]>): () => void;
54
- /** Emit an event synchronously. */
55
122
  emit<K extends EventKey<T>>(event: K, ...payload: T[K] extends void ? [] : [T[K]]): void;
56
- /** Emit an event and await all handlers. */
57
123
  emitAsync<K extends EventKey<T>>(event: K, ...payload: T[K] extends void ? [] : [T[K]]): Promise<void>;
58
- /** Subscribe to all events (wildcard). */
59
124
  onAny(handler: WildcardHandler<T>): () => void;
60
- /** Unsubscribe a wildcard handler. */
61
125
  offAny(handler: WildcardHandler<T>): void;
62
- /** Clear handlers for a specific event or all events. */
63
126
  clear<K extends EventKey<T>>(event?: K): void;
64
- /** Get the number of listeners for an event. */
65
127
  listenerCount<K extends EventKey<T>>(event: K): number;
66
- /** Set a custom error handler at runtime. */
67
- setErrorHandler(handler: EmitochondriaOptions['onError']): void;
68
- /** Set the maximum number of listeners per event before warning. */
128
+ setErrorHandler(handler: ErrorHandler<T> | 'throw'): void;
69
129
  setMaxListeners(n: number): void;
70
- /** Get the current max listener limit. */
71
130
  getMaxListeners(): number;
72
- /** Get all event names that currently have registered listeners. */
73
131
  eventNames(): EventKey<T>[];
74
- /** Get all handlers registered for a specific event. */
75
- listeners<K extends EventKey<T>>(event: K): ReadonlyArray<EventHandler<T[K]>>;
76
- /** Get all wildcard handlers. */
77
- wildcardListeners(): ReadonlyArray<WildcardHandler<T>>;
78
- /** Check if a specific handler is registered for an event. */
132
+ listeners<K extends EventKey<T>>(event: K): EventHandler<T[K]>[];
133
+ wildcardListeners(): WildcardHandler<T>[];
79
134
  hasListener<K extends EventKey<T>>(event: K, handler: EventHandler<T[K]>): boolean;
80
- /** Alias for `on` — Bind a receptor to a signal. */
81
- bind: Emitochondria<T>['on'];
82
- /** Alias for `off` — Release a receptor. */
83
- release: Emitochondria<T>['off'];
84
- /** Alias for `emit` — Pulse energy through the system. */
85
- pulse: Emitochondria<T>['emit'];
86
- /** Alias for `emitAsync` — Trigger a signal cascade. */
87
- cascade: Emitochondria<T>['emitAsync'];
88
- /** Alias for `once` — Single spike of energy. */
89
- spike: Emitochondria<T>['once'];
90
- /** Alias for `onAny` — Membrane catches all signals. */
91
- membrane: Emitochondria<T>['onAny'];
92
- /** Alias for `clear` — Programmed cell death. */
93
- apoptosis: Emitochondria<T>['clear'];
94
- /** Alias for `listenerCount` — Count of receptors. */
95
- receptors: Emitochondria<T>['listenerCount'];
96
135
  }
136
+
137
+ declare function createEmitochondria<T extends EventMap>(options?: EmitochondriaOptions<T>): Emitochondria<T>;
138
+
97
139
  /**
98
- * Create a new typed event emitter.
99
- *
100
- * @example
101
- * ```typescript
102
- * type MyEvents = {
103
- * 'user:login': { userId: string };
104
- * 'app:ready': void;
105
- * };
106
- *
107
- * const mito = createEmitochondria<MyEvents>();
108
- *
109
- * // Standard API
110
- * mito.on('user:login', (data) => {
111
- * console.log(data.userId); // fully typed!
112
- * });
113
- * mito.emit('user:login', { userId: '123' });
114
- * mito.emit('app:ready');
115
- *
116
- * // Biological API
117
- * mito.bind('user:login', (data) => console.log(data.userId));
118
- * mito.pulse('user:login', { userId: '123' });
119
- * ```
140
+ * Clear cached config (useful for testing).
120
141
  */
121
- declare function createEmitochondria<T extends EventMap>(options?: EmitochondriaOptions): Emitochondria<T>;
142
+ declare function clearConfigCache(): void;
122
143
 
123
- export { type Emitochondria, type EmitochondriaOptions, type EventHandler, type EventKey, type EventMap, type WildcardHandler, createEmitochondria, createEmitochondria as default };
144
+ export { type Emitochondria, type EmitochondriaConfig, type EmitochondriaOptions, type ErrorHandler, type EventHandler, type EventKey, type EventMap, type LogFormat, type LoggingOptions, type MaxListenersHandler, type TimestampFormat, type Timezone, type WildcardHandler, clearConfigCache, createEmitochondria, createEmitochondria as default };
package/dist/index.js CHANGED
@@ -1,2 +1,3 @@
1
- "use strict";var E=Object.defineProperty;var h=Object.getOwnPropertyDescriptor;var w=Object.getOwnPropertyNames;var K=Object.prototype.hasOwnProperty;var x=(d,a)=>{for(var c in a)E(d,c,{get:a[c],enumerable:!0})},H=(d,a,c,o)=>{if(a&&typeof a=="object"||typeof a=="function")for(let l of w(a))!K.call(d,l)&&l!==c&&E(d,l,{get:()=>a[l],enumerable:!(o=h(a,l))||o.enumerable});return d};var k=d=>H(E({},"__esModule",{value:!0}),d);var A={};x(A,{createEmitochondria:()=>v,default:()=>g});module.exports=k(A);function v(d={}){let a=new Map,c=new Set,o=d.onError??((e,n)=>{globalThis.process?.env?.NODE_ENV!=="production"&&console.error(`[Emitochondria] Error in handler for event "${n}":`,e)}),l=d.maxListeners??10,y=d.onMaxListenersExceeded??((e,n,r)=>{console.warn(`[Emitochondria] Possible memory leak detected: ${n} listeners added for event "${e}". Maximum is ${r}. Use setMaxListeners() to increase limit.`)});function T(e,n){l>0&&n>l&&y(e,n,l)}function p(e,n,r){try{let t=e(r);return t instanceof Promise?t.catch(s=>{if(o==="throw")throw s;typeof o=="function"&&o(s,n,e)}):t}catch(t){if(o==="throw")throw t;typeof o=="function"&&o(t,n,e)}}function u(e){let n=a.get(e);return n||(n=new Set,a.set(e,n)),n}let i={on(e,n){let r=u(e);return r.add(n),T(e,r.size),()=>i.off(e,n)},off(e,n){let r=a.get(e);if(r){if(r.delete(n)){r.size===0&&a.delete(e);return}for(let t of r)if(t.__original===n){r.delete(t),r.size===0&&a.delete(e);break}}},once(e,n){let r=(t=>(i.off(e,r),n(t)));return r.__original=n,i.on(e,r)},emit(e,...n){let r=n[0];u(e).forEach(t=>{p(t,e,r)}),c.forEach(t=>{try{let s=t(e,r);s instanceof Promise&&s.catch(f=>{if(o==="throw")throw f;typeof o=="function"&&o(f,e,t)})}catch(s){if(o==="throw")throw s;typeof o=="function"&&o(s,e,t)}})},async emitAsync(e,...n){let r=n[0],t=[];u(e).forEach(s=>{let f=p(s,e,r);f instanceof Promise&&t.push(f)}),c.forEach(s=>{try{let f=s(e,r);f instanceof Promise&&t.push(f.catch(m=>{if(o==="throw")throw m;typeof o=="function"&&o(m,e,s)}))}catch(f){if(o==="throw")throw f;typeof o=="function"&&o(f,e,s)}}),await Promise.all(t)},onAny(e){return c.add(e),()=>i.offAny(e)},offAny(e){c.delete(e)},clear(e){e?a.delete(e):(a.clear(),c.clear())},listenerCount(e){return u(e).size},setErrorHandler(e){o=e},setMaxListeners(e){l=e},getMaxListeners(){return l},eventNames(){return Array.from(a.keys()).filter(e=>{let n=a.get(e);return n&&n.size>0})},listeners(e){let n=a.get(e);return n?Array.from(n).map(r=>r.__original||r):[]},wildcardListeners(){return Array.from(c)},hasListener(e,n){let r=a.get(e);if(!r)return!1;if(r.has(n))return!0;for(let t of r)if(t.__original===n)return!0;return!1},bind:null,release:null,pulse:null,cascade:null,spike:null,membrane:null,apoptosis:null,receptors:null};return i.bind=i.on,i.release=i.off,i.pulse=i.emit,i.cascade=i.emitAsync,i.spike=i.once,i.membrane=i.onAny,i.apoptosis=i.clear,i.receptors=i.listenerCount,i}var g=v;0&&(module.exports={createEmitochondria});
1
+ "use strict";var w=Object.defineProperty;var z=Object.getOwnPropertyDescriptor;var C=Object.getOwnPropertyNames;var M=Object.prototype.hasOwnProperty;var O=(a,e)=>{for(var n in e)w(a,n,{get:e[n],enumerable:!0})},W=(a,e,n,r)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of C(e))!M.call(a,i)&&i!==n&&w(a,i,{get:()=>e[i],enumerable:!(r=z(e,i))||r.enumerable});return a};var K=a=>W(w({},"__esModule",{value:!0}),a);var N={};O(N,{clearConfigCache:()=>H,createEmitochondria:()=>v,default:()=>v});module.exports=K(N);var x=typeof globalThis.process<"u"&&globalThis.process?.versions?.node!=null,_=typeof globalThis.window<"u";function $(a,e){let n=new Date;switch(a){case"unix":return String(n.getTime());case"human":return n.toLocaleString("en-US",{timeZone:e==="utc"?"UTC":void 0,month:"short",day:"numeric",hour:"numeric",minute:"2-digit",second:"2-digit",hour12:!0});default:return e==="utc"?n.toISOString():new Date(n.getTime()-n.getTimezoneOffset()*6e4).toISOString().slice(0,-1)}}function B(a){let e=a.match(/^(\d+(?:\.\d+)?)\s*(KB|MB|GB)$/i);if(!e)return 10*1024*1024;let n=parseFloat(e[1]),r=e[2].toUpperCase(),i={KB:1024,MB:1024*1024,GB:1024*1024*1024};return n*(i[r]??10*1024*1024)}var y=class{options;maxSizeBytes;browserWarned=!1;fs=null;path=null;constructor(e={}){this.options={timestamps:e.timestamps??!1,timestampFormat:e.timestampFormat??"iso",timezone:e.timezone??"utc",persist:e.persist??!1,path:e.path??"./logs/emitochondria.log",format:e.format??"text",maxSize:e.maxSize??"10MB",logEvents:e.logEvents??!1,logErrors:e.logErrors??!0,logWarnings:e.logWarnings??!0},this.maxSizeBytes=B(this.options.maxSize),this.options.persist&&this.initFileLogging()}initFileLogging(){if(_){this.browserWarned||(console.warn("[Emitochondria] File logging is not available in browser environments. Logs will only be written to console."),this.browserWarned=!0);return}if(x)try{let e=globalThis.require;if(e){this.fs=e("fs"),this.path=e("path");let n=this.path.dirname(this.options.path);this.fs.existsSync(n)||this.fs.mkdirSync(n,{recursive:!0})}}catch(e){console.error("[Emitochondria] Failed to initialize file logging:",e)}}getTimestamp(){if(this.options.timestamps)return $(this.options.timestampFormat,this.options.timezone)}formatEntry(e){let n=this.getTimestamp();if(this.options.format==="json"){let i={level:e.level,type:e.type,message:e.message};return n&&(i.timestamp=n),e.event&&(i.event=e.event),e.payload!==void 0&&(i.payload=e.payload),e.error&&(i.error=String(e.error)),JSON.stringify(i)}let r=[];return n&&r.push(`[${n}]`),r.push(`[${e.level.toUpperCase()}]`),e.event&&r.push(`[${e.event}]`),r.push(e.message),e.payload!==void 0&&r.push(`| Payload: ${JSON.stringify(e.payload)}`),e.error&&r.push(`| Error: ${e.error}`),r.join(" ")}writeToFile(e){if(!(!this.fs||!this.path||!x))try{let n=e+`
2
+ `;this.fs.existsSync(this.options.path)&&this.fs.statSync(this.options.path).size>=this.maxSizeBytes&&this.rotateLog(),this.fs.appendFileSync(this.options.path,n,"utf8")}catch(n){console.error("[Emitochondria] Failed to write to log file:",n)}}rotateLog(){if(!(!this.fs||!this.path))try{let e=1;for(;this.fs.existsSync(`${this.options.path}.${e}`);)e++;this.fs.renameSync(this.options.path,`${this.options.path}.${e}`)}catch(e){console.error("[Emitochondria] Failed to rotate log file:",e)}}logEvent(e,n){if(!this.options.logEvents)return;let r={level:"info",type:"event",event:e,message:"Event emitted",payload:n},i=this.formatEntry(r);console.log(i),this.options.persist&&this.writeToFile(i)}logError(e,n,r){if(!this.options.logErrors)return;let i={level:"error",type:"error",event:e,message:"Handler threw an error",error:n},c=this.formatEntry(i);console.error(c),this.options.persist&&this.writeToFile(c)}logWarning(e,n,r){if(!this.options.logWarnings)return;let i={level:"warn",type:"warning",event:e,message:n,payload:r},c=this.formatEntry(i);console.warn(c),this.options.persist&&this.writeToFile(c)}};var A="emitochondria.config.json",d=null;function P(){if(d!==null)return d;let a=globalThis.process;if(typeof a>"u"||!a?.versions?.node)return d={},d;try{let e=globalThis.require;if(!e)return d={},d;let n=e("fs"),i=e("path").join(a.cwd(),A),c=n.readFileSync(i,"utf-8");return d=JSON.parse(c),d}catch{return d={},d}}function L(a,e){let n={...a};for(let r in e)e[r]!==void 0&&(typeof e[r]=="object"&&e[r]!==null&&!Array.isArray(e[r])&&typeof n[r]=="object"&&n[r]!==null?n[r]=L(n[r],e[r]):n[r]=e[r]);return n}function S(a={}){let e=P();return L(e,a)}function H(){d=null}var j=(a,e,n)=>{};function v(a={}){let e=S(a),n=new y(e.logging),r=new Map,i=new Set,c=e.maxListeners??10,k=a.onMaxListenersExceeded??j,E=e.onError??"default",p=new Set,m=t=>r.get(t)??r.set(t,new Set).get(t),u=(t,s,o)=>{if(n.logError(s,t,o),E==="throw")throw t;typeof E=="function"&&E(t,s,o)},T=(t,s,o)=>{try{let l=t(o);if(l instanceof Promise)return l.catch(f=>u(f,s,t))}catch(l){u(l,s,t)}},b=(t,s)=>{if(c>0&&s>c&&!p.has(t)){p.add(t);let o=`Possible memory leak: ${s} listeners (max: ${c})`;n.logWarning(t,o,{count:s,max:c}),k(t,s,c)}},h={on(t,s){let o=m(t);return o.add(s),b(t,o.size),()=>h.off(t,s)},off(t,s){let o=m(t);if(o.delete(s)){o.size<=c&&p.delete(t);return}for(let l of o)if(l.__original===s){o.delete(l),o.size<=c&&p.delete(t);break}},once(t,s){let o=(l=>(h.off(t,o),s(l)));return o.__original=s,h.on(t,o)},emit(t,...s){let o=s[0];n.logEvent(t,o),m(t).forEach(l=>T(l,t,o)),i.forEach(l=>{try{l(t,o)}catch(f){u(f,t,l)}})},async emitAsync(t,...s){let o=s[0],l=[];n.logEvent(t,o),m(t).forEach(f=>{let g=T(f,t,o);g instanceof Promise&&l.push(g)}),i.forEach(f=>{try{let g=f(t,o);g instanceof Promise&&l.push(g.catch(F=>u(F,t,f)))}catch(g){u(g,t,f)}}),await Promise.all(l)},onAny(t){return i.add(t),()=>h.offAny(t)},offAny(t){i.delete(t)},clear(t){t?(r.delete(t),p.delete(t)):(r.clear(),i.clear(),p.clear())},listenerCount(t){return m(t).size},setErrorHandler(t){E=t},setMaxListeners(t){c=t,t===0&&p.clear()},getMaxListeners(){return c},eventNames(){return Array.from(r.keys()).filter(t=>r.get(t).size>0)},listeners(t){let s=m(t);return Array.from(s).map(o=>o.__original||o)},wildcardListeners(){return Array.from(i)},hasListener(t,s){let o=m(t);if(o.has(s))return!0;for(let l of o)if(l.__original===s)return!0;return!1}};return h}0&&(module.exports={clearConfigCache,createEmitochondria});
2
3
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Base type for event maps.\n * Keys are event names, values are payload types.\n */\nexport type EventMap = Record<string, unknown>;\n\n/**\n * Extracts event names from an event map as a string union.\n */\nexport type EventKey<T extends EventMap> = keyof T & string;\n\n/**\n * Handler function for a specific event payload.\n * Can be sync or async.\n */\nexport type EventHandler<T> = (payload: T) => void | Promise<void>;\n\n/**\n * Wildcard handler that receives event name and payload.\n * Useful for logging/debugging.\n */\nexport type WildcardHandler<T extends EventMap> = <K extends EventKey<T>>(\n event: K,\n payload: T[K]\n) => void | Promise<void>;\n\n/**\n * Internal type for wrapped handlers (used by once()).\n * Stores reference to original handler for manual removal.\n * @internal\n */\ntype WrappedHandler<T> = EventHandler<T> & {\n __original?: EventHandler<T>;\n};\n\n/**\n * Options for creating an Emitochondria instance.\n */\nexport interface EmitochondriaOptions {\n /**\n * Custom error handler for errors thrown by event handlers.\n *\n * @default - Logs to console in development, silent in production\n * @example 'throw' - Preserve original throwing behavior\n * @example (error, event) => logger.error(error)\n */\n onError?:\n | ((error: Error, event: string, handler: EventHandler<unknown>) => void)\n | 'throw';\n\n /**\n * Maximum listeners per event before warning.\n * Set to 0 to disable warnings.\n * @default 10\n */\n maxListeners?: number;\n\n /**\n * Custom handler when max listeners is exceeded.\n * If not provided, logs a warning to console.\n */\n onMaxListenersExceeded?: (event: string, count: number, max: number) => void;\n}\n\n/**\n * The typed emitter interface.\n */\nexport interface Emitochondria<T extends EventMap> {\n /** Subscribe to an event. Returns unsubscribe function. */\n on<K extends EventKey<T>>(event: K, handler: EventHandler<T[K]>): () => void;\n /** Unsubscribe a handler from an event. */\n off<K extends EventKey<T>>(event: K, handler: EventHandler<T[K]>): void;\n /** Subscribe for a single emission only. */\n once<K extends EventKey<T>>(event: K, handler: EventHandler<T[K]>): () => void;\n /** Emit an event synchronously. */\n emit<K extends EventKey<T>>(event: K, ...payload: T[K] extends void ? [] : [T[K]]): void;\n /** Emit an event and await all handlers. */\n emitAsync<K extends EventKey<T>>(event: K, ...payload: T[K] extends void ? [] : [T[K]]): Promise<void>;\n /** Subscribe to all events (wildcard). */\n onAny(handler: WildcardHandler<T>): () => void;\n /** Unsubscribe a wildcard handler. */\n offAny(handler: WildcardHandler<T>): void;\n /** Clear handlers for a specific event or all events. */\n clear<K extends EventKey<T>>(event?: K): void;\n /** Get the number of listeners for an event. */\n listenerCount<K extends EventKey<T>>(event: K): number;\n /** Set a custom error handler at runtime. */\n setErrorHandler(handler: EmitochondriaOptions['onError']): void;\n /** Set the maximum number of listeners per event before warning. */\n setMaxListeners(n: number): void;\n /** Get the current max listener limit. */\n getMaxListeners(): number;\n /** Get all event names that currently have registered listeners. */\n eventNames(): EventKey<T>[];\n /** Get all handlers registered for a specific event. */\n listeners<K extends EventKey<T>>(event: K): ReadonlyArray<EventHandler<T[K]>>;\n /** Get all wildcard handlers. */\n wildcardListeners(): ReadonlyArray<WildcardHandler<T>>;\n /** Check if a specific handler is registered for an event. */\n hasListener<K extends EventKey<T>>(event: K, handler: EventHandler<T[K]>): boolean;\n\n // Biological aliases\n /** Alias for `on` — Bind a receptor to a signal. */\n bind: Emitochondria<T>['on'];\n /** Alias for `off` — Release a receptor. */\n release: Emitochondria<T>['off'];\n /** Alias for `emit` — Pulse energy through the system. */\n pulse: Emitochondria<T>['emit'];\n /** Alias for `emitAsync` — Trigger a signal cascade. */\n cascade: Emitochondria<T>['emitAsync'];\n /** Alias for `once` — Single spike of energy. */\n spike: Emitochondria<T>['once'];\n /** Alias for `onAny` — Membrane catches all signals. */\n membrane: Emitochondria<T>['onAny'];\n /** Alias for `clear` — Programmed cell death. */\n apoptosis: Emitochondria<T>['clear'];\n /** Alias for `listenerCount` — Count of receptors. */\n receptors: Emitochondria<T>['listenerCount'];\n}\n\n/**\n * Create a new typed event emitter.\n *\n * @example\n * ```typescript\n * type MyEvents = {\n * 'user:login': { userId: string };\n * 'app:ready': void;\n * };\n *\n * const mito = createEmitochondria<MyEvents>();\n *\n * // Standard API\n * mito.on('user:login', (data) => {\n * console.log(data.userId); // fully typed!\n * });\n * mito.emit('user:login', { userId: '123' });\n * mito.emit('app:ready');\n *\n * // Biological API\n * mito.bind('user:login', (data) => console.log(data.userId));\n * mito.pulse('user:login', { userId: '123' });\n * ```\n */\nexport function createEmitochondria<T extends EventMap>(\n options: EmitochondriaOptions = {}\n): Emitochondria<T> {\n const handlers: Map<string, Set<WrappedHandler<unknown>>> = new Map();\n const wildcardHandlers: Set<WildcardHandler<T>> = new Set();\n\n // Default error handler\n let errorHandler: EmitochondriaOptions['onError'] = options.onError ?? ((error: Error, event: string) => {\n // Log in development, silent in production\n // Check for NODE_ENV in a way that works in both Node and browser\n const nodeEnv = (globalThis as any).process?.env?.NODE_ENV;\n if (nodeEnv !== 'production') {\n console.error(`[Emitochondria] Error in handler for event \"${event}\":`, error);\n }\n });\n\n // Max listeners configuration\n let maxListeners = options.maxListeners ?? 10;\n\n const onMaxListenersExceeded = options.onMaxListenersExceeded ?? ((event, count, max) => {\n console.warn(\n `[Emitochondria] Possible memory leak detected: ` +\n `${count} listeners added for event \"${event}\". ` +\n `Maximum is ${max}. Use setMaxListeners() to increase limit.`\n );\n });\n\n /**\n * Check if max listeners exceeded and warn if needed.\n */\n function checkMaxListeners(event: string, count: number): void {\n if (maxListeners > 0 && count > maxListeners) {\n onMaxListenersExceeded(event, count, maxListeners);\n }\n }\n\n /**\n * Safely execute a handler, catching errors according to configuration.\n */\n function safeCall<K extends EventKey<T>>(\n handler: EventHandler<unknown>,\n event: K,\n payload: unknown\n ): void | Promise<void> {\n try {\n const result = handler(payload);\n\n // Handle async errors\n if (result instanceof Promise) {\n return result.catch((error) => {\n if (errorHandler === 'throw') throw error;\n if (typeof errorHandler === 'function') {\n errorHandler(error, event, handler);\n }\n });\n }\n\n return result;\n } catch (error) {\n if (errorHandler === 'throw') throw error;\n if (typeof errorHandler === 'function') {\n errorHandler(error as Error, event, handler);\n }\n }\n }\n\n function getHandlers(event: string): Set<WrappedHandler<unknown>> {\n let set = handlers.get(event);\n if (!set) {\n set = new Set();\n handlers.set(event, set);\n }\n return set;\n }\n\n const e: Emitochondria<T> = {\n on(event, handler) {\n const set = getHandlers(event);\n set.add(handler as EventHandler<unknown>);\n\n // Check for potential memory leak\n checkMaxListeners(event, set.size);\n\n return () => e.off(event, handler);\n },\n\n off(event, handler) {\n const set = handlers.get(event);\n if (!set) return;\n\n // Try direct removal first (for regular handlers)\n if (set.delete(handler as EventHandler<unknown>)) {\n // Clean up empty sets to prevent memory leak\n if (set.size === 0) {\n handlers.delete(event);\n }\n return;\n }\n\n // Search for wrapped handler (for once handlers)\n for (const wrapped of set) {\n if (wrapped.__original === handler) {\n set.delete(wrapped);\n if (set.size === 0) {\n handlers.delete(event);\n }\n break;\n }\n }\n },\n\n once(event, handler) {\n const wrapper = ((payload: T[typeof event]) => {\n e.off(event, wrapper as EventHandler<T[typeof event]>);\n return handler(payload);\n }) as WrappedHandler<T[typeof event]>;\n\n // Store original reference for manual removal\n wrapper.__original = handler;\n\n return e.on(event, wrapper);\n },\n\n emit(event, ...payload) {\n const data = payload[0];\n getHandlers(event).forEach((handler) => {\n safeCall(handler, event, data);\n });\n wildcardHandlers.forEach((handler) => {\n // Wildcard handlers have different signature (event, payload)\n try {\n const result = handler(event, data as T[typeof event]);\n if (result instanceof Promise) {\n result.catch((error) => {\n if (errorHandler === 'throw') throw error;\n if (typeof errorHandler === 'function') {\n errorHandler(error, event, handler as EventHandler<unknown>);\n }\n });\n }\n } catch (error) {\n if (errorHandler === 'throw') throw error;\n if (typeof errorHandler === 'function') {\n errorHandler(error as Error, event, handler as EventHandler<unknown>);\n }\n }\n });\n },\n\n async emitAsync(event, ...payload) {\n const data = payload[0];\n const promises: Promise<void>[] = [];\n\n getHandlers(event).forEach((handler) => {\n const result = safeCall(handler, event, data);\n if (result instanceof Promise) {\n promises.push(result);\n }\n });\n\n wildcardHandlers.forEach((handler) => {\n try {\n const result = handler(event, data as T[typeof event]);\n if (result instanceof Promise) {\n promises.push(\n result.catch((error) => {\n if (errorHandler === 'throw') throw error;\n if (typeof errorHandler === 'function') {\n errorHandler(error, event, handler as EventHandler<unknown>);\n }\n })\n );\n }\n } catch (error) {\n if (errorHandler === 'throw') throw error;\n if (typeof errorHandler === 'function') {\n errorHandler(error as Error, event, handler as EventHandler<unknown>);\n }\n }\n });\n\n await Promise.all(promises);\n },\n\n onAny(handler) {\n wildcardHandlers.add(handler);\n return () => e.offAny(handler);\n },\n\n offAny(handler) {\n wildcardHandlers.delete(handler);\n },\n\n clear(event?) {\n if (event) {\n handlers.delete(event);\n } else {\n handlers.clear();\n wildcardHandlers.clear();\n }\n },\n\n listenerCount(event) {\n return getHandlers(event).size;\n },\n\n setErrorHandler(handler) {\n errorHandler = handler;\n },\n\n setMaxListeners(n) {\n maxListeners = n;\n },\n\n getMaxListeners() {\n return maxListeners;\n },\n\n eventNames() {\n return Array.from(handlers.keys()).filter(\n key => {\n const set = handlers.get(key);\n return set && set.size > 0;\n }\n ) as EventKey<T>[];\n },\n\n listeners(event) {\n const set = handlers.get(event);\n if (!set) return [];\n\n // Return unwrapped handlers for better debugging\n return Array.from(set).map(handler => {\n const wrapped = handler as WrappedHandler<unknown>;\n return (wrapped.__original || handler) as EventHandler<T[typeof event]>;\n });\n },\n\n wildcardListeners() {\n return Array.from(wildcardHandlers);\n },\n\n hasListener(event, handler) {\n const set = handlers.get(event);\n if (!set) return false;\n\n // Check direct match\n if (set.has(handler as EventHandler<unknown>)) {\n return true;\n }\n\n // Check wrapped handlers (once)\n for (const wrapped of set) {\n if (wrapped.__original === handler) {\n return true;\n }\n }\n\n return false;\n },\n\n // ⚡ Biological aliases (zero-cost: just references)\n bind: null as unknown as Emitochondria<T>['on'],\n release: null as unknown as Emitochondria<T>['off'],\n pulse: null as unknown as Emitochondria<T>['emit'],\n cascade: null as unknown as Emitochondria<T>['emitAsync'],\n spike: null as unknown as Emitochondria<T>['once'],\n membrane: null as unknown as Emitochondria<T>['onAny'],\n apoptosis: null as unknown as Emitochondria<T>['clear'],\n receptors: null as unknown as Emitochondria<T>['listenerCount'],\n };\n\n // Assign aliases (smaller than repeating in object literal)\n e.bind = e.on;\n e.release = e.off;\n e.pulse = e.emit;\n e.cascade = e.emitAsync;\n e.spike = e.once;\n e.membrane = e.onAny;\n e.apoptosis = e.clear;\n e.receptors = e.listenerCount;\n\n return e;\n}\n\n// Default export for convenience\nexport default createEmitochondria;\n"],"mappings":"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","names":["index_exports","__export","createEmitochondria","index_default","__toCommonJS","options","handlers","wildcardHandlers","errorHandler","error","event","maxListeners","onMaxListenersExceeded","count","max","checkMaxListeners","safeCall","handler","payload","result","getHandlers","set","e","wrapped","wrapper","data","promises","n","key"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/logger.ts","../src/config.ts","../src/emitter.ts"],"sourcesContent":["// Core\nexport { createEmitochondria } from './emitter.js';\n\n// Types\nexport type {\n EventMap,\n EventKey,\n EventHandler,\n WildcardHandler,\n ErrorHandler,\n MaxListenersHandler,\n EmitochondriaConfig,\n EmitochondriaOptions,\n Emitochondria,\n LoggingOptions,\n TimestampFormat,\n Timezone,\n LogFormat,\n} from './types.js';\n\n// Utilities (for testing/advanced use)\nexport { clearConfigCache } from './config.js';\n\n// Default export\nexport { createEmitochondria as default } from './emitter.js';\n","import { LoggingOptions, TimestampFormat, Timezone } from './types.js';\n\n// ============================================================\n// ENVIRONMENT DETECTION\n// ============================================================\n\nconst isNode = typeof (globalThis as any).process !== 'undefined'\n && (globalThis as any).process?.versions?.node != null;\n\nconst isBrowser = typeof (globalThis as any).window !== 'undefined';\n\n// ============================================================\n// TIMESTAMP FORMATTING\n// ============================================================\n\nfunction formatTimestamp(format: TimestampFormat, timezone: Timezone): string {\n const date = new Date();\n\n switch (format) {\n case 'unix':\n return String(date.getTime());\n\n case 'human':\n return date.toLocaleString('en-US', {\n timeZone: timezone === 'utc' ? 'UTC' : undefined,\n month: 'short',\n day: 'numeric',\n hour: 'numeric',\n minute: '2-digit',\n second: '2-digit',\n hour12: true,\n });\n\n case 'iso':\n default:\n return timezone === 'utc'\n ? date.toISOString()\n : new Date(date.getTime() - date.getTimezoneOffset() * 60000)\n .toISOString()\n .slice(0, -1); // Remove 'Z' for local\n }\n}\n\n// ============================================================\n// SIZE PARSING\n// ============================================================\n\nfunction parseSize(size: string): number {\n const match = size.match(/^(\\d+(?:\\.\\d+)?)\\s*(KB|MB|GB)$/i);\n if (!match) return 10 * 1024 * 1024; // Default 10MB\n\n const value = parseFloat(match[1]!);\n const unit = match[2]!.toUpperCase();\n\n const multipliers: Record<string, number> = {\n 'KB': 1024,\n 'MB': 1024 * 1024,\n 'GB': 1024 * 1024 * 1024,\n };\n\n return value * (multipliers[unit] ?? 10 * 1024 * 1024);\n}\n\n// ============================================================\n// LOG ENTRY TYPES\n// ============================================================\n\nexport type LogLevel = 'info' | 'warn' | 'error';\n\nexport interface LogEntry {\n level: LogLevel;\n type: 'event' | 'error' | 'warning';\n event?: string;\n message: string;\n payload?: unknown;\n error?: unknown;\n}\n\n// ============================================================\n// LOGGER CLASS\n// ============================================================\n\nexport class Logger {\n private options: Required<LoggingOptions>;\n private maxSizeBytes: number;\n private browserWarned = false;\n private fs: any = null;\n private path: any = null;\n\n constructor(options: LoggingOptions = {}) {\n this.options = {\n timestamps: options.timestamps ?? false,\n timestampFormat: options.timestampFormat ?? 'iso',\n timezone: options.timezone ?? 'utc',\n persist: options.persist ?? false,\n path: options.path ?? './logs/emitochondria.log',\n format: options.format ?? 'text',\n maxSize: options.maxSize ?? '10MB',\n logEvents: options.logEvents ?? false,\n logErrors: options.logErrors ?? true,\n logWarnings: options.logWarnings ?? true,\n };\n\n this.maxSizeBytes = parseSize(this.options.maxSize);\n\n if (this.options.persist) {\n this.initFileLogging();\n }\n }\n\n private initFileLogging(): void {\n if (isBrowser) {\n if (!this.browserWarned) {\n console.warn(\n '[Emitochondria] File logging is not available in browser environments. ' +\n 'Logs will only be written to console.'\n );\n this.browserWarned = true;\n }\n return;\n }\n\n if (!isNode) return;\n\n try {\n // Require fs and path synchronously\n const req = (globalThis as any).require;\n if (req) {\n this.fs = req('fs');\n this.path = req('path');\n\n // Create directory if needed\n const dir = this.path.dirname(this.options.path);\n if (!this.fs.existsSync(dir)) {\n this.fs.mkdirSync(dir, { recursive: true });\n }\n }\n } catch (err) {\n console.error('[Emitochondria] Failed to initialize file logging:', err);\n }\n }\n\n private getTimestamp(): string | undefined {\n if (!this.options.timestamps) return undefined;\n return formatTimestamp(this.options.timestampFormat, this.options.timezone);\n }\n\n private formatEntry(entry: LogEntry): string {\n const timestamp = this.getTimestamp();\n\n if (this.options.format === 'json') {\n const obj: any = {\n level: entry.level,\n type: entry.type,\n message: entry.message,\n };\n if (timestamp) obj.timestamp = timestamp;\n if (entry.event) obj.event = entry.event;\n if (entry.payload !== undefined) obj.payload = entry.payload;\n if (entry.error) obj.error = String(entry.error);\n return JSON.stringify(obj);\n }\n\n // Text format\n const parts: string[] = [];\n if (timestamp) parts.push(`[${timestamp}]`);\n parts.push(`[${entry.level.toUpperCase()}]`);\n if (entry.event) parts.push(`[${entry.event}]`);\n parts.push(entry.message);\n if (entry.payload !== undefined) {\n parts.push(`| Payload: ${JSON.stringify(entry.payload)}`);\n }\n if (entry.error) {\n parts.push(`| Error: ${entry.error}`);\n }\n\n return parts.join(' ');\n }\n\n private writeToFile(content: string): void {\n if (!this.fs || !this.path || !isNode) return;\n\n try {\n const line = content + '\\n';\n\n // Check if rotation needed\n if (this.fs.existsSync(this.options.path)) {\n const stats = this.fs.statSync(this.options.path);\n if (stats.size >= this.maxSizeBytes) {\n this.rotateLog();\n }\n }\n\n // Sync write — simple, ordered, reliable\n this.fs.appendFileSync(this.options.path, line, 'utf8');\n } catch (err) {\n console.error('[Emitochondria] Failed to write to log file:', err);\n }\n }\n\n private rotateLog(): void {\n if (!this.fs || !this.path) return;\n\n try {\n // Find next rotation number\n let rotationNum = 1;\n while (this.fs.existsSync(`${this.options.path}.${rotationNum}`)) {\n rotationNum++;\n }\n\n // Rename current file\n this.fs.renameSync(this.options.path, `${this.options.path}.${rotationNum}`);\n } catch (err) {\n console.error('[Emitochondria] Failed to rotate log file:', err);\n }\n }\n\n // ============================================================\n // PUBLIC LOGGING METHODS\n // ============================================================\n\n logEvent(event: string, payload: unknown): void {\n if (!this.options.logEvents) return;\n\n const entry: LogEntry = {\n level: 'info',\n type: 'event',\n event,\n message: 'Event emitted',\n payload,\n };\n\n const formatted = this.formatEntry(entry);\n console.log(formatted);\n\n if (this.options.persist) {\n this.writeToFile(formatted);\n }\n }\n\n logError(event: string, error: unknown, _handler?: unknown): void {\n if (!this.options.logErrors) return;\n\n const entry: LogEntry = {\n level: 'error',\n type: 'error',\n event,\n message: 'Handler threw an error',\n error,\n };\n\n const formatted = this.formatEntry(entry);\n console.error(formatted);\n\n if (this.options.persist) {\n this.writeToFile(formatted);\n }\n }\n\n logWarning(event: string, message: string, details?: Record<string, unknown>): void {\n if (!this.options.logWarnings) return;\n\n const entry: LogEntry = {\n level: 'warn',\n type: 'warning',\n event,\n message,\n payload: details,\n };\n\n const formatted = this.formatEntry(entry);\n console.warn(formatted);\n\n if (this.options.persist) {\n this.writeToFile(formatted);\n }\n }\n}\n","import { EmitochondriaConfig, EmitochondriaOptions, EventMap } from './types.js';\n\n// ============================================================\n// CONFIG FILE LOADING\n// ============================================================\n\nconst CONFIG_FILENAME = 'emitochondria.config.json';\n\nlet cachedConfig: EmitochondriaConfig | null = null;\n\n/**\n * Synchronous version for initial load.\n * Falls back to empty config if loading fails.\n */\nfunction loadConfigFileSync(): EmitochondriaConfig {\n if (cachedConfig !== null) return cachedConfig;\n\n const proc = (globalThis as any).process;\n if (typeof proc === 'undefined' || !proc?.versions?.node) {\n cachedConfig = {};\n return cachedConfig;\n }\n\n try {\n const req = (globalThis as any).require;\n if (!req) {\n cachedConfig = {};\n return cachedConfig;\n }\n\n const fs = req('fs');\n const path = req('path');\n\n const configPath = path.join(proc.cwd(), CONFIG_FILENAME);\n const content = fs.readFileSync(configPath, 'utf-8');\n cachedConfig = JSON.parse(content) as EmitochondriaConfig;\n\n return cachedConfig;\n } catch {\n cachedConfig = {};\n return cachedConfig;\n }\n}\n\n// ============================================================\n// CONFIG MERGING\n// ============================================================\n\n/**\n * Deep merge two objects, with second taking precedence.\n */\nfunction deepMerge<T extends Record<string, any>>(base: T, override: Partial<T>): T {\n const result = { ...base };\n\n for (const key in override) {\n if (override[key] !== undefined) {\n if (\n typeof override[key] === 'object' &&\n override[key] !== null &&\n !Array.isArray(override[key]) &&\n typeof result[key] === 'object' &&\n result[key] !== null\n ) {\n result[key] = deepMerge(result[key], override[key]);\n } else {\n result[key] = override[key] as T[typeof key];\n }\n }\n }\n\n return result;\n}\n\n/**\n * Merge file config with options.\n * Options take precedence over file config.\n */\nexport function mergeConfig<T extends EventMap>(\n options: EmitochondriaOptions<T> = {}\n): EmitochondriaOptions<T> {\n const fileConfig = loadConfigFileSync();\n // Type assertion is safe here because we control both inputs\n const merged = deepMerge(fileConfig as any, options as any);\n return merged as EmitochondriaOptions<T>;\n}\n\n/**\n * Clear cached config (useful for testing).\n */\nexport function clearConfigCache(): void {\n cachedConfig = null;\n}\n","import {\n EventMap,\n EventKey,\n EventHandler,\n WildcardHandler,\n ErrorHandler,\n EmitochondriaOptions,\n Emitochondria,\n} from './types.js';\nimport { Logger } from './logger.js';\nimport { mergeConfig } from './config.js';\n\n// ============================================================\n// DEFAULT HANDLERS\n// ============================================================\n\nconst defaultMaxListenersHandler = <T extends EventMap>(\n _event: EventKey<T>,\n _count: number,\n _max: number\n): void => {\n // Handled by logger\n};\n\n// ============================================================\n// FACTORY FUNCTION\n// ============================================================\n\nexport function createEmitochondria<T extends EventMap>(\n options: EmitochondriaOptions<T> = {}\n): Emitochondria<T> {\n // Merge file config with options\n const config = mergeConfig(options);\n\n // Initialize logger\n const logger = new Logger(config.logging);\n\n // Handler storage\n const handlers: Map<string, Set<EventHandler<unknown>>> = new Map();\n const wildcards: Set<WildcardHandler<T>> = new Set();\n\n // Configuration state\n let maxListeners = config.maxListeners ?? 10;\n let onMaxListenersExceeded = options.onMaxListenersExceeded ?? defaultMaxListenersHandler;\n let errorHandler: ErrorHandler<T> | 'throw' | 'default' = config.onError ?? 'default';\n\n // Memory leak tracking\n const warned = new Set<string>();\n\n // ============================================================\n // HELPER FUNCTIONS\n // ============================================================\n\n // Track original handlers for once() wrappers\n type WrappedHandler<T> = EventHandler<T> & { __original?: EventHandler<T> };\n\n const get = (e: string) => handlers.get(e) ?? handlers.set(e, new Set()).get(e)!;\n\n const handleError = <K extends EventKey<T>>(\n error: unknown,\n event: K,\n handler: EventHandler<unknown>\n ): void => {\n logger.logError(event, error, handler);\n\n if (errorHandler === 'throw') {\n throw error;\n } else if (typeof errorHandler === 'function') {\n errorHandler(error, event, handler);\n }\n // 'default' = just log (already done above)\n };\n\n const safeCall = <K extends EventKey<T>>(\n handler: EventHandler<unknown>,\n event: K,\n payload: unknown\n ): void | Promise<void> => {\n try {\n const result = handler(payload);\n if (result instanceof Promise) {\n return result.catch((err) => handleError(err, event, handler));\n }\n } catch (err) {\n handleError(err, event, handler);\n }\n };\n\n const checkMaxListeners = <K extends EventKey<T>>(event: K, count: number): void => {\n if (maxListeners > 0 && count > maxListeners && !warned.has(event)) {\n warned.add(event);\n\n const message = `Possible memory leak: ${count} listeners (max: ${maxListeners})`;\n logger.logWarning(event, message, { count, max: maxListeners });\n\n onMaxListenersExceeded(event, count, maxListeners);\n }\n };\n\n // ============================================================\n // EMITTER IMPLEMENTATION\n // ============================================================\n\n const emitter: Emitochondria<T> = {\n on(event, handler) {\n const set = get(event);\n set.add(handler as EventHandler<unknown>);\n checkMaxListeners(event, set.size);\n return () => emitter.off(event, handler);\n },\n\n off(event, handler) {\n const set = get(event);\n\n // Try direct removal first\n if (set.delete(handler as EventHandler<unknown>)) {\n if (set.size <= maxListeners) {\n warned.delete(event);\n }\n return;\n }\n\n // Search for wrapped handler (for once handlers)\n for (const wrapped of set) {\n const w = wrapped as WrappedHandler<unknown>;\n if (w.__original === handler) {\n set.delete(wrapped);\n if (set.size <= maxListeners) {\n warned.delete(event);\n }\n break;\n }\n }\n },\n\n once(event, handler) {\n const wrapper = ((payload: T[typeof event]) => {\n emitter.off(event, wrapper as EventHandler<T[typeof event]>);\n return handler(payload);\n }) as WrappedHandler<T[typeof event]>;\n\n // Store original reference for manual removal\n wrapper.__original = handler;\n\n return emitter.on(event, wrapper);\n },\n\n emit(event, ...payload) {\n const data = payload[0];\n\n logger.logEvent(event, data);\n\n get(event).forEach((h) => safeCall(h, event, data));\n wildcards.forEach((h) => {\n try {\n h(event, data as T[typeof event]);\n } catch (err) {\n handleError(err, event, h as unknown as EventHandler<unknown>);\n }\n });\n },\n\n async emitAsync(event, ...payload) {\n const data = payload[0];\n const promises: Promise<void>[] = [];\n\n logger.logEvent(event, data);\n\n get(event).forEach((h) => {\n const result = safeCall(h, event, data);\n if (result instanceof Promise) promises.push(result);\n });\n\n wildcards.forEach((h) => {\n try {\n const result = h(event, data as T[typeof event]);\n if (result instanceof Promise) {\n promises.push(result.catch((err) =>\n handleError(err, event, h as unknown as EventHandler<unknown>)\n ));\n }\n } catch (err) {\n handleError(err, event, h as unknown as EventHandler<unknown>);\n }\n });\n\n await Promise.all(promises);\n },\n\n onAny(handler) {\n wildcards.add(handler);\n return () => emitter.offAny(handler);\n },\n\n offAny(handler) {\n wildcards.delete(handler);\n },\n\n clear(event?) {\n if (event) {\n handlers.delete(event);\n warned.delete(event);\n } else {\n handlers.clear();\n wildcards.clear();\n warned.clear();\n }\n },\n\n listenerCount(event) {\n return get(event).size;\n },\n\n setErrorHandler(handler) {\n errorHandler = handler;\n },\n\n setMaxListeners(n) {\n maxListeners = n;\n if (n === 0) warned.clear();\n },\n\n getMaxListeners() {\n return maxListeners;\n },\n\n eventNames() {\n return Array.from(handlers.keys()).filter(\n (k) => handlers.get(k)!.size > 0\n ) as EventKey<T>[];\n },\n\n listeners(event) {\n const set = get(event);\n // Return unwrapped handlers for better debugging\n return Array.from(set).map(handler => {\n const wrapped = handler as WrappedHandler<unknown>;\n return (wrapped.__original || handler) as EventHandler<T[typeof event]>;\n });\n },\n\n wildcardListeners() {\n return Array.from(wildcards);\n },\n\n hasListener(event, handler) {\n const set = get(event);\n\n // Check direct match\n if (set.has(handler as EventHandler<unknown>)) {\n return true;\n }\n\n // Check wrapped handlers (once)\n for (const wrapped of set) {\n const w = wrapped as WrappedHandler<unknown>;\n if (w.__original === handler) {\n return true;\n }\n }\n\n return false;\n },\n };\n\n return emitter;\n}\n"],"mappings":"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","names":["index_exports","__export","clearConfigCache","createEmitochondria","__toCommonJS","isNode","isBrowser","formatTimestamp","format","timezone","date","parseSize","size","match","value","unit","multipliers","Logger","options","req","dir","err","entry","timestamp","obj","parts","content","line","rotationNum","event","payload","formatted","error","_handler","message","details","CONFIG_FILENAME","cachedConfig","loadConfigFileSync","proc","req","fs","configPath","content","deepMerge","base","override","result","key","mergeConfig","options","fileConfig","clearConfigCache","defaultMaxListenersHandler","_event","_count","_max","createEmitochondria","options","config","mergeConfig","logger","Logger","handlers","wildcards","maxListeners","onMaxListenersExceeded","errorHandler","warned","get","e","handleError","error","event","handler","safeCall","payload","result","err","checkMaxListeners","count","message","emitter","set","wrapped","wrapper","data","h","promises","n","k"]}
package/dist/index.mjs CHANGED
@@ -1,2 +1,3 @@
1
- function y(u={}){let s=new Map,c=new Set,a=u.onError??((e,n)=>{globalThis.process?.env?.NODE_ENV!=="production"&&console.error(`[Emitochondria] Error in handler for event "${n}":`,e)}),l=u.maxListeners??10,m=u.onMaxListenersExceeded??((e,n,r)=>{console.warn(`[Emitochondria] Possible memory leak detected: ${n} listeners added for event "${e}". Maximum is ${r}. Use setMaxListeners() to increase limit.`)});function v(e,n){l>0&&n>l&&m(e,n,l)}function E(e,n,r){try{let t=e(r);return t instanceof Promise?t.catch(i=>{if(a==="throw")throw i;typeof a=="function"&&a(i,n,e)}):t}catch(t){if(a==="throw")throw t;typeof a=="function"&&a(t,n,e)}}function f(e){let n=s.get(e);return n||(n=new Set,s.set(e,n)),n}let o={on(e,n){let r=f(e);return r.add(n),v(e,r.size),()=>o.off(e,n)},off(e,n){let r=s.get(e);if(r){if(r.delete(n)){r.size===0&&s.delete(e);return}for(let t of r)if(t.__original===n){r.delete(t),r.size===0&&s.delete(e);break}}},once(e,n){let r=(t=>(o.off(e,r),n(t)));return r.__original=n,o.on(e,r)},emit(e,...n){let r=n[0];f(e).forEach(t=>{E(t,e,r)}),c.forEach(t=>{try{let i=t(e,r);i instanceof Promise&&i.catch(d=>{if(a==="throw")throw d;typeof a=="function"&&a(d,e,t)})}catch(i){if(a==="throw")throw i;typeof a=="function"&&a(i,e,t)}})},async emitAsync(e,...n){let r=n[0],t=[];f(e).forEach(i=>{let d=E(i,e,r);d instanceof Promise&&t.push(d)}),c.forEach(i=>{try{let d=i(e,r);d instanceof Promise&&t.push(d.catch(p=>{if(a==="throw")throw p;typeof a=="function"&&a(p,e,i)}))}catch(d){if(a==="throw")throw d;typeof a=="function"&&a(d,e,i)}}),await Promise.all(t)},onAny(e){return c.add(e),()=>o.offAny(e)},offAny(e){c.delete(e)},clear(e){e?s.delete(e):(s.clear(),c.clear())},listenerCount(e){return f(e).size},setErrorHandler(e){a=e},setMaxListeners(e){l=e},getMaxListeners(){return l},eventNames(){return Array.from(s.keys()).filter(e=>{let n=s.get(e);return n&&n.size>0})},listeners(e){let n=s.get(e);return n?Array.from(n).map(r=>r.__original||r):[]},wildcardListeners(){return Array.from(c)},hasListener(e,n){let r=s.get(e);if(!r)return!1;if(r.has(n))return!0;for(let t of r)if(t.__original===n)return!0;return!1},bind:null,release:null,pulse:null,cascade:null,spike:null,membrane:null,apoptosis:null,receptors:null};return o.bind=o.on,o.release=o.off,o.pulse=o.emit,o.cascade=o.emitAsync,o.spike=o.once,o.membrane=o.onAny,o.apoptosis=o.clear,o.receptors=o.listenerCount,o}var T=y;export{y as createEmitochondria,T as default};
1
+ var v=typeof globalThis.process<"u"&&globalThis.process?.versions?.node!=null,b=typeof globalThis.window<"u";function F(c,e){let n=new Date;switch(c){case"unix":return String(n.getTime());case"human":return n.toLocaleString("en-US",{timeZone:e==="utc"?"UTC":void 0,month:"short",day:"numeric",hour:"numeric",minute:"2-digit",second:"2-digit",hour12:!0});default:return e==="utc"?n.toISOString():new Date(n.getTime()-n.getTimezoneOffset()*6e4).toISOString().slice(0,-1)}}function z(c){let e=c.match(/^(\d+(?:\.\d+)?)\s*(KB|MB|GB)$/i);if(!e)return 10*1024*1024;let n=parseFloat(e[1]),r=e[2].toUpperCase(),s={KB:1024,MB:1024*1024,GB:1024*1024*1024};return n*(s[r]??10*1024*1024)}var y=class{options;maxSizeBytes;browserWarned=!1;fs=null;path=null;constructor(e={}){this.options={timestamps:e.timestamps??!1,timestampFormat:e.timestampFormat??"iso",timezone:e.timezone??"utc",persist:e.persist??!1,path:e.path??"./logs/emitochondria.log",format:e.format??"text",maxSize:e.maxSize??"10MB",logEvents:e.logEvents??!1,logErrors:e.logErrors??!0,logWarnings:e.logWarnings??!0},this.maxSizeBytes=z(this.options.maxSize),this.options.persist&&this.initFileLogging()}initFileLogging(){if(b){this.browserWarned||(console.warn("[Emitochondria] File logging is not available in browser environments. Logs will only be written to console."),this.browserWarned=!0);return}if(v)try{let e=globalThis.require;if(e){this.fs=e("fs"),this.path=e("path");let n=this.path.dirname(this.options.path);this.fs.existsSync(n)||this.fs.mkdirSync(n,{recursive:!0})}}catch(e){console.error("[Emitochondria] Failed to initialize file logging:",e)}}getTimestamp(){if(this.options.timestamps)return F(this.options.timestampFormat,this.options.timezone)}formatEntry(e){let n=this.getTimestamp();if(this.options.format==="json"){let s={level:e.level,type:e.type,message:e.message};return n&&(s.timestamp=n),e.event&&(s.event=e.event),e.payload!==void 0&&(s.payload=e.payload),e.error&&(s.error=String(e.error)),JSON.stringify(s)}let r=[];return n&&r.push(`[${n}]`),r.push(`[${e.level.toUpperCase()}]`),e.event&&r.push(`[${e.event}]`),r.push(e.message),e.payload!==void 0&&r.push(`| Payload: ${JSON.stringify(e.payload)}`),e.error&&r.push(`| Error: ${e.error}`),r.join(" ")}writeToFile(e){if(!(!this.fs||!this.path||!v))try{let n=e+`
2
+ `;this.fs.existsSync(this.options.path)&&this.fs.statSync(this.options.path).size>=this.maxSizeBytes&&this.rotateLog(),this.fs.appendFileSync(this.options.path,n,"utf8")}catch(n){console.error("[Emitochondria] Failed to write to log file:",n)}}rotateLog(){if(!(!this.fs||!this.path))try{let e=1;for(;this.fs.existsSync(`${this.options.path}.${e}`);)e++;this.fs.renameSync(this.options.path,`${this.options.path}.${e}`)}catch(e){console.error("[Emitochondria] Failed to rotate log file:",e)}}logEvent(e,n){if(!this.options.logEvents)return;let r={level:"info",type:"event",event:e,message:"Event emitted",payload:n},s=this.formatEntry(r);console.log(s),this.options.persist&&this.writeToFile(s)}logError(e,n,r){if(!this.options.logErrors)return;let s={level:"error",type:"error",event:e,message:"Handler threw an error",error:n},l=this.formatEntry(s);console.error(l),this.options.persist&&this.writeToFile(l)}logWarning(e,n,r){if(!this.options.logWarnings)return;let s={level:"warn",type:"warning",event:e,message:n,payload:r},l=this.formatEntry(s);console.warn(l),this.options.persist&&this.writeToFile(l)}};var C="emitochondria.config.json",d=null;function M(){if(d!==null)return d;let c=globalThis.process;if(typeof c>"u"||!c?.versions?.node)return d={},d;try{let e=globalThis.require;if(!e)return d={},d;let n=e("fs"),s=e("path").join(c.cwd(),C),l=n.readFileSync(s,"utf-8");return d=JSON.parse(l),d}catch{return d={},d}}function T(c,e){let n={...c};for(let r in e)e[r]!==void 0&&(typeof e[r]=="object"&&e[r]!==null&&!Array.isArray(e[r])&&typeof n[r]=="object"&&n[r]!==null?n[r]=T(n[r],e[r]):n[r]=e[r]);return n}function x(c={}){let e=M();return T(e,c)}function O(){d=null}var W=(c,e,n)=>{};function L(c={}){let e=x(c),n=new y(e.logging),r=new Map,s=new Set,l=e.maxListeners??10,S=c.onMaxListenersExceeded??W,E=e.onError??"default",p=new Set,m=t=>r.get(t)??r.set(t,new Set).get(t),u=(t,i,o)=>{if(n.logError(i,t,o),E==="throw")throw t;typeof E=="function"&&E(t,i,o)},w=(t,i,o)=>{try{let a=t(o);if(a instanceof Promise)return a.catch(f=>u(f,i,t))}catch(a){u(a,i,t)}},H=(t,i)=>{if(l>0&&i>l&&!p.has(t)){p.add(t);let o=`Possible memory leak: ${i} listeners (max: ${l})`;n.logWarning(t,o,{count:i,max:l}),S(t,i,l)}},h={on(t,i){let o=m(t);return o.add(i),H(t,o.size),()=>h.off(t,i)},off(t,i){let o=m(t);if(o.delete(i)){o.size<=l&&p.delete(t);return}for(let a of o)if(a.__original===i){o.delete(a),o.size<=l&&p.delete(t);break}},once(t,i){let o=(a=>(h.off(t,o),i(a)));return o.__original=i,h.on(t,o)},emit(t,...i){let o=i[0];n.logEvent(t,o),m(t).forEach(a=>w(a,t,o)),s.forEach(a=>{try{a(t,o)}catch(f){u(f,t,a)}})},async emitAsync(t,...i){let o=i[0],a=[];n.logEvent(t,o),m(t).forEach(f=>{let g=w(f,t,o);g instanceof Promise&&a.push(g)}),s.forEach(f=>{try{let g=f(t,o);g instanceof Promise&&a.push(g.catch(k=>u(k,t,f)))}catch(g){u(g,t,f)}}),await Promise.all(a)},onAny(t){return s.add(t),()=>h.offAny(t)},offAny(t){s.delete(t)},clear(t){t?(r.delete(t),p.delete(t)):(r.clear(),s.clear(),p.clear())},listenerCount(t){return m(t).size},setErrorHandler(t){E=t},setMaxListeners(t){l=t,t===0&&p.clear()},getMaxListeners(){return l},eventNames(){return Array.from(r.keys()).filter(t=>r.get(t).size>0)},listeners(t){let i=m(t);return Array.from(i).map(o=>o.__original||o)},wildcardListeners(){return Array.from(s)},hasListener(t,i){let o=m(t);if(o.has(i))return!0;for(let a of o)if(a.__original===i)return!0;return!1}};return h}export{O as clearConfigCache,L as createEmitochondria,L as default};
2
3
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * Base type for event maps.\n * Keys are event names, values are payload types.\n */\nexport type EventMap = Record<string, unknown>;\n\n/**\n * Extracts event names from an event map as a string union.\n */\nexport type EventKey<T extends EventMap> = keyof T & string;\n\n/**\n * Handler function for a specific event payload.\n * Can be sync or async.\n */\nexport type EventHandler<T> = (payload: T) => void | Promise<void>;\n\n/**\n * Wildcard handler that receives event name and payload.\n * Useful for logging/debugging.\n */\nexport type WildcardHandler<T extends EventMap> = <K extends EventKey<T>>(\n event: K,\n payload: T[K]\n) => void | Promise<void>;\n\n/**\n * Internal type for wrapped handlers (used by once()).\n * Stores reference to original handler for manual removal.\n * @internal\n */\ntype WrappedHandler<T> = EventHandler<T> & {\n __original?: EventHandler<T>;\n};\n\n/**\n * Options for creating an Emitochondria instance.\n */\nexport interface EmitochondriaOptions {\n /**\n * Custom error handler for errors thrown by event handlers.\n *\n * @default - Logs to console in development, silent in production\n * @example 'throw' - Preserve original throwing behavior\n * @example (error, event) => logger.error(error)\n */\n onError?:\n | ((error: Error, event: string, handler: EventHandler<unknown>) => void)\n | 'throw';\n\n /**\n * Maximum listeners per event before warning.\n * Set to 0 to disable warnings.\n * @default 10\n */\n maxListeners?: number;\n\n /**\n * Custom handler when max listeners is exceeded.\n * If not provided, logs a warning to console.\n */\n onMaxListenersExceeded?: (event: string, count: number, max: number) => void;\n}\n\n/**\n * The typed emitter interface.\n */\nexport interface Emitochondria<T extends EventMap> {\n /** Subscribe to an event. Returns unsubscribe function. */\n on<K extends EventKey<T>>(event: K, handler: EventHandler<T[K]>): () => void;\n /** Unsubscribe a handler from an event. */\n off<K extends EventKey<T>>(event: K, handler: EventHandler<T[K]>): void;\n /** Subscribe for a single emission only. */\n once<K extends EventKey<T>>(event: K, handler: EventHandler<T[K]>): () => void;\n /** Emit an event synchronously. */\n emit<K extends EventKey<T>>(event: K, ...payload: T[K] extends void ? [] : [T[K]]): void;\n /** Emit an event and await all handlers. */\n emitAsync<K extends EventKey<T>>(event: K, ...payload: T[K] extends void ? [] : [T[K]]): Promise<void>;\n /** Subscribe to all events (wildcard). */\n onAny(handler: WildcardHandler<T>): () => void;\n /** Unsubscribe a wildcard handler. */\n offAny(handler: WildcardHandler<T>): void;\n /** Clear handlers for a specific event or all events. */\n clear<K extends EventKey<T>>(event?: K): void;\n /** Get the number of listeners for an event. */\n listenerCount<K extends EventKey<T>>(event: K): number;\n /** Set a custom error handler at runtime. */\n setErrorHandler(handler: EmitochondriaOptions['onError']): void;\n /** Set the maximum number of listeners per event before warning. */\n setMaxListeners(n: number): void;\n /** Get the current max listener limit. */\n getMaxListeners(): number;\n /** Get all event names that currently have registered listeners. */\n eventNames(): EventKey<T>[];\n /** Get all handlers registered for a specific event. */\n listeners<K extends EventKey<T>>(event: K): ReadonlyArray<EventHandler<T[K]>>;\n /** Get all wildcard handlers. */\n wildcardListeners(): ReadonlyArray<WildcardHandler<T>>;\n /** Check if a specific handler is registered for an event. */\n hasListener<K extends EventKey<T>>(event: K, handler: EventHandler<T[K]>): boolean;\n\n // Biological aliases\n /** Alias for `on` — Bind a receptor to a signal. */\n bind: Emitochondria<T>['on'];\n /** Alias for `off` — Release a receptor. */\n release: Emitochondria<T>['off'];\n /** Alias for `emit` — Pulse energy through the system. */\n pulse: Emitochondria<T>['emit'];\n /** Alias for `emitAsync` — Trigger a signal cascade. */\n cascade: Emitochondria<T>['emitAsync'];\n /** Alias for `once` — Single spike of energy. */\n spike: Emitochondria<T>['once'];\n /** Alias for `onAny` — Membrane catches all signals. */\n membrane: Emitochondria<T>['onAny'];\n /** Alias for `clear` — Programmed cell death. */\n apoptosis: Emitochondria<T>['clear'];\n /** Alias for `listenerCount` — Count of receptors. */\n receptors: Emitochondria<T>['listenerCount'];\n}\n\n/**\n * Create a new typed event emitter.\n *\n * @example\n * ```typescript\n * type MyEvents = {\n * 'user:login': { userId: string };\n * 'app:ready': void;\n * };\n *\n * const mito = createEmitochondria<MyEvents>();\n *\n * // Standard API\n * mito.on('user:login', (data) => {\n * console.log(data.userId); // fully typed!\n * });\n * mito.emit('user:login', { userId: '123' });\n * mito.emit('app:ready');\n *\n * // Biological API\n * mito.bind('user:login', (data) => console.log(data.userId));\n * mito.pulse('user:login', { userId: '123' });\n * ```\n */\nexport function createEmitochondria<T extends EventMap>(\n options: EmitochondriaOptions = {}\n): Emitochondria<T> {\n const handlers: Map<string, Set<WrappedHandler<unknown>>> = new Map();\n const wildcardHandlers: Set<WildcardHandler<T>> = new Set();\n\n // Default error handler\n let errorHandler: EmitochondriaOptions['onError'] = options.onError ?? ((error: Error, event: string) => {\n // Log in development, silent in production\n // Check for NODE_ENV in a way that works in both Node and browser\n const nodeEnv = (globalThis as any).process?.env?.NODE_ENV;\n if (nodeEnv !== 'production') {\n console.error(`[Emitochondria] Error in handler for event \"${event}\":`, error);\n }\n });\n\n // Max listeners configuration\n let maxListeners = options.maxListeners ?? 10;\n\n const onMaxListenersExceeded = options.onMaxListenersExceeded ?? ((event, count, max) => {\n console.warn(\n `[Emitochondria] Possible memory leak detected: ` +\n `${count} listeners added for event \"${event}\". ` +\n `Maximum is ${max}. Use setMaxListeners() to increase limit.`\n );\n });\n\n /**\n * Check if max listeners exceeded and warn if needed.\n */\n function checkMaxListeners(event: string, count: number): void {\n if (maxListeners > 0 && count > maxListeners) {\n onMaxListenersExceeded(event, count, maxListeners);\n }\n }\n\n /**\n * Safely execute a handler, catching errors according to configuration.\n */\n function safeCall<K extends EventKey<T>>(\n handler: EventHandler<unknown>,\n event: K,\n payload: unknown\n ): void | Promise<void> {\n try {\n const result = handler(payload);\n\n // Handle async errors\n if (result instanceof Promise) {\n return result.catch((error) => {\n if (errorHandler === 'throw') throw error;\n if (typeof errorHandler === 'function') {\n errorHandler(error, event, handler);\n }\n });\n }\n\n return result;\n } catch (error) {\n if (errorHandler === 'throw') throw error;\n if (typeof errorHandler === 'function') {\n errorHandler(error as Error, event, handler);\n }\n }\n }\n\n function getHandlers(event: string): Set<WrappedHandler<unknown>> {\n let set = handlers.get(event);\n if (!set) {\n set = new Set();\n handlers.set(event, set);\n }\n return set;\n }\n\n const e: Emitochondria<T> = {\n on(event, handler) {\n const set = getHandlers(event);\n set.add(handler as EventHandler<unknown>);\n\n // Check for potential memory leak\n checkMaxListeners(event, set.size);\n\n return () => e.off(event, handler);\n },\n\n off(event, handler) {\n const set = handlers.get(event);\n if (!set) return;\n\n // Try direct removal first (for regular handlers)\n if (set.delete(handler as EventHandler<unknown>)) {\n // Clean up empty sets to prevent memory leak\n if (set.size === 0) {\n handlers.delete(event);\n }\n return;\n }\n\n // Search for wrapped handler (for once handlers)\n for (const wrapped of set) {\n if (wrapped.__original === handler) {\n set.delete(wrapped);\n if (set.size === 0) {\n handlers.delete(event);\n }\n break;\n }\n }\n },\n\n once(event, handler) {\n const wrapper = ((payload: T[typeof event]) => {\n e.off(event, wrapper as EventHandler<T[typeof event]>);\n return handler(payload);\n }) as WrappedHandler<T[typeof event]>;\n\n // Store original reference for manual removal\n wrapper.__original = handler;\n\n return e.on(event, wrapper);\n },\n\n emit(event, ...payload) {\n const data = payload[0];\n getHandlers(event).forEach((handler) => {\n safeCall(handler, event, data);\n });\n wildcardHandlers.forEach((handler) => {\n // Wildcard handlers have different signature (event, payload)\n try {\n const result = handler(event, data as T[typeof event]);\n if (result instanceof Promise) {\n result.catch((error) => {\n if (errorHandler === 'throw') throw error;\n if (typeof errorHandler === 'function') {\n errorHandler(error, event, handler as EventHandler<unknown>);\n }\n });\n }\n } catch (error) {\n if (errorHandler === 'throw') throw error;\n if (typeof errorHandler === 'function') {\n errorHandler(error as Error, event, handler as EventHandler<unknown>);\n }\n }\n });\n },\n\n async emitAsync(event, ...payload) {\n const data = payload[0];\n const promises: Promise<void>[] = [];\n\n getHandlers(event).forEach((handler) => {\n const result = safeCall(handler, event, data);\n if (result instanceof Promise) {\n promises.push(result);\n }\n });\n\n wildcardHandlers.forEach((handler) => {\n try {\n const result = handler(event, data as T[typeof event]);\n if (result instanceof Promise) {\n promises.push(\n result.catch((error) => {\n if (errorHandler === 'throw') throw error;\n if (typeof errorHandler === 'function') {\n errorHandler(error, event, handler as EventHandler<unknown>);\n }\n })\n );\n }\n } catch (error) {\n if (errorHandler === 'throw') throw error;\n if (typeof errorHandler === 'function') {\n errorHandler(error as Error, event, handler as EventHandler<unknown>);\n }\n }\n });\n\n await Promise.all(promises);\n },\n\n onAny(handler) {\n wildcardHandlers.add(handler);\n return () => e.offAny(handler);\n },\n\n offAny(handler) {\n wildcardHandlers.delete(handler);\n },\n\n clear(event?) {\n if (event) {\n handlers.delete(event);\n } else {\n handlers.clear();\n wildcardHandlers.clear();\n }\n },\n\n listenerCount(event) {\n return getHandlers(event).size;\n },\n\n setErrorHandler(handler) {\n errorHandler = handler;\n },\n\n setMaxListeners(n) {\n maxListeners = n;\n },\n\n getMaxListeners() {\n return maxListeners;\n },\n\n eventNames() {\n return Array.from(handlers.keys()).filter(\n key => {\n const set = handlers.get(key);\n return set && set.size > 0;\n }\n ) as EventKey<T>[];\n },\n\n listeners(event) {\n const set = handlers.get(event);\n if (!set) return [];\n\n // Return unwrapped handlers for better debugging\n return Array.from(set).map(handler => {\n const wrapped = handler as WrappedHandler<unknown>;\n return (wrapped.__original || handler) as EventHandler<T[typeof event]>;\n });\n },\n\n wildcardListeners() {\n return Array.from(wildcardHandlers);\n },\n\n hasListener(event, handler) {\n const set = handlers.get(event);\n if (!set) return false;\n\n // Check direct match\n if (set.has(handler as EventHandler<unknown>)) {\n return true;\n }\n\n // Check wrapped handlers (once)\n for (const wrapped of set) {\n if (wrapped.__original === handler) {\n return true;\n }\n }\n\n return false;\n },\n\n // ⚡ Biological aliases (zero-cost: just references)\n bind: null as unknown as Emitochondria<T>['on'],\n release: null as unknown as Emitochondria<T>['off'],\n pulse: null as unknown as Emitochondria<T>['emit'],\n cascade: null as unknown as Emitochondria<T>['emitAsync'],\n spike: null as unknown as Emitochondria<T>['once'],\n membrane: null as unknown as Emitochondria<T>['onAny'],\n apoptosis: null as unknown as Emitochondria<T>['clear'],\n receptors: null as unknown as Emitochondria<T>['listenerCount'],\n };\n\n // Assign aliases (smaller than repeating in object literal)\n e.bind = e.on;\n e.release = e.off;\n e.pulse = e.emit;\n e.cascade = e.emitAsync;\n e.spike = e.once;\n e.membrane = e.onAny;\n e.apoptosis = e.clear;\n e.receptors = e.listenerCount;\n\n return e;\n}\n\n// Default export for convenience\nexport default createEmitochondria;\n"],"mappings":"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","names":["createEmitochondria","options","handlers","wildcardHandlers","errorHandler","error","event","maxListeners","onMaxListenersExceeded","count","max","checkMaxListeners","safeCall","handler","payload","result","getHandlers","set","e","wrapped","wrapper","data","promises","n","key","index_default"]}
1
+ {"version":3,"sources":["../src/logger.ts","../src/config.ts","../src/emitter.ts"],"sourcesContent":["import { LoggingOptions, TimestampFormat, Timezone } from './types.js';\n\n// ============================================================\n// ENVIRONMENT DETECTION\n// ============================================================\n\nconst isNode = typeof (globalThis as any).process !== 'undefined'\n && (globalThis as any).process?.versions?.node != null;\n\nconst isBrowser = typeof (globalThis as any).window !== 'undefined';\n\n// ============================================================\n// TIMESTAMP FORMATTING\n// ============================================================\n\nfunction formatTimestamp(format: TimestampFormat, timezone: Timezone): string {\n const date = new Date();\n\n switch (format) {\n case 'unix':\n return String(date.getTime());\n\n case 'human':\n return date.toLocaleString('en-US', {\n timeZone: timezone === 'utc' ? 'UTC' : undefined,\n month: 'short',\n day: 'numeric',\n hour: 'numeric',\n minute: '2-digit',\n second: '2-digit',\n hour12: true,\n });\n\n case 'iso':\n default:\n return timezone === 'utc'\n ? date.toISOString()\n : new Date(date.getTime() - date.getTimezoneOffset() * 60000)\n .toISOString()\n .slice(0, -1); // Remove 'Z' for local\n }\n}\n\n// ============================================================\n// SIZE PARSING\n// ============================================================\n\nfunction parseSize(size: string): number {\n const match = size.match(/^(\\d+(?:\\.\\d+)?)\\s*(KB|MB|GB)$/i);\n if (!match) return 10 * 1024 * 1024; // Default 10MB\n\n const value = parseFloat(match[1]!);\n const unit = match[2]!.toUpperCase();\n\n const multipliers: Record<string, number> = {\n 'KB': 1024,\n 'MB': 1024 * 1024,\n 'GB': 1024 * 1024 * 1024,\n };\n\n return value * (multipliers[unit] ?? 10 * 1024 * 1024);\n}\n\n// ============================================================\n// LOG ENTRY TYPES\n// ============================================================\n\nexport type LogLevel = 'info' | 'warn' | 'error';\n\nexport interface LogEntry {\n level: LogLevel;\n type: 'event' | 'error' | 'warning';\n event?: string;\n message: string;\n payload?: unknown;\n error?: unknown;\n}\n\n// ============================================================\n// LOGGER CLASS\n// ============================================================\n\nexport class Logger {\n private options: Required<LoggingOptions>;\n private maxSizeBytes: number;\n private browserWarned = false;\n private fs: any = null;\n private path: any = null;\n\n constructor(options: LoggingOptions = {}) {\n this.options = {\n timestamps: options.timestamps ?? false,\n timestampFormat: options.timestampFormat ?? 'iso',\n timezone: options.timezone ?? 'utc',\n persist: options.persist ?? false,\n path: options.path ?? './logs/emitochondria.log',\n format: options.format ?? 'text',\n maxSize: options.maxSize ?? '10MB',\n logEvents: options.logEvents ?? false,\n logErrors: options.logErrors ?? true,\n logWarnings: options.logWarnings ?? true,\n };\n\n this.maxSizeBytes = parseSize(this.options.maxSize);\n\n if (this.options.persist) {\n this.initFileLogging();\n }\n }\n\n private initFileLogging(): void {\n if (isBrowser) {\n if (!this.browserWarned) {\n console.warn(\n '[Emitochondria] File logging is not available in browser environments. ' +\n 'Logs will only be written to console.'\n );\n this.browserWarned = true;\n }\n return;\n }\n\n if (!isNode) return;\n\n try {\n // Require fs and path synchronously\n const req = (globalThis as any).require;\n if (req) {\n this.fs = req('fs');\n this.path = req('path');\n\n // Create directory if needed\n const dir = this.path.dirname(this.options.path);\n if (!this.fs.existsSync(dir)) {\n this.fs.mkdirSync(dir, { recursive: true });\n }\n }\n } catch (err) {\n console.error('[Emitochondria] Failed to initialize file logging:', err);\n }\n }\n\n private getTimestamp(): string | undefined {\n if (!this.options.timestamps) return undefined;\n return formatTimestamp(this.options.timestampFormat, this.options.timezone);\n }\n\n private formatEntry(entry: LogEntry): string {\n const timestamp = this.getTimestamp();\n\n if (this.options.format === 'json') {\n const obj: any = {\n level: entry.level,\n type: entry.type,\n message: entry.message,\n };\n if (timestamp) obj.timestamp = timestamp;\n if (entry.event) obj.event = entry.event;\n if (entry.payload !== undefined) obj.payload = entry.payload;\n if (entry.error) obj.error = String(entry.error);\n return JSON.stringify(obj);\n }\n\n // Text format\n const parts: string[] = [];\n if (timestamp) parts.push(`[${timestamp}]`);\n parts.push(`[${entry.level.toUpperCase()}]`);\n if (entry.event) parts.push(`[${entry.event}]`);\n parts.push(entry.message);\n if (entry.payload !== undefined) {\n parts.push(`| Payload: ${JSON.stringify(entry.payload)}`);\n }\n if (entry.error) {\n parts.push(`| Error: ${entry.error}`);\n }\n\n return parts.join(' ');\n }\n\n private writeToFile(content: string): void {\n if (!this.fs || !this.path || !isNode) return;\n\n try {\n const line = content + '\\n';\n\n // Check if rotation needed\n if (this.fs.existsSync(this.options.path)) {\n const stats = this.fs.statSync(this.options.path);\n if (stats.size >= this.maxSizeBytes) {\n this.rotateLog();\n }\n }\n\n // Sync write — simple, ordered, reliable\n this.fs.appendFileSync(this.options.path, line, 'utf8');\n } catch (err) {\n console.error('[Emitochondria] Failed to write to log file:', err);\n }\n }\n\n private rotateLog(): void {\n if (!this.fs || !this.path) return;\n\n try {\n // Find next rotation number\n let rotationNum = 1;\n while (this.fs.existsSync(`${this.options.path}.${rotationNum}`)) {\n rotationNum++;\n }\n\n // Rename current file\n this.fs.renameSync(this.options.path, `${this.options.path}.${rotationNum}`);\n } catch (err) {\n console.error('[Emitochondria] Failed to rotate log file:', err);\n }\n }\n\n // ============================================================\n // PUBLIC LOGGING METHODS\n // ============================================================\n\n logEvent(event: string, payload: unknown): void {\n if (!this.options.logEvents) return;\n\n const entry: LogEntry = {\n level: 'info',\n type: 'event',\n event,\n message: 'Event emitted',\n payload,\n };\n\n const formatted = this.formatEntry(entry);\n console.log(formatted);\n\n if (this.options.persist) {\n this.writeToFile(formatted);\n }\n }\n\n logError(event: string, error: unknown, _handler?: unknown): void {\n if (!this.options.logErrors) return;\n\n const entry: LogEntry = {\n level: 'error',\n type: 'error',\n event,\n message: 'Handler threw an error',\n error,\n };\n\n const formatted = this.formatEntry(entry);\n console.error(formatted);\n\n if (this.options.persist) {\n this.writeToFile(formatted);\n }\n }\n\n logWarning(event: string, message: string, details?: Record<string, unknown>): void {\n if (!this.options.logWarnings) return;\n\n const entry: LogEntry = {\n level: 'warn',\n type: 'warning',\n event,\n message,\n payload: details,\n };\n\n const formatted = this.formatEntry(entry);\n console.warn(formatted);\n\n if (this.options.persist) {\n this.writeToFile(formatted);\n }\n }\n}\n","import { EmitochondriaConfig, EmitochondriaOptions, EventMap } from './types.js';\n\n// ============================================================\n// CONFIG FILE LOADING\n// ============================================================\n\nconst CONFIG_FILENAME = 'emitochondria.config.json';\n\nlet cachedConfig: EmitochondriaConfig | null = null;\n\n/**\n * Synchronous version for initial load.\n * Falls back to empty config if loading fails.\n */\nfunction loadConfigFileSync(): EmitochondriaConfig {\n if (cachedConfig !== null) return cachedConfig;\n\n const proc = (globalThis as any).process;\n if (typeof proc === 'undefined' || !proc?.versions?.node) {\n cachedConfig = {};\n return cachedConfig;\n }\n\n try {\n const req = (globalThis as any).require;\n if (!req) {\n cachedConfig = {};\n return cachedConfig;\n }\n\n const fs = req('fs');\n const path = req('path');\n\n const configPath = path.join(proc.cwd(), CONFIG_FILENAME);\n const content = fs.readFileSync(configPath, 'utf-8');\n cachedConfig = JSON.parse(content) as EmitochondriaConfig;\n\n return cachedConfig;\n } catch {\n cachedConfig = {};\n return cachedConfig;\n }\n}\n\n// ============================================================\n// CONFIG MERGING\n// ============================================================\n\n/**\n * Deep merge two objects, with second taking precedence.\n */\nfunction deepMerge<T extends Record<string, any>>(base: T, override: Partial<T>): T {\n const result = { ...base };\n\n for (const key in override) {\n if (override[key] !== undefined) {\n if (\n typeof override[key] === 'object' &&\n override[key] !== null &&\n !Array.isArray(override[key]) &&\n typeof result[key] === 'object' &&\n result[key] !== null\n ) {\n result[key] = deepMerge(result[key], override[key]);\n } else {\n result[key] = override[key] as T[typeof key];\n }\n }\n }\n\n return result;\n}\n\n/**\n * Merge file config with options.\n * Options take precedence over file config.\n */\nexport function mergeConfig<T extends EventMap>(\n options: EmitochondriaOptions<T> = {}\n): EmitochondriaOptions<T> {\n const fileConfig = loadConfigFileSync();\n // Type assertion is safe here because we control both inputs\n const merged = deepMerge(fileConfig as any, options as any);\n return merged as EmitochondriaOptions<T>;\n}\n\n/**\n * Clear cached config (useful for testing).\n */\nexport function clearConfigCache(): void {\n cachedConfig = null;\n}\n","import {\n EventMap,\n EventKey,\n EventHandler,\n WildcardHandler,\n ErrorHandler,\n EmitochondriaOptions,\n Emitochondria,\n} from './types.js';\nimport { Logger } from './logger.js';\nimport { mergeConfig } from './config.js';\n\n// ============================================================\n// DEFAULT HANDLERS\n// ============================================================\n\nconst defaultMaxListenersHandler = <T extends EventMap>(\n _event: EventKey<T>,\n _count: number,\n _max: number\n): void => {\n // Handled by logger\n};\n\n// ============================================================\n// FACTORY FUNCTION\n// ============================================================\n\nexport function createEmitochondria<T extends EventMap>(\n options: EmitochondriaOptions<T> = {}\n): Emitochondria<T> {\n // Merge file config with options\n const config = mergeConfig(options);\n\n // Initialize logger\n const logger = new Logger(config.logging);\n\n // Handler storage\n const handlers: Map<string, Set<EventHandler<unknown>>> = new Map();\n const wildcards: Set<WildcardHandler<T>> = new Set();\n\n // Configuration state\n let maxListeners = config.maxListeners ?? 10;\n let onMaxListenersExceeded = options.onMaxListenersExceeded ?? defaultMaxListenersHandler;\n let errorHandler: ErrorHandler<T> | 'throw' | 'default' = config.onError ?? 'default';\n\n // Memory leak tracking\n const warned = new Set<string>();\n\n // ============================================================\n // HELPER FUNCTIONS\n // ============================================================\n\n // Track original handlers for once() wrappers\n type WrappedHandler<T> = EventHandler<T> & { __original?: EventHandler<T> };\n\n const get = (e: string) => handlers.get(e) ?? handlers.set(e, new Set()).get(e)!;\n\n const handleError = <K extends EventKey<T>>(\n error: unknown,\n event: K,\n handler: EventHandler<unknown>\n ): void => {\n logger.logError(event, error, handler);\n\n if (errorHandler === 'throw') {\n throw error;\n } else if (typeof errorHandler === 'function') {\n errorHandler(error, event, handler);\n }\n // 'default' = just log (already done above)\n };\n\n const safeCall = <K extends EventKey<T>>(\n handler: EventHandler<unknown>,\n event: K,\n payload: unknown\n ): void | Promise<void> => {\n try {\n const result = handler(payload);\n if (result instanceof Promise) {\n return result.catch((err) => handleError(err, event, handler));\n }\n } catch (err) {\n handleError(err, event, handler);\n }\n };\n\n const checkMaxListeners = <K extends EventKey<T>>(event: K, count: number): void => {\n if (maxListeners > 0 && count > maxListeners && !warned.has(event)) {\n warned.add(event);\n\n const message = `Possible memory leak: ${count} listeners (max: ${maxListeners})`;\n logger.logWarning(event, message, { count, max: maxListeners });\n\n onMaxListenersExceeded(event, count, maxListeners);\n }\n };\n\n // ============================================================\n // EMITTER IMPLEMENTATION\n // ============================================================\n\n const emitter: Emitochondria<T> = {\n on(event, handler) {\n const set = get(event);\n set.add(handler as EventHandler<unknown>);\n checkMaxListeners(event, set.size);\n return () => emitter.off(event, handler);\n },\n\n off(event, handler) {\n const set = get(event);\n\n // Try direct removal first\n if (set.delete(handler as EventHandler<unknown>)) {\n if (set.size <= maxListeners) {\n warned.delete(event);\n }\n return;\n }\n\n // Search for wrapped handler (for once handlers)\n for (const wrapped of set) {\n const w = wrapped as WrappedHandler<unknown>;\n if (w.__original === handler) {\n set.delete(wrapped);\n if (set.size <= maxListeners) {\n warned.delete(event);\n }\n break;\n }\n }\n },\n\n once(event, handler) {\n const wrapper = ((payload: T[typeof event]) => {\n emitter.off(event, wrapper as EventHandler<T[typeof event]>);\n return handler(payload);\n }) as WrappedHandler<T[typeof event]>;\n\n // Store original reference for manual removal\n wrapper.__original = handler;\n\n return emitter.on(event, wrapper);\n },\n\n emit(event, ...payload) {\n const data = payload[0];\n\n logger.logEvent(event, data);\n\n get(event).forEach((h) => safeCall(h, event, data));\n wildcards.forEach((h) => {\n try {\n h(event, data as T[typeof event]);\n } catch (err) {\n handleError(err, event, h as unknown as EventHandler<unknown>);\n }\n });\n },\n\n async emitAsync(event, ...payload) {\n const data = payload[0];\n const promises: Promise<void>[] = [];\n\n logger.logEvent(event, data);\n\n get(event).forEach((h) => {\n const result = safeCall(h, event, data);\n if (result instanceof Promise) promises.push(result);\n });\n\n wildcards.forEach((h) => {\n try {\n const result = h(event, data as T[typeof event]);\n if (result instanceof Promise) {\n promises.push(result.catch((err) =>\n handleError(err, event, h as unknown as EventHandler<unknown>)\n ));\n }\n } catch (err) {\n handleError(err, event, h as unknown as EventHandler<unknown>);\n }\n });\n\n await Promise.all(promises);\n },\n\n onAny(handler) {\n wildcards.add(handler);\n return () => emitter.offAny(handler);\n },\n\n offAny(handler) {\n wildcards.delete(handler);\n },\n\n clear(event?) {\n if (event) {\n handlers.delete(event);\n warned.delete(event);\n } else {\n handlers.clear();\n wildcards.clear();\n warned.clear();\n }\n },\n\n listenerCount(event) {\n return get(event).size;\n },\n\n setErrorHandler(handler) {\n errorHandler = handler;\n },\n\n setMaxListeners(n) {\n maxListeners = n;\n if (n === 0) warned.clear();\n },\n\n getMaxListeners() {\n return maxListeners;\n },\n\n eventNames() {\n return Array.from(handlers.keys()).filter(\n (k) => handlers.get(k)!.size > 0\n ) as EventKey<T>[];\n },\n\n listeners(event) {\n const set = get(event);\n // Return unwrapped handlers for better debugging\n return Array.from(set).map(handler => {\n const wrapped = handler as WrappedHandler<unknown>;\n return (wrapped.__original || handler) as EventHandler<T[typeof event]>;\n });\n },\n\n wildcardListeners() {\n return Array.from(wildcards);\n },\n\n hasListener(event, handler) {\n const set = get(event);\n\n // Check direct match\n if (set.has(handler as EventHandler<unknown>)) {\n return true;\n }\n\n // Check wrapped handlers (once)\n for (const wrapped of set) {\n const w = wrapped as WrappedHandler<unknown>;\n if (w.__original === handler) {\n return true;\n }\n }\n\n return false;\n },\n };\n\n return emitter;\n}\n"],"mappings":"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","names":["isNode","isBrowser","formatTimestamp","format","timezone","date","parseSize","size","match","value","unit","multipliers","Logger","options","req","dir","err","entry","timestamp","obj","parts","content","line","rotationNum","event","payload","formatted","error","_handler","message","details","CONFIG_FILENAME","cachedConfig","loadConfigFileSync","proc","req","fs","configPath","content","deepMerge","base","override","result","key","mergeConfig","options","fileConfig","clearConfigCache","defaultMaxListenersHandler","_event","_count","_max","createEmitochondria","options","config","mergeConfig","logger","Logger","handlers","wildcards","maxListeners","onMaxListenersExceeded","errorHandler","warned","get","e","handleError","error","event","handler","safeCall","payload","result","err","checkMaxListeners","count","message","emitter","set","wrapped","wrapper","data","h","promises","n","k"]}
package/package.json CHANGED
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "name": "emitochondria",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "type": "module",
5
5
  "description": "The powerhouse of your events — A tiny, fully-typed event emitter for TypeScript with built-in error handling and memory leak detection",
6
6
  "author": "Pablo Díaz A.K.A exudev",
7
7
  "license": "MIT",
8
+ "bin": {
9
+ "emitochondria": "./bin/emitochondria.cjs"
10
+ },
8
11
  "keywords": [
9
12
  "events",
10
13
  "emitter",
@@ -37,10 +40,13 @@
37
40
  "types": "./dist/index.d.ts",
38
41
  "default": "./dist/index.js"
39
42
  }
40
- }
43
+ },
44
+ "./schema.json": "./schema.json"
41
45
  },
42
46
  "files": [
43
- "dist"
47
+ "dist",
48
+ "bin",
49
+ "schema.json"
44
50
  ],
45
51
  "sideEffects": false,
46
52
  "scripts": {
package/schema.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "title": "Emitochondria Configuration",
4
+ "type": "object",
5
+ "properties": {
6
+ "maxListeners": {
7
+ "type": "number",
8
+ "default": 10,
9
+ "description": "Maximum listeners per event before warning. Set to 0 to disable."
10
+ },
11
+ "onError": {
12
+ "type": "string",
13
+ "enum": ["throw"],
14
+ "description": "Set to 'throw' to re-throw errors instead of catching them."
15
+ },
16
+ "logging": {
17
+ "type": "object",
18
+ "properties": {
19
+ "timestamps": {
20
+ "type": "boolean",
21
+ "default": false,
22
+ "description": "Enable timestamps in logs."
23
+ },
24
+ "timestampFormat": {
25
+ "type": "string",
26
+ "enum": ["iso", "unix", "human"],
27
+ "default": "iso",
28
+ "description": "Format for timestamps."
29
+ },
30
+ "timezone": {
31
+ "type": "string",
32
+ "enum": ["utc", "local"],
33
+ "default": "utc",
34
+ "description": "Timezone for timestamps."
35
+ },
36
+ "persist": {
37
+ "type": "boolean",
38
+ "default": false,
39
+ "description": "Enable file logging. Only works in Node.js."
40
+ },
41
+ "path": {
42
+ "type": "string",
43
+ "default": "./logs/emitochondria.log",
44
+ "description": "Path to log file."
45
+ },
46
+ "format": {
47
+ "type": "string",
48
+ "enum": ["text", "json"],
49
+ "default": "text",
50
+ "description": "Log file format."
51
+ },
52
+ "maxSize": {
53
+ "type": "string",
54
+ "default": "10MB",
55
+ "description": "Maximum log file size before rotation. E.g., '10MB', '1GB'."
56
+ },
57
+ "logEvents": {
58
+ "type": "boolean",
59
+ "default": false,
60
+ "description": "Log every emitted event. Can be verbose!"
61
+ },
62
+ "logErrors": {
63
+ "type": "boolean",
64
+ "default": true,
65
+ "description": "Log handler errors."
66
+ },
67
+ "logWarnings": {
68
+ "type": "boolean",
69
+ "default": true,
70
+ "description": "Log memory leak warnings."
71
+ }
72
+ }
73
+ }
74
+ }
75
+ }