evnty 5.0.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +28 -28
  2. package/build/async.cjs +101 -0
  3. package/build/async.cjs.map +1 -0
  4. package/build/async.d.ts +37 -0
  5. package/build/async.js +83 -0
  6. package/build/async.js.map +1 -0
  7. package/build/broadcast.cjs +205 -0
  8. package/build/broadcast.cjs.map +1 -0
  9. package/build/broadcast.d.ts +164 -0
  10. package/build/broadcast.js +184 -0
  11. package/build/broadcast.js.map +1 -0
  12. package/build/dispatch-result.cjs +154 -0
  13. package/build/dispatch-result.cjs.map +1 -0
  14. package/build/dispatch-result.d.ts +49 -0
  15. package/build/dispatch-result.js +127 -0
  16. package/build/dispatch-result.js.map +1 -0
  17. package/build/event.cjs +92 -127
  18. package/build/event.cjs.map +1 -1
  19. package/build/event.d.ts +92 -167
  20. package/build/event.js +90 -122
  21. package/build/event.js.map +1 -1
  22. package/build/index.cjs +3 -1
  23. package/build/index.cjs.map +1 -1
  24. package/build/index.d.ts +3 -1
  25. package/build/index.js +3 -1
  26. package/build/index.js.map +1 -1
  27. package/build/iterator.cjs +578 -91
  28. package/build/iterator.cjs.map +1 -1
  29. package/build/iterator.d.ts +178 -7
  30. package/build/iterator.js +579 -92
  31. package/build/iterator.js.map +1 -1
  32. package/build/listener-registry.cjs +114 -0
  33. package/build/listener-registry.cjs.map +1 -0
  34. package/build/listener-registry.d.ts +54 -0
  35. package/build/listener-registry.js +104 -0
  36. package/build/listener-registry.js.map +1 -0
  37. package/build/ring-buffer.cjs +171 -0
  38. package/build/ring-buffer.cjs.map +1 -0
  39. package/build/ring-buffer.d.ts +80 -0
  40. package/build/ring-buffer.js +161 -0
  41. package/build/ring-buffer.js.map +1 -0
  42. package/build/sequence.cjs +34 -35
  43. package/build/sequence.cjs.map +1 -1
  44. package/build/sequence.d.ts +38 -24
  45. package/build/sequence.js +34 -35
  46. package/build/sequence.js.map +1 -1
  47. package/build/signal.cjs +26 -35
  48. package/build/signal.cjs.map +1 -1
  49. package/build/signal.d.ts +36 -39
  50. package/build/signal.js +26 -35
  51. package/build/signal.js.map +1 -1
  52. package/build/types.cjs +0 -11
  53. package/build/types.cjs.map +1 -1
  54. package/build/types.d.ts +86 -9
  55. package/build/types.js +1 -5
  56. package/build/types.js.map +1 -1
  57. package/build/utils.cjs +202 -22
  58. package/build/utils.cjs.map +1 -1
  59. package/build/utils.d.ts +85 -26
  60. package/build/utils.js +181 -22
  61. package/build/utils.js.map +1 -1
  62. package/package.json +27 -25
  63. package/src/__tests__/example.js +19 -24
  64. package/src/index.ts +3 -1
  65. package/build/callable.cjs +0 -72
  66. package/build/callable.cjs.map +0 -1
  67. package/build/callable.d.ts +0 -34
  68. package/build/callable.js +0 -51
  69. package/build/callable.js.map +0 -1
package/build/sequence.js CHANGED
@@ -1,58 +1,57 @@
1
- import { RingBuffer } from 'fastds';
2
- import { CallableAsyncIterator } from "./callable.js";
1
+ import { Async } from "./async.js";
3
2
  import { Signal } from "./signal.js";
4
- export class Sequence extends CallableAsyncIterator {
5
- abortSignal;
6
- queue;
7
- nextSignal;
8
- sendSignal;
3
+ import { RingBuffer } from "./ring-buffer.js";
4
+ export class Sequence extends Async {
5
+ #queue;
6
+ #nextSignal;
7
+ #sendSignal;
9
8
  [Symbol.toStringTag] = 'Sequence';
10
9
  static merge(target, ...sequences) {
11
10
  for (const source of sequences){
12
11
  queueMicrotask(async ()=>{
13
- try {
12
+ if (!target.disposed) try {
13
+ const sink = target.sink;
14
14
  for await (const value of source){
15
- target(value);
15
+ if (!sink(value)) {
16
+ return;
17
+ }
16
18
  }
17
19
  } catch {}
18
20
  });
19
21
  }
20
22
  }
21
23
  constructor(abortSignal){
22
- super((value)=>{
23
- if (this.abortSignal?.aborted) {
24
- this.nextSignal(false);
25
- return false;
26
- } else {
27
- this.queue.push(value);
28
- this.nextSignal(true);
29
- return true;
30
- }
31
- }), this.abortSignal = abortSignal;
32
- this.queue = new RingBuffer();
33
- this.nextSignal = new Signal(this.abortSignal);
34
- this.sendSignal = new Signal(this.abortSignal);
35
- this.abortSignal?.addEventListener('abort', ()=>this.nextSignal(false), {
36
- once: true
37
- });
24
+ super(abortSignal);
25
+ this.#queue = new RingBuffer();
26
+ this.#nextSignal = new Signal(abortSignal);
27
+ this.#sendSignal = new Signal(abortSignal);
38
28
  }
39
29
  get size() {
40
- return this.queue.length;
30
+ return this.#queue.length;
41
31
  }
42
32
  async reserve(capacity) {
43
- while(this.queue.length > capacity){
44
- await this.sendSignal;
33
+ while(this.#queue.length > capacity){
34
+ await this.#sendSignal;
45
35
  }
46
36
  }
47
- async next() {
48
- while(!this.queue.length){
49
- await this.nextSignal;
37
+ emit(value) {
38
+ const ok = !this.disposed;
39
+ if (ok) {
40
+ this.#queue.push(value);
50
41
  }
51
- this.sendSignal();
52
- return this.queue.shift();
42
+ this.#nextSignal.emit();
43
+ return ok;
53
44
  }
54
- [Symbol.dispose]() {
55
- this.nextSignal(false);
45
+ async receive() {
46
+ while(!this.#queue.length){
47
+ await this.#nextSignal;
48
+ }
49
+ this.#sendSignal.emit();
50
+ return this.#queue.shift();
51
+ }
52
+ dispose() {
53
+ this.#sendSignal[Symbol.dispose]();
54
+ this.#nextSignal[Symbol.dispose]();
56
55
  }
57
56
  }
58
57
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/sequence.ts"],"sourcesContent":["import { RingBuffer } from 'fastds';\nimport { CallableAsyncIterator } from './callable.js';\nimport { Signal } from './signal.js';\n\n/**\n * A sequence is a FIFO (First-In-First-Out) queue for async consumption.\n * Designed for single consumer with multiple producers pattern.\n * Values are queued and consumed in order, with backpressure support.\n *\n * Key characteristics:\n * - Single consumer - values are consumed once, in order\n * - Multiple producers can push values concurrently\n * - FIFO ordering - first value in is first value out\n * - Backpressure control via reserve() method\n * - Async iteration support for continuous consumption\n *\n * @template T The type of values in the sequence.\n *\n * ```typescript\n * // Create a sequence for processing tasks\n * const tasks = new Sequence<string>();\n *\n * // Producer: Add tasks to the queue\n * tasks('task1');\n * tasks('task2');\n * tasks('task3');\n *\n * // Consumer: Process tasks in order\n * const task1 = await tasks.next(); // 'task1'\n * const task2 = await tasks.next(); // 'task2'\n * const task3 = await tasks.next(); // 'task3'\n * ```\n */\nexport class Sequence<T> extends CallableAsyncIterator<T, boolean> {\n private queue: RingBuffer<T>;\n private nextSignal: Signal<boolean>;\n private sendSignal: Signal<void>;\n\n readonly [Symbol.toStringTag] = 'Sequence';\n\n /**\n * Merges multiple source sequences into a target sequence.\n * Values from all sources are forwarded to the target sequence.\n * Each source is consumed independently and concurrently.\n *\n * @param target The sequence that will receive values from all sources\n * @param sequences The source sequences to merge from\n *\n * ```typescript\n * // Create target and source sequences\n * const target = new Sequence<number>();\n * const source1 = new Sequence<number>();\n * const source2 = new Sequence<number>();\n *\n * // Merge sources into target\n * Sequence.merge(target, source1, source2);\n *\n * // Values from both sources appear in target\n * source1(1);\n * source2(2);\n * source1(3);\n *\n * // Consumer gets values as they arrive\n * await target.next(); // Could be 1, 2, or 3 depending on timing\n * ```\n */\n static merge<T>(target: Sequence<T>, ...sequences: Sequence<T>[]): void {\n for (const source of sequences) {\n queueMicrotask(async () => {\n try {\n for await (const value of source) {\n target(value);\n }\n } catch {\n // sequence is aborted\n }\n });\n }\n }\n\n constructor(private readonly abortSignal?: AbortSignal) {\n super((value: T) => {\n if (this.abortSignal?.aborted) {\n this.nextSignal(false);\n return false;\n } else {\n this.queue.push(value);\n this.nextSignal(true);\n return true;\n }\n });\n this.queue = new RingBuffer();\n this.nextSignal = new Signal(this.abortSignal);\n this.sendSignal = new Signal(this.abortSignal);\n this.abortSignal?.addEventListener('abort', () => this.nextSignal(false), { once: true });\n }\n\n /**\n * Returns the number of values currently queued.\n *\n * @returns The current queue size\n */\n get size(): number {\n return this.queue.length;\n }\n\n /**\n * Waits until the queue size drops to or below the specified capacity.\n * Useful for implementing backpressure - producers can wait before adding more items.\n *\n * @param capacity The maximum queue size to wait for\n * @returns A promise that resolves when the queue size is at or below capacity\n *\n * ```typescript\n * // Producer with backpressure control\n * const sequence = new Sequence<string>();\n *\n * // Wait if queue has more than 10 items\n * await sequence.reserve(10);\n * sequence('new item'); // Safe to add, queue has space\n * ```\n */\n async reserve(capacity: number): Promise<void> {\n while (this.queue.length > capacity) {\n await this.sendSignal;\n }\n }\n\n /**\n * Consumes and returns the next value from the queue.\n * If the queue is empty, waits for a value to be added.\n * Values are consumed in FIFO order.\n *\n * @returns A promise that resolves with the next value\n *\n * ```typescript\n * const sequence = new Sequence<number>();\n *\n * // Consumer waits for values\n * const valuePromise = sequence.next();\n *\n * // Producer adds value\n * sequence(42);\n *\n * // Consumer receives it\n * const value = await valuePromise; // 42\n * ```\n */\n async next(): Promise<T> {\n while (!this.queue.length) {\n await this.nextSignal;\n }\n this.sendSignal();\n return this.queue.shift()!;\n }\n\n /**\n * Disposes of the sequence, signaling any waiting consumers.\n * Called automatically when used with `using` declaration.\n */\n [Symbol.dispose](): void {\n this.nextSignal(false);\n }\n}\n"],"names":["RingBuffer","CallableAsyncIterator","Signal","Sequence","queue","nextSignal","sendSignal","Symbol","toStringTag","merge","target","sequences","source","queueMicrotask","value","abortSignal","aborted","push","addEventListener","once","size","length","reserve","capacity","next","shift","dispose"],"mappings":"AAAA,SAASA,UAAU,QAAQ,SAAS;AACpC,SAASC,qBAAqB,QAAQ,gBAAgB;AACtD,SAASC,MAAM,QAAQ,cAAc;AA+BrC,OAAO,MAAMC,iBAAoBF;;IACvBG,MAAqB;IACrBC,WAA4B;IAC5BC,WAAyB;IAExB,CAACC,OAAOC,WAAW,CAAC,GAAG,WAAW;IA4B3C,OAAOC,MAASC,MAAmB,EAAE,GAAGC,SAAwB,EAAQ;QACtE,KAAK,MAAMC,UAAUD,UAAW;YAC9BE,eAAe;gBACb,IAAI;oBACF,WAAW,MAAMC,SAASF,OAAQ;wBAChCF,OAAOI;oBACT;gBACF,EAAE,OAAM,CAER;YACF;QACF;IACF;IAEA,YAAY,AAAiBC,WAAyB,CAAE;QACtD,KAAK,CAAC,CAACD;YACL,IAAI,IAAI,CAACC,WAAW,EAAEC,SAAS;gBAC7B,IAAI,CAACX,UAAU,CAAC;gBAChB,OAAO;YACT,OAAO;gBACL,IAAI,CAACD,KAAK,CAACa,IAAI,CAACH;gBAChB,IAAI,CAACT,UAAU,CAAC;gBAChB,OAAO;YACT;QACF,SAV2BU,cAAAA;QAW3B,IAAI,CAACX,KAAK,GAAG,IAAIJ;QACjB,IAAI,CAACK,UAAU,GAAG,IAAIH,OAAO,IAAI,CAACa,WAAW;QAC7C,IAAI,CAACT,UAAU,GAAG,IAAIJ,OAAO,IAAI,CAACa,WAAW;QAC7C,IAAI,CAACA,WAAW,EAAEG,iBAAiB,SAAS,IAAM,IAAI,CAACb,UAAU,CAAC,QAAQ;YAAEc,MAAM;QAAK;IACzF;IAOA,IAAIC,OAAe;QACjB,OAAO,IAAI,CAAChB,KAAK,CAACiB,MAAM;IAC1B;IAkBA,MAAMC,QAAQC,QAAgB,EAAiB;QAC7C,MAAO,IAAI,CAACnB,KAAK,CAACiB,MAAM,GAAGE,SAAU;YACnC,MAAM,IAAI,CAACjB,UAAU;QACvB;IACF;IAsBA,MAAMkB,OAAmB;QACvB,MAAO,CAAC,IAAI,CAACpB,KAAK,CAACiB,MAAM,CAAE;YACzB,MAAM,IAAI,CAAChB,UAAU;QACvB;QACA,IAAI,CAACC,UAAU;QACf,OAAO,IAAI,CAACF,KAAK,CAACqB,KAAK;IACzB;IAMA,CAAClB,OAAOmB,OAAO,CAAC,GAAS;QACvB,IAAI,CAACrB,UAAU,CAAC;IAClB;AACF"}
1
+ {"version":3,"sources":["../src/sequence.ts"],"sourcesContent":["import { Async } from './async.js';\nimport { Signal } from './signal.js';\nimport { RingBuffer } from './ring-buffer.js';\n\n/**\n * A sequence is a FIFO (First-In-First-Out) queue for async consumption.\n * Designed for single consumer with multiple producers pattern.\n * Values are queued and consumed in order, with backpressure support.\n * Respects an optional AbortSignal: emit() returns false when aborted; waits reject.\n *\n * Key characteristics:\n * - Single consumer - values are consumed once, in order\n * - Multiple producers can push values concurrently\n * - FIFO ordering - first value in is first value out\n * - Backpressure control via reserve() method\n * - Async iteration support for continuous consumption\n *\n * @template T The type of values in the sequence.\n *\n * @example\n * ```typescript\n * // Create a sequence for processing tasks\n * const tasks = new Sequence<string>();\n *\n * // Producer: Add tasks to the queue\n * tasks.emit('task1');\n * tasks.emit('task2');\n * tasks.emit('task3');\n *\n * // Consumer: Process tasks in order\n * const task1 = await tasks.receive(); // 'task1'\n * const task2 = await tasks.receive(); // 'task2'\n * const task3 = await tasks.receive(); // 'task3'\n * ```\n */\nexport class Sequence<T> extends Async<T, boolean> {\n #queue: RingBuffer<T>;\n #nextSignal: Signal<void>;\n #sendSignal: Signal<void>;\n\n readonly [Symbol.toStringTag] = 'Sequence';\n\n /**\n * Merges multiple source sequences into a target sequence.\n * Values from all sources are forwarded to the target sequence.\n * Each source is consumed independently and concurrently.\n *\n * @param target The sequence that will receive values from all sources\n * @param sequences The source sequences to merge from\n *\n * @example\n * ```typescript\n * // Create target and source sequences\n * const target = new Sequence<number>();\n * const source1 = new Sequence<number>();\n * const source2 = new Sequence<number>();\n *\n * // Merge sources into target\n * Sequence.merge(target, source1, source2);\n *\n * // Values from both sources appear in target\n * source1.emit(1);\n * source2.emit(2);\n * source1.emit(3);\n *\n * // Consumer gets values as they arrive\n * await target.receive(); // Could be 1, 2, or 3 depending on timing\n * ```\n */\n static merge<T>(target: Sequence<T>, ...sequences: Sequence<T>[]): void {\n for (const source of sequences) {\n queueMicrotask(async () => {\n if (!target.disposed)\n try {\n const sink = target.sink;\n for await (const value of source) {\n if (!sink(value)) {\n return;\n }\n }\n } catch {\n // sequence is disposed\n }\n });\n }\n }\n\n /**\n * Creates a new Sequence instance.\n * @param abortSignal - Optional AbortSignal to cancel pending operations\n */\n constructor(abortSignal?: AbortSignal) {\n super(abortSignal);\n this.#queue = new RingBuffer();\n this.#nextSignal = new Signal(abortSignal);\n this.#sendSignal = new Signal(abortSignal);\n }\n\n /**\n * Returns the number of values currently queued.\n *\n * @returns The current queue size\n */\n get size(): number {\n return this.#queue.length;\n }\n\n /**\n * Waits until the queue size drops to or below the specified capacity.\n * Useful for implementing backpressure - producers can wait before adding more items.\n *\n * @param capacity The maximum queue size to wait for\n * @returns A promise that resolves when the queue size is at or below capacity\n *\n * @example\n * ```typescript\n * // Producer with backpressure control\n * const sequence = new Sequence<string>();\n *\n * // Wait if queue has more than 10 items\n * await sequence.reserve(10);\n * sequence.emit('new item'); // Safe to add, queue has space\n * ```\n */\n async reserve(capacity: number): Promise<void> {\n while (this.#queue.length > capacity) {\n await this.#sendSignal;\n }\n }\n\n /**\n * Pushes a value onto the queue. Wakes any pending `receive()` waiter.\n *\n * @param value - The value to enqueue.\n * @returns `true` if the sequence is still active.\n */\n emit(value: T): boolean {\n const ok = !this.disposed;\n if (ok) {\n this.#queue.push(value);\n }\n this.#nextSignal.emit();\n return ok;\n }\n\n /**\n * Consumes and returns the next value from the queue.\n * If the queue is empty, waits for a value to be added.\n * Values are consumed in FIFO order.\n * If the sequence has been aborted or disposed, this method rejects with Error('Disposed').\n *\n * @returns A promise that resolves with the next value\n *\n * @example\n * ```typescript\n * const sequence = new Sequence<number>();\n *\n * // Consumer waits for values\n * const valuePromise = sequence.receive();\n *\n * // Producer adds value\n * sequence.emit(42);\n *\n * // Consumer receives it\n * const value = await valuePromise; // 42\n * ```\n */\n async receive(): Promise<T> {\n while (!this.#queue.length) {\n await this.#nextSignal;\n }\n this.#sendSignal.emit();\n return this.#queue.shift()!;\n }\n\n /**\n * Disposes of the sequence, rejecting any pending `receive()` waiters.\n * Called by `[Symbol.dispose]()` (inherited from Async) when using the `using` declaration.\n */\n dispose(): void {\n this.#sendSignal[Symbol.dispose]();\n this.#nextSignal[Symbol.dispose]();\n }\n}\n"],"names":["Async","Signal","RingBuffer","Sequence","Symbol","toStringTag","merge","target","sequences","source","queueMicrotask","disposed","sink","value","abortSignal","size","length","reserve","capacity","emit","ok","push","receive","shift","dispose"],"mappings":"AAAA,SAASA,KAAK,QAAQ,aAAa;AACnC,SAASC,MAAM,QAAQ,cAAc;AACrC,SAASC,UAAU,QAAQ,mBAAmB;AAiC9C,OAAO,MAAMC,iBAAoBH;IAC/B,CAAA,KAAM,CAAgB;IACtB,CAAA,UAAW,CAAe;IAC1B,CAAA,UAAW,CAAe;IAEjB,CAACI,OAAOC,WAAW,CAAC,GAAG,WAAW;IA6B3C,OAAOC,MAASC,MAAmB,EAAE,GAAGC,SAAwB,EAAQ;QACtE,KAAK,MAAMC,UAAUD,UAAW;YAC9BE,eAAe;gBACb,IAAI,CAACH,OAAOI,QAAQ,EAClB,IAAI;oBACF,MAAMC,OAAOL,OAAOK,IAAI;oBACxB,WAAW,MAAMC,SAASJ,OAAQ;wBAChC,IAAI,CAACG,KAAKC,QAAQ;4BAChB;wBACF;oBACF;gBACF,EAAE,OAAM,CAER;YACJ;QACF;IACF;IAMA,YAAYC,WAAyB,CAAE;QACrC,KAAK,CAACA;QACN,IAAI,CAAC,CAAA,KAAM,GAAG,IAAIZ;QAClB,IAAI,CAAC,CAAA,UAAW,GAAG,IAAID,OAAOa;QAC9B,IAAI,CAAC,CAAA,UAAW,GAAG,IAAIb,OAAOa;IAChC;IAOA,IAAIC,OAAe;QACjB,OAAO,IAAI,CAAC,CAAA,KAAM,CAACC,MAAM;IAC3B;IAmBA,MAAMC,QAAQC,QAAgB,EAAiB;QAC7C,MAAO,IAAI,CAAC,CAAA,KAAM,CAACF,MAAM,GAAGE,SAAU;YACpC,MAAM,IAAI,CAAC,CAAA,UAAW;QACxB;IACF;IAQAC,KAAKN,KAAQ,EAAW;QACtB,MAAMO,KAAK,CAAC,IAAI,CAACT,QAAQ;QACzB,IAAIS,IAAI;YACN,IAAI,CAAC,CAAA,KAAM,CAACC,IAAI,CAACR;QACnB;QACA,IAAI,CAAC,CAAA,UAAW,CAACM,IAAI;QACrB,OAAOC;IACT;IAwBA,MAAME,UAAsB;QAC1B,MAAO,CAAC,IAAI,CAAC,CAAA,KAAM,CAACN,MAAM,CAAE;YAC1B,MAAM,IAAI,CAAC,CAAA,UAAW;QACxB;QACA,IAAI,CAAC,CAAA,UAAW,CAACG,IAAI;QACrB,OAAO,IAAI,CAAC,CAAA,KAAM,CAACI,KAAK;IAC1B;IAMAC,UAAgB;QACd,IAAI,CAAC,CAAA,UAAW,CAACpB,OAAOoB,OAAO,CAAC;QAChC,IAAI,CAAC,CAAA,UAAW,CAACpB,OAAOoB,OAAO,CAAC;IAClC;AACF"}
package/build/signal.cjs CHANGED
@@ -8,55 +8,46 @@ Object.defineProperty(exports, "Signal", {
8
8
  return Signal;
9
9
  }
10
10
  });
11
- const _callablecjs = require("./callable.cjs");
12
- class Signal extends _callablecjs.CallableAsyncIterator {
13
- abortSignal;
14
- rx;
11
+ const _asynccjs = require("./async.cjs");
12
+ class Signal extends _asynccjs.Async {
13
+ #rx;
15
14
  [Symbol.toStringTag] = 'Signal';
16
15
  static merge(target, ...signals) {
16
+ if (target.disposed) {
17
+ return;
18
+ }
17
19
  for (const source of signals){
18
- queueMicrotask(async ()=>{
20
+ void (async ()=>{
19
21
  try {
22
+ const sink = target.sink;
20
23
  for await (const value of source){
21
- if (target.aborted) break;
22
- target(value);
24
+ if (!sink(value) && target.disposed) {
25
+ return;
26
+ }
23
27
  }
24
28
  } catch {}
25
- });
29
+ })();
26
30
  }
27
31
  }
28
32
  constructor(abortSignal){
29
- super((value)=>{
30
- if (this.rx) {
31
- this.rx.resolve(value);
32
- this.rx = undefined;
33
- return true;
34
- } else {
35
- return false;
36
- }
37
- }), this.abortSignal = abortSignal;
38
- this.abortSignal?.addEventListener('abort', ()=>{
39
- this.rx?.reject(this.abortSignal.reason);
40
- this.rx = undefined;
41
- }, {
42
- once: true
43
- });
33
+ super(abortSignal);
44
34
  }
45
- get aborted() {
46
- return !!this.abortSignal?.aborted;
35
+ emit(value) {
36
+ if (!this.#rx) return false;
37
+ this.#rx.resolve(value);
38
+ this.#rx = undefined;
39
+ return true;
47
40
  }
48
- next() {
49
- if (this.abortSignal?.aborted) {
50
- return Promise.reject(this.abortSignal.reason);
51
- }
52
- if (!this.rx) {
53
- this.rx = Promise.withResolvers();
41
+ receive() {
42
+ if (this.disposed) {
43
+ return Promise.reject(new Error('Disposed'));
54
44
  }
55
- return this.rx.promise;
45
+ this.#rx ??= Promise.withResolvers();
46
+ return this.#rx.promise;
56
47
  }
57
- [Symbol.dispose]() {
58
- this.rx?.reject(new Error('Disposed'));
59
- this.rx = undefined;
48
+ dispose() {
49
+ this.#rx?.reject(new Error('Disposed'));
50
+ this.#rx = undefined;
60
51
  }
61
52
  }
62
53
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/signal.ts"],"sourcesContent":["import { CallableAsyncIterator } from './callable.js';\n\n/**\n * A signal is a broadcast async primitive for coordinating between producers and consumers.\n * When a value is sent, ALL waiting consumers receive the same value (broadcast pattern).\n * Signals can be reused - each call to next() creates a new promise for the next value.\n *\n * Key characteristics:\n * - Multiple consumers can wait simultaneously\n * - All waiting consumers receive the same value when sent\n * - Reusable - can send multiple values over time\n * - Supports async iteration for continuous value streaming\n *\n * @template T The type of value that this signal carries.\n *\n * ```typescript\n * // Create a signal for string values\n * const signal = new Signal<string>();\n *\n * // Multiple consumers wait for the same value\n * const promise1 = signal.next();\n * const promise2 = signal.next();\n *\n * // Send a value - both consumers receive it\n * signal('Hello World');\n *\n * const [value1, value2] = await Promise.all([promise1, promise2]);\n * console.log(value1 === value2); // true - both got 'Hello World'\n * ```\n */\nexport class Signal<T> extends CallableAsyncIterator<T, boolean> {\n private rx?: PromiseWithResolvers<T>;\n\n readonly [Symbol.toStringTag] = 'Signal';\n\n /**\n * Merges multiple source signals into a target signal.\n * Values from any source signal are forwarded to the target signal.\n * The merge continues until the target signal is aborted.\n *\n * @param target The signal that will receive values from all sources\n * @param signals The source signals to merge from\n *\n * ```typescript\n * // Create a target signal and source signals\n * const target = new Signal<string>();\n * const source1 = new Signal<string>();\n * const source2 = new Signal<string>();\n *\n * // Merge sources into target\n * Signal.merge(target, source1, source2);\n *\n * // Values from any source appear in target\n * source1('Hello');\n * const value = await target; // 'Hello'\n * ```\n */\n static merge<T>(target: Signal<T>, ...signals: Signal<T>[]): void {\n for (const source of signals) {\n queueMicrotask(async () => {\n try {\n for await (const value of source) {\n if (target.aborted) break;\n target(value);\n }\n } catch {\n // ignore aborted signal\n }\n });\n }\n }\n\n /**\n * Creates a new Signal instance.\n *\n * @param abortSignal An optional AbortSignal that can be used to cancel the signal operation.\n *\n * ```typescript\n * // Create a signal with abort capability\n * const controller = new AbortController();\n * const signal = new Signal<number>(controller.signal);\n *\n * // Signal can be cancelled\n * controller.abort('Operation cancelled');\n * ```\n */\n constructor(private readonly abortSignal?: AbortSignal) {\n super((value: T) => {\n if (this.rx) {\n this.rx.resolve(value);\n this.rx = undefined;\n return true;\n } else {\n return false;\n }\n });\n this.abortSignal?.addEventListener(\n 'abort',\n () => {\n this.rx?.reject(this.abortSignal!.reason);\n this.rx = undefined;\n },\n { once: true },\n );\n }\n\n get aborted(): boolean {\n return !!this.abortSignal?.aborted;\n }\n\n /**\n * Waits for the next value to be sent to this signal. If the signal has been aborted,\n * this method will reject with the abort reason.\n *\n * @returns A promise that resolves with the next value sent to the signal.\n *\n * ```typescript\n * const signal = new Signal<string>();\n *\n * // Wait for a value\n * const valuePromise = signal.next();\n *\n * // Send a value from elsewhere\n * signal('Hello');\n *\n * const value = await valuePromise; // 'Hello'\n * ```\n */\n next(): Promise<T> {\n if (this.abortSignal?.aborted) {\n return Promise.reject(this.abortSignal.reason);\n }\n if (!this.rx) {\n this.rx = Promise.withResolvers<T>();\n }\n return this.rx.promise;\n }\n\n /**\n * Disposes of the signal, cleaning up any pending promise resolvers.\n * This method is called automatically when the signal is used with a `using` declaration.\n */\n [Symbol.dispose](): void {\n this.rx?.reject(new Error('Disposed'));\n this.rx = undefined;\n }\n}\n"],"names":["Signal","CallableAsyncIterator","rx","Symbol","toStringTag","merge","target","signals","source","queueMicrotask","value","aborted","abortSignal","resolve","undefined","addEventListener","reject","reason","once","next","Promise","withResolvers","promise","dispose","Error"],"mappings":";;;;+BA8BaA;;;eAAAA;;;6BA9ByB;AA8B/B,MAAMA,eAAkBC,kCAAqB;;IAC1CC,GAA6B;IAE5B,CAACC,OAAOC,WAAW,CAAC,GAAG,SAAS;IAwBzC,OAAOC,MAASC,MAAiB,EAAE,GAAGC,OAAoB,EAAQ;QAChE,KAAK,MAAMC,UAAUD,QAAS;YAC5BE,eAAe;gBACb,IAAI;oBACF,WAAW,MAAMC,SAASF,OAAQ;wBAChC,IAAIF,OAAOK,OAAO,EAAE;wBACpBL,OAAOI;oBACT;gBACF,EAAE,OAAM,CAER;YACF;QACF;IACF;IAgBA,YAAY,AAAiBE,WAAyB,CAAE;QACtD,KAAK,CAAC,CAACF;YACL,IAAI,IAAI,CAACR,EAAE,EAAE;gBACX,IAAI,CAACA,EAAE,CAACW,OAAO,CAACH;gBAChB,IAAI,CAACR,EAAE,GAAGY;gBACV,OAAO;YACT,OAAO;gBACL,OAAO;YACT;QACF,SAT2BF,cAAAA;QAU3B,IAAI,CAACA,WAAW,EAAEG,iBAChB,SACA;YACE,IAAI,CAACb,EAAE,EAAEc,OAAO,IAAI,CAACJ,WAAW,CAAEK,MAAM;YACxC,IAAI,CAACf,EAAE,GAAGY;QACZ,GACA;YAAEI,MAAM;QAAK;IAEjB;IAEA,IAAIP,UAAmB;QACrB,OAAO,CAAC,CAAC,IAAI,CAACC,WAAW,EAAED;IAC7B;IAoBAQ,OAAmB;QACjB,IAAI,IAAI,CAACP,WAAW,EAAED,SAAS;YAC7B,OAAOS,QAAQJ,MAAM,CAAC,IAAI,CAACJ,WAAW,CAACK,MAAM;QAC/C;QACA,IAAI,CAAC,IAAI,CAACf,EAAE,EAAE;YACZ,IAAI,CAACA,EAAE,GAAGkB,QAAQC,aAAa;QACjC;QACA,OAAO,IAAI,CAACnB,EAAE,CAACoB,OAAO;IACxB;IAMA,CAACnB,OAAOoB,OAAO,CAAC,GAAS;QACvB,IAAI,CAACrB,EAAE,EAAEc,OAAO,IAAIQ,MAAM;QAC1B,IAAI,CAACtB,EAAE,GAAGY;IACZ;AACF"}
1
+ {"version":3,"sources":["../src/signal.ts"],"sourcesContent":["import { Async } from './async.js';\n\n/**\n * Promise-based async coordination primitive.\n * `emit()` resolves the pending `receive()` promise (shared across callers).\n * Reusable - after each emission a new round of `receive()` calls can be made.\n * Disposable via `[Symbol.dispose]()` or an optional AbortSignal.\n *\n * @template T The type of value that this signal carries.\n *\n * @example\n * ```typescript\n * const signal = new Signal<string>();\n *\n * const promise = signal.receive();\n * signal.emit('hello');\n * await promise; // 'hello'\n * ```\n */\nexport class Signal<T> extends Async<T, boolean> {\n #rx?: PromiseWithResolvers<T>;\n\n readonly [Symbol.toStringTag] = 'Signal';\n\n /**\n * Merges multiple source signals into a target signal.\n * Values from any source signal are emitted to the target signal.\n * The merge continues until the target signal is disposed.\n *\n * Note: When the target is disposed, iteration stops after the next value\n * from each source. For immediate cleanup, dispose source signals directly.\n *\n * @param target The signal that will receive values from all sources\n * @param signals The source signals to merge from\n *\n * @example\n * ```typescript\n * // Create a target signal and source signals\n * const target = new Signal<string>();\n * const source1 = new Signal<string>();\n * const source2 = new Signal<string>();\n *\n * // Merge sources into target\n * Signal.merge(target, source1, source2);\n *\n * // Values from any source appear in target\n * const promise = target.receive();\n * source1.emit('Hello');\n * const value = await promise; // 'Hello'\n * ```\n */\n static merge<T>(target: Signal<T>, ...signals: Signal<T>[]): void {\n if (target.disposed) {\n return;\n }\n for (const source of signals) {\n void (async () => {\n try {\n const sink = target.sink;\n for await (const value of source) {\n if (!sink(value) && target.disposed) {\n return;\n }\n }\n } catch {\n // ignore disposed signal\n }\n })();\n }\n }\n\n /**\n * Creates a new Signal instance.\n *\n * @param abortSignal An optional AbortSignal that can be used to cancel the signal operation.\n *\n * @example\n * ```typescript\n * // Create a signal with abort capability\n * const controller = new AbortController();\n * const signal = new Signal<number>(controller.signal);\n *\n * // Signal can be cancelled\n * controller.abort('Operation cancelled');\n * ```\n */\n constructor(abortSignal?: AbortSignal) {\n super(abortSignal);\n }\n\n /**\n * Sends a value to the waiting receiver, if any.\n *\n * @param value - The value to send.\n * @returns `true` if the value was emitted.\n */\n emit(value: T): boolean {\n if (!this.#rx) return false;\n this.#rx.resolve(value);\n this.#rx = undefined;\n return true;\n }\n\n /**\n * Waits for the next value to be sent to this signal. If the signal has been aborted\n * or disposed, this method rejects with Error('Disposed').\n *\n * @returns A promise that resolves with the next value sent to the signal.\n *\n * @example\n * ```typescript\n * const signal = new Signal<string>();\n *\n * // Wait for a value\n * const valuePromise = signal.receive();\n *\n * // Send a value from elsewhere\n * signal.emit('Hello');\n *\n * const value = await valuePromise; // 'Hello'\n * ```\n */\n receive(): Promise<T> {\n if (this.disposed) {\n return Promise.reject(new Error('Disposed'));\n }\n this.#rx ??= Promise.withResolvers<T>();\n return this.#rx.promise;\n }\n\n dispose(): void {\n this.#rx?.reject(new Error('Disposed'));\n this.#rx = undefined;\n }\n}\n"],"names":["Signal","Async","Symbol","toStringTag","merge","target","signals","disposed","source","sink","value","abortSignal","emit","resolve","undefined","receive","Promise","reject","Error","withResolvers","promise","dispose"],"mappings":";;;;+BAmBaA;;;eAAAA;;;0BAnBS;AAmBf,MAAMA,eAAkBC,eAAK;IAClC,CAAA,EAAG,CAA2B;IAErB,CAACC,OAAOC,WAAW,CAAC,GAAG,SAAS;IA6BzC,OAAOC,MAASC,MAAiB,EAAE,GAAGC,OAAoB,EAAQ;QAChE,IAAID,OAAOE,QAAQ,EAAE;YACnB;QACF;QACA,KAAK,MAAMC,UAAUF,QAAS;YAC5B,KAAK,AAAC,CAAA;gBACJ,IAAI;oBACF,MAAMG,OAAOJ,OAAOI,IAAI;oBACxB,WAAW,MAAMC,SAASF,OAAQ;wBAChC,IAAI,CAACC,KAAKC,UAAUL,OAAOE,QAAQ,EAAE;4BACnC;wBACF;oBACF;gBACF,EAAE,OAAM,CAER;YACF,CAAA;QACF;IACF;IAiBA,YAAYI,WAAyB,CAAE;QACrC,KAAK,CAACA;IACR;IAQAC,KAAKF,KAAQ,EAAW;QACtB,IAAI,CAAC,IAAI,CAAC,CAAA,EAAG,EAAE,OAAO;QACtB,IAAI,CAAC,CAAA,EAAG,CAACG,OAAO,CAACH;QACjB,IAAI,CAAC,CAAA,EAAG,GAAGI;QACX,OAAO;IACT;IAqBAC,UAAsB;QACpB,IAAI,IAAI,CAACR,QAAQ,EAAE;YACjB,OAAOS,QAAQC,MAAM,CAAC,IAAIC,MAAM;QAClC;QACA,IAAI,CAAC,CAAA,EAAG,KAAKF,QAAQG,aAAa;QAClC,OAAO,IAAI,CAAC,CAAA,EAAG,CAACC,OAAO;IACzB;IAEAC,UAAgB;QACd,IAAI,CAAC,CAAA,EAAG,EAAEJ,OAAO,IAAIC,MAAM;QAC3B,IAAI,CAAC,CAAA,EAAG,GAAGJ;IACb;AACF"}
package/build/signal.d.ts CHANGED
@@ -1,44 +1,36 @@
1
- import { CallableAsyncIterator } from './callable.js';
1
+ import { Async } from './async.js';
2
2
  /**
3
- * A signal is a broadcast async primitive for coordinating between producers and consumers.
4
- * When a value is sent, ALL waiting consumers receive the same value (broadcast pattern).
5
- * Signals can be reused - each call to next() creates a new promise for the next value.
6
- *
7
- * Key characteristics:
8
- * - Multiple consumers can wait simultaneously
9
- * - All waiting consumers receive the same value when sent
10
- * - Reusable - can send multiple values over time
11
- * - Supports async iteration for continuous value streaming
3
+ * Promise-based async coordination primitive.
4
+ * `emit()` resolves the pending `receive()` promise (shared across callers).
5
+ * Reusable - after each emission a new round of `receive()` calls can be made.
6
+ * Disposable via `[Symbol.dispose]()` or an optional AbortSignal.
12
7
  *
13
8
  * @template T The type of value that this signal carries.
14
9
  *
10
+ * @example
15
11
  * ```typescript
16
- * // Create a signal for string values
17
12
  * const signal = new Signal<string>();
18
13
  *
19
- * // Multiple consumers wait for the same value
20
- * const promise1 = signal.next();
21
- * const promise2 = signal.next();
22
- *
23
- * // Send a value - both consumers receive it
24
- * signal('Hello World');
25
- *
26
- * const [value1, value2] = await Promise.all([promise1, promise2]);
27
- * console.log(value1 === value2); // true - both got 'Hello World'
14
+ * const promise = signal.receive();
15
+ * signal.emit('hello');
16
+ * await promise; // 'hello'
28
17
  * ```
29
18
  */
30
- export declare class Signal<T> extends CallableAsyncIterator<T, boolean> {
31
- private readonly abortSignal?;
32
- private rx?;
19
+ export declare class Signal<T> extends Async<T, boolean> {
20
+ #private;
33
21
  readonly [Symbol.toStringTag] = "Signal";
34
22
  /**
35
23
  * Merges multiple source signals into a target signal.
36
- * Values from any source signal are forwarded to the target signal.
37
- * The merge continues until the target signal is aborted.
24
+ * Values from any source signal are emitted to the target signal.
25
+ * The merge continues until the target signal is disposed.
26
+ *
27
+ * Note: When the target is disposed, iteration stops after the next value
28
+ * from each source. For immediate cleanup, dispose source signals directly.
38
29
  *
39
30
  * @param target The signal that will receive values from all sources
40
31
  * @param signals The source signals to merge from
41
32
  *
33
+ * @example
42
34
  * ```typescript
43
35
  * // Create a target signal and source signals
44
36
  * const target = new Signal<string>();
@@ -49,8 +41,9 @@ export declare class Signal<T> extends CallableAsyncIterator<T, boolean> {
49
41
  * Signal.merge(target, source1, source2);
50
42
  *
51
43
  * // Values from any source appear in target
52
- * source1('Hello');
53
- * const value = await target; // 'Hello'
44
+ * const promise = target.receive();
45
+ * source1.emit('Hello');
46
+ * const value = await promise; // 'Hello'
54
47
  * ```
55
48
  */
56
49
  static merge<T>(target: Signal<T>, ...signals: Signal<T>[]): void;
@@ -59,6 +52,7 @@ export declare class Signal<T> extends CallableAsyncIterator<T, boolean> {
59
52
  *
60
53
  * @param abortSignal An optional AbortSignal that can be used to cancel the signal operation.
61
54
  *
55
+ * @example
62
56
  * ```typescript
63
57
  * // Create a signal with abort capability
64
58
  * const controller = new AbortController();
@@ -68,30 +62,33 @@ export declare class Signal<T> extends CallableAsyncIterator<T, boolean> {
68
62
  * controller.abort('Operation cancelled');
69
63
  * ```
70
64
  */
71
- constructor(abortSignal?: AbortSignal | undefined);
72
- get aborted(): boolean;
65
+ constructor(abortSignal?: AbortSignal);
73
66
  /**
74
- * Waits for the next value to be sent to this signal. If the signal has been aborted,
75
- * this method will reject with the abort reason.
67
+ * Sends a value to the waiting receiver, if any.
68
+ *
69
+ * @param value - The value to send.
70
+ * @returns `true` if the value was emitted.
71
+ */
72
+ emit(value: T): boolean;
73
+ /**
74
+ * Waits for the next value to be sent to this signal. If the signal has been aborted
75
+ * or disposed, this method rejects with Error('Disposed').
76
76
  *
77
77
  * @returns A promise that resolves with the next value sent to the signal.
78
78
  *
79
+ * @example
79
80
  * ```typescript
80
81
  * const signal = new Signal<string>();
81
82
  *
82
83
  * // Wait for a value
83
- * const valuePromise = signal.next();
84
+ * const valuePromise = signal.receive();
84
85
  *
85
86
  * // Send a value from elsewhere
86
- * signal('Hello');
87
+ * signal.emit('Hello');
87
88
  *
88
89
  * const value = await valuePromise; // 'Hello'
89
90
  * ```
90
91
  */
91
- next(): Promise<T>;
92
- /**
93
- * Disposes of the signal, cleaning up any pending promise resolvers.
94
- * This method is called automatically when the signal is used with a `using` declaration.
95
- */
96
- [Symbol.dispose](): void;
92
+ receive(): Promise<T>;
93
+ dispose(): void;
97
94
  }
package/build/signal.js CHANGED
@@ -1,52 +1,43 @@
1
- import { CallableAsyncIterator } from "./callable.js";
2
- export class Signal extends CallableAsyncIterator {
3
- abortSignal;
4
- rx;
1
+ import { Async } from "./async.js";
2
+ export class Signal extends Async {
3
+ #rx;
5
4
  [Symbol.toStringTag] = 'Signal';
6
5
  static merge(target, ...signals) {
6
+ if (target.disposed) {
7
+ return;
8
+ }
7
9
  for (const source of signals){
8
- queueMicrotask(async ()=>{
10
+ void (async ()=>{
9
11
  try {
12
+ const sink = target.sink;
10
13
  for await (const value of source){
11
- if (target.aborted) break;
12
- target(value);
14
+ if (!sink(value) && target.disposed) {
15
+ return;
16
+ }
13
17
  }
14
18
  } catch {}
15
- });
19
+ })();
16
20
  }
17
21
  }
18
22
  constructor(abortSignal){
19
- super((value)=>{
20
- if (this.rx) {
21
- this.rx.resolve(value);
22
- this.rx = undefined;
23
- return true;
24
- } else {
25
- return false;
26
- }
27
- }), this.abortSignal = abortSignal;
28
- this.abortSignal?.addEventListener('abort', ()=>{
29
- this.rx?.reject(this.abortSignal.reason);
30
- this.rx = undefined;
31
- }, {
32
- once: true
33
- });
23
+ super(abortSignal);
34
24
  }
35
- get aborted() {
36
- return !!this.abortSignal?.aborted;
25
+ emit(value) {
26
+ if (!this.#rx) return false;
27
+ this.#rx.resolve(value);
28
+ this.#rx = undefined;
29
+ return true;
37
30
  }
38
- next() {
39
- if (this.abortSignal?.aborted) {
40
- return Promise.reject(this.abortSignal.reason);
41
- }
42
- if (!this.rx) {
43
- this.rx = Promise.withResolvers();
31
+ receive() {
32
+ if (this.disposed) {
33
+ return Promise.reject(new Error('Disposed'));
44
34
  }
45
- return this.rx.promise;
35
+ this.#rx ??= Promise.withResolvers();
36
+ return this.#rx.promise;
46
37
  }
47
- [Symbol.dispose]() {
48
- this.rx?.reject(new Error('Disposed'));
49
- this.rx = undefined;
38
+ dispose() {
39
+ this.#rx?.reject(new Error('Disposed'));
40
+ this.#rx = undefined;
50
41
  }
51
42
  }
52
43
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/signal.ts"],"sourcesContent":["import { CallableAsyncIterator } from './callable.js';\n\n/**\n * A signal is a broadcast async primitive for coordinating between producers and consumers.\n * When a value is sent, ALL waiting consumers receive the same value (broadcast pattern).\n * Signals can be reused - each call to next() creates a new promise for the next value.\n *\n * Key characteristics:\n * - Multiple consumers can wait simultaneously\n * - All waiting consumers receive the same value when sent\n * - Reusable - can send multiple values over time\n * - Supports async iteration for continuous value streaming\n *\n * @template T The type of value that this signal carries.\n *\n * ```typescript\n * // Create a signal for string values\n * const signal = new Signal<string>();\n *\n * // Multiple consumers wait for the same value\n * const promise1 = signal.next();\n * const promise2 = signal.next();\n *\n * // Send a value - both consumers receive it\n * signal('Hello World');\n *\n * const [value1, value2] = await Promise.all([promise1, promise2]);\n * console.log(value1 === value2); // true - both got 'Hello World'\n * ```\n */\nexport class Signal<T> extends CallableAsyncIterator<T, boolean> {\n private rx?: PromiseWithResolvers<T>;\n\n readonly [Symbol.toStringTag] = 'Signal';\n\n /**\n * Merges multiple source signals into a target signal.\n * Values from any source signal are forwarded to the target signal.\n * The merge continues until the target signal is aborted.\n *\n * @param target The signal that will receive values from all sources\n * @param signals The source signals to merge from\n *\n * ```typescript\n * // Create a target signal and source signals\n * const target = new Signal<string>();\n * const source1 = new Signal<string>();\n * const source2 = new Signal<string>();\n *\n * // Merge sources into target\n * Signal.merge(target, source1, source2);\n *\n * // Values from any source appear in target\n * source1('Hello');\n * const value = await target; // 'Hello'\n * ```\n */\n static merge<T>(target: Signal<T>, ...signals: Signal<T>[]): void {\n for (const source of signals) {\n queueMicrotask(async () => {\n try {\n for await (const value of source) {\n if (target.aborted) break;\n target(value);\n }\n } catch {\n // ignore aborted signal\n }\n });\n }\n }\n\n /**\n * Creates a new Signal instance.\n *\n * @param abortSignal An optional AbortSignal that can be used to cancel the signal operation.\n *\n * ```typescript\n * // Create a signal with abort capability\n * const controller = new AbortController();\n * const signal = new Signal<number>(controller.signal);\n *\n * // Signal can be cancelled\n * controller.abort('Operation cancelled');\n * ```\n */\n constructor(private readonly abortSignal?: AbortSignal) {\n super((value: T) => {\n if (this.rx) {\n this.rx.resolve(value);\n this.rx = undefined;\n return true;\n } else {\n return false;\n }\n });\n this.abortSignal?.addEventListener(\n 'abort',\n () => {\n this.rx?.reject(this.abortSignal!.reason);\n this.rx = undefined;\n },\n { once: true },\n );\n }\n\n get aborted(): boolean {\n return !!this.abortSignal?.aborted;\n }\n\n /**\n * Waits for the next value to be sent to this signal. If the signal has been aborted,\n * this method will reject with the abort reason.\n *\n * @returns A promise that resolves with the next value sent to the signal.\n *\n * ```typescript\n * const signal = new Signal<string>();\n *\n * // Wait for a value\n * const valuePromise = signal.next();\n *\n * // Send a value from elsewhere\n * signal('Hello');\n *\n * const value = await valuePromise; // 'Hello'\n * ```\n */\n next(): Promise<T> {\n if (this.abortSignal?.aborted) {\n return Promise.reject(this.abortSignal.reason);\n }\n if (!this.rx) {\n this.rx = Promise.withResolvers<T>();\n }\n return this.rx.promise;\n }\n\n /**\n * Disposes of the signal, cleaning up any pending promise resolvers.\n * This method is called automatically when the signal is used with a `using` declaration.\n */\n [Symbol.dispose](): void {\n this.rx?.reject(new Error('Disposed'));\n this.rx = undefined;\n }\n}\n"],"names":["CallableAsyncIterator","Signal","rx","Symbol","toStringTag","merge","target","signals","source","queueMicrotask","value","aborted","abortSignal","resolve","undefined","addEventListener","reject","reason","once","next","Promise","withResolvers","promise","dispose","Error"],"mappings":"AAAA,SAASA,qBAAqB,QAAQ,gBAAgB;AA8BtD,OAAO,MAAMC,eAAkBD;;IACrBE,GAA6B;IAE5B,CAACC,OAAOC,WAAW,CAAC,GAAG,SAAS;IAwBzC,OAAOC,MAASC,MAAiB,EAAE,GAAGC,OAAoB,EAAQ;QAChE,KAAK,MAAMC,UAAUD,QAAS;YAC5BE,eAAe;gBACb,IAAI;oBACF,WAAW,MAAMC,SAASF,OAAQ;wBAChC,IAAIF,OAAOK,OAAO,EAAE;wBACpBL,OAAOI;oBACT;gBACF,EAAE,OAAM,CAER;YACF;QACF;IACF;IAgBA,YAAY,AAAiBE,WAAyB,CAAE;QACtD,KAAK,CAAC,CAACF;YACL,IAAI,IAAI,CAACR,EAAE,EAAE;gBACX,IAAI,CAACA,EAAE,CAACW,OAAO,CAACH;gBAChB,IAAI,CAACR,EAAE,GAAGY;gBACV,OAAO;YACT,OAAO;gBACL,OAAO;YACT;QACF,SAT2BF,cAAAA;QAU3B,IAAI,CAACA,WAAW,EAAEG,iBAChB,SACA;YACE,IAAI,CAACb,EAAE,EAAEc,OAAO,IAAI,CAACJ,WAAW,CAAEK,MAAM;YACxC,IAAI,CAACf,EAAE,GAAGY;QACZ,GACA;YAAEI,MAAM;QAAK;IAEjB;IAEA,IAAIP,UAAmB;QACrB,OAAO,CAAC,CAAC,IAAI,CAACC,WAAW,EAAED;IAC7B;IAoBAQ,OAAmB;QACjB,IAAI,IAAI,CAACP,WAAW,EAAED,SAAS;YAC7B,OAAOS,QAAQJ,MAAM,CAAC,IAAI,CAACJ,WAAW,CAACK,MAAM;QAC/C;QACA,IAAI,CAAC,IAAI,CAACf,EAAE,EAAE;YACZ,IAAI,CAACA,EAAE,GAAGkB,QAAQC,aAAa;QACjC;QACA,OAAO,IAAI,CAACnB,EAAE,CAACoB,OAAO;IACxB;IAMA,CAACnB,OAAOoB,OAAO,CAAC,GAAS;QACvB,IAAI,CAACrB,EAAE,EAAEc,OAAO,IAAIQ,MAAM;QAC1B,IAAI,CAACtB,EAAE,GAAGY;IACZ;AACF"}
1
+ {"version":3,"sources":["../src/signal.ts"],"sourcesContent":["import { Async } from './async.js';\n\n/**\n * Promise-based async coordination primitive.\n * `emit()` resolves the pending `receive()` promise (shared across callers).\n * Reusable - after each emission a new round of `receive()` calls can be made.\n * Disposable via `[Symbol.dispose]()` or an optional AbortSignal.\n *\n * @template T The type of value that this signal carries.\n *\n * @example\n * ```typescript\n * const signal = new Signal<string>();\n *\n * const promise = signal.receive();\n * signal.emit('hello');\n * await promise; // 'hello'\n * ```\n */\nexport class Signal<T> extends Async<T, boolean> {\n #rx?: PromiseWithResolvers<T>;\n\n readonly [Symbol.toStringTag] = 'Signal';\n\n /**\n * Merges multiple source signals into a target signal.\n * Values from any source signal are emitted to the target signal.\n * The merge continues until the target signal is disposed.\n *\n * Note: When the target is disposed, iteration stops after the next value\n * from each source. For immediate cleanup, dispose source signals directly.\n *\n * @param target The signal that will receive values from all sources\n * @param signals The source signals to merge from\n *\n * @example\n * ```typescript\n * // Create a target signal and source signals\n * const target = new Signal<string>();\n * const source1 = new Signal<string>();\n * const source2 = new Signal<string>();\n *\n * // Merge sources into target\n * Signal.merge(target, source1, source2);\n *\n * // Values from any source appear in target\n * const promise = target.receive();\n * source1.emit('Hello');\n * const value = await promise; // 'Hello'\n * ```\n */\n static merge<T>(target: Signal<T>, ...signals: Signal<T>[]): void {\n if (target.disposed) {\n return;\n }\n for (const source of signals) {\n void (async () => {\n try {\n const sink = target.sink;\n for await (const value of source) {\n if (!sink(value) && target.disposed) {\n return;\n }\n }\n } catch {\n // ignore disposed signal\n }\n })();\n }\n }\n\n /**\n * Creates a new Signal instance.\n *\n * @param abortSignal An optional AbortSignal that can be used to cancel the signal operation.\n *\n * @example\n * ```typescript\n * // Create a signal with abort capability\n * const controller = new AbortController();\n * const signal = new Signal<number>(controller.signal);\n *\n * // Signal can be cancelled\n * controller.abort('Operation cancelled');\n * ```\n */\n constructor(abortSignal?: AbortSignal) {\n super(abortSignal);\n }\n\n /**\n * Sends a value to the waiting receiver, if any.\n *\n * @param value - The value to send.\n * @returns `true` if the value was emitted.\n */\n emit(value: T): boolean {\n if (!this.#rx) return false;\n this.#rx.resolve(value);\n this.#rx = undefined;\n return true;\n }\n\n /**\n * Waits for the next value to be sent to this signal. If the signal has been aborted\n * or disposed, this method rejects with Error('Disposed').\n *\n * @returns A promise that resolves with the next value sent to the signal.\n *\n * @example\n * ```typescript\n * const signal = new Signal<string>();\n *\n * // Wait for a value\n * const valuePromise = signal.receive();\n *\n * // Send a value from elsewhere\n * signal.emit('Hello');\n *\n * const value = await valuePromise; // 'Hello'\n * ```\n */\n receive(): Promise<T> {\n if (this.disposed) {\n return Promise.reject(new Error('Disposed'));\n }\n this.#rx ??= Promise.withResolvers<T>();\n return this.#rx.promise;\n }\n\n dispose(): void {\n this.#rx?.reject(new Error('Disposed'));\n this.#rx = undefined;\n }\n}\n"],"names":["Async","Signal","Symbol","toStringTag","merge","target","signals","disposed","source","sink","value","abortSignal","emit","resolve","undefined","receive","Promise","reject","Error","withResolvers","promise","dispose"],"mappings":"AAAA,SAASA,KAAK,QAAQ,aAAa;AAmBnC,OAAO,MAAMC,eAAkBD;IAC7B,CAAA,EAAG,CAA2B;IAErB,CAACE,OAAOC,WAAW,CAAC,GAAG,SAAS;IA6BzC,OAAOC,MAASC,MAAiB,EAAE,GAAGC,OAAoB,EAAQ;QAChE,IAAID,OAAOE,QAAQ,EAAE;YACnB;QACF;QACA,KAAK,MAAMC,UAAUF,QAAS;YAC5B,KAAK,AAAC,CAAA;gBACJ,IAAI;oBACF,MAAMG,OAAOJ,OAAOI,IAAI;oBACxB,WAAW,MAAMC,SAASF,OAAQ;wBAChC,IAAI,CAACC,KAAKC,UAAUL,OAAOE,QAAQ,EAAE;4BACnC;wBACF;oBACF;gBACF,EAAE,OAAM,CAER;YACF,CAAA;QACF;IACF;IAiBA,YAAYI,WAAyB,CAAE;QACrC,KAAK,CAACA;IACR;IAQAC,KAAKF,KAAQ,EAAW;QACtB,IAAI,CAAC,IAAI,CAAC,CAAA,EAAG,EAAE,OAAO;QACtB,IAAI,CAAC,CAAA,EAAG,CAACG,OAAO,CAACH;QACjB,IAAI,CAAC,CAAA,EAAG,GAAGI;QACX,OAAO;IACT;IAqBAC,UAAsB;QACpB,IAAI,IAAI,CAACR,QAAQ,EAAE;YACjB,OAAOS,QAAQC,MAAM,CAAC,IAAIC,MAAM;QAClC;QACA,IAAI,CAAC,CAAA,EAAG,KAAKF,QAAQG,aAAa;QAClC,OAAO,IAAI,CAAC,CAAA,EAAG,CAACC,OAAO;IACzB;IAEAC,UAAgB;QACd,IAAI,CAAC,CAAA,EAAG,EAAEJ,OAAO,IAAIC,MAAM;QAC3B,IAAI,CAAC,CAAA,EAAG,GAAGJ;IACb;AACF"}
package/build/types.cjs CHANGED
@@ -2,16 +2,5 @@
2
2
  Object.defineProperty(exports, "__esModule", {
3
3
  value: true
4
4
  });
5
- Object.defineProperty(exports, "HookType", {
6
- enumerable: true,
7
- get: function() {
8
- return HookType;
9
- }
10
- });
11
- var HookType = /*#__PURE__*/ function(HookType) {
12
- HookType[HookType["Add"] = 0] = "Add";
13
- HookType[HookType["Remove"] = 1] = "Remove";
14
- return HookType;
15
- }({});
16
5
 
17
6
  //# sourceMappingURL=types.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts"],"sourcesContent":["export interface Fn<A extends unknown[], R> {\n (...args: A): R;\n}\n\nexport type MaybePromise<T> = Promise<T> | PromiseLike<T> | T;\n\nexport type AnyIterable<T, TReturn, TNext> = Iterable<T, TReturn, TNext> | AsyncIterable<T, TReturn, TNext>;\n\nexport interface Callback<R = void> extends Fn<[], MaybePromise<R>> {}\n\nexport interface Listener<T, R = unknown> extends Fn<[T], MaybePromise<R | void>> {}\n\nexport enum HookType {\n Add,\n Remove,\n}\n\nexport interface HookListener<T, R> {\n (listener: Listener<T, R> | undefined, type: HookType): void;\n}\n\nexport interface FilterIndexFunction<T> {\n (value: T, index: number): boolean;\n}\n\nexport interface FilterFunction<T> {\n (value: T): boolean;\n}\n\nexport interface AsyncFilterFunction<T> {\n (value: T): MaybePromise<boolean>;\n}\n\nexport interface Predicate<T, P extends T> {\n (value: T): value is P;\n}\n\nexport type Filter<T, P extends T> = Predicate<T, P> | FilterFunction<T> | AsyncFilterFunction<T>;\n\nexport interface Mapper<T, R> {\n (value: T): MaybePromise<R>;\n}\n\nexport interface AsyncGenerable<T, R> {\n (value: T): AsyncGenerator<R, void, unknown>;\n}\n\nexport interface Reducer<T, R> {\n (result: R, value: T): MaybePromise<R>;\n}\n\nexport interface Expander<T, R> {\n (value: T): MaybePromise<R>;\n}\n\nexport interface Promiseable<T> {\n next(): Promise<T>;\n}\n"],"names":["HookType"],"mappings":";;;;+BAYYA;;;eAAAA;;;AAAL,IAAA,AAAKA,kCAAAA;;;WAAAA"}
1
+ {"version":3,"sources":["../src/types.ts"],"names":[],"mappings":""}