Skip to content

feat(fetch, node)!: drop the content-type of an empty body - #178

Merged
dinwwwh merged 5 commits into
mainfrom
claude/laughing-bell-0opftl
Oct 28, 2026
Merged

dinwwwh merged 5 commits into
mainfrom
claude/laughing-bell-0opftl

Conversation

@dinwwwh

@dinwwwh dinwwwh commented Oct 28, 2026

Copy link
Copy Markdown
Member

Summary

toFetchBody and toNodeHttpBody now drop a content-type given with an undefined body, like the peer adapter, instead of keeping it and adding standard-server: none. Fastify and AWS Lambda pick this up through toNodeHttpBody.

An empty body has no media type, so it now reads back as undefined from the content headers alone, including where the receiver can't see standard-server (for example a cross-origin browser client without Access-Control-Expose-Headers). Keeping the header didn't help HEAD responses either: the added hint and the cleared content-length already made their headers differ from GET.

To send an empty response with your own headers, such as for HEAD, use an already-closed ReadableStream. The core README now says so.

Changes

  • packages/fetch/src/body.ts, packages/node/src/body.ts: the undefined branch clears content-type and no longer calls getRequiredStandardBodyHint('none', …).
  • Tests: the undefined encoder tests now pass a content-type and hint that must be dropped. Removed the undefined as html rows from the "keeps a given content-type" tables, and the undefined as text/html e2e row, which only asserted the body and is covered by the plain undefined row.
  • packages/core/README.md: one sentence on empty bodies.

Breaking change

A content-type set with an undefined body is no longer sent. This reverts the empty-body part of #176 (released in v0.12.0).

Notes for reviewers

  • A throwaway probe (not committed) sent an already-closed ReadableStream as a GET and HEAD response over Express 4/5, Fastify, h3, Hono, srvx, node:http/https/http2 and Bun.serve. Every transport answered without errors and kept the custom headers.
  • Out of scope, found while probing: on node:http/https, Express and Fastify, a HEAD response whose stream doesn't end promptly stalls. Node sends HEAD headers only on end(), and the response is piped from the whole stream first, so an endless stream never responds. A closed stream is unaffected. Not fixed here.

Testing

  • The updated undefined encoder tests fail on main and pass here.
  • vitest run (1485 tests), the Bun tests, and uncheck (sherif, oxlint, oxfmt, tsc) pass locally.

Generated by Claude Code

`toFetchBody` and `toNodeHttpBody` now drop a given `content-type` when
the body is `undefined`, like the peer adapter, instead of keeping it and
adding `standard-server: none`. An empty body needs no hint, so it reads
back as `undefined` from the content headers alone, even where the
receiver can't see `standard-server` (e.g. a cross-origin browser client).

Keeping it bought nothing for HEAD either: the hint and the cleared
content-length already made the headers differ from GET. To send headers
without content, send an empty Blob with `standard-server: none` (and
`content-disposition: []`); for HEAD, send the same body as GET. Express
4/5, Fastify, h3, Hono, srvx, node:http/https/http2 and Bun.serve all drop
a HEAD response's bytes and keep its headers, including content-length.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VVaw7QvHcuoeP7ZfCahKtA
…-type

- Fold the "drops a given content-type" check into the existing
  `undefined` encoder tests instead of a near-copy next to them.
- Remove the fetch-only empty-Blob round-trip test, which re-tested the
  unchanged Blob branch, and the `undefined as text/html` e2e row, which
  only asserted what the plain `undefined` row already does.
- Say in the encoder comment why an empty body drops the content-type,
  unlike the other bodies, instead of repeating the README.
- Shorten the README note and drop the HEAD advice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VVaw7QvHcuoeP7ZfCahKtA
…stream

Recommend an already-closed `ReadableStream` with `standard-server: none`
instead of an empty Blob: it needs no `content-disposition: []`, and it
works as a HEAD response on every HTTP adapter (Express, Fastify, h3,
Hono, srvx, node:http/https/http2, Bun), reading back as `undefined`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VVaw7QvHcuoeP7ZfCahKtA
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VVaw7QvHcuoeP7ZfCahKtA
@pkg-pr-new

pkg-pr-new Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
@standard-server/aws-lambda

npm i https://pkg.pr.new/@standard-server/aws-lambda@178

@standard-server/core

npm i https://pkg.pr.new/@standard-server/core@178

@standard-server/fastify

npm i https://pkg.pr.new/@standard-server/fastify@178

@standard-server/fetch

npm i https://pkg.pr.new/@standard-server/fetch@178

@standard-server/node

npm i https://pkg.pr.new/@standard-server/node@178

@standard-server/peer

npm i https://pkg.pr.new/@standard-server/peer@178

@standard-server/shared

npm i https://pkg.pr.new/@standard-server/shared@178

commit: 17c7e22

@codspeed

codspeed Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

1 warning

⚠️ 5 benchmarks are dominated by system calls

System calls cannot be simulated consistently and are left out of the measure. The reported value understates the real cost. Switch to the Walltime instrument to measure them.

Learn more about system calls

See affected benchmarks

✅ 26 untouched benchmarks
⏩ 108 skipped benchmarks1


Comparing claude/laughing-bell-0opftl (17c7e22) with main (7636c00)2

Open in CodSpeed

Footnotes

  1. 108 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

  2. No successful run was found on main (23d3742) during the generation of this report, so 7636c00 was used instead as the comparison base. There might be some changes unrelated to this pull request in this report. ↩

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

  • toFetchBody / toNodeHttpBody empty-body branch — an undefined body now clears content-type (instead of emitting standard-server: none next to a caller-supplied content-type); standard-server and content-length were already cleared.
  • Tests — the undefined encoder tests now pass content-type: text/html plus a standard-server: file hint and assert both are dropped; the redundant undefined as html / undefined as text/html rows were removed.
  • Core README — documents that an empty body is sent without content-type, and points to an already-closed ReadableStream for an empty response carrying custom headers.

I confirmed the receiver side still resolves none from the cleared headers (no content-type, content-length absent/0 → none, packages/core/src/utils.ts:123), that the peer adapter already drops content-type for undefined (packages/peer/src/body.ts:174), and that fastify/aws-lambda route through toNodeHttpBody. Full suite passes (1485 tests).

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VVaw7QvHcuoeP7ZfCahKtA
@codecov

codecov Bot commented Oct 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@dinwwwh
dinwwwh merged commit 216da52 into main Oct 28, 2026
13 checks passed
dinwwwh pushed a commit to middleapi/orpc that referenced this pull request Oct 28, 2026
`@standard-server` adapters drop the `content-type` of an `undefined`
body (middleapi/standard-server#178), so the handler codec no longer
special-cases it. Also import `ORPCError` in the link decoder docs example.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VVaw7SJxePN5EdCgmjsAN5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants