├── .coveralls.yml ├── .gitignore ├── .travis.yml ├── README.md ├── gulpfile.js ├── lib ├── index.js ├── pipe.js ├── readable.js ├── stream.js ├── through.js ├── wrap.js └── writable.js ├── package.json └── test ├── readable-stream-test.js ├── stream-test.js ├── through-test.js ├── wrap-test.js └── writable-stream-test.js /.coveralls.yml: -------------------------------------------------------------------------------- 1 | repo_token: kczKGxSLYWzhxBgumU2ReRtO9FYKBOLzl -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | npm-* 4 | coverage 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.10 4 | 5 | 6 | notifications: 7 | email: 8 | - craig.j.condon@gmail.com 9 | 10 | env: 11 | global: 12 | - SAUCE_USERNAME: gRqb+PFgsDPeXnmgkoNPxIIhm1zcXqA7n68Nj+e7vT12PJKIWF58J/Izi484hOhlzaOQNM5IZhHdRuvM22qlifxjfqwoWN25H5cNSwyueLKl6d5bBJuJwGPXP0wJCha1IXWbwNgukemF7+m8XRd5Fk3hlaLnW+DsmzHbZm2Ak0M= 13 | - SAUCE_ACCESS_KEY: XzidGwY+FhOGMlHDUh5TFcJhLsg3rDBdBs4yuoTLpzWtm1ga4FUukOV05s1WSQ2/KNkPbc+PPDG+JRWOoWY767g/J2MzxF6q2Vq0dDkXEPsfuE5lnT8rBnNfbf7hdk04p7bvlNfrvfUC6d/bE6F1jwxxzmzNR/MQ6abRb//BYEs= 14 | - BS_USERNAME: craigcondon1 15 | - BS_ACCESS_KEY: KzfHhPy4HesktcAQwsYg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/mojo-js/obj-stream.svg)](https://travis-ci.org/mojo-js/obj-stream) [![Coverage Status](https://coveralls.io/repos/mojo-js/obj-stream/badge.svg?branch=master)](https://coveralls.io/r/mojo-js/obj-stream?branch=master) [![Dependency Status](https://david-dm.org/mojo-js/obj-stream.svg)](https://david-dm.org/mojo-js/obj-stream) 2 | 3 | A lightweight object stream library for NodeJS and the browser. 4 | 5 | ```javascript 6 | var stream = require("obj-stream"); 7 | 8 | var stream = new stream.Stream(); 9 | stream.write({ name: "obj" }); // valid 10 | stream.write("blah"); // invalid 11 | stream.end(); 12 | 13 | var readable = new stream.Readable([{ name: "obj" }]); 14 | readable.pipe(new stream.Stream()); 15 | ``` 16 | 17 | #### writable stream.writable() 18 | 19 | creates a new writable stream 20 | 21 | ```javascript 22 | var writable = stream.writable(); 23 | ``` 24 | 25 | #### writable.write(object) 26 | 27 | Writes a new object 28 | 29 | ```javascript 30 | writable.write({ name: "Jeff Gordon" }); 31 | ``` 32 | 33 | #### writable.end([object]) 34 | 35 | Ends the stream 36 | 37 | ```javascript 38 | writable.write({ name: "Donkay" }); 39 | writable.write({ name: "Shrek" }); 40 | writable.end(); // done 41 | ``` 42 | 43 | #### writable.once(event, callback) 44 | 45 | listens to one event then disposes 46 | 47 | #### reader writable.reader 48 | 49 | ```javascript 50 | Returns the readable object 51 | 52 | var writer = stream.writable(); 53 | var reader = writer.reader; 54 | 55 | reader.on("data", function() { 56 | 57 | }); 58 | 59 | reader.on("end", function() { 60 | 61 | }); 62 | 63 | writer.end({ name: "Oprah" }); // write & end 64 | ``` 65 | 66 | #### reader stream.readable() 67 | 68 | creates a new readable object 69 | 70 | #### reader.on(event, callback) 71 | 72 | listens for an event. 73 | 74 | - `event` 75 | - `error` - emitted on error 76 | - `data` - emitted on data 77 | - `end` - emitted when the stream closes 78 | - `drain` - emitted when the writer is resumed 79 | 80 | #### reader.once(event, callback) 81 | 82 | listens to one event then disposes 83 | 84 | #### reader.removeListener(event, callback) 85 | 86 | removes a listener 87 | 88 | #### reader.pause() 89 | 90 | pauses the stream. 91 | 92 | ```javascript 93 | var writer = stream.writable(); 94 | var reader = writer.reader; 95 | 96 | reader.pause(); 97 | writer.write({ name: "bob" }); // buffered, doesn't get emitted as data 98 | ``` 99 | 100 | #### reader.resume() 101 | 102 | resumes the stream. 103 | 104 | #### reader.isPaused() 105 | 106 | returns true if the stream is paused. 107 | 108 | #### writable.pipe(writable) 109 | 110 | pipes data to a writable 111 | 112 | #### stream stream.through(fn) 113 | 114 | helper for transforming data 115 | 116 | ```javascript 117 | var writable = stream.writable(); 118 | writable.reader.pipe(stream.though(function(data, next) { 119 | this.push("blah"); 120 | next(); // done 121 | })).on("data", function(data) { 122 | // blah is always emitted here 123 | }); 124 | 125 | writable.write({ name: "org" }); 126 | writable.end(); 127 | ``` 128 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require("gulp"); 2 | var istanbul = require("gulp-istanbul"); 3 | var mocha = require("gulp-mocha"); 4 | var plumber = require("gulp-plumber"); 5 | var jshint = require("gulp-jshint"); 6 | var browserify = require("browserify"); 7 | var uglify = require("gulp-uglify"); 8 | var source = require("vinyl-source-stream"); 9 | var buffer = require("vinyl-buffer"); 10 | var jscs = require("gulp-jscs"); 11 | var coveralls = require("gulp-coveralls"); 12 | var karma = require("karma").server; 13 | var options = require("yargs").argv; 14 | 15 | /** 16 | */ 17 | 18 | var paths = { 19 | testFiles : ["test/**/*-test.js"], 20 | appFiles : ["lib/**/*.js"], 21 | allFiles : ["test/**", "lib/**"] 22 | }; 23 | 24 | /** 25 | */ 26 | 27 | var mochaOptions = { 28 | bail : options.bail !== 'false', 29 | reporter : options.reporter || 'dot', 30 | grep : options.grep || options.only, 31 | timeout : options.timeout || 500 32 | } 33 | 34 | /** 35 | */ 36 | 37 | gulp.task("test-coverage", function (complete) { 38 | gulp. 39 | src(paths.appFiles). 40 | pipe(istanbul()). 41 | pipe(istanbul.hookRequire()). 42 | on("finish", function () { 43 | gulp. 44 | src(paths.testFiles). 45 | pipe(plumber()). 46 | pipe(mocha(mochaOptions)). 47 | pipe(istanbul.writeReports({ 48 | reporters: ["text","text-summary", "lcov"] 49 | })). 50 | on("end", complete); 51 | }); 52 | }); 53 | 54 | /** 55 | */ 56 | 57 | gulp.task("test-coveralls", ["test-coverage"], function () { 58 | return gulp. 59 | src("coverage/**/lcov.info"). 60 | pipe(coveralls()); 61 | }); 62 | 63 | /** 64 | */ 65 | 66 | gulp.task("bundle", function() { 67 | return browserify("./lib/index.js"). 68 | bundle(). 69 | pipe(source('caplet.js')). 70 | pipe(buffer()). 71 | pipe(gulp.dest('./dist')); 72 | }); 73 | 74 | /** 75 | */ 76 | 77 | gulp.task("minify", ["bundle"], function() { 78 | return gulp. 79 | src("./dist/crudlet.js"). 80 | pipe(uglify()). 81 | pipe(rename(function(path) { 82 | path.basename += ".min"; 83 | })). 84 | pipe(gulp.dest('./dist')); 85 | }); 86 | 87 | /** 88 | */ 89 | 90 | gulp.task("test-browser", function(complete) { 91 | karma.start({ 92 | configFile: __dirname + '/karma.conf.js', 93 | singleRun: true 94 | }, complete); 95 | }); 96 | 97 | 98 | /** 99 | */ 100 | 101 | gulp.task("lint", function() { 102 | return gulp.run(["jshint", "jscs"]); 103 | }); 104 | 105 | /** 106 | */ 107 | 108 | gulp.task("jscs", function() { 109 | return gulp. 110 | src(paths.allFiles). 111 | pipe(jscs({ 112 | "preset": "google", 113 | "requireParenthesesAroundIIFE": true, 114 | "maximumLineLength": 120, 115 | "validateLineBreaks": "LF", 116 | "validateIndentation": 2, 117 | "validateQuoteMarks": "\"", 118 | 119 | "disallowKeywords": ["with"], 120 | "disallowSpacesInsideObjectBrackets": null, 121 | "disallowImplicitTypeConversion": ["string"], 122 | "requireCurlyBraces": [], 123 | 124 | "safeContextKeyword": "self" 125 | })); 126 | }); 127 | 128 | /** 129 | */ 130 | 131 | gulp.task("jshint", function() { 132 | return gulp. 133 | src(paths.allFiles). 134 | pipe(jshint()). 135 | pipe(jshint.reporter('default')); 136 | }); 137 | 138 | /** 139 | */ 140 | 141 | gulp.task("test", function (complete) { 142 | gulp. 143 | src(paths.testFiles, { read: false }). 144 | pipe(plumber()). 145 | pipe(mocha(mochaOptions)). 146 | on("end", complete); 147 | }); 148 | 149 | var iofwatch = process.argv.indexOf("watch"); 150 | 151 | /** 152 | * runs previous tasks (1 or more) 153 | */ 154 | 155 | gulp.task("watch", function () { 156 | gulp.watch(paths.allFiles, process.argv.slice(2, iofwatch)); 157 | }); 158 | 159 | /** 160 | */ 161 | 162 | gulp.task("default", function () { 163 | return gulp.run("test-coverage"); 164 | }); 165 | 166 | /** 167 | */ 168 | 169 | gulp.doneCallback = function (err) { 170 | 171 | // a bit hacky, but fixes issue with testing where process 172 | // doesn't exist process. Also fixes case where timeout / interval are set (CC) 173 | if (!~iofwatch) process.exit(err ? 1 : 0); 174 | }; 175 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | var Readable = require("./readable"); 2 | var Writable = require("./writable"); 3 | var Stream = require("./stream"); 4 | var through = require("./through"); 5 | var wrap = require("./wrap"); 6 | 7 | exports.Readable = Readable; 8 | exports.readable = Readable; 9 | 10 | exports.Writable = Writable; 11 | exports.writable = Writable; 12 | 13 | exports.Stream = Stream; 14 | exports.stream = Stream; 15 | 16 | exports.through = through; 17 | 18 | exports.wrap = wrap; 19 | -------------------------------------------------------------------------------- /lib/pipe.js: -------------------------------------------------------------------------------- 1 | module.exports = function(src, dst, ops) { 2 | 3 | var listeners = []; 4 | 5 | function cleanup() { 6 | for (var i = listeners.length; i--;) listeners[i].dispose(); 7 | } 8 | 9 | function onData(data) { 10 | if (dst.writable && dst.write(data) === false) { 11 | src.pause(); 12 | } 13 | } 14 | 15 | function onDrain() { 16 | if (src.readable) { 17 | src.resume(); 18 | } 19 | } 20 | 21 | function onError(error) { 22 | cleanup(); 23 | dst.emit("error", error); 24 | // TODO: throw error if there are no handlers here 25 | } 26 | 27 | var didEnd = false; 28 | 29 | function onEnd() { 30 | if (didEnd) return; 31 | didEnd = true; 32 | dst.end(); 33 | } 34 | 35 | function onClose() { 36 | if (didEnd) return; 37 | didEnd = true; 38 | if (typeof dst.destroy === "function") dst.destroy(); 39 | } 40 | 41 | function listen(target, event, listener) { 42 | target.on(event, listener); 43 | return { 44 | dispose: function() { 45 | return target.removeListener(event, listener); 46 | } 47 | }; 48 | } 49 | 50 | if (!ops || ops.end !== false) { 51 | listeners.push( 52 | listen(src, "end", onEnd), 53 | listen(src, "close", onClose) 54 | ); 55 | } 56 | 57 | listeners.push( 58 | listen(src, "data", onData), 59 | listen(dst, "drain", onDrain), 60 | listen(src, "end", cleanup), 61 | listen(src, "close", cleanup), 62 | listen(dst, "close", cleanup), 63 | listen(src, "error", onError), 64 | listen(dst, "error", cleanup) 65 | ); 66 | 67 | dst.emit("pipe", src); 68 | 69 | return dst; 70 | }; 71 | -------------------------------------------------------------------------------- /lib/readable.js: -------------------------------------------------------------------------------- 1 | var protoclass = require("protoclass"); 2 | var EventEmitter = require("events").EventEmitter; 3 | var pipe = require("./pipe"); 4 | 5 | /** 6 | */ 7 | 8 | function Readable () { 9 | if (!(this instanceof Readable)) return new Readable(); 10 | EventEmitter.call(this); 11 | this.setMaxListeners(0); 12 | } 13 | 14 | /** 15 | */ 16 | 17 | protoclass(EventEmitter, Readable, { 18 | 19 | /** 20 | */ 21 | 22 | _flowing: true, 23 | readable: true, 24 | writable: false, 25 | 26 | /** 27 | */ 28 | 29 | pause: function() { 30 | if (!this._flowing) return; 31 | this._flowing = false; 32 | this.emit("pause"); 33 | }, 34 | 35 | /** 36 | */ 37 | 38 | resume: function() { 39 | if (this._flowing) return; 40 | this._flowing = true; 41 | this.emit("resume"); 42 | }, 43 | 44 | /** 45 | */ 46 | 47 | isPaused: function() { 48 | return !this._flowing; 49 | }, 50 | 51 | /** 52 | */ 53 | 54 | pipe: function(dst, ops) { 55 | return pipe(this, dst, ops); 56 | } 57 | }); 58 | 59 | module.exports = Readable; 60 | -------------------------------------------------------------------------------- /lib/stream.js: -------------------------------------------------------------------------------- 1 | var protoclass = require("protoclass"); 2 | var Writer = require("./writable"); 3 | 4 | /** 5 | */ 6 | 7 | function Stream (reader, writer) { 8 | if (!(this instanceof Stream)) return new Stream(); 9 | this._writer = writer || new Writer(); 10 | this._reader = reader || this._writer.reader; 11 | } 12 | 13 | /** 14 | */ 15 | 16 | protoclass(Stream, { 17 | 18 | /** 19 | */ 20 | 21 | readable: true, 22 | writable: true, 23 | 24 | /** 25 | */ 26 | 27 | pause: function() { 28 | return this._reader.pause(); 29 | }, 30 | 31 | /** 32 | */ 33 | 34 | resume: function() { 35 | return this._reader.resume(); 36 | }, 37 | 38 | /** 39 | */ 40 | 41 | write: function(object) { 42 | return this._writer.write.apply(this._writer, arguments); 43 | }, 44 | 45 | /** 46 | */ 47 | 48 | end: function(object) { 49 | return this._writer.end.apply(this._writer, arguments); 50 | }, 51 | 52 | /** 53 | */ 54 | 55 | emit: function() { 56 | return this._reader.emit.apply(this._reader, arguments); 57 | }, 58 | 59 | /** 60 | */ 61 | 62 | on: function() { 63 | this._reader.on.apply(this._reader, arguments); 64 | return this; 65 | }, 66 | 67 | /** 68 | */ 69 | 70 | once: function() { 71 | this._reader.once.apply(this._reader, arguments); 72 | return this; 73 | }, 74 | 75 | /** 76 | */ 77 | 78 | removeListener: function() { 79 | return this._reader.removeListener.apply(this._reader, arguments); 80 | }, 81 | 82 | /** 83 | */ 84 | 85 | pipe: function() { 86 | return this._reader.pipe.apply(this._reader, arguments); 87 | } 88 | }); 89 | 90 | module.exports = Stream; 91 | -------------------------------------------------------------------------------- /lib/through.js: -------------------------------------------------------------------------------- 1 | var protoclass = require("protoclass"); 2 | var Readable = require("./readable"); 3 | var Stream = require("./stream"); 4 | var Writable = require("./writable"); 5 | 6 | /** 7 | */ 8 | 9 | function Through (stream) { 10 | this._stream = stream; 11 | this._reader = stream.reader; 12 | } 13 | 14 | /** 15 | */ 16 | 17 | protoclass(Through, { 18 | push: function(object) { 19 | 20 | if (!this._reader._events.data) { 21 | this._waitForListener(); 22 | } 23 | 24 | this._stream.write(object); 25 | }, 26 | 27 | /** 28 | */ 29 | 30 | _waitForListener: function() { 31 | if (this._waiting) return; 32 | this._waiting = true; 33 | 34 | var self = this; 35 | function onListener(name) { 36 | if (name === "data") { 37 | self._reader.removeListener("newListener", onListener); 38 | process.nextTick(function() { 39 | self._reader.resume(); 40 | }); 41 | } 42 | } 43 | 44 | this._reader.on("newListener", onListener); 45 | this._reader.pause(); 46 | } 47 | }); 48 | 49 | /** 50 | */ 51 | 52 | module.exports = function(write, end) { 53 | 54 | var dstWriter = new Writable(); 55 | var srcWriter = new Writable(); 56 | var stream = new Stream(dstWriter.reader, srcWriter); 57 | var through = new Through(dstWriter); 58 | 59 | var buffer = []; 60 | var running = false; 61 | var ended = false; 62 | 63 | function _write() { 64 | if (running) return; 65 | 66 | if (buffer.length) { 67 | running = true; 68 | return write.call(through, buffer.shift(), function() { 69 | running = false; 70 | _write(); 71 | }); 72 | } 73 | 74 | if (ended) { 75 | if (end) end.call(through); 76 | dstWriter.end(); 77 | } 78 | } 79 | 80 | srcWriter.reader.on("data", function(data) { 81 | buffer.push(data); 82 | _write(); 83 | }).on("end", function() { 84 | ended = true; 85 | _write(); 86 | }); 87 | 88 | return stream; 89 | }; 90 | -------------------------------------------------------------------------------- /lib/wrap.js: -------------------------------------------------------------------------------- 1 | var Stream = require("./stream"); 2 | 3 | module.exports = function(fn) { 4 | return function() { 5 | var s = new Stream(); 6 | setTimeout(function(args) { 7 | fn.apply(void 0, args.concat(function(err, data) { 8 | if (err) return s.emit("error", err); 9 | s.end(data); 10 | })); 11 | }, 0, Array.prototype.slice.call(arguments)); 12 | return s; 13 | }; 14 | }; 15 | -------------------------------------------------------------------------------- /lib/writable.js: -------------------------------------------------------------------------------- 1 | var protoclass = require("protoclass"); 2 | var EventEmitter = require("events").EventEmitter; 3 | var Reader = require("./readable"); 4 | 5 | /** 6 | */ 7 | 8 | function Writable () { 9 | if (!(this instanceof Writable)) return new Writable(); 10 | EventEmitter.call(this); 11 | this.setMaxListeners(0); 12 | 13 | this._pool = []; 14 | this.reader = new Reader(); 15 | 16 | var self = this; 17 | 18 | this.reader.on("pause", function() { 19 | self._pause(); 20 | }); 21 | 22 | this.reader.on("resume", function() { 23 | self._resume(); 24 | }); 25 | 26 | } 27 | 28 | /** 29 | */ 30 | 31 | protoclass(EventEmitter, Writable, { 32 | 33 | /** 34 | */ 35 | 36 | _flowing: true, 37 | readable: false, 38 | writable: true, 39 | 40 | /** 41 | */ 42 | 43 | write: function(object) { 44 | if (!this._write(object)) { 45 | this._pool.push(object); 46 | return false; 47 | } 48 | return true; 49 | }, 50 | 51 | /** 52 | */ 53 | 54 | end: function(object) { 55 | 56 | this._ended = true; 57 | 58 | if (object != void 0) { 59 | this.write(object); 60 | } 61 | 62 | if (this._flowing) { 63 | this.reader.emit("end"); 64 | } 65 | }, 66 | 67 | /** 68 | */ 69 | 70 | _write: function(object) { 71 | if (this._flowing) { 72 | this.reader.emit("data", object); 73 | 74 | // might have changed on emit 75 | return this._flowing; 76 | } else { 77 | return false; 78 | } 79 | }, 80 | 81 | /** 82 | */ 83 | 84 | _pause: function() { 85 | this._flowing = false; 86 | }, 87 | 88 | /** 89 | */ 90 | 91 | _resume: function() { 92 | if (this._flowing) return; 93 | this._flowing = true; 94 | this.reader.emit("drain"); 95 | 96 | while (this._pool.length) { 97 | var item = this._pool.shift(); 98 | if (!this._write(item)) { 99 | this._pool.unshift(item); 100 | break; 101 | } 102 | } 103 | 104 | if (!this._pool.length && this._ended) { 105 | this.end(); 106 | } 107 | } 108 | }); 109 | 110 | module.exports = Writable; 111 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obj-stream", 3 | "version": "0.0.26", 4 | "description": "simple object streams", 5 | "main": "./lib/index.js", 6 | "scripts": { 7 | "test": "gulp lint test-coveralls" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/crcn/crudlet.js.git" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/crcn/crudlet.js/issues" 17 | }, 18 | "homepage": "https://github.com/crcn/crudlet.js", 19 | "devDependencies": { 20 | "browserify": "^9.0.3", 21 | "crudlet": "0.0.x", 22 | "expect.js": "^0.3.1", 23 | "gulp": "^3.8.11", 24 | "gulp-concat": "^2.5.2", 25 | "gulp-coveralls": "^0.1.3", 26 | "gulp-istanbul": "^0.7.0", 27 | "gulp-jscs": "^1.4.0", 28 | "gulp-jshint": "^1.9.4", 29 | "gulp-mocha": "^2.0.0", 30 | "gulp-plumber": "^1.0.0", 31 | "gulp-uglify": "^1.1.0", 32 | "highland": "^2.4.0", 33 | "karma": "^0.12.31", 34 | "protoclass": "0.0.6", 35 | "sinon": "^1.14.1", 36 | "vinyl-buffer": "^1.0.0", 37 | "vinyl-source-stream": "^1.1.0", 38 | "yargs": "^3.6.0" 39 | }, 40 | "dependencies": { 41 | "protoclass": "0.0.6" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /test/readable-stream-test.js: -------------------------------------------------------------------------------- 1 | var expect = require("expect.js"); 2 | var stream = require(".."); 3 | var _ = require("highland"); 4 | var sinon = require("sinon"); 5 | 6 | describe(__filename + "#", function() { 7 | 8 | it("can be created with the new keyword", function() { 9 | expect(new stream.Readable()).to.be.a(stream.Readable); 10 | }); 11 | 12 | it("can be created without the new keyword", function() { 13 | expect(stream.readable()).to.be.a(stream.Readable); 14 | }); 15 | 16 | it("emits 'pause' when paused", function(next) { 17 | var readable = new stream.Readable(); 18 | readable.on("pause", next); 19 | readable.pause(); 20 | readable.pause(); 21 | }); 22 | 23 | it("returns isPaused TRUE if paused", function() { 24 | var readable = new stream.Readable(); 25 | expect(readable.isPaused()).to.be(false); 26 | readable.pause(); 27 | expect(readable.isPaused()).to.be(true); 28 | }); 29 | 30 | it("emits 'resume' when paused", function(next) { 31 | var readable = new stream.Readable(); 32 | readable.on("resume", next); 33 | readable.pause(); 34 | readable.resume(); 35 | }); 36 | 37 | it("emits pipe on the dest when pipe() is called", function(next) { 38 | var dest = _(); 39 | var reader = stream.readable(); 40 | dest.on("pipe", function(src) { 41 | expect(src).to.be(reader); 42 | next(); 43 | }); 44 | reader.pipe(dest); 45 | }); 46 | 47 | it("ends the piped stream when 'end' is emitted from the src", function(next) { 48 | var readable = stream.readable(); 49 | var items = []; 50 | 51 | readable.pipe(_()).on("data", function() { }).on("end", next); 52 | 53 | readable.emit("end"); 54 | }); 55 | 56 | it("ends the piped stream when 'close' is emitted from the src", function(next) { 57 | var readable = stream.readable(); 58 | var items = []; 59 | readable.pipe(_()).on("data", function() { }).on("end", next); 60 | readable.emit("close"); 61 | }); 62 | 63 | it("doesn't end the dest stream if ops.end is false", function() { 64 | var readable = stream.readable(); 65 | var items = []; 66 | var i = 0; 67 | readable.pipe(_(), { end: false }).on("end", function() { 68 | i++; 69 | }); 70 | readable.emit("end"); 71 | expect(i).to.be(0); 72 | }); 73 | 74 | it("can write data to the dst", function(next) { 75 | var readable = stream.readable(); 76 | var items = []; 77 | 78 | readable.pipe(_.pipeline( 79 | _.collect 80 | )).on("data", function(data) { 81 | items = data; 82 | }).on("end", function() { 83 | expect(items[0]).to.be("a"); 84 | expect(items[1]).to.be("b"); 85 | next(); 86 | }); 87 | 88 | readable.emit("data", "a"); 89 | readable.emit("data", "b"); 90 | readable.emit("end"); 91 | }); 92 | 93 | it("pauses the readable stream if the dest stream is paused", function(next) { 94 | var readable = stream.readable(); 95 | var dest = _(); 96 | dest.pause(); 97 | readable.once("pause", next); 98 | readable.pipe(dest); 99 | readable.emit("data", "a"); 100 | }); 101 | 102 | it("resumes the readable stream if the dest stream is paused", function(next) { 103 | var readable = stream.readable(); 104 | var dest = _(); 105 | dest.pause(); 106 | readable.pipe(dest); 107 | readable.once("resume", next); 108 | readable.emit("data", "a"); 109 | dest.resume(); 110 | }); 111 | 112 | it("cleans up the pipe when the readable source stream is closed", function() { 113 | var readable = stream.readable(); 114 | readable.pipe(_()); 115 | expect(readable._events.end).not.to.be(void 0); 116 | readable.emit("close"); 117 | expect(readable._events.end).to.be(void 0); 118 | }); 119 | 120 | it("cleans up the pipe when the readable source stream is ended", function() { 121 | var readable = stream.readable(); 122 | readable.pipe(_()); 123 | expect(readable._events.end).not.to.be(void 0); 124 | readable.emit("end"); 125 | expect(readable._events.end).to.be(void 0); 126 | }); 127 | 128 | it("cleans up the pipe when the readable dest is closed", function() { 129 | var readable = stream.readable(); 130 | var dest = _(); 131 | readable.pipe(dest); 132 | expect(readable._events.end).not.to.be(void 0); 133 | dest.emit("close"); 134 | expect(readable._events.end).to.be(void 0); 135 | }); 136 | 137 | it("passes the error down stream", function(next) { 138 | var readable = stream.readable(); 139 | readable.pipe(_.pipeline()).on("error", function(err) { 140 | expect(err.message).to.be("err"); 141 | next(); 142 | }); 143 | readable.emit("error", new Error("err")); 144 | }); 145 | 146 | it("cleans up the pipe when the src emits an error", function() { 147 | var readable = stream.readable(); 148 | var dest = _(); 149 | readable.pipe(dest).on("error", function() { }); 150 | expect(readable._events.end).not.to.be(void 0); 151 | readable.emit("error", new Error("e")); 152 | expect(readable._events.end).to.be(void 0); 153 | }); 154 | 155 | it("calls destroy on the dest when close is emitted", function() { 156 | var readable = stream.readable(); 157 | var dest = _(); 158 | var stub = sinon.stub(dest, "destroy"); 159 | readable.pipe(dest); 160 | readable.emit("close"); 161 | expect(stub.callCount).to.be(1); 162 | }); 163 | 164 | it("does not write to the destination if it's not writable", function() { 165 | var readable = stream.readable(); 166 | var dest = _(); 167 | dest.writable = false; 168 | var stub = sinon.stub(dest, "write"); 169 | readable.pipe(dest); 170 | readable.emit("data", "a"); 171 | expect(stub.callCount).to.be(0); 172 | }); 173 | 174 | it("does resume the readable stream if it's not readable", function() { 175 | var readable = stream.readable(); 176 | readable.readable = false; 177 | var dest = _(); 178 | dest.pause(); 179 | var stub = sinon.stub(readable, "resume"); 180 | readable.pipe(dest); 181 | readable.emit("data", "a"); 182 | dest.resume(); 183 | expect(stub.callCount).to.be(0); 184 | }); 185 | }); 186 | -------------------------------------------------------------------------------- /test/stream-test.js: -------------------------------------------------------------------------------- 1 | var expect = require("expect.js"); 2 | var stream = require(".."); 3 | var sinon = require("sinon"); 4 | 5 | describe(__filename + "#", function() { 6 | 7 | it("can be created with the new keyword", function() { 8 | expect(new stream.Stream()).to.be.a(stream.Stream); 9 | }); 10 | 11 | it("can be created without the new keyword", function() { 12 | expect(stream.Stream()).to.be.a(stream.Stream); 13 | }); 14 | 15 | it("can pipe data do another stream", function(next) { 16 | var s1 = stream.stream(); 17 | var s2 = stream.stream(); 18 | var items = []; 19 | 20 | s1.pipe(s2).on("data", items.push.bind(items)).on("end", function() { 21 | expect(items.length).to.be(2); 22 | next(); 23 | }); 24 | 25 | s1.write("a"); 26 | s1.end("b"); 27 | }); 28 | 29 | it("can pause downstream", function() { 30 | var s1 = stream.stream(); 31 | var s2 = stream.stream(); 32 | var s3 = stream.stream(); 33 | s1.pipe(s2).pipe(s3); 34 | s3.pause(); 35 | s1.write("a"); 36 | expect(s1._writer._flowing).to.be(false); 37 | }); 38 | 39 | it("waits for all pipes to resume before emitting data", function() { 40 | var s1 = stream.stream(); 41 | var s2 = stream.stream(); 42 | var s3 = stream.stream(); 43 | 44 | s1.pipe(s2); 45 | s1.pipe(s3); 46 | 47 | s3.pause(); 48 | s2.pause(); 49 | s1.write("a"); 50 | expect(s1._writer._flowing).to.be(false); 51 | s3.resume(); 52 | expect(s1._writer._flowing).to.be(false); 53 | s2.resume(); 54 | expect(s1._writer._flowing).to.be(true); 55 | }); 56 | }); 57 | -------------------------------------------------------------------------------- /test/through-test.js: -------------------------------------------------------------------------------- 1 | var expect = require("expect.js"); 2 | var stream = require(".."); 3 | var _ = require("highland"); 4 | 5 | describe(__filename + "#", function() { 6 | 7 | it("can be created", function() { 8 | var s1 = stream.stream(); 9 | var items = []; 10 | s1.pipe(stream.through(function(obj, next) { 11 | for (var i = 5; i--;) this.push(1); 12 | next(); 13 | })).on("data", items.push.bind(items)); 14 | s1.end("ab"); 15 | expect(items.length).to.be(5); 16 | }); 17 | 18 | it("next can be async", function(next) { 19 | var s1 = stream.stream(); 20 | var items = []; 21 | s1.pipe(stream.through(function(obj, next) { 22 | this.push(obj); 23 | process.nextTick(next); 24 | })).on("data", items.push.bind(items)); 25 | s1.write("ab"); 26 | expect(items.length).to.be(1); 27 | s1.write("ab"); 28 | s1.write("ab"); 29 | s1.write("ab"); 30 | setTimeout(function() { 31 | expect(items[0]).to.be("ab"); 32 | next(); 33 | }, 10); 34 | }); 35 | 36 | it("can write a stream of operations", function() { 37 | var items = []; 38 | var s1 = stream.through(function(obj, next) { 39 | this.push(obj); 40 | this.push(obj); 41 | next(); 42 | }).on("data", items.push.bind(items)); 43 | 44 | s1.write("ab"); 45 | expect(items.length).to.be(2); 46 | }); 47 | 48 | it("can buffer content and pass downstream", function(next) { 49 | var buffer = []; 50 | var s1 = stream.stream(); 51 | s1.pipe(stream.through(function(data, next) { 52 | buffer.push(data); 53 | next(); 54 | }, function() { 55 | this.push(buffer); 56 | })).on("data", function(data) { 57 | expect(data.length).to.be(3); 58 | next(); 59 | }); 60 | 61 | s1.write(1); 62 | s1.write(2); 63 | s1.end(3); 64 | }); 65 | 66 | it("passes errors through", function(next) { 67 | var s1 = stream.stream(); 68 | var s2 = s1.pipe(stream.through(function(data, next) { 69 | next(); 70 | })); 71 | 72 | s2.on("error", function() { 73 | next(); 74 | }); 75 | 76 | s1.emit("error"); 77 | }); 78 | 79 | it("can pass to multiple through objects", function(next) { 80 | var s1 = stream.stream(); 81 | var map = function(i, next) { 82 | this.push(i + 1); 83 | next(); 84 | }; 85 | s1.pipe(stream.through(map)).pipe(stream.through(map)).on("data", function(num) { 86 | expect(num).to.be(3); 87 | next(); 88 | }); 89 | 90 | s1.write(1); 91 | }); 92 | 93 | it("pauses the stream if there are no handlers", function(next) { 94 | 95 | var buffer = []; 96 | 97 | var s = _([1, 2, 3]).pipe(stream.through(function(data, next) { 98 | buffer.push(data); 99 | next(); 100 | }, function() { 101 | this.push(buffer); 102 | })); 103 | 104 | s.on("data", function() {}); 105 | s.on("end", function() { 106 | expect(buffer.length).to.be(3); 107 | next(); 108 | }); 109 | }); 110 | }); 111 | -------------------------------------------------------------------------------- /test/wrap-test.js: -------------------------------------------------------------------------------- 1 | var expect = require("expect.js"); 2 | var stream = require(".."); 3 | 4 | describe(__filename + "#", function() { 5 | it("can wrap a callback as a streamable", function(next) { 6 | var streamable = stream.wrap(function(o, next) { 7 | next(void 0, o.name); 8 | }); 9 | 10 | streamable({name:"blah"}).on("data", function(name) { 11 | expect(name).to.be("blah"); 12 | next(); 13 | }); 14 | }); 15 | 16 | it("can wrap a callback as a streamable and emit an error", function(next) { 17 | var streamable = stream.wrap(function(o, next) { 18 | next(o.name); 19 | }); 20 | 21 | streamable({name:"blah"}).on("error", function(error) { 22 | expect(error).to.be("blah"); 23 | next(); 24 | }); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /test/writable-stream-test.js: -------------------------------------------------------------------------------- 1 | var expect = require("expect.js"); 2 | var stream = require(".."); 3 | var sinon = require("sinon"); 4 | var _ = require("highland"); 5 | 6 | describe(__filename + "#", function() { 7 | 8 | it("can be created with the new keyword", function() { 9 | expect(new stream.Writable()).to.be.a(stream.Writable); 10 | }); 11 | 12 | it("can be created without the new keyword", function() { 13 | expect(stream.writable()).to.be.a(stream.Writable); 14 | }); 15 | 16 | it("has a readable property", function() { 17 | expect(stream.writable().reader).to.be.a(stream.Readable); 18 | }); 19 | 20 | it("pauses the writer when the reader is paused", function() { 21 | var writable = stream.writable(); 22 | var stub = sinon.stub(writable, "_pause"); 23 | writable.reader.pause(); 24 | expect(stub.callCount).to.be(1); 25 | }); 26 | 27 | it("resumes the writer when the reader is resumed", function() { 28 | var writable = stream.writable(); 29 | var stub = sinon.stub(writable, "_resume"); 30 | writable.reader.pause(); 31 | expect(stub.callCount).to.be(0); 32 | writable.reader.resume(); 33 | expect(stub.callCount).to.be(1); 34 | }); 35 | 36 | it("buffers written data if the writable is paused", function() { 37 | var writable = stream.writable(); 38 | writable.reader.pause(); 39 | expect(writable._pool.length).to.be(0); 40 | writable.write({ name: "a" }); 41 | expect(writable._pool.length).to.be(1); 42 | }); 43 | 44 | it("emits drain wen the stream is resumed", function(next) { 45 | var writable = stream.writable(); 46 | writable.reader.pause(); 47 | writable.reader.once("drain", next); 48 | writable.reader.resume(); 49 | }); 50 | 51 | it("emits pooled data after resuming", function() { 52 | var items = []; 53 | var writable = stream.writable(); 54 | writable.reader.pause(); 55 | writable.write("data", "a"); 56 | writable.write("data", "a"); 57 | writable.write("data", "b"); 58 | writable.reader.on("data", function(item) { 59 | items.push(item); 60 | }); 61 | writable.reader.resume(); 62 | expect(items.length).to.be(3); 63 | }); 64 | 65 | it("emits end when end is called", function(next) { 66 | var writable = stream.writable(); 67 | writable.reader.once("end", next); 68 | writable.end(); 69 | }); 70 | 71 | it("can pass data into end", function(next) { 72 | var writable = stream.writable(); 73 | writable.reader.once("data", function() { next(); }); 74 | writable.end("a"); 75 | }); 76 | 77 | it("only ends when flowing", function() { 78 | var writable = stream.writable(); 79 | writable.reader.pause(); 80 | var i = 0; 81 | writable.reader.on("end", function() { i++; }); 82 | writable.write("a"); 83 | writable.end(); 84 | expect(i).to.be(0); 85 | writable.reader.resume(); 86 | expect(i).to.be(1); 87 | }); 88 | 89 | it("can collect items", function(next) { 90 | var writable = stream.writable(); 91 | writable.reader.pipe(_.pipeline(_.collect)).on("data", function(items) { 92 | expect(items.length).to.be(3); 93 | next(); 94 | }); 95 | writable.write("a"); 96 | writable.write("a"); 97 | writable.end("a"); 98 | }); 99 | 100 | it("can be piped to", function(next) { 101 | var a = stream.writable(); 102 | var b = stream.writable(); 103 | a.reader.pipe(b).reader.on("data", function(data) { 104 | expect(data).to.be(1); 105 | next(); 106 | }); 107 | 108 | a.write(1); 109 | }); 110 | }); 111 | --------------------------------------------------------------------------------