Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/shy-cycles-flow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": patch
---

Fix Channel.decodeText corrupting UTF-8 characters split across chunk boundaries.
8 changes: 5 additions & 3 deletions packages/effect/src/Channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6257,8 +6257,9 @@ export const splitLines = <Err, Done>(): Channel<
/**
* Decodes incoming `Uint8Array` chunks into strings using `TextDecoder`.
*
* Each `Uint8Array` inside an emitted array is decoded independently. The
* optional `encoding` and `options` are passed to `TextDecoder`.
* Input chunks are decoded with streaming enabled so multi-byte characters may
* span `Uint8Array` boundaries. The optional `encoding` and `options` are
* passed to `TextDecoder`.
*
* @category String manipulation
* @since 4.0.0
Expand All @@ -6274,7 +6275,8 @@ export const decodeText = <Err, Done>(encoding?: string, options?: TextDecoderOp
fromTransform((upstream, _scope) =>
Effect.sync(() => {
const decoder = new TextDecoder(encoding, options)
return Effect.map(upstream, Arr.map((line) => decoder.decode(line)))
const streamOptions = { stream: true }
return Effect.map(upstream, Arr.map((line) => decoder.decode(line, streamOptions)))
})
)

Expand Down
16 changes: 16 additions & 0 deletions packages/effect/test/Channel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,22 @@ describe("Channel", () => {
}))
})

describe("encoding", () => {
it.effect("decodeText handles multi-byte characters split across Uint8Array boundaries", () =>
Effect.gen(function*() {
const bytes = new TextEncoder().encode("a🌍b")
const chunks: ReadonlyArray<readonly [Uint8Array, ...ReadonlyArray<Uint8Array>]> = [
[bytes.slice(0, 2)],
[bytes.slice(2, 4), bytes.slice(4)]
]
const result = yield* Channel.fromArray(chunks).pipe(
Channel.pipeTo(Channel.decodeText()),
Channel.runCollect
)
assert.strictEqual(result.flat().join(""), "a🌍b")
}))
})

describe("merging", () => {
it.effect("merge - interrupts left side if halt strategy is set to 'right'", () =>
Effect.gen(function*() {
Expand Down
Loading