@effect-uai/core 0.15.0 → 0.17.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 (114) 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/decision-model/Decision.d.mts +155 -0
  10. package/dist/decision-model/Decision.d.mts.map +1 -0
  11. package/dist/decision-model/Decision.mjs +118 -0
  12. package/dist/decision-model/Decision.mjs.map +1 -0
  13. package/dist/decision-model/Decision.test.mjs +164 -0
  14. package/dist/decision-model/Decision.test.mjs.map +1 -0
  15. package/dist/decision-model/DecisionModel.d.mts +45 -0
  16. package/dist/decision-model/DecisionModel.d.mts.map +1 -0
  17. package/dist/decision-model/DecisionModel.mjs +25 -0
  18. package/dist/decision-model/DecisionModel.mjs.map +1 -0
  19. package/dist/domain/AiError.d.mts +2 -2
  20. package/dist/domain/AiError.mjs +9 -2
  21. package/dist/domain/AiError.mjs.map +1 -1
  22. package/dist/domain/Realtime.d.mts +616 -0
  23. package/dist/domain/Realtime.d.mts.map +1 -0
  24. package/dist/domain/Realtime.mjs +21 -0
  25. package/dist/domain/Realtime.mjs.map +1 -0
  26. package/dist/domain/Transcript.d.mts +17 -3
  27. package/dist/domain/Transcript.d.mts.map +1 -1
  28. package/dist/domain/Transcript.mjs +30 -1
  29. package/dist/domain/Transcript.mjs.map +1 -1
  30. package/dist/domain/Transcript.test.d.mts +1 -0
  31. package/dist/domain/Transcript.test.mjs +110 -0
  32. package/dist/domain/Transcript.test.mjs.map +1 -0
  33. package/dist/embedding-model/EmbeddingModel.d.mts +1 -1
  34. package/dist/image-generator/ImageGenerator.d.mts +1 -1
  35. package/dist/index.d.mts +11 -5
  36. package/dist/index.mjs +9 -3
  37. package/dist/job/Job.d.mts +1 -1
  38. package/dist/language-model/LanguageModel.d.mts +2 -2
  39. package/dist/loop/Loop.d.mts +1 -1
  40. package/dist/math/Probability.d.mts +21 -0
  41. package/dist/math/Probability.d.mts.map +1 -0
  42. package/dist/math/Probability.mjs +31 -0
  43. package/dist/math/Probability.mjs.map +1 -0
  44. package/dist/math/Probability.test.d.mts +1 -0
  45. package/dist/math/Probability.test.mjs +95 -0
  46. package/dist/math/Probability.test.mjs.map +1 -0
  47. package/dist/music-generator/MusicGenerator.d.mts +1 -1
  48. package/dist/realtime/RealtimeSession.d.mts +50 -0
  49. package/dist/realtime/RealtimeSession.d.mts.map +1 -0
  50. package/dist/realtime/RealtimeSession.mjs +45 -0
  51. package/dist/realtime/RealtimeSession.mjs.map +1 -0
  52. package/dist/realtime/RealtimeSession.test.d.mts +1 -0
  53. package/dist/realtime/RealtimeSession.test.mjs +95 -0
  54. package/dist/realtime/RealtimeSession.test.mjs.map +1 -0
  55. package/dist/reranker/Reranker.d.mts +1 -1
  56. package/dist/research/DeepResearch.d.mts +1 -1
  57. package/dist/retry/Retry.d.mts +1 -1
  58. package/dist/speech-synthesizer/SpeechSynthesizer.d.mts +1 -1
  59. package/dist/streaming/Settle.d.mts +45 -0
  60. package/dist/streaming/Settle.d.mts.map +1 -0
  61. package/dist/streaming/Settle.mjs +95 -0
  62. package/dist/streaming/Settle.mjs.map +1 -0
  63. package/dist/streaming/Settle.test.d.mts +1 -0
  64. package/dist/streaming/Settle.test.mjs +252 -0
  65. package/dist/streaming/Settle.test.mjs.map +1 -0
  66. package/dist/streaming/WebSocketSession.d.mts +36 -0
  67. package/dist/streaming/WebSocketSession.d.mts.map +1 -0
  68. package/dist/streaming/WebSocketSession.mjs +89 -0
  69. package/dist/streaming/WebSocketSession.mjs.map +1 -0
  70. package/dist/streaming/WebSocketSession.test.d.mts +1 -0
  71. package/dist/streaming/WebSocketSession.test.mjs +51 -0
  72. package/dist/streaming/WebSocketSession.test.mjs.map +1 -0
  73. package/dist/testing/FakeWebSocket.d.mts +25 -0
  74. package/dist/testing/FakeWebSocket.d.mts.map +1 -0
  75. package/dist/testing/FakeWebSocket.mjs +79 -0
  76. package/dist/testing/FakeWebSocket.mjs.map +1 -0
  77. package/dist/testing/MockRealtimeSession.d.mts +30 -0
  78. package/dist/testing/MockRealtimeSession.d.mts.map +1 -0
  79. package/dist/testing/MockRealtimeSession.mjs +52 -0
  80. package/dist/testing/MockRealtimeSession.mjs.map +1 -0
  81. package/dist/tool/Toolkit.d.mts +1 -1
  82. package/dist/transcriber/Transcriber.d.mts +1 -1
  83. package/dist/web-read/WebRead.d.mts +1 -1
  84. package/dist/web-read/WebReadTool.d.mts +1 -1
  85. package/dist/web-search/WebSearch.d.mts +1 -1
  86. package/dist/web-search/WebSearchTool.d.mts +1 -1
  87. package/package.json +36 -4
  88. package/src/decision-model/Decision.test.ts +146 -0
  89. package/src/decision-model/Decision.ts +273 -0
  90. package/src/decision-model/DecisionModel.ts +53 -0
  91. package/src/domain/AiError.ts +11 -0
  92. package/src/domain/Realtime.ts +101 -0
  93. package/src/domain/Transcript.test.ts +98 -0
  94. package/src/domain/Transcript.ts +53 -1
  95. package/src/index.ts +7 -1
  96. package/src/math/Probability.test.ts +61 -0
  97. package/src/math/Probability.ts +44 -0
  98. package/src/realtime/RealtimeSession.test.ts +100 -0
  99. package/src/realtime/RealtimeSession.ts +77 -0
  100. package/src/streaming/Settle.test.ts +344 -0
  101. package/src/streaming/Settle.ts +196 -0
  102. package/src/streaming/WebSocketSession.test.ts +52 -0
  103. package/src/streaming/WebSocketSession.ts +132 -0
  104. package/src/testing/FakeWebSocket.ts +120 -0
  105. package/src/testing/MockRealtimeSession.ts +92 -0
  106. package/dist/inbox/Inbox.d.mts +0 -16
  107. package/dist/inbox/Inbox.d.mts.map +0 -1
  108. package/dist/inbox/Inbox.mjs +0 -28
  109. package/dist/inbox/Inbox.mjs.map +0 -1
  110. package/dist/inbox/Inbox.test.mjs +0 -42
  111. package/dist/inbox/Inbox.test.mjs.map +0 -1
  112. package/src/inbox/Inbox.test.ts +0 -52
  113. package/src/inbox/Inbox.ts +0 -33
  114. /package/dist/{inbox/Inbox.test.d.mts → decision-model/Decision.test.d.mts} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RealtimeSession.mjs","names":[],"sources":["../../src/realtime/RealtimeSession.ts"],"sourcesContent":["/**\n * A duplex speech-to-speech session. The server drives turn-taking, so this is\n * its own primitive rather than a variant of `Loop`.\n *\n * The session is deliberately low level: tool execution, playback, barge-in\n * bookkeeping and reconnection are the caller's, built from `send` and\n * `events`. There is no agent wrapper, no hidden queue and no automatic\n * reconnect here.\n */\nimport { Context, Effect, type Scope, Stream } from \"effect\"\nimport type * as AiError from \"../domain/AiError.js\"\nimport type { ImageSource } from \"../domain/Image.js\"\nimport { type CommonSessionRequest, RealtimeInput, RealtimeEvent } from \"../domain/Realtime.js\"\n\n/**\n * The live connection. `events` ends when the socket closes cleanly; a close\n * during a response ends it with `AiError.IncompleteTurn` instead, and never\n * synthesizes a `ResponseDone`. `send` after close fails `Unavailable`.\n */\nexport type RealtimeSessionHandle = {\n readonly send: (input: RealtimeInput) => Effect.Effect<void, AiError.AiError>\n readonly events: Stream.Stream<RealtimeEvent, AiError.AiError>\n}\n\nexport type RealtimeSessionService = {\n /**\n * Connect and complete the provider handshake before succeeding. Closing the\n * `Scope` closes the socket.\n */\n readonly open: (\n request: CommonSessionRequest,\n ) => Effect.Effect<RealtimeSessionHandle, AiError.AiError, Scope.Scope>\n}\n\nexport class RealtimeSession extends Context.Service<RealtimeSession, RealtimeSessionService>()(\n \"@betalyra/effect-uai/RealtimeSession\",\n) {}\n\n/**\n * Capability marker for sessions that accept video frames. Phantom: providers\n * register with `Layer.succeed(RealtimeVideoInput, undefined)`. Only Gemini\n * ships it, so `sendVideoFrame` against an OpenAI-only Layer is a type error.\n */\nexport class RealtimeVideoInput extends Context.Service<RealtimeVideoInput, void>()(\n \"@betalyra/effect-uai/capability/RealtimeVideoInput\",\n) {}\n\n/** Open a session for the lifetime of the surrounding `Scope`. */\nexport const open = (\n request: CommonSessionRequest,\n): Effect.Effect<RealtimeSessionHandle, AiError.AiError, RealtimeSession | Scope.Scope> =>\n Effect.flatMap(RealtimeSession, (s) => s.open(request))\n\n/**\n * Push one video frame. Requires `RealtimeVideoInput`, so a provider without\n * video fails at `Effect.provide` rather than at runtime.\n */\nexport const sendVideoFrame = (\n handle: RealtimeSessionHandle,\n frame: ImageSource,\n): Effect.Effect<void, AiError.AiError, RealtimeVideoInput> =>\n Effect.flatMap(RealtimeVideoInput, () => handle.send(RealtimeInput.VideoFrame({ frame })))\n\nconst isAudioDelta = RealtimeEvent.$is(\"AudioDelta\")\nconst isToolCall = RealtimeEvent.$is(\"ToolCall\")\n\n/** Just the audio, for wiring straight to playback. */\nexport const audioDeltas = <E, R>(\n events: Stream.Stream<RealtimeEvent, E, R>,\n): Stream.Stream<Extract<RealtimeEvent, { readonly _tag: \"AudioDelta\" }>, E, R> =>\n Stream.filter(events, isAudioDelta)\n\n/** Just the tool calls. Answer each with a `ToolResult`; the adapter resumes. */\nexport const toolCalls = <E, R>(\n events: Stream.Stream<RealtimeEvent, E, R>,\n): Stream.Stream<Extract<RealtimeEvent, { readonly _tag: \"ToolCall\" }>, E, R> =>\n Stream.filter(events, isToolCall)\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAkCA,IAAa,kBAAb,cAAqC,QAAQ,QAAiD,CAAC,CAC7F,sCACF,CAAC,CAAC,CAAC;;;;;;AAOH,IAAa,qBAAb,cAAwC,QAAQ,QAAkC,CAAC,CACjF,oDACF,CAAC,CAAC,CAAC;;AAGH,MAAa,QACX,YAEA,OAAO,QAAQ,kBAAkB,MAAM,EAAE,KAAK,OAAO,CAAC;;;;;AAMxD,MAAa,kBACX,QACA,UAEA,OAAO,QAAQ,0BAA0B,OAAO,KAAK,cAAc,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC;AAE3F,MAAM,eAAe,cAAc,IAAI,YAAY;AACnD,MAAM,aAAa,cAAc,IAAI,UAAU;;AAG/C,MAAa,eACX,WAEA,OAAO,OAAO,QAAQ,YAAY;;AAGpC,MAAa,aACX,WAEA,OAAO,OAAO,QAAQ,UAAU"}
@@ -0,0 +1 @@
1
+ export {}
@@ -0,0 +1,95 @@
1
+ import { imageBase64 } from "../domain/Image.mjs";
2
+ import { RealtimeEvent, RealtimeInput } from "../domain/Realtime.mjs";
3
+ import { open, sendVideoFrame, toolCalls } from "./RealtimeSession.mjs";
4
+ import { o as describe } from "../utils-CiH_DjFW.mjs";
5
+ import { n as globalExpect } from "../test.DNmyFkvJ-YPzDR0Co.mjs";
6
+ import { t as import_dist } from "../dist-BaenoU1A.mjs";
7
+ import { t as it } from "../dist-nohcnkD6.mjs";
8
+ import { layer, layerAudioOnly } from "../testing/MockRealtimeSession.mjs";
9
+ import { Effect, Stream } from "effect";
10
+ //#region src/realtime/RealtimeSession.test.ts
11
+ const pcm = {
12
+ container: "raw",
13
+ encoding: "pcm_s16le",
14
+ sampleRate: 24e3,
15
+ channels: 1
16
+ };
17
+ const request = {
18
+ model: "gpt-realtime-2.1",
19
+ inputFormat: pcm,
20
+ outputFormat: pcm
21
+ };
22
+ const call = {
23
+ type: "function_call",
24
+ call_id: "call_1",
25
+ name: "get_time",
26
+ arguments: "{}"
27
+ };
28
+ describe("RealtimeSession against a scripted session", () => {
29
+ it.effect("answers each input and ends `events` when the scope closes", () => Effect.gen(function* () {
30
+ const mock = layer({
31
+ initial: [RealtimeEvent.ResponseStarted({ responseId: "r1" })],
32
+ onInput: (input) => RealtimeInput.$is("Audio")(input) ? [RealtimeEvent.AudioDelta({
33
+ responseId: "r1",
34
+ bytes: new Uint8Array([7])
35
+ })] : [RealtimeEvent.ResponseDone({
36
+ responseId: "r1",
37
+ reason: "interrupted"
38
+ })]
39
+ });
40
+ const handle = yield* Effect.scoped(Effect.gen(function* () {
41
+ const handle = yield* open(request);
42
+ yield* handle.send(RealtimeInput.Audio({ bytes: new Uint8Array([1, 2]) }));
43
+ yield* handle.send(RealtimeInput.Interrupt());
44
+ return handle;
45
+ })).pipe(Effect.provide(mock.layer));
46
+ const events = yield* Stream.runCollect(handle.events);
47
+ globalExpect(events).toEqual([
48
+ RealtimeEvent.ResponseStarted({ responseId: "r1" }),
49
+ RealtimeEvent.AudioDelta({
50
+ responseId: "r1",
51
+ bytes: new Uint8Array([7])
52
+ }),
53
+ RealtimeEvent.ResponseDone({
54
+ responseId: "r1",
55
+ reason: "interrupted"
56
+ })
57
+ ]);
58
+ const recorder = yield* mock.recorder;
59
+ globalExpect(recorder.openCalls.map((r) => r.model)).toEqual(["gpt-realtime-2.1"]);
60
+ globalExpect(recorder.sent.map((i) => i._tag)).toEqual(["Audio", "Interrupt"]);
61
+ }));
62
+ it.effect("narrows `toolCalls` to the calls the model made", () => Effect.gen(function* () {
63
+ const mock = layer({ initial: [
64
+ RealtimeEvent.ResponseStarted({ responseId: "r1" }),
65
+ RealtimeEvent.ToolCall({
66
+ responseId: "r1",
67
+ call
68
+ }),
69
+ RealtimeEvent.OutputTranscriptDelta({
70
+ responseId: "r1",
71
+ text: "one moment"
72
+ })
73
+ ] });
74
+ const handle = yield* Effect.scoped(open(request)).pipe(Effect.provide(mock.layer));
75
+ const calls = yield* Stream.runCollect(toolCalls(handle.events));
76
+ globalExpect(calls.map((e) => e.call.call_id)).toEqual(["call_1"]);
77
+ }));
78
+ });
79
+ describe("RealtimeVideoInput marker", () => {
80
+ const frame = imageBase64("aW1n", "image/jpeg");
81
+ it("leaves the marker unsatisfied against an audio-only Layer", () => {
82
+ const mock = layerAudioOnly({});
83
+ const program = Effect.scoped(Effect.flatMap(open(request), (handle) => sendVideoFrame(handle, frame))).pipe(Effect.provide(mock.layer));
84
+ (0, import_dist.expectTypeOf)(program).toEqualTypeOf();
85
+ });
86
+ it("clears R to never against a Layer that ships the marker", () => {
87
+ const mock = layer({});
88
+ const program = Effect.scoped(Effect.flatMap(open(request), (handle) => sendVideoFrame(handle, frame))).pipe(Effect.provide(mock.layer));
89
+ (0, import_dist.expectTypeOf)(program).toEqualTypeOf();
90
+ });
91
+ });
92
+ //#endregion
93
+ export {};
94
+
95
+ //# sourceMappingURL=RealtimeSession.test.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RealtimeSession.test.mjs","names":["MockRealtimeSession.layer","MockRealtimeSession.layerAudioOnly"],"sources":["../../src/realtime/RealtimeSession.test.ts"],"sourcesContent":["import { describe, it } from \"@effect/vitest\"\nimport { Effect, Stream } from \"effect\"\nimport { expect, expectTypeOf } from \"vitest\"\nimport type * as AiError from \"../domain/AiError.js\"\nimport { imageBase64 } from \"../domain/Image.js\"\nimport { type CommonSessionRequest, RealtimeEvent, RealtimeInput } from \"../domain/Realtime.js\"\nimport * as MockRealtimeSession from \"../testing/MockRealtimeSession.js\"\nimport { RealtimeVideoInput, open, sendVideoFrame, toolCalls } from \"./RealtimeSession.js\"\n\nconst pcm = { container: \"raw\", encoding: \"pcm_s16le\", sampleRate: 24000, channels: 1 } as const\n\nconst request: CommonSessionRequest = {\n model: \"gpt-realtime-2.1\",\n inputFormat: pcm,\n outputFormat: pcm,\n}\n\nconst call = {\n type: \"function_call\",\n call_id: \"call_1\",\n name: \"get_time\",\n arguments: \"{}\",\n} as const\n\ndescribe(\"RealtimeSession against a scripted session\", () => {\n it.effect(\"answers each input and ends `events` when the scope closes\", () =>\n Effect.gen(function* () {\n const mock = MockRealtimeSession.layer({\n initial: [RealtimeEvent.ResponseStarted({ responseId: \"r1\" })],\n onInput: (input) =>\n RealtimeInput.$is(\"Audio\")(input)\n ? [RealtimeEvent.AudioDelta({ responseId: \"r1\", bytes: new Uint8Array([7]) })]\n : [RealtimeEvent.ResponseDone({ responseId: \"r1\", reason: \"interrupted\" })],\n })\n\n const handle = yield* Effect.scoped(\n Effect.gen(function* () {\n const handle = yield* open(request)\n yield* handle.send(RealtimeInput.Audio({ bytes: new Uint8Array([1, 2]) }))\n yield* handle.send(RealtimeInput.Interrupt())\n return handle\n }),\n ).pipe(Effect.provide(mock.layer))\n\n // The scope is closed, so the stream drains what is queued and ends\n // rather than hanging.\n const events = yield* Stream.runCollect(handle.events)\n expect(events).toEqual([\n RealtimeEvent.ResponseStarted({ responseId: \"r1\" }),\n RealtimeEvent.AudioDelta({ responseId: \"r1\", bytes: new Uint8Array([7]) }),\n RealtimeEvent.ResponseDone({ responseId: \"r1\", reason: \"interrupted\" }),\n ])\n\n const recorder = yield* mock.recorder\n expect(recorder.openCalls.map((r) => r.model)).toEqual([\"gpt-realtime-2.1\"])\n expect(recorder.sent.map((i) => i._tag)).toEqual([\"Audio\", \"Interrupt\"])\n }),\n )\n\n it.effect(\"narrows `toolCalls` to the calls the model made\", () =>\n Effect.gen(function* () {\n const mock = MockRealtimeSession.layer({\n initial: [\n RealtimeEvent.ResponseStarted({ responseId: \"r1\" }),\n RealtimeEvent.ToolCall({ responseId: \"r1\", call }),\n RealtimeEvent.OutputTranscriptDelta({ responseId: \"r1\", text: \"one moment\" }),\n ],\n })\n\n const handle = yield* Effect.scoped(open(request)).pipe(Effect.provide(mock.layer))\n const calls = yield* Stream.runCollect(toolCalls(handle.events))\n\n expect(calls.map((e) => e.call.call_id)).toEqual([\"call_1\"])\n }),\n )\n})\n\ndescribe(\"RealtimeVideoInput marker\", () => {\n const frame = imageBase64(\"aW1n\", \"image/jpeg\")\n\n it(\"leaves the marker unsatisfied against an audio-only Layer\", () => {\n const mock = MockRealtimeSession.layerAudioOnly({})\n const program = Effect.scoped(\n Effect.flatMap(open(request), (handle) => sendVideoFrame(handle, frame)),\n ).pipe(Effect.provide(mock.layer))\n\n // `RealtimeSession` is provided, `RealtimeVideoInput` is not, so running\n // this would be a type error.\n expectTypeOf(program).toEqualTypeOf<Effect.Effect<void, AiError.AiError, RealtimeVideoInput>>()\n })\n\n it(\"clears R to never against a Layer that ships the marker\", () => {\n const mock = MockRealtimeSession.layer({})\n const program = Effect.scoped(\n Effect.flatMap(open(request), (handle) => sendVideoFrame(handle, frame)),\n ).pipe(Effect.provide(mock.layer))\n\n expectTypeOf(program).toEqualTypeOf<Effect.Effect<void, AiError.AiError, never>>()\n })\n})\n"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { t as AiError } from "../AiError-eLV9t1lU.mjs";
1
+ import { t as AiError } from "../AiError-DvhLIbmd.mjs";
2
2
  import { Context, Effect } from "effect";
3
3
  declare namespace Reranker_d_exports {
4
4
  export { CommonRerankRequest, RerankResponse, RerankResult, RerankUsage, Reranker, RerankerService, rerank };
@@ -1,4 +1,4 @@
1
- import { t as AiError } from "../AiError-eLV9t1lU.mjs";
1
+ import { t as AiError } from "../AiError-DvhLIbmd.mjs";
2
2
  import { JobConfig } from "../job/Job.mjs";
3
3
  import { Turn, TurnEvent } from "../domain/Turn.mjs";
4
4
  import { ResearchJobRef, ResearchRequest, ResearchState } from "../domain/Research.mjs";
@@ -1,4 +1,4 @@
1
- import { d as RateLimited, m as Unavailable, p as Timeout, t as AiError, u as InvalidRequest } from "../AiError-eLV9t1lU.mjs";
1
+ import { d as RateLimited, h as Unavailable, m as Timeout, t as AiError, u as InvalidRequest } from "../AiError-DvhLIbmd.mjs";
2
2
  import { Effect, Schedule, Stream } from "effect";
3
3
  declare namespace Retry_d_exports {
4
4
  export { Retryable, effect, isRetryable, stream };
@@ -1,4 +1,4 @@
1
- import { t as AiError } from "../AiError-eLV9t1lU.mjs";
1
+ import { t as AiError } from "../AiError-DvhLIbmd.mjs";
2
2
  import { n as AudioChunk, r as AudioFormat, t as AudioBlob } from "../Audio-_nGR4V0X.mjs";
3
3
  import { Context, Effect, Stream } from "effect";
4
4
  declare namespace SpeechSynthesizer_d_exports {
@@ -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":"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"}