event-emission 0.1.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.
@@ -0,0 +1,139 @@
1
+ declare global {
2
+ function queueMicrotask(callback: () => void): void;
3
+ }
4
+ /**
5
+ * Event structure passed to listeners.
6
+ */
7
+ export interface EventfulEvent<Detail> {
8
+ /** The event type identifier. */
9
+ type: string;
10
+ /** The event payload data. */
11
+ detail: Detail;
12
+ }
13
+ /**
14
+ * Event passed to wildcard listeners, includes the original event type.
15
+ */
16
+ export interface WildcardEvent<E extends Record<string, unknown>> {
17
+ type: '*' | `${string}:*`;
18
+ originalType: keyof E & string;
19
+ detail: E[keyof E];
20
+ }
21
+ /**
22
+ * Minimal AbortSignal lookalike so consumers do not need DOM libs.
23
+ */
24
+ export interface MinimalAbortSignal {
25
+ readonly aborted: boolean;
26
+ readonly reason?: unknown;
27
+ addEventListener: (type: 'abort', listener: () => void, options?: unknown) => void;
28
+ removeEventListener: (type: 'abort', listener: () => void, options?: unknown) => void;
29
+ }
30
+ /**
31
+ * Options for addEventListener.
32
+ */
33
+ export type AddEventListenerOptionsLike = {
34
+ once?: boolean;
35
+ signal?: MinimalAbortSignal;
36
+ };
37
+ /**
38
+ * TC39 Observable observer interface.
39
+ */
40
+ export interface Observer<T> {
41
+ next?: (value: T) => void;
42
+ error?: (err: unknown) => void;
43
+ complete?: () => void;
44
+ }
45
+ /**
46
+ * TC39 Observable subscription interface.
47
+ */
48
+ export interface Subscription {
49
+ unsubscribe(): void;
50
+ readonly closed: boolean;
51
+ }
52
+ /**
53
+ * TC39 Observable-like interface.
54
+ */
55
+ export interface ObservableLike<T> {
56
+ subscribe(observerOrNext?: Observer<T> | ((value: T) => void), error?: (err: unknown) => void, complete?: () => void): Subscription;
57
+ }
58
+ /**
59
+ * Overflow strategy for async iterator buffer.
60
+ */
61
+ export type OverflowStrategy = 'drop-oldest' | 'drop-latest' | 'throw';
62
+ /**
63
+ * Options for the events() async iterator.
64
+ */
65
+ export interface AsyncIteratorOptions {
66
+ signal?: MinimalAbortSignal;
67
+ bufferSize?: number;
68
+ overflowStrategy?: OverflowStrategy;
69
+ }
70
+ /**
71
+ * Options for the events() async iterator (alias for AsyncIteratorOptions).
72
+ */
73
+ export type EventsIteratorOptions = AsyncIteratorOptions;
74
+ /**
75
+ * Options for interop helpers.
76
+ */
77
+ export interface InteropOptions {
78
+ signal?: MinimalAbortSignal;
79
+ }
80
+ /**
81
+ * Minimal DOM Event interface for interop.
82
+ */
83
+ export interface DOMEventLike {
84
+ type: string;
85
+ detail?: unknown;
86
+ }
87
+ /**
88
+ * Minimal DOM EventTarget interface for interop.
89
+ */
90
+ export interface DOMEventTargetLike {
91
+ addEventListener(type: string, listener: (event: DOMEventLike) => void): void;
92
+ removeEventListener(type: string, listener: (event: DOMEventLike) => void): void;
93
+ dispatchEvent(event: DOMEventLike): boolean;
94
+ }
95
+ /**
96
+ * Internal listener record type.
97
+ */
98
+ export type Listener<E> = {
99
+ fn: (event: EventfulEvent<E>) => void | Promise<void>;
100
+ once?: boolean;
101
+ signal?: MinimalAbortSignal;
102
+ abortHandler?: () => void;
103
+ };
104
+ /**
105
+ * Internal wildcard listener record type.
106
+ */
107
+ export type WildcardListener<E extends Record<string, unknown>> = {
108
+ fn: (event: WildcardEvent<E>) => void | Promise<void>;
109
+ pattern: '*' | `${string}:*`;
110
+ once?: boolean;
111
+ signal?: MinimalAbortSignal;
112
+ abortHandler?: () => void;
113
+ };
114
+ /**
115
+ * Type-safe event target interface compatible with DOM EventTarget
116
+ * and TC39 Observable patterns.
117
+ */
118
+ export interface EventTargetLike<E extends Record<string, any>> {
119
+ addEventListener: <K extends keyof E & string>(type: K, listener: (event: EventfulEvent<E[K]>) => void | Promise<void>, options?: AddEventListenerOptionsLike) => () => void;
120
+ removeEventListener: <K extends keyof E & string>(type: K, listener: (event: EventfulEvent<E[K]>) => void | Promise<void>) => void;
121
+ dispatchEvent: <K extends keyof E & string>(event: EventfulEvent<E[K]>) => boolean;
122
+ clear: () => void;
123
+ once: <K extends keyof E & string>(type: K, listener: (event: EventfulEvent<E[K]>) => void | Promise<void>, options?: Omit<AddEventListenerOptionsLike, 'once'>) => () => void;
124
+ removeAllListeners: <K extends keyof E & string>(type?: K) => void;
125
+ /**
126
+ * Pipe events from this emitter to another target.
127
+ * Note: Only forwards events for types that have listeners when pipe() is called.
128
+ * Events for types registered after piping won't be forwarded automatically.
129
+ */
130
+ pipe: <T extends Record<string, any>>(target: EventTargetLike<T>, mapFn?: <K extends keyof E & string>(event: EventfulEvent<E[K]>) => EventfulEvent<T[keyof T & string]> | null) => () => void;
131
+ complete: () => void;
132
+ readonly completed: boolean;
133
+ addWildcardListener: (pattern: '*' | `${string}:*`, listener: (event: WildcardEvent<E>) => void | Promise<void>, options?: AddEventListenerOptionsLike) => () => void;
134
+ removeWildcardListener: (pattern: '*' | `${string}:*`, listener: (event: WildcardEvent<E>) => void | Promise<void>) => void;
135
+ subscribe: <K extends keyof E & string>(type: K, observerOrNext?: Observer<EventfulEvent<E[K]>> | ((value: EventfulEvent<E[K]>) => void), error?: (err: unknown) => void, complete?: () => void) => Subscription;
136
+ toObservable: () => ObservableLike<EventfulEvent<E[keyof E]>>;
137
+ events: <K extends keyof E & string>(type: K, options?: AsyncIteratorOptions) => AsyncIterableIterator<EventfulEvent<E[K]>>;
138
+ }
139
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,107 @@
1
+ {
2
+ "name": "event-emission",
3
+ "version": "0.1.0",
4
+ "description": "Lightweight typed event emitter with DOM EventTarget and TC39 Observable compatibility",
5
+ "keywords": [
6
+ "event",
7
+ "emitter",
8
+ "events",
9
+ "eventtarget",
10
+ "observable",
11
+ "typescript",
12
+ "typed-events",
13
+ "pubsub",
14
+ "publish-subscribe"
15
+ ],
16
+ "homepage": "https://github.com/stevekinney/event-emission#readme",
17
+ "bugs": {
18
+ "url": "https://github.com/stevekinney/event-emission/issues"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/stevekinney/event-emission.git"
23
+ },
24
+ "license": "MIT",
25
+ "author": "Steve Kinney",
26
+ "type": "module",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.js",
31
+ "require": "./dist/index.cjs",
32
+ "default": "./dist/index.js"
33
+ }
34
+ },
35
+ "main": "./dist/index.js",
36
+ "module": "./dist/index.js",
37
+ "types": "./dist/index.d.ts",
38
+ "files": [
39
+ "dist",
40
+ "src"
41
+ ],
42
+ "scripts": {
43
+ "build": "bun run scripts/build.ts",
44
+ "clean": "rm -rf dist coverage",
45
+ "dev": "bun --watch run src/index.ts",
46
+ "format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"",
47
+ "format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,json,md}\"",
48
+ "lint": "eslint .",
49
+ "lint:fix": "eslint . --fix",
50
+ "prepare": "husky",
51
+ "prepublishOnly": "bun run build",
52
+ "test": "bun test",
53
+ "test:coverage": "bun test --coverage",
54
+ "typecheck": "tsc --noEmit",
55
+ "validate": "bun run lint && bun run typecheck && bun run test && bun run build"
56
+ },
57
+ "lint-staged": {
58
+ "src/**/*.{js,ts,tsx,jsx,mjs,cjs}": [
59
+ "eslint --fix",
60
+ "prettier --write"
61
+ ],
62
+ "scripts/**/*.{js,ts,tsx,jsx,mjs,cjs}": [
63
+ "eslint --fix",
64
+ "prettier --write"
65
+ ],
66
+ "**/*.{json,md,css,scss}": [
67
+ "prettier --write"
68
+ ],
69
+ "package.json": [
70
+ "sort-package-json"
71
+ ]
72
+ },
73
+ "devDependencies": {
74
+ "@eslint/eslintrc": "^3.3.3",
75
+ "@eslint/js": "^9.39.2",
76
+ "@eslint/markdown": "^7.5.1",
77
+ "@types/bun": "^1.3.5",
78
+ "@typescript-eslint/eslint-plugin": "^8.50.0",
79
+ "@typescript-eslint/parser": "^8.50.0",
80
+ "chalk": "^5.6.2",
81
+ "eslint": "^9.39.2",
82
+ "eslint-config-prettier": "^10.1.8",
83
+ "eslint-import-resolver-typescript": "^4.4.4",
84
+ "eslint-plugin-eslint-comments": "^3.2.0",
85
+ "eslint-plugin-import": "^2.32.0",
86
+ "eslint-plugin-prettier": "^5.5.4",
87
+ "eslint-plugin-promise": "^7.2.1",
88
+ "eslint-plugin-regexp": "^2.10.0",
89
+ "eslint-plugin-simple-import-sort": "^12.1.1",
90
+ "eslint-plugin-unicorn": "^62.0.0",
91
+ "eslint-plugin-unused-imports": "^4.3.0",
92
+ "globals": "^16.5.0",
93
+ "husky": "^9.1.7",
94
+ "lint-staged": "^16.2.7",
95
+ "prettier": "^3.7.4",
96
+ "sort-package-json": "^3.6.0",
97
+ "typescript": "^5.8.3",
98
+ "typescript-eslint": "^8.50.0"
99
+ },
100
+ "engines": {
101
+ "bun": ">=1.3.0"
102
+ },
103
+ "publishConfig": {
104
+ "access": "public",
105
+ "registry": "https://registry.npmjs.org"
106
+ }
107
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Error thrown when an async iterator's event buffer overflows
3
+ * and the overflow strategy is 'throw'.
4
+ */
5
+ export class BufferOverflowError extends Error {
6
+ constructor(eventType: string, bufferSize: number) {
7
+ super(`Buffer overflow for event type "${eventType}" (max: ${bufferSize})`);
8
+ this.name = 'BufferOverflowError';
9
+ }
10
+ }
@@ -0,0 +1,323 @@
1
+ import { createEventTarget } from './factory';
2
+ import { SymbolObservable } from './symbols';
3
+ import type {
4
+ AddEventListenerOptionsLike,
5
+ EventfulEvent,
6
+ EventsIteratorOptions,
7
+ EventTargetLike,
8
+ ObservableLike,
9
+ Observer,
10
+ Subscription,
11
+ WildcardEvent,
12
+ } from './types';
13
+
14
+ /**
15
+ * Abstract base class for typed event emitters with DOM EventTarget
16
+ * and TC39 Observable compatibility.
17
+ *
18
+ * Extend this class to create custom event emitters with typed events.
19
+ * The class provides:
20
+ * - DOM EventTarget compatible API (addEventListener, removeEventListener, dispatchEvent)
21
+ * - TC39 Observable interop (subscribe, Symbol.observable)
22
+ * - Async iteration support (events() method)
23
+ * - Wildcard listeners for namespaced events
24
+ * - Lifecycle management (complete(), completed)
25
+ *
26
+ * Listener errors are handled via 'error' event: if a listener throws,
27
+ * an 'error' event is emitted. If no 'error' listener is registered,
28
+ * the error is re-thrown (Node.js behavior).
29
+ *
30
+ * @template E - Event map type where keys are event names and values are event detail types.
31
+ *
32
+ * @example Basic usage
33
+ * ```typescript
34
+ * // Define your emitter with typed events
35
+ * class UserService extends Eventful<{
36
+ * 'user:created': { id: string; name: string };
37
+ * 'user:deleted': { id: string };
38
+ * error: Error;
39
+ * }> {
40
+ * createUser(name: string) {
41
+ * const id = crypto.randomUUID();
42
+ * // ... create user logic
43
+ * this.dispatchEvent({ type: 'user:created', detail: { id, name } });
44
+ * }
45
+ * }
46
+ *
47
+ * const service = new UserService();
48
+ * service.addEventListener('user:created', (event) => {
49
+ * console.log(`Created user: ${event.detail.name}`);
50
+ * });
51
+ * ```
52
+ *
53
+ * @example TC39 Observable interop
54
+ * ```typescript
55
+ * const service = new UserService();
56
+ *
57
+ * // Subscribe to all events
58
+ * service.subscribe({
59
+ * next: (event) => console.log(event.type, event.detail),
60
+ * complete: () => console.log('Service completed'),
61
+ * });
62
+ *
63
+ * // Use with RxJS or other Observable libraries
64
+ * import { from } from 'rxjs';
65
+ * const observable = from(service);
66
+ * ```
67
+ *
68
+ * @example Async iteration
69
+ * ```typescript
70
+ * const service = new UserService();
71
+ *
72
+ * // Iterate over events as async iterator
73
+ * for await (const event of service.events('user:created')) {
74
+ * console.log(`User created: ${event.detail.name}`);
75
+ * }
76
+ * ```
77
+ */
78
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Generic constraint requires any for flexibility
79
+ export abstract class Eventful<E extends Record<string, any>> {
80
+ readonly #target: EventTargetLike<E>;
81
+
82
+ constructor() {
83
+ this.#target = createEventTarget<E>();
84
+ }
85
+
86
+ // ==========================================================================
87
+ // DOM EventTarget Methods
88
+ // ==========================================================================
89
+
90
+ /**
91
+ * Adds an event listener for the specified event type.
92
+ * Returns an unsubscribe function for convenience.
93
+ */
94
+ addEventListener<K extends keyof E & string>(
95
+ type: K,
96
+ listener: (event: EventfulEvent<E[K]>) => void | Promise<void>,
97
+ options?: AddEventListenerOptionsLike,
98
+ ): () => void {
99
+ return this.#target.addEventListener(type, listener, options);
100
+ }
101
+
102
+ /**
103
+ * Removes an event listener for the specified event type.
104
+ */
105
+ removeEventListener<K extends keyof E & string>(
106
+ type: K,
107
+ listener: (event: EventfulEvent<E[K]>) => void | Promise<void>,
108
+ ): void {
109
+ this.#target.removeEventListener(type, listener);
110
+ }
111
+
112
+ /**
113
+ * Dispatches an event to all registered listeners.
114
+ * Returns false if the emitter has been completed, true otherwise.
115
+ */
116
+ dispatchEvent<K extends keyof E & string>(event: EventfulEvent<E[K]>): boolean {
117
+ return this.#target.dispatchEvent(event);
118
+ }
119
+
120
+ // ==========================================================================
121
+ // Convenience Methods
122
+ // ==========================================================================
123
+
124
+ /**
125
+ * Adds a one-time listener for the specified event type.
126
+ * Returns an unsubscribe function.
127
+ */
128
+ once<K extends keyof E & string>(
129
+ type: K,
130
+ listener: (event: EventfulEvent<E[K]>) => void | Promise<void>,
131
+ options?: Omit<AddEventListenerOptionsLike, 'once'>,
132
+ ): () => void {
133
+ return this.#target.once(type, listener, options);
134
+ }
135
+
136
+ /**
137
+ * Removes all listeners, or those of the specified event type.
138
+ */
139
+ removeAllListeners<K extends keyof E & string>(type?: K): void {
140
+ this.#target.removeAllListeners(type);
141
+ }
142
+
143
+ /**
144
+ * Removes all listeners. Does not trigger completion.
145
+ */
146
+ clear(): void {
147
+ this.#target.clear();
148
+ }
149
+
150
+ /**
151
+ * Pipe events from this emitter to another target.
152
+ * Note: Only forwards events for types that have listeners when pipe() is called.
153
+ * Events for types registered after piping won't be forwarded automatically.
154
+ */
155
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Generic constraint requires any
156
+ pipe<T extends Record<string, any>>(
157
+ target: EventTargetLike<T>,
158
+ mapFn?: <K extends keyof E & string>(
159
+ event: EventfulEvent<E[K]>,
160
+ ) => EventfulEvent<T[keyof T & string]> | null,
161
+ ): () => void {
162
+ return this.#target.pipe(target, mapFn);
163
+ }
164
+
165
+ // ==========================================================================
166
+ // Wildcard Support
167
+ // ==========================================================================
168
+
169
+ /**
170
+ * Adds a wildcard listener that receives events matching the pattern.
171
+ * Use '*' for all events, or 'namespace:*' for namespaced events.
172
+ */
173
+ addWildcardListener(
174
+ pattern: '*' | `${string}:*`,
175
+ listener: (event: WildcardEvent<E>) => void | Promise<void>,
176
+ options?: AddEventListenerOptionsLike,
177
+ ): () => void {
178
+ return this.#target.addWildcardListener(pattern, listener, options);
179
+ }
180
+
181
+ /**
182
+ * Removes a wildcard listener.
183
+ */
184
+ removeWildcardListener(
185
+ pattern: '*' | `${string}:*`,
186
+ listener: (event: WildcardEvent<E>) => void | Promise<void>,
187
+ ): void {
188
+ this.#target.removeWildcardListener(pattern, listener);
189
+ }
190
+
191
+ // ==========================================================================
192
+ // TC39 Observable Methods
193
+ // ==========================================================================
194
+
195
+ /**
196
+ * Subscribes an observer to all events (untyped).
197
+ */
198
+ subscribe(
199
+ observerOrNext:
200
+ | Observer<EventfulEvent<E[keyof E]>>
201
+ | ((value: EventfulEvent<E[keyof E]>) => void),
202
+ ): Subscription;
203
+
204
+ /**
205
+ * Subscribes an observer to events of a specific type (typed).
206
+ */
207
+ subscribe<K extends keyof E & string>(
208
+ type: K,
209
+ observerOrNext?:
210
+ | Observer<EventfulEvent<E[K]>>
211
+ | ((value: EventfulEvent<E[K]>) => void),
212
+ error?: (err: unknown) => void,
213
+ completeHandler?: () => void,
214
+ ): Subscription;
215
+
216
+ /**
217
+ * Implementation that handles both typed and untyped subscriptions.
218
+ */
219
+ subscribe<K extends keyof E & string>(
220
+ typeOrObserver:
221
+ | K
222
+ | Observer<EventfulEvent<E[keyof E]>>
223
+ | ((value: EventfulEvent<E[keyof E]>) => void),
224
+ observerOrNext?:
225
+ | Observer<EventfulEvent<E[K]>>
226
+ | ((value: EventfulEvent<E[K]>) => void),
227
+ error?: (err: unknown) => void,
228
+ completeHandler?: () => void,
229
+ ): Subscription {
230
+ // Typed subscribe: first argument is a string event type
231
+ if (typeof typeOrObserver === 'string') {
232
+ return this.#target.subscribe(
233
+ typeOrObserver,
234
+ observerOrNext,
235
+ error,
236
+ completeHandler,
237
+ );
238
+ }
239
+
240
+ // Untyped subscribe: first argument is a callback function
241
+ if (typeof typeOrObserver === 'function') {
242
+ return this.#target.toObservable().subscribe(typeOrObserver);
243
+ }
244
+
245
+ // Untyped subscribe: first argument is an observer object
246
+ if (typeof typeOrObserver === 'object' && typeOrObserver !== null) {
247
+ // Check if it looks like an Observer (has next/error/complete methods)
248
+ const maybeObserver = typeOrObserver as Record<string, unknown>;
249
+ if (
250
+ typeof maybeObserver.next === 'function' ||
251
+ typeof maybeObserver.error === 'function' ||
252
+ typeof maybeObserver.complete === 'function'
253
+ ) {
254
+ return this.#target.toObservable().subscribe(typeOrObserver);
255
+ }
256
+ // Object without observer methods - treat as empty observer (no callbacks)
257
+ return this.#target.toObservable().subscribe({});
258
+ }
259
+
260
+ // Fallback: should not reach here with proper TypeScript usage
261
+ throw new Error(
262
+ 'subscribe() requires a string event type, callback function, or observer object',
263
+ );
264
+ }
265
+
266
+ /**
267
+ * Returns an observable that emits all events.
268
+ */
269
+ toObservable(): ObservableLike<EventfulEvent<E[keyof E]>> {
270
+ return this.#target.toObservable();
271
+ }
272
+
273
+ /**
274
+ * Returns this observable for Symbol.observable interop.
275
+ */
276
+ [SymbolObservable](): ObservableLike<EventfulEvent<E[keyof E]>> {
277
+ return this.toObservable();
278
+ }
279
+
280
+ // ==========================================================================
281
+ // Lifecycle Methods
282
+ // ==========================================================================
283
+
284
+ /**
285
+ * Marks the emitter as complete. Invokes complete() on all observable subscribers,
286
+ * ends all async iterators, and suppresses further emits/dispatches.
287
+ * Idempotent.
288
+ */
289
+ complete(): void {
290
+ this.#target.complete();
291
+ }
292
+
293
+ /**
294
+ * Returns true if the emitter has been completed.
295
+ */
296
+ get completed(): boolean {
297
+ return this.#target.completed;
298
+ }
299
+
300
+ // ==========================================================================
301
+ // Async Iterator Method
302
+ // ==========================================================================
303
+
304
+ /**
305
+ * Returns an async iterator over events of the specified type.
306
+ *
307
+ * @param type - The event type to iterate over
308
+ * @param options - Iterator options (signal, bufferSize, overflowStrategy)
309
+ *
310
+ * @example
311
+ * ```typescript
312
+ * for await (const event of emitter.events('foo')) {
313
+ * console.log(event.detail);
314
+ * }
315
+ * ```
316
+ */
317
+ events<K extends keyof E & string>(
318
+ type: K,
319
+ options?: EventsIteratorOptions,
320
+ ): AsyncIterableIterator<EventfulEvent<E[K]>> {
321
+ return this.#target.events(type, options);
322
+ }
323
+ }