Skip to content

fix(stream): expose web stream state to node:stream - #42416

Merged
Jarred-Sumner merged 1 commit into
oven-sh:mainfrom
steipete:codex/web-stream-node-state
Sep 12, 2026
Merged

Jarred-Sumner merged 1 commit into
oven-sh:mainfrom
steipete:codex/web-stream-node-state

Conversation

@steipete

Copy link
Copy Markdown
Collaborator

What does this PR do?

Bun's WHATWG ReadableStream prototype did not expose Node's registered stream-state symbols. As a result, node:stream.isReadable(new ReadableStream()) returned null instead of tracking the stream's live state.

This breaks installed Undici on a forced HTTP socket close. Undici gates response-body termination on node:stream.isReadable(), so under Bun an original response and its clone could remain pending forever instead of rejecting with the transport error.

This change exposes nodejs.stream.readable, nodejs.stream.errored, and nodejs.stream.disturbed as read-only prototype accessors backed by Bun's existing stream state. It matches Node's behavior for readable, locked, disturbed, closed, and errored streams.

AI assistance: This change was developed and reviewed with Codex.

How did you verify your code works?

  • The new state regression fails 0/2 on the pre-fix custom Bun and passes 2/2 after rebuilding.
  • A reduced installed-Undici forced-close probe hangs past 5 seconds before the fix; after the fix, both response.text() and a cloned response reject promptly with TypeError: terminated and the underlying UND_ERR_SOCKET.
  • OpenClaw's six managed provider capture lifecycle cases pass with the patched runtime, including clean partial EOF, forced transport failure, idle capture, and caller cancellation.
  • Built the release CLI successfully, ran clang-format and Prettier, and completed an independent P0-P2 review with no findings.

@claude claude 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.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@coderabbitai

coderabbitai Bot commented Sep 12, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: 27d53b2f-9d1d-4fd8-b39d-14dc38d91fbb

📥 Commits

Reviewing files that changed from the base of the PR and between 1d487c4 and 3253838.

📒 Files selected for processing (2)
  • src/jsc/bindings/webcore/streams/JSReadableStream.cpp
  • test/js/node/stream/web-stream-state.test.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


Walkthrough

The change adds three symbol-keyed ReadableStream prototype accessors for readable, errored, and disturbed state. Each accessor validates its receiver and returns a boolean. Tests cover normal lifecycle transitions and controller-triggered errors.

Changes

ReadableStream state introspection

Layer / File(s) Summary
Register and implement state accessors
src/jsc/bindings/webcore/streams/JSReadableStream.cpp
The ReadableStream prototype registers three read-only symbol-keyed custom accessors. The getters brand-check the receiver and return readable, errored, or disturbed state.
Validate lifecycle state reporting
test/js/node/stream/web-stream-state.test.ts
Tests verify state helpers during normal consumption and after a controller reports an error.

Suggested reviewers: robobun

Priority: ➖ Normal

Merge Risk: ⚪ Minimal · up to 32538

The new stream-state accessors match the tested lifecycle and locked-stream behavior, with no remaining merge-blocking risk identified.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed The description includes both required sections. It clearly explains the bug, the implementation, the impact, and the verification results.
Title check ✅ Passed The title clearly identifies the stream fix and the exposure of Web Stream state to node:stream. It is concise and directly related to the main change.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@robobun

robobun commented Sep 12, 2026

Copy link
Copy Markdown
Collaborator

Thank you for this fix. It overlaps with #34624, an earlier attempt at the same bug. That PR added a private intrinsic and special cases inside the four node:stream helpers. The prototype getters in this PR are the better approach. They are what Node does, and code that reads the nodejs.stream.* symbols directly (for example the readable-stream package) also sees the correct state. I closed #34624 in favor of this PR.

I built this branch and compared it with Node v26.3.0. isReadable, isErrored and isDisturbed give the same results for every ReadableStream case I tried:

  • fresh, locked, closed, close requested with chunks still queued
  • errored before and after a failed read(), cancelled
  • tee() branches, byte streams, pipeTo(), async iteration, subclasses
  • both sides of a TransformStream (Node does not brand TransformStream itself, and neither does this PR)
  • Response, Request, fetch() and Blob bodies, including a fetch() body whose socket is destroyed mid-response

The property descriptors also match Node (get only, not enumerable, configurable). test/js/node/stream/node-stream.test.js and test/js/web/streams/streams.test.js pass with this branch.

One gap: WritableStream

Node also brands WritableStream.prototype, with nodejs.stream.writable and nodejs.stream.errored getters (lib/internal/webstreams/writablestream.js#L181-L187):

get [kIsErrored]() {
  return this[kState].state === 'errored';
}

get [kIsWritable]() {
  return this[kState].state === 'writable';
}

With this PR, isWritable(new WritableStream()) still returns null (Node: true). isErrored() on an errored WritableStream still returns false (Node: true). The same applies to transformStream.writable.

The fix is the mirror image of this diff in JSWritableStream.cpp, backed by m_state == WritableStreamState::Writable and m_state == WritableStreamState::Errored. The Erroring state reports false for both getters, like Node. I applied the patch below on top of this branch. With it, the WritableStream results and descriptors also match Node v26.3.0, and all 11 cases from #34624 pass.

Patch for src/jsc/bindings/webcore/streams/JSWritableStream.cpp
@@ -18,6 +18,7 @@
 #include "WebStreamsInternals.h"
 #include "ZigGlobalObject.h"
 #include <JavaScriptCore/BuiltinNames.h>
+#include <JavaScriptCore/DOMAttributeGetterSetter.h>
 #include <JavaScriptCore/Error.h>
 #include <JavaScriptCore/FunctionPrototype.h>
 #include <JavaScriptCore/JSCInlines.h>
@@ -36,6 +37,8 @@ static JSC_DECLARE_HOST_FUNCTION(jsWritableStreamPrototypeFunction_abort);
 static JSC_DECLARE_HOST_FUNCTION(jsWritableStreamPrototypeFunction_close);
 static JSC_DECLARE_HOST_FUNCTION(jsWritableStreamPrototypeFunction_getWriter);
 static JSC_DECLARE_CUSTOM_GETTER(jsWritableStreamPrototypeGetter_locked);
+static JSC_DECLARE_CUSTOM_GETTER(jsWritableStreamPrototypeGetter_nodeWritable);
+static JSC_DECLARE_CUSTOM_GETTER(jsWritableStreamPrototypeGetter_nodeErrored);
 static JSC_DECLARE_CUSTOM_GETTER(jsWritableStreamPrototypeGetter_constructor);
 static JSC_DECLARE_HOST_FUNCTION(jsWritableStreamPrototype_inspectCustom);
 
@@ -195,6 +198,12 @@ void JSWritableStreamPrototype::finishCreation(VM& vm)
 {
     Base::finishCreation(vm);
     Bun::reifyStaticPropertyTable(vm, JSWritableStream::info(), JSWritableStreamPrototypeTableValues, *this);
+
+    const auto nodeStreamStateAttributes = JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DontEnum | JSC::PropertyAttribute::ReadOnly;
+    const auto nodeStreamStateAnnotation = DOMAttributeAnnotation { JSWritableStream::info(), nullptr };
+    putDirectCustomAccessor(vm, Identifier::fromUid(vm.symbolRegistry().symbolForKey("nodejs.stream.writable"_s)), DOMAttributeGetterSetter::create(vm, jsWritableStreamPrototypeGetter_nodeWritable, nullptr, nodeStreamStateAnnotation), nodeStreamStateAttributes);
+    putDirectCustomAccessor(vm, Identifier::fromUid(vm.symbolRegistry().symbolForKey("nodejs.stream.errored"_s)), DOMAttributeGetterSetter::create(vm, jsWritableStreamPrototypeGetter_nodeErrored, nullptr, nodeStreamStateAnnotation), nodeStreamStateAttributes);
+
     Bun::WebStreams::installInspectCustom(vm, this, jsWritableStreamPrototype_inspectCustom);
     Bun::putToStringTagWithoutTransition(vm, this, info());
 }
@@ -326,6 +335,26 @@ JSC_DEFINE_CUSTOM_GETTER(jsWritableStreamPrototypeGetter_locked, (JSGlobalObject
     return JSValue::encode(jsBoolean(isWritableStreamLocked(stream)));
 }
 
+JSC_DEFINE_CUSTOM_GETTER(jsWritableStreamPrototypeGetter_nodeWritable, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, PropertyName))
+{
+    auto& vm = JSC::getVM(lexicalGlobalObject);
+    auto scope = DECLARE_THROW_SCOPE(vm);
+    auto* stream = dynamicDowncast<JSWritableStream>(JSValue::decode(thisValue));
+    if (!stream) [[unlikely]]
+        return Bun::ERR::INVALID_THIS(scope, lexicalGlobalObject, "WritableStream"_s);
+    return JSValue::encode(jsBoolean(stream->m_state == WritableStreamState::Writable));
+}
+
+JSC_DEFINE_CUSTOM_GETTER(jsWritableStreamPrototypeGetter_nodeErrored, (JSGlobalObject * lexicalGlobalObject, JSC::EncodedJSValue thisValue, PropertyName))
+{
+    auto& vm = JSC::getVM(lexicalGlobalObject);
+    auto scope = DECLARE_THROW_SCOPE(vm);
+    auto* stream = dynamicDowncast<JSWritableStream>(JSValue::decode(thisValue));
+    if (!stream) [[unlikely]]
+        return Bun::ERR::INVALID_THIS(scope, lexicalGlobalObject, "WritableStream"_s);
+    return JSValue::encode(jsBoolean(stream->m_state == WritableStreamState::Errored));
+}
+
 JSC_DEFINE_HOST_FUNCTION(jsWritableStreamPrototypeFunction_abort, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
 {
     auto& vm = JSC::getVM(lexicalGlobalObject);
WritableStream test cases from #34624, free to adopt
import { isDisturbed, isErrored, isReadable, isWritable } from "node:stream";

const probe = stream => ({
  isReadable: isReadable(stream),
  isWritable: isWritable(stream),
  isErrored: isErrored(stream),
  isDisturbed: isDisturbed(stream),
});

it("WritableStream: writable", () => {
  const ws = new WritableStream({ write() {} });
  expect(probe(ws)).toEqual({ isReadable: null, isWritable: true, isErrored: false, isDisturbed: false });
});

it("WritableStream: closed", async () => {
  const ws = new WritableStream({ write() {} });
  const writer = ws.getWriter();
  await writer.close();
  writer.releaseLock();
  expect(probe(ws)).toEqual({ isReadable: null, isWritable: false, isErrored: false, isDisturbed: false });
});

it("WritableStream: errored", async () => {
  const ws = new WritableStream({
    start(c) {
      c.error(new Error("boom"));
    },
  });
  await ws.getWriter().closed.catch(() => {});
  expect(probe(ws)).toEqual({ isReadable: null, isWritable: false, isErrored: true, isDisturbed: false });
});

Against this branch as it is now, these 3 cases fail and the other 8 cases from #34624 pass.

If you prefer to keep this PR scoped to ReadableStream, that is fine too. The WritableStream half can be a follow-up after this PR lands.

One small note, not blocking. The repo convention (CLAUDE.md, "Test Organization") is to add tests to the existing file for the module and not to create a new file. Here that file is test/js/node/stream/node-stream.test.js.

@Jarred-Sumner
Jarred-Sumner merged commit cadd8db into oven-sh:main Sep 12, 2026
6 of 7 checks passed
@robobun

robobun commented Sep 12, 2026

Copy link
Copy Markdown
Collaborator

The WritableStream half is now in #42457 (nodejs.stream.writable and nodejs.stream.errored getters on WritableStream.prototype, same pattern as this PR).

Jarred-Sumner pushed a commit that referenced this pull request Sep 12, 2026
…ng it (#42516)

### Problem
- `const body = res.body; await res.arrayBuffer()` (or `bytes()`,
`blob()`, `json()`, `formData()`) leaves `body` `readable` when a
string, `Blob`, typed array or file backs it. `isReadable(body)` returns
`true`, `Bun.inspect(body)` prints `state: 'readable'`, and `await
finished(body)` never settles. Node v26.3.0 and `text()` close the
stream.
- The cause is `ReadableStream::to_any_blob`
(`src/runtime/webcore/ReadableStream.rs:195`). It takes the payload out
of the native source and marks the stream disturbed and locked, but
never changes `m_state`. Since #42116 (not released) the stream stays
locked, so nothing can drain it either.
- `.body` read after the consume returns a stand-in stream that is
`readable` and not disturbed.

### Fix
- `to_any_blob` calls the new `ReadableStream__closeConsumedAsBody`: the
`markConsumedAsBody` flags, then a close.
- Correct because `to_any_blob` first rejects a disturbed or locked
stream. No reader or controller exists, so the close only sets the state
and settles the closed promise.
- `ReadableStream__used` makes the stand-in for a consumed body closed
and disturbed. The stand-in for a body still being read is unchanged.
- Verified: `test/js/web/fetch/body.test.ts` (99 new cases, 50 fail on
main), plus the suites in Notes. Self-reviewed: 3 changes asked, 3 made.

### Background
- Such a body gets a lazy native `ReadableStream`. It gets a controller
only when something reads it.
- Until then a consumer can take the whole payload back as a `Blob`. The
body methods, `clone()`, `Bun.serve`, `fetch()` uploads, `Bun.write` and
`Bun.spawn` stdin do so when the payload is in memory or is a file.
- #42416 made `isReadable()`, `isErrored()` and `isDisturbed()` read the
stream's own state. `finished()` waits on a closed promise that each
terminal transition settles.

<details><summary>Notes</summary>

**Origin.** A differential run against node v26.3.0 after #42416 found
this. No user reported it. On 1.4.2 the stream was also left `readable`,
but it was unlocked, so `body.getReader().read()` returned `done` and
closed it. #42116 keeps the stream locked (as undici and Chromium do),
which removed that way out. This PR should land before the next release.

**Repro.**

```js
import { isReadable, isDisturbed } from "node:stream";
import { finished } from "node:stream/promises";
const res = new Response("hello world");
const body = res.body;
await res.arrayBuffer();
console.log(isReadable(body), isDisturbed(body), body.locked, Bun.inspect(body));
await finished(body); // main: never settles
```

| | main b993710 | this PR | node v26.3.0 |
| --- | --- | --- | --- |
| `.body` then `arrayBuffer()` / `bytes()` / `blob()` / `json()` /
`formData()` | readable, `finished()` pending | closed | closed |
| `.body` then `text()` | closed | closed | closed |
| `new Response(blob.stream()).bytes()`, `Bun.file().stream()`, `new
Response(Bun.file()).body` | readable | closed | closed |
| `fetch()` response, `.body` taken, whole body arrived, then
`arrayBuffer()` | readable | closed | closed |
| `Bun.serve` sends it, `fetch()` uploads it, `Bun.write` writes it,
`Bun.spawn` stdin (payload in memory or a file) | readable | closed |
n/a |
| `await res.text(); res.body` (stand-in) | readable, not disturbed |
closed, disturbed | closed, disturbed |
| old `.body` after `clone()` | readable forever | closed at `clone()` |
readable until a branch is drained, then closed |
| JS-source stream | closed | closed | closed |

The error messages of `getReader()`, `cancel()`, `tee()`, `pipeTo()`,
`text()`, `Bun.readableStreamToText()` and `new Response(body)` on the
consumed stream are the same as before. `finished()` registered before
the consume settles too.

**Not in this PR.**
- A stream that a native sink attaches to while the payload still
arrives (`ReadableStream__lockNative`: a `fetch()` upload, `Bun.write`,
`Bun.spawn` stdin or `Bun.serve` response of a body that still streams).
It does not go through `to_any_blob`. #42477 fixes it. This PR should
land first. The conflict in `WebStreamsExports.cpp` is keep-both, and
both use `readableStreamCloseIfPossible` for the close.
- `Readable.fromWeb()` on a native stream (`m_transferred`): the web
stream stays `readable` after the node stream ends. It is the same on
1.4.2. The fix touches the code that #42281 changes, so it follows that
PR.
- The `node-fetch` and `undici` shims wrap `res.body` in a node
`Readable`. Since #42116, `res.body.resume()` or `destroy()` after a
body method emits `'error'` (the stream is locked). 1.4.2 emits `'end'`
/ `'close'`. This PR does not change that. It is tracked separately.
- `.body` read while `text()` is still pending returns a stand-in that
stays `readable` and locked. Nothing tells it how the read ends, so it
must not report closed. A new test pins this.
- `getReader().releaseLock()` marks a native stream disturbed: #33461.
`WritableStream` state: #42457.

**The stand-in for a consumed body** is also what `.body` returns once a
native consumer took a body that still streams (`Bun.write(path, res)`
sets the body to used at once). It reads closed from then on. Before,
`finished()` on it never settled.

**Self-review.** It asked for three changes. The stand-in for a body
still being read keeps its old state (`ReadableStream::in_use`). The
close goes through `readableStreamCloseIfPossible`, not a hand-written
state write. The commit message and this body name the excluded paths
and say that the native consumers take this path only when the payload
is in memory or is a file.

**Suites run** with the debug build and
`BUN_JSC_validateExceptionChecks=1`: `test/js/web/fetch/body.test.ts`
(734 pass), `body-stream.test.ts` (9086 pass), `body-clone.test.ts`,
`body-mixin-errors.test.ts`, `blob.test.ts`, `fetch.stream.test.ts`,
`test/js/web/streams/streams.test.js` (549 pass),
`readable-stream-blob-consumed.test.ts`,
`test/js/node/stream/web-stream-state.test.ts`,
`test/js/third_party/wpt-streams/wpt-streams.test.ts` (1175 pass),
`test/js/bun/spawn/spawn-stdin-readable-stream.test.ts`,
`test/js/bun/io/bun-write.test.js`, `test/js/bun/util/inspect.test.js`.

</details>

<!-- robobun:evidence:begin -->

---

**[human-review]** gate passed · iteration 1 · 5 files touched

<details><summary>fails on main (without fix)</summary>

```console
ASAN without fix: 50 failed, 4 skipped
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/js/web/fetch/body.test.ts
bun test v1.4.3 (6a92015)

test/js/web/fetch/body.test.ts:
(pass) Request > constructor > undefined [6.84ms]
(pass) Request > constructor > null [3.58ms]
(pass) Request > constructor > string > "" [6.59ms]
(pass) Request > constructor > string > "Hello world" [1.39ms]
(pass) Request > constructor > string > "🫠" [1.30ms]
(pass) Request > constructor > string > "⁉️" [1.21ms]
(pass) Request > constructor > ArrayBuffer > empty buffer [11.00ms]
(pass) Request > constructor > ArrayBuffer > small buffer [5.11ms]
(pass) Request > constructor > ArrayBuffer > large buffer [4.31ms]
(pass) Request > constructor > SharedArrayBuffer > empty buffer [1.90ms]
(pass) Request > constructor > SharedArrayBuffer > small buffer [1.58ms]
(pass) Request > constructor > SharedArrayBuffer > large buffer [8.91ms]
(pass) Request > constructor > Buffer > empty buffer [1.49ms]
(pass) Request > constructor > Buffer > small buffer [12.96ms]
(pass) Request > constructor > Buffer > large buffer [3.37ms]
(pass) Request > constructor
... (truncated)

release without fix: 4 skipped
bun test v1.4.3-canary.1 (2c1543d)

test/js/web/fetch/body.test.ts:
(pass) Request > constructor > undefined [0.08ms]
(pass) Request > constructor > null [0.05ms]
(pass) Request > constructor > string > "" [0.12ms]
(pass) Request > constructor > string > "Hello world" [0.01ms]
(pass) Request > constructor > string > "🫠" [0.02ms]
(pass) Request > constructor > string > "⁉️"
(pass) Request > constructor > ArrayBuffer > empty buffer [0.14ms]
(pass) Request > constructor > ArrayBuffer > small buffer [1.40ms]
(pass) Request > constructor > ArrayBuffer > large buffer [1.62ms]
(pass) Request > constructor > SharedArrayBuffer > empty buffer [0.03ms]
(pass) Request > constructor > SharedArrayBuffer > small buffer [0.02ms]
(pass) Request > constructor > SharedArrayBuffer > large buffer [2.61ms]
(pass) Request > constructor > Buffer > empty buffer [0.61ms]
(pass) Request > constructor > Buffer > small buffer [0.02ms]
(pass) Request > constructor > Buffer > large buffer [0.47ms]
(pass) Request > constructor > Uint8Array > empty buffer [0.02ms]
(pass) Request > constructor > Uint8Array > small buffer [0.03ms]
(pass) Request > constructor > Uint8Array > large buffer [1.6
... (truncated)
```

</details>

<details><summary>passes on PR (with fix)</summary>

```console
ASAN with fix: 4 skipped
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/pr_gate.xml" test/js/web/fetch/body.test.ts
bun test v1.4.3 (6a92015)

test/js/web/fetch/body.test.ts:
(pass) Request > constructor > undefined [7.98ms]
(pass) Request > constructor > null [3.44ms]
(pass) Request > constructor > string > "" [6.99ms]
(pass) Request > constructor > string > "Hello world" [1.36ms]
(pass) Request > constructor > string > "🫠" [1.41ms]
(pass) Request > constructor > string > "⁉️" [1.13ms]
(pass) Request > constructor > ArrayBuffer > empty buffer [10.53ms]
(pass) Request > constructor > ArrayBuffer > small buffer [5.26ms]
(pass) Request > constructor > ArrayBuffer > large buffer [4.52ms]
(pass) Request > constructor > SharedArrayBuffer > empty buffer [1.81ms]
(pass) Request > constructor > SharedArrayBuffer > small buffer [1.74ms]
(pass) Request > constructor > SharedArrayBuffer > large buffer [10.36ms]
(pass) Request > constructor > Buffer > empty buffer [10.14ms]
(pass) Request > constructor > Buffer > small buffer [2.11ms]
(pass) Request > constructor > Buffer > large buffer [3.02ms]
(pass) Request > constructo
... (truncated)

release with fix: 4 skipped
$ bun scripts/build.ts --profile=release
[configured] bun-profile → bun (stripped) in 615ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/40] cxx obj/src/jsc/bindings/webcore/streams/CrossRealmTransform.cpp.o
[2/40] cxx obj/src/jsc/bindings/webcore/streams/BunAsyncIterableSource.cpp.o
[3/40] cxx obj/src/jsc/bindings/webcore/streams/BunStreamConsumers.cpp.o
[4/40] cxx obj/src/jsc/bindings/webcore/streams/BunStreamSource.cpp.o
[5/40] cxx obj/src/jsc/bindings/webcore/streams/JSByteLengthQueuingStrategy.cpp.o
[6/40] cxx obj/src/jsc/bindings/webcore/streams/JSCompressionStream.cpp.o
[7/40] cxx obj/src/jsc/bindings/webcore/streams/JSCompressionStreamShared.cpp.o
[8/40] cxx obj/src/jsc/bindings/webcore/streams/JSCountQueuingStrategy.cpp.o
[9/40] cxx obj/src/jsc/bindings/webcore/streams/JSDecompressionStream.cpp.o
[10/40] cxx obj/src/jsc/bindings/webcore/streams/JSDirectStreamController.cpp.o
[11/40] cxx obj/src/jsc/bindings/webcore/streams/JSReadableByteStreamController.cpp.o
[12/40] cxx obj/src/jsc/bindings/webcore/streams/JSReadableStreamAsyncIterator.cpp.o
[13/40] cxx obj/src/jsc/bindings/webcore/streams/JSReadableStreamBYOBReader.cpp.o
[14/4
... (truncated)
```

</details>

<details><summary>diff hotspot</summary>

```
.../bindings/webcore/streams/WebStreamsExports.cpp |  22 +++-
 .../bindings/webcore/streams/WebStreamsInternals.h |   3 +-
 src/runtime/webcore/Body.rs                        |   2 +-
 src/runtime/webcore/ReadableStream.rs              |  15 ++-
 test/js/web/fetch/body.test.ts                     | 136 ++++++++++++++++++++-
 5 files changed, 170 insertions(+), 8 deletions(-)
```

</details>

**gate history** · 2 passed · 0 rejected · iteration 1

<details><summary>evidence per changed file</summary>

```
file                                                    reads  edits  tests
src/jsc/bindings/webcore/streams/WebStreamsExports.cpp      4      6     19
src/jsc/bindings/webcore/streams/WebStreamsInternals.h      3      3     19
src/runtime/webcore/Body.rs                                 5      1     19
src/runtime/webcore/ReadableStream.rs                       5      3     19
test/js/web/fetch/body.test.ts                              4      7     19
```

</details>

<!-- robobun:evidence:end -->
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.

3 participants