@effect-uai/core 0.15.0 → 0.16.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 (91) hide show
  1. package/README.md +30 -16
  2. package/dist/{AiError-eLV9t1lU.d.mts → AiError-DvhLIbmd.d.mts} +15 -4
  3. package/dist/{AiError-eLV9t1lU.d.mts.map → AiError-DvhLIbmd.d.mts.map} +1 -1
  4. package/dist/{Loop-B54AB1ME.d.mts → Loop-ChpaO-ya.d.mts} +2 -2
  5. package/dist/{Loop-B54AB1ME.d.mts.map → Loop-ChpaO-ya.d.mts.map} +1 -1
  6. package/dist/{Toolkit-DAjJ9Tvl.d.mts → Toolkit-tlL0F2YR.d.mts} +2 -2
  7. package/dist/{Toolkit-DAjJ9Tvl.d.mts.map → Toolkit-tlL0F2YR.d.mts.map} +1 -1
  8. package/dist/chunker/Chunker.d.mts +1 -1
  9. package/dist/domain/AiError.d.mts +2 -2
  10. package/dist/domain/AiError.mjs +9 -2
  11. package/dist/domain/AiError.mjs.map +1 -1
  12. package/dist/domain/Realtime.d.mts +616 -0
  13. package/dist/domain/Realtime.d.mts.map +1 -0
  14. package/dist/domain/Realtime.mjs +21 -0
  15. package/dist/domain/Realtime.mjs.map +1 -0
  16. package/dist/domain/Transcript.d.mts +17 -3
  17. package/dist/domain/Transcript.d.mts.map +1 -1
  18. package/dist/domain/Transcript.mjs +30 -1
  19. package/dist/domain/Transcript.mjs.map +1 -1
  20. package/dist/domain/Transcript.test.mjs +110 -0
  21. package/dist/domain/Transcript.test.mjs.map +1 -0
  22. package/dist/embedding-model/EmbeddingModel.d.mts +1 -1
  23. package/dist/image-generator/ImageGenerator.d.mts +1 -1
  24. package/dist/index.d.mts +8 -5
  25. package/dist/index.mjs +6 -3
  26. package/dist/job/Job.d.mts +1 -1
  27. package/dist/language-model/LanguageModel.d.mts +2 -2
  28. package/dist/loop/Loop.d.mts +1 -1
  29. package/dist/music-generator/MusicGenerator.d.mts +1 -1
  30. package/dist/realtime/RealtimeSession.d.mts +50 -0
  31. package/dist/realtime/RealtimeSession.d.mts.map +1 -0
  32. package/dist/realtime/RealtimeSession.mjs +45 -0
  33. package/dist/realtime/RealtimeSession.mjs.map +1 -0
  34. package/dist/realtime/RealtimeSession.test.d.mts +1 -0
  35. package/dist/realtime/RealtimeSession.test.mjs +95 -0
  36. package/dist/realtime/RealtimeSession.test.mjs.map +1 -0
  37. package/dist/reranker/Reranker.d.mts +1 -1
  38. package/dist/research/DeepResearch.d.mts +1 -1
  39. package/dist/retry/Retry.d.mts +1 -1
  40. package/dist/speech-synthesizer/SpeechSynthesizer.d.mts +1 -1
  41. package/dist/streaming/Settle.d.mts +45 -0
  42. package/dist/streaming/Settle.d.mts.map +1 -0
  43. package/dist/streaming/Settle.mjs +95 -0
  44. package/dist/streaming/Settle.mjs.map +1 -0
  45. package/dist/streaming/Settle.test.d.mts +1 -0
  46. package/dist/streaming/Settle.test.mjs +252 -0
  47. package/dist/streaming/Settle.test.mjs.map +1 -0
  48. package/dist/streaming/WebSocketSession.d.mts +36 -0
  49. package/dist/streaming/WebSocketSession.d.mts.map +1 -0
  50. package/dist/streaming/WebSocketSession.mjs +89 -0
  51. package/dist/streaming/WebSocketSession.mjs.map +1 -0
  52. package/dist/streaming/WebSocketSession.test.d.mts +1 -0
  53. package/dist/streaming/WebSocketSession.test.mjs +51 -0
  54. package/dist/streaming/WebSocketSession.test.mjs.map +1 -0
  55. package/dist/testing/FakeWebSocket.d.mts +25 -0
  56. package/dist/testing/FakeWebSocket.d.mts.map +1 -0
  57. package/dist/testing/FakeWebSocket.mjs +79 -0
  58. package/dist/testing/FakeWebSocket.mjs.map +1 -0
  59. package/dist/testing/MockRealtimeSession.d.mts +30 -0
  60. package/dist/testing/MockRealtimeSession.d.mts.map +1 -0
  61. package/dist/testing/MockRealtimeSession.mjs +52 -0
  62. package/dist/testing/MockRealtimeSession.mjs.map +1 -0
  63. package/dist/tool/Toolkit.d.mts +1 -1
  64. package/dist/transcriber/Transcriber.d.mts +1 -1
  65. package/dist/web-read/WebRead.d.mts +1 -1
  66. package/dist/web-read/WebReadTool.d.mts +1 -1
  67. package/dist/web-search/WebSearch.d.mts +1 -1
  68. package/dist/web-search/WebSearchTool.d.mts +1 -1
  69. package/package.json +24 -4
  70. package/src/domain/AiError.ts +11 -0
  71. package/src/domain/Realtime.ts +101 -0
  72. package/src/domain/Transcript.test.ts +98 -0
  73. package/src/domain/Transcript.ts +53 -1
  74. package/src/index.ts +4 -1
  75. package/src/realtime/RealtimeSession.test.ts +100 -0
  76. package/src/realtime/RealtimeSession.ts +77 -0
  77. package/src/streaming/Settle.test.ts +344 -0
  78. package/src/streaming/Settle.ts +196 -0
  79. package/src/streaming/WebSocketSession.test.ts +52 -0
  80. package/src/streaming/WebSocketSession.ts +132 -0
  81. package/src/testing/FakeWebSocket.ts +120 -0
  82. package/src/testing/MockRealtimeSession.ts +92 -0
  83. package/dist/inbox/Inbox.d.mts +0 -16
  84. package/dist/inbox/Inbox.d.mts.map +0 -1
  85. package/dist/inbox/Inbox.mjs +0 -28
  86. package/dist/inbox/Inbox.mjs.map +0 -1
  87. package/dist/inbox/Inbox.test.mjs +0 -42
  88. package/dist/inbox/Inbox.test.mjs.map +0 -1
  89. package/src/inbox/Inbox.test.ts +0 -52
  90. package/src/inbox/Inbox.ts +0 -33
  91. /package/dist/{inbox/Inbox.test.d.mts → domain/Transcript.test.d.mts} +0 -0
@@ -0,0 +1,45 @@
1
+ import { Array, Duration, Effect, Option, Queue, Stream } from "effect";
2
+ declare namespace Settle_d_exports {
3
+ export { SettleBurstOptions, drainBurst, onQuiet, settleBurst };
4
+ }
5
+ /**
6
+ * Block for the first item, then keep taking while the next one lands within
7
+ * `settle` of the previous. The window resets on every arrival, so a burst
8
+ * becomes one batch and a lone message followed by silence returns at once.
9
+ *
10
+ * A take that loses to the window is interrupted before it removes anything,
11
+ * so late arrivals stay queued for the next drain. The queue is the caller's:
12
+ * it owns the backpressure strategy.
13
+ */
14
+ declare const drainBurst: <A, E>(queue: Queue.Dequeue<A, E>, settle: Duration.Input) => Effect.Effect<Array.NonEmptyReadonlyArray<A>, E>;
15
+ type SettleBurstOptions = {
16
+ /** Elements buffered ahead of the batcher. A full buffer suspends the source. */
17
+ readonly capacity?: number;
18
+ };
19
+ /**
20
+ * `drainBurst` over a stream: one array per burst, nothing while a burst is
21
+ * still growing.
22
+ *
23
+ * Detecting a pause means reading ahead of the consumer, so this buffers by
24
+ * construction. The buffer is bounded, so a slow consumer suspends the source
25
+ * instead of growing memory.
26
+ */
27
+ declare const settleBurst: {
28
+ (settle: Duration.Input, options?: SettleBurstOptions): <A, E, R>(self: Stream.Stream<A, E, R>) => Stream.Stream<ReadonlyArray<A>, E, R>;
29
+ <A, E, R>(self: Stream.Stream<A, E, R>, settle: Duration.Input, options?: SettleBurstOptions): Stream.Stream<ReadonlyArray<A>, E, R>;
30
+ };
31
+ /**
32
+ * Pass every element through the moment it arrives, and emit whatever `emit`
33
+ * yields once nothing has arrived for `settle`. At most one emission per quiet
34
+ * period: the next element re-arms it.
35
+ *
36
+ * Nothing is held back, so this suits live output that wants a boundary marker
37
+ * rather than batching. Ends with the source.
38
+ */
39
+ declare const onQuiet: {
40
+ <B>(settle: Duration.Input, emit: Effect.Effect<Option.Option<B>>): <A, E, R>(self: Stream.Stream<A, E, R>) => Stream.Stream<A | B, E, R>;
41
+ <A, E, R, B>(self: Stream.Stream<A, E, R>, settle: Duration.Input, emit: Effect.Effect<Option.Option<B>>): Stream.Stream<A | B, E, R>;
42
+ };
43
+ //#endregion
44
+ export { SettleBurstOptions, drainBurst, onQuiet, settleBurst, Settle_d_exports as t };
45
+ //# sourceMappingURL=Settle.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Settle.d.mts","names":[],"sources":["../../src/streaming/Settle.ts"],"mappings":";;;;;;;;;;;;;cAwCa,aAAc,GAAG,GAAC,OACtB,MAAM,QAAQ,GAAG,IAAE,QAClB,SAAS,UAChB,OAAO,OAAO,MAAI,sBAAsB,IAAI;KAqBnC;;WAED;;;;;;;;;;cAWE;GAET,QAAQ,SAAS,OACjB,UAAU,sBACR,GAAG,GAAG,GAAG,MAAM,OAAO,OAAO,GAAG,GAAG,OAAO,OAAO,OAAO,cAAc,IAAI,GAAG;GAChF,GAAG,GAAG,GACL,MAAM,OAAO,OAAO,GAAG,GAAG,IAC1B,QAAQ,SAAS,OACjB,UAAU,qBACT,OAAO,OAAO,cAAc,IAAI,GAAG;;;;;;;;;;cAsC3B;GACV,GACC,QAAQ,SAAS,OACjB,MAAM,OAAO,OAAO,OAAO,OAAO,OAChC,GAAG,GAAG,GAAG,MAAM,OAAO,OAAO,GAAG,GAAG,OAAO,OAAO,OAAO,IAAI,GAAG,GAAG;GACrE,GAAG,GAAG,GAAG,GACR,MAAM,OAAO,OAAO,GAAG,GAAG,IAC1B,QAAQ,SAAS,OACjB,MAAM,OAAO,OAAO,OAAO,OAAO,MACjC,OAAO,OAAO,IAAI,GAAG,GAAG"}
@@ -0,0 +1,95 @@
1
+ import { n as __exportAll } from "../rolldown-runtime-EAsCxrDo.mjs";
2
+ import { Array, Cause, Clock, Deferred, Duration, Effect, Function, Option, Pull, Queue, Ref, Result, Stream } from "effect";
3
+ //#region src/streaming/Settle.ts
4
+ /**
5
+ * Acting on a pause in a stream of arrivals.
6
+ *
7
+ * A chat user types three lines then stops; a transcriber emits token deltas
8
+ * then goes quiet. Both want a resetting window: every arrival pushes the
9
+ * deadline out. Effect's `groupedWithin` measures a fixed window from the first
10
+ * element of a batch and `debounce` drops all but the last element, so neither
11
+ * expresses this.
12
+ *
13
+ * `settleBurst` withholds elements until a burst ends; `onQuiet` holds nothing
14
+ * back and only adds an event once things go still.
15
+ */
16
+ var Settle_exports = /* @__PURE__ */ __exportAll({
17
+ drainBurst: () => drainBurst,
18
+ onQuiet: () => onQuiet,
19
+ settleBurst: () => settleBurst
20
+ });
21
+ /** Buffer between a source stream and the batcher. 64 matches the realtime adapters. */
22
+ const DEFAULT_CAPACITY = 64;
23
+ /**
24
+ * Block for the first item, then keep taking while the next one lands within
25
+ * `settle` of the previous. The window resets on every arrival, so a burst
26
+ * becomes one batch and a lone message followed by silence returns at once.
27
+ *
28
+ * A take that loses to the window is interrupted before it removes anything,
29
+ * so late arrivals stay queued for the next drain. The queue is the caller's:
30
+ * it owns the backpressure strategy.
31
+ */
32
+ const drainBurst = (queue, settle) => {
33
+ const settled = (batch) => Queue.take(queue).pipe(Effect.timeoutOption(settle), Effect.flatMap(Option.match({
34
+ onNone: () => Effect.succeed(batch),
35
+ onSome: (item) => settled(Array.append(batch, item))
36
+ })), Effect.catchCause((cause) => Pull.isDoneCause(cause) ? Effect.succeed(batch) : Effect.failCause(cause)));
37
+ return Effect.flatMap(Queue.take(queue), (first) => settled([first]));
38
+ };
39
+ /**
40
+ * `drainBurst` over a stream: one array per burst, nothing while a burst is
41
+ * still growing.
42
+ *
43
+ * Detecting a pause means reading ahead of the consumer, so this buffers by
44
+ * construction. The buffer is bounded, so a slow consumer suspends the source
45
+ * instead of growing memory.
46
+ */
47
+ const settleBurst = Function.dual((args) => Stream.isStream(args[0]), (self, settle, options) => Stream.unwrap(Effect.gen(function* () {
48
+ const queue = yield* Queue.bounded(options?.capacity ?? DEFAULT_CAPACITY);
49
+ yield* Stream.runForEach(self, (a) => Queue.offer(queue, a)).pipe(Effect.matchCauseEffect({
50
+ onFailure: (cause) => Cause.hasFails(cause) ? Queue.failCause(queue, cause) : Queue.end(queue),
51
+ onSuccess: () => Queue.end(queue)
52
+ }), Effect.forkScoped);
53
+ return Stream.fromPull(Effect.succeed(Effect.map(drainBurst(queue, settle), (batch) => [batch])));
54
+ })));
55
+ /**
56
+ * Pass every element through the moment it arrives, and emit whatever `emit`
57
+ * yields once nothing has arrived for `settle`. At most one emission per quiet
58
+ * period: the next element re-arms it.
59
+ *
60
+ * Nothing is held back, so this suits live output that wants a boundary marker
61
+ * rather than batching. Ends with the source.
62
+ */
63
+ const onQuiet = Function.dual(3, (self, settle, emit) => Stream.unwrap(Effect.gen(function* () {
64
+ const settleMs = Duration.toMillis(settle);
65
+ const lastAt = yield* Ref.make(0);
66
+ const gate = yield* Ref.make(yield* Deferred.make());
67
+ const source = self.pipe(Stream.tap(() => Effect.gen(function* () {
68
+ const now = yield* Clock.currentTimeMillis;
69
+ yield* Ref.set(lastAt, now);
70
+ const open = yield* Ref.get(gate);
71
+ yield* Deferred.succeed(open, void 0);
72
+ })));
73
+ const quietFor = Effect.map(Effect.all([Clock.currentTimeMillis, Ref.get(lastAt)]), ([now, last]) => now - last);
74
+ const tick = Effect.gen(function* () {
75
+ yield* Effect.flatMap(Ref.get(gate), Deferred.await);
76
+ const elapsed = yield* quietFor;
77
+ if (elapsed < settleMs) {
78
+ yield* Effect.sleep(Duration.millis(settleMs - elapsed));
79
+ return Option.none();
80
+ }
81
+ const next = yield* Deferred.make();
82
+ yield* Ref.set(gate, next);
83
+ if ((yield* quietFor) < settleMs) {
84
+ yield* Deferred.succeed(next, void 0);
85
+ return Option.none();
86
+ }
87
+ return yield* emit;
88
+ });
89
+ const metronome = Stream.fromEffect(tick).pipe(Stream.forever, Stream.filterMap((o) => Option.isSome(o) ? Result.succeed(o.value) : Result.failVoid));
90
+ return source.pipe(Stream.merge(metronome, { haltStrategy: "left" }));
91
+ })));
92
+ //#endregion
93
+ export { drainBurst, onQuiet, settleBurst, Settle_exports as t };
94
+
95
+ //# sourceMappingURL=Settle.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Settle.mjs","names":["Arr"],"sources":["../../src/streaming/Settle.ts"],"sourcesContent":["/**\n * Acting on a pause in a stream of arrivals.\n *\n * A chat user types three lines then stops; a transcriber emits token deltas\n * then goes quiet. Both want a resetting window: every arrival pushes the\n * deadline out. Effect's `groupedWithin` measures a fixed window from the first\n * element of a batch and `debounce` drops all but the last element, so neither\n * expresses this.\n *\n * `settleBurst` withholds elements until a burst ends; `onQuiet` holds nothing\n * back and only adds an event once things go still.\n */\nimport {\n Array as Arr,\n Cause,\n Clock,\n Deferred,\n Duration,\n Effect,\n Function,\n Option,\n Pull,\n Queue,\n Ref,\n Result,\n Stream,\n} from \"effect\"\n\n/** Buffer between a source stream and the batcher. 64 matches the realtime adapters. */\nconst DEFAULT_CAPACITY = 64\n\n/**\n * Block for the first item, then keep taking while the next one lands within\n * `settle` of the previous. The window resets on every arrival, so a burst\n * becomes one batch and a lone message followed by silence returns at once.\n *\n * A take that loses to the window is interrupted before it removes anything,\n * so late arrivals stay queued for the next drain. The queue is the caller's:\n * it owns the backpressure strategy.\n */\nexport const drainBurst = <A, E>(\n queue: Queue.Dequeue<A, E>,\n settle: Duration.Input,\n): Effect.Effect<Arr.NonEmptyReadonlyArray<A>, E> => {\n const settled = (\n batch: Arr.NonEmptyReadonlyArray<A>,\n ): Effect.Effect<Arr.NonEmptyReadonlyArray<A>, E> =>\n Queue.take(queue).pipe(\n Effect.timeoutOption(settle),\n Effect.flatMap(\n Option.match({\n onNone: () => Effect.succeed(batch),\n onSome: (item) => settled(Arr.append(batch, item)),\n }),\n ),\n // A queue that ends mid-burst yields the batch in hand. Interrupts and\n // real failures still propagate.\n Effect.catchCause((cause) =>\n Pull.isDoneCause(cause) ? Effect.succeed(batch) : Effect.failCause(cause),\n ),\n )\n return Effect.flatMap(Queue.take(queue), (first) => settled([first]))\n}\n\nexport type SettleBurstOptions = {\n /** Elements buffered ahead of the batcher. A full buffer suspends the source. */\n readonly capacity?: number\n}\n\n/**\n * `drainBurst` over a stream: one array per burst, nothing while a burst is\n * still growing.\n *\n * Detecting a pause means reading ahead of the consumer, so this buffers by\n * construction. The buffer is bounded, so a slow consumer suspends the source\n * instead of growing memory.\n */\nexport const settleBurst: {\n (\n settle: Duration.Input,\n options?: SettleBurstOptions,\n ): <A, E, R>(self: Stream.Stream<A, E, R>) => Stream.Stream<ReadonlyArray<A>, E, R>\n <A, E, R>(\n self: Stream.Stream<A, E, R>,\n settle: Duration.Input,\n options?: SettleBurstOptions,\n ): Stream.Stream<ReadonlyArray<A>, E, R>\n} = Function.dual(\n (args) => Stream.isStream(args[0]),\n <A, E, R>(\n self: Stream.Stream<A, E, R>,\n settle: Duration.Input,\n options?: SettleBurstOptions,\n ): Stream.Stream<ReadonlyArray<A>, E, R> =>\n Stream.unwrap(\n Effect.gen(function* () {\n // `Cause.Done` as the failure type is what makes `Queue.end` available;\n // `Queue.shutdown` would clear items still waiting to be batched.\n const queue = yield* Queue.bounded<A, E | Cause.Done>(options?.capacity ?? DEFAULT_CAPACITY)\n yield* Stream.runForEach(self, (a) => Queue.offer(queue, a)).pipe(\n // A failing source must reach the consumer. Ending the queue instead\n // would present the failure as a clean end of stream.\n Effect.matchCauseEffect({\n onFailure: (cause) =>\n Cause.hasFails(cause) ? Queue.failCause(queue, cause) : Queue.end(queue),\n onSuccess: () => Queue.end(queue),\n }),\n Effect.forkScoped,\n )\n return Stream.fromPull(\n Effect.succeed(Effect.map(drainBurst(queue, settle), (batch) => [batch] as const)),\n )\n }),\n ),\n)\n\n/**\n * Pass every element through the moment it arrives, and emit whatever `emit`\n * yields once nothing has arrived for `settle`. At most one emission per quiet\n * period: the next element re-arms it.\n *\n * Nothing is held back, so this suits live output that wants a boundary marker\n * rather than batching. Ends with the source.\n */\nexport const onQuiet: {\n <B>(\n settle: Duration.Input,\n emit: Effect.Effect<Option.Option<B>>,\n ): <A, E, R>(self: Stream.Stream<A, E, R>) => Stream.Stream<A | B, E, R>\n <A, E, R, B>(\n self: Stream.Stream<A, E, R>,\n settle: Duration.Input,\n emit: Effect.Effect<Option.Option<B>>,\n ): Stream.Stream<A | B, E, R>\n} = Function.dual(\n 3,\n <A, E, R, B>(\n self: Stream.Stream<A, E, R>,\n settle: Duration.Input,\n emit: Effect.Effect<Option.Option<B>>,\n ): Stream.Stream<A | B, E, R> =>\n Stream.unwrap(\n Effect.gen(function* () {\n const settleMs = Duration.toMillis(settle)\n const lastAt = yield* Ref.make(0)\n // Opened by the first arrival after each emission. An idle stream never\n // wakes the metronome at all.\n const gate = yield* Ref.make(yield* Deferred.make<void>())\n\n const source = self.pipe(\n Stream.tap(() =>\n Effect.gen(function* () {\n // Stamp before opening: the metronome re-reads the stamp after it\n // swaps the gate, so an arrival that races the swap still counts.\n const now = yield* Clock.currentTimeMillis\n yield* Ref.set(lastAt, now)\n const open = yield* Ref.get(gate)\n yield* Deferred.succeed(open, undefined)\n }),\n ),\n )\n\n const quietFor = Effect.map(\n Effect.all([Clock.currentTimeMillis, Ref.get(lastAt)]),\n ([now, last]) => now - last,\n )\n\n // Waits for the gate, then sleeps out whatever is left of the window.\n // Never polls, whether the source is busy or idle.\n const tick = Effect.gen(function* () {\n yield* Effect.flatMap(Ref.get(gate), Deferred.await)\n const elapsed = yield* quietFor\n if (elapsed < settleMs) {\n yield* Effect.sleep(Duration.millis(settleMs - elapsed))\n return Option.none<B>()\n }\n const next = yield* Deferred.make<void>()\n yield* Ref.set(gate, next)\n // An arrival between the check and the swap opened the old gate, so\n // carry it over rather than sleeping through the burst it started.\n if ((yield* quietFor) < settleMs) {\n yield* Deferred.succeed(next, undefined)\n return Option.none<B>()\n }\n return yield* emit\n })\n\n const metronome = Stream.fromEffect(tick).pipe(\n Stream.forever,\n Stream.filterMap((o) => (Option.isSome(o) ? Result.succeed(o.value) : Result.failVoid)),\n )\n\n return source.pipe(Stream.merge(metronome, { haltStrategy: \"left\" }))\n }),\n ),\n)\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA6BA,MAAM,mBAAmB;;;;;;;;;;AAWzB,MAAa,cACX,OACA,WACmD;CACnD,MAAM,WACJ,UAEA,MAAM,KAAK,KAAK,CAAC,CAAC,KAChB,OAAO,cAAc,MAAM,GAC3B,OAAO,QACL,OAAO,MAAM;EACX,cAAc,OAAO,QAAQ,KAAK;EAClC,SAAS,SAAS,QAAQA,MAAI,OAAO,OAAO,IAAI,CAAC;CACnD,CAAC,CACH,GAGA,OAAO,YAAY,UACjB,KAAK,YAAY,KAAK,IAAI,OAAO,QAAQ,KAAK,IAAI,OAAO,UAAU,KAAK,CAC1E,CACF;CACF,OAAO,OAAO,QAAQ,MAAM,KAAK,KAAK,IAAI,UAAU,QAAQ,CAAC,KAAK,CAAC,CAAC;AACtE;;;;;;;;;AAeA,MAAa,cAUT,SAAS,MACV,SAAS,OAAO,SAAS,KAAK,EAAE,IAE/B,MACA,QACA,YAEA,OAAO,OACL,OAAO,IAAI,aAAa;CAGtB,MAAM,QAAQ,OAAO,MAAM,QAA2B,SAAS,YAAY,gBAAgB;CAC3F,OAAO,OAAO,WAAW,OAAO,MAAM,MAAM,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC,KAG3D,OAAO,iBAAiB;EACtB,YAAY,UACV,MAAM,SAAS,KAAK,IAAI,MAAM,UAAU,OAAO,KAAK,IAAI,MAAM,IAAI,KAAK;EACzE,iBAAiB,MAAM,IAAI,KAAK;CAClC,CAAC,GACD,OAAO,UACT;CACA,OAAO,OAAO,SACZ,OAAO,QAAQ,OAAO,IAAI,WAAW,OAAO,MAAM,IAAI,UAAU,CAAC,KAAK,CAAU,CAAC,CACnF;AACF,CAAC,CACH,CACJ;;;;;;;;;AAUA,MAAa,UAUT,SAAS,KACX,IAEE,MACA,QACA,SAEA,OAAO,OACL,OAAO,IAAI,aAAa;CACtB,MAAM,WAAW,SAAS,SAAS,MAAM;CACzC,MAAM,SAAS,OAAO,IAAI,KAAK,CAAC;CAGhC,MAAM,OAAO,OAAO,IAAI,KAAK,OAAO,SAAS,KAAW,CAAC;CAEzD,MAAM,SAAS,KAAK,KAClB,OAAO,UACL,OAAO,IAAI,aAAa;EAGtB,MAAM,MAAM,OAAO,MAAM;EACzB,OAAO,IAAI,IAAI,QAAQ,GAAG;EAC1B,MAAM,OAAO,OAAO,IAAI,IAAI,IAAI;EAChC,OAAO,SAAS,QAAQ,MAAM,KAAA,CAAS;CACzC,CAAC,CACH,CACF;CAEA,MAAM,WAAW,OAAO,IACtB,OAAO,IAAI,CAAC,MAAM,mBAAmB,IAAI,IAAI,MAAM,CAAC,CAAC,IACpD,CAAC,KAAK,UAAU,MAAM,IACzB;CAIA,MAAM,OAAO,OAAO,IAAI,aAAa;EACnC,OAAO,OAAO,QAAQ,IAAI,IAAI,IAAI,GAAG,SAAS,KAAK;EACnD,MAAM,UAAU,OAAO;EACvB,IAAI,UAAU,UAAU;GACtB,OAAO,OAAO,MAAM,SAAS,OAAO,WAAW,OAAO,CAAC;GACvD,OAAO,OAAO,KAAQ;EACxB;EACA,MAAM,OAAO,OAAO,SAAS,KAAW;EACxC,OAAO,IAAI,IAAI,MAAM,IAAI;EAGzB,KAAK,OAAO,YAAY,UAAU;GAChC,OAAO,SAAS,QAAQ,MAAM,KAAA,CAAS;GACvC,OAAO,OAAO,KAAQ;EACxB;EACA,OAAO,OAAO;CAChB,CAAC;CAED,MAAM,YAAY,OAAO,WAAW,IAAI,CAAC,CAAC,KACxC,OAAO,SACP,OAAO,WAAW,MAAO,OAAO,OAAO,CAAC,IAAI,OAAO,QAAQ,EAAE,KAAK,IAAI,OAAO,QAAS,CACxF;CAEA,OAAO,OAAO,KAAK,OAAO,MAAM,WAAW,EAAE,cAAc,OAAO,CAAC,CAAC;AACtE,CAAC,CACH,CACJ"}
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,252 @@
1
+ import { drainBurst, onQuiet, settleBurst } from "./Settle.mjs";
2
+ import { o as describe } from "../utils-CiH_DjFW.mjs";
3
+ import { n as globalExpect } from "../test.DNmyFkvJ-YPzDR0Co.mjs";
4
+ import { t as it } from "../dist-nohcnkD6.mjs";
5
+ import { Effect, Fiber, Option, Queue, Ref, Stream } from "effect";
6
+ import { TestClock } from "effect/testing";
7
+ //#region src/streaming/Settle.test.ts
8
+ describe("drainBurst", () => {
9
+ it.effect("resets the window on every arrival, so a burst outlives one settle", () => Effect.gen(function* () {
10
+ const queue = yield* Queue.unbounded();
11
+ const burst = yield* Effect.forkChild(drainBurst(queue, "1 second"));
12
+ yield* Queue.offer(queue, "a");
13
+ yield* TestClock.adjust("900 millis");
14
+ yield* Queue.offer(queue, "b");
15
+ yield* TestClock.adjust("900 millis");
16
+ globalExpect(burst.pollUnsafe()).toBeUndefined();
17
+ yield* TestClock.adjust("1 second");
18
+ globalExpect(yield* Fiber.join(burst)).toEqual(["a", "b"]);
19
+ }));
20
+ it.effect("waits for the first item without a deadline", () => Effect.gen(function* () {
21
+ const queue = yield* Queue.unbounded();
22
+ const burst = yield* Effect.forkChild(drainBurst(queue, "1 second"));
23
+ yield* TestClock.adjust("1 hour");
24
+ globalExpect(burst.pollUnsafe()).toBeUndefined();
25
+ yield* Queue.offer(queue, "late");
26
+ yield* TestClock.adjust("1 second");
27
+ globalExpect(yield* Fiber.join(burst)).toEqual(["late"]);
28
+ }));
29
+ it.effect("ends on the first quiet gap and leaves later items for the next drain", () => Effect.gen(function* () {
30
+ const queue = yield* Queue.unbounded();
31
+ const burst = yield* Effect.forkChild(drainBurst(queue, "1 second"));
32
+ yield* Queue.offer(queue, "first");
33
+ yield* TestClock.adjust("1 second");
34
+ globalExpect(yield* Fiber.join(burst)).toEqual(["first"]);
35
+ yield* Queue.offer(queue, "second");
36
+ globalExpect(yield* Queue.poll(queue)).toEqual(Option.some("second"));
37
+ }));
38
+ it.effect("yields the batch in hand when the queue ends mid-burst", () => Effect.gen(function* () {
39
+ const queue = yield* Queue.unbounded();
40
+ const burst = yield* Effect.forkChild(drainBurst(queue, "1 second"));
41
+ yield* Queue.offer(queue, "a");
42
+ yield* TestClock.adjust("100 millis");
43
+ yield* Queue.end(queue);
44
+ yield* TestClock.adjust("10 millis");
45
+ globalExpect(yield* Fiber.join(burst)).toEqual(["a"]);
46
+ }));
47
+ it.effect("propagates a queue failure rather than treating it as the end", () => Effect.gen(function* () {
48
+ const queue = yield* Queue.unbounded();
49
+ const burst = yield* Effect.forkChild(drainBurst(queue, "1 second"));
50
+ yield* Queue.offer(queue, "a");
51
+ yield* TestClock.adjust("100 millis");
52
+ yield* Queue.fail(queue, "boom");
53
+ yield* TestClock.adjust("10 millis");
54
+ const exit = yield* Effect.exit(Fiber.join(burst));
55
+ globalExpect(exit._tag).toBe("Failure");
56
+ globalExpect(JSON.stringify(exit)).toContain("boom");
57
+ }));
58
+ it.effect("does not mistake an interrupt mid-burst for the end of the queue", () => Effect.gen(function* () {
59
+ const queue = yield* Queue.unbounded();
60
+ const burst = yield* Effect.forkChild(drainBurst(queue, "1 second"));
61
+ yield* Queue.offer(queue, "a");
62
+ yield* TestClock.adjust("100 millis");
63
+ yield* Fiber.interrupt(burst);
64
+ const exit = yield* Effect.exit(Fiber.join(burst));
65
+ globalExpect(exit._tag).toBe("Failure");
66
+ }));
67
+ });
68
+ describe("settleBurst", () => {
69
+ const collecting = (stream) => Effect.gen(function* () {
70
+ const seen = yield* Ref.make([]);
71
+ return {
72
+ seen,
73
+ fiber: yield* Effect.forkChild(Stream.runForEach(stream, (a) => Ref.update(seen, (xs) => [...xs, a])))
74
+ };
75
+ });
76
+ it.effect("holds a growing burst back, then emits it as one batch", () => Effect.gen(function* () {
77
+ const queue = yield* Queue.unbounded();
78
+ const { seen } = yield* collecting(Stream.fromQueue(queue).pipe(settleBurst("1 second")));
79
+ yield* Queue.offer(queue, "a");
80
+ yield* TestClock.adjust("900 millis");
81
+ yield* Queue.offer(queue, "b");
82
+ yield* TestClock.adjust("900 millis");
83
+ globalExpect(yield* Ref.get(seen)).toEqual([]);
84
+ yield* TestClock.adjust("1 second");
85
+ globalExpect(yield* Ref.get(seen)).toEqual([["a", "b"]]);
86
+ }));
87
+ it.effect("emits the in-flight batch when the source ends mid-burst", () => Effect.gen(function* () {
88
+ const queue = yield* Queue.unbounded();
89
+ const { seen } = yield* collecting(Stream.fromQueue(queue).pipe(settleBurst("1 second")));
90
+ yield* Queue.offer(queue, "only");
91
+ yield* TestClock.adjust("100 millis");
92
+ yield* Queue.end(queue);
93
+ yield* TestClock.adjust("10 millis");
94
+ globalExpect(yield* Ref.get(seen)).toEqual([["only"]]);
95
+ }));
96
+ it.effect("splits bursts on every silence, keeping order across a conversation", () => Effect.gen(function* () {
97
+ const queue = yield* Queue.unbounded();
98
+ const { seen } = yield* collecting(Stream.fromQueue(queue).pipe(settleBurst("1 second")));
99
+ yield* Queue.offer(queue, "hey");
100
+ yield* TestClock.adjust("100 millis");
101
+ yield* Queue.offer(queue, "are you there");
102
+ yield* TestClock.adjust("100 millis");
103
+ yield* Queue.offer(queue, "?");
104
+ yield* TestClock.adjust("2 seconds");
105
+ yield* Queue.offer(queue, "actually");
106
+ yield* TestClock.adjust("100 millis");
107
+ yield* Queue.offer(queue, "never mind");
108
+ yield* TestClock.adjust("2 seconds");
109
+ yield* Queue.offer(queue, "bye");
110
+ yield* TestClock.adjust("2 seconds");
111
+ globalExpect(yield* Ref.get(seen)).toEqual([
112
+ [
113
+ "hey",
114
+ "are you there",
115
+ "?"
116
+ ],
117
+ ["actually", "never mind"],
118
+ ["bye"]
119
+ ]);
120
+ }));
121
+ it.effect("fails the stream when the source fails instead of ending quietly", () => Effect.gen(function* () {
122
+ const source = Stream.make("a").pipe(Stream.concat(Stream.fail("boom")));
123
+ const exit = yield* Effect.exit(Stream.runCollect(source.pipe(settleBurst("1 second"))));
124
+ globalExpect(exit._tag).toBe("Failure");
125
+ globalExpect(JSON.stringify(exit)).toContain("boom");
126
+ }));
127
+ it.effect("stops pulling the source while the consumer is blocked", () => Effect.gen(function* () {
128
+ const pulled = yield* Ref.make(0);
129
+ const source = Stream.fromIterable(Array.from({ length: 50 }, (_, i) => i)).pipe(Stream.rechunk(1), Stream.tap(() => Ref.update(pulled, (n) => n + 1)));
130
+ yield* Effect.forkChild(source.pipe(settleBurst("1 second", { capacity: 2 }), Stream.runForEach(() => Effect.never)));
131
+ yield* TestClock.adjust("10 seconds");
132
+ globalExpect(yield* Ref.get(pulled)).toBeLessThan(50);
133
+ }));
134
+ });
135
+ describe("onQuiet", () => {
136
+ const mark = Effect.succeed(Option.some("<quiet>"));
137
+ const collecting = (stream) => Effect.gen(function* () {
138
+ const seen = yield* Ref.make([]);
139
+ yield* Effect.forkChild(Stream.runForEach(stream, (a) => Ref.update(seen, (xs) => [...xs, a])));
140
+ return seen;
141
+ });
142
+ it.effect("passes elements downstream without waiting for the quiet period", () => Effect.gen(function* () {
143
+ const queue = yield* Queue.unbounded();
144
+ const seen = yield* collecting(Stream.fromQueue(queue).pipe(onQuiet("1 second", mark)));
145
+ yield* Queue.offer(queue, "a");
146
+ yield* TestClock.adjust("10 millis");
147
+ yield* Queue.offer(queue, "b");
148
+ yield* TestClock.adjust("10 millis");
149
+ globalExpect(yield* Ref.get(seen)).toEqual(["a", "b"]);
150
+ }));
151
+ it.effect("emits once the source goes quiet, and not again until it speaks", () => Effect.gen(function* () {
152
+ const queue = yield* Queue.unbounded();
153
+ const seen = yield* collecting(Stream.fromQueue(queue).pipe(onQuiet("1 second", mark)));
154
+ yield* Queue.offer(queue, "a");
155
+ yield* TestClock.adjust("2 seconds");
156
+ globalExpect(yield* Ref.get(seen)).toEqual(["a", "<quiet>"]);
157
+ yield* TestClock.adjust("10 seconds");
158
+ globalExpect(yield* Ref.get(seen)).toEqual(["a", "<quiet>"]);
159
+ yield* Queue.offer(queue, "b");
160
+ yield* TestClock.adjust("2 seconds");
161
+ globalExpect(yield* Ref.get(seen)).toEqual([
162
+ "a",
163
+ "<quiet>",
164
+ "b",
165
+ "<quiet>"
166
+ ]);
167
+ }));
168
+ it.effect("marks an element that arrives in the same instant as the mark", () => Effect.gen(function* () {
169
+ const queue = yield* Queue.unbounded();
170
+ const seen = yield* collecting(Stream.fromQueue(queue).pipe(onQuiet("1 second", mark)));
171
+ yield* Queue.offer(queue, "a");
172
+ yield* TestClock.adjust("1 second");
173
+ yield* Queue.offer(queue, "b");
174
+ yield* TestClock.adjust("2 seconds");
175
+ globalExpect(yield* Ref.get(seen)).toEqual([
176
+ "a",
177
+ "<quiet>",
178
+ "b",
179
+ "<quiet>"
180
+ ]);
181
+ }));
182
+ it.effect("keeps the window open while elements keep arriving", () => Effect.gen(function* () {
183
+ const queue = yield* Queue.unbounded();
184
+ const seen = yield* collecting(Stream.fromQueue(queue).pipe(onQuiet("1 second", mark)));
185
+ yield* Queue.offer(queue, "a");
186
+ yield* TestClock.adjust("900 millis");
187
+ yield* Queue.offer(queue, "b");
188
+ yield* TestClock.adjust("900 millis");
189
+ globalExpect(yield* Ref.get(seen)).toEqual(["a", "b"]);
190
+ yield* TestClock.adjust("2 seconds");
191
+ globalExpect(yield* Ref.get(seen)).toEqual([
192
+ "a",
193
+ "b",
194
+ "<quiet>"
195
+ ]);
196
+ }));
197
+ it.effect("marks each utterance in a run of speech, pauses and more speech", () => Effect.gen(function* () {
198
+ const queue = yield* Queue.unbounded();
199
+ const seen = yield* collecting(Stream.fromQueue(queue).pipe(onQuiet("1 second", mark)));
200
+ for (const token of [
201
+ "what",
202
+ " about",
203
+ " Paris"
204
+ ]) {
205
+ yield* Queue.offer(queue, token);
206
+ yield* TestClock.adjust("200 millis");
207
+ }
208
+ globalExpect(yield* Ref.get(seen)).toEqual([
209
+ "what",
210
+ " about",
211
+ " Paris"
212
+ ]);
213
+ yield* TestClock.adjust("3 seconds");
214
+ for (const token of ["in", " winter"]) {
215
+ yield* Queue.offer(queue, token);
216
+ yield* TestClock.adjust("200 millis");
217
+ }
218
+ yield* TestClock.adjust("3 seconds");
219
+ globalExpect(yield* Ref.get(seen)).toEqual([
220
+ "what",
221
+ " about",
222
+ " Paris",
223
+ "<quiet>",
224
+ "in",
225
+ " winter",
226
+ "<quiet>"
227
+ ]);
228
+ }));
229
+ it.effect("emits nothing when there is nothing to emit, and waits for the next element", () => Effect.gen(function* () {
230
+ const queue = yield* Queue.unbounded();
231
+ const emitted = yield* Ref.make(0);
232
+ const emitIfSecond = Effect.map(Ref.updateAndGet(emitted, (n) => n + 1), (n) => n === 1 ? Option.none() : Option.some("<quiet>"));
233
+ const seen = yield* collecting(Stream.fromQueue(queue).pipe(onQuiet("1 second", emitIfSecond)));
234
+ yield* Queue.offer(queue, "a");
235
+ yield* TestClock.adjust("3 seconds");
236
+ globalExpect(yield* Ref.get(seen)).toEqual(["a"]);
237
+ globalExpect(yield* Ref.get(emitted)).toBe(1);
238
+ yield* TestClock.adjust("10 seconds");
239
+ globalExpect(yield* Ref.get(emitted)).toBe(1);
240
+ yield* Queue.offer(queue, "b");
241
+ yield* TestClock.adjust("3 seconds");
242
+ globalExpect(yield* Ref.get(seen)).toEqual([
243
+ "a",
244
+ "b",
245
+ "<quiet>"
246
+ ]);
247
+ }));
248
+ });
249
+ //#endregion
250
+ export {};
251
+
252
+ //# sourceMappingURL=Settle.test.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Settle.test.mjs","names":[],"sources":["../../src/streaming/Settle.test.ts"],"sourcesContent":["import { describe, it } from \"@effect/vitest\"\nimport { Cause, Effect, Fiber, Option, Queue, Ref, Stream } from \"effect\"\nimport { TestClock } from \"effect/testing\"\nimport { expect } from \"vitest\"\nimport { drainBurst, onQuiet, settleBurst } from \"./Settle.js\"\n\ndescribe(\"drainBurst\", () => {\n it.effect(\"resets the window on every arrival, so a burst outlives one settle\", () =>\n Effect.gen(function* () {\n const queue = yield* Queue.unbounded<string>()\n const burst = yield* Effect.forkChild(drainBurst(queue, \"1 second\"))\n\n yield* Queue.offer(queue, \"a\")\n yield* TestClock.adjust(\"900 millis\")\n yield* Queue.offer(queue, \"b\")\n yield* TestClock.adjust(\"900 millis\")\n // 1.8s elapsed, more than one settle, and the burst is still open.\n expect(burst.pollUnsafe()).toBeUndefined()\n\n yield* TestClock.adjust(\"1 second\")\n expect(yield* Fiber.join(burst)).toEqual([\"a\", \"b\"])\n }),\n )\n\n it.effect(\"waits for the first item without a deadline\", () =>\n Effect.gen(function* () {\n const queue = yield* Queue.unbounded<string>()\n const burst = yield* Effect.forkChild(drainBurst(queue, \"1 second\"))\n\n yield* TestClock.adjust(\"1 hour\")\n expect(burst.pollUnsafe()).toBeUndefined()\n\n yield* Queue.offer(queue, \"late\")\n yield* TestClock.adjust(\"1 second\")\n expect(yield* Fiber.join(burst)).toEqual([\"late\"])\n }),\n )\n\n it.effect(\"ends on the first quiet gap and leaves later items for the next drain\", () =>\n Effect.gen(function* () {\n const queue = yield* Queue.unbounded<string>()\n const burst = yield* Effect.forkChild(drainBurst(queue, \"1 second\"))\n\n yield* Queue.offer(queue, \"first\")\n yield* TestClock.adjust(\"1 second\")\n expect(yield* Fiber.join(burst)).toEqual([\"first\"])\n\n yield* Queue.offer(queue, \"second\")\n expect(yield* Queue.poll(queue)).toEqual(Option.some(\"second\"))\n }),\n )\n\n it.effect(\"yields the batch in hand when the queue ends mid-burst\", () =>\n Effect.gen(function* () {\n const queue = yield* Queue.unbounded<string, Cause.Done>()\n const burst = yield* Effect.forkChild(drainBurst(queue, \"1 second\"))\n\n yield* Queue.offer(queue, \"a\")\n yield* TestClock.adjust(\"100 millis\")\n yield* Queue.end(queue)\n yield* TestClock.adjust(\"10 millis\")\n\n expect(yield* Fiber.join(burst)).toEqual([\"a\"])\n }),\n )\n\n it.effect(\"propagates a queue failure rather than treating it as the end\", () =>\n Effect.gen(function* () {\n const queue = yield* Queue.unbounded<string, string | Cause.Done>()\n const burst = yield* Effect.forkChild(drainBurst(queue, \"1 second\"))\n\n yield* Queue.offer(queue, \"a\")\n yield* TestClock.adjust(\"100 millis\")\n yield* Queue.fail(queue, \"boom\")\n yield* TestClock.adjust(\"10 millis\")\n\n const exit = yield* Effect.exit(Fiber.join(burst))\n expect(exit._tag).toBe(\"Failure\")\n expect(JSON.stringify(exit)).toContain(\"boom\")\n }),\n )\n\n it.effect(\"does not mistake an interrupt mid-burst for the end of the queue\", () =>\n Effect.gen(function* () {\n const queue = yield* Queue.unbounded<string, Cause.Done>()\n const burst = yield* Effect.forkChild(drainBurst(queue, \"1 second\"))\n\n yield* Queue.offer(queue, \"a\")\n yield* TestClock.adjust(\"100 millis\")\n yield* Fiber.interrupt(burst)\n\n // Returning [\"a\"] here would mean interruption silently looked like a\n // settled burst.\n const exit = yield* Effect.exit(Fiber.join(burst))\n expect(exit._tag).toBe(\"Failure\")\n }),\n )\n})\n\ndescribe(\"settleBurst\", () => {\n const collecting = <A>(stream: Stream.Stream<A, never>) =>\n Effect.gen(function* () {\n const seen = yield* Ref.make<ReadonlyArray<A>>([])\n const fiber = yield* Effect.forkChild(\n Stream.runForEach(stream, (a) => Ref.update(seen, (xs) => [...xs, a])),\n )\n return { seen, fiber }\n })\n\n it.effect(\"holds a growing burst back, then emits it as one batch\", () =>\n Effect.gen(function* () {\n const queue = yield* Queue.unbounded<string, Cause.Done>()\n const { seen } = yield* collecting(Stream.fromQueue(queue).pipe(settleBurst(\"1 second\")))\n\n yield* Queue.offer(queue, \"a\")\n yield* TestClock.adjust(\"900 millis\")\n yield* Queue.offer(queue, \"b\")\n yield* TestClock.adjust(\"900 millis\")\n // Each arrival reset the window, so nothing has been emitted yet.\n expect(yield* Ref.get(seen)).toEqual([])\n\n yield* TestClock.adjust(\"1 second\")\n expect(yield* Ref.get(seen)).toEqual([[\"a\", \"b\"]])\n }),\n )\n\n it.effect(\"emits the in-flight batch when the source ends mid-burst\", () =>\n Effect.gen(function* () {\n const queue = yield* Queue.unbounded<string, Cause.Done>()\n const { seen } = yield* collecting(Stream.fromQueue(queue).pipe(settleBurst(\"1 second\")))\n\n yield* Queue.offer(queue, \"only\")\n yield* TestClock.adjust(\"100 millis\")\n yield* Queue.end(queue)\n yield* TestClock.adjust(\"10 millis\")\n\n expect(yield* Ref.get(seen)).toEqual([[\"only\"]])\n }),\n )\n\n it.effect(\"splits bursts on every silence, keeping order across a conversation\", () =>\n Effect.gen(function* () {\n const queue = yield* Queue.unbounded<string, Cause.Done>()\n const { seen } = yield* collecting(Stream.fromQueue(queue).pipe(settleBurst(\"1 second\")))\n\n // Three quick lines, a pause, two more, a pause, then one alone.\n yield* Queue.offer(queue, \"hey\")\n yield* TestClock.adjust(\"100 millis\")\n yield* Queue.offer(queue, \"are you there\")\n yield* TestClock.adjust(\"100 millis\")\n yield* Queue.offer(queue, \"?\")\n yield* TestClock.adjust(\"2 seconds\")\n\n yield* Queue.offer(queue, \"actually\")\n yield* TestClock.adjust(\"100 millis\")\n yield* Queue.offer(queue, \"never mind\")\n yield* TestClock.adjust(\"2 seconds\")\n\n yield* Queue.offer(queue, \"bye\")\n yield* TestClock.adjust(\"2 seconds\")\n\n expect(yield* Ref.get(seen)).toEqual([\n [\"hey\", \"are you there\", \"?\"],\n [\"actually\", \"never mind\"],\n [\"bye\"],\n ])\n }),\n )\n\n it.effect(\"fails the stream when the source fails instead of ending quietly\", () =>\n Effect.gen(function* () {\n const source: Stream.Stream<string, string> = Stream.make(\"a\").pipe(\n Stream.concat(Stream.fail(\"boom\")),\n )\n const exit = yield* Effect.exit(Stream.runCollect(source.pipe(settleBurst(\"1 second\"))))\n\n expect(exit._tag).toBe(\"Failure\")\n expect(JSON.stringify(exit)).toContain(\"boom\")\n }),\n )\n\n it.effect(\"stops pulling the source while the consumer is blocked\", () =>\n Effect.gen(function* () {\n const pulled = yield* Ref.make(0)\n const source = Stream.fromIterable(Array.from({ length: 50 }, (_, i) => i)).pipe(\n Stream.rechunk(1),\n Stream.tap(() => Ref.update(pulled, (n) => n + 1)),\n )\n yield* Effect.forkChild(\n source.pipe(\n settleBurst(\"1 second\", { capacity: 2 }),\n Stream.runForEach(() => Effect.never),\n ),\n )\n yield* TestClock.adjust(\"10 seconds\")\n\n // An unbounded buffer would have drained all 50 into memory by now.\n expect(yield* Ref.get(pulled)).toBeLessThan(50)\n }),\n )\n})\n\ndescribe(\"onQuiet\", () => {\n const mark = Effect.succeed(Option.some(\"<quiet>\"))\n\n const collecting = (stream: Stream.Stream<string, never>) =>\n Effect.gen(function* () {\n const seen = yield* Ref.make<ReadonlyArray<string>>([])\n yield* Effect.forkChild(\n Stream.runForEach(stream, (a) => Ref.update(seen, (xs) => [...xs, a])),\n )\n return seen\n })\n\n it.effect(\"passes elements downstream without waiting for the quiet period\", () =>\n Effect.gen(function* () {\n const queue = yield* Queue.unbounded<string, Cause.Done>()\n const seen = yield* collecting(Stream.fromQueue(queue).pipe(onQuiet(\"1 second\", mark)))\n\n yield* Queue.offer(queue, \"a\")\n yield* TestClock.adjust(\"10 millis\")\n yield* Queue.offer(queue, \"b\")\n yield* TestClock.adjust(\"10 millis\")\n\n // Both are downstream well inside the settle window, and no mark yet.\n expect(yield* Ref.get(seen)).toEqual([\"a\", \"b\"])\n }),\n )\n\n it.effect(\"emits once the source goes quiet, and not again until it speaks\", () =>\n Effect.gen(function* () {\n const queue = yield* Queue.unbounded<string, Cause.Done>()\n const seen = yield* collecting(Stream.fromQueue(queue).pipe(onQuiet(\"1 second\", mark)))\n\n yield* Queue.offer(queue, \"a\")\n yield* TestClock.adjust(\"2 seconds\")\n expect(yield* Ref.get(seen)).toEqual([\"a\", \"<quiet>\"])\n\n // Still silent: the mark does not repeat while nothing arrives.\n yield* TestClock.adjust(\"10 seconds\")\n expect(yield* Ref.get(seen)).toEqual([\"a\", \"<quiet>\"])\n\n // A new element re-arms it.\n yield* Queue.offer(queue, \"b\")\n yield* TestClock.adjust(\"2 seconds\")\n expect(yield* Ref.get(seen)).toEqual([\"a\", \"<quiet>\", \"b\", \"<quiet>\"])\n }),\n )\n\n it.effect(\"marks an element that arrives in the same instant as the mark\", () =>\n Effect.gen(function* () {\n const queue = yield* Queue.unbounded<string, Cause.Done>()\n const seen = yield* collecting(Stream.fromQueue(queue).pipe(onQuiet(\"1 second\", mark)))\n\n yield* Queue.offer(queue, \"a\")\n // The window expires exactly here, so \"b\" races the re-arm.\n yield* TestClock.adjust(\"1 second\")\n yield* Queue.offer(queue, \"b\")\n yield* TestClock.adjust(\"2 seconds\")\n\n // Losing that race would leave \"b\" unmarked until something else spoke.\n expect(yield* Ref.get(seen)).toEqual([\"a\", \"<quiet>\", \"b\", \"<quiet>\"])\n }),\n )\n\n it.effect(\"keeps the window open while elements keep arriving\", () =>\n Effect.gen(function* () {\n const queue = yield* Queue.unbounded<string, Cause.Done>()\n const seen = yield* collecting(Stream.fromQueue(queue).pipe(onQuiet(\"1 second\", mark)))\n\n yield* Queue.offer(queue, \"a\")\n yield* TestClock.adjust(\"900 millis\")\n yield* Queue.offer(queue, \"b\")\n yield* TestClock.adjust(\"900 millis\")\n // 1.8s elapsed, but never 1s of silence.\n expect(yield* Ref.get(seen)).toEqual([\"a\", \"b\"])\n\n yield* TestClock.adjust(\"2 seconds\")\n expect(yield* Ref.get(seen)).toEqual([\"a\", \"b\", \"<quiet>\"])\n }),\n )\n\n it.effect(\"marks each utterance in a run of speech, pauses and more speech\", () =>\n Effect.gen(function* () {\n const queue = yield* Queue.unbounded<string, Cause.Done>()\n const seen = yield* collecting(Stream.fromQueue(queue).pipe(onQuiet(\"1 second\", mark)))\n\n // Deltas of one sentence, arriving faster than the window.\n for (const token of [\"what\", \" about\", \" Paris\"]) {\n yield* Queue.offer(queue, token)\n yield* TestClock.adjust(\"200 millis\")\n }\n expect(yield* Ref.get(seen)).toEqual([\"what\", \" about\", \" Paris\"])\n\n // The speaker stops: one mark, and it does not repeat.\n yield* TestClock.adjust(\"3 seconds\")\n // A second sentence re-arms and marks again.\n for (const token of [\"in\", \" winter\"]) {\n yield* Queue.offer(queue, token)\n yield* TestClock.adjust(\"200 millis\")\n }\n yield* TestClock.adjust(\"3 seconds\")\n\n expect(yield* Ref.get(seen)).toEqual([\n \"what\",\n \" about\",\n \" Paris\",\n \"<quiet>\",\n \"in\",\n \" winter\",\n \"<quiet>\",\n ])\n }),\n )\n\n it.effect(\"emits nothing when there is nothing to emit, and waits for the next element\", () =>\n Effect.gen(function* () {\n const queue = yield* Queue.unbounded<string, Cause.Done>()\n const emitted = yield* Ref.make(0)\n // Stands in for an accumulator that is already empty.\n const emitIfSecond = Effect.map(\n Ref.updateAndGet(emitted, (n) => n + 1),\n (n) => (n === 1 ? Option.none<string>() : Option.some(\"<quiet>\")),\n )\n const seen = yield* collecting(\n Stream.fromQueue(queue).pipe(onQuiet(\"1 second\", emitIfSecond)),\n )\n\n yield* Queue.offer(queue, \"a\")\n yield* TestClock.adjust(\"3 seconds\")\n // First quiet period asked, got None, and stayed silent.\n expect(yield* Ref.get(seen)).toEqual([\"a\"])\n expect(yield* Ref.get(emitted)).toBe(1)\n\n // It is disarmed, so silence alone does not ask again.\n yield* TestClock.adjust(\"10 seconds\")\n expect(yield* Ref.get(emitted)).toBe(1)\n\n yield* Queue.offer(queue, \"b\")\n yield* TestClock.adjust(\"3 seconds\")\n expect(yield* Ref.get(seen)).toEqual([\"a\", \"b\", \"<quiet>\"])\n }),\n )\n})\n"],"mappings":";;;;;;;AAMA,SAAS,oBAAoB;CAC3B,GAAG,OAAO,4EACR,OAAO,IAAI,aAAa;EACtB,MAAM,QAAQ,OAAO,MAAM,UAAkB;EAC7C,MAAM,QAAQ,OAAO,OAAO,UAAU,WAAW,OAAO,UAAU,CAAC;EAEnE,OAAO,MAAM,MAAM,OAAO,GAAG;EAC7B,OAAO,UAAU,OAAO,YAAY;EACpC,OAAO,MAAM,MAAM,OAAO,GAAG;EAC7B,OAAO,UAAU,OAAO,YAAY;EAEpC,aAAO,MAAM,WAAW,CAAC,CAAC,CAAC,cAAc;EAEzC,OAAO,UAAU,OAAO,UAAU;EAClC,aAAO,OAAO,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,GAAG,CAAC;CACrD,CAAC,CACH;CAEA,GAAG,OAAO,qDACR,OAAO,IAAI,aAAa;EACtB,MAAM,QAAQ,OAAO,MAAM,UAAkB;EAC7C,MAAM,QAAQ,OAAO,OAAO,UAAU,WAAW,OAAO,UAAU,CAAC;EAEnE,OAAO,UAAU,OAAO,QAAQ;EAChC,aAAO,MAAM,WAAW,CAAC,CAAC,CAAC,cAAc;EAEzC,OAAO,MAAM,MAAM,OAAO,MAAM;EAChC,OAAO,UAAU,OAAO,UAAU;EAClC,aAAO,OAAO,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC;CACnD,CAAC,CACH;CAEA,GAAG,OAAO,+EACR,OAAO,IAAI,aAAa;EACtB,MAAM,QAAQ,OAAO,MAAM,UAAkB;EAC7C,MAAM,QAAQ,OAAO,OAAO,UAAU,WAAW,OAAO,UAAU,CAAC;EAEnE,OAAO,MAAM,MAAM,OAAO,OAAO;EACjC,OAAO,UAAU,OAAO,UAAU;EAClC,aAAO,OAAO,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC;EAElD,OAAO,MAAM,MAAM,OAAO,QAAQ;EAClC,aAAO,OAAO,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,QAAQ,OAAO,KAAK,QAAQ,CAAC;CAChE,CAAC,CACH;CAEA,GAAG,OAAO,gEACR,OAAO,IAAI,aAAa;EACtB,MAAM,QAAQ,OAAO,MAAM,UAA8B;EACzD,MAAM,QAAQ,OAAO,OAAO,UAAU,WAAW,OAAO,UAAU,CAAC;EAEnE,OAAO,MAAM,MAAM,OAAO,GAAG;EAC7B,OAAO,UAAU,OAAO,YAAY;EACpC,OAAO,MAAM,IAAI,KAAK;EACtB,OAAO,UAAU,OAAO,WAAW;EAEnC,aAAO,OAAO,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC;CAChD,CAAC,CACH;CAEA,GAAG,OAAO,uEACR,OAAO,IAAI,aAAa;EACtB,MAAM,QAAQ,OAAO,MAAM,UAAuC;EAClE,MAAM,QAAQ,OAAO,OAAO,UAAU,WAAW,OAAO,UAAU,CAAC;EAEnE,OAAO,MAAM,MAAM,OAAO,GAAG;EAC7B,OAAO,UAAU,OAAO,YAAY;EACpC,OAAO,MAAM,KAAK,OAAO,MAAM;EAC/B,OAAO,UAAU,OAAO,WAAW;EAEnC,MAAM,OAAO,OAAO,OAAO,KAAK,MAAM,KAAK,KAAK,CAAC;EACjD,aAAO,KAAK,IAAI,CAAC,CAAC,KAAK,SAAS;EAChC,aAAO,KAAK,UAAU,IAAI,CAAC,CAAC,CAAC,UAAU,MAAM;CAC/C,CAAC,CACH;CAEA,GAAG,OAAO,0EACR,OAAO,IAAI,aAAa;EACtB,MAAM,QAAQ,OAAO,MAAM,UAA8B;EACzD,MAAM,QAAQ,OAAO,OAAO,UAAU,WAAW,OAAO,UAAU,CAAC;EAEnE,OAAO,MAAM,MAAM,OAAO,GAAG;EAC7B,OAAO,UAAU,OAAO,YAAY;EACpC,OAAO,MAAM,UAAU,KAAK;EAI5B,MAAM,OAAO,OAAO,OAAO,KAAK,MAAM,KAAK,KAAK,CAAC;EACjD,aAAO,KAAK,IAAI,CAAC,CAAC,KAAK,SAAS;CAClC,CAAC,CACH;AACF,CAAC;AAED,SAAS,qBAAqB;CAC5B,MAAM,cAAiB,WACrB,OAAO,IAAI,aAAa;EACtB,MAAM,OAAO,OAAO,IAAI,KAAuB,CAAC,CAAC;EAIjD,OAAO;GAAE;GAAM,OAAA,OAHM,OAAO,UAC1B,OAAO,WAAW,SAAS,MAAM,IAAI,OAAO,OAAO,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CACvE;EACqB;CACvB,CAAC;CAEH,GAAG,OAAO,gEACR,OAAO,IAAI,aAAa;EACtB,MAAM,QAAQ,OAAO,MAAM,UAA8B;EACzD,MAAM,EAAE,SAAS,OAAO,WAAW,OAAO,UAAU,KAAK,CAAC,CAAC,KAAK,YAAY,UAAU,CAAC,CAAC;EAExF,OAAO,MAAM,MAAM,OAAO,GAAG;EAC7B,OAAO,UAAU,OAAO,YAAY;EACpC,OAAO,MAAM,MAAM,OAAO,GAAG;EAC7B,OAAO,UAAU,OAAO,YAAY;EAEpC,aAAO,OAAO,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;EAEvC,OAAO,UAAU,OAAO,UAAU;EAClC,aAAO,OAAO,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC;CACnD,CAAC,CACH;CAEA,GAAG,OAAO,kEACR,OAAO,IAAI,aAAa;EACtB,MAAM,QAAQ,OAAO,MAAM,UAA8B;EACzD,MAAM,EAAE,SAAS,OAAO,WAAW,OAAO,UAAU,KAAK,CAAC,CAAC,KAAK,YAAY,UAAU,CAAC,CAAC;EAExF,OAAO,MAAM,MAAM,OAAO,MAAM;EAChC,OAAO,UAAU,OAAO,YAAY;EACpC,OAAO,MAAM,IAAI,KAAK;EACtB,OAAO,UAAU,OAAO,WAAW;EAEnC,aAAO,OAAO,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC;CACjD,CAAC,CACH;CAEA,GAAG,OAAO,6EACR,OAAO,IAAI,aAAa;EACtB,MAAM,QAAQ,OAAO,MAAM,UAA8B;EACzD,MAAM,EAAE,SAAS,OAAO,WAAW,OAAO,UAAU,KAAK,CAAC,CAAC,KAAK,YAAY,UAAU,CAAC,CAAC;EAGxF,OAAO,MAAM,MAAM,OAAO,KAAK;EAC/B,OAAO,UAAU,OAAO,YAAY;EACpC,OAAO,MAAM,MAAM,OAAO,eAAe;EACzC,OAAO,UAAU,OAAO,YAAY;EACpC,OAAO,MAAM,MAAM,OAAO,GAAG;EAC7B,OAAO,UAAU,OAAO,WAAW;EAEnC,OAAO,MAAM,MAAM,OAAO,UAAU;EACpC,OAAO,UAAU,OAAO,YAAY;EACpC,OAAO,MAAM,MAAM,OAAO,YAAY;EACtC,OAAO,UAAU,OAAO,WAAW;EAEnC,OAAO,MAAM,MAAM,OAAO,KAAK;EAC/B,OAAO,UAAU,OAAO,WAAW;EAEnC,aAAO,OAAO,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,QAAQ;GACnC;IAAC;IAAO;IAAiB;GAAG;GAC5B,CAAC,YAAY,YAAY;GACzB,CAAC,KAAK;EACR,CAAC;CACH,CAAC,CACH;CAEA,GAAG,OAAO,0EACR,OAAO,IAAI,aAAa;EACtB,MAAM,SAAwC,OAAO,KAAK,GAAG,CAAC,CAAC,KAC7D,OAAO,OAAO,OAAO,KAAK,MAAM,CAAC,CACnC;EACA,MAAM,OAAO,OAAO,OAAO,KAAK,OAAO,WAAW,OAAO,KAAK,YAAY,UAAU,CAAC,CAAC,CAAC;EAEvF,aAAO,KAAK,IAAI,CAAC,CAAC,KAAK,SAAS;EAChC,aAAO,KAAK,UAAU,IAAI,CAAC,CAAC,CAAC,UAAU,MAAM;CAC/C,CAAC,CACH;CAEA,GAAG,OAAO,gEACR,OAAO,IAAI,aAAa;EACtB,MAAM,SAAS,OAAO,IAAI,KAAK,CAAC;EAChC,MAAM,SAAS,OAAO,aAAa,MAAM,KAAK,EAAE,QAAQ,GAAG,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,KAC1E,OAAO,QAAQ,CAAC,GAChB,OAAO,UAAU,IAAI,OAAO,SAAS,MAAM,IAAI,CAAC,CAAC,CACnD;EACA,OAAO,OAAO,UACZ,OAAO,KACL,YAAY,YAAY,EAAE,UAAU,EAAE,CAAC,GACvC,OAAO,iBAAiB,OAAO,KAAK,CACtC,CACF;EACA,OAAO,UAAU,OAAO,YAAY;EAGpC,aAAO,OAAO,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,aAAa,EAAE;CAChD,CAAC,CACH;AACF,CAAC;AAED,SAAS,iBAAiB;CACxB,MAAM,OAAO,OAAO,QAAQ,OAAO,KAAK,SAAS,CAAC;CAElD,MAAM,cAAc,WAClB,OAAO,IAAI,aAAa;EACtB,MAAM,OAAO,OAAO,IAAI,KAA4B,CAAC,CAAC;EACtD,OAAO,OAAO,UACZ,OAAO,WAAW,SAAS,MAAM,IAAI,OAAO,OAAO,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,CACvE;EACA,OAAO;CACT,CAAC;CAEH,GAAG,OAAO,yEACR,OAAO,IAAI,aAAa;EACtB,MAAM,QAAQ,OAAO,MAAM,UAA8B;EACzD,MAAM,OAAO,OAAO,WAAW,OAAO,UAAU,KAAK,CAAC,CAAC,KAAK,QAAQ,YAAY,IAAI,CAAC,CAAC;EAEtF,OAAO,MAAM,MAAM,OAAO,GAAG;EAC7B,OAAO,UAAU,OAAO,WAAW;EACnC,OAAO,MAAM,MAAM,OAAO,GAAG;EAC7B,OAAO,UAAU,OAAO,WAAW;EAGnC,aAAO,OAAO,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,GAAG,CAAC;CACjD,CAAC,CACH;CAEA,GAAG,OAAO,yEACR,OAAO,IAAI,aAAa;EACtB,MAAM,QAAQ,OAAO,MAAM,UAA8B;EACzD,MAAM,OAAO,OAAO,WAAW,OAAO,UAAU,KAAK,CAAC,CAAC,KAAK,QAAQ,YAAY,IAAI,CAAC,CAAC;EAEtF,OAAO,MAAM,MAAM,OAAO,GAAG;EAC7B,OAAO,UAAU,OAAO,WAAW;EACnC,aAAO,OAAO,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,SAAS,CAAC;EAGrD,OAAO,UAAU,OAAO,YAAY;EACpC,aAAO,OAAO,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,SAAS,CAAC;EAGrD,OAAO,MAAM,MAAM,OAAO,GAAG;EAC7B,OAAO,UAAU,OAAO,WAAW;EACnC,aAAO,OAAO,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,QAAQ;GAAC;GAAK;GAAW;GAAK;EAAS,CAAC;CACvE,CAAC,CACH;CAEA,GAAG,OAAO,uEACR,OAAO,IAAI,aAAa;EACtB,MAAM,QAAQ,OAAO,MAAM,UAA8B;EACzD,MAAM,OAAO,OAAO,WAAW,OAAO,UAAU,KAAK,CAAC,CAAC,KAAK,QAAQ,YAAY,IAAI,CAAC,CAAC;EAEtF,OAAO,MAAM,MAAM,OAAO,GAAG;EAE7B,OAAO,UAAU,OAAO,UAAU;EAClC,OAAO,MAAM,MAAM,OAAO,GAAG;EAC7B,OAAO,UAAU,OAAO,WAAW;EAGnC,aAAO,OAAO,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,QAAQ;GAAC;GAAK;GAAW;GAAK;EAAS,CAAC;CACvE,CAAC,CACH;CAEA,GAAG,OAAO,4DACR,OAAO,IAAI,aAAa;EACtB,MAAM,QAAQ,OAAO,MAAM,UAA8B;EACzD,MAAM,OAAO,OAAO,WAAW,OAAO,UAAU,KAAK,CAAC,CAAC,KAAK,QAAQ,YAAY,IAAI,CAAC,CAAC;EAEtF,OAAO,MAAM,MAAM,OAAO,GAAG;EAC7B,OAAO,UAAU,OAAO,YAAY;EACpC,OAAO,MAAM,MAAM,OAAO,GAAG;EAC7B,OAAO,UAAU,OAAO,YAAY;EAEpC,aAAO,OAAO,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,GAAG,CAAC;EAE/C,OAAO,UAAU,OAAO,WAAW;EACnC,aAAO,OAAO,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,QAAQ;GAAC;GAAK;GAAK;EAAS,CAAC;CAC5D,CAAC,CACH;CAEA,GAAG,OAAO,yEACR,OAAO,IAAI,aAAa;EACtB,MAAM,QAAQ,OAAO,MAAM,UAA8B;EACzD,MAAM,OAAO,OAAO,WAAW,OAAO,UAAU,KAAK,CAAC,CAAC,KAAK,QAAQ,YAAY,IAAI,CAAC,CAAC;EAGtF,KAAK,MAAM,SAAS;GAAC;GAAQ;GAAU;EAAQ,GAAG;GAChD,OAAO,MAAM,MAAM,OAAO,KAAK;GAC/B,OAAO,UAAU,OAAO,YAAY;EACtC;EACA,aAAO,OAAO,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,QAAQ;GAAC;GAAQ;GAAU;EAAQ,CAAC;EAGjE,OAAO,UAAU,OAAO,WAAW;EAEnC,KAAK,MAAM,SAAS,CAAC,MAAM,SAAS,GAAG;GACrC,OAAO,MAAM,MAAM,OAAO,KAAK;GAC/B,OAAO,UAAU,OAAO,YAAY;EACtC;EACA,OAAO,UAAU,OAAO,WAAW;EAEnC,aAAO,OAAO,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,QAAQ;GACnC;GACA;GACA;GACA;GACA;GACA;GACA;EACF,CAAC;CACH,CAAC,CACH;CAEA,GAAG,OAAO,qFACR,OAAO,IAAI,aAAa;EACtB,MAAM,QAAQ,OAAO,MAAM,UAA8B;EACzD,MAAM,UAAU,OAAO,IAAI,KAAK,CAAC;EAEjC,MAAM,eAAe,OAAO,IAC1B,IAAI,aAAa,UAAU,MAAM,IAAI,CAAC,IACrC,MAAO,MAAM,IAAI,OAAO,KAAa,IAAI,OAAO,KAAK,SAAS,CACjE;EACA,MAAM,OAAO,OAAO,WAClB,OAAO,UAAU,KAAK,CAAC,CAAC,KAAK,QAAQ,YAAY,YAAY,CAAC,CAChE;EAEA,OAAO,MAAM,MAAM,OAAO,GAAG;EAC7B,OAAO,UAAU,OAAO,WAAW;EAEnC,aAAO,OAAO,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC;EAC1C,aAAO,OAAO,IAAI,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;EAGtC,OAAO,UAAU,OAAO,YAAY;EACpC,aAAO,OAAO,IAAI,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;EAEtC,OAAO,MAAM,MAAM,OAAO,GAAG;EAC7B,OAAO,UAAU,OAAO,WAAW;EACnC,aAAO,OAAO,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,QAAQ;GAAC;GAAK;GAAK;EAAS,CAAC;CAC5D,CAAC,CACH;AACF,CAAC"}
@@ -0,0 +1,36 @@
1
+ import { t as AiError } from "../AiError-DvhLIbmd.mjs";
2
+ import { Cause, Duration, Effect, Queue, Schema, Scope } from "effect";
3
+ import * as Socket from "effect/unstable/socket/Socket";
4
+ declare namespace WebSocketSession_d_exports {
5
+ export { OpenOptions, WebSocketSession, open, redactUrl, toAiError };
6
+ }
7
+ type WebSocketSession<A> = {
8
+ /** Fails `Unavailable` once the socket is closed. */
9
+ readonly send: (frame: string) => Effect.Effect<void, AiError>;
10
+ /** Ends when the socket closes cleanly, fails when it does not. */
11
+ readonly frames: Queue.Dequeue<A, AiError | Cause.Done>;
12
+ };
13
+ type OpenOptions<A, I> = {
14
+ readonly url: string;
15
+ readonly provider: string;
16
+ /** Frames that do not decode are dropped, and logged at debug level. */
17
+ readonly schema: Schema.Codec<A, I>;
18
+ readonly openTimeout?: Duration.Input;
19
+ readonly capacity?: number;
20
+ };
21
+ /**
22
+ * Some wires authenticate with a key in the query string, and some runtimes
23
+ * name the socket URL in their open error (Bun does). Everything taken off a
24
+ * socket error goes through this.
25
+ */
26
+ declare const redactUrl: (text: string) => string;
27
+ declare const toAiError: (provider: string) => (error: Socket.SocketError) => AiError;
28
+ /**
29
+ * Connect for the lifetime of the surrounding `Scope`. The reader runs in a
30
+ * forked fiber; a dirty close reaches the consumer as a failed queue rather
31
+ * than a clean end.
32
+ */
33
+ declare const open: <A, I>(options: OpenOptions<A, I>) => Effect.Effect<WebSocketSession<A>, AiError, Scope.Scope | Socket.WebSocketConstructor>;
34
+ //#endregion
35
+ export { OpenOptions, WebSocketSession, open, redactUrl, WebSocketSession_d_exports as t, toAiError };
36
+ //# sourceMappingURL=WebSocketSession.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebSocketSession.d.mts","names":[],"sources":["../../src/streaming/WebSocketSession.ts"],"mappings":";;;;;;KAuBY,iBAAiB;;WAElB,OAAO,kBAAkB,OAAO,aAAa;;WAE7C,QAAQ,MAAM,QAAQ,GAAG,UAAkB,MAAM;;KAGhD,YAAY,GAAG;WAChB;WACA;;WAEA,QAAQ,OAAO,MAAM,GAAG;WACxB,cAAc,SAAS;WACvB;;;;;;;cAiBE,YAAS;cAeT,YAAS,sBACH,OACT,OAAO,gBAAc;;;;;;cAsBlB,OAAQ,GAAG,GAAC,SACd,YAAY,GAAG,OACvB,OAAO,OAAO,iBAAiB,IAAI,SAAiB,MAAM,QAAQ,OAAO"}
@@ -0,0 +1,89 @@
1
+ import { n as __exportAll } from "../rolldown-runtime-EAsCxrDo.mjs";
2
+ import { AuthFailed, Timeout, Unavailable } from "../domain/AiError.mjs";
3
+ import { parseSafe } from "./JSONL.mjs";
4
+ import { Cause, Effect, Match, Option, Predicate, Queue, Schema } from "effect";
5
+ import * as Socket from "effect/unstable/socket/Socket";
6
+ //#region src/streaming/WebSocketSession.ts
7
+ /**
8
+ * One JSON-over-WebSocket session: connect, read decoded frames off a queue,
9
+ * write frames back.
10
+ *
11
+ * For duplex protocols where the caller drives the conversation frame by
12
+ * frame. The stream-shaped adapters (realtime STT and TTS) still carry their
13
+ * own copies of this.
14
+ */
15
+ var WebSocketSession_exports = /* @__PURE__ */ __exportAll({
16
+ open: () => open,
17
+ redactUrl: () => redactUrl,
18
+ toAiError: () => toAiError
19
+ });
20
+ /**
21
+ * The cause of a failed open is a DOM `ErrorEvent`, not an `Error`, and its
22
+ * `message` is a prototype getter, so a structural decode does not see it.
23
+ */
24
+ const describeCause = (cause) => Predicate.hasProperty(cause, "message") && Predicate.isString(cause.message) ? cause.message : String(cause);
25
+ /**
26
+ * Some wires authenticate with a key in the query string, and some runtimes
27
+ * name the socket URL in their open error (Bun does). Everything taken off a
28
+ * socket error goes through this.
29
+ */
30
+ const redactUrl = (text) => text.replace(/(\b(?:wss?|https?):\/\/[^\s"']*?)\?[^\s"']*/gi, "$1?<redacted>");
31
+ const detailOf = Match.type().pipe(Match.tag("SocketCloseError", (reason) => reason.message), Match.orElse((reason) => describeCause(reason.cause)));
32
+ /** Never the `SocketError` itself: callers print `raw` with `Cause.pretty`. */
33
+ const rawOf = (error) => redactUrl(`${error.reason._tag}: ${detailOf(error.reason)}`);
34
+ /** A rejected WS upgrade surfaces only as prose, so the status is read from it. */
35
+ const isAuthRejection = (text) => /\b40[13]\b/.test(text);
36
+ const toAiError = (provider) => (error) => {
37
+ const raw = rawOf(error);
38
+ return Match.value(error.reason).pipe(Match.tag("SocketOpenError", (reason) => Match.value(reason.kind).pipe(Match.when("Timeout", () => new Timeout({
39
+ provider,
40
+ raw
41
+ })), Match.orElse(() => isAuthRejection(describeCause(reason.cause)) ? new AuthFailed({
42
+ provider,
43
+ subtype: "auth",
44
+ raw
45
+ }) : new Unavailable({
46
+ provider,
47
+ raw
48
+ })))), Match.orElse(() => new Unavailable({
49
+ provider,
50
+ raw
51
+ })));
52
+ };
53
+ /**
54
+ * Connect for the lifetime of the surrounding `Scope`. The reader runs in a
55
+ * forked fiber; a dirty close reaches the consumer as a failed queue rather
56
+ * than a clean end.
57
+ */
58
+ const open = (options) => Effect.gen(function* () {
59
+ const asAiError = toAiError(options.provider);
60
+ const decode = Schema.decodeUnknownEffect(options.schema);
61
+ const socket = yield* Socket.makeWebSocket(options.url, {
62
+ closeCodeIsError: (code) => code !== 1e3 && code !== 1001 && code !== 1005,
63
+ ...options.openTimeout !== void 0 && { openTimeout: options.openTimeout }
64
+ });
65
+ const frames = yield* Queue.bounded(options.capacity ?? 64);
66
+ const write = yield* socket.writer;
67
+ const onFrame = (raw) => Effect.gen(function* () {
68
+ const json = yield* parseSafe(raw);
69
+ if (json === void 0) return;
70
+ const decoded = yield* decode(json).pipe(Effect.option);
71
+ if (Option.isNone(decoded)) {
72
+ yield* Effect.logDebug(`[${options.provider}] dropped frame`, { frame: json });
73
+ return;
74
+ }
75
+ yield* Queue.offer(frames, decoded.value);
76
+ });
77
+ yield* socket.runString(onFrame).pipe(Effect.mapError(asAiError), Effect.matchCauseEffect({
78
+ onFailure: (cause) => Cause.hasFails(cause) ? Queue.failCause(frames, cause) : Queue.end(frames),
79
+ onSuccess: () => Queue.end(frames)
80
+ }), Effect.forkScoped);
81
+ return {
82
+ send: (frame) => write(frame).pipe(Effect.mapError(asAiError)),
83
+ frames
84
+ };
85
+ });
86
+ //#endregion
87
+ export { open, redactUrl, WebSocketSession_exports as t, toAiError };
88
+
89
+ //# sourceMappingURL=WebSocketSession.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebSocketSession.mjs","names":["AiError.Timeout","AiError.AuthFailed","AiError.Unavailable","JSONL.parseSafe"],"sources":["../../src/streaming/WebSocketSession.ts"],"sourcesContent":["/**\n * One JSON-over-WebSocket session: connect, read decoded frames off a queue,\n * write frames back.\n *\n * For duplex protocols where the caller drives the conversation frame by\n * frame. The stream-shaped adapters (realtime STT and TTS) still carry their\n * own copies of this.\n */\nimport {\n Cause,\n type Duration,\n Effect,\n Match,\n Option,\n Predicate,\n Queue,\n Schema,\n type Scope,\n} from \"effect\"\nimport * as Socket from \"effect/unstable/socket/Socket\"\nimport * as AiError from \"../domain/AiError.js\"\nimport * as JSONL from \"./JSONL.js\"\n\nexport type WebSocketSession<A> = {\n /** Fails `Unavailable` once the socket is closed. */\n readonly send: (frame: string) => Effect.Effect<void, AiError.AiError>\n /** Ends when the socket closes cleanly, fails when it does not. */\n readonly frames: Queue.Dequeue<A, AiError.AiError | Cause.Done>\n}\n\nexport type OpenOptions<A, I> = {\n readonly url: string\n readonly provider: string\n /** Frames that do not decode are dropped, and logged at debug level. */\n readonly schema: Schema.Codec<A, I>\n readonly openTimeout?: Duration.Input\n readonly capacity?: number\n}\n\n/**\n * The cause of a failed open is a DOM `ErrorEvent`, not an `Error`, and its\n * `message` is a prototype getter, so a structural decode does not see it.\n */\nconst describeCause = (cause: unknown): string =>\n Predicate.hasProperty(cause, \"message\") && Predicate.isString(cause.message)\n ? cause.message\n : String(cause)\n\n/**\n * Some wires authenticate with a key in the query string, and some runtimes\n * name the socket URL in their open error (Bun does). Everything taken off a\n * socket error goes through this.\n */\nexport const redactUrl = (text: string): string =>\n text.replace(/(\\b(?:wss?|https?):\\/\\/[^\\s\"']*?)\\?[^\\s\"']*/gi, \"$1?<redacted>\")\n\nconst detailOf = Match.type<Socket.SocketError[\"reason\"]>().pipe(\n Match.tag(\"SocketCloseError\", (reason) => reason.message),\n Match.orElse((reason) => describeCause(reason.cause)),\n)\n\n/** Never the `SocketError` itself: callers print `raw` with `Cause.pretty`. */\nconst rawOf = (error: Socket.SocketError): string =>\n redactUrl(`${error.reason._tag}: ${detailOf(error.reason)}`)\n\n/** A rejected WS upgrade surfaces only as prose, so the status is read from it. */\nconst isAuthRejection = (text: string): boolean => /\\b40[13]\\b/.test(text)\n\nexport const toAiError =\n (provider: string) =>\n (error: Socket.SocketError): AiError.AiError => {\n const raw = rawOf(error)\n return Match.value(error.reason).pipe(\n Match.tag(\"SocketOpenError\", (reason) =>\n Match.value(reason.kind).pipe(\n Match.when(\"Timeout\", (): AiError.AiError => new AiError.Timeout({ provider, raw })),\n Match.orElse((): AiError.AiError =>\n isAuthRejection(describeCause(reason.cause))\n ? new AiError.AuthFailed({ provider, subtype: \"auth\", raw })\n : new AiError.Unavailable({ provider, raw }),\n ),\n ),\n ),\n Match.orElse((): AiError.AiError => new AiError.Unavailable({ provider, raw })),\n )\n }\n\n/**\n * Connect for the lifetime of the surrounding `Scope`. The reader runs in a\n * forked fiber; a dirty close reaches the consumer as a failed queue rather\n * than a clean end.\n */\nexport const open = <A, I>(\n options: OpenOptions<A, I>,\n): Effect.Effect<WebSocketSession<A>, AiError.AiError, Scope.Scope | Socket.WebSocketConstructor> =>\n Effect.gen(function* () {\n const asAiError = toAiError(options.provider)\n const decode = Schema.decodeUnknownEffect(options.schema)\n const socket = yield* Socket.makeWebSocket(options.url, {\n // Effect's Socket treats every close code as an error by default.\n closeCodeIsError: (code) => code !== 1000 && code !== 1001 && code !== 1005,\n ...(options.openTimeout !== undefined && { openTimeout: options.openTimeout }),\n })\n const frames = yield* Queue.bounded<A, AiError.AiError | Cause.Done>(options.capacity ?? 64)\n const write = yield* socket.writer\n\n const onFrame = (raw: string) =>\n Effect.gen(function* () {\n const json = yield* JSONL.parseSafe(raw)\n if (json === undefined) return\n const decoded = yield* decode(json).pipe(Effect.option)\n if (Option.isNone(decoded)) {\n yield* Effect.logDebug(`[${options.provider}] dropped frame`, { frame: json })\n return\n }\n yield* Queue.offer(frames, decoded.value)\n })\n\n yield* socket.runString(onFrame).pipe(\n Effect.mapError(asAiError),\n // `Queue.end` on a clean close so the consumer drains what is queued;\n // a failure must not arrive looking like the end of the conversation.\n Effect.matchCauseEffect({\n onFailure: (cause) =>\n Cause.hasFails(cause) ? Queue.failCause(frames, cause) : Queue.end(frames),\n onSuccess: () => Queue.end(frames),\n }),\n Effect.forkScoped,\n )\n\n return { send: (frame: string) => write(frame).pipe(Effect.mapError(asAiError)), frames }\n })\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AA2CA,MAAM,iBAAiB,UACrB,UAAU,YAAY,OAAO,SAAS,KAAK,UAAU,SAAS,MAAM,OAAO,IACvE,MAAM,UACN,OAAO,KAAK;;;;;;AAOlB,MAAa,aAAa,SACxB,KAAK,QAAQ,iDAAiD,eAAe;AAE/E,MAAM,WAAW,MAAM,KAAmC,CAAC,CAAC,KAC1D,MAAM,IAAI,qBAAqB,WAAW,OAAO,OAAO,GACxD,MAAM,QAAQ,WAAW,cAAc,OAAO,KAAK,CAAC,CACtD;;AAGA,MAAM,SAAS,UACb,UAAU,GAAG,MAAM,OAAO,KAAK,IAAI,SAAS,MAAM,MAAM,GAAG;;AAG7D,MAAM,mBAAmB,SAA0B,aAAa,KAAK,IAAI;AAEzE,MAAa,aACV,cACA,UAA+C;CAC9C,MAAM,MAAM,MAAM,KAAK;CACvB,OAAO,MAAM,MAAM,MAAM,MAAM,CAAC,CAAC,KAC/B,MAAM,IAAI,oBAAoB,WAC5B,MAAM,MAAM,OAAO,IAAI,CAAC,CAAC,KACvB,MAAM,KAAK,iBAAkC,IAAIA,QAAgB;EAAE;EAAU;CAAI,CAAC,CAAC,GACnF,MAAM,aACJ,gBAAgB,cAAc,OAAO,KAAK,CAAC,IACvC,IAAIC,WAAmB;EAAE;EAAU,SAAS;EAAQ;CAAI,CAAC,IACzD,IAAIC,YAAoB;EAAE;EAAU;CAAI,CAAC,CAC/C,CACF,CACF,GACA,MAAM,aAA8B,IAAIA,YAAoB;EAAE;EAAU;CAAI,CAAC,CAAC,CAChF;AACF;;;;;;AAOF,MAAa,QACX,YAEA,OAAO,IAAI,aAAa;CACtB,MAAM,YAAY,UAAU,QAAQ,QAAQ;CAC5C,MAAM,SAAS,OAAO,oBAAoB,QAAQ,MAAM;CACxD,MAAM,SAAS,OAAO,OAAO,cAAc,QAAQ,KAAK;EAEtD,mBAAmB,SAAS,SAAS,OAAQ,SAAS,QAAQ,SAAS;EACvE,GAAI,QAAQ,gBAAgB,KAAA,KAAa,EAAE,aAAa,QAAQ,YAAY;CAC9E,CAAC;CACD,MAAM,SAAS,OAAO,MAAM,QAAyC,QAAQ,YAAY,EAAE;CAC3F,MAAM,QAAQ,OAAO,OAAO;CAE5B,MAAM,WAAW,QACf,OAAO,IAAI,aAAa;EACtB,MAAM,OAAO,OAAOC,UAAgB,GAAG;EACvC,IAAI,SAAS,KAAA,GAAW;EACxB,MAAM,UAAU,OAAO,OAAO,IAAI,CAAC,CAAC,KAAK,OAAO,MAAM;EACtD,IAAI,OAAO,OAAO,OAAO,GAAG;GAC1B,OAAO,OAAO,SAAS,IAAI,QAAQ,SAAS,kBAAkB,EAAE,OAAO,KAAK,CAAC;GAC7E;EACF;EACA,OAAO,MAAM,MAAM,QAAQ,QAAQ,KAAK;CAC1C,CAAC;CAEH,OAAO,OAAO,UAAU,OAAO,CAAC,CAAC,KAC/B,OAAO,SAAS,SAAS,GAGzB,OAAO,iBAAiB;EACtB,YAAY,UACV,MAAM,SAAS,KAAK,IAAI,MAAM,UAAU,QAAQ,KAAK,IAAI,MAAM,IAAI,MAAM;EAC3E,iBAAiB,MAAM,IAAI,MAAM;CACnC,CAAC,GACD,OAAO,UACT;CAEA,OAAO;EAAE,OAAO,UAAkB,MAAM,KAAK,CAAC,CAAC,KAAK,OAAO,SAAS,SAAS,CAAC;EAAG;CAAO;AAC1F,CAAC"}
@@ -0,0 +1 @@
1
+ export {}