evnty 5.1.1 → 5.2.15
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/package.json +19 -19
- package/src/async.ts +112 -0
- package/src/broadcast.ts +348 -0
- package/src/dispatch-result.ts +166 -0
- package/src/event.ts +408 -0
- package/src/iterator.ts +899 -0
- package/src/listener-registry.ts +178 -0
- package/src/ring-buffer.ts +234 -0
- package/src/sequence.ts +184 -0
- package/src/signal.ts +135 -0
- package/src/types.ts +137 -0
- package/src/utils.ts +426 -0
package/src/event.ts
ADDED
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import { Callback, Listener, FilterFunction, Predicate, Mapper, Reducer, Action, Fn, Emitter, MaybePromise, Promiseable } from './types.js';
|
|
2
|
+
import { Disposer } from './async.js';
|
|
3
|
+
import { Signal } from './signal.js';
|
|
4
|
+
import { ListenerRegistry } from './listener-registry.js';
|
|
5
|
+
import { DispatchResult } from './dispatch-result.js';
|
|
6
|
+
|
|
7
|
+
export type Unsubscribe = Action;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export class EventIterator<T> implements AsyncIterator<T, void, void> {
|
|
13
|
+
#signal: Signal<T>;
|
|
14
|
+
|
|
15
|
+
constructor(signal: Signal<T>) {
|
|
16
|
+
this.#signal = signal;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async next(): Promise<IteratorResult<T, void>> {
|
|
20
|
+
try {
|
|
21
|
+
const value = await this.#signal.receive();
|
|
22
|
+
return { value, done: false };
|
|
23
|
+
} catch {
|
|
24
|
+
return { value: undefined, done: true };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async return(): Promise<IteratorResult<T, void>> {
|
|
29
|
+
return { value: undefined, done: true };
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Multi-listener event emitter with async support.
|
|
35
|
+
* All registered listeners are called for each emission, and their return
|
|
36
|
+
* values are collected in a DispatchResult. Supports async iteration and
|
|
37
|
+
* an `onDispose` callback for cleanup.
|
|
38
|
+
*
|
|
39
|
+
* Differs from:
|
|
40
|
+
* - Signal: Event has persistent listeners; Signal is promise-based (receive per round)
|
|
41
|
+
* - Sequence: Event broadcasts to all listeners; Sequence is a single-consumer queue
|
|
42
|
+
*
|
|
43
|
+
* @template T - The type of value emitted to listeners (event payload)
|
|
44
|
+
* @template R - The return type of listener functions
|
|
45
|
+
*/
|
|
46
|
+
export class Event<T = unknown, R = unknown> implements Emitter<T, DispatchResult<void | R>>, Promiseable<T>, Promise<T>, Disposable, AsyncIterable<T> {
|
|
47
|
+
#listeners = new ListenerRegistry<[T], R | void>();
|
|
48
|
+
#signal = new Signal<T>();
|
|
49
|
+
#disposer: Disposer;
|
|
50
|
+
#onDispose?: Callback;
|
|
51
|
+
#sink?: Fn<[T], DispatchResult<void | R>>;
|
|
52
|
+
|
|
53
|
+
readonly [Symbol.toStringTag] = 'Event';
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Creates a new event.
|
|
57
|
+
*
|
|
58
|
+
* @param onDispose - A function to call on the event disposal.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```typescript
|
|
62
|
+
* // Create a click event.
|
|
63
|
+
* const clickEvent = new Event<[x: number, y: number], void>();
|
|
64
|
+
* clickEvent.on(([x, y]) => console.log(`Clicked at ${x}, ${y}`));
|
|
65
|
+
* clickEvent.emit([10, 20]);
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
constructor(onDispose?: Callback) {
|
|
69
|
+
this.#onDispose = onDispose;
|
|
70
|
+
this.#disposer = new Disposer(this);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Returns a bound emit function for use as a callback.
|
|
75
|
+
* Useful for passing to other APIs that expect a function.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```typescript
|
|
79
|
+
* const event = new Event<string>();
|
|
80
|
+
* someApi.onMessage(event.sink);
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
get sink(): Fn<[T], DispatchResult<void | R>> {
|
|
84
|
+
return (this.#sink ??= this.emit.bind(this));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* DOM EventListener interface compatibility.
|
|
89
|
+
* Allows the event to be used directly with addEventListener.
|
|
90
|
+
*/
|
|
91
|
+
handleEvent(event: T): void {
|
|
92
|
+
this.emit(event);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The number of listeners for the event.
|
|
97
|
+
*/
|
|
98
|
+
get size(): number {
|
|
99
|
+
return this.#listeners.size;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Emits a value to all registered listeners.
|
|
104
|
+
* Each listener is called with the value and their return values are collected.
|
|
105
|
+
*
|
|
106
|
+
* @param value - The value to emit to all listeners.
|
|
107
|
+
* @returns A DispatchResult containing all listener return values.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```typescript
|
|
111
|
+
* const event = new Event<string, number>();
|
|
112
|
+
* event.on(str => str.length);
|
|
113
|
+
* const result = event.emit('hello');
|
|
114
|
+
* await result.all(); // [5]
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
emit(value: T): DispatchResult<void | R> {
|
|
118
|
+
this.#signal.emit(value);
|
|
119
|
+
return new DispatchResult(this.#listeners.dispatch(value));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Checks if the given listener is NOT registered for this event.
|
|
124
|
+
*
|
|
125
|
+
* @param listener - The listener function to check against the registered listeners.
|
|
126
|
+
* @returns `true` if the listener is not already registered; otherwise, `false`.
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```typescript
|
|
130
|
+
* // Check if a listener is not already added
|
|
131
|
+
* if (event.lacks(myListener)) {
|
|
132
|
+
* event.on(myListener);
|
|
133
|
+
* }
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
lacks(listener: Listener<T, R>): boolean {
|
|
137
|
+
return !this.#listeners.has(listener);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Checks if the given listener is registered for this event.
|
|
142
|
+
*
|
|
143
|
+
* @param listener - The listener function to check.
|
|
144
|
+
* @returns `true` if the listener is currently registered; otherwise, `false`.
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* ```typescript
|
|
148
|
+
* // Verify if a listener is registered
|
|
149
|
+
* if (event.has(myListener)) {
|
|
150
|
+
* console.log('Listener is already registered');
|
|
151
|
+
* }
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
has(listener: Listener<T, R>): boolean {
|
|
155
|
+
return this.#listeners.has(listener);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Removes a specific listener from this event.
|
|
160
|
+
*
|
|
161
|
+
* @param listener - The listener to remove.
|
|
162
|
+
* @returns The event instance for chaining.
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```typescript
|
|
166
|
+
* // Remove a listener
|
|
167
|
+
* event.off(myListener);
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
off(listener: Listener<T, R>): this {
|
|
171
|
+
this.#listeners.off(listener);
|
|
172
|
+
return this;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Registers a listener that is called on every emission.
|
|
177
|
+
*
|
|
178
|
+
* @param listener - The function to call when the event occurs.
|
|
179
|
+
* @returns A function that removes this listener when called.
|
|
180
|
+
*
|
|
181
|
+
* @example
|
|
182
|
+
* ```typescript
|
|
183
|
+
* const unsubscribe = event.on((data) => {
|
|
184
|
+
* console.log('Event data:', data);
|
|
185
|
+
* });
|
|
186
|
+
* ```
|
|
187
|
+
*/
|
|
188
|
+
on(listener: Listener<T, R>): Unsubscribe {
|
|
189
|
+
this.#listeners.on(listener);
|
|
190
|
+
return () => void this.off(listener);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Registers a listener that is called only once on the next emission, then auto-removed.
|
|
195
|
+
*
|
|
196
|
+
* @param listener - The listener to trigger once.
|
|
197
|
+
* @returns A function that removes this listener when called (if it hasn't fired yet).
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* ```typescript
|
|
201
|
+
* const cancel = event.once((data) => {
|
|
202
|
+
* console.log('Received data once:', data);
|
|
203
|
+
* });
|
|
204
|
+
* ```
|
|
205
|
+
*/
|
|
206
|
+
once(listener: Listener<T, R>): Unsubscribe {
|
|
207
|
+
this.#listeners.once(listener);
|
|
208
|
+
return () => void this.off(listener);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Removes all listeners from the event.
|
|
213
|
+
* Does not dispose the event - new listeners can still be added after clearing.
|
|
214
|
+
*
|
|
215
|
+
* @returns The event instance for chaining.
|
|
216
|
+
*/
|
|
217
|
+
clear(): this {
|
|
218
|
+
this.#listeners.clear();
|
|
219
|
+
return this;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Waits for the next emission and returns the emitted value.
|
|
224
|
+
*
|
|
225
|
+
* @returns A promise that resolves with the next emitted value.
|
|
226
|
+
*/
|
|
227
|
+
receive(): Promise<T> {
|
|
228
|
+
return this.#signal.receive();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
then<OK = T, ERR = never>(onfulfilled?: Fn<[T], MaybePromise<OK>> | null, onrejected?: Fn<[unknown], MaybePromise<ERR>> | null): Promise<OK | ERR> {
|
|
232
|
+
return this.receive().then(onfulfilled, onrejected);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
catch<ERR = never>(onrejected?: Fn<[unknown], MaybePromise<ERR>> | null): Promise<T | ERR> {
|
|
236
|
+
return this.receive().catch(onrejected);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
finally(onfinally?: Action | null): Promise<T> {
|
|
240
|
+
return this.receive().finally(onfinally);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Waits for the next emission via `receive()` and wraps the outcome in a
|
|
245
|
+
* `PromiseSettledResult` - always resolves, never rejects.
|
|
246
|
+
*
|
|
247
|
+
* @returns A promise that resolves with the settled result.
|
|
248
|
+
*
|
|
249
|
+
* @example
|
|
250
|
+
* ```typescript
|
|
251
|
+
* const result = await event.settle();
|
|
252
|
+
* if (result.status === 'fulfilled') {
|
|
253
|
+
* console.log('Value:', result.value);
|
|
254
|
+
* } else {
|
|
255
|
+
* console.error('Reason:', result.reason);
|
|
256
|
+
* }
|
|
257
|
+
* ```
|
|
258
|
+
*/
|
|
259
|
+
settle(): Promise<PromiseSettledResult<T>> {
|
|
260
|
+
return this.receive().then(
|
|
261
|
+
(value) => ({ status: 'fulfilled', value }) as const,
|
|
262
|
+
(reason: unknown) => ({ status: 'rejected', reason }) as const,
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
[Symbol.asyncIterator](): AsyncIterator<T, void, void> {
|
|
267
|
+
return new EventIterator(this.#signal);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
dispose(): void {
|
|
271
|
+
this[Symbol.dispose]();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
[Symbol.dispose](): void {
|
|
275
|
+
if (this.#disposer[Symbol.dispose]()) {
|
|
276
|
+
this.#signal[Symbol.dispose]();
|
|
277
|
+
this.#listeners.clear();
|
|
278
|
+
void this.#onDispose?.();
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
export type EventParameters<T> = T extends Event<infer P, any> ? P : never;
|
|
284
|
+
|
|
285
|
+
export type EventReturn<T> = T extends Event<any, infer R> ? R : never;
|
|
286
|
+
|
|
287
|
+
export type AllEventsParameters<T extends Event<any, any>[]> = { [K in keyof T]: EventParameters<T[K]> }[number];
|
|
288
|
+
|
|
289
|
+
export type AllEventsResults<T extends Event<any, any>[]> = { [K in keyof T]: EventReturn<T[K]> }[number];
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Merges multiple events into a single event that triggers whenever any source triggers.
|
|
293
|
+
* Disposing the merged event unsubscribes from all sources.
|
|
294
|
+
*
|
|
295
|
+
* @param events - The events to merge.
|
|
296
|
+
* @returns A new Event that forwards emissions from all sources.
|
|
297
|
+
*
|
|
298
|
+
* @example
|
|
299
|
+
* ```typescript
|
|
300
|
+
* const mouseEvent = createEvent<MouseEvent>();
|
|
301
|
+
* const keyboardEvent = createEvent<KeyboardEvent>();
|
|
302
|
+
* const inputEvent = merge(mouseEvent, keyboardEvent);
|
|
303
|
+
* inputEvent.on(event => console.log('Input event:', event));
|
|
304
|
+
* ```
|
|
305
|
+
*/
|
|
306
|
+
export const merge = <Events extends Event<any, any>[]>(...events: Events): Event<AllEventsParameters<Events>, AllEventsResults<Events>> => {
|
|
307
|
+
const mergedEvent = new Event<AllEventsParameters<Events>, AllEventsResults<Events>>(() => {
|
|
308
|
+
for (const event of events) {
|
|
309
|
+
event.off(mergedEvent.sink);
|
|
310
|
+
}
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
for (const event of events) {
|
|
314
|
+
event.on(mergedEvent.sink);
|
|
315
|
+
}
|
|
316
|
+
return mergedEvent;
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Creates a periodic event that emits an incrementing counter (starting from 0) at a fixed interval.
|
|
321
|
+
* Disposing the event clears the interval.
|
|
322
|
+
*
|
|
323
|
+
* @param interval - The interval in milliseconds.
|
|
324
|
+
* @returns An Event that triggers at the specified interval.
|
|
325
|
+
*
|
|
326
|
+
* @example
|
|
327
|
+
* ```typescript
|
|
328
|
+
* const tickEvent = createInterval(1000);
|
|
329
|
+
* tickEvent.on(tickNumber => console.log('Tick:', tickNumber));
|
|
330
|
+
* ```
|
|
331
|
+
*/
|
|
332
|
+
export const createInterval = <R = unknown>(interval: number): Event<number, R> => {
|
|
333
|
+
let counter = 0;
|
|
334
|
+
const intervalEvent = new Event<number, R>(() => clearInterval(timerId));
|
|
335
|
+
const timerId: ReturnType<typeof setInterval> = setInterval(() => {
|
|
336
|
+
intervalEvent.emit(counter++);
|
|
337
|
+
}, interval);
|
|
338
|
+
return intervalEvent;
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Creates a new Event instance for multi-listener event handling.
|
|
343
|
+
*
|
|
344
|
+
* @example
|
|
345
|
+
* ```typescript
|
|
346
|
+
* const messageEvent = createEvent<string>();
|
|
347
|
+
* messageEvent.on(msg => console.log('Received:', msg));
|
|
348
|
+
* messageEvent.emit('Hello'); // All listeners receive 'Hello'
|
|
349
|
+
*
|
|
350
|
+
* // Listeners can return values, collected via DispatchResult
|
|
351
|
+
* const validateEvent = createEvent<string, boolean>();
|
|
352
|
+
* validateEvent.on(str => str.length > 0);
|
|
353
|
+
* validateEvent.on(str => str.length < 100);
|
|
354
|
+
* const results = await validateEvent.emit('test').all(); // [true, true]
|
|
355
|
+
* ```
|
|
356
|
+
*/
|
|
357
|
+
export const createEvent = <T = unknown, R = unknown>(): Event<T, R> => new Event<T, R>();
|
|
358
|
+
|
|
359
|
+
export default createEvent;
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Extracts the listener function type from an Event type.
|
|
363
|
+
* Useful for type-safe listener definitions.
|
|
364
|
+
*
|
|
365
|
+
* @template E - The Event type to extract the listener type from
|
|
366
|
+
*
|
|
367
|
+
* @example
|
|
368
|
+
* ```typescript
|
|
369
|
+
* type MyEvent = Event<string, boolean>;
|
|
370
|
+
* type MyListener = EventHandler<MyEvent>; // (value: string) => boolean | Promise<boolean>
|
|
371
|
+
* ```
|
|
372
|
+
*/
|
|
373
|
+
export type EventHandler<E> = E extends Event<infer T, infer R> ? Listener<T, R> : never;
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Extracts a filter function type for an Event's parameters.
|
|
377
|
+
* Used for creating type-safe event filters.
|
|
378
|
+
*
|
|
379
|
+
* @template E - The Event type to create a filter for
|
|
380
|
+
*/
|
|
381
|
+
export type EventFilter<E> = FilterFunction<EventParameters<E>>;
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Extracts a predicate function type for an Event's parameters.
|
|
385
|
+
* Used for type narrowing with event values.
|
|
386
|
+
*
|
|
387
|
+
* @template E - The Event type to create a predicate for
|
|
388
|
+
* @template P - The narrowed type that the predicate validates
|
|
389
|
+
*/
|
|
390
|
+
export type EventPredicate<E, P extends EventParameters<E>> = Predicate<EventParameters<E>, P>;
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Extracts a mapper function type for transforming Event parameters.
|
|
394
|
+
* Used for creating type-safe event value transformations.
|
|
395
|
+
*
|
|
396
|
+
* @template E - The Event type to create a mapper for
|
|
397
|
+
* @template M - The target type to map event values to
|
|
398
|
+
*/
|
|
399
|
+
export type EventMapper<E, M> = Mapper<EventParameters<E>, M>;
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Extracts a reducer function type for Event parameters.
|
|
403
|
+
* Used for creating type-safe event value reducers.
|
|
404
|
+
*
|
|
405
|
+
* @template E - The Event type to create a reducer for
|
|
406
|
+
* @template R - The accumulator type for the reduction
|
|
407
|
+
*/
|
|
408
|
+
export type EventReducer<E, R> = Reducer<EventParameters<E>, R>;
|