Skip to content

streams: restore the ERR_INVALID_THIS and ERR_INVALID_ARG_TYPE error codes - #47276

Open
robobun wants to merge 2 commits into
mainfrom
robobun/7580a254/streams-invalid-this-code
Open

robobun wants to merge 2 commits into
mainfrom
robobun/7580a254/streams-invalid-this-code

Conversation

@robobun

@robobun robobun commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Problem

  • A web stream member that returns a promise rejects a wrong this with a TypeError without a code: ReadableStreamDefaultReader.prototype.read can only be called on a ReadableStreamDefaultReader. Node.js gives ERR_INVALID_THIS. Bun 1.3.14 did too for 12 of the 15.
  • Two byte-stream argument errors lost ERR_INVALID_ARG_TYPE the same way: ReadableStreamBYOBReader.read(x) where x is not a view, and ReadableByteStreamController.enqueue().
  • Cause: webstreams: rewrite ReadableStream, WritableStream, and TransformStream in C++ (zero JS builtins) #33193 (streams in C++). Bun::ERR::INVALID_THIS (ErrorCode.cpp:1913) can only throw, so each rejecting branch built a plain createTypeError.

Fix

  • Bun::createInvalidThisErrorForType makes the error of ERR::INVALID_THIS as a value. The thrower and the 15 rejecting branches use it.
  • The two argument sites use Bun::ERR::INVALID_ARG_INSTANCE, as respondWithNewView() does.
  • Correct: only calls that already fail change. Code and message equal Node.js v26.3.0 on each receiver that Node.js also refuses.
  • Verified: test/js/web/streams/streams.test.js (4 new tests, main fails all 4), WPT streams, and 8 files on other ERR::INVALID_THIS callers. Self-reviewed: 10 concerns raised, 10 addressed.

Background

  • Per WebIDL, a member that returns a promise rejects on a wrong this. It does not throw.
  • Node's own tests assert code: 'ERR_INVALID_THIS' on 14 of the 15 rejections.
  • Rejected: 15 inline Bun::createError calls (they repeat Node's sentence), and a throw inside promiseFromSteps (one JSC::Exception per wrong call).

Downsides

Notes

Rebase. The branch is rebased onto main a4c3a5e (head c89ac33). It had one conflict. #47316 put NEVER_INLINE on Bun::throwInvalidThisCallError, the function below the new creator in ErrorCode.cpp. That function is the one call in the wrong-this branch of each generated host function. The creator has the same role in the 15 hand-written branches, so it is NEVER_INLINE too. No other line of the diff changed. Each number and test result in these notes is from the rebased head and from main a4c3a5e.

Repro.

for (const call of [
  () => ReadableStreamDefaultReader.prototype.read.call({}),
  () => new ReadableStream({ type: "bytes" }).getReader({ mode: "byob" }).read("x"),
  async () => { let c; new ReadableStream({ type: "bytes", start(x) { c = x; } }); c.enqueue(); },
]) {
  try { await call(); } catch (e) { console.log(e.name, e.code, e.message); }
}
call Bun 1.3.14 main Node.js v26.3.0 and this PR
read.call({}) ERR_INVALID_THIS no code, ReadableStreamDefaultReader.prototype.read can only be called on a ReadableStreamDefaultReader ERR_INVALID_THIS, Value of "this" must be of type ReadableStreamDefaultReader
BYOB read("x") ERR_INVALID_ARG_TYPE no code, ReadableStreamBYOBReader.prototype.read requires an ArrayBufferView ERR_INVALID_ARG_TYPE, The "view" argument must be an instance of Buffer, TypedArray, or DataView. Received type string ('x')
enqueue() ERR_INVALID_ARG_TYPE no code, Not enough arguments ERR_INVALID_ARG_TYPE, The "buffer" argument must be an instance of Buffer, TypedArray, or DataView. Received undefined

Reach. No issue reports this. A differential run against Node.js and Bun 1.3.14 found it. Each affected call already fails with a TypeError, as a rejection or a throw as before. Only code and message change.

The 15 members.

  • ReadableStream: cancel, pipeTo.
  • ReadableStreamDefaultReader and ReadableStreamBYOBReader: closed, cancel, read.
  • WritableStream: abort, close.
  • WritableStreamDefaultWriter: closed, ready, abort, close, write.

12 are regressions from 1.3.14. The three closed getters never had the code in Bun. They are the same wrong-this branch with the same line shape, Node.js gives them the code, and Node's tests assert it, so they get it here too. I called every own method and getter of the 17 classes of node:stream/web on wrong receivers. These 15 are the only members that answer a wrong this with a promise, on main, on this PR and on Node.js.

Excluded on purpose.

  • Bun's own ReadableStream.prototype.text(), json(), bytes(), blob() and ReadableStreamDefaultReader.prototype.readMany() return a promise on a valid receiver and throw on a wrong one. The four throw ERR_INVALID_THIS, readMany() throws a TypeError without a code. 1.3.14 does the same, and Node.js has none of them. This PR does not change how they fail.
  • The ReadableStream async iterator. Its next() and return() already reject with ERR_INVALID_THIS.

Receivers that Node.js accepts. Node.js accepts a Proxy of an instance, and an object that inherits from an instance, as this of all 15 members. Its brand check reads a symbol, which both forward. WebIDL refuses both, and so does Bun, on 1.3.14, on main and here. The new test has both receivers and pins the rejection.

Not in this PR. The rule: this PR restores the codes that 1.3.14 had, plus the three closed getters. Argument and state errors that never had a code stay as they are. These neighbours of the two argument sites have a code on Node.js and none on Bun. 1.3.14 raised no error for any of them, so they are not regressions:

  • JSReadableStreamBYOBReader.cpp:131, read(view, 5): Node.js ERR_INVALID_ARG_TYPE.
  • JSReadableStreamBYOBReader.cpp:138, read(view, { min: "a" }): Node.js ERR_INVALID_ARG_TYPE.
  • JSReadableStreamBYOBReader.cpp:433, read(view, { min: 0 }): Node.js ERR_INVALID_ARG_VALUE.
  • JSReadableStreamBYOBReader.cpp:437, read(view, { min: 2 }) on a 1-byte view: Node.js ERR_OUT_OF_RANGE.
  • JSReadableByteStreamController.cpp:564, enqueue() of a view over a SharedArrayBuffer: Node.js ERR_INVALID_ARG_VALUE.

A survey of 179 wrong calls across the stream classes found 80 such rows in total: arguments of constructors, pipeTo(), pipeThrough() and respond(), the min option, write() after close(). Each needs a decision on its message. A comment on this PR has the survey script and its results.

BYOB read(x) and the value describer. Bun::ERR::INVALID_ARG_INSTANCE describes the value for the Received ... part. That reads x.constructor. What a getter throws there is the rejection, as on Node.js (main never ran it, 1.3.14 threw it synchronously). For an object without a constructor, the describer prints the whole object on one line:

argument (null prototype) Node.js v26.3.0 main this PR
500 keys ERR_INVALID_ARG_TYPE, 109 characters no code, 67 characters ERR_INVALID_ARG_TYPE, 5,392 characters
chain 20,000 deep ERR_INVALID_ARG_TYPE, 109 characters no code, 67 characters RangeError: Maximum call stack size exceeded., no code

enqueue(x) gives the same two results on main. read(-0) says type number (0) where Node.js says (-0). #38642 changes the describer for both cases. The second commit (read() and enqueue()) can move to its own PR unchanged if this is a concern.

Measurements. Release builds of main a4c3a5e and of this PR (c89ac33), linux-x64, same machine and flags: --profile=release --trimmable=on, the link that CI uses.

  • Valid-receiver path of the 15 members and of enqueue(): no member runs more instructions, 3 run 2 fewer (gdb nexti in the frame of the member, 3 identical runs per binary). closed 10 to 10, ready 27 to 27, read() 61 to 61 (queued chunk) and 94 to 94 (pending), write() 31 to 31, enqueue() 94 to 94. The 3 are cancel() of both readers (31 to 29) and BYOB read() (169 to 167).
  • Release binary: .text 110,637,772 to 110,637,196 B (-576), .rodata 26,845,264 to 26,843,920 B (-1,344), file 140,680,312 to 140,672,120 B (-8,192). The 15 member symbols 8,244 to 7,688 B. ERR::INVALID_THIS 216 to 48 B. The new creator is 137 B. BYOB read grows 66 B, in its cold branch (size -A, nm -S. bloaty is not installed).
  • A hello world from bun build --compile --aot, mapped bytes as test/bundler/compile-aot-size.test.ts counts them: 68,116,932 to 68,116,868 B, and with --trim 25,964,996 to 25,960,836 B. CI built the same two commits (builds 137005 and 137037): 68,117,668 to 68,113,572 B, and with --trim 25,957,540 to 25,961,636 B. --trim removes whole pages only (src/trim/apply.rs), so a change of a few hundred bytes moves that total by 4,096 B in either direction. The --trim bound of that test (25,880,000 B) fails on main without this PR.
  • CI's binary-size check of build 137037 against main 137005: 10 of 12 targets differ by 0.0 KB, Windows x64 by -0.5 KB, Windows aarch64 by -6.0 KB.
  • Host functions +0 (217 to 217 definitions in streams/), prototype table rows +0, startup work +0. New C++ functions: 1.
  • Per wrong-this call: 4 mi_malloc calls (main 4), 4 GC cells (main 3: a string, a Promise, an Error. The fourth is the JSString that Bun::createError makes from its String argument), 0 JSC::Exception cells, 0 calls of VM::throwException. Counted with heapStats().objectTypeCounts over 400 calls with no collection between the readings, and with gdb breakpoints on the main thread.
  • The other ERR::INVALID_THIS throw sites (67 call sites): mimalloc calls per throw 7 to 5, on 5 sync stream members (locked, getReader(), desiredSize, both releaseLock()).
  • 918 wrong-receiver calls (own methods and getters of the classes on globalThis and in 8 node: modules, each called on {}): 15 rows differ between main and this PR, the 15 members. The 325 rows that already give ERR_INVALID_THIS on main keep name, code and message.
  • Build only: the ErrorCode.h edit recompiles 216 of the 290 objects outside vendor/.
  • Rows that differ from Node.js v26.3.0: wrong-this 0 of 15 (main 15), wrong-argument 0 of 9 (main 9). The survey of 179 wrong calls changes in exactly those 9 rows.

Tests.

  • The wrong-this test enumerates the classes of node:stream/web and their members. A new member that answers a wrong this with a promise and rejects without the code fails it. Its logic, run as a script on Node.js v26.3.0 without the two receivers that Node.js accepts, gives the same 15 names and no differing outcome.
  • Debug build: the 4 new tests pass, also with BUN_JSC_validateExceptionChecks=1. streams.test.js: 731 pass, 2 skip. WPT streams: 1175 pass. With the src/ of main the 4 new tests fail and the other 729 pass (release build).
  • Release build: streams.test.js 733 pass, WPT streams 1175 pass. These 8 files pass on both builds, except the any-receiver file on the debug build: readablestream-helpers.test.ts, globals.test.js, histogram.test.ts, members-of-prototype-objects-for-any-receiver.test.ts, and test-whatwg-readablebytestream.js, test-crypto-keyobject-brand-check.js, test-vm-module-errors.js, test-broadcastchannel-custom-inspect.js.
  • On the debug build the any-receiver file fails 5 of 32 (node:events, node:stream, node:http, node:zlib, node:async_hooks). A debug build of main a4c3a5e fails the same 5 with the same output, so this diff is not the cause.
  • Node's test-whatwg-readablestream.js and test-whatwg-writablestream.js assert these codes. They need --expose-internals, so this PR cannot add them.

Merge order with #41630. #41630 changes the same 4 getter lines (the three closed getters and ready). It sets JSC's native-getter flag on a TypeError that has no code, so that a print of ReadableStreamDefaultReader.prototype does not end the process. The two PRs conflict on those lines, and each fails the other's test. The line that serves both is the same in either order:

auto* error = static_cast<ErrorInstance*>(Bun::createInvalidThisErrorForType(lexicalGlobalObject, "ReadableStreamDefaultReader"_s));
error->setNativeGetterTypeError();
return JSValue::encode(promiseRejectedWith(lexicalGlobalObject, error));

This PR is the smaller one and has no open question, so it can land first. Then #41630 takes that line, and its two expectations in test/js/bun/util/inspect.test.js that pin the old getter text take the new sentence.


no test proof · iteration 11 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/js/web/streams/streams.test.js

@robobun

robobun commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 6:14 PM PT - Nov 2nd, 2026

❌ @robobun, your commit c89ac33 has 41 failures in Build #137037 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 47276

That installs a local version of the PR into your bun-47276 executable, so you can run:

bun-47276 --bun

@robobun

robobun commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status

  • Reproduced on main a4c3a5e (debug build and release build) with two scripts. 10 of 10 wrong-this rows reject with a TypeError that has no code. 9 of 9 wrong-argument rows (ReadableStreamBYOBReader.read(x) with no view, ReadableByteStreamController.enqueue()) fail with a TypeError that has no code. Node.js v26.3.0 and Bun 1.3.14 give ERR_INVALID_THIS and ERR_INVALID_ARG_TYPE on all rows.
  • Fix: this PR, streams: restore the ERR_INVALID_THIS and ERR_INVALID_ARG_TYPE error codes #47276. The head is c89ac33: two commits, rebased onto main a4c3a5e.
  • The rebase had one conflict, in src/jsc/bindings/ErrorCode.cpp. AOT: bun test --aot on large suites, module loading fixes, fuzzer fixes #47316 put NEVER_INLINE on Bun::throwInvalidThisCallError, the function below the new Bun::createInvalidThisErrorForType. The new function is NEVER_INLINE too now. No other line of the diff changed.
  • On the rebased head the 4 new tests pass and all of test/js/web/streams/streams.test.js passes (debug build: 731 pass, 2 skip. Release build: 733 pass). Both scripts print what Node.js v26.3.0 prints. With the src/ of main the 4 new tests fail. The measurements in the PR body are new, from the rebased head and main a4c3a5e.
  • CI, build 137037 (c89ac33), finished: no failure comes from this diff.
    • test/js/web/streams/streams.test.js passed on the 11 lanes where it ran: Alpine, Debian and Ubuntu (x64 and aarch64), Linux ASAN, Windows x64 and aarch64, macOS x64 and aarch64.
    • 41 test files are red, in 65 of 188 jobs. 40 of them are the same 40 files that are red in the build of main at the same commit (137005): 39 files under test/aot/ and test/bundler/compile-aot-size.test.ts.
    • The other one is test/js/node/async_hooks/AsyncLocalStorage-tracking.test.ts on Alpine x64. Its tls-connect case connects to example.com:443 and timed out after 90 s on each of 4 attempts. It passed on the other lanes of this build.
    • CI's binary-size check against main 137005: 10 of 12 targets differ by 0.0 KB, Windows x64 by -0.5 KB, Windows aarch64 by -6.0 KB.
  • The diff is ready for review. A green build needs the test/aot failures of main fixed first, and then one more rebase. Main is still red on them at 6cdb383 (build 137093).

@robobun

robobun commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator Author

Survey behind the numbers in the PR body: 179 wrong calls across the web stream classes, run on Node.js v26.3.0, Bun 1.3.14, a release build of main 11c3671 and a release build of this PR.

  • 9 rows get their code back in this PR (the wrong-argument rows). The wrong-this rows are a separate script, in the PR body.
  • 80 rows have a code on Node.js and none (or another) on this PR. Bun 1.3.14 did not match Node.js on them either. They are not in this PR.
Results: call | Node.js | Bun 1.3.14 | main | this PR
byobReader.read(none) | rejects TypeError ERR_INVALID_ARG_TYPE | rejects TypeError ERR_INVALID_ARG_TYPE | rejects TypeError no code | rejects TypeError ERR_INVALID_ARG_TYPE | fixed
byteController.enqueue(none) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError no code | throws TypeError ERR_INVALID_ARG_TYPE | fixed
byobRequest.respondWithNewView(none) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | 
byobReader.read(undefined) | rejects TypeError ERR_INVALID_ARG_TYPE | rejects TypeError ERR_INVALID_ARG_TYPE | rejects TypeError no code | rejects TypeError ERR_INVALID_ARG_TYPE | fixed
byteController.enqueue(undefined) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | 
byobRequest.respondWithNewView(undefined) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | 
byobReader.read(null) | rejects TypeError ERR_INVALID_ARG_TYPE | rejects TypeError ERR_INVALID_ARG_TYPE | rejects TypeError no code | rejects TypeError ERR_INVALID_ARG_TYPE | fixed
byteController.enqueue(null) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | 
byobRequest.respondWithNewView(null) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | 
byobReader.read(5) | rejects TypeError ERR_INVALID_ARG_TYPE | rejects TypeError ERR_INVALID_ARG_TYPE | rejects TypeError no code | rejects TypeError ERR_INVALID_ARG_TYPE | fixed
byteController.enqueue(5) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | 
byobRequest.respondWithNewView(5) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | 
byobReader.read('x') | rejects TypeError ERR_INVALID_ARG_TYPE | rejects TypeError ERR_INVALID_ARG_TYPE | rejects TypeError no code | rejects TypeError ERR_INVALID_ARG_TYPE | fixed
byteController.enqueue('x') | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | 
byobRequest.respondWithNewView('x') | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | 
byobReader.read({}) | rejects TypeError ERR_INVALID_ARG_TYPE | rejects TypeError ERR_INVALID_ARG_TYPE | rejects TypeError no code | rejects TypeError ERR_INVALID_ARG_TYPE | fixed
byteController.enqueue({}) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | 
byobRequest.respondWithNewView({}) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | 
byobReader.read([]) | rejects TypeError ERR_INVALID_ARG_TYPE | rejects TypeError ERR_INVALID_ARG_TYPE | rejects TypeError no code | rejects TypeError ERR_INVALID_ARG_TYPE | fixed
byteController.enqueue([]) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | 
byobRequest.respondWithNewView([]) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | 
byobReader.read(ArrayBuffer) | rejects TypeError ERR_INVALID_ARG_TYPE | rejects TypeError ERR_INVALID_ARG_TYPE | rejects TypeError no code | rejects TypeError ERR_INVALID_ARG_TYPE | fixed
byteController.enqueue(ArrayBuffer) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | 
byobRequest.respondWithNewView(ArrayBuffer) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | 
byobRequest.respond(none) | throws TypeError ERR_INVALID_ARG_VALUE | throws RangeError no code | throws TypeError no code | throws TypeError no code | gap
byobRequest.respond('x') | returns | throws RangeError no code | throws TypeError no code | throws TypeError no code | 
byobRequest.respond(-1) | returns | throws RangeError no code | throws TypeError no code | throws TypeError no code | 
byobRequest.respond(NaN) | throws TypeError ERR_INVALID_ARG_VALUE | throws RangeError no code | throws TypeError no code | throws TypeError no code | gap
byobRequest.respond(1e30) | throws RangeError ERR_INVALID_ARG_VALUE | throws RangeError no code | throws TypeError no code | throws TypeError no code | gap
byobRequest.respond({}) | returns | throws RangeError no code | throws TypeError no code | throws TypeError no code | 
byobRequest.respond(9 (too many)) | throws RangeError ERR_INVALID_ARG_VALUE | throws RangeError no code | throws RangeError no code | throws RangeError no code | gap
byobRequest.respond(0) | throws TypeError ERR_INVALID_ARG_VALUE | returns | throws TypeError no code | throws TypeError no code | gap
byobRequest.respondWithNewView(larger view) | throws RangeError ERR_INVALID_ARG_VALUE | throws TypeError ERR_INVALID_ARG_VALUE | throws RangeError ERR_INVALID_ARG_VALUE | throws RangeError ERR_INVALID_ARG_VALUE | 
byobRequest.respondWithNewView(view at other offset) | throws RangeError ERR_INVALID_ARG_VALUE | throws RangeError no code | throws RangeError ERR_INVALID_ARG_VALUE | throws RangeError ERR_INVALID_ARG_VALUE | 
byobRequest.respondWithNewView(zero-length, readable) | throws TypeError ERR_INVALID_STATE | returns | throws TypeError no code | throws TypeError no code | gap
byobReader.read(zero-length view) | rejects TypeError ERR_INVALID_STATE | rejects TypeError no code | rejects TypeError ERR_INVALID_STATE | rejects TypeError ERR_INVALID_STATE | 
byobReader.read(view, 5) | rejects TypeError ERR_INVALID_ARG_TYPE | fulfilled | rejects TypeError no code | rejects TypeError no code | gap
byobReader.read(view, {min:0}) | rejects TypeError ERR_INVALID_ARG_VALUE | fulfilled | rejects TypeError no code | rejects TypeError no code | gap
byobReader.read(view, {min:9}) | rejects RangeError ERR_OUT_OF_RANGE | fulfilled | rejects RangeError no code | rejects RangeError no code | gap
byobReader.read(view, {min:-1}) | rejects TypeError ERR_INVALID_ARG_VALUE | fulfilled | rejects TypeError no code | rejects TypeError no code | gap
byobReader.read(view, {min:'x'}) | rejects TypeError ERR_INVALID_ARG_TYPE | fulfilled | rejects TypeError no code | rejects TypeError no code | gap
byteController.enqueue(zero-length view) | throws TypeError ERR_INVALID_STATE | returns | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | 
rs.getReader('x') | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | 
rs.getReader(5) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | 
rs.getReader({mode:'x'}) | throws TypeError ERR_INVALID_ARG_VALUE | throws TypeError ERR_INVALID_ARG_VALUE | throws TypeError ERR_INVALID_ARG_VALUE | throws TypeError ERR_INVALID_ARG_VALUE | 
rs.getReader({mode:5}) | throws TypeError ERR_INVALID_ARG_VALUE | throws TypeError ERR_INVALID_ARG_VALUE | throws TypeError ERR_INVALID_ARG_VALUE | throws TypeError ERR_INVALID_ARG_VALUE | 
rs.getReader({mode:'byob'}) on a default stream | throws TypeError ERR_INVALID_ARG_VALUE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
rs.pipeTo() | rejects TypeError ERR_INVALID_ARG_TYPE | rejects TypeError no code | rejects TypeError no code | rejects TypeError no code | gap
rs.pipeTo({}) | rejects TypeError ERR_INVALID_ARG_TYPE | rejects TypeError no code | rejects TypeError no code | rejects TypeError no code | gap
rs.pipeTo(5) | rejects TypeError ERR_INVALID_ARG_TYPE | rejects TypeError no code | rejects TypeError no code | rejects TypeError no code | gap
rs.pipeTo(ws, 5) | rejects TypeError ERR_INVALID_ARG_TYPE | rejects TypeError no code | rejects TypeError no code | rejects TypeError no code | gap
rs.pipeTo(ws, {signal:5}) | rejects TypeError ERR_INVALID_ARG_TYPE | rejects TypeError no code | rejects TypeError no code | rejects TypeError no code | gap
rs.pipeTo(ws, {signal:{}}) | rejects TypeError ERR_INVALID_ARG_TYPE | rejects TypeError no code | rejects TypeError no code | rejects TypeError no code | gap
rs.pipeThrough() | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
rs.pipeThrough(5) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
rs.pipeThrough({}) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
rs.pipeThrough({readable:rs}) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
rs.pipeThrough({readable:{}, writable:ws}) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
rs.pipeThrough({readable:rs, writable:{}}) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
rs.pipeThrough(ts, 5) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
rs.pipeThrough(ts, {signal:5}) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
rs.values(5) | throws TypeError ERR_INVALID_ARG_TYPE | returns | throws TypeError no code | throws TypeError no code | gap
rs.values('x') | throws TypeError ERR_INVALID_ARG_TYPE | returns | throws TypeError no code | throws TypeError no code | gap
rs[Symbol.asyncIterator](5) | throws TypeError ERR_INVALID_ARG_TYPE | returns | throws TypeError no code | throws TypeError no code | gap
new ReadableStream(5) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
new ReadableStream(null) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
new ReadableStream({type:'x'}) | throws TypeError ERR_INVALID_ARG_VALUE | throws RangeError no code | throws TypeError no code | throws TypeError no code | gap
new ReadableStream({}, 5) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
new ReadableStream({pull:5}) | throws TypeError ERR_INVALID_ARG_TYPE | returns | throws TypeError no code | throws TypeError no code | gap
new ReadableStream({start:5}) | throws TypeError no code | throws TypeError no code | throws TypeError no code | throws TypeError no code | 
new ReadableStream({cancel:5}) | throws TypeError ERR_INVALID_ARG_TYPE | returns | throws TypeError no code | throws TypeError no code | gap
new ReadableStream({}, {size:5}) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
new ReadableStream({}, {highWaterMark:-1}) | throws RangeError ERR_INVALID_ARG_VALUE | throws RangeError no code | throws RangeError no code | throws RangeError no code | gap
new ReadableStream({}, {highWaterMark:NaN}) | throws RangeError ERR_INVALID_ARG_VALUE | throws RangeError no code | throws RangeError no code | throws RangeError no code | gap
new ReadableStream({type:'bytes'}, {size(){}}) | throws RangeError ERR_INVALID_ARG_VALUE | throws RangeError no code | throws RangeError no code | throws RangeError no code | gap
new ReadableStream({type:'bytes', autoAllocateChunkSize:0}) | throws TypeError ERR_INVALID_ARG_VALUE | throws RangeError no code | throws TypeError no code | throws TypeError no code | gap
new WritableStream(5) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
new WritableStream({type:'x'}) | throws RangeError ERR_INVALID_ARG_VALUE | throws RangeError no code | throws RangeError no code | throws RangeError no code | gap
new WritableStream({write:5}) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
new WritableStream({start:5}) | throws TypeError no code | throws TypeError no code | throws TypeError no code | throws TypeError no code | 
new WritableStream({close:5}) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
new WritableStream({abort:5}) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
new WritableStream({}, 5) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
new WritableStream({}, {size:5}) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
new WritableStream({}, {highWaterMark:-1}) | throws RangeError ERR_INVALID_ARG_VALUE | throws RangeError no code | throws RangeError no code | throws RangeError no code | gap
new TransformStream(5) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
new TransformStream({readableType:'x'}) | throws RangeError ERR_INVALID_ARG_VALUE | throws RangeError no code | throws RangeError no code | throws RangeError no code | gap
new TransformStream({writableType:'x'}) | throws RangeError ERR_INVALID_ARG_VALUE | throws RangeError no code | throws RangeError no code | throws RangeError no code | gap
new TransformStream({transform:5}) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
new TransformStream({flush:5}) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
new TransformStream({start:5}) | throws TypeError no code | throws TypeError no code | throws TypeError no code | throws TypeError no code | 
new TransformStream({}, 5) | throws TypeError ERR_INVALID_ARG_TYPE | returns | throws TypeError no code | throws TypeError no code | gap
new TransformStream({}, {}, 5) | throws TypeError ERR_INVALID_ARG_TYPE | returns | throws TypeError no code | throws TypeError no code | gap
new TransformStream({}, {highWaterMark:-1}) | throws RangeError ERR_INVALID_ARG_VALUE | throws RangeError no code | throws RangeError no code | throws RangeError no code | gap
new ReadableStreamDefaultReader() | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
new ReadableStreamDefaultReader(5) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
new ReadableStreamDefaultReader({}) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
new ReadableStreamDefaultReader(locked) | throws TypeError ERR_INVALID_STATE | throws TypeError no code | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | 
new ReadableStreamBYOBReader() | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
new ReadableStreamBYOBReader({}) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
new ReadableStreamBYOBReader(default stream) | throws TypeError ERR_INVALID_ARG_VALUE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
new ReadableStreamBYOBReader(locked) | throws TypeError ERR_INVALID_STATE | throws TypeError no code | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | 
new WritableStreamDefaultWriter() | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
new WritableStreamDefaultWriter({}) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
new WritableStreamDefaultWriter(locked) | throws TypeError ERR_INVALID_STATE | throws TypeError no code | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | 
new ReadableStreamDefaultController() | throws TypeError ERR_ILLEGAL_CONSTRUCTOR | throws TypeError no code | throws TypeError ERR_ILLEGAL_CONSTRUCTOR | throws TypeError ERR_ILLEGAL_CONSTRUCTOR | 
new ReadableByteStreamController() | throws TypeError ERR_ILLEGAL_CONSTRUCTOR | throws TypeError no code | throws TypeError ERR_ILLEGAL_CONSTRUCTOR | throws TypeError ERR_ILLEGAL_CONSTRUCTOR | 
new ReadableStreamBYOBRequest() | throws TypeError ERR_ILLEGAL_CONSTRUCTOR | throws TypeError no code | throws TypeError ERR_ILLEGAL_CONSTRUCTOR | throws TypeError ERR_ILLEGAL_CONSTRUCTOR | 
new WritableStreamDefaultController() | throws TypeError ERR_ILLEGAL_CONSTRUCTOR | returns | throws TypeError ERR_ILLEGAL_CONSTRUCTOR | throws TypeError ERR_ILLEGAL_CONSTRUCTOR | 
new TransformStreamDefaultController() | throws TypeError ERR_ILLEGAL_CONSTRUCTOR | returns | throws TypeError ERR_ILLEGAL_CONSTRUCTOR | throws TypeError ERR_ILLEGAL_CONSTRUCTOR | 
new ByteLengthQueuingStrategy() | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
new ByteLengthQueuingStrategy(5) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
new ByteLengthQueuingStrategy({}) | throws TypeError ERR_MISSING_OPTION | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
new CountQueuingStrategy() | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
new CountQueuingStrategy(5) | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
new CountQueuingStrategy({}) | throws TypeError ERR_MISSING_OPTION | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
new CompressionStream('x') | throws TypeError ERR_INVALID_ARG_VALUE | throws TypeError ERR_INVALID_ARG_VALUE | throws TypeError ERR_INVALID_ARG_VALUE | throws TypeError ERR_INVALID_ARG_VALUE | 
new CompressionStream() | throws TypeError ERR_INVALID_ARG_VALUE | throws TypeError ERR_INVALID_ARG_VALUE | throws TypeError ERR_INVALID_ARG_VALUE | throws TypeError ERR_INVALID_ARG_VALUE | 
new DecompressionStream('x') | throws TypeError ERR_INVALID_ARG_VALUE | throws TypeError ERR_INVALID_ARG_VALUE | throws TypeError ERR_INVALID_ARG_VALUE | throws TypeError ERR_INVALID_ARG_VALUE | 
new DecompressionStream() | throws TypeError ERR_INVALID_ARG_VALUE | throws TypeError ERR_INVALID_ARG_VALUE | throws TypeError ERR_INVALID_ARG_VALUE | throws TypeError ERR_INVALID_ARG_VALUE | 
new TextDecoderStream('bad-label') | throws RangeError ERR_ENCODING_NOT_SUPPORTED | throws RangeError ERR_ENCODING_NOT_SUPPORTED | throws RangeError ERR_ENCODING_NOT_SUPPORTED | throws RangeError ERR_ENCODING_NOT_SUPPORTED | 
new TextDecoderStream('utf-8', 5) | throws TypeError ERR_INVALID_ARG_TYPE | returns | throws TypeError ERR_INVALID_ARG_TYPE | throws TypeError ERR_INVALID_ARG_TYPE | 
ReadableStream() | throws TypeError no code | throws TypeError no code | throws TypeError ERR_ILLEGAL_CONSTRUCTOR | throws TypeError ERR_ILLEGAL_CONSTRUCTOR | 
WritableStream() | throws TypeError no code | throws TypeError ERR_ILLEGAL_CONSTRUCTOR | throws TypeError ERR_ILLEGAL_CONSTRUCTOR | throws TypeError ERR_ILLEGAL_CONSTRUCTOR | 
TransformStream() | throws TypeError no code | throws TypeError no code | throws TypeError ERR_ILLEGAL_CONSTRUCTOR | throws TypeError ERR_ILLEGAL_CONSTRUCTOR | 
rs.cancel() locked | rejects TypeError ERR_INVALID_STATE | rejects TypeError ERR_INVALID_STATE | rejects TypeError ERR_INVALID_STATE | rejects TypeError ERR_INVALID_STATE | 
rs.getReader() locked | throws TypeError ERR_INVALID_STATE | throws TypeError no code | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | 
rs.tee() locked | throws TypeError ERR_INVALID_STATE | throws TypeError no code | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | 
rs.pipeTo(ws) locked | rejects TypeError ERR_INVALID_STATE | rejects TypeError ERR_INVALID_STATE | rejects TypeError ERR_INVALID_STATE | rejects TypeError ERR_INVALID_STATE | 
rs.pipeTo(locked ws) | rejects TypeError ERR_INVALID_STATE | rejects TypeError no code | rejects TypeError ERR_INVALID_STATE | rejects TypeError ERR_INVALID_STATE | 
rs.pipeThrough(ts) locked | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | 
rs.pipeThrough(locked writable) | throws TypeError ERR_INVALID_STATE | throws TypeError no code | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | 
rs.values() locked | throws TypeError ERR_INVALID_STATE | returns | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | 
reader.read() released | rejects TypeError ERR_INVALID_STATE | rejects TypeError ERR_INVALID_STATE | rejects TypeError ERR_INVALID_STATE | rejects TypeError ERR_INVALID_STATE | 
reader.cancel() released | rejects TypeError ERR_INVALID_STATE | rejects TypeError ERR_INVALID_STATE | rejects TypeError ERR_INVALID_STATE | rejects TypeError ERR_INVALID_STATE | 
reader.closed released | rejects TypeError ERR_INVALID_STATE | rejects AbortError ERR_STREAM_RELEASE_LOCK | rejects TypeError ERR_INVALID_STATE | rejects TypeError ERR_INVALID_STATE | 
reader.read() pending then released | rejects TypeError ERR_INVALID_STATE | rejects AbortError ERR_STREAM_RELEASE_LOCK | rejects TypeError ERR_INVALID_STATE | rejects TypeError ERR_INVALID_STATE | 
byobReader.read() released | rejects TypeError ERR_INVALID_STATE | rejects TypeError ERR_INVALID_STATE | rejects TypeError ERR_INVALID_STATE | rejects TypeError ERR_INVALID_STATE | 
byobReader.cancel() released | rejects TypeError ERR_INVALID_STATE | rejects TypeError ERR_INVALID_STATE | rejects TypeError ERR_INVALID_STATE | rejects TypeError ERR_INVALID_STATE | 
byobReader.closed released | rejects TypeError ERR_INVALID_STATE | rejects AbortError ERR_STREAM_RELEASE_LOCK | rejects TypeError ERR_INVALID_STATE | rejects TypeError ERR_INVALID_STATE | 
byobReader.read() pending then released | rejects TypeError ERR_INVALID_STATE | throws TypeError no code | rejects TypeError ERR_INVALID_STATE | rejects TypeError ERR_INVALID_STATE | 
defController.enqueue() after close | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | 
defController.close() twice | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | 
defController.enqueue() after error | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | 
defController.close() after error | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | 
byteController.enqueue() after close | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | 
byteController.close() twice | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | 
byteController.close() after error | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | 
byteController.enqueue() after error | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | 
byteController.close() with partial pending pull | throws TypeError ERR_INVALID_STATE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
byobRequest.respond(1) after invalidated | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | 
byobRequest.respondWithNewView() after invalidated | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | 
byobRequest.respond(1) closed stream | throws TypeError ERR_INVALID_ARG_VALUE | returns | throws TypeError no code | throws TypeError no code | gap
byobRequest.respond(0) readable stream | throws TypeError ERR_INVALID_ARG_VALUE | returns | throws TypeError no code | throws TypeError no code | gap
ws.abort() locked | rejects TypeError ERR_INVALID_STATE | rejects TypeError no code | rejects TypeError ERR_INVALID_STATE | rejects TypeError ERR_INVALID_STATE | 
ws.close() locked | rejects TypeError ERR_INVALID_STATE | rejects TypeError no code | rejects TypeError ERR_INVALID_STATE | rejects TypeError ERR_INVALID_STATE | 
ws.close() twice | rejects TypeError ERR_INVALID_STATE | rejects TypeError no code | rejects TypeError ERR_INVALID_STATE | rejects TypeError ERR_INVALID_STATE | 
ws.getWriter() locked | throws TypeError ERR_INVALID_STATE | throws TypeError no code | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | 
writer.write() released | rejects TypeError ERR_INVALID_STATE | rejects TypeError no code | rejects TypeError ERR_INVALID_STATE | rejects TypeError ERR_INVALID_STATE | 
writer.close() released | rejects TypeError ERR_INVALID_STATE | rejects TypeError no code | rejects TypeError ERR_INVALID_STATE | rejects TypeError ERR_INVALID_STATE | 
writer.abort() released | rejects TypeError ERR_INVALID_STATE | rejects TypeError no code | rejects TypeError ERR_INVALID_STATE | rejects TypeError ERR_INVALID_STATE | 
writer.desiredSize released | throws TypeError ERR_INVALID_STATE | throws TypeError no code | throws TypeError ERR_INVALID_STATE | throws TypeError ERR_INVALID_STATE | 
writer.ready released | rejects TypeError ERR_INVALID_STATE | rejects TypeError no code | rejects TypeError ERR_INVALID_STATE | rejects TypeError ERR_INVALID_STATE | 
writer.closed released | rejects TypeError ERR_INVALID_STATE | rejects TypeError no code | rejects TypeError ERR_INVALID_STATE | rejects TypeError ERR_INVALID_STATE | 
writer.close() twice | rejects TypeError ERR_INVALID_STATE | rejects TypeError no code | rejects TypeError ERR_INVALID_STATE | rejects TypeError ERR_INVALID_STATE | 
writer.write() after close | rejects TypeError ERR_INVALID_STATE | rejects TypeError no code | rejects TypeError no code | rejects TypeError no code | gap
writer.write() after abort | rejects None no code | rejects None no code | rejects None no code | rejects None no code | 
wsController.error() twice | returns | returns | returns | returns | 
tsController.enqueue() after terminate | throws TypeError ERR_INVALID_STATE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
tsController.enqueue() after error | throws TypeError ERR_INVALID_STATE | throws TypeError no code | throws TypeError no code | throws TypeError no code | gap
tsController.terminate() twice | returns | returns | returns | returns | 
ts writable write after terminate | rejects TypeError ERR_INVALID_STATE | rejects TypeError no code | rejects TypeError no code | rejects TypeError no code | gap
ReadableStream.from() | throws TypeError no code | throws TypeError no code | throws TypeError no code | throws TypeError no code | 
ReadableStream.from(5) | throws TypeError ERR_ARG_NOT_ITERABLE | throws TypeError no code | throws TypeError ERR_ARG_NOT_ITERABLE | throws TypeError ERR_ARG_NOT_ITERABLE | 
ReadableStream.from({}) | throws TypeError ERR_ARG_NOT_ITERABLE | throws TypeError no code | throws TypeError ERR_ARG_NOT_ITERABLE | throws TypeError ERR_ARG_NOT_ITERABLE | 
ReadableStream.from(null) | throws TypeError no code | throws TypeError no code | throws TypeError no code | throws TypeError no code | 
queuing strategy size() returns NaN | throws RangeError ERR_INVALID_ARG_VALUE | throws RangeError no code | throws RangeError no code | throws RangeError no code | gap
queuing strategy size() returns -1 | throws RangeError ERR_INVALID_ARG_VALUE | throws RangeError no code | throws RangeError no code | throws RangeError no code | gap
ws size() returns -1: writer.write | rejects RangeError ERR_INVALID_ARG_VALUE | rejects RangeError no code | rejects RangeError no code | rejects RangeError no code | gap
Script (prints one JSON line for each call)
// Battery: erroneous calls across the web streams API. One JSON line per probe:
// {label, how, name, code, message}. Compare runtimes by label.
process.on('unhandledRejection', () => {});
const rows = [];
const P = (label, fn) => rows.push([label, fn]);

const rs = (src, strat) => new ReadableStream(src, strat);
const bytes = () => { let c; const s = new ReadableStream({ type: 'bytes', start(x) { c = x; } }); return { s, c }; };
const byobReader = () => { const { s, c } = bytes(); c.enqueue(new Uint8Array([7, 8])); return s.getReader({ mode: 'byob' }); };
const defCtl = () => { let c; const s = new ReadableStream({ start(x) { c = x; } }); return { s, c }; };
const wsCtl = () => { let c; const s = new WritableStream({ start(x) { c = x; } }); return { s, c }; };
const tsCtl = () => { let c; const s = new TransformStream({ start(x) { c = x; } }); return { s, c }; };
// a byte stream with a pending BYOB request
const pendingByob = () => {
  let c; const s = new ReadableStream({ type: 'bytes', start(x) { c = x; } });
  const r = s.getReader({ mode: 'byob' });
  const p = r.read(new Uint8Array(8)); p.catch(() => {});
  return { s, c, r, req: c.byobRequest };
};

// ── argument errors ──
for (const [l, v] of [['none', '__none__'], ['undefined', undefined], ['null', null], ['5', 5], ["'x'", 'x'], ['{}', {}], ['[]', []], ['ArrayBuffer', new ArrayBuffer(1)]]) {
  P(`byobReader.read(${l})`, () => v === '__none__' ? byobReader().read() : byobReader().read(v));
  P(`byteController.enqueue(${l})`, () => v === '__none__' ? bytes().c.enqueue() : bytes().c.enqueue(v));
  P(`byobRequest.respondWithNewView(${l})`, () => { const { req } = pendingByob(); return v === '__none__' ? req.respondWithNewView() : req.respondWithNewView(v); });
}
for (const [l, v] of [['none', '__none__'], ["'x'", 'x'], ['-1', -1], ['NaN', NaN], ['1e30', 1e30], ['{}', {}], ['9 (too many)', 9], ['0', 0]]) {
  P(`byobRequest.respond(${l})`, () => { const { req } = pendingByob(); return v === '__none__' ? req.respond() : req.respond(v); });
}
P('byobRequest.respondWithNewView(larger view)', () => { const { req } = pendingByob(); return req.respondWithNewView(new Uint8Array(64)); });
P('byobRequest.respondWithNewView(view at other offset)', () => { const { req } = pendingByob(); return req.respondWithNewView(new Uint8Array(req.view.buffer, 2, 2)); });
P('byobRequest.respondWithNewView(zero-length, readable)', () => { const { req } = pendingByob(); return req.respondWithNewView(new Uint8Array(req.view.buffer, 0, 0)); });
P('byobReader.read(zero-length view)', () => byobReader().read(new Uint8Array(0)));
P('byobReader.read(view, 5)', () => byobReader().read(new Uint8Array(2), 5));
P('byobReader.read(view, {min:0})', () => byobReader().read(new Uint8Array(2), { min: 0 }));
P('byobReader.read(view, {min:9})', () => byobReader().read(new Uint8Array(2), { min: 9 }));
P('byobReader.read(view, {min:-1})', () => byobReader().read(new Uint8Array(2), { min: -1 }));
P("byobReader.read(view, {min:'x'})", () => byobReader().read(new Uint8Array(2), { min: 'x' }));
P('byteController.enqueue(zero-length view)', () => bytes().c.enqueue(new Uint8Array(0)));

P("rs.getReader('x')", () => rs().getReader('x'));
P('rs.getReader(5)', () => rs().getReader(5));
P("rs.getReader({mode:'x'})", () => rs().getReader({ mode: 'x' }));
P('rs.getReader({mode:5})', () => rs().getReader({ mode: 5 }));
P("rs.getReader({mode:'byob'}) on a default stream", () => rs().getReader({ mode: 'byob' }));
P('rs.pipeTo()', () => rs().pipeTo());
P('rs.pipeTo({})', () => rs().pipeTo({}));
P('rs.pipeTo(5)', () => rs().pipeTo(5));
P('rs.pipeTo(ws, 5)', () => rs().pipeTo(new WritableStream(), 5));
P('rs.pipeTo(ws, {signal:5})', () => rs().pipeTo(new WritableStream(), { signal: 5 }));
P('rs.pipeTo(ws, {signal:{}})', () => rs().pipeTo(new WritableStream(), { signal: {} }));
P('rs.pipeThrough()', () => rs().pipeThrough());
P('rs.pipeThrough(5)', () => rs().pipeThrough(5));
P('rs.pipeThrough({})', () => rs().pipeThrough({}));
P('rs.pipeThrough({readable:rs})', () => rs().pipeThrough({ readable: rs() }));
P('rs.pipeThrough({readable:{}, writable:ws})', () => rs().pipeThrough({ readable: {}, writable: new WritableStream() }));
P('rs.pipeThrough({readable:rs, writable:{}})', () => rs().pipeThrough({ readable: rs(), writable: {} }));
P('rs.pipeThrough(ts, 5)', () => rs().pipeThrough(new TransformStream(), 5));
P('rs.pipeThrough(ts, {signal:5})', () => rs().pipeThrough(new TransformStream(), { signal: 5 }));
P('rs.values(5)', () => rs().values(5));
P("rs.values('x')", () => rs().values('x'));
P('rs[Symbol.asyncIterator](5)', () => rs()[Symbol.asyncIterator](5));

// ── constructors ──
P('new ReadableStream(5)', () => new ReadableStream(5));
P('new ReadableStream(null)', () => new ReadableStream(null));
P("new ReadableStream({type:'x'})", () => new ReadableStream({ type: 'x' }));
P('new ReadableStream({}, 5)', () => new ReadableStream({}, 5));
P('new ReadableStream({pull:5})', () => new ReadableStream({ pull: 5 }));
P('new ReadableStream({start:5})', () => new ReadableStream({ start: 5 }));
P('new ReadableStream({cancel:5})', () => new ReadableStream({ cancel: 5 }));
P('new ReadableStream({}, {size:5})', () => new ReadableStream({}, { size: 5 }));
P('new ReadableStream({}, {highWaterMark:-1})', () => new ReadableStream({}, { highWaterMark: -1 }));
P('new ReadableStream({}, {highWaterMark:NaN})', () => new ReadableStream({}, { highWaterMark: NaN }));
P("new ReadableStream({type:'bytes'}, {size(){}})", () => new ReadableStream({ type: 'bytes' }, { size() {} }));
P("new ReadableStream({type:'bytes', autoAllocateChunkSize:0})", () => new ReadableStream({ type: 'bytes', autoAllocateChunkSize: 0 }));
P('new WritableStream(5)', () => new WritableStream(5));
P("new WritableStream({type:'x'})", () => new WritableStream({ type: 'x' }));
P('new WritableStream({write:5})', () => new WritableStream({ write: 5 }));
P('new WritableStream({start:5})', () => new WritableStream({ start: 5 }));
P('new WritableStream({close:5})', () => new WritableStream({ close: 5 }));
P('new WritableStream({abort:5})', () => new WritableStream({ abort: 5 }));
P('new WritableStream({}, 5)', () => new WritableStream({}, 5));
P('new WritableStream({}, {size:5})', () => new WritableStream({}, { size: 5 }));
P('new WritableStream({}, {highWaterMark:-1})', () => new WritableStream({}, { highWaterMark: -1 }));
P('new TransformStream(5)', () => new TransformStream(5));
P("new TransformStream({readableType:'x'})", () => new TransformStream({ readableType: 'x' }));
P("new TransformStream({writableType:'x'})", () => new TransformStream({ writableType: 'x' }));
P('new TransformStream({transform:5})', () => new TransformStream({ transform: 5 }));
P('new TransformStream({flush:5})', () => new TransformStream({ flush: 5 }));
P('new TransformStream({start:5})', () => new TransformStream({ start: 5 }));
P('new TransformStream({}, 5)', () => new TransformStream({}, 5));
P('new TransformStream({}, {}, 5)', () => new TransformStream({}, {}, 5));
P('new TransformStream({}, {highWaterMark:-1})', () => new TransformStream({}, { highWaterMark: -1 }));
P('new ReadableStreamDefaultReader()', () => new ReadableStreamDefaultReader());
P('new ReadableStreamDefaultReader(5)', () => new ReadableStreamDefaultReader(5));
P('new ReadableStreamDefaultReader({})', () => new ReadableStreamDefaultReader({}));
P('new ReadableStreamDefaultReader(locked)', () => { const s = rs(); s.getReader(); return new ReadableStreamDefaultReader(s); });
P('new ReadableStreamBYOBReader()', () => new ReadableStreamBYOBReader());
P('new ReadableStreamBYOBReader({})', () => new ReadableStreamBYOBReader({}));
P('new ReadableStreamBYOBReader(default stream)', () => new ReadableStreamBYOBReader(rs()));
P('new ReadableStreamBYOBReader(locked)', () => { const { s } = bytes(); s.getReader(); return new ReadableStreamBYOBReader(s); });
P('new WritableStreamDefaultWriter()', () => new WritableStreamDefaultWriter());
P('new WritableStreamDefaultWriter({})', () => new WritableStreamDefaultWriter({}));
P('new WritableStreamDefaultWriter(locked)', () => { const s = new WritableStream(); s.getWriter(); return new WritableStreamDefaultWriter(s); });
P('new ReadableStreamDefaultController()', () => new ReadableStreamDefaultController());
P('new ReadableByteStreamController()', () => new ReadableByteStreamController());
P('new ReadableStreamBYOBRequest()', () => new ReadableStreamBYOBRequest());
P('new WritableStreamDefaultController()', () => new WritableStreamDefaultController());
P('new TransformStreamDefaultController()', () => new TransformStreamDefaultController());
P('new ByteLengthQueuingStrategy()', () => new ByteLengthQueuingStrategy());
P('new ByteLengthQueuingStrategy(5)', () => new ByteLengthQueuingStrategy(5));
P('new ByteLengthQueuingStrategy({})', () => new ByteLengthQueuingStrategy({}));
P('new CountQueuingStrategy()', () => new CountQueuingStrategy());
P('new CountQueuingStrategy(5)', () => new CountQueuingStrategy(5));
P('new CountQueuingStrategy({})', () => new CountQueuingStrategy({}));
P("new CompressionStream('x')", () => new CompressionStream('x'));
P('new CompressionStream()', () => new CompressionStream());
P("new DecompressionStream('x')", () => new DecompressionStream('x'));
P('new DecompressionStream()', () => new DecompressionStream());
P("new TextDecoderStream('bad-label')", () => new TextDecoderStream('bad-label'));
P("new TextDecoderStream('utf-8', 5)", () => new TextDecoderStream('utf-8', 5));
P('ReadableStream()', () => ReadableStream());
P('WritableStream()', () => WritableStream());
P('TransformStream()', () => TransformStream());

// ── state errors ──
P('rs.cancel() locked', () => { const s = rs(); s.getReader(); return s.cancel(); });
P('rs.getReader() locked', () => { const s = rs(); s.getReader(); return s.getReader(); });
P('rs.tee() locked', () => { const s = rs(); s.getReader(); return s.tee(); });
P('rs.pipeTo(ws) locked', () => { const s = rs(); s.getReader(); return s.pipeTo(new WritableStream()); });
P('rs.pipeTo(locked ws)', () => { const w = new WritableStream(); w.getWriter(); return rs().pipeTo(w); });
P('rs.pipeThrough(ts) locked', () => { const s = rs(); s.getReader(); return s.pipeThrough(new TransformStream()); });
P('rs.pipeThrough(locked writable)', () => { const t = new TransformStream(); t.writable.getWriter(); return rs().pipeThrough(t); });
P('rs.values() locked', () => { const s = rs(); s.getReader(); return s.values(); });
P('reader.read() released', () => { const r = rs().getReader(); r.releaseLock(); return r.read(); });
P('reader.cancel() released', () => { const r = rs().getReader(); r.releaseLock(); return r.cancel(); });
P('reader.closed released', () => { const r = rs().getReader(); r.releaseLock(); return r.closed; });
P('reader.read() pending then released', () => { const r = rs().getReader(); const p = r.read(); r.releaseLock(); return p; });
P('byobReader.read() released', () => { const r = byobReader(); r.releaseLock(); return r.read(new Uint8Array(1)); });
P('byobReader.cancel() released', () => { const r = byobReader(); r.releaseLock(); return r.cancel(); });
P('byobReader.closed released', () => { const r = byobReader(); r.releaseLock(); return r.closed; });
P('byobReader.read() pending then released', () => { const { s } = bytes(); const r = s.getReader({ mode: 'byob' }); const p = r.read(new Uint8Array(1)); r.releaseLock(); return p; });
P('defController.enqueue() after close', () => { const { c } = defCtl(); c.close(); return c.enqueue(1); });
P('defController.close() twice', () => { const { c } = defCtl(); c.close(); return c.close(); });
P('defController.enqueue() after error', () => { const { c } = defCtl(); c.error(new Error('x')); return c.enqueue(1); });
P('defController.close() after error', () => { const { c } = defCtl(); c.error(new Error('x')); return c.close(); });
P('byteController.enqueue() after close', () => { const { c } = bytes(); c.close(); return c.enqueue(new Uint8Array(1)); });
P('byteController.close() twice', () => { const { c } = bytes(); c.close(); return c.close(); });
P('byteController.close() after error', () => { const { c } = bytes(); c.error(new Error('x')); return c.close(); });
P('byteController.enqueue() after error', () => { const { c } = bytes(); c.error(new Error('x')); return c.enqueue(new Uint8Array(1)); });
P('byteController.close() with partial pending pull', () => { const { c, req } = (() => { let c; const s = new ReadableStream({ type: 'bytes', start(x) { c = x; } }); const r = s.getReader({ mode: 'byob' }); r.read(new Uint16Array(2)).catch(() => {}); c.enqueue(new Uint8Array(1)); return { c }; })(); return c.close(); });
P('byobRequest.respond(1) after invalidated', () => { const { c, req } = pendingByob(); c.enqueue(new Uint8Array(8)); return req.respond(1); });
P('byobRequest.respondWithNewView() after invalidated', () => { const { c, req } = pendingByob(); c.enqueue(new Uint8Array(8)); return req.respondWithNewView(new Uint8Array(1)); });
P('byobRequest.respond(1) closed stream', () => { const { c, req } = pendingByob(); c.close(); return req.respond(1); });
P('byobRequest.respond(0) readable stream', () => { const { req } = pendingByob(); return req.respond(0); });
P('ws.abort() locked', () => { const w = new WritableStream(); w.getWriter(); return w.abort(); });
P('ws.close() locked', () => { const w = new WritableStream(); w.getWriter(); return w.close(); });
P('ws.close() twice', () => { const w = new WritableStream(); w.close(); return w.close(); });
P('ws.getWriter() locked', () => { const w = new WritableStream(); w.getWriter(); return w.getWriter(); });
P('writer.write() released', () => { const w = new WritableStream().getWriter(); w.releaseLock(); return w.write(1); });
P('writer.close() released', () => { const w = new WritableStream().getWriter(); w.releaseLock(); return w.close(); });
P('writer.abort() released', () => { const w = new WritableStream().getWriter(); w.releaseLock(); return w.abort(); });
P('writer.desiredSize released', () => { const w = new WritableStream().getWriter(); w.releaseLock(); return w.desiredSize; });
P('writer.ready released', () => { const w = new WritableStream().getWriter(); w.releaseLock(); return w.ready; });
P('writer.closed released', () => { const w = new WritableStream().getWriter(); w.releaseLock(); return w.closed; });
P('writer.close() twice', () => { const w = new WritableStream().getWriter(); w.close(); return w.close(); });
P('writer.write() after close', () => { const w = new WritableStream().getWriter(); w.close(); return w.write(1); });
P('writer.write() after abort', () => { const w = new WritableStream().getWriter(); w.abort(); return w.write(1); });
P('wsController.error() twice', () => { const { c } = wsCtl(); c.error(new Error('x')); return c.error(new Error('y')); });
P('tsController.enqueue() after terminate', () => { const { c } = tsCtl(); c.terminate(); return c.enqueue(1); });
P('tsController.enqueue() after error', () => { const { c } = tsCtl(); c.error(new Error('x')); return c.enqueue(1); });
P('tsController.terminate() twice', () => { const { c } = tsCtl(); c.terminate(); return c.terminate(); });
P('ts writable write after terminate', () => { const { s, c } = tsCtl(); c.terminate(); return s.writable.getWriter().write(1); });
P('ReadableStream.from()', () => ReadableStream.from());
P('ReadableStream.from(5)', () => ReadableStream.from(5));
P('ReadableStream.from({})', () => ReadableStream.from({}));
P('ReadableStream.from(null)', () => ReadableStream.from(null));
P('queuing strategy size() returns NaN', () => { let c; const s = new ReadableStream({ start(x) { c = x; } }, { size() { return NaN; } }); return c.enqueue(1); });
P('queuing strategy size() returns -1', () => { let c; const s = new ReadableStream({ start(x) { c = x; } }, { size() { return -1; } }); return c.enqueue(1); });
P('ws size() returns -1: writer.write', () => { const w = new WritableStream({}, { size() { return -1; } }).getWriter(); return w.write(1); });

const only = process.argv[2];
for (const [label, fn] of rows) {
  if (only && !label.includes(only)) continue;
  let how, e, v;
  try {
    v = fn();
    if (v && typeof v.then === 'function') {
      let timer;
      const pending = new Promise(res => { timer = setTimeout(() => res('__pending__'), 150); });
      try { const r = await Promise.race([v, pending]); how = r === '__pending__' ? 'pending' : 'fulfilled'; } catch (x) { how = 'rejects'; e = x; }
      clearTimeout(timer);
    } else how = 'returns';
  } catch (x) { how = 'throws'; e = x; }
  const isErr = e !== undefined;
  console.log(JSON.stringify({ label, how, name: isErr ? (e && e.name) ?? typeof e : null, code: isErr ? (e && e.code) ?? null : null, message: isErr ? String((e && e.message) ?? e) : null }));
}
process.exit(0);

@coderabbitai

coderabbitai Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
Commits

Reviewing files that changed from the base of the PR and between 04f876e and c89ac33.

Files selected for processing: 1
  • src/jsc/bindings/ErrorCode.cpp

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 0 remain after this review.

Walkthrough

Web Streams methods now use a shared invalid-this error. BYOB reads and byte-stream controller enqueue() calls also use invalid-argument errors for missing or invalid values. Tests cover receiver errors, argument errors, and BYOB read behavior.

Changes

Web Streams Error Compatibility

Layer / File(s) Summary
Shared invalid-receiver errors
src/jsc/bindings/ErrorCode.cpp, src/jsc/bindings/ErrorCode.h, src/jsc/bindings/webcore/streams/JSReadableStream*.cpp, src/jsc/bindings/webcore/streams/JSWritableStream*.cpp, test/js/web/streams/streams.test.js
A shared helper creates ERR_INVALID_THIS errors. Readable and writable stream methods use it for invalid receivers. Tests check rejected promises, error codes, messages, and promise uniqueness.
Byte-stream argument errors
src/jsc/bindings/webcore/streams/JSReadableByteStreamController.cpp, src/jsc/bindings/webcore/streams/JSReadableStreamBYOBReader.cpp, test/js/web/streams/streams.test.js
BYOB read() and byte-stream enqueue() use invalid-argument errors for missing or invalid values. Tests also check BYOB error messages and confirm queued bytes remain available after a rejected read.

Suggested reviewers: cirospaciari

Merge Risk: Minimal · up to c89ac

No actionable merge-blocking issue is established for the shared error-helper change. Merge readiness remains subject to normal build and stream-test checks.

Pre-merge checks: Passed 4
Check name Status Explanation
Title check Passed The title names the two error codes the PR restores, ERR_INVALID_THIS and ERR_INVALID_ARG_TYPE, which are the main change in the streams bindings. It fits the changeset, which adds createInvalidThisEr…
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.
Description check Passed The description follows the repository template. It has both required sections, "What does this PR do?" and "How did you verify your code works?". The Problem, Fix, and Background parts explain the ch…
Full details: Title check

Explanation

The title names the two error codes the PR restores, ERR_INVALID_THIS and ERR_INVALID_ARG_TYPE, which are the main change in the streams bindings. It fits the changeset, which adds createInvalidThisErrorForType and switches the BYOB read and enqueue argument checks to ERR::INVALID_ARG_INSTANCE. The title is concise and specific, so a teammate scanning history can see the primary change.

Full details: Description check

Explanation

The description follows the repository template. It has both required sections, "What does this PR do?" and "How did you verify your code works?". The Problem, Fix, and Background parts explain the change and its scope. The Verified part names the new tests, the WPT run, and the other affected test files. It also gives a reproduction table, a list of the 15 changed members, and measurements. Its verification section is detailed, and the evidence note says the streams test file did not run on the author's machine, so CI platform validation is deferred. The description is complete enough to pass. The remaining gap is that CI-only validation is deferred, and the description states this.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

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

Nothing blocking. The comments below are optional suggestions. There is no need to push a fix for them before merging.

Beyond the inline finding, I also checked the new BYOB read() argument path for exception handling: INVALID_ARG_INSTANCE propagates a throw from the value's constructor getter via RELEASE_RETURN_IF_EXCEPTION, and the caller's RETURN_IF_EXCEPTION inside promiseFromSteps turns it into the rejection the new test pins. I also grepped the streams bindings for any remaining bespoke wrong-this rejections (none left; the two remaining createTypeError rejections are argument/state errors the PR scopes out) and the test suite for assertions on the old message text (none).

Extended reasoning...

The diff is a mechanical swap of 15 wrong-receiver rejections in the C++ WHATWG stream bindings to a new Bun::createInvalidThisErrorForType helper, plus two byte-stream argument sites routed through Bun::ERR::INVALID_ARG_INSTANCE, with a new test enumerating every node:stream/web prototype member. It touches no security-sensitive surface. The one inline finding (the enqueue() error naming a "buffer" argument where Node says "chunk") is now pinned by the new test while the PR claims message parity with Node, so a human should decide whether to rename before merging; that is why this is a defer rather than an approve.

@robobun

robobun commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator Author

On the one review finding (enqueue() should name the argument "chunk"): Node.js names it "buffer", so the code and the test stay as they are.

controller.enqueue() on Node.js v26.3.0 throws The "buffer" argument must be an instance of Buffer, TypedArray, or DataView. Received undefined. enqueue(chunk) calls validateBuffer(chunk) with no name, and the default name is 'buffer'. The run and the source links are in the thread: #47276 (comment)

@robobun

robobun commented Oct 29, 2026

Copy link
Copy Markdown
Collaborator Author

Merged main (da867c6) into this branch so that CI runs on a green main. There was no conflict.

  • CI run 134763 was red for a reason outside this PR: only test/aot/* tests on the lane test-aot-drawn failed. The same families failed on main (builds 134576 and 134732).
  • On a debug build of da867c6 with this branch and eight other open PRs merged in: test/js/web/streams/streams.test.js passes (731 tests).

…eturning members

The 15 web stream methods and getters that return a promise rejected a
wrong `this` with a plain TypeError. Bun::ERR::INVALID_THIS can only
throw, so each rejecting arm built its own error, with no code.

Bun::createInvalidThisErrorForType makes that error as a value. The
thrower now calls it, and the 15 arms reject with it. The code and the
message equal Node.js: ERR_INVALID_THIS, `Value of "this" must be of
type <Class>`.
ReadableStreamBYOBReader.prototype.read() with a value that is not a
view, and ReadableByteStreamController.prototype.enqueue() with no
argument, failed with a TypeError that had no code. Both now use
Bun::ERR::INVALID_ARG_INSTANCE, as respondWithNewView() and
enqueue(value) already do. A missing argument reads as undefined.
@robobun
robobun force-pushed the robobun/7580a254/streams-invalid-this-code branch from 04f876e to c89ac33 Compare November 2, 2026 21:45

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

LGTM — a mechanical swap of 15 wrong-this rejections to a shared ERR_INVALID_THIS creator plus two argument sites moved to the existing INVALID_ARG_INSTANCE helper, with a test that pins all of it.

What was reviewed:

  • Exception/scope handling at the two new throw sites: convertBYOBReadArguments leaves the exception pending and the promiseFromSteps lambda checks it; enqueue returns the helper's value directly and argument(0) makes the zero-arg case undefined rather than a Not-enough-arguments throw.
  • createInvalidThisErrorForType is makeString over ASCIILiterals with no JS entry, so the getters calling it without a ThrowScope are fine; ERR::INVALID_THIS keeps the same code and text.
  • Grepped the suite for the old "can only be called on" / "requires an ArrayBufferView" strings — no stale assertions remain.
  • Considered the sync-throw vs rejection change for a view whose constructor getter throws: WebIDL rejects for promise-returning operations and the new test pins it, so not a defect.
Extended reasoning...

The diff touches src/jsc/bindings/ErrorCode.{h,cpp} (one new helper, INVALID_THIS refactored to use it) and six hand-written WHATWG stream bindings, changing only the error code and message of calls that already fail, plus 151 lines of tests in test/js/web/streams/streams.test.js. It touches no security-sensitive surface. The changes are small, repetitive, follow the existing INVALID_ARG_INSTANCE pattern used by respondWithNewView, and the new test enumerates every prototype member against a receiver matrix with a non-vacuous expected list. No CODEOWNERS entry covers the changed files, the hunt ran dry, and the only prior inline thread (argument name "buffer") matches Node's validateBuffer default, so nothing blocks approval.

This branch has not been deployed

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants