Skip to content

refactor(streams): build duplex streams from a single transport shape - #1138

Merged
rekmarks-consensys-1 merged 3 commits into
mainfrom
refactor/streams-consolidate-transports
Oct 2, 2026
Merged

rekmarks-consensys-1 merged 3 commits into
mainfrom
refactor/streams-consolidate-transports

Conversation

@rekmarks-consensys-1

@rekmarks-consensys-1 rekmarks-consensys-1 commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Stacked on #1137.

Every transport in @metamask/streams had a Reader class, a Writer class, and a duplex class that wired the two together by hand. The four copies had drifted:

  • NodeWorkerReader never removed its listener and dropped the error passed to onEnd.
  • MessagePortReader ignored the name it was given.
  • SplitStream named itself "Function".
  • Only PostMessageDuplexStream accepted onEnd.

This PR moves the wiring into BaseDuplexStream, which takes one transport shape and builds its own reader and writer:

{ name, listen, onDispatch, validateInput?, onEnd? }

listen(receiveInput) subscribes to the transport and may return an unsubscribe function, which the reader calls when it ends. The writer ends last, so the final Done or Error signal is dispatched before onEnd closes the transport. #1137 fixed that ordering for PostMessageDuplexStream; it now holds for every transport, and onEnd runs exactly once.

Other changes:

  • BaseReader gets input through listen instead of the protected getReceiveInput(), which removes SplitReader. Input that fails validation or signal parsing ends the reader with that error, as before.
  • BaseWriter drops the mutually recursive #dispatch/#throw. A failed write sends the underlying error to the remote once and always rejects with "<name> experienced a dispatch failure". Previously a second failure surfaced the raw transport error.
  • split has one variadic signature in place of four overloads, and it narrows any number of splits.
  • Reader/Writer are defined locally, so the @endo/stream dependency is gone. remote-iterables still uses it, so the only lockfile change is the @metamask/streams entry.
  • TestDuplexStream takes a single onEnd in place of readerOnEnd/writerOnEnd, and its unused onDispatch getter is removed. The two tests that used the old options are updated.

packages/streams non-test source goes from 2,191 to 1,581 lines (−28%), and tests plus mocks from 2,768 to 2,155.

Breaking changes

  • The *Reader/*Writer classes for MessagePort, PostMessage, ChromeRuntime, and NodeWorker are no longer exported. Nothing in this monorepo imports them. BaseReader and BaseWriter are now exported in their place, for one-way streams over any transport.
  • NodePort requires off. Node's Worker and worker_threads MessagePort both provide it.

Testing

  • @metamask/streams: 145 tests pass with 99% coverage. The transport tests were ported from the removed classes to the duplex streams, and there are new cases for single onEnd, dispatch-before-onEnd ordering, and listener removal.
  • yarn build succeeds. The tests for logger, kernel-node-runtime, ocap-kernel, kernel-browser-runtime, kernel-test, kernel-language-model-service, extension, and omnium-gatherum pass.

🤖 Generated with Claude Code


Note

High Risk
Breaking export and transport-interface changes plus reworked stream end/error semantics affect all kernel IPC paths that depend on @metamask/streams.

Overview
Refactors @metamask/streams so every transport implements one shape (listen, onDispatch, optional validateInput/onEnd) and BaseDuplexStream wires its own BaseReader/BaseWriter, instead of hand-rolling separate Reader/Writer classes per transport.

Lifecycle and errors: Duplex streams expose a single onEnd (replacing readerOnEnd/writerOnEnd in tests and mocks). The writer shuts down last so the final done/error signal is dispatched before onEnd runs. BaseReader subscribes via listen (no getReceiveInput()); bad input ends the reader. BaseWriter drops recursive dispatch/retry—failed writes emit one error signal and reject with "<name> experienced a dispatch failure".

Public API (breaking): Per-transport *Reader/*Writer exports are removed; use duplex streams or newly exported BaseReader/BaseWriter. NodePort must implement off for listener cleanup. split is variadic with per-predicate type narrowing. ChromeRuntimeDuplexStream throws in the constructor when local and remote targets match. @endo/stream is removed; Reader/Writer types live in-package.

Downstream test helpers (TestDuplexStream, VatSupervisor, internal-comms mocks) switch to onEnd. Transport tests consolidate on duplex streams with coverage for listener removal and end ordering.

Reviewed by Cursor Bugbot for commit 5a2ca2e. Bugbot is set up for automated code reviews on this repo. Configure here.

@rekmarks-consensys-1
rekmarks-consensys-1 added this pull request to stack #1139 October 1, 2026 19:19
@rekmarks-consensys-1
rekmarks-consensys-1 marked this pull request as ready for review October 1, 2026 19:20
@rekmarks-consensys-1
rekmarks-consensys-1 requested a review from a team as a code owner October 1, 2026 19:20
@rekmarks-consensys-1
rekmarks-consensys-1 force-pushed the refactor/streams-consolidate-transports branch from d1ae14a to 8ec9dde Compare October 1, 2026 19:42
@rekmarks-consensys-1
rekmarks-consensys-1 marked this pull request as draft October 1, 2026 19:44
Base automatically changed from fix/streams-writer-end-on-remote-close to main October 2, 2026 16:17
rekmarks-consensys-1 and others added 3 commits October 2, 2026 09:17
`BaseDuplexStream` now takes `{ listen, onDispatch, onEnd }` and builds its
own reader and writer, so each transport is one class instead of three.
`BaseReader` receives input through `listen` rather than a protected
`getReceiveInput()`, `BaseWriter` ends without the mutually recursive
dispatch/throw path, and `split` uses a single variadic signature. The
local `Reader`/`Writer` types replace the `@endo/stream` dependency.

The per-transport `*Reader` and `*Writer` classes are no longer exported,
and `NodePort` requires `off` so the Node stream removes its listener on end.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@rekmarks-consensys-1
rekmarks-consensys-1 force-pushed the refactor/streams-consolidate-transports branch from 8d3730e to 5a2ca2e Compare October 2, 2026 16:17
@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 73.15%
⬇️ -0.18%
9880 / 13505
🔵 Statements 72.95%
⬇️ -0.17%
10011 / 13723
🔵 Functions 73.55%
⬇️ -0.20%
2311 / 3142
🔵 Branches 67.53%
⬇️ -0.16%
4080 / 6041
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
packages/streams/src/BaseDuplexStream.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/streams/src/BaseStream.ts 98.92%
⬇️ -1.08%
100%
🟰 ±0%
95.65%
⬇️ -4.35%
100%
🟰 ±0%
183
packages/streams/src/index.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/streams/src/split.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/streams/src/utils.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/streams/src/browser/ChromeRuntimeStream.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/streams/src/browser/MessagePortStream.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/streams/src/browser/PostMessageStream.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/streams/src/browser/index.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
packages/streams/src/node/NodeWorkerStream.ts 100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
100%
🟰 ±0%
Generated in workflow #5102 for commit 5a2ca2e by the Vitest Coverage Report Action

@rekmarks-consensys-1
rekmarks-consensys-1 marked this pull request as ready for review October 2, 2026 17:00

@ci-belphegor ci-belphegor 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.

These lines will not be missed

@rekmarks-consensys-1
rekmarks-consensys-1 added this pull request to the merge queue Oct 2, 2026
Merged via the queue into main with commit efb2643 Oct 2, 2026
28 checks passed
@rekmarks-consensys-1
rekmarks-consensys-1 deleted the refactor/streams-consolidate-transports branch October 2, 2026 21:22
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