evnty 2.1.105 → 3.0.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/src/index.ts CHANGED
@@ -8,11 +8,6 @@ export interface Listener<T, R = unknown> {
8
8
  (event: T): MaybePromise<R | void>;
9
9
  }
10
10
 
11
- export interface Result<T, E> {
12
- ok: boolean;
13
- result: T | E;
14
- }
15
-
16
11
  export interface FilterFunction<T> {
17
12
  (event: T): MaybePromise<boolean>;
18
13
  }
@@ -31,7 +26,36 @@ export interface Reducer<T, R> {
31
26
  (result: R, event: T): MaybePromise<R>;
32
27
  }
33
28
 
34
- export type Listeners<T, R> = Listener<T, R>[];
29
+ export interface Expander<T, R> {
30
+ (event: T): MaybePromise<R>;
31
+ }
32
+
33
+ /**
34
+ * Removes a listener from the provided array of listeners. It searches for the listener and removes all instances of it from the array.
35
+ * This method ensures that the listener is fully unregistered, preventing any residual calls to a potentially deprecated handler.
36
+ *
37
+ * @param {Listener<T, R>[]} listeners - The array of listeners from which to remove the listener.
38
+ * @param {Listener<T, R>} listener - The listener function to remove from the list of listeners.
39
+ * @returns {boolean} - Returns `true` if the listener was found and removed, `false` otherwise.
40
+ *
41
+ * @template T - The type of the event that listeners are associated with.
42
+ * @template R - The type of the return value that listeners are expected to return.
43
+ *
44
+ * @example
45
+ * // Assuming an array of listeners for click events
46
+ * const listeners = [onClickHandler1, onClickHandler2];
47
+ * const wasRemoved = removeListener(listeners, onClickHandler1);
48
+ * console.log(wasRemoved); // Output: true
49
+ */
50
+ export const removeListener = <T, R>(listeners: Listener<T, R>[], listener: Listener<T, R>): boolean => {
51
+ let index = listeners.indexOf(listener);
52
+ const wasRemoved = index !== -1;
53
+ while (~index) {
54
+ listeners.splice(index, 1);
55
+ index = listeners.indexOf(listener);
56
+ }
57
+ return wasRemoved;
58
+ };
35
59
 
36
60
  /**
37
61
  * An abstract class that extends the built-in Function class. It allows instances of the class
@@ -46,34 +70,34 @@ export abstract class FunctionExt extends Function {
46
70
  }
47
71
  }
48
72
 
49
- export interface Dismiss {
73
+ export interface Unsubscribe {
50
74
  (): MaybePromise<void>;
51
75
  }
52
76
 
53
77
  /**
54
78
  * @internal
55
79
  */
56
- export class Dismiss extends FunctionExt {
80
+ export class Unsubscribe extends FunctionExt {
57
81
  constructor(callback: Callback) {
58
82
  super(callback);
59
83
  }
60
84
 
61
- pre(callback: Callback): Dismiss {
62
- return new Dismiss(async () => {
85
+ pre(callback: Callback): Unsubscribe {
86
+ return new Unsubscribe(async () => {
63
87
  await callback();
64
88
  await this();
65
89
  });
66
90
  }
67
91
 
68
- post(callback: Callback): Dismiss {
69
- return new Dismiss(async () => {
92
+ post(callback: Callback): Unsubscribe {
93
+ return new Unsubscribe(async () => {
70
94
  await this();
71
95
  await callback();
72
96
  });
73
97
  }
74
98
 
75
- countdown(count: number): Dismiss {
76
- return new Dismiss(async () => {
99
+ countdown(count: number): Unsubscribe {
100
+ return new Unsubscribe(async () => {
77
101
  if (!--count) {
78
102
  await this();
79
103
  }
@@ -81,21 +105,50 @@ export class Dismiss extends FunctionExt {
81
105
  }
82
106
  }
83
107
 
84
- const eventEmitter = <A, R>(listeners: Listeners<A, R>, event: A): Promise<(void | R)[]> => Promise.all(listeners.map((listener) => listener(event)));
85
-
86
- type EventType<T> = T extends undefined ? void : T;
87
-
88
- export interface Event<T = unknown, R = void> {
89
- (event?: EventType<T>): Promise<(R | undefined)[]>;
108
+ export interface Event<T = any, R = any> {
109
+ (event: T): Promise<(void | Awaited<R>)[]>;
90
110
  }
91
111
 
92
- export type EventParameters<T> = T extends Event<infer P, unknown> ? P : never;
93
-
94
- export type EventResult<T> = T extends Event<unknown, infer R> ? R : never;
95
-
96
- export type AllEventsParameters<T extends Event<unknown, unknown>[]> = { [K in keyof T]: EventParameters<T[K]> }[number];
112
+ /**
113
+ * Represents a pair of events handling both successful outcomes and errors.
114
+ * This interface is used to manage asynchronous operations where events can either
115
+ * result in a successful output or an error.
116
+ *
117
+ * The `value` event is triggered when the operation succeeds and emits a result.
118
+ * The `error` event is triggered when the operation encounters an error, allowing
119
+ * error handling mechanisms to process or log the error accordingly.
120
+ *
121
+ * @template T The type of data emitted by the successful outcome of the event.
122
+ * @template R The type of data (if any) emitted by the error event.
123
+ * @template E The type of error information emitted by the error event, usually an Error object or string.
124
+ *
125
+ * @example
126
+ * // Assume an asynchronous function that fetches user data
127
+ * function fetchUserData(): ResultEvents<UserData, Error> {
128
+ * const success = new Event<UserData>();
129
+ * const failure = new Event<Error>();
130
+ *
131
+ * fetch('/api/user')
132
+ * .then(response => response.json())
133
+ * .then(data => success(data))
134
+ * .catch(error => failure(error));
135
+ *
136
+ * return { value: success, error: failure };
137
+ * }
138
+ *
139
+ * const userDataEvent = fetchUserData();
140
+ * userDataEvent.value.on(data => console.log('User data received:', data));
141
+ * userDataEvent.error.on(error => console.error('An error occurred:', error));
142
+ */
143
+ export interface ResultEvents<T, R, E = unknown> {
144
+ value: Event<T, R>; // Event triggered on a successful result.
145
+ error: Event<E, void>; // Event triggered on an error occurrence.
146
+ }
97
147
 
98
- export type AllEventsResults<T extends Event<unknown, unknown>[]> = { [K in keyof T]: EventResult<T[K]> }[number];
148
+ export interface Queue<T> {
149
+ pop(): MaybePromise<T | undefined>;
150
+ stop(): MaybePromise<void>;
151
+ }
99
152
 
100
153
  /**
101
154
  * A class representing an anonymous event that can be listened to or triggered.
@@ -107,7 +160,12 @@ export class Event<T, R> extends FunctionExt {
107
160
  /**
108
161
  * The array of listeners for the event.
109
162
  */
110
- private listeners: Listeners<T, R>;
163
+ private listeners: Listener<T, R>[];
164
+ /**
165
+ * The array of listeners for the event.
166
+ */
167
+ private addSpies: Array<(listener: Listener<T, R> | void) => void> = [];
168
+ private removeSpies: Array<(listener: Listener<T, R> | void) => void> = [];
111
169
 
112
170
  /**
113
171
  * A function that disposes of the event and its listeners.
@@ -119,16 +177,16 @@ export class Event<T, R> extends FunctionExt {
119
177
  * @example
120
178
  * // Create a click event.
121
179
  * const clickEvent = new Event<[x: number, y: number], void>();
122
- * clickEvent.on((x, y) => console.log(`Clicked at ${x}, ${y}`));
180
+ * clickEvent.on(([x, y]) => console.log(`Clicked at ${x}, ${y}`));
123
181
  *
124
182
  * @param dispose - A function to call on the event disposal.
125
183
  */
126
184
  constructor(dispose?: Callback) {
127
- const listeners: Listeners<T, R> = [];
128
- const fn = (event: T) => eventEmitter(listeners, event);
129
-
130
- super(fn);
185
+ const listeners: Listener<T, R>[] = [];
186
+ // passes listeners exceptions to catch method
187
+ super(async (value: T): Promise<(void | Awaited<R>)[]> => Promise.all(listeners.map(async (listener) => listener(await value))));
131
188
  this.listeners = listeners;
189
+
132
190
  this.dispose = async () => {
133
191
  this.clear();
134
192
  await dispose?.();
@@ -143,51 +201,93 @@ export class Event<T, R> extends FunctionExt {
143
201
  }
144
202
 
145
203
  /**
146
- * Checks if a listener is not registered for the event.
147
- * @param listener - The listener to check.
148
- * @returns `true` if the listener is not registered, `false` otherwise.
204
+ * Checks if a specific listener is not registered for the event.
205
+ * This method is typically used to verify whether an event listener has not been added to prevent duplicate registrations.
206
+ * @param listener - The listener function to check against the registered listeners.
207
+ * @returns `true` if the listener is not already registered; otherwise, `false`.
208
+ *
209
+ * @example
210
+ * // Check if a listener is not already added
211
+ * if (event.lacks(myListener)) {
212
+ * event.on(myListener);
213
+ * }
214
+ *
149
215
  */
150
216
  lacks(listener: Listener<T, R>): boolean {
151
217
  return this.listeners.indexOf(listener) === -1;
152
218
  }
153
219
 
154
220
  /**
155
- * Checks if a listener is registered for the event.
156
- * @param listener - The listener to check.
157
- * @returns `true` if the listener is registered, `false` otherwise.
221
+ * Checks if a specific listener is registered for the event.
222
+ * This method is used to confirm the presence of a listener in the event's registration list.
223
+ *
224
+ * @param listener - The listener function to verify.
225
+ * @returns `true` if the listener is currently registered; otherwise, `false`.
226
+ *
227
+ * @example
228
+ * // Verify if a listener is registered
229
+ * if (event.has(myListener)) {
230
+ * console.log('Listener is already registered');
231
+ * }
158
232
  */
159
233
  has(listener: Listener<T, R>): boolean {
160
234
  return this.listeners.indexOf(listener) !== -1;
161
235
  }
162
236
 
163
237
  /**
164
- * Removes a listener from the event.
238
+ * Removes a listener from the event's registration list.
239
+ * This method is used when the listener is no longer needed, helping to prevent memory leaks and unnecessary executions.
240
+ *
165
241
  * @param listener - The listener to remove.
242
+ * @returns The event instance, allowing for method chaining.
243
+ *
244
+ * @example
245
+ * // Remove a listener
246
+ * event.off(myListener);
166
247
  */
167
- off(listener: Listener<T, R>): void {
168
- let index = this.listeners.indexOf(listener);
169
- while (~index) {
170
- this.listeners.splice(index, 1);
171
- index = this.listeners.indexOf(listener);
248
+ off(listener: Listener<T, R>): this {
249
+ if (removeListener(this.listeners, listener)) {
250
+ this.removeSpies.forEach((spy) => spy(listener));
172
251
  }
252
+ return this;
173
253
  }
174
254
 
175
255
  /**
176
- * Adds a listener to the event.
177
- * @param listener - The listener to add.
178
- * @returns An object that can be used to remove the listener.
256
+ * Registers a listener that gets triggered whenever the event is emitted.
257
+ * This is the primary method for adding event handlers that will react to the event being triggered.
258
+ *
259
+ * @param listener - The function to call when the event occurs.
260
+ * @returns An object that can be used to unsubscribe the listener, ensuring easy cleanup.
261
+ *
262
+ * @example
263
+ * // Add a listener to an event
264
+ * const unsubscribe = event.on((data) => {
265
+ * console.log('Event data:', data);
266
+ * });
179
267
  */
180
- on(listener: Listener<T, R>): Dismiss {
268
+ on(listener: Listener<T, R>): Unsubscribe {
181
269
  this.listeners.push(listener);
182
- return new Dismiss(() => this.off(listener));
270
+ if (this.addSpies.length > 0) {
271
+ this.addSpies.forEach((spy) => spy(listener));
272
+ }
273
+ return new Unsubscribe(() => {
274
+ this.off(listener);
275
+ });
183
276
  }
184
277
 
185
278
  /**
186
- * Adds a listener to the event that will only be called once.
187
- * @param listener - The listener to add.
188
- * @returns An object that can be used to remove the listener.
279
+ * Adds a listener that will be called only once the next time the event is emitted.
280
+ * This method is useful for one-time notifications or single-trigger scenarios.
281
+ *
282
+ * @param listener - The listener to trigger once.
283
+ * @returns An object that can be used to remove the listener if the event has not yet occurred.
284
+ * @example
285
+ * // Register a one-time listener
286
+ * const onceUnsubscribe = event.once((data) => {
287
+ * console.log('Received data once:', data);
288
+ * });
189
289
  */
190
- once(listener: Listener<T, R>): Dismiss {
290
+ once(listener: Listener<T, R>): Unsubscribe {
191
291
  const oneTimeListener = (event: T) => {
192
292
  this.off(oneTimeListener);
193
293
  return listener(event);
@@ -196,69 +296,94 @@ export class Event<T, R> extends FunctionExt {
196
296
  }
197
297
 
198
298
  /**
199
- * Returns a Promise that resolves with the first emitted by the event arguments.
200
- * @returns A Promise that resolves with the first emitted by the event.
299
+ * Returns a Promise that resolves with the first event argument emitted.
300
+ * This method is useful for scenarios where you need to wait for the first occurrence
301
+ * of an event and then perform actions based on the event data.
302
+ *
303
+ * @returns {Promise<T>} A Promise that resolves with the first event argument emitted.
304
+ * @example
305
+ * const clickEvent = new Event<[number, number]>();
306
+ * clickEvent.onceAsync().then(([x, y]) => {
307
+ * console.log(`First click at (${x}, ${y})`);
308
+ * });
201
309
  */
202
310
  onceAsync(): Promise<T> {
203
- return new Promise((resolve) => this.once((event) => resolve(event)));
311
+ return new Promise<T>((resolve) => this.once((event) => resolve(event)));
204
312
  }
205
313
 
206
314
  /**
207
- * Removes all listeners from the event.
315
+ * Removes all listeners from the event, effectively resetting it. This is useful when you need to
316
+ * cleanly dispose of all event handlers to prevent memory leaks or unwanted triggerings after certain conditions.
317
+ *
318
+ * @returns {this} The instance of the event, allowing for method chaining.
319
+ * @example
320
+ * const myEvent = new Event();
321
+ * myEvent.on(data => console.log(data));
322
+ * myEvent.clear(); // Clears all listeners
208
323
  */
209
- clear(): void {
324
+ clear(): this {
210
325
  this.listeners.splice(0);
326
+ this.removeSpies.forEach((spy) => spy());
327
+ return this;
211
328
  }
212
329
 
213
330
  /**
214
- * Returns a new event that only triggers when the provided filter function returns `true`.
215
- * @example
216
- * const spacePressEvent = keyboardEvent.filter((key) => key === 'Space');
331
+ * Filters events, creating a new event that only triggers when the provided filter function returns `true`.
332
+ * This method can be used to selectively process events that meet certain criteria.
217
333
  *
218
- * @param filter The filter function to apply to the event.
219
- * @returns A new event that only triggers when the provided filter function returns `true`.
334
+ * @param {Filter<T, P>} filter The filter function or predicate to apply to each event.
335
+ * @returns {Event<P, R>} A new event that only triggers for filtered events.
336
+ * @example
337
+ * const keyPressedEvent = new Event<string>();
338
+ * const enterPressedEvent = keyPressedEvent.filter(key => key === 'Enter');
339
+ * enterPressedEvent.on(() => console.log('Enter key was pressed.'));
220
340
  */
221
341
  filter<P extends T>(predicate: Predicate<T, P>): Event<P, R>;
222
342
  filter<P extends T>(filter: FilterFunction<T>): Event<P, R>;
223
343
  filter<P extends T>(filter: Filter<T, P>): Event<P, R> {
224
- const dispose = this.on(async (event: T) => {
344
+ const unsubscribe = this.on(async (event: T) => {
225
345
  if (filteredEvent.size > 0 && (await filter(event))) {
226
- await filteredEvent(event as EventType<P>);
346
+ await filteredEvent(event as P);
227
347
  }
228
348
  });
229
- const filteredEvent = new Event<P, R>(dispose);
349
+ const filteredEvent = new Event<P, R>(unsubscribe);
230
350
  return filteredEvent;
231
351
  }
232
352
 
233
353
  /**
234
- * Returns a new event that will only be triggered once the provided filter function returns `true`.
235
- * @example
236
- * const escPressEvent = keyboardEvent.first((key) => key === 'Esc');
237
- * await escPressEvent.toPromise();
354
+ * Creates a new event that will only be triggered once when the provided filter function returns `true`.
355
+ * This method is useful for handling one-time conditions in a stream of events.
238
356
  *
239
- * @param filter - The filter function.
240
- * @returns A new event that will only be triggered once the provided filter function returns `true`.
357
+ * @param {Filter<T, P>} filter - The filter function or predicate.
358
+ * @returns {Event<P, R>} A new event that will be triggered only once when the filter condition is met.
359
+ * @example
360
+ * const sizeChangeEvent = new Event<number>();
361
+ * const sizeReachedEvent = sizeChangeEvent.first(size => size > 1024);
362
+ * sizeReachedEvent.on(() => console.log('Size threshold exceeded.'));
241
363
  */
242
364
  first<P extends T>(predicate: Predicate<T, P>): Event<P, R>;
243
365
  first<P extends T>(filter: FilterFunction<T>): Event<P, R>;
244
366
  first<P extends T>(filter: Filter<T, P>): Event<P, R> {
245
- const dispose = this.on(async (event: T) => {
367
+ const unsubscribe = this.on(async (event: T) => {
246
368
  if (filteredEvent.size > 0 && (await filter(event))) {
247
- await dispose();
248
- await filteredEvent(event as EventType<P>);
369
+ await unsubscribe();
370
+ await filteredEvent(event as P);
249
371
  }
250
372
  });
251
- const filteredEvent = new Event<P, R>(dispose);
373
+ const filteredEvent = new Event<P, R>(unsubscribe);
252
374
  return filteredEvent;
253
375
  }
254
376
 
255
377
  /**
256
- * Returns a new promise that will be resolved once the provided filter function returns `true`.
257
- * @example
258
- * const escPressEvent = await keyboardEvent.firstAsync((key) => key === 'Esc');
378
+ * Returns a Promise that resolves once an event occurs that meets the filter criteria.
379
+ * This method is particularly useful for handling asynchronous flows where you need to wait for a specific condition.
259
380
  *
260
- * @param filter - The filter function.
261
- * @returns A new promise that will be resolved once the provided filter function returns `true`.
381
+ * @param {Filter<T, P>} filter - The filter function or predicate.
382
+ * @returns {Promise<P>} A Promise that resolves once the filter condition is met.
383
+ * @example
384
+ * const mouseEvent = new Event<{x: number, y: number}>();
385
+ * const clickAtPosition = mouseEvent.firstAsync(pos => pos.x > 200 && pos.y > 200);
386
+ * clickAtPosition.then(pos => console.log(`Clicked at (${pos.x}, ${pos.y})`));
262
387
  */
263
388
  firstAsync<P extends T>(predicate: Predicate<T, P>): Promise<P>;
264
389
  firstAsync<P extends T>(filter: FilterFunction<T>): Promise<P>;
@@ -267,83 +392,332 @@ export class Event<T, R> extends FunctionExt {
267
392
  }
268
393
 
269
394
  /**
270
- * Returns a new event that maps the values of this event using the provided mapper function.
395
+ * Transforms the data emitted by this event using a mapping function. Each emitted event is processed by the `mapper`
396
+ * function, which returns a new value that is then emitted by the returned `Event` instance. This is useful for data transformation
397
+ * or adapting the event's data structure.
398
+ *
399
+ * @template M The type of data that the mapper function will produce.
400
+ * @template MR The type of data emitted by the mapped event, usually related to or the same as `M`.
401
+ * @param {Mapper<T, M>} mapper A function that takes the original event data and returns the transformed data.
402
+ * @returns {Event<M, MR>} A new `Event` instance that emits the mapped values.
403
+ *
271
404
  * @example
272
- * const keyPressEvent = keyboardEvent.map((key) => key.toUpperCase()); // ['a'] -> ['A']
405
+ * // Assuming an event that emits numbers, create a new event that emits their squares.
406
+ * const numberEvent = new Event<number>();
407
+ * const squaredEvent = numberEvent.map(num => num * num);
408
+ * squaredEvent.on(squared => console.log('Squared number:', squared));
409
+ * await numberEvent(5); // Logs: "Squared number: 25"
273
410
  *
274
411
  * @param mapper A function that maps the values of this event to a new value.
275
412
  * @returns A new event that emits the mapped values.
276
413
  */
277
414
  map<M, MR = R>(mapper: Mapper<T, M>): Event<M, MR> {
278
- const dispose = this.on(async (event) => {
415
+ const unsubscribe = this.on(async (event) => {
279
416
  if (mappedEvent.size > 0) {
280
417
  const value = await mapper(event);
281
- await mappedEvent(value as EventType<M>);
418
+ await mappedEvent(value);
282
419
  }
283
420
  });
284
- const mappedEvent = new Event<M, MR>(dispose);
421
+ const mappedEvent = new Event<M, MR>(unsubscribe);
285
422
  return mappedEvent;
286
423
  }
287
424
 
288
425
  /**
289
- * Returns a new event that reduces the emitted values using the provided reducer function.
426
+ * Accumulates the values emitted by this event using a reducer function, starting from an initial value. The reducer
427
+ * function takes the accumulated value and the latest emitted event data, then returns a new accumulated value. This
428
+ * new value is then emitted by the returned `Event` instance. This is particularly useful for accumulating state over time.
429
+ *
290
430
  * @example
291
431
  * const sumEvent = numberEvent.reduce((a, b) => a + b, 0);
292
432
  * sumEvent.on((sum) => console.log(sum)); // 1, 3, 6
293
- * sumEvent(1);
294
- * sumEvent(2);
295
- * sumEvent(3);
296
- *
297
- * @typeParam A The type of the accumulated value.
298
- * @typeParam AR The type of the reduced value.
299
- * @param {Reducer<T, A>} reducer The reducer function that accumulates the values emitted by this `Event`.
300
- * @param {A} init The initial value of the accumulated value.
301
- * @returns {Event<[A], AR>} A new `Event` that emits the reduced value.
433
+ * await sumEvent(1);
434
+ * await sumEvent(2);
435
+ * await sumEvent(3);
436
+ *
437
+ * @template A The type of the accumulator value.
438
+ * @template AR The type of data emitted by the reduced event, usually the same as `A`.
439
+ * @param {Reducer<T, A>} reducer A function that takes the current accumulated value and the new event data, returning the new accumulated value.
440
+ * @param {A} init The initial value of the accumulator.
441
+ * @returns {Event<A, AR>} A new `Event` instance that emits the reduced value.
442
+ *
302
443
  */
303
444
  reduce<A, AR = R>(reducer: Reducer<T, A>, init: A): Event<A, AR> {
304
445
  let value = init;
305
- const dispose = this.on(async (event) => {
446
+ const unsubscribe = this.on(async (event) => {
306
447
  if (reducedEvent.size > 0) {
307
448
  value = await reducer(value, event);
308
- await reducedEvent(value as EventType<A>);
449
+ await reducedEvent(value);
309
450
  }
310
451
  });
311
- const reducedEvent = new Event<A, AR>(dispose);
452
+ const reducedEvent = new Event<A, AR>(unsubscribe);
312
453
  return reducedEvent;
313
454
  }
314
455
 
315
456
  /**
316
- * Returns a new debounced event that will not fire until a certain amount of time has passed
317
- * since the last time it was triggered.
457
+ * Transforms each event's data into multiple events using an expander function. The expander function takes
458
+ * the original event's data and returns an array of new data elements, each of which will be emitted individually
459
+ * by the returned `Event` instance. This method is useful for scenarios where an event's data can naturally
460
+ * be expanded into multiple, separate pieces of data which should each trigger further processing independently.
461
+ *
462
+ * @template ET - The type of data elements in the array returned by the expander function.
463
+ * @template ER - The type of responses emitted by the expanded event, usually related to or the same as `ET`.
464
+ * @param {Expander<T, ET[]>} expander - A function that takes the original event data and returns an array of new data elements.
465
+ * @returns {Event<ET, ER>} - A new `Event` instance that emits each value from the array returned by the expander function.
466
+ *
467
+ * @example
468
+ * // Assuming an event that emits a sentence, create a new event that emits each word from the sentence separately.
469
+ * const sentenceEvent = new Event<string>();
470
+ * const wordEvent = sentenceEvent.expand(sentence => sentence.split(' '));
471
+ * wordEvent.on(word => console.log('Word:', word));
472
+ * await sentenceEvent('Hello world'); // Logs: "Word: Hello", "Word: world"
473
+ */
474
+ expand<ET, ER>(expander: Expander<T, ET[]>): Event<ET, ER> {
475
+ const unsubscribe = this.on(async (event) => {
476
+ if (expandedEvent.size > 0) {
477
+ const values = await expander(event);
478
+ for (const value of values) {
479
+ await expandedEvent(value);
480
+ }
481
+ }
482
+ });
483
+ const expandedEvent = new Event<ET, ER>(unsubscribe);
484
+ return expandedEvent;
485
+ }
486
+
487
+ /**
488
+ * Creates a new event that emits values based on a conductor event. The orchestrated event will emit the last value
489
+ * captured from the original event each time the conductor event is triggered. This method is useful for synchronizing
490
+ * events, where the emission of one event controls the timing of another.
491
+ *
492
+ * @example
493
+ * const rightClickPositionEvent = mouseMoveEvent.orchestrate(mouseRightClickEvent);
494
+ *
495
+ * @example
496
+ * // An event that emits whenever a "tick" event occurs.
497
+ * const tickEvent = new Event<void>();
498
+ * const dataEvent = new Event<string>();
499
+ * const synchronizedEvent = dataEvent.orchestrate(tickEvent);
500
+ * synchronizedEvent.on(data => console.log('Data on tick:', data));
501
+ * await dataEvent('Hello');
502
+ * await dataEvent('World!');
503
+ * await tickEvent(); // Logs: "Data on tick: World!"
504
+ *
505
+ * @template T The type of data emitted by the original event.
506
+ * @template R The type of data emitted by the orchestrated event, usually the same as `T`.
507
+ * @param {Event<unknown, unknown>} conductor An event that signals when the orchestrated event should emit.
508
+ * @returns {Event<T, R>} An orchestrated event that emits values based on the conductor's trigger.
509
+ *
510
+ */
511
+ orchestrate(conductor: Event<any, any>): Event<T, R> {
512
+ let initialized = false;
513
+ let lastValue: T;
514
+ const unsubscribe = this.on(async (event) => {
515
+ initialized = true;
516
+ lastValue = event;
517
+ });
518
+ const unsubscribeConductor = conductor.on(async () => {
519
+ if (initialized) {
520
+ await orchestratedEvent(lastValue);
521
+ initialized = false;
522
+ }
523
+ });
524
+
525
+ const orchestratedEvent = new Event<T, R>(unsubscribe.post(unsubscribeConductor));
526
+ return orchestratedEvent;
527
+ }
528
+ /**
529
+ * Creates a debounced event that delays triggering until after a specified interval has elapsed
530
+ * following the last time it was invoked. This method is particularly useful for limiting the rate
531
+ * at which a function is executed. Common use cases include handling rapid user inputs, window resizing,
532
+ * or scroll events.
533
+ *
318
534
  * @example
319
535
  * const debouncedEvent = textInputEvent.debounce(100);
320
- * debouncedEvent.on((str) => console.log(str)); // 'test'
321
- * event('t');
322
- * event('te');
323
- * event('tes');
324
- * event('test');
325
- *
326
- * @param interval - The amount of time to wait before firing the debounced event, in milliseconds.
327
- * @returns A new debounced event.
536
+ * debouncedEvent.on((str) => console.log(str)); // only 'text' is emitted
537
+ * await event('t');
538
+ * await event('te');
539
+ * await event('tex');
540
+ * await event('text');
541
+ *
542
+ * @param {number} interval - The amount of time to wait (in milliseconds) before firing the debounced event.
543
+ * @returns {ResultEvents<T, R, unknown>} An object containing two events: `value` for the debounced successful
544
+ * outputs and `error` for catching errors during the debounce handling.
328
545
  */
329
- debounce(interval: number): Event<T, R> {
546
+ debounce(interval: number): ResultEvents<T, R, unknown> {
330
547
  let timer: ReturnType<typeof setTimeout>;
331
- const dispose = this.on((event) => {
548
+ const unsubscribe = this.on((event) => {
332
549
  clearTimeout(timer);
333
- timer = setTimeout(() => debouncedEvent(event as EventType<T>), interval);
550
+ timer = setTimeout(() => {
551
+ value(event).catch(error);
552
+ }, interval);
334
553
  });
335
- const debouncedEvent = new Event<T, R>(dispose);
336
- return debouncedEvent;
554
+ const value = new Event<T, R>(unsubscribe);
555
+ const error = new Event<unknown, void>();
556
+ return { value, error };
557
+ }
558
+
559
+ /**
560
+ * Aggregates multiple event emissions into batches and emits the batched events either at specified
561
+ * time intervals or when the batch reaches a predefined size. This method is useful for grouping
562
+ * a high volume of events into manageable chunks, such as logging or processing data in bulk.
563
+ *
564
+ * @example
565
+ * // Batch messages for bulk processing every 1 second or when 10 messages are collected
566
+ * const messageEvent = createEvent<string, void>();
567
+ * const batchedMessageEvent = messageEvent.batch(1000, 10);
568
+ * batchedMessageEvent.value.on((messages) => console.log('Batched Messages:', messages));
569
+ *
570
+ * @param {number} interval - The time in milliseconds between batch emissions.
571
+ * @param {number} [size] - Optional. The maximum size of each batch. If specified, triggers a batch emission
572
+ * once the batch reaches this size, regardless of the interval.
573
+ * @returns {ResultEvents<T[], R, unknown>} An object containing two events: `value` for the batched results
574
+ * and `error` for errors that may occur during batching.
575
+ */
576
+ batch(interval: number, size?: number): ResultEvents<T[], R, unknown> {
577
+ let timer: ReturnType<typeof setTimeout>;
578
+ const batch: T[] = [];
579
+
580
+ const emitBatch = () => {
581
+ if (batch.length !== 0) {
582
+ clearTimeout(timer);
583
+ value(batch.splice(0)).catch(error);
584
+ }
585
+ };
586
+
587
+ const unsubscribe = this.on((event) => {
588
+ if (batch.length === 0) {
589
+ timer = setTimeout(emitBatch, interval);
590
+ }
591
+
592
+ batch.push(event);
593
+ if (size !== undefined && batch.length >= size) {
594
+ emitBatch();
595
+ }
596
+ });
597
+ const value = new Event<T[], R>(unsubscribe);
598
+ const error = new Event<unknown, void>();
599
+ return { value, error };
600
+ }
601
+
602
+ /**
603
+ * Transforms an event into an AsyncIterable that yields values as they are emitted by the event. This allows for the consumption
604
+ * of event data using async iteration mechanisms. The iterator generated will yield all emitted values until the event
605
+ * signals it should no longer be active.
606
+ *
607
+ * @returns {AsyncIterable<T>} An async iterable that yields values emitted by the event.
608
+ * @example
609
+ * // Assuming an event that emits numbers
610
+ * const numberEvent = new Event<number>();
611
+ * const numberIterable = numberEvent.generator();
612
+ * (async () => {
613
+ * for await (const num of numberIterable) {
614
+ * console.log('Number:', num);
615
+ * }
616
+ * })();
617
+ * await numberEvent(1);
618
+ * await numberEvent(2);
619
+ * await numberEvent(3);
620
+ */
621
+ generator(): AsyncIterable<T> {
622
+ const queue: T[] = [];
623
+ const valueEvent = new Event<boolean>();
624
+ const emitEvent = async (value: T) => {
625
+ queue.push(value);
626
+ await valueEvent(false);
627
+ };
628
+ const unsubscribe = this.on(emitEvent).pre(() => {
629
+ removeListener(this.removeSpies, spy);
630
+ });
631
+
632
+ const spy: (typeof this.removeSpies)[number] = (target = emitEvent) => {
633
+ if (target === emitEvent) {
634
+ valueEvent(true);
635
+ unsubscribe();
636
+ }
637
+ };
638
+ this.removeSpies.push(spy);
639
+
640
+ return {
641
+ [Symbol.asyncIterator]() {
642
+ return {
643
+ async next() {
644
+ if (queue.length) {
645
+ return Promise.resolve({ value: queue.shift()!, done: false });
646
+ }
647
+ const result = await valueEvent.onceAsync();
648
+ if (!result) {
649
+ return Promise.resolve({ value: queue.shift()!, done: false });
650
+ }
651
+ return Promise.resolve({ value: undefined, done: true });
652
+ },
653
+ };
654
+ },
655
+ };
656
+ }
657
+
658
+ /**
659
+ * Creates a queue from the event, where each emitted value is sequentially processed. The returned object allows popping elements
660
+ * from the queue, ensuring that elements are handled one at a time. This method is ideal for scenarios where order and sequential processing are critical.
661
+ *
662
+ * @returns {Queue<T>} An object representing the queue. The 'pop' method retrieves the next element from the queue, while 'stop' halts further processing.
663
+ * @example
664
+ * // Queueing tasks for sequential execution
665
+ * const taskEvent = new Event<string>();
666
+ * const taskQueue = taskEvent.queue();
667
+ * await taskEvent('Task 1');
668
+ * await taskEvent('Task 2');
669
+ * (async () => {
670
+ * console.log('Processing:', await taskQueue.pop()); // Processing: Task 1
671
+ * console.log('Processing:', await taskQueue.pop()); // Processing: Task 2
672
+ * })();
673
+ */
674
+ queue(): Queue<T> {
675
+ const queue: T[] = [];
676
+ const valueEvent = new Event<void>();
677
+ const unsubscribe = this.on(async (value) => {
678
+ queue.push(value);
679
+ await valueEvent();
680
+ });
681
+
682
+ return {
683
+ async pop() {
684
+ if (!queue.length) {
685
+ await valueEvent.onceAsync();
686
+ }
687
+ return queue.shift();
688
+ },
689
+ async stop() {
690
+ await unsubscribe();
691
+ },
692
+ };
337
693
  }
338
694
  }
339
695
 
696
+ export type EventParameters<T> = T extends Event<infer P, any> ? P : never;
697
+
698
+ export type EventResult<T> = T extends Event<any, infer R> ? R : never;
699
+
700
+ export type AllEventsParameters<T extends Event<any, any>[]> = { [K in keyof T]: EventParameters<T[K]> }[number];
701
+
702
+ export type AllEventsResults<T extends Event<any, any>[]> = { [K in keyof T]: EventResult<T[K]> }[number];
703
+
340
704
  /**
341
- * Merges multiple events into a single event.
342
- * @example
343
- * const inputEvent = Event.merge(mouseEvent, keyboardEvent);
705
+ * Merges multiple events into a single event. This function takes any number of `Event` instances
706
+ * and returns a new `Event` that triggers whenever any of the input events trigger. The parameters
707
+ * and results of the merged event are derived from the input events, providing a flexible way to
708
+ * handle multiple sources of events in a unified manner.
709
+ *
710
+ * @template Events - An array of `Event` instances.
711
+ * @param {...Events} events - A rest parameter that takes multiple events to be merged.
712
+ * @returns {Event<AllEventsParameters<Events>, AllEventsResults<Events>>} - Returns a new `Event` instance
713
+ * that triggers with the parameters and results of any of the merged input events.
344
714
  *
345
- * @param events - The events to merge.
346
- * @returns The merged event.
715
+ * @example
716
+ * // Merging mouse and keyboard events into a single event
717
+ * const mouseEvent = createEvent<MouseEvent>();
718
+ * const keyboardEvent = createEvent<KeyboardEvent>();
719
+ * const inputEvent = merge(mouseEvent, keyboardEvent);
720
+ * inputEvent.on(event => console.log('Input event:', event));
347
721
  */
348
722
  export const merge = <Events extends Event<any, any>[]>(...events: Events): Event<AllEventsParameters<Events>, AllEventsResults<Events>> => {
349
723
  const mergedEvent = new Event<AllEventsParameters<Events>, AllEventsResults<Events>>();
@@ -352,13 +726,20 @@ export const merge = <Events extends Event<any, any>[]>(...events: Events): Even
352
726
  };
353
727
 
354
728
  /**
355
- * Creates an event that triggers at a specified interval.
356
- * @example
357
- * const tickEvent = Event.interval(1000);
358
- * tickEvent.on((tickNumber) => console.log(tickNumber));
729
+ * Creates a periodic event that triggers at a specified interval. The event will automatically emit
730
+ * an incrementing counter value each time it triggers, starting from zero. This function is useful
731
+ * for creating time-based triggers within an application, such as updating UI elements, polling,
732
+ * or any other timed operation.
359
733
  *
360
- * @param interval - The interval at which to trigger the event.
361
- * @returns The interval event.
734
+ * @template R - The return type of the event handler function, defaulting to `void`.
735
+ * @param {number} interval - The interval in milliseconds at which the event should trigger.
736
+ * @returns {Event<number, R>} - An `Event` instance that triggers at the specified interval,
737
+ * emitting an incrementing counter value.
738
+ *
739
+ * @example
740
+ * // Creating an interval event that logs a message every second
741
+ * const tickEvent = createInterval(1000);
742
+ * tickEvent.on(tickNumber => console.log('Tick:', tickNumber));
362
743
  */
363
744
  export const createInterval = <R = void>(interval: number): Event<number, R> => {
364
745
  let counter = 0;
@@ -368,16 +749,18 @@ export const createInterval = <R = void>(interval: number): Event<number, R> =>
368
749
  };
369
750
 
370
751
  /**
371
- * Creates a new event instance.
752
+ * Creates a new instance of the `Event` class, which allows for the registration of event handlers that get called when the event is emitted.
753
+ * This factory function simplifies the creation of events by encapsulating the instantiation logic, providing a clean and simple API for event creation.
372
754
  *
373
- * @typeParam T - An array of argument types that the event will accept.
374
- * @typeParam R - The return type of the event handler function.
375
- * @returns A new instance of the `Event` class.
755
+ * @typeParam T - The tuple of argument types that the event will accept.
756
+ * @typeParam R - The return type of the event handler function, which is emitted after processing the event data.
757
+ * @returns {Event<T, R>} - A new instance of the `Event` class, ready to have listeners added to it.
376
758
  *
377
759
  * @example
378
- * const myEvent = createEvent<[string], number>();
760
+ * // Create a new event that accepts a string and returns the string length
761
+ * const myEvent = createEvent<string, number>();
379
762
  * myEvent.on((str: string) => str.length);
380
- * await myEvent('hello'); // [5]
763
+ * myEvent('hello').then(results => console.log(results)); // Logs: [5]
381
764
  */
382
765
  export const createEvent = <T, R = void>(): Event<T, R> => new Event<T, R>();
383
766