pax_global_header00006660000000000000000000000064142615564520014524gustar00rootroot0000000000000052 comment=c5d78303545b895c738693f3c14a4f04c3038adc duplexer3-1.0.0/000077500000000000000000000000001426155645200134355ustar00rootroot00000000000000duplexer3-1.0.0/.editorconfig000066400000000000000000000002571426155645200161160ustar00rootroot00000000000000root = true [*] indent_style = tab end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.yml] indent_style = space indent_size = 2 duplexer3-1.0.0/.gitattributes000066400000000000000000000000231426155645200163230ustar00rootroot00000000000000* text=auto eol=lf duplexer3-1.0.0/.gitignore000066400000000000000000000000271426155645200154240ustar00rootroot00000000000000node_modules yarn.lock duplexer3-1.0.0/.npmrc000066400000000000000000000000231426155645200145500ustar00rootroot00000000000000package-lock=false duplexer3-1.0.0/example.js000077500000000000000000000016251426155645200154350ustar00rootroot00000000000000#!/usr/bin/env node import stream from 'node:stream'; import duplexer from './index.js'; const writable = new stream.Writable({objectMode: true}); const readable = new stream.Readable({objectMode: true}); writable._write = function (input, encoding, done) { if (readable.push(input)) { return done(); } readable.once('drain', done); }; readable._read = function () { // Noop }; // Simulate the readable thing closing after a bit writable.once('finish', () => { setTimeout(() => { readable.push(null); }, 500); }); const duplex = duplexer(writable, readable); duplex.on('data', data => { console.log('got data', JSON.stringify(data)); }); duplex.on('finish', () => { console.log('got finish event'); }); duplex.on('end', () => { console.log('got end event'); }); duplex.write('oh, hi there', () => { console.log('finished writing'); }); duplex.end(() => { console.log('finished ending'); }); duplexer3-1.0.0/index.js000066400000000000000000000027501426155645200151060ustar00rootroot00000000000000import stream from 'node:stream'; export function DuplexWrapper(options, writable, readable) { if (typeof readable === 'undefined') { readable = writable; writable = options; options = undefined; } stream.Duplex.call(this, options); if (typeof readable.read !== 'function') { readable = (new stream.Readable(options)).wrap(readable); } this._writable = writable; this._readable = readable; this._waiting = false; writable.once('finish', () => { this.end(); }); this.once('finish', () => { writable.end(); }); readable.on('readable', () => { if (this._waiting) { this._waiting = false; this._read(); } }); readable.once('end', () => { this.push(null); }); if (!options || typeof options.bubbleErrors === 'undefined' || options.bubbleErrors) { writable.on('error', error => { this.emit('error', error); }); readable.on('error', error => { this.emit('error', error); }); } } DuplexWrapper.prototype = Object.create(stream.Duplex.prototype, {constructor: {value: DuplexWrapper}}); DuplexWrapper.prototype._write = function (input, encoding, done) { this._writable.write(input, encoding, done); }; DuplexWrapper.prototype._read = function () { let buffer; let readCount = 0; while ((buffer = this._readable.read()) !== null) { this.push(buffer); readCount++; } if (readCount === 0) { this._waiting = true; } }; export default function duplexer(options, writable, readable) { return new DuplexWrapper(options, writable, readable); } duplexer3-1.0.0/license000066400000000000000000000030101426155645200147740ustar00rootroot00000000000000Copyright (c) 2022, Sindre Sorhus. Copyright (c) 2020, Vsevolod Strukchinsky. Copyright (c) 2013, Deoxxa Development. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. duplexer3-1.0.0/package.json000066400000000000000000000010241426155645200157200ustar00rootroot00000000000000{ "name": "duplexer3", "version": "1.0.0", "description": "Modern version of `duplexer2`", "license": "BSD-3-Clause", "repository": "sindresorhus/duplexer3", "funding": "https://github.com/sponsors/sindresorhus", "type": "module", "exports": "./index.js", "engines": { "node": ">=14.16" }, "scripts": { "test": "xo && mocha" }, "files": [ "index.js" ], "keywords": [ "duplex", "duplexer", "stream", "streams", "join", "combine" ], "devDependencies": { "mocha": "^10.0.0", "xo": "^0.50.0" } } duplexer3-1.0.0/readme.md000066400000000000000000000024611426155645200152170ustar00rootroot00000000000000# duplexer3 > Modern version of `duplexer2` ## Install ```sh npm install duplexer3 ``` ## Usage ```js import stream from 'node:stream'; import duplexer from 'duplexer3'; const writable = new stream.Writable({objectMode: true}); const readable = new stream.Readable({objectMode: true}); writable._write = function (input, encoding, done) { if (readable.push(input)) { done(); } else { readable.once('drain', done); } }; readable._read = function () { // Noop }; // Simulate the readable thing closing after a bit writable.once('finish', () => { setTimeout(() => { readable.push(null); }, 500); }); const duplex = duplexer3(writable, readable); duplex.on('data', data => { console.log('got data', JSON.stringify(data)); }); duplex.on('finish', () => { console.log('got finish event'); }); duplex.on('end', () => { console.log('got end event'); }); duplex.write('oh, hi there', () => { console.log('finished writing'); }); duplex.end(() => { console.log('finished ending'); }); ``` ``` got data 'oh, hi there' finished writing got finish event finished ending got end event ``` ## API ### duplexer(options?, writableStream, readableStream) #### options Type: `object` ##### bubbleErrors Type: `boolean`\ Default: `true` Whether to bubble errors from the underlying readable/writable streams. duplexer3-1.0.0/test.js000066400000000000000000000115611426155645200147560ustar00rootroot00000000000000/* globals describe, it, beforeEach */ import assert from 'node:assert'; import stream from 'node:stream'; import duplexer, {DuplexWrapper} from './index.js'; describe('duplexer3', () => { let writable; let readable; beforeEach(() => { writable = new stream.Writable({objectMode: true}); readable = new stream.Readable({objectMode: true}); writable._write = function (input, encoding, done) { return done(); }; readable._read = function () {}; }); it('should interact with the writable stream properly for writing', done => { const duplex = duplexer(writable, readable); writable._write = function (input, _encoding, _done) { assert.strictEqual(input.toString(), 'well hello there'); return done(); }; duplex.write('well hello there'); }); it('should interact with the readable stream properly for reading', done => { const duplex = duplexer(writable, readable); duplex.on('data', data => { assert.strictEqual(data.toString(), 'well hello there'); return done(); }); readable.push('well hello there'); }); it('should end the writable stream, causing it to finish', done => { const duplex = duplexer(writable, readable); writable.once('finish', done); duplex.end(); }); it('should finish when the writable stream finishes', done => { const duplex = duplexer(writable, readable); duplex.once('finish', done); writable.end(); }); it('should end when the readable stream ends', done => { const duplex = duplexer(writable, readable); // Required to let "end" fire without reading duplex.resume(); duplex.once('end', done); readable.push(null); }); it('should bubble errors from the writable stream when no behaviour is specified', done => { const duplex = duplexer(writable, readable); const originalError = new Error('testing'); duplex.on('error', error => { assert.strictEqual(error, originalError); return done(); }); writable.emit('error', originalError); }); it('should bubble errors from the readable stream when no behaviour is specified', done => { const duplex = duplexer(writable, readable); const originalError = new Error('testing'); duplex.on('error', error => { assert.strictEqual(error, originalError); return done(); }); readable.emit('error', originalError); }); it('should bubble errors from the writable stream when bubbleErrors is true', done => { const duplex = duplexer({bubbleErrors: true}, writable, readable); const originalError = new Error('testing'); duplex.on('error', error => { assert.strictEqual(error, originalError); return done(); }); writable.emit('error', originalError); }); it('should bubble errors from the readable stream when bubbleErrors is true', done => { const duplex = duplexer({bubbleErrors: true}, writable, readable); const originalError = new Error('testing'); duplex.on('error', error => { assert.strictEqual(error, originalError); return done(); }); readable.emit('error', originalError); }); it('should not bubble errors from the writable stream when bubbleErrors is false', done => { const duplex = duplexer({bubbleErrors: false}, writable, readable); const timeout = setTimeout(done, 25); duplex.on('error', _error => { clearTimeout(timeout); return done(new Error('shouldn\'t bubble error')); }); // Prevent uncaught error exception writable.on('error', () => {}); writable.emit('error', new Error('testing')); }); it('should not bubble errors from the readable stream when bubbleErrors is false', done => { const duplex = duplexer({bubbleErrors: false}, writable, readable); const timeout = setTimeout(done, 25); duplex.on('error', _error => { clearTimeout(timeout); return done(new Error('shouldn\'t bubble error')); }); // Prevent uncaught error exception readable.on('error', () => {}); readable.emit('error', new Error('testing')); }); it('should export the DuplexWrapper constructor', () => { assert.equal(typeof DuplexWrapper, 'function'); }); it('should not force flowing-mode', done => { const writable = new stream.PassThrough(); const readable = new stream.PassThrough(); assert.equal(readable._readableState.flowing, null); const duplexStream = duplexer(writable, readable); duplexStream.end('aaa'); assert.equal(readable._readableState.flowing, false); const transformStream = new stream.Transform({ transform(chunk, encoding, cb) { this.push(String(chunk).toUpperCase()); cb(); }, }); writable.pipe(transformStream).pipe(readable); assert.equal(readable._readableState.flowing, false); setTimeout(() => { assert.equal(readable._readableState.flowing, false); let source = ''; duplexStream.on('data', buffer => { source += String(buffer); }); duplexStream.on('end', () => { assert.equal(source, 'AAA'); done(); }); assert.equal(readable._readableState.flowing, false); }); }); });