stream: make endSync() optional in pipeToSync()

pipeToSync() threw ERR_INVALID_ARG_TYPE before writing anything when
the writer had no endSync() method and preventClose was not set.
endSync() is optional: the spec (pipeToSync() step 7) only calls it if
the writer has it, and pipeTo() already treats it that way. A writer
without endSync() now receives the data and is not closed. pipeToSync()
still never falls back to the async end().

This also fixes the from-sync-writev case of
benchmark/streams/iter-from-batching.js, whose writer has no endSync().

testPipeToSyncNoEndSync asserted the previous rejection and now checks
that the data is written and end() is not called. The documentation of
the writer requirements is corrected as well: only writeSync() is
required.

Assisted-by: OpenCode
Signed-off-by: James M Snell <jasnell@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/66648
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
Reviewed-By: Gürgün Dayıoğlu <hey@gurgun.day>
This commit is contained in:
James M Snell authored and Node.js GitHub Bot committed 2026-10-15 13:26:45 +00:00
1 parent 82563d8c39
commit 5bcade91de
3 files changed
+18 -20

No files matched your search

+6 -3
View File
@@ -714,7 +714,7 @@ added:
* `source` {Iterable} The sync data source.
* `...transforms` {Function|Object} Zero or more sync transforms.
* `writer` {Object} Destination with `write(chunk)` method.
* `writer` {Object} Destination with a `writeSync(chunk)` method.
* `options` {Object}
* `failOnIncompleteClose` {boolean} If `true`, call `writer.fail()` when
`writer.endSync()` cannot close the writer synchronously. Ignored when
@@ -727,8 +727,11 @@ added:
Synchronous version of [`pipeTo()`][]. The `source`, all transforms, and the
`writer` must be synchronous. Cannot accept async iterables or promises.
The `writer` must have the `*Sync` methods (`writeSync`, `writevSync`,
`endSync`) and `fail()` for this to work.
The `writer` must have a `writeSync()` method. The other methods are
optional: `writevSync()` is used for batches of more than one chunk if it is
present, `endSync()` is called to close the writer (unless `preventClose` is
`true`), and `fail()` is called if the pipe fails (unless `preventFail` is
`true`). A writer without `endSync()` is not closed.
`pipeToSync()` never falls back to the asynchronous writer methods. If
`writer.endSync()` returns `-1` because the writer cannot close synchronously
+3 -6
View File
@@ -1032,12 +1032,9 @@ function pipeToSync(source, ...args) {
context: 'options',
});
const hasWritevSync = typeof writer.writevSync === 'function';
// endSync() is optional: a writer without it is not closed.
const endSync = writer.endSync;
if (!options.preventClose && typeof endSync !== 'function') {
throw new ERR_INVALID_ARG_TYPE(
'writer.endSync', 'Function', endSync);
}
const hasEndSync = typeof endSync === 'function';
// Normalize source and create pipeline
const normalized = fromSync(source);
@@ -1074,7 +1071,7 @@ function pipeToSync(source, ...args) {
}
}
if (!options.preventClose) {
if (!options.preventClose && hasEndSync) {
closedSync = FunctionPrototypeCall(endSync, writer) >= 0;
}
} catch (error) {
+9 -11
View File
@@ -47,20 +47,18 @@ async function testPipeToSyncEndSyncFailureDoesNotFailWriter() {
assert.strictEqual(await result, 'abcdef');
}
// pipeToSync requires endSync() when closing is enabled.
// pipeToSync does not require endSync().
async function testPipeToSyncNoEndSync() {
let writeCalled = false;
let endCalled = false;
// endSync() is optional. Without it the data is still written and the
// writer is not closed; pipeToSync() never falls back to end().
const written = [];
const writer = {
writeSync() { writeCalled = true; return true; },
end() { endCalled = true; },
writeSync(chunk) { written.push(chunk); return true; },
end: common.mustNotCall(),
fail: common.mustNotCall(),
};
assert.throws(
() => pipeToSync(fromSync('data'), writer),
{ code: 'ERR_INVALID_ARG_TYPE' },
);
assert.strictEqual(writeCalled, false);
assert.strictEqual(endCalled, false);
assert.strictEqual(pipeToSync(fromSync('data'), writer), 4);
assert.deepStrictEqual(written, [new TextEncoder().encode('data')]);
}
// pipeToSync with preventFail: true — source error does NOT call fail()