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.
- package/README.md +28 -28
- package/build/async.cjs +101 -0
- package/build/async.cjs.map +1 -0
- package/build/async.d.ts +37 -0
- package/build/async.js +83 -0
- package/build/async.js.map +1 -0
- package/build/broadcast.cjs +205 -0
- package/build/broadcast.cjs.map +1 -0
- package/build/broadcast.d.ts +164 -0
- package/build/broadcast.js +184 -0
- package/build/broadcast.js.map +1 -0
- package/build/dispatch-result.cjs +154 -0
- package/build/dispatch-result.cjs.map +1 -0
- package/build/dispatch-result.d.ts +49 -0
- package/build/dispatch-result.js +127 -0
- package/build/dispatch-result.js.map +1 -0
- package/build/event.cjs +92 -127
- package/build/event.cjs.map +1 -1
- package/build/event.d.ts +92 -167
- package/build/event.js +90 -122
- package/build/event.js.map +1 -1
- package/build/index.cjs +3 -1
- package/build/index.cjs.map +1 -1
- package/build/index.d.ts +3 -1
- package/build/index.js +3 -1
- package/build/index.js.map +1 -1
- package/build/iterator.cjs +578 -91
- package/build/iterator.cjs.map +1 -1
- package/build/iterator.d.ts +178 -7
- package/build/iterator.js +579 -92
- package/build/iterator.js.map +1 -1
- package/build/listener-registry.cjs +114 -0
- package/build/listener-registry.cjs.map +1 -0
- package/build/listener-registry.d.ts +54 -0
- package/build/listener-registry.js +104 -0
- package/build/listener-registry.js.map +1 -0
- package/build/ring-buffer.cjs +171 -0
- package/build/ring-buffer.cjs.map +1 -0
- package/build/ring-buffer.d.ts +80 -0
- package/build/ring-buffer.js +161 -0
- package/build/ring-buffer.js.map +1 -0
- package/build/sequence.cjs +34 -35
- package/build/sequence.cjs.map +1 -1
- package/build/sequence.d.ts +38 -24
- package/build/sequence.js +34 -35
- package/build/sequence.js.map +1 -1
- package/build/signal.cjs +26 -35
- package/build/signal.cjs.map +1 -1
- package/build/signal.d.ts +36 -39
- package/build/signal.js +26 -35
- package/build/signal.js.map +1 -1
- package/build/types.cjs +0 -11
- package/build/types.cjs.map +1 -1
- package/build/types.d.ts +86 -9
- package/build/types.js +1 -5
- package/build/types.js.map +1 -1
- package/build/utils.cjs +202 -22
- package/build/utils.cjs.map +1 -1
- package/build/utils.d.ts +85 -26
- package/build/utils.js +181 -22
- package/build/utils.js.map +1 -1
- package/package.json +27 -25
- package/src/__tests__/example.js +19 -24
- package/src/index.ts +3 -1
- package/build/callable.cjs +0 -72
- package/build/callable.cjs.map +0 -1
- package/build/callable.d.ts +0 -34
- package/build/callable.js +0 -51
- 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.
|
|
61
|
-
const promise2 = signal.
|
|
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 `
|
|
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
|
-
- **
|
|
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
|
-
- **
|
|
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
|
|
126
|
-
- **
|
|
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
|
-
//
|
|
180
|
-
|
|
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.
|
|
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.
|
|
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)
|
package/build/async.cjs
ADDED
|
@@ -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"}
|
package/build/async.d.ts
ADDED
|
@@ -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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"}
|