evnty 5.0.0 → 5.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.
Files changed (69) hide show
  1. package/README.md +28 -28
  2. package/build/async.cjs +101 -0
  3. package/build/async.cjs.map +1 -0
  4. package/build/async.d.ts +37 -0
  5. package/build/async.js +83 -0
  6. package/build/async.js.map +1 -0
  7. package/build/broadcast.cjs +205 -0
  8. package/build/broadcast.cjs.map +1 -0
  9. package/build/broadcast.d.ts +164 -0
  10. package/build/broadcast.js +184 -0
  11. package/build/broadcast.js.map +1 -0
  12. package/build/dispatch-result.cjs +154 -0
  13. package/build/dispatch-result.cjs.map +1 -0
  14. package/build/dispatch-result.d.ts +49 -0
  15. package/build/dispatch-result.js +127 -0
  16. package/build/dispatch-result.js.map +1 -0
  17. package/build/event.cjs +92 -127
  18. package/build/event.cjs.map +1 -1
  19. package/build/event.d.ts +92 -167
  20. package/build/event.js +90 -122
  21. package/build/event.js.map +1 -1
  22. package/build/index.cjs +3 -1
  23. package/build/index.cjs.map +1 -1
  24. package/build/index.d.ts +3 -1
  25. package/build/index.js +3 -1
  26. package/build/index.js.map +1 -1
  27. package/build/iterator.cjs +578 -91
  28. package/build/iterator.cjs.map +1 -1
  29. package/build/iterator.d.ts +178 -7
  30. package/build/iterator.js +579 -92
  31. package/build/iterator.js.map +1 -1
  32. package/build/listener-registry.cjs +114 -0
  33. package/build/listener-registry.cjs.map +1 -0
  34. package/build/listener-registry.d.ts +54 -0
  35. package/build/listener-registry.js +104 -0
  36. package/build/listener-registry.js.map +1 -0
  37. package/build/ring-buffer.cjs +171 -0
  38. package/build/ring-buffer.cjs.map +1 -0
  39. package/build/ring-buffer.d.ts +80 -0
  40. package/build/ring-buffer.js +161 -0
  41. package/build/ring-buffer.js.map +1 -0
  42. package/build/sequence.cjs +34 -35
  43. package/build/sequence.cjs.map +1 -1
  44. package/build/sequence.d.ts +38 -24
  45. package/build/sequence.js +34 -35
  46. package/build/sequence.js.map +1 -1
  47. package/build/signal.cjs +26 -35
  48. package/build/signal.cjs.map +1 -1
  49. package/build/signal.d.ts +36 -39
  50. package/build/signal.js +26 -35
  51. package/build/signal.js.map +1 -1
  52. package/build/types.cjs +0 -11
  53. package/build/types.cjs.map +1 -1
  54. package/build/types.d.ts +86 -9
  55. package/build/types.js +1 -5
  56. package/build/types.js.map +1 -1
  57. package/build/utils.cjs +202 -22
  58. package/build/utils.cjs.map +1 -1
  59. package/build/utils.d.ts +85 -26
  60. package/build/utils.js +181 -22
  61. package/build/utils.js.map +1 -1
  62. package/package.json +27 -25
  63. package/src/__tests__/example.js +19 -24
  64. package/src/index.ts +3 -1
  65. package/build/callable.cjs +0 -72
  66. package/build/callable.cjs.map +0 -1
  67. package/build/callable.d.ts +0 -34
  68. package/build/callable.js +0 -51
  69. package/build/callable.js.map +0 -1
package/README.md CHANGED
@@ -23,7 +23,7 @@ Async-first, reactive event handling library for complex event flows with three
23
23
  - [Features](#features)
24
24
  - [Platform Support](#platform-support)
25
25
  - [Installing](#installing)
26
- - [Documentation](https://3axap4ehko.github.io/evnty/)
26
+ - [Documentation](https://3axap4ehko.github.io/evnty/) <!--API_TOC-->
27
27
  - [Examples](#examples)
28
28
  - [License](#license)
29
29
 
@@ -42,7 +42,7 @@ clickEvent.on(({ x, y }) => console.log(`Click at ${x},${y}`));
42
42
  clickEvent.on(({ x, y }) => updateUI(x, y));
43
43
 
44
44
  // All listeners receive the value
45
- clickEvent({ x: 100, y: 200 });
45
+ clickEvent.emit({ x: 100, y: 200 });
46
46
  ```
47
47
 
48
48
  **Use Event when:**
@@ -57,11 +57,11 @@ Signals are for coordinating async operations. When a value is sent, ALL waiting
57
57
  const signal = new Signal<string>();
58
58
 
59
59
  // Multiple consumers can wait
60
- const promise1 = signal.next();
61
- const promise2 = signal.next();
60
+ const promise1 = signal.receive();
61
+ const promise2 = signal.receive();
62
62
 
63
63
  // Send value - all waiting consumers receive it
64
- signal('data');
64
+ signal.emit('data');
65
65
  const [result1, result2] = await Promise.all([promise1, promise2]);
66
66
  // result1 === 'data' && result2 === 'data'
67
67
  ```
@@ -78,9 +78,9 @@ Sequences are FIFO queues for single-consumer scenarios. Values are consumed in
78
78
  const taskQueue = new Sequence<Task>();
79
79
 
80
80
  // Producer adds tasks
81
- taskQueue(task1);
82
- taskQueue(task2);
83
- taskQueue(task3);
81
+ taskQueue.emit(task1);
82
+ taskQueue.emit(task2);
83
+ taskQueue.emit(task3);
84
84
 
85
85
  // Single consumer processes in order
86
86
  for await (const task of taskQueue) {
@@ -100,7 +100,7 @@ for await (const task of taskQueue) {
100
100
  | **Consumers** | Multiple persistent listeners | Multiple one-time receivers | Single consumer |
101
101
  | **Delivery** | All listeners called | All waiting get same value | Each value consumed once |
102
102
  | **Pattern** | Pub/Sub | Broadcast coordination | Queue/Stream |
103
- | **Persistence** | Listeners stay registered | Resolves once per `next()` | Values queued until consumed |
103
+ | **Persistence** | Listeners stay registered | Resolves once per `receive()` | Values queued until consumed |
104
104
 
105
105
  ## Motivation
106
106
 
@@ -113,17 +113,17 @@ Traditional event handling in JavaScript/TypeScript has limitations:
113
113
  Evnty solves these problems by providing:
114
114
  - **Type-safe events** with full TypeScript inference
115
115
  - **Three specialized primitives** for different async patterns
116
- - **Rich functional operators** (map, filter, reduce, debounce, batch, etc.)
116
+ - **Async iterator transformations** via `AsyncIteratorObject` (map, filter, reduce, expand, etc.)
117
117
  - **Composable abstractions** that work together seamlessly
118
118
 
119
119
  ## Features
120
120
 
121
121
  - **Async-First Design**: Built from the ground up for asynchronous event handling with full Promise support
122
- - **Functional Programming**: Rich set of operators including map, filter, reduce, debounce, batch, and expand for event stream transformations
122
+ - **Iterator Transformations**: `AsyncIteratorObject` provides map, filter, reduce, take, drop, flatMap, and expand operators
123
123
  - **Type-Safe**: Full TypeScript support with strong typing and inference throughout the event pipeline
124
- - **Async Iteration**: Events can be consumed as async iterables using for-await-of loops
125
- - **Event Composition**: Merge, combine, and transform multiple event streams into new events
126
- - **Minimal Dependencies**: Lightweight with only essential dependencies for optimal bundle size
124
+ - **Async Iteration**: Events, Signals, and Sequences can be consumed as async iterables using for-await-of loops
125
+ - **Event Composition**: Merge multiple event streams into unified events
126
+ - **Zero Dependencies**: Lightweight with no external dependencies for optimal bundle size
127
127
  - **Universal**: Works seamlessly in both browser and Node.js environments, including service workers
128
128
 
129
129
  ## Platform Support
@@ -174,12 +174,10 @@ userEvent.on(user => updateUI(user));
174
174
  userEvent.on(user => saveToCache(user));
175
175
 
176
176
  // Emit - all listeners are called
177
- userEvent({ id: 1, name: 'Alice' });
177
+ userEvent.emit({ id: 1, name: 'Alice' });
178
178
 
179
- // Functional transformations
180
- const adminEvent = userEvent
181
- .filter(user => user.id < 100)
182
- .map(user => ({ ...user, role: 'admin' }));
179
+ // One-time listener
180
+ userEvent.once(user => console.log('First user only:', user));
183
181
 
184
182
  // Async iteration
185
183
  for await (const user of userEvent) {
@@ -196,12 +194,12 @@ const dataSignal = new Signal<Buffer>();
196
194
 
197
195
  // Multiple operations wait for the same data
198
196
  async function processA() {
199
- const data = await dataSignal.next();
197
+ const data = await dataSignal.receive();
200
198
  // Process data in way A
201
199
  }
202
200
 
203
201
  async function processB() {
204
- const data = await dataSignal.next();
202
+ const data = await dataSignal.receive();
205
203
  // Process data in way B
206
204
  }
207
205
 
@@ -209,7 +207,7 @@ async function processB() {
209
207
  Promise.all([processA(), processB()]);
210
208
 
211
209
  // Both receive the same data when it arrives
212
- dataSignal(Buffer.from('shared data'));
210
+ dataSignal.emit(Buffer.from('shared data'));
213
211
  ```
214
212
 
215
213
  ### Sequence - Task Queue
@@ -228,13 +226,13 @@ const taskQueue = new Sequence<() => Promise<void>>();
228
226
  })();
229
227
 
230
228
  // Multiple producers add tasks
231
- taskQueue(async () => fetchData());
232
- taskQueue(async () => processData());
233
- taskQueue(async () => saveResults());
229
+ taskQueue.emit(async () => fetchData());
230
+ taskQueue.emit(async () => processData());
231
+ taskQueue.emit(async () => saveResults());
234
232
 
235
233
  // Backpressure control
236
234
  await taskQueue.reserve(10); // Wait until queue has ≤10 items
237
- taskQueue(async () => nonUrgentTask());
235
+ taskQueue.emit(async () => nonUrgentTask());
238
236
  ```
239
237
 
240
238
  ### Combining Primitives
@@ -245,14 +243,14 @@ const responseSignal = new Signal<Response>();
245
243
 
246
244
  requestEvent.on(async (req) => {
247
245
  const response = await handleRequest(req);
248
- responseSignal(response);
246
+ responseSignal.emit(response);
249
247
  });
250
248
 
251
249
  // Event + Sequence for buffered processing
252
250
  const dataEvent = createEvent<Data>();
253
251
  const processQueue = new Sequence<Data>();
254
252
 
255
- dataEvent.on(data => processQueue(data));
253
+ dataEvent.on(data => processQueue.emit(data));
256
254
 
257
255
  // Process with controlled concurrency
258
256
  for await (const data of processQueue) {
@@ -260,6 +258,8 @@ for await (const data of processQueue) {
260
258
  }
261
259
  ```
262
260
 
261
+ <!--API_REFERENCE-->
262
+
263
263
  ## License
264
264
 
265
265
  License [The MIT License](./LICENSE)
@@ -0,0 +1,101 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ function _export(target, all) {
6
+ for(var name in all)Object.defineProperty(target, name, {
7
+ enumerable: true,
8
+ get: Object.getOwnPropertyDescriptor(all, name).get
9
+ });
10
+ }
11
+ _export(exports, {
12
+ get Async () {
13
+ return Async;
14
+ },
15
+ get Disposer () {
16
+ return Disposer;
17
+ }
18
+ });
19
+ class Disposer {
20
+ #target;
21
+ #abortSignal;
22
+ constructor(target, abortSignal){
23
+ if (abortSignal?.aborted) return;
24
+ this.#target = target;
25
+ if (abortSignal) {
26
+ this.#abortSignal = abortSignal;
27
+ abortSignal.addEventListener('abort', this);
28
+ }
29
+ }
30
+ get disposed() {
31
+ return !this.#target;
32
+ }
33
+ [Symbol.dispose]() {
34
+ if (!this.#target) return false;
35
+ this.#target = undefined;
36
+ if (this.#abortSignal) {
37
+ this.#abortSignal.removeEventListener('abort', this);
38
+ this.#abortSignal = undefined;
39
+ }
40
+ return true;
41
+ }
42
+ handleEvent() {
43
+ this.#target?.[Symbol.dispose]();
44
+ }
45
+ }
46
+ class Async {
47
+ #sink;
48
+ #disposer;
49
+ constructor(abortSignal){
50
+ this.#disposer = new Disposer(this, abortSignal);
51
+ }
52
+ get disposed() {
53
+ return this.#disposer.disposed;
54
+ }
55
+ get sink() {
56
+ return this.#sink ??= this.emit.bind(this);
57
+ }
58
+ handleEvent(event) {
59
+ this.emit(event);
60
+ }
61
+ catch(onrejected) {
62
+ return this.receive().catch(onrejected);
63
+ }
64
+ finally(onfinally) {
65
+ return this.receive().finally(onfinally);
66
+ }
67
+ then(onfulfilled, onrejected) {
68
+ return this.receive().then(onfulfilled, onrejected);
69
+ }
70
+ async next() {
71
+ try {
72
+ const value = await this.receive();
73
+ return {
74
+ value,
75
+ done: false
76
+ };
77
+ } catch {
78
+ return {
79
+ value: undefined,
80
+ done: true
81
+ };
82
+ }
83
+ }
84
+ async return() {
85
+ this.dispose?.();
86
+ return {
87
+ value: undefined,
88
+ done: true
89
+ };
90
+ }
91
+ [Symbol.asyncIterator]() {
92
+ return this;
93
+ }
94
+ [Symbol.dispose]() {
95
+ if (this.#disposer[Symbol.dispose]()) {
96
+ this.dispose?.();
97
+ }
98
+ }
99
+ }
100
+
101
+ //# sourceMappingURL=async.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/async.ts"],"sourcesContent":["import { Action, Fn, Emitter, MaybePromise, Promiseable } from './types.js';\n\n/**\n * @internal\n */\nexport class Disposer {\n #target?: Disposable;\n #abortSignal?: AbortSignal;\n\n constructor(target: Disposable, abortSignal?: AbortSignal) {\n if (abortSignal?.aborted) return;\n this.#target = target;\n if (abortSignal) {\n this.#abortSignal = abortSignal;\n abortSignal.addEventListener('abort', this);\n }\n }\n\n get disposed(): boolean {\n return !this.#target;\n }\n\n [Symbol.dispose](): boolean {\n if (!this.#target) return false;\n this.#target = undefined;\n // Stryker disable all: cleanup is memory optimization, no observable behavior after disposal\n if (this.#abortSignal) {\n this.#abortSignal.removeEventListener('abort', this);\n this.#abortSignal = undefined;\n }\n // Stryker restore all\n return true;\n }\n\n handleEvent(): void {\n // Stryker disable next-line OptionalChaining: #target is always set when abort listener fires (synchronous code)\n this.#target?.[Symbol.dispose]();\n }\n}\n\n/**\n * @internal\n */\nexport interface Async<T, R> extends Emitter<T, R>, Disposable {}\n\n/**\n * @internal\n */\nexport abstract class Async<T, R> implements Emitter<T, R>, Promiseable<T>, Promise<T>, AsyncIterator<T, void, void>, AsyncIterable<T> {\n abstract [Symbol.toStringTag]: string;\n abstract emit(value: T): R;\n abstract receive(): Promise<T>;\n\n dispose?(): void;\n\n #sink?: Fn<[T], R>;\n #disposer: Disposer;\n\n constructor(abortSignal?: AbortSignal) {\n this.#disposer = new Disposer(this, abortSignal);\n }\n\n get disposed(): boolean {\n return this.#disposer.disposed;\n }\n\n get sink(): Fn<[T], R> {\n return (this.#sink ??= this.emit.bind(this));\n }\n\n handleEvent(event: T) {\n this.emit(event);\n }\n\n catch<OK = never>(onrejected?: Fn<[unknown], MaybePromise<OK>> | null): Promise<T | OK> {\n return this.receive().catch(onrejected);\n }\n\n finally(onfinally?: Action | null): Promise<T> {\n return this.receive().finally(onfinally);\n }\n\n then<OK = T, ERR = never>(onfulfilled?: Fn<[T], MaybePromise<OK>> | null, onrejected?: Fn<[unknown], MaybePromise<ERR>> | null): Promise<OK | ERR> {\n return this.receive().then(onfulfilled, onrejected);\n }\n\n async next(): Promise<IteratorResult<T, void>> {\n try {\n const value = await this.receive();\n return { value, done: false };\n } catch {\n return { value: undefined, done: true };\n }\n }\n\n async return(): Promise<IteratorResult<T, void>> {\n // Stryker disable next-line OptionalChaining: all subclasses define dispose()\n this.dispose?.();\n return { value: undefined, done: true };\n }\n\n [Symbol.asyncIterator](): AsyncIterator<T, void, void> {\n return this;\n }\n\n [Symbol.dispose](): void {\n if (this.#disposer[Symbol.dispose]()) {\n // Stryker disable next-line OptionalChaining: all subclasses define dispose()\n this.dispose?.();\n }\n }\n}\n"],"names":["Async","Disposer","target","abortSignal","aborted","addEventListener","disposed","Symbol","dispose","undefined","removeEventListener","handleEvent","sink","emit","bind","event","catch","onrejected","receive","finally","onfinally","then","onfulfilled","next","value","done","return","asyncIterator"],"mappings":";;;;;;;;;;;QAgDsBA;eAAAA;;QA3CTC;eAAAA;;;AAAN,MAAMA;IACX,CAAA,MAAO,CAAc;IACrB,CAAA,WAAY,CAAe;IAE3B,YAAYC,MAAkB,EAAEC,WAAyB,CAAE;QACzD,IAAIA,aAAaC,SAAS;QAC1B,IAAI,CAAC,CAAA,MAAO,GAAGF;QACf,IAAIC,aAAa;YACf,IAAI,CAAC,CAAA,WAAY,GAAGA;YACpBA,YAAYE,gBAAgB,CAAC,SAAS,IAAI;QAC5C;IACF;IAEA,IAAIC,WAAoB;QACtB,OAAO,CAAC,IAAI,CAAC,CAAA,MAAO;IACtB;IAEA,CAACC,OAAOC,OAAO,CAAC,GAAY;QAC1B,IAAI,CAAC,IAAI,CAAC,CAAA,MAAO,EAAE,OAAO;QAC1B,IAAI,CAAC,CAAA,MAAO,GAAGC;QAEf,IAAI,IAAI,CAAC,CAAA,WAAY,EAAE;YACrB,IAAI,CAAC,CAAA,WAAY,CAACC,mBAAmB,CAAC,SAAS,IAAI;YACnD,IAAI,CAAC,CAAA,WAAY,GAAGD;QACtB;QAEA,OAAO;IACT;IAEAE,cAAoB;QAElB,IAAI,CAAC,CAAA,MAAO,EAAE,CAACJ,OAAOC,OAAO,CAAC;IAChC;AACF;AAUO,MAAeR;IAOpB,CAAA,IAAK,CAAc;IACnB,CAAA,QAAS,CAAW;IAEpB,YAAYG,WAAyB,CAAE;QACrC,IAAI,CAAC,CAAA,QAAS,GAAG,IAAIF,SAAS,IAAI,EAAEE;IACtC;IAEA,IAAIG,WAAoB;QACtB,OAAO,IAAI,CAAC,CAAA,QAAS,CAACA,QAAQ;IAChC;IAEA,IAAIM,OAAmB;QACrB,OAAQ,IAAI,CAAC,CAAA,IAAK,KAAK,IAAI,CAACC,IAAI,CAACC,IAAI,CAAC,IAAI;IAC5C;IAEAH,YAAYI,KAAQ,EAAE;QACpB,IAAI,CAACF,IAAI,CAACE;IACZ;IAEAC,MAAkBC,UAAmD,EAAmB;QACtF,OAAO,IAAI,CAACC,OAAO,GAAGF,KAAK,CAACC;IAC9B;IAEAE,QAAQC,SAAyB,EAAc;QAC7C,OAAO,IAAI,CAACF,OAAO,GAAGC,OAAO,CAACC;IAChC;IAEAC,KAA0BC,WAA8C,EAAEL,UAAoD,EAAqB;QACjJ,OAAO,IAAI,CAACC,OAAO,GAAGG,IAAI,CAACC,aAAaL;IAC1C;IAEA,MAAMM,OAAyC;QAC7C,IAAI;YACF,MAAMC,QAAQ,MAAM,IAAI,CAACN,OAAO;YAChC,OAAO;gBAAEM;gBAAOC,MAAM;YAAM;QAC9B,EAAE,OAAM;YACN,OAAO;gBAAED,OAAOf;gBAAWgB,MAAM;YAAK;QACxC;IACF;IAEA,MAAMC,SAA2C;QAE/C,IAAI,CAAClB,OAAO;QACZ,OAAO;YAAEgB,OAAOf;YAAWgB,MAAM;QAAK;IACxC;IAEA,CAAClB,OAAOoB,aAAa,CAAC,GAAiC;QACrD,OAAO,IAAI;IACb;IAEA,CAACpB,OAAOC,OAAO,CAAC,GAAS;QACvB,IAAI,IAAI,CAAC,CAAA,QAAS,CAACD,OAAOC,OAAO,CAAC,IAAI;YAEpC,IAAI,CAACA,OAAO;QACd;IACF;AACF"}
@@ -0,0 +1,37 @@
1
+ import { Action, Fn, Emitter, MaybePromise, Promiseable } from './types.js';
2
+ /**
3
+ * @internal
4
+ */
5
+ export declare class Disposer {
6
+ #private;
7
+ constructor(target: Disposable, abortSignal?: AbortSignal);
8
+ get disposed(): boolean;
9
+ [Symbol.dispose](): boolean;
10
+ handleEvent(): void;
11
+ }
12
+ /**
13
+ * @internal
14
+ */
15
+ export interface Async<T, R> extends Emitter<T, R>, Disposable {
16
+ }
17
+ /**
18
+ * @internal
19
+ */
20
+ export declare abstract class Async<T, R> implements Emitter<T, R>, Promiseable<T>, Promise<T>, AsyncIterator<T, void, void>, AsyncIterable<T> {
21
+ #private;
22
+ abstract [Symbol.toStringTag]: string;
23
+ abstract emit(value: T): R;
24
+ abstract receive(): Promise<T>;
25
+ dispose?(): void;
26
+ constructor(abortSignal?: AbortSignal);
27
+ get disposed(): boolean;
28
+ get sink(): Fn<[T], R>;
29
+ handleEvent(event: T): void;
30
+ catch<OK = never>(onrejected?: Fn<[unknown], MaybePromise<OK>> | null): Promise<T | OK>;
31
+ finally(onfinally?: Action | null): Promise<T>;
32
+ then<OK = T, ERR = never>(onfulfilled?: Fn<[T], MaybePromise<OK>> | null, onrejected?: Fn<[unknown], MaybePromise<ERR>> | null): Promise<OK | ERR>;
33
+ next(): Promise<IteratorResult<T, void>>;
34
+ return(): Promise<IteratorResult<T, void>>;
35
+ [Symbol.asyncIterator](): AsyncIterator<T, void, void>;
36
+ [Symbol.dispose](): void;
37
+ }
package/build/async.js ADDED
@@ -0,0 +1,83 @@
1
+ export class Disposer {
2
+ #target;
3
+ #abortSignal;
4
+ constructor(target, abortSignal){
5
+ if (abortSignal?.aborted) return;
6
+ this.#target = target;
7
+ if (abortSignal) {
8
+ this.#abortSignal = abortSignal;
9
+ abortSignal.addEventListener('abort', this);
10
+ }
11
+ }
12
+ get disposed() {
13
+ return !this.#target;
14
+ }
15
+ [Symbol.dispose]() {
16
+ if (!this.#target) return false;
17
+ this.#target = undefined;
18
+ if (this.#abortSignal) {
19
+ this.#abortSignal.removeEventListener('abort', this);
20
+ this.#abortSignal = undefined;
21
+ }
22
+ return true;
23
+ }
24
+ handleEvent() {
25
+ this.#target?.[Symbol.dispose]();
26
+ }
27
+ }
28
+ export class Async {
29
+ #sink;
30
+ #disposer;
31
+ constructor(abortSignal){
32
+ this.#disposer = new Disposer(this, abortSignal);
33
+ }
34
+ get disposed() {
35
+ return this.#disposer.disposed;
36
+ }
37
+ get sink() {
38
+ return this.#sink ??= this.emit.bind(this);
39
+ }
40
+ handleEvent(event) {
41
+ this.emit(event);
42
+ }
43
+ catch(onrejected) {
44
+ return this.receive().catch(onrejected);
45
+ }
46
+ finally(onfinally) {
47
+ return this.receive().finally(onfinally);
48
+ }
49
+ then(onfulfilled, onrejected) {
50
+ return this.receive().then(onfulfilled, onrejected);
51
+ }
52
+ async next() {
53
+ try {
54
+ const value = await this.receive();
55
+ return {
56
+ value,
57
+ done: false
58
+ };
59
+ } catch {
60
+ return {
61
+ value: undefined,
62
+ done: true
63
+ };
64
+ }
65
+ }
66
+ async return() {
67
+ this.dispose?.();
68
+ return {
69
+ value: undefined,
70
+ done: true
71
+ };
72
+ }
73
+ [Symbol.asyncIterator]() {
74
+ return this;
75
+ }
76
+ [Symbol.dispose]() {
77
+ if (this.#disposer[Symbol.dispose]()) {
78
+ this.dispose?.();
79
+ }
80
+ }
81
+ }
82
+
83
+ //# sourceMappingURL=async.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/async.ts"],"sourcesContent":["import { Action, Fn, Emitter, MaybePromise, Promiseable } from './types.js';\n\n/**\n * @internal\n */\nexport class Disposer {\n #target?: Disposable;\n #abortSignal?: AbortSignal;\n\n constructor(target: Disposable, abortSignal?: AbortSignal) {\n if (abortSignal?.aborted) return;\n this.#target = target;\n if (abortSignal) {\n this.#abortSignal = abortSignal;\n abortSignal.addEventListener('abort', this);\n }\n }\n\n get disposed(): boolean {\n return !this.#target;\n }\n\n [Symbol.dispose](): boolean {\n if (!this.#target) return false;\n this.#target = undefined;\n // Stryker disable all: cleanup is memory optimization, no observable behavior after disposal\n if (this.#abortSignal) {\n this.#abortSignal.removeEventListener('abort', this);\n this.#abortSignal = undefined;\n }\n // Stryker restore all\n return true;\n }\n\n handleEvent(): void {\n // Stryker disable next-line OptionalChaining: #target is always set when abort listener fires (synchronous code)\n this.#target?.[Symbol.dispose]();\n }\n}\n\n/**\n * @internal\n */\nexport interface Async<T, R> extends Emitter<T, R>, Disposable {}\n\n/**\n * @internal\n */\nexport abstract class Async<T, R> implements Emitter<T, R>, Promiseable<T>, Promise<T>, AsyncIterator<T, void, void>, AsyncIterable<T> {\n abstract [Symbol.toStringTag]: string;\n abstract emit(value: T): R;\n abstract receive(): Promise<T>;\n\n dispose?(): void;\n\n #sink?: Fn<[T], R>;\n #disposer: Disposer;\n\n constructor(abortSignal?: AbortSignal) {\n this.#disposer = new Disposer(this, abortSignal);\n }\n\n get disposed(): boolean {\n return this.#disposer.disposed;\n }\n\n get sink(): Fn<[T], R> {\n return (this.#sink ??= this.emit.bind(this));\n }\n\n handleEvent(event: T) {\n this.emit(event);\n }\n\n catch<OK = never>(onrejected?: Fn<[unknown], MaybePromise<OK>> | null): Promise<T | OK> {\n return this.receive().catch(onrejected);\n }\n\n finally(onfinally?: Action | null): Promise<T> {\n return this.receive().finally(onfinally);\n }\n\n then<OK = T, ERR = never>(onfulfilled?: Fn<[T], MaybePromise<OK>> | null, onrejected?: Fn<[unknown], MaybePromise<ERR>> | null): Promise<OK | ERR> {\n return this.receive().then(onfulfilled, onrejected);\n }\n\n async next(): Promise<IteratorResult<T, void>> {\n try {\n const value = await this.receive();\n return { value, done: false };\n } catch {\n return { value: undefined, done: true };\n }\n }\n\n async return(): Promise<IteratorResult<T, void>> {\n // Stryker disable next-line OptionalChaining: all subclasses define dispose()\n this.dispose?.();\n return { value: undefined, done: true };\n }\n\n [Symbol.asyncIterator](): AsyncIterator<T, void, void> {\n return this;\n }\n\n [Symbol.dispose](): void {\n if (this.#disposer[Symbol.dispose]()) {\n // Stryker disable next-line OptionalChaining: all subclasses define dispose()\n this.dispose?.();\n }\n }\n}\n"],"names":["Disposer","target","abortSignal","aborted","addEventListener","disposed","Symbol","dispose","undefined","removeEventListener","handleEvent","Async","sink","emit","bind","event","catch","onrejected","receive","finally","onfinally","then","onfulfilled","next","value","done","return","asyncIterator"],"mappings":"AAKA,OAAO,MAAMA;IACX,CAAA,MAAO,CAAc;IACrB,CAAA,WAAY,CAAe;IAE3B,YAAYC,MAAkB,EAAEC,WAAyB,CAAE;QACzD,IAAIA,aAAaC,SAAS;QAC1B,IAAI,CAAC,CAAA,MAAO,GAAGF;QACf,IAAIC,aAAa;YACf,IAAI,CAAC,CAAA,WAAY,GAAGA;YACpBA,YAAYE,gBAAgB,CAAC,SAAS,IAAI;QAC5C;IACF;IAEA,IAAIC,WAAoB;QACtB,OAAO,CAAC,IAAI,CAAC,CAAA,MAAO;IACtB;IAEA,CAACC,OAAOC,OAAO,CAAC,GAAY;QAC1B,IAAI,CAAC,IAAI,CAAC,CAAA,MAAO,EAAE,OAAO;QAC1B,IAAI,CAAC,CAAA,MAAO,GAAGC;QAEf,IAAI,IAAI,CAAC,CAAA,WAAY,EAAE;YACrB,IAAI,CAAC,CAAA,WAAY,CAACC,mBAAmB,CAAC,SAAS,IAAI;YACnD,IAAI,CAAC,CAAA,WAAY,GAAGD;QACtB;QAEA,OAAO;IACT;IAEAE,cAAoB;QAElB,IAAI,CAAC,CAAA,MAAO,EAAE,CAACJ,OAAOC,OAAO,CAAC;IAChC;AACF;AAUA,OAAO,MAAeI;IAOpB,CAAA,IAAK,CAAc;IACnB,CAAA,QAAS,CAAW;IAEpB,YAAYT,WAAyB,CAAE;QACrC,IAAI,CAAC,CAAA,QAAS,GAAG,IAAIF,SAAS,IAAI,EAAEE;IACtC;IAEA,IAAIG,WAAoB;QACtB,OAAO,IAAI,CAAC,CAAA,QAAS,CAACA,QAAQ;IAChC;IAEA,IAAIO,OAAmB;QACrB,OAAQ,IAAI,CAAC,CAAA,IAAK,KAAK,IAAI,CAACC,IAAI,CAACC,IAAI,CAAC,IAAI;IAC5C;IAEAJ,YAAYK,KAAQ,EAAE;QACpB,IAAI,CAACF,IAAI,CAACE;IACZ;IAEAC,MAAkBC,UAAmD,EAAmB;QACtF,OAAO,IAAI,CAACC,OAAO,GAAGF,KAAK,CAACC;IAC9B;IAEAE,QAAQC,SAAyB,EAAc;QAC7C,OAAO,IAAI,CAACF,OAAO,GAAGC,OAAO,CAACC;IAChC;IAEAC,KAA0BC,WAA8C,EAAEL,UAAoD,EAAqB;QACjJ,OAAO,IAAI,CAACC,OAAO,GAAGG,IAAI,CAACC,aAAaL;IAC1C;IAEA,MAAMM,OAAyC;QAC7C,IAAI;YACF,MAAMC,QAAQ,MAAM,IAAI,CAACN,OAAO;YAChC,OAAO;gBAAEM;gBAAOC,MAAM;YAAM;QAC9B,EAAE,OAAM;YACN,OAAO;gBAAED,OAAOhB;gBAAWiB,MAAM;YAAK;QACxC;IACF;IAEA,MAAMC,SAA2C;QAE/C,IAAI,CAACnB,OAAO;QACZ,OAAO;YAAEiB,OAAOhB;YAAWiB,MAAM;QAAK;IACxC;IAEA,CAACnB,OAAOqB,aAAa,CAAC,GAAiC;QACrD,OAAO,IAAI;IACb;IAEA,CAACrB,OAAOC,OAAO,CAAC,GAAS;QACvB,IAAI,IAAI,CAAC,CAAA,QAAS,CAACD,OAAOC,OAAO,CAAC,IAAI;YAEpC,IAAI,CAACA,OAAO;QACd;IACF;AACF"}
@@ -0,0 +1,205 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ function _export(target, all) {
6
+ for(var name in all)Object.defineProperty(target, name, {
7
+ enumerable: true,
8
+ get: Object.getOwnPropertyDescriptor(all, name).get
9
+ });
10
+ }
11
+ _export(exports, {
12
+ get Broadcast () {
13
+ return Broadcast;
14
+ },
15
+ get BroadcastIterator () {
16
+ return BroadcastIterator;
17
+ },
18
+ get ConsumerHandle () {
19
+ return ConsumerHandle;
20
+ }
21
+ });
22
+ const _ringbuffercjs = require("./ring-buffer.cjs");
23
+ const _signalcjs = require("./signal.cjs");
24
+ const _asynccjs = require("./async.cjs");
25
+ const _utilscjs = require("./utils.cjs");
26
+ class ConsumerHandle {
27
+ #broadcast;
28
+ constructor(broadcast){
29
+ this.#broadcast = broadcast;
30
+ }
31
+ get cursor() {
32
+ return this.#broadcast.getCursor(this);
33
+ }
34
+ [Symbol.dispose]() {
35
+ this.#broadcast.leave(this);
36
+ }
37
+ }
38
+ class BroadcastIterator {
39
+ #broadcast;
40
+ #signal;
41
+ #handle;
42
+ constructor(broadcast, signal, handle){
43
+ this.#broadcast = broadcast;
44
+ this.#signal = signal;
45
+ this.#handle = handle;
46
+ }
47
+ async next() {
48
+ try {
49
+ while(true){
50
+ const result = this.#broadcast.tryConsume(this.#handle);
51
+ if (!result.done) {
52
+ return {
53
+ value: result.value,
54
+ done: false
55
+ };
56
+ }
57
+ await this.#signal.receive();
58
+ }
59
+ } catch {
60
+ return {
61
+ value: undefined,
62
+ done: true
63
+ };
64
+ }
65
+ }
66
+ async return() {
67
+ this.#broadcast.leave(this.#handle);
68
+ return {
69
+ value: undefined,
70
+ done: true
71
+ };
72
+ }
73
+ }
74
+ class Broadcast {
75
+ #buffer = new _ringbuffercjs.RingBuffer();
76
+ #signal = new _signalcjs.Signal();
77
+ #disposer;
78
+ #sink;
79
+ #nextId = 0;
80
+ #cursors = new Map();
81
+ #handles = new WeakMap();
82
+ #minCursor = 0;
83
+ #registry = new FinalizationRegistry((id)=>{
84
+ const cursor = this.#cursors.get(id);
85
+ this.#cursors.delete(id);
86
+ if (cursor === this.#minCursor) {
87
+ this.#minCursor = (0, _utilscjs.min)(this.#cursors.values(), this.#buffer.right);
88
+ const shift = this.#minCursor - this.#buffer.left;
89
+ if (shift > 0) this.#buffer.shiftN(shift);
90
+ }
91
+ });
92
+ [Symbol.toStringTag] = 'Broadcast';
93
+ constructor(){
94
+ this.#disposer = new _asynccjs.Disposer(this);
95
+ }
96
+ get sink() {
97
+ return this.#sink ??= this.emit.bind(this);
98
+ }
99
+ handleEvent(event) {
100
+ this.emit(event);
101
+ }
102
+ get size() {
103
+ return this.#cursors.size;
104
+ }
105
+ emit(value) {
106
+ if (this.#disposer.disposed) {
107
+ return false;
108
+ }
109
+ this.#buffer.push(value);
110
+ this.#signal.emit(value);
111
+ return true;
112
+ }
113
+ receive() {
114
+ return this.#signal.receive();
115
+ }
116
+ then(onfulfilled, onrejected) {
117
+ return this.receive().then(onfulfilled, onrejected);
118
+ }
119
+ catch(onrejected) {
120
+ return this.receive().catch(onrejected);
121
+ }
122
+ finally(onfinally) {
123
+ return this.receive().finally(onfinally);
124
+ }
125
+ join() {
126
+ const id = this.#nextId++;
127
+ const cursor = this.#buffer.right;
128
+ const handle = new ConsumerHandle(this);
129
+ this.#handles.set(handle, id);
130
+ this.#cursors.set(id, cursor);
131
+ if (this.#cursors.size === 1 || cursor < this.#minCursor) {
132
+ this.#minCursor = cursor;
133
+ }
134
+ this.#registry.register(handle, id, handle);
135
+ return handle;
136
+ }
137
+ getCursor(handle) {
138
+ const id = this.#handles.get(handle);
139
+ if (id === undefined) throw new Error('Invalid handle');
140
+ const cursor = this.#cursors.get(id);
141
+ if (cursor === undefined) throw new Error('Invalid handle');
142
+ return cursor;
143
+ }
144
+ leave(handle) {
145
+ const id = this.#handles.get(handle);
146
+ if (id === undefined) return;
147
+ const cursor = this.#cursors.get(id);
148
+ this.#handles.delete(handle);
149
+ this.#cursors.delete(id);
150
+ this.#registry.unregister(handle);
151
+ if (cursor === this.#minCursor) {
152
+ this.#minCursor = (0, _utilscjs.min)(this.#cursors.values(), this.#buffer.right);
153
+ const shift = this.#minCursor - this.#buffer.left;
154
+ if (shift > 0) this.#buffer.shiftN(shift);
155
+ }
156
+ }
157
+ consume(handle) {
158
+ const result = this.tryConsume(handle);
159
+ if (result.done) {
160
+ throw new Error('No value available');
161
+ }
162
+ return result.value;
163
+ }
164
+ tryConsume(handle) {
165
+ const id = this.#handles.get(handle);
166
+ if (id === undefined) throw new Error('Invalid handle');
167
+ const cursor = this.#cursors.get(id);
168
+ if (cursor === undefined) throw new Error('Invalid handle');
169
+ if (cursor >= this.#buffer.right) {
170
+ return {
171
+ value: undefined,
172
+ done: true
173
+ };
174
+ }
175
+ const value = this.#buffer.peekAt(cursor);
176
+ this.#cursors.set(id, cursor + 1);
177
+ if (cursor === this.#minCursor) {
178
+ this.#minCursor = (0, _utilscjs.min)(this.#cursors.values(), this.#buffer.right);
179
+ const shift = this.#minCursor - this.#buffer.left;
180
+ if (shift > 0) this.#buffer.shiftN(shift);
181
+ }
182
+ return {
183
+ value,
184
+ done: false
185
+ };
186
+ }
187
+ readable(handle) {
188
+ return this.getCursor(handle) < this.#buffer.right;
189
+ }
190
+ [Symbol.asyncIterator]() {
191
+ return new BroadcastIterator(this, this.#signal, this.join());
192
+ }
193
+ dispose() {
194
+ this[Symbol.dispose]();
195
+ }
196
+ [Symbol.dispose]() {
197
+ if (this.#disposer[Symbol.dispose]()) {
198
+ this.#signal[Symbol.dispose]();
199
+ this.#buffer.clear();
200
+ this.#cursors.clear();
201
+ }
202
+ }
203
+ }
204
+
205
+ //# sourceMappingURL=broadcast.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/broadcast.ts"],"sourcesContent":["import { RingBuffer } from './ring-buffer.js';\nimport { Signal } from './signal.js';\nimport { Disposer } from './async.js';\nimport { min } from './utils.js';\nimport { Action, Fn, Emitter, MaybePromise, Promiseable } from './types.js';\n\n/**\n * A handle representing a consumer's position in a Broadcast.\n * Returned by `Broadcast.join()` and used to consume values.\n * Implements Disposable for automatic cleanup via `using` keyword.\n *\n * @example\n * ```typescript\n * const broadcast = new Broadcast<number>();\n * using handle = broadcast.join();\n * broadcast.emit(42);\n * const value = broadcast.consume(handle); // 42\n * ```\n */\nexport class ConsumerHandle<T> implements Disposable {\n #broadcast: Broadcast<T>;\n\n constructor(broadcast: Broadcast<T>) {\n this.#broadcast = broadcast;\n }\n\n /**\n * The current position of this consumer in the buffer.\n */\n get cursor(): number {\n return this.#broadcast.getCursor(this);\n }\n\n /**\n * Leaves the broadcast, releasing this consumer's position.\n */\n [Symbol.dispose](): void {\n this.#broadcast.leave(this);\n }\n}\n\n/**\n * @internal\n */\nexport class BroadcastIterator<T> implements AsyncIterator<T, void, void> {\n #broadcast: Broadcast<T>;\n #signal: Signal<T>;\n #handle: ConsumerHandle<T>;\n\n constructor(broadcast: Broadcast<T>, signal: Signal<T>, handle: ConsumerHandle<T>) {\n this.#broadcast = broadcast;\n this.#signal = signal;\n this.#handle = handle;\n }\n\n async next(): Promise<IteratorResult<T, void>> {\n try {\n while (true) {\n const result = this.#broadcast.tryConsume(this.#handle);\n if (!result.done) {\n return { value: result.value, done: false };\n }\n await this.#signal.receive();\n }\n } catch {\n return { value: undefined, done: true };\n }\n }\n\n async return(): Promise<IteratorResult<T, void>> {\n this.#broadcast.leave(this.#handle);\n return { value: undefined, done: true };\n }\n}\n\n/**\n * A multi-consumer FIFO queue where each consumer maintains its own read position.\n * Values are buffered and each consumer can read them independently at their own pace.\n * The buffer automatically compacts when all consumers have read past a position.\n *\n * Key characteristics:\n * - Multiple consumers - each gets their own cursor position\n * - Buffered delivery - values are stored until all consumers read them\n * - Late joiners only see values emitted after joining\n * - Automatic cleanup via FinalizationRegistry when handles are garbage collected\n *\n * Differs from:\n * - Event: Broadcast buffers values, Event does not\n * - Sequence: Broadcast supports multiple consumers, Sequence is single-consumer\n * - Signal: Broadcast buffers values, Signal only notifies current waiters\n *\n * @template T - The type of values in the broadcast\n *\n * @example\n * ```typescript\n * const broadcast = new Broadcast<number>();\n *\n * const handle1 = broadcast.join();\n * const handle2 = broadcast.join();\n *\n * broadcast.emit(1);\n * broadcast.emit(2);\n *\n * broadcast.consume(handle1); // 1\n * broadcast.consume(handle2); // 1\n * broadcast.consume(handle1); // 2\n * ```\n */\nexport class Broadcast<T> implements Emitter<T, boolean>, Promiseable<T>, Promise<T>, Disposable, AsyncIterable<T> {\n #buffer = new RingBuffer<T>();\n #signal = new Signal<T>();\n #disposer: Disposer;\n #sink?: Fn<[T], boolean>;\n #nextId = 0;\n #cursors = new Map<number, number>();\n #handles = new WeakMap<ConsumerHandle<T>, number>();\n #minCursor = 0;\n\n // Stryker disable all: FinalizationRegistry callback only testable via GC, excluded from mutation testing\n #registry = new FinalizationRegistry<number>((id) => {\n const cursor = this.#cursors.get(id)!;\n this.#cursors.delete(id);\n\n if (cursor === this.#minCursor) {\n this.#minCursor = min(this.#cursors.values(), this.#buffer.right);\n const shift = this.#minCursor - this.#buffer.left;\n if (shift > 0) this.#buffer.shiftN(shift);\n }\n });\n // Stryker restore all\n\n readonly [Symbol.toStringTag] = 'Broadcast';\n\n constructor() {\n this.#disposer = new Disposer(this);\n }\n\n /**\n * Returns a bound emit function for use as a callback.\n */\n get sink(): Fn<[T], boolean> {\n return (this.#sink ??= this.emit.bind(this));\n }\n\n /**\n * DOM EventListener interface compatibility.\n */\n handleEvent(event: T): void {\n this.emit(event);\n }\n\n /**\n * The number of active consumers.\n */\n get size(): number {\n return this.#cursors.size;\n }\n\n /**\n * Emits a value to all consumers. The value is buffered for consumption.\n *\n * @param value - The value to emit.\n * @returns `true` if the value was emitted.\n */\n emit(value: T): boolean {\n if (this.#disposer.disposed) {\n return false;\n }\n this.#buffer.push(value);\n this.#signal.emit(value);\n return true;\n }\n\n /**\n * Waits for the next emitted value without joining as a consumer.\n * Does not buffer - only receives values emitted after calling.\n *\n * @returns A promise that resolves with the next emitted value.\n */\n receive(): Promise<T> {\n return this.#signal.receive();\n }\n\n then<OK = T, ERR = never>(onfulfilled?: Fn<[T], MaybePromise<OK>> | null, onrejected?: Fn<[unknown], MaybePromise<ERR>> | null): Promise<OK | ERR> {\n return this.receive().then(onfulfilled, onrejected);\n }\n\n catch<ERR = never>(onrejected?: Fn<[unknown], MaybePromise<ERR>> | null): Promise<T | ERR> {\n return this.receive().catch(onrejected);\n }\n\n finally(onfinally?: Action | null): Promise<T> {\n return this.receive().finally(onfinally);\n }\n\n /**\n * Joins the broadcast as a consumer. Returns a handle used to consume values.\n * The consumer starts at the current buffer position and will only see\n * values emitted after joining.\n *\n * @example\n * ```typescript\n * const handle = broadcast.join();\n * // Use handle with consume(), readable(), leave()\n * ```\n */\n join(): ConsumerHandle<T> {\n // Stryker disable next-line UpdateOperator: IDs only need uniqueness, direction is irrelevant\n const id = this.#nextId++;\n const cursor = this.#buffer.right;\n const handle = new ConsumerHandle<T>(this);\n\n this.#handles.set(handle, id);\n this.#cursors.set(id, cursor);\n\n // Stryker disable all: minCursor is an optimization hint; tryConsume recalculates on use\n if (this.#cursors.size === 1 || cursor < this.#minCursor) {\n this.#minCursor = cursor;\n }\n // Stryker restore all\n\n this.#registry.register(handle, id, handle);\n return handle;\n }\n\n /**\n * Gets the current cursor position for a consumer handle.\n *\n * @param handle - The consumer handle.\n * @returns The cursor position.\n * @throws If the handle is invalid (already left or never joined).\n */\n getCursor(handle: ConsumerHandle<T>): number {\n const id = this.#handles.get(handle);\n // Stryker disable next-line ConditionalExpression: second cursor check catches invalid handles\n if (id === undefined) throw new Error('Invalid handle');\n const cursor = this.#cursors.get(id);\n if (cursor === undefined) throw new Error('Invalid handle');\n return cursor;\n }\n\n /**\n * Removes a consumer from the broadcast. The handle becomes invalid after this call.\n * Idempotent - calling multiple times has no effect.\n *\n * @param handle - The consumer handle to remove.\n */\n leave(handle: ConsumerHandle<T>): void {\n const id = this.#handles.get(handle);\n // Stryker disable next-line ConditionalExpression,EqualityOperator: subsequent ops are safe with undefined id\n if (id === undefined) return;\n\n const cursor = this.#cursors.get(id)!;\n this.#handles.delete(handle);\n this.#cursors.delete(id);\n this.#registry.unregister(handle);\n\n // Stryker disable all: compaction condition is optimization; buffer reads are correct regardless\n if (cursor === this.#minCursor) {\n this.#minCursor = min(this.#cursors.values(), this.#buffer.right);\n const shift = this.#minCursor - this.#buffer.left;\n if (shift > 0) this.#buffer.shiftN(shift);\n }\n // Stryker restore all\n }\n\n /**\n * Consumes and returns the next value for a consumer.\n * Advances the consumer's cursor position.\n *\n * @param handle - The consumer handle.\n * @throws If no value is available or the handle is invalid.\n *\n * @example\n * ```typescript\n * if (broadcast.readable(handle)) {\n * const value = broadcast.consume(handle);\n * }\n * ```\n */\n consume(handle: ConsumerHandle<T>): T {\n const result = this.tryConsume(handle);\n if (result.done) {\n throw new Error('No value available');\n }\n return result.value;\n }\n\n /**\n * Attempts to consume the next value for a consumer.\n * Returns `{ done: true }` when no value is currently available.\n *\n * @param handle - The consumer handle.\n * @returns The next value, or `{ done: true }` when nothing is available.\n * @throws If the handle is invalid.\n */\n tryConsume(handle: ConsumerHandle<T>): IteratorResult<T, void> {\n const id = this.#handles.get(handle);\n // Stryker disable next-line ConditionalExpression: second cursor check catches invalid handles\n if (id === undefined) throw new Error('Invalid handle');\n\n const cursor = this.#cursors.get(id);\n if (cursor === undefined) throw new Error('Invalid handle');\n if (cursor >= this.#buffer.right) {\n return { value: undefined, done: true };\n }\n\n const value = this.#buffer.peekAt(cursor)!;\n this.#cursors.set(id, cursor + 1);\n\n // Stryker disable all: compaction condition is optimization; buffer reads are correct regardless\n if (cursor === this.#minCursor) {\n this.#minCursor = min(this.#cursors.values(), this.#buffer.right);\n const shift = this.#minCursor - this.#buffer.left;\n if (shift > 0) this.#buffer.shiftN(shift);\n }\n // Stryker restore all\n\n return { value, done: false };\n }\n\n /**\n * Checks if there are values available for a consumer to read.\n *\n * @param handle - The consumer handle.\n * @returns `true` if there are unread values, `false` otherwise.\n */\n readable(handle: ConsumerHandle<T>): boolean {\n return this.getCursor(handle) < this.#buffer.right;\n }\n\n [Symbol.asyncIterator](): AsyncIterator<T, void, void> {\n return new BroadcastIterator(this, this.#signal, this.join());\n }\n\n dispose(): void {\n this[Symbol.dispose]();\n }\n\n [Symbol.dispose](): void {\n // Stryker disable next-line ConditionalExpression: double-dispose re-clears empty collections, no observable effect\n if (this.#disposer[Symbol.dispose]()) {\n this.#signal[Symbol.dispose]();\n this.#buffer.clear();\n this.#cursors.clear();\n }\n }\n}\n"],"names":["Broadcast","BroadcastIterator","ConsumerHandle","broadcast","cursor","getCursor","Symbol","dispose","leave","signal","handle","next","result","tryConsume","done","value","receive","undefined","return","RingBuffer","Signal","Map","WeakMap","FinalizationRegistry","id","get","delete","min","values","right","shift","left","shiftN","toStringTag","Disposer","sink","emit","bind","handleEvent","event","size","disposed","push","then","onfulfilled","onrejected","catch","finally","onfinally","join","set","register","Error","unregister","consume","peekAt","readable","asyncIterator","clear"],"mappings":"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"}