diff --git a/doc/api/stream_iter.md b/doc/api/stream_iter.md index aa96ce02813..e2f30ce6a94 100644 --- a/doc/api/stream_iter.md +++ b/doc/api/stream_iter.md @@ -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 diff --git a/lib/internal/streams/iter/pull.js b/lib/internal/streams/iter/pull.js index 6dff97e42be..70e89a99423 100644 --- a/lib/internal/streams/iter/pull.js +++ b/lib/internal/streams/iter/pull.js @@ -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) { diff --git a/test/parallel/test-stream-iter-pipeto-edge.js b/test/parallel/test-stream-iter-pipeto-edge.js index 0c5448db5a8..082871a9e4c 100644 --- a/test/parallel/test-stream-iter-pipeto-edge.js +++ b/test/parallel/test-stream-iter-pipeto-edge.js @@ -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()