@bernierllc/event-emitter 1.0.8 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/errors.d.ts +16 -0
- package/dist/errors.js +28 -0
- package/dist/event-emitter.js +9 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -1
- package/package.json +7 -2
- package/.eslintrc.js +0 -29
- package/CHANGELOG.md +0 -73
- package/__tests__/async-handlers.test.ts +0 -260
- package/__tests__/error-handling.test.ts +0 -310
- package/__tests__/event-emitter.test.ts +0 -281
- package/__tests__/logger-integration.test.ts +0 -132
- package/__tests__/wildcard.test.ts +0 -151
- package/jest.config.cjs +0 -30
- package/src/event-emitter.ts +0 -190
- package/src/factory.ts +0 -17
- package/src/index.ts +0 -11
- package/src/types.ts +0 -46
- package/tsconfig.json +0 -22
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type EventEmitterErrorCode = 'EVENT_EMITTER_ERROR' | 'EVENT_MAX_LISTENERS_EXCEEDED';
|
|
2
|
+
/**
|
|
3
|
+
* Error thrown by event-emitter when invariants are violated (e.g. exceeding
|
|
4
|
+
* the configured max-listeners ceiling). Uses ES2022 `Error.cause` so upstream
|
|
5
|
+
* failures from handler execution can be wrapped and inspected via
|
|
6
|
+
* `getErrorChain` from `@bernierllc/logger`.
|
|
7
|
+
*/
|
|
8
|
+
export declare class EventEmitterError extends Error {
|
|
9
|
+
readonly code: EventEmitterErrorCode | string;
|
|
10
|
+
readonly context: Record<string, unknown> | undefined;
|
|
11
|
+
constructor(message: string, options?: {
|
|
12
|
+
cause?: Error | undefined;
|
|
13
|
+
code?: EventEmitterErrorCode | string | undefined;
|
|
14
|
+
context?: Record<string, unknown> | undefined;
|
|
15
|
+
});
|
|
16
|
+
}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Copyright (c) 2025 Bernier LLC
|
|
4
|
+
|
|
5
|
+
This file is licensed to the client under a limited-use license.
|
|
6
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
7
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.EventEmitterError = void 0;
|
|
11
|
+
/**
|
|
12
|
+
* Error thrown by event-emitter when invariants are violated (e.g. exceeding
|
|
13
|
+
* the configured max-listeners ceiling). Uses ES2022 `Error.cause` so upstream
|
|
14
|
+
* failures from handler execution can be wrapped and inspected via
|
|
15
|
+
* `getErrorChain` from `@bernierllc/logger`.
|
|
16
|
+
*/
|
|
17
|
+
class EventEmitterError extends Error {
|
|
18
|
+
code;
|
|
19
|
+
context;
|
|
20
|
+
constructor(message, options) {
|
|
21
|
+
super(message, options?.cause ? { cause: options.cause } : undefined);
|
|
22
|
+
this.name = 'EventEmitterError';
|
|
23
|
+
this.code = options?.code ?? 'EVENT_EMITTER_ERROR';
|
|
24
|
+
this.context = options?.context;
|
|
25
|
+
Error.captureStackTrace?.(this, this.constructor);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.EventEmitterError = EventEmitterError;
|
package/dist/event-emitter.js
CHANGED
|
@@ -8,10 +8,15 @@ Redistribution or use in other products or commercial offerings is not permitted
|
|
|
8
8
|
*/
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
10
|
exports.EventEmitter = void 0;
|
|
11
|
+
const errors_1 = require("./errors");
|
|
11
12
|
/**
|
|
12
13
|
* Event bus for inter-package communication
|
|
13
14
|
*/
|
|
14
15
|
class EventEmitter {
|
|
16
|
+
listeners;
|
|
17
|
+
wildcardListeners;
|
|
18
|
+
options;
|
|
19
|
+
logger;
|
|
15
20
|
constructor(options = {}) {
|
|
16
21
|
this.listeners = new Map();
|
|
17
22
|
this.wildcardListeners = new Set();
|
|
@@ -38,7 +43,10 @@ class EventEmitter {
|
|
|
38
43
|
if (this.options.maxListeners > 0) {
|
|
39
44
|
const currentCount = this.listenerCount(eventType);
|
|
40
45
|
if (currentCount >= this.options.maxListeners) {
|
|
41
|
-
throw new
|
|
46
|
+
throw new errors_1.EventEmitterError(`Max listeners (${this.options.maxListeners}) exceeded for event type: ${eventType}`, {
|
|
47
|
+
code: 'EVENT_MAX_LISTENERS_EXCEEDED',
|
|
48
|
+
context: { eventType, maxListeners: this.options.maxListeners, currentCount },
|
|
49
|
+
});
|
|
42
50
|
}
|
|
43
51
|
}
|
|
44
52
|
const subscription = {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { EventEmitter } from './event-emitter';
|
|
2
2
|
export { createEventEmitter } from './factory';
|
|
3
3
|
export type { EventHandler, EventSubscription, EventEmitterOptions } from './types';
|
|
4
|
+
export { EventEmitterError } from './errors';
|
|
5
|
+
export type { EventEmitterErrorCode } from './errors';
|
package/dist/index.js
CHANGED
|
@@ -7,8 +7,10 @@ The client may use and modify this code *only within the scope of the project it
|
|
|
7
7
|
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
8
8
|
*/
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
-
exports.createEventEmitter = exports.EventEmitter = void 0;
|
|
10
|
+
exports.EventEmitterError = exports.createEventEmitter = exports.EventEmitter = void 0;
|
|
11
11
|
var event_emitter_1 = require("./event-emitter");
|
|
12
12
|
Object.defineProperty(exports, "EventEmitter", { enumerable: true, get: function () { return event_emitter_1.EventEmitter; } });
|
|
13
13
|
var factory_1 = require("./factory");
|
|
14
14
|
Object.defineProperty(exports, "createEventEmitter", { enumerable: true, get: function () { return factory_1.createEventEmitter; } });
|
|
15
|
+
var errors_1 = require("./errors");
|
|
16
|
+
Object.defineProperty(exports, "EventEmitterError", { enumerable: true, get: function () { return errors_1.EventEmitterError; } });
|
package/package.json
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bernierllc/event-emitter",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Type-safe event bus for inter-package communication",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"README.md",
|
|
10
|
+
"LICENSE"
|
|
11
|
+
],
|
|
7
12
|
"keywords": [
|
|
8
13
|
"event-emitter",
|
|
9
14
|
"event-bus",
|
|
@@ -14,7 +19,7 @@
|
|
|
14
19
|
"author": "Bernier LLC",
|
|
15
20
|
"license": "SEE LICENSE IN LICENSE",
|
|
16
21
|
"dependencies": {
|
|
17
|
-
"@bernierllc/logger": "1.4.
|
|
22
|
+
"@bernierllc/logger": "1.4.3"
|
|
18
23
|
},
|
|
19
24
|
"devDependencies": {
|
|
20
25
|
"@types/jest": "^29.5.0",
|
package/.eslintrc.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Copyright (c) 2025 Bernier LLC
|
|
3
|
-
|
|
4
|
-
This file is licensed to the client under a limited-use license.
|
|
5
|
-
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
6
|
-
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
module.exports = {
|
|
10
|
-
parser: '@typescript-eslint/parser',
|
|
11
|
-
parserOptions: {
|
|
12
|
-
ecmaVersion: 2020,
|
|
13
|
-
sourceType: 'module'
|
|
14
|
-
},
|
|
15
|
-
extends: [
|
|
16
|
-
'eslint:recommended',
|
|
17
|
-
'plugin:@typescript-eslint/recommended'
|
|
18
|
-
],
|
|
19
|
-
plugins: ['@typescript-eslint'],
|
|
20
|
-
env: {
|
|
21
|
-
node: true,
|
|
22
|
-
jest: true
|
|
23
|
-
},
|
|
24
|
-
rules: {
|
|
25
|
-
'@typescript-eslint/explicit-function-return-type': 'warn',
|
|
26
|
-
'@typescript-eslint/no-explicit-any': 'off',
|
|
27
|
-
'@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }]
|
|
28
|
-
}
|
|
29
|
-
};
|
package/CHANGELOG.md
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
# Change Log
|
|
2
|
-
|
|
3
|
-
All notable changes to this project will be documented in this file.
|
|
4
|
-
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
|
-
|
|
6
|
-
## [1.0.8](https://github.com/bernierllc/tools/compare/@bernierllc/event-emitter@1.0.7...@bernierllc/event-emitter@1.0.8) (2026-03-30)
|
|
7
|
-
|
|
8
|
-
**Note:** Version bump only for package @bernierllc/event-emitter
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
## [1.0.7](https://github.com/bernierllc/tools/compare/@bernierllc/event-emitter@1.0.6...@bernierllc/event-emitter@1.0.7) (2026-03-09)
|
|
15
|
-
|
|
16
|
-
**Note:** Version bump only for package @bernierllc/event-emitter
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
## [1.0.6](https://github.com/bernierllc/tools/compare/@bernierllc/event-emitter@1.0.5...@bernierllc/event-emitter@1.0.6) (2026-03-03)
|
|
23
|
-
|
|
24
|
-
**Note:** Version bump only for package @bernierllc/event-emitter
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
## 1.0.5 (2025-12-25)
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
### Bug Fixes
|
|
34
|
-
|
|
35
|
-
* **ci:** add lerna version step before publish ([3d20300](https://github.com/bernierllc/tools/commit/3d203002143bf353fffafe4f8a78a99009567347))
|
|
36
|
-
* **ci:** correct jest testEnvironment paths for CI ([c47144c](https://github.com/bernierllc/tools/commit/c47144c3d78aec2be1e1454066c235c2227823fd))
|
|
37
|
-
* **ci:** fix release workflow + convert internal deps to workspace:* ([01c078b](https://github.com/bernierllc/tools/commit/01c078b49d6025f7eef750f79207a1c71c8d85dc))
|
|
38
|
-
* **jest:** resolve Node.js v25+ localStorage SecurityError ([8ffc512](https://github.com/bernierllc/tools/commit/8ffc512805519bc5ba523bdbcd1c457e84998c9e))
|
|
39
|
-
* update neverhub-adapter deps to workspace:* and publish 0.1.2 ([f0e3d04](https://github.com/bernierllc/tools/commit/f0e3d04d8d4f094e3bb899ddf81e93243d16e2c2))
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
## 1.0.4 (2025-12-25)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
### Bug Fixes
|
|
49
|
-
|
|
50
|
-
* **ci:** correct jest testEnvironment paths for CI ([c47144c](https://github.com/bernierllc/tools/commit/c47144c3d78aec2be1e1454066c235c2227823fd))
|
|
51
|
-
* **ci:** fix release workflow + convert internal deps to workspace:* ([01c078b](https://github.com/bernierllc/tools/commit/01c078b49d6025f7eef750f79207a1c71c8d85dc))
|
|
52
|
-
* **jest:** resolve Node.js v25+ localStorage SecurityError ([8ffc512](https://github.com/bernierllc/tools/commit/8ffc512805519bc5ba523bdbcd1c457e84998c9e))
|
|
53
|
-
* update neverhub-adapter deps to workspace:* and publish 0.1.2 ([f0e3d04](https://github.com/bernierllc/tools/commit/f0e3d04d8d4f094e3bb899ddf81e93243d16e2c2))
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
# @bernierllc/event-emitter
|
|
60
|
-
|
|
61
|
-
## 1.0.3
|
|
62
|
-
|
|
63
|
-
### Patch Changes
|
|
64
|
-
|
|
65
|
-
- Updated dependencies [[`73c1d1e`](https://github.com/bernierllc/tools/commit/73c1d1e46114cf2ccfd5b74b83743361247f4502)]:
|
|
66
|
-
- @bernierllc/logger@1.1.0
|
|
67
|
-
|
|
68
|
-
## 1.0.2
|
|
69
|
-
|
|
70
|
-
### Patch Changes
|
|
71
|
-
|
|
72
|
-
- Updated dependencies [[`24899fa`](https://github.com/bernierllc/tools/commit/24899fa4eaf80ae329f7bd74483cf97a132f5d4c)]:
|
|
73
|
-
- @bernierllc/logger@1.0.4
|
|
@@ -1,260 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Copyright (c) 2025 Bernier LLC
|
|
3
|
-
|
|
4
|
-
This file is licensed to the client under a limited-use license.
|
|
5
|
-
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
6
|
-
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { EventEmitter } from '../src';
|
|
10
|
-
|
|
11
|
-
describe('EventEmitter - Async Handlers', () => {
|
|
12
|
-
let emitter: EventEmitter;
|
|
13
|
-
|
|
14
|
-
beforeEach(() => {
|
|
15
|
-
emitter = new EventEmitter();
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
describe('async handler support', () => {
|
|
19
|
-
it('should handle async handlers', async () => {
|
|
20
|
-
const results: string[] = [];
|
|
21
|
-
|
|
22
|
-
const asyncHandler = async (data: { message: string }) => {
|
|
23
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
24
|
-
results.push(data.message);
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
emitter.on('test', asyncHandler);
|
|
28
|
-
await emitter.emit('test', { message: 'hello' });
|
|
29
|
-
|
|
30
|
-
expect(results).toEqual(['hello']);
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it('should wait for all async handlers to complete', async () => {
|
|
34
|
-
const results: number[] = [];
|
|
35
|
-
|
|
36
|
-
const handler1 = async () => {
|
|
37
|
-
await new Promise(resolve => setTimeout(resolve, 30));
|
|
38
|
-
results.push(1);
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
const handler2 = async () => {
|
|
42
|
-
await new Promise(resolve => setTimeout(resolve, 20));
|
|
43
|
-
results.push(2);
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const handler3 = async () => {
|
|
47
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
48
|
-
results.push(3);
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
emitter.on('test', handler1);
|
|
52
|
-
emitter.on('test', handler2);
|
|
53
|
-
emitter.on('test', handler3);
|
|
54
|
-
|
|
55
|
-
await emitter.emit('test', {});
|
|
56
|
-
|
|
57
|
-
// All handlers should have completed
|
|
58
|
-
expect(results).toHaveLength(3);
|
|
59
|
-
expect(results).toContain(1);
|
|
60
|
-
expect(results).toContain(2);
|
|
61
|
-
expect(results).toContain(3);
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it('should handle mix of sync and async handlers', async () => {
|
|
65
|
-
const results: string[] = [];
|
|
66
|
-
|
|
67
|
-
const syncHandler = (data: { type: string }) => {
|
|
68
|
-
results.push(`sync-${data.type}`);
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
const asyncHandler = async (data: { type: string }) => {
|
|
72
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
73
|
-
results.push(`async-${data.type}`);
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
emitter.on('test', syncHandler);
|
|
77
|
-
emitter.on('test', asyncHandler);
|
|
78
|
-
|
|
79
|
-
await emitter.emit('test', { type: 'mixed' });
|
|
80
|
-
|
|
81
|
-
expect(results).toHaveLength(2);
|
|
82
|
-
expect(results).toContain('sync-mixed');
|
|
83
|
-
expect(results).toContain('async-mixed');
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it('should handle async once listeners', async () => {
|
|
87
|
-
const handler = jest.fn(async () => {
|
|
88
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
emitter.once('test', handler);
|
|
92
|
-
|
|
93
|
-
await emitter.emit('test', { data: 'first' });
|
|
94
|
-
await emitter.emit('test', { data: 'second' });
|
|
95
|
-
|
|
96
|
-
expect(handler).toHaveBeenCalledTimes(1);
|
|
97
|
-
expect(emitter.listenerCount('test')).toBe(0);
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
it('should handle async wildcard listeners', async () => {
|
|
101
|
-
const results: string[] = [];
|
|
102
|
-
|
|
103
|
-
const asyncWildcard = async (data: { event: string }) => {
|
|
104
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
105
|
-
results.push(data.event);
|
|
106
|
-
};
|
|
107
|
-
|
|
108
|
-
emitter.on('*', asyncWildcard);
|
|
109
|
-
|
|
110
|
-
await emitter.emit('event1', { event: 'one' });
|
|
111
|
-
await emitter.emit('event2', { event: 'two' });
|
|
112
|
-
|
|
113
|
-
expect(results).toEqual(['one', 'two']);
|
|
114
|
-
});
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
describe('async handler execution order', () => {
|
|
118
|
-
it('should execute async handlers in parallel', async () => {
|
|
119
|
-
const startTime = Date.now();
|
|
120
|
-
const delays = [50, 50, 50];
|
|
121
|
-
let completedCount = 0;
|
|
122
|
-
|
|
123
|
-
delays.forEach(delay => {
|
|
124
|
-
emitter.on('test', async () => {
|
|
125
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
126
|
-
completedCount++;
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
await emitter.emit('test', {});
|
|
131
|
-
|
|
132
|
-
const duration = Date.now() - startTime;
|
|
133
|
-
|
|
134
|
-
// If executed in parallel, total time should be ~50ms (the max delay)
|
|
135
|
-
// If executed sequentially, total time would be ~150ms
|
|
136
|
-
expect(duration).toBeLessThan(100); // Allow some buffer
|
|
137
|
-
expect(completedCount).toBe(3);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it('should handle async handlers that resolve at different times', async () => {
|
|
141
|
-
const order: number[] = [];
|
|
142
|
-
|
|
143
|
-
emitter.on('test', async () => {
|
|
144
|
-
await new Promise(resolve => setTimeout(resolve, 30));
|
|
145
|
-
order.push(1);
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
emitter.on('test', async () => {
|
|
149
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
150
|
-
order.push(2);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
emitter.on('test', async () => {
|
|
154
|
-
await new Promise(resolve => setTimeout(resolve, 20));
|
|
155
|
-
order.push(3);
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
await emitter.emit('test', {});
|
|
159
|
-
|
|
160
|
-
// Handlers complete in order of their delays, not registration order
|
|
161
|
-
expect(order).toEqual([2, 3, 1]);
|
|
162
|
-
});
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
describe('async handler data flow', () => {
|
|
166
|
-
it('should pass data to async handlers correctly', async () => {
|
|
167
|
-
const receivedData: any[] = [];
|
|
168
|
-
|
|
169
|
-
const handler = async (data: any) => {
|
|
170
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
171
|
-
receivedData.push(data);
|
|
172
|
-
};
|
|
173
|
-
|
|
174
|
-
emitter.on('test', handler);
|
|
175
|
-
|
|
176
|
-
const testData = { id: 123, message: 'test', nested: { value: true } };
|
|
177
|
-
await emitter.emit('test', testData);
|
|
178
|
-
|
|
179
|
-
expect(receivedData).toHaveLength(1);
|
|
180
|
-
expect(receivedData[0]).toEqual(testData);
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
it('should handle async handlers with complex data types', async () => {
|
|
184
|
-
interface ComplexData {
|
|
185
|
-
users: Array<{ id: number; name: string }>;
|
|
186
|
-
metadata: Record<string, any>;
|
|
187
|
-
timestamp: Date;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
let receivedData: ComplexData | null = null;
|
|
191
|
-
|
|
192
|
-
const handler = async (data: ComplexData) => {
|
|
193
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
194
|
-
receivedData = data;
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
emitter.on('test', handler);
|
|
198
|
-
|
|
199
|
-
const complexData: ComplexData = {
|
|
200
|
-
users: [
|
|
201
|
-
{ id: 1, name: 'Alice' },
|
|
202
|
-
{ id: 2, name: 'Bob' }
|
|
203
|
-
],
|
|
204
|
-
metadata: { source: 'api', version: 2 },
|
|
205
|
-
timestamp: new Date()
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
await emitter.emit('test', complexData);
|
|
209
|
-
|
|
210
|
-
expect(receivedData).toEqual(complexData);
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
describe('async handler cleanup', () => {
|
|
215
|
-
it('should handle unsubscribe during async handler execution', async () => {
|
|
216
|
-
let handlerStarted = false;
|
|
217
|
-
let handlerCompleted = false;
|
|
218
|
-
|
|
219
|
-
const handler = async () => {
|
|
220
|
-
handlerStarted = true;
|
|
221
|
-
await new Promise(resolve => setTimeout(resolve, 50));
|
|
222
|
-
handlerCompleted = true;
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
const subscription = emitter.on('test', handler);
|
|
226
|
-
|
|
227
|
-
// Start emission
|
|
228
|
-
const emitPromise = emitter.emit('test', {});
|
|
229
|
-
|
|
230
|
-
// Unsubscribe immediately (while handler is still running)
|
|
231
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
232
|
-
subscription.unsubscribe();
|
|
233
|
-
|
|
234
|
-
// Wait for emission to complete
|
|
235
|
-
await emitPromise;
|
|
236
|
-
|
|
237
|
-
// Handler should have started and completed despite unsubscribe
|
|
238
|
-
expect(handlerStarted).toBe(true);
|
|
239
|
-
expect(handlerCompleted).toBe(true);
|
|
240
|
-
|
|
241
|
-
// But listener should be removed for future events
|
|
242
|
-
expect(emitter.listenerCount('test')).toBe(0);
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
it('should handle async once listeners cleanup', async () => {
|
|
246
|
-
const handler = jest.fn(async () => {
|
|
247
|
-
await new Promise(resolve => setTimeout(resolve, 10));
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
emitter.once('test', handler);
|
|
251
|
-
|
|
252
|
-
expect(emitter.listenerCount('test')).toBe(1);
|
|
253
|
-
|
|
254
|
-
await emitter.emit('test', {});
|
|
255
|
-
|
|
256
|
-
expect(emitter.listenerCount('test')).toBe(0);
|
|
257
|
-
expect(handler).toHaveBeenCalledTimes(1);
|
|
258
|
-
});
|
|
259
|
-
});
|
|
260
|
-
});
|