Repository navigation
Description
Summary
Follow-up to #234. Now that Blob has working .arrayBuffer() / .text() / .bytes() / .slice() (PR for #234), the only remaining gap on the WHATWG Blob surface is blob.stream(): ReadableStream<Uint8Array> — and that needs the broader Web Streams API stood up first.
Pre-existing state (verified via grep across crates/):
- Zero
ReadableStream/getReader/ReadableStreamDefaultReaderimplementations. Response.bodyis not a stream —js_response_*FFIs incrates/perry-stdlib/src/fetch.rsonly emit fully-bufferedtext/json/arrayBuffer/blob.Request.bodylikewise only carries the buffered bytes.Symbol.asyncIteratoris recognized at HIR (crates/perry-hir/src/lower_decl.rs:77) but no protocol is wired for stream chunk iteration.
A scoped "Blob-only stream" stub would create inconsistency: users would discover blob.stream().getReader() works but response.body.getReader() fails the same way. Better to land Web Streams as a coherent surface.
Surface to land
Minimum viable for parity with Node 22 / browsers:
ReadableStream<Uint8Array>constructor +getReader()/cancel()/tee()/pipeTo(dest)/pipeThrough({ readable, writable })ReadableStreamDefaultReaderwithread(): Promise<{ done, value }>,releaseLock(),closedpromiseSymbol.asyncIteratorprotocol sofor await (const chunk of stream)worksWritableStream+WritableStreamDefaultWriter(needed forpipeTo)TransformStream(needed forpipeThrough)
Once those exist, the consumers fall in:
-
blob.stream()— already declared in response.blob() drops body bytes — same shape as #227, needs Blob instance methods #234's docs/FFIs as deferred; wires to a ReadableStream that emits the body bytes as one chunk (or multiple if we add a chunk-size knob) thendone: true. -
response.body— switch from "always-buffered, never streamed" to a streaming pipe driven fromreqwest::Response::chunk(). -
new Request(url, { body: stream })— accept a ReadableStream as a body source. -
new Response(stream)— accept a ReadableStream and serialize on read.
Why this needs its own issue
- ~500-800 LOC of runtime + codegen across multiple crates (
perry-runtime,perry-stdlib/fetch,perry-codegen,perry-hir). - New GC scanners for the per-stream / per-reader queues (chunks held in the queue must be marked).
- Backpressure semantics (
ReadableStreamDefaultController.desiredSize, queueing strategies) need design work. - Async-iteration protocol for streams is its own subsystem that doesn't exist yet —
Symbol.asyncIteratorresolution +next()/return()shape.
Acceptance criteria
```ts
// blob.stream() round-trip
const blob = new Response("hello world").blob();
const reader = blob.stream().getReader();
const { value, done } = await reader.read(); // value: Uint8Array(b"hello world"), done: false
const r2 = await reader.read(); // r2.done: true
// for-await-of
let total = "";
for await (const chunk of (await new Response("abc").blob()).stream()) {
total += new TextDecoder().decode(chunk);
}
assertEq(total, "abc");
// response.body streaming (proves the API isn't blob-only)
const res = await fetch("https://example.com/big.bin\");
const reader = res.body.getReader();
let bytesSeen = 0;
while (true) {
const { value, done } = await reader.read();
if (done) break;
bytesSeen += value.length;
}
```
Related
- Issue response.blob() drops body bytes — same shape as #227, needs Blob instance methods #234 — Blob instance methods (resolved minus
.stream()) - Issue response.arrayBuffer() returns metadata-only object — body bytes never reach TS #227 —
response.arrayBuffer()body bytes (the fix that made response.blob() drops body bytes — same shape as #227, needs Blob instance methods #234 surface its own gap) - Issue fix(fetch): copy body bytes into response.arrayBuffer() result #232 — same lineage
Activity
fix(fetch): #234 real Blob with arrayBuffer/text/bytes/slice instance…
chore(release): v0.5.359 — fold in PR #238 + slice spec fix + comment…
chore(stdlib): closes #320 — pin ReadableStream regression test (link…
feat(streams): BYOB readers + real ByteLengthQueuingStrategy accounti…
feat(streams): BYOB readers + real ByteLengthQueuingStrategy accounti…