Repository navigation
fix(node): keep multipart bytes intact when the request has an encoding set - #147
Conversation
…ng set When something upstream calls `setEncoding` on the request, the stream yields strings. The form-data path passed the raw stream to `Response`, and undici encodes string chunks as utf8, so: - `latin1` silently corrupted file parts (`ff fe 00 80 41` arrived as `c3 bf c3 be 00 c2 80 41`). - `base64` and `hex` failed with `Failed to parse body as FormData`. The body now goes through `toWebReadableStream`, which encodes string chunks back with the stream's own encoding, like the json, file and stream paths since #131. A form-data body without a content-type now throws `Failed to parse body as FormData: missing content-type header` instead of sending the literal header `undefined` to the parser. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019hbEDi4A2Q9W3mUUGrMN3U
The binary block repeated the encoding block's handler and ran the same file and form-data paths for the same encodings. One block now picks the payload per encoding: arbitrary bytes for the lossless ones, valid text for utf8, which can't round-trip invalid byte sequences. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019hbEDi4A2Q9W3mUUGrMN3U
Drop the custom missing-content-type error and leave the rejection to `Response.formData()`, as the aws-lambda adapter does. An absent header is passed as `''` rather than the literal string `undefined`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019hbEDi4A2Q9W3mUUGrMN3U
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019hbEDi4A2Q9W3mUUGrMN3U
@standard-server/aws-lambda
@standard-server/core
@standard-server/fastify
@standard-server/fetch
@standard-server/node
@standard-server/peer
@standard-server/shared
commit: |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Merging this PR will not alter performance
Comparing Footnotes
|
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
nodeform-data path keeps encoded bytes —_streamToFormDatanow feedsResponseatoWebReadableStream(stream)instead of the rawReadable, so string chunks are re-encoded withstream.readableEncoding(readableChunkToBytes) rather than utf8 by undici; missing content-type now arrives as''.- Encoding regression coverage — the
describe.eachblock expands toutf8/latin1/base64/hex, thefilecase asserts exact bytes, aform-datacase asserts field + file bytes, and a new edge case covers form-data with no content-type rejecting withTypeError.
I ran pnpm --filter @standard-server/node exec vitest run src/body.test.ts (52 passing) and pnpm run check (sherif/oxlint/oxfmt/tsc all pass). Reverting body.ts to the parent commit fails the latin1/base64/hex form-data cases, confirming the tests are discriminating. The contentType ?? '' change is behavior-preserving (new Headers({'content-type': undefined}) would have produced the literal string "undefined", which also fails formData()) and now matches the aws-lambda adapter.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

Summary
Multipart request bodies are no longer corrupted when something upstream calls
setEncodingon the request. #131 fixed string chunks for every other body type but missed form-data, which passed the raw Node stream toResponse. undici encodes string chunks as utf8, whatever encoding produced them, so:latin1silently corrupted file parts: the bytesff fe 00 80 41arrived asc3 bf c3 be 00 c2 80 41.base64andhexfailed withTypeError: Failed to parse body as FormData.Changes
toWebReadableStream, like the octet-stream path. It encodes string chunks back to bytes with the stream's own encoding.''instead of the literal stringundefined, as in the aws-lambda adapter. The parser still rejects it.Testing
body.test.tsnow run forutf8,latin1,base64andhex, and include form-data. Each one sends the bytesff fe 00 80 41(valid text for utf8, which can't round-trip invalid bytes) as a multipart file and with thefilehint, then checks the exact bytes received.pnpm run checkandpnpm testpass, andpackages/node/src/body.tsis at 100% coverage.🤖 Generated with Claude Code
https://claude.ai/code/session_019hbEDi4A2Q9W3mUUGrMN3U
Generated by Claude Code