Skip to content

feat(http): expose the original Web Response on HttpClientResponse - #8209

Open
xia-chao wants to merge 3 commits into
Effect-TS:mainfrom
xia-chao:fix/httpclient-response-to-web-8205
Open

xia-chao wants to merge 3 commits into
Effect-TS:mainfrom
xia-chao:fix/httpclient-response-to-web-8205

Conversation

@xia-chao

@xia-chao xia-chao commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

What was wrong

HttpClientResponse already holds the original Web Response, but the field was private, so nobody could get to it. The only public body API was the Effect Stream.

That bites on workerd. R2 refuses a stream unless it knows the length up front:

Provided readable stream must have a known length (request/response body or readable half of FixedLengthStream)

The original response body knows its length. Stream.toReadableStreamEffect(response.stream) builds a brand new stream and throws that knowledge away. So this fails:

const body = yield* Stream.toReadableStreamEffect(response.stream)
yield* Effect.tryPromise(() => env.BACKUP.put(key, body))

What this PR does

Adds HttpClientResponse.toWeb and exposes source, so you can hand R2 the original body:

const source = HttpClientResponse.toWeb(response)
if (source?.body) yield* Effect.tryPromise(() => env.BACKUP.put(key, source.body))

HttpClientRequest, HttpServerRequest and HttpServerResponse already had toWeb. This just fills the gap.

Why not fix the stream conversion instead

Because it can't be fixed. The length is a marker on native workerd streams, and any new ReadableStream({...}) drops it. A Content-Length header does not bring it back. On top of that, HttpClient.execute wraps the body in Stream.ensuring(..., controller.abort()) to abort the request on interruption, and that finalizer has to keep running — we can't just pass the original stream through.

Same bytes, put into R2 in workerd:

what you put in R2 result
new Response(bytes).body accepted
the original response body accepted
body.tee()[0] accepted
new ReadableStream({ start }) rejected
new Response(stream, { "content-length": "3" }).body rejected
toReadableStreamEffect(response.stream) rejected

Tests

I ran the whole repo suite from the repo root, the same way CI does (pnpm test --run --max-concurrency=10): 11848 passed.

The only failures are environment problems that already fail on main:

  • platform-node-shared > NodeChildProcessSpawner: the process-group kill tests also fail on main on this machine (started from a clean checkout of main)
  • effect > cluster/ClusterWorkflowEngine: one flaky timeout under full-suite parallelism — it passes when run on its own, and 3 out of 3 on repeat

Plus:

  • new HttpClientResponse.test.ts checks toWeb returns the very same Response, also through HttpClient.execute
  • pnpm check, test-types, oxlint, dprint check → pass
  • verified in workerd: toWeb(response).body accepted by R2, toReadableStreamEffect(response.stream) rejected
  • pnpm jsdocs --check fails on unstable/ai/McpSchema.ts both before and after this change

HttpClientResponse now exposes the platform response it was built from
and adds toWeb, which returns the original Web Response when the client
provides one.

Runtimes can attach capabilities to native response bodies that a stream
rebuilt in JavaScript cannot carry. On workerd, R2Bucket.put accepts
toWeb(response).body but rejects Stream.toReadableStreamEffect(response.stream)
with "Provided readable stream must have a known length".

Refs Effect-TS#8205
@changeset-bot

changeset-bot Bot commented Sep 12, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: b2595ea

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 30 packages
Name Type
effect Patch
@effect/opentelemetry Patch
@effect/vitest Patch
@effect/ai-anthropic Patch
@effect/ai-openai-compat Patch
@effect/ai-openai Patch
@effect/ai-openrouter Patch
@effect/atom-react Patch
@effect/atom-solid Patch
@effect/atom-vue Patch
@effect/platform-browser Patch
@effect/platform-bun Patch
@effect/platform-deno Patch
@effect/platform-node-shared Patch
@effect/platform-node Patch
@effect/sql-clickhouse Patch
@effect/sql-d1 Patch
@effect/sql-libsql Patch
@effect/sql-mssql Patch
@effect/sql-mysql2 Patch
@effect/sql-pg Patch
@effect/sql-pglite Patch
@effect/sql-sqlite-bun Patch
@effect/sql-sqlite-do Patch
@effect/sql-sqlite-node Patch
@effect/sql-sqlite-react-native Patch
@effect/sql-sqlite-wasm Patch
@effect/docgen Patch
@effect/doctest Patch
@effect/openapi-generator Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions

github-actions Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Bundle Size Analysis

Generated from PR build output; treat the content below as untrusted.

File Name Current Size Previous Size Difference
arbitrary-combinators.ts 34.58 KB 34.58 KB 0.00 KB (0.00%)
basic.ts 6.87 KB 6.87 KB 0.00 KB (0.00%)
batching.ts 9.95 KB 9.95 KB 0.00 KB (0.00%)
brand.ts 6.45 KB 6.45 KB 0.00 KB (0.00%)
cache.ts 10.77 KB 10.77 KB 0.00 KB (0.00%)
config.ts 21.51 KB 21.51 KB 0.00 KB (0.00%)
differ.ts 20.32 KB 20.32 KB 0.00 KB (0.00%)
http-client.ts 21.93 KB 21.93 KB +0.01 KB (+0.03%)
http-router.ts 33.48 KB 33.48 KB 0.00 KB (0.00%)
logger.ts 10.88 KB 10.88 KB 0.00 KB (0.00%)
metric.ts 9.02 KB 9.02 KB 0.00 KB (0.00%)
optic.ts 6.70 KB 6.70 KB 0.00 KB (0.00%)
pubsub.ts 15.10 KB 15.10 KB 0.00 KB (0.00%)
queue.ts 11.85 KB 11.85 KB 0.00 KB (0.00%)
schedule.ts 10.96 KB 10.96 KB 0.00 KB (0.00%)
schema-binary.ts 39.51 KB 39.51 KB 0.00 KB (0.00%)
schema-class.ts 20.06 KB 20.06 KB 0.00 KB (0.00%)
schema-fromJsonSchemaDocument.ts 30.93 KB 30.93 KB 0.00 KB (0.00%)
schema-representation-roundtrip.ts 26.34 KB 26.34 KB 0.00 KB (0.00%)
schema-string-transformation.ts 13.63 KB 13.63 KB 0.00 KB (0.00%)
schema-string.ts 11.12 KB 11.12 KB 0.00 KB (0.00%)
schema-template-literal.ts 15.41 KB 15.41 KB 0.00 KB (0.00%)
schema-toArbitrary.ts 34.10 KB 34.10 KB 0.00 KB (0.00%)
schema-toCodeDocument.ts 24.56 KB 24.56 KB 0.00 KB (0.00%)
schema-toCodecJson.ts 19.27 KB 19.27 KB 0.00 KB (0.00%)
schema-toEquivalence.ts 19.38 KB 19.38 KB 0.00 KB (0.00%)
schema-toFormatter.ts 19.49 KB 19.49 KB 0.00 KB (0.00%)
schema-toJsonSchemaDocument.ts 23.77 KB 23.77 KB 0.00 KB (0.00%)
schema-toRepresentation.ts 19.54 KB 19.54 KB 0.00 KB (0.00%)
schema.ts 19.27 KB 19.27 KB 0.00 KB (0.00%)
stm.ts 12.80 KB 12.80 KB 0.00 KB (0.00%)
stream.ts 9.83 KB 9.83 KB 0.00 KB (0.00%)

@xia-chao

Copy link
Copy Markdown
Contributor Author

@tim-smart
Please review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

4.0 enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant