From e7354c867f927c633e8badfae28ebb334222dd30 Mon Sep 17 00:00:00 2001 From: Sebastian Lorenz Date: Tue, 4 Aug 2026 13:57:13 +0000 Subject: [PATCH 1/3] Preserve lexical ordering in streaming template interpolation --- .../effect/test/unstable/http/Template.test.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 packages/effect/test/unstable/http/Template.test.ts diff --git a/packages/effect/test/unstable/http/Template.test.ts b/packages/effect/test/unstable/http/Template.test.ts new file mode 100644 index 00000000000..0787fe42658 --- /dev/null +++ b/packages/effect/test/unstable/http/Template.test.ts @@ -0,0 +1,17 @@ +import { assert, describe, it } from "@effect/vitest" +import { Effect, Fiber, Stream } from "effect" +import { TestClock } from "effect/testing" +import { Template } from "effect/unstable/http" + +describe("Template", () => { + it.effect("preserves template segment order", () => + Effect.gen(function*() { + const fiber = yield* Stream.runCollect( + Template.stream`a${Effect.delay(Effect.succeed("slow"), "1 second")}b${"fast"}c` + ).pipe(Effect.forkChild) + yield* Effect.yieldNow + yield* TestClock.adjust("1 second") + const chunks = yield* Fiber.join(fiber) + assert.strictEqual(chunks.join(""), "aslowbfastc") + })) +}) From b42f33207f9a92b9d25f471ce7c33ae7f3fe8275 Mon Sep 17 00:00:00 2001 From: Tim Smart Date: Tue, 4 Aug 2026 22:05:01 +0000 Subject: [PATCH 2/3] Fix streaming template interpolation order --- .changeset/tidy-cats-stream.md | 5 +++++ packages/effect/src/unstable/http/Template.ts | 3 +-- 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 .changeset/tidy-cats-stream.md diff --git a/.changeset/tidy-cats-stream.md b/.changeset/tidy-cats-stream.md new file mode 100644 index 00000000000..c14e87c6a17 --- /dev/null +++ b/.changeset/tidy-cats-stream.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Preserve lexical ordering in streaming template interpolation. diff --git a/packages/effect/src/unstable/http/Template.ts b/packages/effect/src/unstable/http/Template.ts index bc283584232..6756d054125 100644 --- a/packages/effect/src/unstable/http/Template.ts +++ b/packages/effect/src/unstable/http/Template.ts @@ -218,8 +218,7 @@ export function stream>( return Stream.flatMap( Stream.fromIterable(chunks), (chunk) => - typeof chunk === "string" ? Stream.succeed(chunk) : Effect.isEffect(chunk) ? Stream.fromEffect(chunk) : chunk, - { concurrency: "unbounded" } + typeof chunk === "string" ? Stream.succeed(chunk) : Effect.isEffect(chunk) ? Stream.fromEffect(chunk) : chunk ) } From 46b805ab9e4a3929208c59e5e49472865e7d6001 Mon Sep 17 00:00:00 2001 From: Tim Smart Date: Tue, 4 Aug 2026 22:50:36 +0000 Subject: [PATCH 3/3] Preserve concurrent template evaluation --- packages/effect/src/unstable/http/Template.ts | 10 ++++---- .../test/unstable/http/Template.test.ts | 23 ++++++++++++++++++- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/packages/effect/src/unstable/http/Template.ts b/packages/effect/src/unstable/http/Template.ts index 6756d054125..994ecd461fd 100644 --- a/packages/effect/src/unstable/http/Template.ts +++ b/packages/effect/src/unstable/http/Template.ts @@ -215,10 +215,12 @@ export function stream>( buffer = "" } - return Stream.flatMap( - Stream.fromIterable(chunks), - (chunk) => - typeof chunk === "string" ? Stream.succeed(chunk) : Effect.isEffect(chunk) ? Stream.fromEffect(chunk) : chunk + return Stream.fromIterable(chunks).pipe( + Stream.mapEffect( + (chunk) => Effect.isEffect(chunk) ? chunk : Effect.succeed(chunk), + { concurrency: "unbounded" } + ), + Stream.flatMap((chunk) => typeof chunk === "string" ? Stream.succeed(chunk) : chunk) ) } diff --git a/packages/effect/test/unstable/http/Template.test.ts b/packages/effect/test/unstable/http/Template.test.ts index 0787fe42658..bbe76e7bb10 100644 --- a/packages/effect/test/unstable/http/Template.test.ts +++ b/packages/effect/test/unstable/http/Template.test.ts @@ -1,5 +1,5 @@ import { assert, describe, it } from "@effect/vitest" -import { Effect, Fiber, Stream } from "effect" +import { Deferred, Effect, Fiber, Stream } from "effect" import { TestClock } from "effect/testing" import { Template } from "effect/unstable/http" @@ -14,4 +14,25 @@ describe("Template", () => { const chunks = yield* Fiber.join(fiber) assert.strictEqual(chunks.join(""), "aslowbfastc") })) + + it.effect("evaluates effect interpolations concurrently", () => + Effect.gen(function*() { + const firstStarted = yield* Deferred.make() + const secondStarted = yield* Deferred.make() + const releaseFirst = yield* Deferred.make() + const fiber = yield* Template.stream`${ + Deferred.succeed(firstStarted, void 0).pipe( + Effect.andThen(Deferred.await(releaseFirst)), + Effect.as("first") + ) + }${Deferred.succeed(secondStarted, void 0).pipe(Effect.as("second"))}`.pipe( + Stream.runCollect, + Effect.forkChild + ) + yield* Deferred.await(firstStarted) + yield* Effect.yieldNow + assert.isTrue(yield* Deferred.isDone(secondStarted)) + yield* Deferred.succeed(releaseFirst, void 0) + assert.deepStrictEqual(yield* Fiber.join(fiber), ["first", "second"]) + })) })