Commit 89fef14f69a for nodejs
commit 89fef14f69afcd1532e711bb98530af7d9799c1d
Author: James M Snell <jasnell@gmail.com>
Date: Sat Oct 3 15:13:47 2026 +0000
doc: document stream/iter behaviors and Node.js extensions
Document behaviors that were previously only visible in the code:
- the writer "closing" state after end()/endSync(),
- writes made from argument conversion being ordered first,
- zero-length push() writes not being buffered,
- broadcast.cancel() also closing the paired writer,
- merge() not waiting for the other sources' cleanup on error,
- the options argument passed to the tap() callback,
- FileHandle writer() ordering of un-awaited writes, and the lazy
locking of FileHandle pull() and pullSync().
Also link the WinterTC Iterable Streams API draft and list the exports
that are Node.js extensions to it.
Assisted-by: OpenCode
Signed-off-by: James M Snell <jasnell@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66483
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
diff --git a/doc/api/fs.md b/doc/api/fs.md
index f49e131ee7b..e327a330a14 100644
--- a/doc/api/fs.md
+++ b/doc/api/fs.md
@@ -412,8 +412,9 @@ Return the file contents as an async iterable using the
chunks (default 128 KB). If transforms are provided, they are applied
via [`stream/iter pull()`][].
-The file handle is locked while the iterable is being consumed and unlocked
-when iteration completes, an error occurs, or the consumer breaks.
+The file handle is locked from the first read of the iterable, and unlocked
+when iteration completes, an error occurs, or the consumer breaks. An iterable
+that is never read does not lock the file handle.
This function is only available when the `--experimental-stream-iter` flag is
enabled.
@@ -487,8 +488,8 @@ Synchronous counterpart of [`filehandle.pull()`][]. Returns a sync iterable
that reads the file using synchronous I/O on the main thread. Reads are
performed in `chunkSize`-byte chunks (default 128 KB).
-The file handle is locked while the iterable is being consumed. Unlike the
-async `pull()`, this method does not support `AbortSignal` since all
+The file handle is locked from the first read of the iterable until iteration
+ends, as with [`filehandle.pull()`][]. Unlike the async `pull()`, this method does not support `AbortSignal` since all
operations are synchronous.
This function is only available when the `--experimental-stream-iter` flag is
@@ -1134,6 +1135,11 @@ The writer supports both `Symbol.asyncDispose` and `Symbol.dispose`:
for it to complete.
* `using w = fh.writer()` — calls `fail()` unconditionally.
+Async writes (`write()` and `writev()`) that are started without awaiting the
+previous one are performed one at a time, in the order they were called, so
+they never overlap in the file. A queued write is not performed if the writer
+fails, or its `signal` aborts, before its turn.
+
The `writeSync()` and `writevSync()` methods enable the try-sync fast path
used by [`stream/iter pipeTo()`][]. When the reader's chunk size matches the
writer's `chunkSize`, all writes in a `pipeTo()` pipeline complete
diff --git a/doc/api/stream_iter.md b/doc/api/stream_iter.md
index fb4a26a6cd6..0e78c64f202 100644
--- a/doc/api/stream_iter.md
+++ b/doc/api/stream_iter.md
@@ -18,6 +18,13 @@ functions or objects with a `transform` method.
Data flows in **batches** ({Uint8Array\[]} per iteration) to amortize the cost
of async operations.
+The module implements the WinterTC [Iterable Streams API][] draft. The
+classic stream interop functions ([`fromReadable()`][], [`fromWritable()`][],
+[`toReadable()`][], [`toReadableSync()`][] and [`toWritable()`][]),
+[`Broadcast.from()`][], [`Share.from()`][], [`SyncShare.fromSync()`][] and the
+protocol symbols exported by `Stream` are Node.js extensions that are not part
+of the draft.
+
```mjs
import { from, pull, text } from 'node:stream/iter';
import { compressGzip, decompressGzip } from 'node:zlib/iter';
@@ -407,6 +414,18 @@ converted to a `USVString` and then UTF-8 encoded. `writev()` and
chunks. Writer option dictionaries treat `null` as an empty dictionary and
ignore unknown members.
+Arguments are converted before the write itself starts. If the conversion runs
+user code (for example a `toString()` method, or the iterator of a `writev()`
+argument) that writes to the same writer, those writes are ordered before the
+write whose argument is being converted, and they count against the same
+backpressure limits.
+
+After `end()` or `endSync()` has been called, and until all buffered data has
+been consumed, the writer is _closing_. While closing, `canWrite` is `null`,
+`write()` and `writev()` reject with a `TypeError`, `writeSync()` and
+`writevSync()` return `false`, `endSync()` returns `-1`, and calling `end()`
+again returns the same promise as the first call.
+
Each async method has a synchronous `*Sync` counterpart designed for a
try-fallback pattern: attempt the fast synchronous path first, and fall back
to the async version only when the synchronous call indicates it could not
@@ -869,6 +888,10 @@ run().catch(console.error);
The writer returned by `push()` conforms to the \[Writer interface]\[].
+Zero-length chunks are accepted without being buffered: they are not delivered
+to the consumer, and `writeSync()` and `write()` report success for them even
+when backpressure is active.
+
## Duplex channels
### `duplex([options])`
@@ -1275,7 +1298,12 @@ added:
Merge multiple async iterables by yielding batches in temporal order
(whichever source produces data first). All sources are consumed
-concurrently.
+concurrently, with at most one pending `next()` call per source.
+
+If a source fails, the returned iterable rejects with its error. `merge()`
+calls `return()` on the other sources but does not wait for it to settle: an
+async generator source that is suspended in an `await` only runs its cleanup
+once that `await` completes.
```mjs
import { from, merge, text } from 'node:stream/iter';
@@ -1303,8 +1331,9 @@ added:
- v24.20.0
-->
-* `callback` {Function} `(chunks) => void` Called with each batch and with
- `null` when the source ends.
+* `callback` {Function} `(chunks, options) => void` Called with each batch and
+ with `null` when the source ends. `options.signal` is the pipeline's
+ {AbortSignal}.
* Returns: {Function} A stateless transform.
Create a pass-through transform that observes batches without modifying them.
@@ -1433,6 +1462,10 @@ run().catch(console.error);
Cancel the broadcast. If `reason` is provided, all consumers reject with that
exact reason. If it is omitted, consumers complete normally.
+Cancelling also closes the paired writer: afterwards its `canWrite` is `null`
+and `write()` rejects with a `TypeError`. This lets a [`Broadcast.from()`][]
+pump stop pulling from its source.
+
#### `broadcast.consumerCount`
* {number}
@@ -2342,13 +2375,19 @@ const stream = fromSync(new Greeting('world'));
console.log(textSync(stream)); // 'hello world'
```
+[Iterable Streams API]: https://iter-streams.proposal.wintertc.org/
[`--experimental-stream-iter`]: cli.md#--experimental-stream-iter
+[`Broadcast.from()`]: #broadcastfrominput-options
+[`Share.from()`]: #static-method-sharefrominput-options
+[`SyncShare.fromSync()`]: #static-method-syncsharefromsyncinput-options
[`array()`]: #arraysource-options
[`arrayBuffer()`]: #arraybuffersource-options
[`bytes()`]: #bytessource-options
[`dump()`]: #drainsource-options
[`from()`]: #frominput
+[`fromReadable()`]: #fromreadablereadable
[`fromSync()`]: #fromsyncinput
+[`fromWritable()`]: #fromwritablewritable-options
[`node:zlib/iter`]: zlib.md#iterable-compression
[`ondrain()`]: #ondraindrainable
[`pipeTo()`]: #pipetosource-transforms-writer-options
@@ -2360,3 +2399,6 @@ console.log(textSync(stream)); // 'hello world'
[`tap()`]: #tapcallback
[`text()`]: #textsource-options
[`toAsyncStreamable`]: #streamtoasyncstreamable
+[`toReadable()`]: #toreadablesource-options
+[`toReadableSync()`]: #toreadablesyncsource-options
+[`toWritable()`]: #towritablewriter