evnty 5.3.2 → 5.4.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/build/async.cjs +16 -16
- package/build/async.cjs.map +1 -1
- package/build/async.d.ts +8 -0
- package/build/async.js +10 -16
- package/build/async.js.map +1 -1
- package/build/broadcast.cjs +4 -13
- package/build/broadcast.cjs.map +1 -1
- package/build/broadcast.js +5 -14
- package/build/broadcast.js.map +1 -1
- package/build/event.cjs +5 -16
- package/build/event.cjs.map +1 -1
- package/build/event.js +6 -17
- package/build/event.js.map +1 -1
- package/build/iterator.cjs +11 -40
- package/build/iterator.cjs.map +1 -1
- package/build/iterator.js +11 -40
- package/build/iterator.js.map +1 -1
- package/build/sequence.cjs +8 -9
- package/build/sequence.cjs.map +1 -1
- package/build/sequence.js +8 -9
- package/build/sequence.js.map +1 -1
- package/build/utils.cjs +32 -46
- package/build/utils.cjs.map +1 -1
- package/build/utils.js +32 -46
- package/build/utils.js.map +1 -1
- package/package.json +11 -10
- package/src/async.ts +17 -9
- package/src/broadcast.ts +5 -5
- package/src/event.ts +8 -10
- package/src/iterator.ts +11 -10
- package/src/sequence.ts +8 -9
- package/src/utils.ts +26 -25
package/build/async.cjs
CHANGED
|
@@ -14,6 +14,12 @@ _export(exports, {
|
|
|
14
14
|
},
|
|
15
15
|
get Disposer () {
|
|
16
16
|
return Disposer;
|
|
17
|
+
},
|
|
18
|
+
get ITERATOR_DONE () {
|
|
19
|
+
return ITERATOR_DONE;
|
|
20
|
+
},
|
|
21
|
+
get ITERATOR_DONE_PROMISE () {
|
|
22
|
+
return ITERATOR_DONE_PROMISE;
|
|
17
23
|
}
|
|
18
24
|
});
|
|
19
25
|
class Disposer {
|
|
@@ -43,6 +49,11 @@ class Disposer {
|
|
|
43
49
|
this.#target?.[Symbol.dispose]();
|
|
44
50
|
}
|
|
45
51
|
}
|
|
52
|
+
const ITERATOR_DONE = Object.freeze({
|
|
53
|
+
value: undefined,
|
|
54
|
+
done: true
|
|
55
|
+
});
|
|
56
|
+
const ITERATOR_DONE_PROMISE = Promise.resolve(ITERATOR_DONE);
|
|
46
57
|
class Async {
|
|
47
58
|
#sink;
|
|
48
59
|
#disposer;
|
|
@@ -67,26 +78,15 @@ class Async {
|
|
|
67
78
|
then(onfulfilled, onrejected) {
|
|
68
79
|
return this.receive().then(onfulfilled, onrejected);
|
|
69
80
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const value = await this.receive();
|
|
73
|
-
return {
|
|
81
|
+
next() {
|
|
82
|
+
return this.receive().then((value)=>({
|
|
74
83
|
value,
|
|
75
84
|
done: false
|
|
76
|
-
};
|
|
77
|
-
} catch {
|
|
78
|
-
return {
|
|
79
|
-
value: undefined,
|
|
80
|
-
done: true
|
|
81
|
-
};
|
|
82
|
-
}
|
|
85
|
+
}), ()=>ITERATOR_DONE);
|
|
83
86
|
}
|
|
84
|
-
|
|
87
|
+
return() {
|
|
85
88
|
this.dispose?.();
|
|
86
|
-
return
|
|
87
|
-
value: undefined,
|
|
88
|
-
done: true
|
|
89
|
-
};
|
|
89
|
+
return ITERATOR_DONE_PROMISE;
|
|
90
90
|
}
|
|
91
91
|
[Symbol.asyncIterator]() {
|
|
92
92
|
return this;
|
package/build/async.cjs.map
CHANGED
|
@@ -1 +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
|
|
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 const ITERATOR_DONE: IteratorResult<never, void> = Object.freeze({ value: undefined, done: true });\n\n/**\n * @internal\n */\nexport const ITERATOR_DONE_PROMISE: Promise<IteratorResult<never, void>> = Promise.resolve(ITERATOR_DONE);\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 next(): Promise<IteratorResult<T, void>> {\n return this.receive().then(\n (value) => ({ value, done: false }),\n () => ITERATOR_DONE,\n );\n }\n\n return(): Promise<IteratorResult<T, void>> {\n // Stryker disable next-line OptionalChaining: all subclasses define dispose()\n this.dispose?.();\n return ITERATOR_DONE_PROMISE;\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","ITERATOR_DONE","ITERATOR_DONE_PROMISE","target","abortSignal","aborted","addEventListener","disposed","Symbol","dispose","undefined","removeEventListener","handleEvent","Object","freeze","value","done","Promise","resolve","sink","emit","bind","event","catch","onrejected","receive","finally","onfinally","then","onfulfilled","next","return","asyncIterator"],"mappings":";;;;;;;;;;;QA0DsBA;eAAAA;;QArDTC;eAAAA;;QAsCAC;eAAAA;;QAKAC;eAAAA;;;AA3CN,MAAMF;IACX,CAAA,MAAO,CAAc;IACrB,CAAA,WAAY,CAAe;IAE3B,YAAYG,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;AAKO,MAAMR,gBAA6CY,OAAOC,MAAM,CAAC;IAAEC,OAAOL;IAAWM,MAAM;AAAK;AAKhG,MAAMd,wBAA8De,QAAQC,OAAO,CAACjB;AAUpF,MAAeF;IAOpB,CAAA,IAAK,CAAc;IACnB,CAAA,QAAS,CAAW;IAEpB,YAAYK,WAAyB,CAAE;QACrC,IAAI,CAAC,CAAA,QAAS,GAAG,IAAIJ,SAAS,IAAI,EAAEI;IACtC;IAEA,IAAIG,WAAoB;QACtB,OAAO,IAAI,CAAC,CAAA,QAAS,CAACA,QAAQ;IAChC;IAEA,IAAIY,OAAmB;QACrB,OAAQ,IAAI,CAAC,CAAA,IAAK,KAAK,IAAI,CAACC,IAAI,CAACC,IAAI,CAAC,IAAI;IAC5C;IAEAT,YAAYU,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;IAEAM,OAAyC;QACvC,OAAO,IAAI,CAACL,OAAO,GAAGG,IAAI,CACxB,CAACb,QAAW,CAAA;gBAAEA;gBAAOC,MAAM;YAAM,CAAA,GACjC,IAAMf;IAEV;IAEA8B,SAA2C;QAEzC,IAAI,CAACtB,OAAO;QACZ,OAAOP;IACT;IAEA,CAACM,OAAOwB,aAAa,CAAC,GAAiC;QACrD,OAAO,IAAI;IACb;IAEA,CAACxB,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
CHANGED
|
@@ -9,6 +9,14 @@ export declare class Disposer {
|
|
|
9
9
|
[Symbol.dispose](): boolean;
|
|
10
10
|
handleEvent(): void;
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* @internal
|
|
14
|
+
*/
|
|
15
|
+
export declare const ITERATOR_DONE: IteratorResult<never, void>;
|
|
16
|
+
/**
|
|
17
|
+
* @internal
|
|
18
|
+
*/
|
|
19
|
+
export declare const ITERATOR_DONE_PROMISE: Promise<IteratorResult<never, void>>;
|
|
12
20
|
/**
|
|
13
21
|
* @internal
|
|
14
22
|
*/
|
package/build/async.js
CHANGED
|
@@ -25,6 +25,11 @@ export class Disposer {
|
|
|
25
25
|
this.#target?.[Symbol.dispose]();
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
+
export const ITERATOR_DONE = Object.freeze({
|
|
29
|
+
value: undefined,
|
|
30
|
+
done: true
|
|
31
|
+
});
|
|
32
|
+
export const ITERATOR_DONE_PROMISE = Promise.resolve(ITERATOR_DONE);
|
|
28
33
|
export class Async {
|
|
29
34
|
#sink;
|
|
30
35
|
#disposer;
|
|
@@ -49,26 +54,15 @@ export class Async {
|
|
|
49
54
|
then(onfulfilled, onrejected) {
|
|
50
55
|
return this.receive().then(onfulfilled, onrejected);
|
|
51
56
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const value = await this.receive();
|
|
55
|
-
return {
|
|
57
|
+
next() {
|
|
58
|
+
return this.receive().then((value)=>({
|
|
56
59
|
value,
|
|
57
60
|
done: false
|
|
58
|
-
};
|
|
59
|
-
} catch {
|
|
60
|
-
return {
|
|
61
|
-
value: undefined,
|
|
62
|
-
done: true
|
|
63
|
-
};
|
|
64
|
-
}
|
|
61
|
+
}), ()=>ITERATOR_DONE);
|
|
65
62
|
}
|
|
66
|
-
|
|
63
|
+
return() {
|
|
67
64
|
this.dispose?.();
|
|
68
|
-
return
|
|
69
|
-
value: undefined,
|
|
70
|
-
done: true
|
|
71
|
-
};
|
|
65
|
+
return ITERATOR_DONE_PROMISE;
|
|
72
66
|
}
|
|
73
67
|
[Symbol.asyncIterator]() {
|
|
74
68
|
return this;
|
package/build/async.js.map
CHANGED
|
@@ -1 +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
|
|
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 const ITERATOR_DONE: IteratorResult<never, void> = Object.freeze({ value: undefined, done: true });\n\n/**\n * @internal\n */\nexport const ITERATOR_DONE_PROMISE: Promise<IteratorResult<never, void>> = Promise.resolve(ITERATOR_DONE);\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 next(): Promise<IteratorResult<T, void>> {\n return this.receive().then(\n (value) => ({ value, done: false }),\n () => ITERATOR_DONE,\n );\n }\n\n return(): Promise<IteratorResult<T, void>> {\n // Stryker disable next-line OptionalChaining: all subclasses define dispose()\n this.dispose?.();\n return ITERATOR_DONE_PROMISE;\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","ITERATOR_DONE","Object","freeze","value","done","ITERATOR_DONE_PROMISE","Promise","resolve","Async","sink","emit","bind","event","catch","onrejected","receive","finally","onfinally","then","onfulfilled","next","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;AAKA,OAAO,MAAMI,gBAA6CC,OAAOC,MAAM,CAAC;IAAEC,OAAON;IAAWO,MAAM;AAAK,GAAG;AAK1G,OAAO,MAAMC,wBAA8DC,QAAQC,OAAO,CAACP,eAAe;AAU1G,OAAO,MAAeQ;IAOpB,CAAA,IAAK,CAAc;IACnB,CAAA,QAAS,CAAW;IAEpB,YAAYjB,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,IAAIe,OAAmB;QACrB,OAAQ,IAAI,CAAC,CAAA,IAAK,KAAK,IAAI,CAACC,IAAI,CAACC,IAAI,CAAC,IAAI;IAC5C;IAEAZ,YAAYa,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;IAEAM,OAAyC;QACvC,OAAO,IAAI,CAACL,OAAO,GAAGG,IAAI,CACxB,CAACf,QAAW,CAAA;gBAAEA;gBAAOC,MAAM;YAAM,CAAA,GACjC,IAAMJ;IAEV;IAEAqB,SAA2C;QAEzC,IAAI,CAACzB,OAAO;QACZ,OAAOS;IACT;IAEA,CAACV,OAAO2B,aAAa,CAAC,GAAiC;QACrD,OAAO,IAAI;IACb;IAEA,CAAC3B,OAAOC,OAAO,CAAC,GAAS;QACvB,IAAI,IAAI,CAAC,CAAA,QAAS,CAACD,OAAOC,OAAO,CAAC,IAAI;YAEpC,IAAI,CAACA,OAAO;QACd;IACF;AACF"}
|
package/build/broadcast.cjs
CHANGED
|
@@ -57,18 +57,12 @@ class BroadcastIterator {
|
|
|
57
57
|
await this.#signal.receive();
|
|
58
58
|
}
|
|
59
59
|
} catch {
|
|
60
|
-
return
|
|
61
|
-
value: undefined,
|
|
62
|
-
done: true
|
|
63
|
-
};
|
|
60
|
+
return _asynccjs.ITERATOR_DONE;
|
|
64
61
|
}
|
|
65
62
|
}
|
|
66
|
-
|
|
63
|
+
return() {
|
|
67
64
|
this.#broadcast.leave(this.#handle);
|
|
68
|
-
return
|
|
69
|
-
value: undefined,
|
|
70
|
-
done: true
|
|
71
|
-
};
|
|
65
|
+
return _asynccjs.ITERATOR_DONE_PROMISE;
|
|
72
66
|
}
|
|
73
67
|
}
|
|
74
68
|
class Broadcast {
|
|
@@ -167,10 +161,7 @@ class Broadcast {
|
|
|
167
161
|
const cursor = this.#cursors.get(id);
|
|
168
162
|
if (cursor === undefined) throw new Error('Invalid handle');
|
|
169
163
|
if (cursor >= this.#buffer.right) {
|
|
170
|
-
return
|
|
171
|
-
value: undefined,
|
|
172
|
-
done: true
|
|
173
|
-
};
|
|
164
|
+
return _asynccjs.ITERATOR_DONE;
|
|
174
165
|
}
|
|
175
166
|
const value = this.#buffer.peekAt(cursor);
|
|
176
167
|
this.#cursors.set(id, cursor + 1);
|
package/build/broadcast.cjs.map
CHANGED
|
@@ -1 +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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"}
|
|
1
|
+
{"version":3,"sources":["../src/broadcast.ts"],"sourcesContent":["import { RingBuffer } from './ring-buffer.js';\nimport { Signal } from './signal.js';\nimport { Disposer, ITERATOR_DONE, ITERATOR_DONE_PROMISE } 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 ITERATOR_DONE;\n }\n }\n\n return(): Promise<IteratorResult<T, void>> {\n this.#broadcast.leave(this.#handle);\n return ITERATOR_DONE_PROMISE;\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 ITERATOR_DONE;\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","ITERATOR_DONE","return","ITERATOR_DONE_PROMISE","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","undefined","Error","unregister","consume","peekAt","readable","asyncIterator","clear"],"mappings":"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"}
|
package/build/broadcast.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { RingBuffer } from "./ring-buffer.js";
|
|
2
2
|
import { Signal } from "./signal.js";
|
|
3
|
-
import { Disposer } from "./async.js";
|
|
3
|
+
import { Disposer, ITERATOR_DONE, ITERATOR_DONE_PROMISE } from "./async.js";
|
|
4
4
|
import { min } from "./utils.js";
|
|
5
5
|
export class ConsumerHandle {
|
|
6
6
|
#broadcast;
|
|
@@ -36,18 +36,12 @@ export class BroadcastIterator {
|
|
|
36
36
|
await this.#signal.receive();
|
|
37
37
|
}
|
|
38
38
|
} catch {
|
|
39
|
-
return
|
|
40
|
-
value: undefined,
|
|
41
|
-
done: true
|
|
42
|
-
};
|
|
39
|
+
return ITERATOR_DONE;
|
|
43
40
|
}
|
|
44
41
|
}
|
|
45
|
-
|
|
42
|
+
return() {
|
|
46
43
|
this.#broadcast.leave(this.#handle);
|
|
47
|
-
return
|
|
48
|
-
value: undefined,
|
|
49
|
-
done: true
|
|
50
|
-
};
|
|
44
|
+
return ITERATOR_DONE_PROMISE;
|
|
51
45
|
}
|
|
52
46
|
}
|
|
53
47
|
export class Broadcast {
|
|
@@ -146,10 +140,7 @@ export class Broadcast {
|
|
|
146
140
|
const cursor = this.#cursors.get(id);
|
|
147
141
|
if (cursor === undefined) throw new Error('Invalid handle');
|
|
148
142
|
if (cursor >= this.#buffer.right) {
|
|
149
|
-
return
|
|
150
|
-
value: undefined,
|
|
151
|
-
done: true
|
|
152
|
-
};
|
|
143
|
+
return ITERATOR_DONE;
|
|
153
144
|
}
|
|
154
145
|
const value = this.#buffer.peekAt(cursor);
|
|
155
146
|
this.#cursors.set(id, cursor + 1);
|
package/build/broadcast.js.map
CHANGED
|
@@ -1 +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":["RingBuffer","Signal","Disposer","min","ConsumerHandle","broadcast","cursor","getCursor","Symbol","dispose","leave","BroadcastIterator","signal","handle","next","result","tryConsume","done","value","receive","undefined","return","Broadcast","Map","WeakMap","FinalizationRegistry","id","get","delete","values","right","shift","left","shiftN","toStringTag","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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"}
|
|
1
|
+
{"version":3,"sources":["../src/broadcast.ts"],"sourcesContent":["import { RingBuffer } from './ring-buffer.js';\nimport { Signal } from './signal.js';\nimport { Disposer, ITERATOR_DONE, ITERATOR_DONE_PROMISE } 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 ITERATOR_DONE;\n }\n }\n\n return(): Promise<IteratorResult<T, void>> {\n this.#broadcast.leave(this.#handle);\n return ITERATOR_DONE_PROMISE;\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 ITERATOR_DONE;\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":["RingBuffer","Signal","Disposer","ITERATOR_DONE","ITERATOR_DONE_PROMISE","min","ConsumerHandle","broadcast","cursor","getCursor","Symbol","dispose","leave","BroadcastIterator","signal","handle","next","result","tryConsume","done","value","receive","return","Broadcast","Map","WeakMap","FinalizationRegistry","id","get","delete","values","right","shift","left","shiftN","toStringTag","sink","emit","bind","handleEvent","event","size","disposed","push","then","onfulfilled","onrejected","catch","finally","onfinally","join","set","register","undefined","Error","unregister","consume","peekAt","readable","asyncIterator","clear"],"mappings":"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"}
|
package/build/event.cjs
CHANGED
|
@@ -37,25 +37,14 @@ class EventIterator {
|
|
|
37
37
|
constructor(signal){
|
|
38
38
|
this.#signal = signal;
|
|
39
39
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const value = await this.#signal.receive();
|
|
43
|
-
return {
|
|
40
|
+
next() {
|
|
41
|
+
return this.#signal.receive().then((value)=>({
|
|
44
42
|
value,
|
|
45
43
|
done: false
|
|
46
|
-
};
|
|
47
|
-
} catch {
|
|
48
|
-
return {
|
|
49
|
-
value: undefined,
|
|
50
|
-
done: true
|
|
51
|
-
};
|
|
52
|
-
}
|
|
44
|
+
}), ()=>_asynccjs.ITERATOR_DONE);
|
|
53
45
|
}
|
|
54
|
-
|
|
55
|
-
return
|
|
56
|
-
value: undefined,
|
|
57
|
-
done: true
|
|
58
|
-
};
|
|
46
|
+
return() {
|
|
47
|
+
return _asynccjs.ITERATOR_DONE_PROMISE;
|
|
59
48
|
}
|
|
60
49
|
}
|
|
61
50
|
class Event {
|
package/build/event.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/event.ts"],"sourcesContent":["import { Callback, Listener, FilterFunction, Predicate, Mapper, Reducer, Action, Fn, Emitter, MaybePromise, Promiseable } from './types.js';\nimport { Disposer } from './async.js';\nimport { Signal } from './signal.js';\nimport { ListenerRegistry } from './listener-registry.js';\nimport { DispatchResult } from './dispatch-result.js';\n\nexport type Unsubscribe = Action;\n\n/**\n * @internal\n */\nexport class EventIterator<T> implements AsyncIterator<T, void, void> {\n #signal: Signal<T>;\n\n constructor(signal: Signal<T>) {\n this.#signal = signal;\n }\n\n async next(): Promise<IteratorResult<T, void>> {\n try {\n const value = await this.#signal.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 return { value: undefined, done: true };\n }\n}\n\n/**\n * Multi-listener event emitter with async support.\n * All registered listeners are called for each emission, and their return\n * values are collected in a DispatchResult. Supports async iteration and\n * an `onDispose` callback for cleanup.\n *\n * Differs from:\n * - Signal: Event has persistent listeners; Signal is promise-based (receive per round)\n * - Sequence: Event broadcasts to all listeners; Sequence is a single-consumer queue\n *\n * @template T - The type of value emitted to listeners (event payload)\n * @template R - The return type of listener functions\n */\nexport class Event<T = unknown, R = unknown> implements Emitter<T, DispatchResult<void | R>>, Promiseable<T>, Promise<T>, Disposable, AsyncIterable<T> {\n #listeners = new ListenerRegistry<[T], R | void>();\n #signal = new Signal<T>();\n #disposer: Disposer;\n #onDispose?: Callback;\n #sink?: Fn<[T], DispatchResult<void | R>>;\n\n readonly [Symbol.toStringTag] = 'Event';\n\n /**\n * Creates a new event.\n *\n * @param onDispose - A function to call on the event disposal.\n *\n * @example\n * ```typescript\n * // Create a click event.\n * const clickEvent = new Event<[x: number, y: number], void>();\n * clickEvent.on(([x, y]) => console.log(`Clicked at ${x}, ${y}`));\n * clickEvent.emit([10, 20]);\n * ```\n */\n constructor(onDispose?: Callback) {\n this.#onDispose = onDispose;\n this.#disposer = new Disposer(this);\n }\n\n /**\n * Returns a bound emit function for use as a callback.\n * Useful for passing to other APIs that expect a function.\n *\n * @example\n * ```typescript\n * const event = new Event<string>();\n * someApi.onMessage(event.sink);\n * ```\n */\n get sink(): Fn<[T], DispatchResult<void | R>> {\n return (this.#sink ??= this.emit.bind(this));\n }\n\n /**\n * DOM EventListener interface compatibility.\n * Allows the event to be used directly with addEventListener.\n */\n handleEvent(event: T): void {\n this.emit(event);\n }\n\n /**\n * The number of listeners for the event.\n */\n get size(): number {\n return this.#listeners.size;\n }\n\n /**\n * Emits a value to all registered listeners.\n * Each listener is called with the value and their return values are collected.\n *\n * @param value - The value to emit to all listeners.\n * @returns A DispatchResult containing all listener return values.\n *\n * @example\n * ```typescript\n * const event = new Event<string, number>();\n * event.on(str => str.length);\n * const result = event.emit('hello');\n * await result.all(); // [5]\n * ```\n */\n emit(value: T): DispatchResult<void | R> {\n this.#signal.emit(value);\n return new DispatchResult(this.#listeners.dispatch(value));\n }\n\n /**\n * Checks if the given listener is NOT registered for this event.\n *\n * @param listener - The listener function to check against the registered listeners.\n * @returns `true` if the listener is not already registered; otherwise, `false`.\n *\n * @example\n * ```typescript\n * // Check if a listener is not already added\n * if (event.lacks(myListener)) {\n * event.on(myListener);\n * }\n * ```\n */\n lacks(listener: Listener<T, R>): boolean {\n return !this.#listeners.has(listener);\n }\n\n /**\n * Checks if the given listener is registered for this event.\n *\n * @param listener - The listener function to check.\n * @returns `true` if the listener is currently registered; otherwise, `false`.\n *\n * @example\n * ```typescript\n * // Verify if a listener is registered\n * if (event.has(myListener)) {\n * console.log('Listener is already registered');\n * }\n * ```\n */\n has(listener: Listener<T, R>): boolean {\n return this.#listeners.has(listener);\n }\n\n /**\n * Removes a specific listener from this event.\n *\n * @param listener - The listener to remove.\n * @returns The event instance for chaining.\n *\n * @example\n * ```typescript\n * // Remove a listener\n * event.off(myListener);\n * ```\n */\n off(listener: Listener<T, R>): this {\n this.#listeners.off(listener);\n return this;\n }\n\n /**\n * Registers a listener that is called on every emission.\n *\n * @param listener - The function to call when the event occurs.\n * @returns A function that removes this listener when called.\n *\n * @example\n * ```typescript\n * const unsubscribe = event.on((data) => {\n * console.log('Event data:', data);\n * });\n * ```\n */\n on(listener: Listener<T, R>): Unsubscribe {\n this.#listeners.on(listener);\n return () => void this.off(listener);\n }\n\n /**\n * Registers a listener that is called only once on the next emission, then auto-removed.\n *\n * @param listener - The listener to trigger once.\n * @returns A function that removes this listener when called (if it hasn't fired yet).\n *\n * @example\n * ```typescript\n * const cancel = event.once((data) => {\n * console.log('Received data once:', data);\n * });\n * ```\n */\n once(listener: Listener<T, R>): Unsubscribe {\n this.#listeners.once(listener);\n return () => void this.off(listener);\n }\n\n /**\n * Removes all listeners from the event.\n * Does not dispose the event - new listeners can still be added after clearing.\n *\n * @returns The event instance for chaining.\n */\n clear(): this {\n this.#listeners.clear();\n return this;\n }\n\n /**\n * Waits for the next emission and returns the emitted value.\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 * Waits for the next emission via `receive()` and wraps the outcome in a\n * `PromiseSettledResult` - always resolves, never rejects.\n *\n * @returns A promise that resolves with the settled result.\n *\n * @example\n * ```typescript\n * const result = await event.settle();\n * if (result.status === 'fulfilled') {\n * console.log('Value:', result.value);\n * } else {\n * console.error('Reason:', result.reason);\n * }\n * ```\n */\n settle(): Promise<PromiseSettledResult<T>> {\n return this.receive().then(\n (value) => ({ status: 'fulfilled', value }) as const,\n (reason: unknown) => ({ status: 'rejected', reason }) as const,\n );\n }\n\n [Symbol.asyncIterator](): AsyncIterator<T, void, void> {\n return new EventIterator(this.#signal);\n }\n\n dispose(): void {\n this[Symbol.dispose]();\n }\n\n [Symbol.dispose](): void {\n if (this.#disposer[Symbol.dispose]()) {\n this.#signal[Symbol.dispose]();\n this.#listeners.clear();\n void this.#onDispose?.();\n }\n }\n}\n\nexport type EventParameters<T> = T extends Event<infer P, any> ? P : never;\n\nexport type EventReturn<T> = T extends Event<any, infer R> ? R : never;\n\nexport type AllEventsParameters<T extends Event<any, any>[]> = { [K in keyof T]: EventParameters<T[K]> }[number];\n\nexport type AllEventsResults<T extends Event<any, any>[]> = { [K in keyof T]: EventReturn<T[K]> }[number];\n\n/**\n * Merges multiple events into a single event that triggers whenever any source triggers.\n * Disposing the merged event unsubscribes from all sources.\n *\n * @param events - The events to merge.\n * @returns A new Event that forwards emissions from all sources.\n *\n * @example\n * ```typescript\n * const mouseEvent = createEvent<MouseEvent>();\n * const keyboardEvent = createEvent<KeyboardEvent>();\n * const inputEvent = merge(mouseEvent, keyboardEvent);\n * inputEvent.on(event => console.log('Input event:', event));\n * ```\n */\nexport const merge = <Events extends Event<any, any>[]>(...events: Events): Event<AllEventsParameters<Events>, AllEventsResults<Events>> => {\n const mergedEvent = new Event<AllEventsParameters<Events>, AllEventsResults<Events>>(() => {\n for (const event of events) {\n event.off(mergedEvent.sink);\n }\n });\n\n for (const event of events) {\n event.on(mergedEvent.sink);\n }\n return mergedEvent;\n};\n\n/**\n * Creates a periodic event that emits an incrementing counter (starting from 0) at a fixed interval.\n * Disposing the event clears the interval.\n *\n * @param interval - The interval in milliseconds.\n * @returns An Event that triggers at the specified interval.\n *\n * @example\n * ```typescript\n * const tickEvent = createInterval(1000);\n * tickEvent.on(tickNumber => console.log('Tick:', tickNumber));\n * ```\n */\nexport const createInterval = <R = unknown>(interval: number): Event<number, R> => {\n let counter = 0;\n const intervalEvent = new Event<number, R>(() => clearInterval(timerId));\n const timerId: ReturnType<typeof setInterval> = setInterval(() => {\n intervalEvent.emit(counter++);\n }, interval);\n return intervalEvent;\n};\n\n/**\n * Creates a new Event instance for multi-listener event handling.\n *\n * @example\n * ```typescript\n * const messageEvent = createEvent<string>();\n * messageEvent.on(msg => console.log('Received:', msg));\n * messageEvent.emit('Hello'); // All listeners receive 'Hello'\n *\n * // Listeners can return values, collected via DispatchResult\n * const validateEvent = createEvent<string, boolean>();\n * validateEvent.on(str => str.length > 0);\n * validateEvent.on(str => str.length < 100);\n * const results = await validateEvent.emit('test').all(); // [true, true]\n * ```\n */\nexport const createEvent = <T = unknown, R = unknown>(): Event<T, R> => new Event<T, R>();\n\nexport default createEvent;\n\n/**\n * Extracts the listener function type from an Event type.\n * Useful for type-safe listener definitions.\n *\n * @template E - The Event type to extract the listener type from\n *\n * @example\n * ```typescript\n * type MyEvent = Event<string, boolean>;\n * type MyListener = EventHandler<MyEvent>; // (value: string) => boolean | Promise<boolean>\n * ```\n */\nexport type EventHandler<E> = E extends Event<infer T, infer R> ? Listener<T, R> : never;\n\n/**\n * Extracts a filter function type for an Event's parameters.\n * Used for creating type-safe event filters.\n *\n * @template E - The Event type to create a filter for\n */\nexport type EventFilter<E> = FilterFunction<EventParameters<E>>;\n\n/**\n * Extracts a predicate function type for an Event's parameters.\n * Used for type narrowing with event values.\n *\n * @template E - The Event type to create a predicate for\n * @template P - The narrowed type that the predicate validates\n */\nexport type EventPredicate<E, P extends EventParameters<E>> = Predicate<EventParameters<E>, P>;\n\n/**\n * Extracts a mapper function type for transforming Event parameters.\n * Used for creating type-safe event value transformations.\n *\n * @template E - The Event type to create a mapper for\n * @template M - The target type to map event values to\n */\nexport type EventMapper<E, M> = Mapper<EventParameters<E>, M>;\n\n/**\n * Extracts a reducer function type for Event parameters.\n * Used for creating type-safe event value reducers.\n *\n * @template E - The Event type to create a reducer for\n * @template R - The accumulator type for the reduction\n */\nexport type EventReducer<E, R> = Reducer<EventParameters<E>, R>;\n"],"names":["Event","EventIterator","createEvent","createInterval","merge","signal","next","value","receive","done","undefined","return","ListenerRegistry","Signal","Symbol","toStringTag","onDispose","Disposer","sink","emit","bind","handleEvent","event","size","DispatchResult","dispatch","lacks","listener","has","off","on","once","clear","then","onfulfilled","onrejected","catch","finally","onfinally","settle","status","reason","asyncIterator","dispose","events","mergedEvent","interval","counter","intervalEvent","clearInterval","timerId","setInterval"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../src/event.ts"],"sourcesContent":["import { Callback, Listener, FilterFunction, Predicate, Mapper, Reducer, Action, Fn, Emitter, MaybePromise, Promiseable } from './types.js';\nimport { Disposer, ITERATOR_DONE, ITERATOR_DONE_PROMISE } from './async.js';\nimport { Signal } from './signal.js';\nimport { ListenerRegistry } from './listener-registry.js';\nimport { DispatchResult } from './dispatch-result.js';\n\nexport type Unsubscribe = Action;\n\n/**\n * @internal\n */\nexport class EventIterator<T> implements AsyncIterator<T, void, void> {\n #signal: Signal<T>;\n\n constructor(signal: Signal<T>) {\n this.#signal = signal;\n }\n\n next(): Promise<IteratorResult<T, void>> {\n return this.#signal.receive().then(\n (value): IteratorResult<T, void> => ({ value, done: false }),\n (): IteratorResult<T, void> => ITERATOR_DONE,\n );\n }\n\n return(): Promise<IteratorResult<T, void>> {\n return ITERATOR_DONE_PROMISE;\n }\n}\n\n/**\n * Multi-listener event emitter with async support.\n * All registered listeners are called for each emission, and their return\n * values are collected in a DispatchResult. Supports async iteration and\n * an `onDispose` callback for cleanup.\n *\n * Differs from:\n * - Signal: Event has persistent listeners; Signal is promise-based (receive per round)\n * - Sequence: Event broadcasts to all listeners; Sequence is a single-consumer queue\n *\n * @template T - The type of value emitted to listeners (event payload)\n * @template R - The return type of listener functions\n */\nexport class Event<T = unknown, R = unknown> implements Emitter<T, DispatchResult<void | R>>, Promiseable<T>, Promise<T>, Disposable, AsyncIterable<T> {\n #listeners = new ListenerRegistry<[T], R | void>();\n #signal = new Signal<T>();\n #disposer: Disposer;\n #onDispose?: Callback;\n #sink?: Fn<[T], DispatchResult<void | R>>;\n\n readonly [Symbol.toStringTag] = 'Event';\n\n /**\n * Creates a new event.\n *\n * @param onDispose - A function to call on the event disposal.\n *\n * @example\n * ```typescript\n * // Create a click event.\n * const clickEvent = new Event<[x: number, y: number], void>();\n * clickEvent.on(([x, y]) => console.log(`Clicked at ${x}, ${y}`));\n * clickEvent.emit([10, 20]);\n * ```\n */\n constructor(onDispose?: Callback) {\n this.#onDispose = onDispose;\n this.#disposer = new Disposer(this);\n }\n\n /**\n * Returns a bound emit function for use as a callback.\n * Useful for passing to other APIs that expect a function.\n *\n * @example\n * ```typescript\n * const event = new Event<string>();\n * someApi.onMessage(event.sink);\n * ```\n */\n get sink(): Fn<[T], DispatchResult<void | R>> {\n return (this.#sink ??= this.emit.bind(this));\n }\n\n /**\n * DOM EventListener interface compatibility.\n * Allows the event to be used directly with addEventListener.\n */\n handleEvent(event: T): void {\n this.emit(event);\n }\n\n /**\n * The number of listeners for the event.\n */\n get size(): number {\n return this.#listeners.size;\n }\n\n /**\n * Emits a value to all registered listeners.\n * Each listener is called with the value and their return values are collected.\n *\n * @param value - The value to emit to all listeners.\n * @returns A DispatchResult containing all listener return values.\n *\n * @example\n * ```typescript\n * const event = new Event<string, number>();\n * event.on(str => str.length);\n * const result = event.emit('hello');\n * await result.all(); // [5]\n * ```\n */\n emit(value: T): DispatchResult<void | R> {\n this.#signal.emit(value);\n return new DispatchResult(this.#listeners.dispatch(value));\n }\n\n /**\n * Checks if the given listener is NOT registered for this event.\n *\n * @param listener - The listener function to check against the registered listeners.\n * @returns `true` if the listener is not already registered; otherwise, `false`.\n *\n * @example\n * ```typescript\n * // Check if a listener is not already added\n * if (event.lacks(myListener)) {\n * event.on(myListener);\n * }\n * ```\n */\n lacks(listener: Listener<T, R>): boolean {\n return !this.#listeners.has(listener);\n }\n\n /**\n * Checks if the given listener is registered for this event.\n *\n * @param listener - The listener function to check.\n * @returns `true` if the listener is currently registered; otherwise, `false`.\n *\n * @example\n * ```typescript\n * // Verify if a listener is registered\n * if (event.has(myListener)) {\n * console.log('Listener is already registered');\n * }\n * ```\n */\n has(listener: Listener<T, R>): boolean {\n return this.#listeners.has(listener);\n }\n\n /**\n * Removes a specific listener from this event.\n *\n * @param listener - The listener to remove.\n * @returns The event instance for chaining.\n *\n * @example\n * ```typescript\n * // Remove a listener\n * event.off(myListener);\n * ```\n */\n off(listener: Listener<T, R>): this {\n this.#listeners.off(listener);\n return this;\n }\n\n /**\n * Registers a listener that is called on every emission.\n *\n * @param listener - The function to call when the event occurs.\n * @returns A function that removes this listener when called.\n *\n * @example\n * ```typescript\n * const unsubscribe = event.on((data) => {\n * console.log('Event data:', data);\n * });\n * ```\n */\n on(listener: Listener<T, R>): Unsubscribe {\n this.#listeners.on(listener);\n return () => void this.off(listener);\n }\n\n /**\n * Registers a listener that is called only once on the next emission, then auto-removed.\n *\n * @param listener - The listener to trigger once.\n * @returns A function that removes this listener when called (if it hasn't fired yet).\n *\n * @example\n * ```typescript\n * const cancel = event.once((data) => {\n * console.log('Received data once:', data);\n * });\n * ```\n */\n once(listener: Listener<T, R>): Unsubscribe {\n this.#listeners.once(listener);\n return () => void this.off(listener);\n }\n\n /**\n * Removes all listeners from the event.\n * Does not dispose the event - new listeners can still be added after clearing.\n *\n * @returns The event instance for chaining.\n */\n clear(): this {\n this.#listeners.clear();\n return this;\n }\n\n /**\n * Waits for the next emission and returns the emitted value.\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 * Waits for the next emission via `receive()` and wraps the outcome in a\n * `PromiseSettledResult` - always resolves, never rejects.\n *\n * @returns A promise that resolves with the settled result.\n *\n * @example\n * ```typescript\n * const result = await event.settle();\n * if (result.status === 'fulfilled') {\n * console.log('Value:', result.value);\n * } else {\n * console.error('Reason:', result.reason);\n * }\n * ```\n */\n settle(): Promise<PromiseSettledResult<T>> {\n return this.receive().then(\n (value) => ({ status: 'fulfilled', value }) as const,\n (reason: unknown) => ({ status: 'rejected', reason }) as const,\n );\n }\n\n [Symbol.asyncIterator](): AsyncIterator<T, void, void> {\n return new EventIterator(this.#signal);\n }\n\n dispose(): void {\n this[Symbol.dispose]();\n }\n\n [Symbol.dispose](): void {\n if (this.#disposer[Symbol.dispose]()) {\n this.#signal[Symbol.dispose]();\n this.#listeners.clear();\n void this.#onDispose?.();\n }\n }\n}\n\nexport type EventParameters<T> = T extends Event<infer P, any> ? P : never;\n\nexport type EventReturn<T> = T extends Event<any, infer R> ? R : never;\n\nexport type AllEventsParameters<T extends Event<any, any>[]> = { [K in keyof T]: EventParameters<T[K]> }[number];\n\nexport type AllEventsResults<T extends Event<any, any>[]> = { [K in keyof T]: EventReturn<T[K]> }[number];\n\n/**\n * Merges multiple events into a single event that triggers whenever any source triggers.\n * Disposing the merged event unsubscribes from all sources.\n *\n * @param events - The events to merge.\n * @returns A new Event that forwards emissions from all sources.\n *\n * @example\n * ```typescript\n * const mouseEvent = createEvent<MouseEvent>();\n * const keyboardEvent = createEvent<KeyboardEvent>();\n * const inputEvent = merge(mouseEvent, keyboardEvent);\n * inputEvent.on(event => console.log('Input event:', event));\n * ```\n */\nexport const merge = <Events extends Event<any, any>[]>(...events: Events): Event<AllEventsParameters<Events>, AllEventsResults<Events>> => {\n const mergedEvent = new Event<AllEventsParameters<Events>, AllEventsResults<Events>>(() => {\n for (const event of events) {\n event.off(mergedEvent.sink);\n }\n });\n\n for (const event of events) {\n event.on(mergedEvent.sink);\n }\n return mergedEvent;\n};\n\n/**\n * Creates a periodic event that emits an incrementing counter (starting from 0) at a fixed interval.\n * Disposing the event clears the interval.\n *\n * @param interval - The interval in milliseconds.\n * @returns An Event that triggers at the specified interval.\n *\n * @example\n * ```typescript\n * const tickEvent = createInterval(1000);\n * tickEvent.on(tickNumber => console.log('Tick:', tickNumber));\n * ```\n */\nexport const createInterval = <R = unknown>(interval: number): Event<number, R> => {\n let counter = 0;\n const intervalEvent = new Event<number, R>(() => clearInterval(timerId));\n const timerId: ReturnType<typeof setInterval> = setInterval(() => {\n intervalEvent.emit(counter++);\n }, interval);\n return intervalEvent;\n};\n\n/**\n * Creates a new Event instance for multi-listener event handling.\n *\n * @example\n * ```typescript\n * const messageEvent = createEvent<string>();\n * messageEvent.on(msg => console.log('Received:', msg));\n * messageEvent.emit('Hello'); // All listeners receive 'Hello'\n *\n * // Listeners can return values, collected via DispatchResult\n * const validateEvent = createEvent<string, boolean>();\n * validateEvent.on(str => str.length > 0);\n * validateEvent.on(str => str.length < 100);\n * const results = await validateEvent.emit('test').all(); // [true, true]\n * ```\n */\nexport const createEvent = <T = unknown, R = unknown>(): Event<T, R> => new Event<T, R>();\n\nexport default createEvent;\n\n/**\n * Extracts the listener function type from an Event type.\n * Useful for type-safe listener definitions.\n *\n * @template E - The Event type to extract the listener type from\n *\n * @example\n * ```typescript\n * type MyEvent = Event<string, boolean>;\n * type MyListener = EventHandler<MyEvent>; // (value: string) => boolean | Promise<boolean>\n * ```\n */\nexport type EventHandler<E> = E extends Event<infer T, infer R> ? Listener<T, R> : never;\n\n/**\n * Extracts a filter function type for an Event's parameters.\n * Used for creating type-safe event filters.\n *\n * @template E - The Event type to create a filter for\n */\nexport type EventFilter<E> = FilterFunction<EventParameters<E>>;\n\n/**\n * Extracts a predicate function type for an Event's parameters.\n * Used for type narrowing with event values.\n *\n * @template E - The Event type to create a predicate for\n * @template P - The narrowed type that the predicate validates\n */\nexport type EventPredicate<E, P extends EventParameters<E>> = Predicate<EventParameters<E>, P>;\n\n/**\n * Extracts a mapper function type for transforming Event parameters.\n * Used for creating type-safe event value transformations.\n *\n * @template E - The Event type to create a mapper for\n * @template M - The target type to map event values to\n */\nexport type EventMapper<E, M> = Mapper<EventParameters<E>, M>;\n\n/**\n * Extracts a reducer function type for Event parameters.\n * Used for creating type-safe event value reducers.\n *\n * @template E - The Event type to create a reducer for\n * @template R - The accumulator type for the reduction\n */\nexport type EventReducer<E, R> = Reducer<EventParameters<E>, R>;\n"],"names":["Event","EventIterator","createEvent","createInterval","merge","signal","next","receive","then","value","done","ITERATOR_DONE","return","ITERATOR_DONE_PROMISE","ListenerRegistry","Signal","Symbol","toStringTag","onDispose","Disposer","sink","emit","bind","handleEvent","event","size","DispatchResult","dispatch","lacks","listener","has","off","on","once","clear","onfulfilled","onrejected","catch","finally","onfinally","settle","status","reason","asyncIterator","dispose","events","mergedEvent","interval","counter","intervalEvent","clearInterval","timerId","setInterval"],"mappings":"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"}
|
package/build/event.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Disposer } from "./async.js";
|
|
1
|
+
import { Disposer, ITERATOR_DONE, ITERATOR_DONE_PROMISE } from "./async.js";
|
|
2
2
|
import { Signal } from "./signal.js";
|
|
3
3
|
import { ListenerRegistry } from "./listener-registry.js";
|
|
4
4
|
import { DispatchResult } from "./dispatch-result.js";
|
|
@@ -7,25 +7,14 @@ export class EventIterator {
|
|
|
7
7
|
constructor(signal){
|
|
8
8
|
this.#signal = signal;
|
|
9
9
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const value = await this.#signal.receive();
|
|
13
|
-
return {
|
|
10
|
+
next() {
|
|
11
|
+
return this.#signal.receive().then((value)=>({
|
|
14
12
|
value,
|
|
15
13
|
done: false
|
|
16
|
-
};
|
|
17
|
-
} catch {
|
|
18
|
-
return {
|
|
19
|
-
value: undefined,
|
|
20
|
-
done: true
|
|
21
|
-
};
|
|
22
|
-
}
|
|
14
|
+
}), ()=>ITERATOR_DONE);
|
|
23
15
|
}
|
|
24
|
-
|
|
25
|
-
return
|
|
26
|
-
value: undefined,
|
|
27
|
-
done: true
|
|
28
|
-
};
|
|
16
|
+
return() {
|
|
17
|
+
return ITERATOR_DONE_PROMISE;
|
|
29
18
|
}
|
|
30
19
|
}
|
|
31
20
|
export class Event {
|