Web Streams & Interop
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Node.js 26 (26.10.0, released 2026-09-21, the Current line that becomes LTS on 2026-10-28), with the differences
of Node.js 24 "Krypton" (24.21.0, Active LTS) and Node.js 22 "Jod" (22.23.3, Maintenance) called out; versions and stability levels were
checked against the official documentation on 2026-10-03, and every example below was run on all three lines unless the text says
otherwise. The Web Streams API is the WHATWG Streams Standard: the same ReadableStream, WritableStream and TransformStream that browsers,
Deno, Bun and edge runtimes expose. Node.js has had its own, older stream API since its first release (see
Streams); web streams arrived later and are what fetch() returns as a response body, so any Node.js
program that talks HTTP meets both. The API is Stability 2 (Stable) since v21.0.0, and the interoperability methods Readable.toWeb(),
Readable.fromWeb() and their Writable and Duplex counterparts have been Stable since v24.0.0 and v22.17.0 (they were experimental before).
This page owns the web-stream classes, the bridge between the two worlds and the decision of which to pick; the Node.js classes are covered in
Streams and Implementing Custom Streams, and the zlib
module that sits behind CompressionStream in Compression with zlib. The running scenario is the
Bookshelf application that grows across this section, whose architecture is described in
Introduction & Architecture.
The stability and version map
| API | Stability (Node.js 26 docs) | Version notes |
|---|---|---|
|
2 Stable ( |
Added in v16.5.0; no longer experimental in v21.0.0; no runtime warning since v18.0.0; exposed as globals since v18.0.0. |
|
Stable |
Added in v20.6.0. |
|
Stable (globals) |
Added in v16.6.0; globals since v18.0.0. |
|
Stable (globals) |
Added in v17.0.0; the |
|
Stable |
Added in v16.7.0; |
|
Stable |
Added in v17.0.0; marked stable in v24.0.0 and v22.17.0; the |
|
Stable |
|
|
Stable |
Added in v17.0.0; stable in v24.0.0 and v22.17.0; always a byte stream since v23.8.0 and v22.15.0. |
Two stream APIs, one runtime
Node.js ships two unrelated stream implementations that solve the same problem: moving data piece by piece, with a bounded memory budget and
backpressure, instead of loading it all at once. The Node.js API (node:stream) is built on EventEmitter, uses Buffer chunks by default and
has .pipe(), 'data' events and callbacks. The web API (node:stream/web, also available as globals) is built on promises and
Uint8Array, has no events, and is the one standardised across runtimes.
The classes are globals, so no import is needed in ES modules or CommonJS; node:stream/web exports the same objects, which the first example
checks. Both families fix the same three roles: a source (Readable or ReadableStream), a destination (Writable or WritableStream) and
a processing step in the middle (Transform or TransformStream). A web TransformStream is a pair, with a writable half that you write into
and a readable half that you read from, rather than one object that is both.
| Aspect | Node.js streams (node:stream) |
Web streams (WHATWG) |
|---|---|---|
Chunks |
|
Any JavaScript value; byte streams carry |
Programming model |
Events ( |
Promises, readers and writers, |
Backpressure signal |
|
|
Error and cleanup helpers |
|
|
Where it is native |
Node.js: |
|
Portability |
Node.js only (partially emulated elsewhere) |
The same code runs in browsers, Deno, Bun and workers |
ReadableStream
A ReadableStream represents a source of data. You create one from an underlying source, an object with start(), pull() and cancel()
callbacks, and an optional queuing strategy. Chunks that the source enqueues wait in an internal queue; the highWaterMark of the strategy is
the size beyond which the stream asks for no more data, and controller.desiredSize tells the source how much room remains. Node.js calls
pull() only while the queue is below the mark, which is how a web stream does backpressure without events. The easiest way to build one is
ReadableStream.from() with any iterable or async iterable (an array, a generator, an async generator), and the easiest way to consume one is
for await.
console.log(ReadableStream === (await import('node:stream/web')).ReadableStream);
const stream = ReadableStream.from(['Dune', 'Emma', 'Ulysses']);
for await (const title of stream) {
console.log(title);
}
// A pull-based source: pull() is called only while the queue is below the highWaterMark.
let n = 0;
const counter = new ReadableStream(
{
pull(controller) {
console.log('pull, desiredSize =', controller.desiredSize);
controller.enqueue(n++);
if (n === 5) controller.close();
},
cancel(reason) {
console.log('cancelled:', reason);
},
},
{ highWaterMark: 2 },
);
const reader = counter.getReader();
console.log(await reader.read());
console.log(await reader.read());
await reader.cancel('enough');
console.log(await reader.closed);
true
Dune
Emma
Ulysses
pull, desiredSize = 2
{ done: false, value: 0 }
pull, desiredSize = 2
{ done: false, value: 1 }
cancelled: enough
undefined
Source: Node.js API: Web Streams API (ReadableStream), Node.js API: Web Streams API (ReadableStream.from)
Three behaviours are specific to the web model and surprise people coming from Node.js streams:
-
Locking. A stream has at most one reader at a time.
getReader(),for await,pipeTo()andpipeThrough()all lock it (readableStream.lockedbecomestrue) untilreleaseLock()or the end of the iteration. Reading a locked stream throws. -
Early exit cancels. Leaving a
for awaitloop withbreak,returnorthrowcancels the stream, which calls the source’scancel()callback. To keep the stream, iteratestream.values({ preventCancel: true }). Node.js streams, by contrast, have to be destroyed explicitly. -
Cancel is not close.
reader.cancel(reason)tells the source that nobody will read any more, andcontroller.close()is how the source says it has no more data. The first is the consumer’s signal, the second the producer’s.
Streams are single-consumer by design. To read the same data twice, use stream.tee(), which returns two branches that each receive every chunk
(and locks the original). The data is buffered for the slower branch, so a branch that is never read keeps memory growing, which is the web
equivalent of an ignored backpressure signal.
Byte streams and BYOB readers
A stream created with type: 'bytes' is a byte stream: its chunks must be ArrayBufferView`s, and besides the default reader it supports a
`ReadableStreamBYOBReader ("bring your own buffer"), where the consumer passes the buffer to fill. That avoids allocating a new Uint8Array per read,
which is the point on hot paths such as parsers. The buffer you pass is transferred to the stream, so you must use the returned value, not
your original reference, and you can hand its memory back for the next read.
const bytes = new ReadableStream({
type: 'bytes',
start(controller) {
controller.enqueue(new TextEncoder().encode('Dune,Herbert\n'));
controller.close();
},
});
const reader = bytes.getReader({ mode: 'byob' });
let buffer = new Uint8Array(4);
while (true) {
const { done, value } = await reader.read(buffer);
if (done) break;
console.log(value.byteLength, JSON.stringify(new TextDecoder().decode(value)));
buffer = new Uint8Array(value.buffer); // hand the same memory back
}
4 "Dune"
4 ",Her"
4 "bert"
1 "\n"
Source: Node.js API: Web Streams API (ReadableStreamBYOBReader), Node.js API: Web Streams API (ReadableByteStreamController)
A stream that is not a byte stream rejects a BYOB reader with ERR_INVALID_ARG_VALUE ("must be a byte stream"); in the experiments behind this
page that is what getReader({ mode: 'byob' }) did on a stream converted with Readable.toWeb() on Node.js 22, because the type: 'bytes' option of
Readable.toWeb() only exists from v25.4.0 and v24.14.0.
WritableStream
A WritableStream is a destination. Its underlying sink has start(), write(chunk, controller), close() and abort(reason) callbacks, and
write() may return a promise: the stream does not call it again for the next chunk until that promise settles. You write through a
WritableStreamDefaultWriter obtained with getWriter(), which exposes the backpressure state directly: writer.desiredSize is the room left
in the queue and await writer.ready resolves when there is room. This is the web version of write() returning false followed by 'drain'.
const upper = new TransformStream({
transform(chunk, controller) {
controller.enqueue(chunk.toUpperCase());
},
});
const lines = [];
const sink = new WritableStream({
write(chunk) {
lines.push(chunk);
},
close() {
console.log('closed with', lines);
},
});
await ReadableStream.from(['dune', 'emma']).pipeThrough(upper).pipeTo(sink);
// Writer API and backpressure: ready resolves when desiredSize > 0
const w = new WritableStream(
{ write() { return new Promise((resolve) => setTimeout(resolve, 10)); } },
{ highWaterMark: 1 },
).getWriter();
console.log('desiredSize', w.desiredSize);
w.write('x');
console.log('after one write', w.desiredSize);
await w.ready;
console.log('ready again', w.desiredSize);
await w.close();
closed with [ 'DUNE', 'EMMA' ]
desiredSize 1
after one write 0
ready again 1
Source: Node.js API: Web Streams API (WritableStream), Node.js API: Web Streams API (WritableStreamDefaultWriter)
Do not call writer.write() in a tight loop without awaiting writer.ready (or the promise write() returns): the queue grows without bound exactly as
it does when a Node.js producer ignores the false from write(). pipeTo() does this for you.
TransformStream and composing pipelines
A TransformStream connects a writable side to a readable side: whatever you write is passed through transform(chunk, controller), which
calls controller.enqueue() zero, one or many times, and then comes out of the readable side. flush(controller) runs when the writable side is closed,
which is where a stateful transformer emits what it still holds. readable.pipeThrough(transformStream) returns the readable half, so steps chain,
and pipeTo(writableStream) ends the chain and returns a promise that resolves when everything has been written and closed. The Bookshelf
import needs a classic stateful step: chunks do not respect line boundaries, so the transformer keeps the incomplete tail between calls.
class LineSplitter extends TransformStream {
constructor() {
let rest = '';
super({
transform(chunk, controller) {
const parts = (rest + chunk).split('\n');
rest = parts.pop();
for (const line of parts) controller.enqueue(line);
},
flush(controller) {
if (rest) controller.enqueue(rest);
},
});
}
}
const out = await Array.fromAsync(
ReadableStream.from(['title,au', 'thor\nDune,Her', 'bert\nEmma,Austen'])
.pipeThrough(new LineSplitter()),
);
console.log(out);
// tee: two branches, each sees every chunk
const [a, b] = ReadableStream.from([1, 2, 3]).tee();
console.log(await Array.fromAsync(a), await Array.fromAsync(b));
// Errors: an error in the transform rejects the pipeTo() promise
try {
await ReadableStream.from(['a'])
.pipeThrough(new TransformStream({ transform() { throw new Error('boom'); } }))
.pipeTo(new WritableStream());
} catch (e) { console.log('pipeTo rejected:', e.message); }
// Cancellation with an AbortSignal
const ac = new AbortController();
const slow = new ReadableStream({
async pull(c) { await new Promise((r) => setTimeout(r, 20)); c.enqueue('tick'); },
});
const p = slow.pipeTo(new WritableStream(), { signal: ac.signal });
setTimeout(() => ac.abort(), 50);
await p.catch((e) => console.log(e.name));
[ 'title,author', 'Dune,Herbert', 'Emma,Austen' ]
[ 1, 2, 3 ] [ 1, 2, 3 ]
pipeTo rejected: boom
AbortError
Source: Node.js API: Web Streams API (TransformStream), Node.js API: Web Streams API (readableStream.pipeThrough), WHATWG Streams Standard
The error and cancellation rules mirror what pipeline() gives Node.js streams: an error in any stage errors the others and rejects the
pipeTo() promise, and an AbortSignal passed in the signal option stops the transfer. The preventAbort, preventCancel and preventClose options
turn the individual propagation steps off when you need to reuse a stream after a failed pipe. TransformStream accepts a cancel transformer callback
since v21.5.0 and v20.14.0, which is where you release resources when the readable side is cancelled. Web streams can also be transferred to a
worker with postMessage() (listing them in the transfer list), which Node.js streams cannot; see
Worker Threads.
TextEncoderStream and CompressionStream
Four ready-made transform streams exist as globals, so a byte pipeline needs no hand-written transformer:
-
TextEncoderStreamturns string chunks into UTF-8Uint8Arraychunks;TextDecoderStreamturns bytes back into strings. The decoder keeps state, so a multi-byte character split across two chunks is reassembled (the example below splits the three bytes of a character in two and gets it back).TextDecoderStreamtakes an encoding (default'utf-8') and thefatalandignoreBOMoptions ofTextDecoder. -
CompressionStream(format)compresses bytes;DecompressionStream(format)reverses it. The formats are'gzip','deflate'(the zlib wrapper),'deflate-raw'and, in current lines,'brotli'. -
Both directions are real transform streams, so they work with
pipeThrough()and also directly inside Node.jspipeline(), as a later example shows.
import { text, json, buffer } from 'node:stream/consumers';
const encoded = ReadableStream.from(['Café ', '☕']).pipeThrough(new TextEncoderStream());
const chunks = await Array.fromAsync(encoded);
console.log(chunks.map((c) => c.constructor.name), chunks[0].byteLength);
// A multi-byte character split across chunks is reassembled by the decoder
const bytes = new TextEncoder().encode('☕');
const split = ReadableStream.from([bytes.subarray(0, 1), bytes.subarray(1)]);
console.log(await text(split.pipeThrough(new TextDecoderStream())));
for (const format of ['gzip', 'deflate', 'deflate-raw', 'brotli']) {
const input = 'Dune,Herbert\n'.repeat(1000);
const packed = await buffer(
ReadableStream.from([input])
.pipeThrough(new TextEncoderStream())
.pipeThrough(new CompressionStream(format)),
);
const unpacked = await text(
ReadableStream.from([packed]).pipeThrough(new DecompressionStream(format)),
);
console.log(format, input.length, '->', packed.length, unpacked === input);
}
console.log(await json(ReadableStream.from([
new TextEncoder().encode('{"ok":'),
new TextEncoder().encode('true}'),
])));
[ 'Uint8Array', 'Uint8Array' ] 6
☕
gzip 13000 -> 78 true
deflate 13000 -> 66 true
deflate-raw 13000 -> 60 true
brotli 13000 -> 27 true
{ ok: true }
Source: Node.js API: Web Streams API (TextEncoderStream), Node.js API: Web Streams API (CompressionStream), WHATWG Compression Standard, WHATWG Encoding Standard
The example also shows the third helper family, node:stream/consumers: text(), json(), buffer(), arrayBuffer(), blob() and (from v25.6.0 and
v24.14.0) bytes() read a whole stream into one value. They accept web streams, Node.js Readable`s and async iterators, so they are the shortest
bridge between the two worlds, but they buffer everything, so they belong on small, bounded inputs only. `CompressionStream is the portable choice;
the node:zlib module offers more (compression levels, dictionaries, flush modes, zstd, one-shot calls, crc32) and is the better tool when you
are on Node.js only, see Compression with zlib.
Converting between Node.js streams and web streams
Each Node.js stream class has a pair of static conversion methods. All of them are stable since v24.0.0 and v22.17.0:
| Method | Converts | Notable options |
|---|---|---|
|
A Node.js |
|
|
A |
|
|
A Node.js |
None. |
|
A |
|
|
A Node.js |
|
|
A pair of web streams into a Node.js |
|
Three things to remember about the bridge. The conversion wraps the original stream rather than copying the data, so you should treat the original as owned by the wrapper and stop using it directly; backpressure and errors cross the bridge (observed in the last example). A Node.js Readable yields Buffer`s, so after `toWeb() the chunks are
Uint8Array views (a Buffer is one), which is what TextDecoderStream and CompressionStream need. A Readable in object mode produces
arbitrary values, which only object-friendly consumers can handle. And errors cross the bridge both ways: destroying a Node.js stream with an error
rejects the pending read() on the web side, as the last example shows.
import { Readable, Writable } from 'node:stream';
import { pipeline } from 'node:stream/promises';
import { createReadStream, createWriteStream } from 'node:fs';
import { readFile } from 'node:fs/promises';
// Node -> Web
const web = Readable.toWeb(createReadStream('books.csv'));
console.log(web.constructor.name);
for await (const c of web.pipeThrough(new TextDecoderStream())) console.log(JSON.stringify(c));
// Web -> Node
const node = Readable.fromWeb(ReadableStream.from(['a', 'b']), { objectMode: true });
for await (const c of node) console.log(c);
// Mix both worlds in pipeline(): web streams are accepted directly
await pipeline(
createReadStream('books.csv'),
new CompressionStream('gzip'),
createWriteStream('books.csv.gz'),
);
console.log((await readFile('books.csv.gz')).byteLength, 'bytes gzipped');
// Writable.toWeb: let a web stream write into a Node.js Writable
await ReadableStream.from(['x\n', 'y\n']).pipeTo(Writable.toWeb(createWriteStream('out.txt')));
console.log(JSON.stringify(await readFile('out.txt', 'utf8')));
// A byte stream (Node.js 24.14+, 25.4+) can be read with a BYOB reader
const b = Readable.toWeb(createReadStream('books.csv'), { type: 'bytes' });
console.log(b.getReader({ mode: 'byob' }).constructor.name);
// Errors cross the bridge
const r = Readable.toWeb(new Readable({ read() { this.destroy(new Error('disk gone')); } }));
await r.getReader().read().catch((e) => console.log('web side rejected:', e.message));
type: 'bytes' line throws ERR_INVALID_ARG_VALUE: the option does not exist there, see the version map)ReadableStream
"title,author\nDune,Herbert\nEmma,Austen\nUlysses,Joyce\n"
a
b
72 bytes gzipped
"x\ny\n"
ReadableStreamBYOBReader
web side rejected: disk gone
Source: Node.js API: Stream (stream.Readable.toWeb), Node.js API: Stream (stream.Readable.fromWeb), Node.js API: Stream (stream.Writable.toWeb), Node.js API: Stream (stream.pipeline)
Web streams that Node.js APIs give you
Several built-ins already speak web streams, so you often convert only at the edges:
-
fetch()andResponse:response.bodyis aReadableStream, andnew Response(body)accepts one. The next example shows both directions. The consequence for requests is covered in Fetch & HTTP Clients. -
Blob.stream()returns aReadableStreamofUint8Arraychunks. -
filehandle.readableWebStream()fromnode:fs/promisesgives a byte stream over an open file, which you pair withfs.open()when you want web streams withoutcreateReadStream(); see File System. -
stream.pipeline(),stream.finished()andDuplex.from()accept web streams, so a mixed chain needs no explicit conversion.
import { Readable } from 'node:stream';
import { pipeline } from 'node:stream/promises';
import { createWriteStream } from 'node:fs';
import { open } from 'node:fs/promises';
import http from 'node:http';
const server = http.createServer((req, res) => res.end('Dune,Herbert\nEmma,Austen\n')).listen({ port: 0 });
await new Promise((resolve) => server.once('listening', resolve));
const { port } = server.address();
const res = await fetch(`http://127.0.0.1:${port}/`);
console.log(res.body.constructor.name);
await pipeline(Readable.fromWeb(res.body), createWriteStream('fetched.csv'));
// A Response body must be bytes: string chunks are rejected
const ok = new Response(Readable.toWeb(Readable.from([Buffer.from('a'), Buffer.from('b')])));
console.log(await ok.text());
const bad = new Response(Readable.toWeb(Readable.from(['a', 'b'])));
await bad.text().catch((e) => console.log(e.name, e.message));
const fh = await open('books.csv');
for await (const c of fh.readableWebStream()) console.log(c.constructor.name, c.byteLength);
await fh.close();
console.log((await new Blob(['hi']).stream().getReader().read()).value);
server.close();
ReadableStream
ab
TypeError Received non-Uint8Array chunk
Uint8Array 52
Uint8Array(2) [ 104, 105 ]
Source: Node.js API: Globals (fetch), Node.js API: File system (filehandle.readableWebStream), Node.js API: Web Streams API
The failed Response is the one pitfall of this section worth memorising: a Readable made from strings converts to a web stream of strings, and
fetch-family consumers (Response, Request bodies) require Uint8Array chunks, so the read fails with TypeError: Received non-Uint8Array chunk.
Encode first (a Buffer is a Uint8Array, or pipe through TextEncoderStream).
A Bookshelf pipeline, entirely in web streams
The Node.js version of the Bookshelf CSV export is a pipeline() of a file Readable, a parsing Transform and a file Writable. Written with web
streams the stages are the same, and the only Node.js-specific parts are the two file bridges at the ends. Everything between them (the decoder,
the line splitter, the mapping step, the encoder and the gzip step) is portable, and could run in a browser or an edge worker unchanged.
import { Readable, Writable } from 'node:stream';
import { createReadStream, createWriteStream } from 'node:fs';
import { text } from 'node:stream/consumers';
class LineSplitter extends TransformStream {
constructor() {
let rest = '';
super({
transform(chunk, controller) {
const parts = (rest + chunk).split('\n');
rest = parts.pop();
for (const line of parts) controller.enqueue(line);
},
flush(controller) {
if (rest) controller.enqueue(rest);
},
});
}
}
await Readable.toWeb(createReadStream('books.csv'))
.pipeThrough(new TextDecoderStream())
.pipeThrough(new LineSplitter())
.pipeThrough(new TransformStream({
transform(line, controller) {
const [title, author] = line.split(',');
if (title !== 'title') controller.enqueue(`${JSON.stringify({ title, author })}\n`);
},
}))
.pipeThrough(new TextEncoderStream())
.pipeThrough(new CompressionStream('gzip'))
.pipeTo(Writable.toWeb(createWriteStream('books.ndjson.gz')));
console.log(await text(
Readable.toWeb(createReadStream('books.ndjson.gz')).pipeThrough(new DecompressionStream('gzip')),
));
{"title":"Dune","author":"Herbert"}
{"title":"Emma","author":"Austen"}
{"title":"Ulysses","author":"Joyce"}
The naive line splitter in this example does not handle quoted fields, which is why the real Bookshelf importer in Implementing Custom Streams and any production CSV code should use a parser library.
When to use which
Neither API is deprecated, and neither is going away: Node.js streams are the foundation of fs, net, http and zlib, and web streams are the
standard that fetch() and the other runtimes use. The choice is mostly decided by what the code touches and where it must run.
| Situation | Prefer | Why |
|---|---|---|
File, socket, HTTP server request or response, child process stdio |
Node.js streams |
These APIs produce and consume them natively; no conversion layer, and |
Code shared between Node.js and browsers, Deno, Bun or edge workers |
Web streams |
Only the WHATWG API is portable; keep Node.js specifics at the edges with |
Consuming a |
Web streams, converting with |
|
Compression inside a portable pipeline |
|
Standard API in all runtimes; for Node.js-only code |
Text encoding or decoding of chunks |
|
Correct across chunk boundaries and available everywhere. The Node.js alternative is |
Custom streaming source or transformer with a promise-based code style |
Web streams, or an async generator with |
Generators and promises compose well with both; see Implementing Custom Streams. |
Streaming data to or from a worker thread |
Web streams (transferable) or a |
Web streams can be transferred with |
Object-mode pipelines, |
Node.js streams |
Much of the ecosystem (parsers, upload handlers, loggers) is written against |
Two final cautions. Do not convert at every step of a pipeline: each toWeb() or fromWeb() adds a wrapper and a queue, so convert once at each
boundary and keep the interior in a single model. And prefer the stable interop methods over the hand-rolled adapters found in older code,
for example a Readable built by hand around reader.read(): fromWeb() is the documented bridge and takes the encoding, object-mode,
highWaterMark and signal options.
What changed recently
| Version | Change |
|---|---|
v18.0.0 |
The web stream classes, |
v20.6.0 |
|
v20.12.0 and v21.2.0 |
|
v21.0.0 |
The Web Streams API is no longer experimental. |
v22.15.0 and v23.8.0 |
|
v22.17.0 and v24.0.0 |
|
v22.20.0 and v24.7.0 |
|
v24.14.0 and v25.4.0 |
|
v25.7.0 |
|
Where the books are outdated or incomplete
Efficient Node.js (Samer Buna, O’Reilly 2025) teaches streams through the Node.js API only: pipe(), pipeline(), the readable and writable events and
for await over a Readable. In the passages consulted for this page it does not cover web streams, toWeb() and fromWeb(), or the
CompressionStream family, so a reader who only knows the book will not know why fetch() hands back an object without .pipe(). Node.js Projects
(Jonathan Wexler, O’Reilly 2025) uses streams at the level of concepts and file reading and writing, and likewise does not mention the web stream
classes. Both books predate the stabilisation of the interoperability methods (v24.0.0 and v22.17.0) and the brotli format (v24.7.0), so older
advice such as "web streams are experimental" or "convert with a hand-written adapter" no longer applies.
| Statement you may meet | Current situation |
|---|---|
"Web streams are experimental in Node.js" |
Stable since v21.0.0; the interop methods are stable since v24.0.0 and v22.17.0. |
“fetch()` returns a Node.js stream you can `.pipe()” |
|
"You must import |
They are globals since v18.0.0; the import still works and is the same object. |
"`CompressionStream` only does gzip and deflate" |
|
"A Response can be built from any stream" |
Its body chunks must be |
References
-
Node.js API: Web Streams API, https://nodejs.org/docs/latest/api/webstreams.html
-
Node.js API: Stream (Readable.toWeb, Readable.fromWeb, Writable.toWeb, Duplex.fromWeb, pipeline), https://nodejs.org/docs/latest/api/stream.html
-
Node.js API: File system (filehandle.readableWebStream), https://nodejs.org/docs/latest/api/fs.html
-
Node.js API: Globals (fetch), https://nodejs.org/docs/latest/api/globals.html
-
Node.js API: Zlib, https://nodejs.org/docs/latest/api/zlib.html
-
Node.js Learn: How to use streams, https://nodejs.org/learn/modules/how-to-use-streams
-
WHATWG Streams Standard, https://streams.spec.whatwg.org/
-
WHATWG Compression Standard, https://compression.spec.whatwg.org/
-
WHATWG Encoding Standard, https://encoding.spec.whatwg.org/
-
O’Reilly: Efficient Node.js, Samer Buna, https://www.oreilly.com/library/view/efficient-node-js/9781098145187
-
O’Reilly: Node.js Projects, Jonathan Wexler, https://www.oreilly.com/library/view/node-js-projects/9781098173135/