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.
- package/LICENSE +21 -0
- package/README.md +331 -0
- package/dist/errors.d.ts +8 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/eventful.d.ts +153 -0
- package/dist/eventful.d.ts.map +1 -0
- package/dist/factory.d.ts +89 -0
- package/dist/factory.d.ts.map +1 -0
- package/dist/index.cjs +1126 -0
- package/dist/index.cjs.map +15 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1093 -0
- package/dist/index.js.map +15 -0
- package/dist/interop.d.ts +163 -0
- package/dist/interop.d.ts.map +1 -0
- package/dist/observe.d.ts +182 -0
- package/dist/observe.d.ts.map +1 -0
- package/dist/symbols.d.ts +6 -0
- package/dist/symbols.d.ts.map +1 -0
- package/dist/types.d.ts +139 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +107 -0
- package/src/errors.ts +10 -0
- package/src/eventful.ts +323 -0
- package/src/factory.ts +948 -0
- package/src/index.ts +71 -0
- package/src/interop.ts +271 -0
- package/src/observe.ts +734 -0
- package/src/symbols.ts +12 -0
- package/src/types.ts +206 -0
package/dist/types.d.ts
ADDED
|
@@ -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
|
+
}
|
package/src/eventful.ts
ADDED
|
@@ -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
|
+
}
|