diff --git a/lib/lazystream.js b/lib/lazystream.js index d9f6170..4eaf964 100644 --- a/lib/lazystream.js +++ b/lib/lazystream.js @@ -1,54 +1,58 @@ -var util = require('util'); -var PassThrough = require('readable-stream/passthrough'); - -module.exports = { - Readable: Readable, - Writable: Writable -}; - -util.inherits(Readable, PassThrough); -util.inherits(Writable, PassThrough); +const { PassThrough } = require('stream'); // Patch the given method of instance so that the callback // is executed once, before the actual method is called the // first time. function beforeFirstCall(instance, method, callback) { - instance[method] = function() { + instance[method] = function(...args) { delete instance[method]; - callback.apply(this, arguments); - return this[method].apply(this, arguments); + callback.apply(this, args); + return this[method].apply(this, args); }; } -function Readable(fn, options) { - if (!(this instanceof Readable)) - return new Readable(fn, options); +class Readable extends PassThrough { + constructor(fn, options) { + super(options); - PassThrough.call(this, options); + // Support calling without new + if (!(this instanceof Readable)) { + return new Readable(fn, options); + } - beforeFirstCall(this, '_read', function() { - var source = fn.call(this, options); - var emit = this.emit.bind(this, 'error'); - source.on('error', emit); - source.pipe(this); - }); + beforeFirstCall(this, '_read', function() { + const source = fn.call(this, options); + const emit = this.emit.bind(this, 'error'); + source.on('error', emit); + source.pipe(this); + }); - this.emit('readable'); + this.emit('readable'); + } } -function Writable(fn, options) { - if (!(this instanceof Writable)) - return new Writable(fn, options); +class Writable extends PassThrough { + constructor(fn, options) { + super(options); - PassThrough.call(this, options); + // Support calling without new + if (!(this instanceof Writable)) { + return new Writable(fn, options); + } - beforeFirstCall(this, '_write', function() { - var destination = fn.call(this, options); - var emit = this.emit.bind(this, 'error'); - destination.on('error', emit); - this.pipe(destination); - }); + beforeFirstCall(this, '_write', function() { + const destination = fn.call(this, options); + const emit = this.emit.bind(this, 'error'); + destination.on('error', emit); + this.pipe(destination); + }); - this.emit('writable'); + this.emit('writable'); + } } +module.exports = { + Readable, + Writable +}; + diff --git a/package.json b/package.json index 94565e4..9838b71 100644 --- a/package.json +++ b/package.json @@ -21,22 +21,16 @@ "license": "MIT", "main": "lib/lazystream.js", "engines": { - "node": ">= 0.6.3" + "node": ">= 18.0.0" }, "scripts": { - "test": "nodeunit test/readable_test.js test/writable_test.js test/pipe_test.js test/fs_test.js" + "test": "node --test test/readable_test.js test/writable_test.js test/pipe_test.js test/fs_test.js" }, "files": [ "lib/lazystream.js", "test/*.js", "test/*.md" ], - "dependencies": { - "readable-stream": "^2.0.5" - }, - "devDependencies": { - "nodeunit": "^0.9.1" - }, "keywords": [ "emfile", "lazy", diff --git a/test/fs_test.js b/test/fs_test.js index 149b1c4..c393f66 100644 --- a/test/fs_test.js +++ b/test/fs_test.js @@ -1,69 +1,58 @@ +const { test, describe } = require('node:test'); +const assert = require('node:assert/strict'); +const stream = require('../lib/lazystream'); +const fs = require('fs'); +const fsp = fs.promises; +const tmpDir = 'test/tmp/'; +const readFile = 'test/data.md'; +const writeFile = tmpDir + 'data.md'; + +describe('fs', () => { + test('readwrite', async () => { + let readfd, writefd; + + // Clean up and prepare directories + await fsp.mkdir(tmpDir, { recursive: true }); + try { + await fsp.unlink(writeFile); + } catch (error) { + if (error.code !== 'ENOENT') throw error; + } -var stream = require('../lib/lazystream'); -var fs = require('fs'); -var tmpDir = 'test/tmp/'; -var readFile = 'test/data.md'; -var writeFile = tmpDir + 'data.md'; - -exports.fs = { - readwrite: function(test) { - var readfd, writefd; - - var readable = new stream.Readable(function() { + const readable = new stream.Readable(function() { return fs.createReadStream(readFile) .on('open', function(fd) { readfd = fd; - }) - .on('close', function() { - readfd = undefined; - step(); }); }); - var writable = new stream.Writable(function() { + const writable = new stream.Writable(function() { return fs.createWriteStream(writeFile) .on('open', function(fd) { writefd = fd; - }) - .on('close', function() { - writefd = undefined; - step(); }); }); - test.expect(3); - - test.equal(readfd, undefined, 'Input file should not be opened until read'); - test.equal(writefd, undefined, 'Output file should not be opened until write'); - - if (!fs.existsSync(tmpDir)) { - fs.mkdirSync(tmpDir); - } - if (fs.existsSync(writeFile)) { - fs.unlinkSync(writeFile); - } - - readable.on('end', function() { step(); }); - writable.on('end', function() { step(); }); - - var steps = 0; - function step() { - steps += 1; - if (steps == 4) { - var input = fs.readFileSync(readFile); - var output = fs.readFileSync(writeFile); - - test.ok(input >= output && input <= output, 'Should be equal'); - - fs.unlinkSync(writeFile); - fs.rmdirSync(tmpDir); - - test.done(); - } - }; - - readable.pipe(writable); - } -}; + assert.equal(readfd, undefined, 'Input file should not be opened until read'); + assert.equal(writefd, undefined, 'Output file should not be opened until write'); + + // Pipe files and wait for completion + await new Promise((resolve, reject) => { + readable.pipe(writable); + writable.on('finish', async () => { + try { + const input = await fsp.readFile(readFile); + const output = await fsp.readFile(writeFile); + + assert.ok(Buffer.isBuffer(input) && Buffer.isBuffer(output), 'Both should be buffers'); + assert.deepEqual(input, output, 'Files should be equal'); + resolve(); + } catch (error) { + reject(error); + } + }); + }); + }); +}); diff --git a/test/helper.js b/test/helper.js index 9d41191..3fa1daa 100644 --- a/test/helper.js +++ b/test/helper.js @@ -1,39 +1,35 @@ +const { Readable, Writable } = require('stream'); -var _Readable = require('readable-stream/readable'); -var _Writable = require('readable-stream/writable'); -var util = require('util'); - -module.exports = { - DummyReadable: DummyReadable, - DummyWritable: DummyWritable -}; +class DummyReadable extends Readable { + constructor(strings) { + super(); + this.strings = strings; + this.emit('readable'); + } -function DummyReadable(strings) { - _Readable.call(this); - this.strings = strings; - this.emit('readable'); + _read(n) { + if (this.strings.length) { + this.push(Buffer.from(this.strings.shift())); + } else { + this.push(null); + } + } } -util.inherits(DummyReadable, _Readable); - -DummyReadable.prototype._read = function _read(n) { - if (this.strings.length) { - this.push(new Buffer(this.strings.shift())); - } else { - this.push(null); +class DummyWritable extends Writable { + constructor(strings) { + super(); + this.strings = strings; + this.emit('writable'); } -}; -function DummyWritable(strings) { - _Writable.call(this); - this.strings = strings; - this.emit('writable'); + _write(chunk, encoding, callback) { + this.strings.push(chunk.toString()); + if (callback) callback(); + } } -util.inherits(DummyWritable, _Writable); - -DummyWritable.prototype._write = function _write(chunk, encoding, callback) { - this.strings.push(chunk.toString()); - if (callback) callback(); +module.exports = { + DummyReadable, + DummyWritable }; - diff --git a/test/pipe_test.js b/test/pipe_test.js index 7129e35..5fa0e70 100644 --- a/test/pipe_test.js +++ b/test/pipe_test.js @@ -1,36 +1,37 @@ - -var stream = require('../lib/lazystream'); -var helper = require('./helper'); - -exports.pipe = { - readwrite: function(test) { - var expected = [ 'line1\n', 'line2\n' ]; - var actual = []; - var readableInstantiated = false; - var writableInstantiated = false; - - test.expect(3); - - var readable = new stream.Readable(function() { +const { test, describe } = require('node:test'); +const assert = require('node:assert/strict'); +const stream = require('../lib/lazystream'); +const helper = require('./helper'); + +describe('pipe', () => { + test('readwrite', async () => { + const expected = [ 'line1\n', 'line2\n' ]; + const actual = []; + let readableInstantiated = false; + let writableInstantiated = false; + + const readable = new stream.Readable(function() { readableInstantiated = true; return new helper.DummyReadable([].concat(expected)); }); - var writable = new stream.Writable(function() { + const writable = new stream.Writable(function() { writableInstantiated = true; return new helper.DummyWritable(actual); }); - test.equal(readableInstantiated, false, 'DummyReadable should only be instantiated when it is needed'); - test.equal(writableInstantiated, false, 'DummyWritable should only be instantiated when it is needed'); + assert.equal(readableInstantiated, false, 'DummyReadable should only be instantiated when it is needed'); + assert.equal(writableInstantiated, false, 'DummyWritable should only be instantiated when it is needed'); + + return new Promise((resolve) => { + writable.on('end', function() { + assert.equal(actual.join(''), expected.join(''), 'Piping on demand streams should keep data intact'); + resolve(); + }); - writable.on('end', function() { - test.equal(actual.join(''), expected.join(''), 'Piping on demand streams should keep data intact'); - test.done(); + readable.pipe(writable); }); - - readable.pipe(writable); - } -}; + }); +}); diff --git a/test/readable_test.js b/test/readable_test.js index 9ae0636..636b79b 100644 --- a/test/readable_test.js +++ b/test/readable_test.js @@ -1,90 +1,90 @@ +const { test, describe } = require('node:test'); +const assert = require('node:assert/strict'); +const { Readable } = require('../lib/lazystream'); +const { DummyReadable } = require('./helper'); + +describe('readable', () => { + test('dummy', async () => { + const expected = [ 'line1\n', 'line2\n' ]; + const actual = []; + + return new Promise((resolve) => { + new DummyReadable([].concat(expected)) + .on('data', function(chunk) { + actual.push(chunk.toString()); + }) + .on('end', function() { + assert.equal(actual.join(''), expected.join(''), 'DummyReadable should produce the data it was created with'); + resolve(); + }); + }); + }); -var Readable = require('../lib/lazystream').Readable; -var DummyReadable = require('./helper').DummyReadable; - -exports.readable = { - dummy: function(test) { - var expected = [ 'line1\n', 'line2\n' ]; - var actual = []; - - test.expect(1); - - new DummyReadable([].concat(expected)) - .on('data', function(chunk) { - actual.push(chunk.toString()); - }) - .on('end', function() { - test.equal(actual.join(''), expected.join(''), 'DummyReadable should produce the data it was created with'); - test.done(); - }); - }, - options: function(test) { - test.expect(3); - - var readable = new Readable(function(options) { - test.ok(this instanceof Readable, "Readable should bind itself to callback's this"); - test.equal(options.encoding, "utf-8", "Readable should make options accessible to callback"); + test('options', () => { + const readable = new Readable(function(options) { + assert.ok(this instanceof Readable, "Readable should bind itself to callback's this"); + assert.equal(options.encoding, "utf-8", "Readable should make options accessible to callback"); this.ok = true; return new DummyReadable(["test"]); }, {encoding: "utf-8"}); readable.read(4); - test.ok(readable.ok); - - test.done(); - }, - streams2: function(test) { - var expected = [ 'line1\n', 'line2\n' ]; - var actual = []; - var instantiated = false; + assert.ok(readable.ok); + }); - test.expect(2); + test('streams2', async () => { + const expected = [ 'line1\n', 'line2\n' ]; + const actual = []; + let instantiated = false; - var readable = new Readable(function() { + const readable = new Readable(function() { instantiated = true; return new DummyReadable([].concat(expected)); }); - test.equal(instantiated, false, 'DummyReadable should only be instantiated when it is needed'); + assert.equal(instantiated, false, 'DummyReadable should only be instantiated when it is needed'); - readable.on('readable', function() { - var chunk; - while ((chunk = readable.read())) { - actual.push(chunk.toString()); - } - }); - readable.on('end', function() { - test.equal(actual.join(''), expected.join(''), 'Readable should not change the data of the underlying stream'); - test.done(); - }); + return new Promise((resolve) => { + readable.on('readable', function() { + let chunk; + while ((chunk = readable.read())) { + actual.push(chunk.toString()); + } + }); + readable.on('end', function() { + assert.equal(actual.join(''), expected.join(''), 'Readable should not change the data of the underlying stream'); + resolve(); + }); - readable.read(0); - }, - resume: function(test) { - var expected = [ 'line1\n', 'line2\n' ]; - var actual = []; - var instantiated = false; + readable.read(0); + }); + }); - test.expect(2); + test('resume', async () => { + const expected = [ 'line1\n', 'line2\n' ]; + let actual = []; + let instantiated = false; - var readable = new Readable(function() { + const readable = new Readable(function() { instantiated = true; return new DummyReadable([].concat(expected)); }); readable.pause(); - readable.on('data', function(chunk) { - actual.push(chunk.toString()); - }); - readable.on('end', function() { - test.equal(actual.join(''), expected.join(''), 'Readable should not change the data of the underlying stream'); - test.done(); - }); + assert.equal(instantiated, false, 'DummyReadable should only be instantiated when it is needed'); - test.equal(instantiated, false, 'DummyReadable should only be instantiated when it is needed'); + return new Promise((resolve) => { + readable.on('data', function(chunk) { + actual.push(chunk.toString()); + }); + readable.on('end', function() { + assert.equal(actual.join(''), expected.join(''), 'Readable should not change the data of the underlying stream'); + resolve(); + }); - readable.resume(); - } -}; + readable.resume(); + }); + }); +}); diff --git a/test/writable_test.js b/test/writable_test.js index a663845..b042236 100644 --- a/test/writable_test.js +++ b/test/writable_test.js @@ -1,59 +1,53 @@ - -var Writable = require('../lib/lazystream').Writable; -var DummyWritable = require('./helper').DummyWritable; - -exports.writable = { - options: function(test) { - test.expect(3); - - var writable = new Writable(function(options) { - test.ok(this instanceof Writable, "Writable should bind itself to callback's this"); - test.equal(options.encoding, "utf-8", "Writable should make options accessible to callback"); +const { test, describe } = require('node:test'); +const assert = require('node:assert/strict'); +const { Writable } = require('../lib/lazystream'); +const { DummyWritable } = require('./helper'); + +describe('writable', () => { + test('options', () => { + const writable = new Writable(function(options) { + assert.ok(this instanceof Writable, "Writable should bind itself to callback's this"); + assert.equal(options.encoding, "utf-8", "Writable should make options accessible to callback"); this.ok = true; return new DummyWritable([]); }, {encoding: "utf-8"}); writable.write("test"); - test.ok(writable.ok); - - test.done(); - }, - dummy: function(test) { - var expected = [ 'line1\n', 'line2\n' ]; - var actual = []; - - test.expect(0); + assert.ok(writable.ok); + }); - var dummy = new DummyWritable(actual); + test('dummy', () => { + const expected = [ 'line1\n', 'line2\n' ]; + const actual = []; + const dummy = new DummyWritable(actual); expected.forEach(function(item) { - dummy.write(new Buffer(item)); + dummy.write(Buffer.from(item)); }); - test.done(); - }, - streams2: function(test) { - var expected = [ 'line1\n', 'line2\n' ]; - var actual = []; - var instantiated = false; + }); - test.expect(2); + test('streams2', async () => { + const expected = [ 'line1\n', 'line2\n' ]; + const actual = []; + let instantiated = false; - var writable = new Writable(function() { + const writable = new Writable(function() { instantiated = true; return new DummyWritable(actual); }); - test.equal(instantiated, false, 'DummyWritable should only be instantiated when it is needed'); - - writable.on('end', function() { - test.equal(actual.join(''), expected.join(''), 'Writable should not change the data of the underlying stream'); - test.done(); - }); - - expected.forEach(function(item) { - writable.write(new Buffer(item)); + assert.equal(instantiated, false, 'DummyWritable should only be instantiated when it is needed'); + + return new Promise((resolve) => { + writable.on('end', function() { + assert.equal(actual.join(''), expected.join(''), 'Writable should not change the data of the underlying stream'); + resolve(); + }); + expected.forEach(function(item) { + writable.write(Buffer.from(item)); + }); + writable.end(); }); - writable.end(); - } -}; + }); +});