diff --git a/.changeset/fix-stream-mkuint8array-mutable-fold.md b/.changeset/fix-stream-mkuint8array-mutable-fold.md new file mode 100644 index 0000000000..e673b00860 --- /dev/null +++ b/.changeset/fix-stream-mkuint8array-mutable-fold.md @@ -0,0 +1,7 @@ +--- +"effect": patch +--- + +Fix `Stream.mkUint8Array` crash in Bun compiled+minified binaries. + +Reverted to the pre-beta.59 immutable `Channel.runFold` pattern where each fold iteration returns a new `Uint8Array` instead of mutating a shared accumulator. The beta.59 refactor introduced in-place mutation (`acc.bytes +=`, `acc.arrays.push`) which throws in Bun `--compile --minify` binaries. diff --git a/packages/effect/src/Stream.ts b/packages/effect/src/Stream.ts index 5da1667098..380a47a5eb 100644 --- a/packages/effect/src/Stream.ts +++ b/packages/effect/src/Stream.ts @@ -10339,31 +10339,20 @@ export const mkString = (self: Stream): Effect.Effect(self: Stream): Effect.Effect => - Effect.map( - Channel.runFold( - self.channel, - (): { - bytes: number - readonly arrays: Array - } => ({ - bytes: 0, - arrays: [] - }), - (acc, chunk) => { - for (let i = 0; i < chunk.length; i++) { - acc.bytes += chunk[i].length - acc.arrays.push(chunk[i]) - } - return acc + Channel.runFold( + self.channel, + () => new Uint8Array(0), + (acc, chunk) => { + let chunkLength = 0 + for (let i = 0; i < chunk.length; i++) { + chunkLength += chunk[i].length } - ), - ({ arrays, bytes }) => { - const result = new Uint8Array(bytes) - let offset = 0 - for (let i = 0; i < arrays.length; i++) { - const array = arrays[i] - result.set(array, offset) - offset += array.length + const result = new Uint8Array(acc.length + chunkLength) + result.set(acc, 0) + let offset = acc.length + for (let i = 0; i < chunk.length; i++) { + result.set(chunk[i], offset) + offset += chunk[i].length } return result } diff --git a/packages/effect/test/Stream.test.ts b/packages/effect/test/Stream.test.ts index cd8fb0fd19..4685884f65 100644 --- a/packages/effect/test/Stream.test.ts +++ b/packages/effect/test/Stream.test.ts @@ -205,6 +205,29 @@ describe("Stream", () => { })) }) + describe("mkUint8Array", () => { + it.effect("concatenates chunks into a single Uint8Array", () => + Effect.gen(function*() { + const result = yield* Stream.mkUint8Array( + Stream.make(new Uint8Array([1, 2]), new Uint8Array([3, 4])) + ) + assert.deepStrictEqual(result, new Uint8Array([1, 2, 3, 4])) + })) + + it.effect("works with many chunks (regression: mutable accumulator in Bun compiled binaries)", () => + Effect.gen(function*() { + const chunks = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].map((i) => new Uint8Array([i])) + const result = yield* Stream.mkUint8Array(Stream.fromIterable(chunks)) + assert.deepStrictEqual(result, new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])) + })) + + it.effect("handles empty stream", () => + Effect.gen(function*() { + const result = yield* Stream.mkUint8Array(Stream.empty) + assert.deepStrictEqual(result, new Uint8Array([])) + })) + }) + describe("encoding", () => { it.effect("decodeText handles multi-byte characters split across chunks", () => Effect.gen(function*() {