Skip to content

wrpc v2.0.1: fix route binary stream chunks through the worker proxy - #7

Merged
Alex-Dolid merged 2 commits into
mainfrom
fix/proxy-stream-chunks
Oct 7, 2026
Merged

Alex-Dolid merged 2 commits into
mainfrom
fix/proxy-stream-chunks

Conversation

@Alex-Dolid

Copy link
Copy Markdown
Contributor

Summary

Binary streams were broken behind WrpcClientProxy, the worker that holds one connection for every tab (the event transport). In both directions the stream packets got through but the bytes between them did not:

  • Tab → server (upload): the proxy treated any bytes that were not an attachments frame as junk and dropped them. The server handler got the stream's announcement and its end, with no bytes in between.
  • Server → tab (ctx.client.createStream()): the worker's upstream client passed the stream packets on to the proxy but tried to read each chunk itself. It had no such stream, so it logged Stream … is not initialized for every chunk, and the tab's readable got nothing.

This releases the fix as 2.0.1.

The fix

A chunk carries only its stream id, so the proxy now routes it by the stream's announcement:

  • An upload's chunks go up only from the tab that announced the stream. Chunks from any other tab, and chunks after the stream ended, are dropped.
  • A server stream's chunks go down to wherever its stream packet went: the one tab waiting on that id, or every tab.
  • The upstream client hands each chunk to the proxy synchronously, so it keeps its place between the stream packets that route it.

Cleanup that came with it:

  • A tab that leaves mid-upload: the proxy sends terminate upstream. The server used to wait for the rest of the bytes forever.
  • A lost upstream connection: the proxy sends terminate for every server stream a tab was reading, so those readables end instead of hanging.
  • Memory leak: a tab's stream end now frees the pending slot its announcement took. Before, one slot leaked per upload for the life of the worker.

Bundle size

The fix adds +235 B min+gzip to the shared client core.

Entry Before After Budget
main browser entry 28,401 B 28,636 B 28 KB (unchanged, still fits)
@alexify/wrpc/sse browser 29,519 B 29,753 B 29 KB → 30 KB

The sse entry went over its budget, so the budget is raised, with the reason recorded in scripts/size.js as earlier raises were. The size tables in README, docs/guide/browser.md and llms.txt are updated to match.

Release prep (CONTRIBUTING › Release checklist)

  • package.json → 2.0.1. npm has 1.0.0 and 2.0.0.
  • CHANGELOG.md: the fix moved under ## [2.0.1] - 2026-10-08, with a fresh empty [Unreleased] and its empty ### Changed (breaking) above it
  • Docs nav label → v2.0.1
  • Size tables refreshed from pnpm size

Verification

  • 4 new tests in tests/proxy.test.js: download, upload (including chunks from another tab and after the end), a tab leaving mid-upload, and a lost upstream. Each one failed before the fix.
  • pnpm lint && pnpm format:check && pnpm test:coverage && pnpm test:coverage:floors && pnpm test:types && pnpm size && pnpm docs:build: all green. 3042 tests: 2996 pass, 0 fail, 46 skipped (integration, skip-guarded). Line coverage 98.02%.
  • npm publish --dry-run: 203 files. Every root entry file and .d.ts, bin/ and src/ are in. docs/, tests/, bench/, scripts/, examples/ and coverage/ are out.
  • The packed tarball, installed in a clean directory:
    • CJS require works
    • ESM import works
    • wrpc --version prints 2.0.1
    • tsc --moduleResolution bundler type-checks a Node consumer (with @types/node) and a browser-condition consumer (no Node types at all)

Notes for reviewers

  • The internal proxy client option now also gets a third argument, chunk. client.d.ts is left unchanged on purpose: tests/index.test-d.ts requires a callback typed (data: string) => void to stay assignable to the option.
  • No wire or protocol change: chunks and stream packets are exactly what they were.

After merge

  1. Tag the merge commit on main, not the branch commit (a squash merge changes the SHA): git tag v2.0.1 && git push --follow-tags
  2. npm publish
  3. Create a GitHub release with the 2.0.1 CHANGELOG entry
  4. Check that both https://wrpc.vercel.app/ and https://wrpc.vercel.app/reference/protocol answer 200

🤖 Generated with Claude Code

Alex-Dolid and others added 2 commits October 8, 2026 00:26
Behind WrpcClientProxy both directions of a binary stream arrived empty:
the stream packets were forwarded, the chunks between them were not.

- Page to server: the proxy treated any bytes that were not an
  attachments frame as junk and dropped them, so an upload reached the
  server's handler as an announcement and an end with no bytes.
- Server to page: the worker's upstream client handed the stream packets
  to the proxy but read every chunk itself, found no such stream, and
  logged "Stream … is not initialized"; the tab's readable got nothing.

A chunk carries only its stream id, so the proxy now routes it by the
stream's announcement: an upload's chunks go up only from the page that
announced it, a server stream's chunks go down wherever its stream packet
went (the page waiting on the id, or every page). The upstream client
hands a chunk over synchronously, so it keeps its place between the
stream packets it is routed by.

A page that leaves mid-upload has its stream terminated upstream (the
server waited for the rest of the bytes forever), a lost upstream
terminates the server streams the pages were reading, and a page's stream
end releases the pending slot its announcement took.

+235 B min+gzip in the shared client core: the main browser entry stays
under its 28 KB (28,636 B), the sse entry crossed its 29 KB (29,753 B), so
its budget goes to 30 with the reason in scripts/size.js; the size tables
in README, the browser guide and llms.txt follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- package.json and the docs nav label to 2.0.1 (npm has 1.0.0 and 2.0.0;
  [Unreleased] carries no breaking change, so a patch).
- CHANGELOG: the worker-proxy stream fix moves under [2.0.1] - 2026-10-08,
  with a fresh empty [Unreleased] and its empty Changed (breaking) above.
- The size tables in README and the browser guide refreshed from
  `pnpm size`.

Checked per CONTRIBUTING's release checklist: lint, format:check,
test:coverage (2996 pass, 0 fail, 46 skipped), test:coverage:floors,
test:types, size and docs:build green; `npm publish --dry-run` lists every
root shim and .d.ts, bin/ and src/, and none of docs/tests/bench/scripts/
examples/coverage; the packed tarball installs and resolves under CJS, ESM,
`wrpc --version` and a bundler-resolution tsc.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
wrpc Ready Ready Preview Oct 7, 2026 9:35pm UTC

@Alex-Dolid
Alex-Dolid merged commit 29b347f into main Oct 7, 2026
10 checks passed
@Alex-Dolid
Alex-Dolid deleted the fix/proxy-stream-chunks branch October 7, 2026 21:41

This branch was successfully deployed

1 active deployment
Preview — 1b179637 Deployed Oct 7, 2026 by vercel[bot]
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.

1 participant