├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── duplex.js ├── index.js ├── package.json ├── readable.js ├── test ├── pipe.js ├── readable.js └── writable.js ├── transform.js └── writable.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | - '0.12' 5 | - '4.0' 6 | - '5.0' 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Mathias Buus 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vintage-streams 2 | 3 | A userland streams implementation that mostly mimicks node core streams but adds error handling and a few other tweaks 4 | 5 | ``` 6 | npm install vintage-streams 7 | ``` 8 | 9 | [![build status](http://img.shields.io/travis/mafintosh/vintage-streams.svg?style=flat)](http://travis-ci.org/mafintosh/vintage-streams) 10 | 11 | ## Usage 12 | 13 | ``` js 14 | var streams = require('vintage-streams') 15 | 16 | var rs = streams.Readable({ 17 | read: function (cb) { 18 | this.push(Buffer('hello')) 19 | this.push(Buffer('world')) 20 | this.push(null) 21 | cb() 22 | } 23 | }) 24 | 25 | rs.on('data', function (data) { 26 | console.log(data) // first hello, then data 27 | }) 28 | 29 | rs.on('end', function () { 30 | console.log('(no more data)') 31 | }) 32 | 33 | rs.on('close', function () { 34 | console.log('(stream is completely closed)') 35 | }) 36 | ``` 37 | 38 | ## API 39 | 40 | ### `streams.Readable` 41 | 42 | A readable stream represents a source of data that you can read from. An example of this could be a stream the reads the contents of a file. 43 | 44 | To make a readable stream use the base class provided by `streams.Readable`. 45 | 46 | #### `Readable.push(data)` 47 | 48 | Call this method to push data to the stream. Returns `true` if the internal buffer is not full and you should continue pushing data. 49 | If you pass `.push(null)` the stream will end. 50 | 51 | Per default all streams pass the data unaltered. If you are writing binary data it might be conveinient to convert all strings to buffers. To do this you can set the `toBuffer` (or `readableToBuffer`) option in the constructor 52 | 53 | ``` 54 | var rs = stream.Readable({ 55 | toBuffer: true // converts strings to buffers when pushing data 56 | }) 57 | ``` 58 | 59 | #### `Readable._read(callback)` 60 | 61 | The `_read` method is called every time the stream has drained and wants you to push more data to it. You should override this method with your custom reading logic. When you are done reading call the callback. If you call the callback with an error the stream is destroyed. Optionally you can pass a data value as the second argument to the callback to push it. If you pass `null` as the second argument the stream will end. 62 | 63 | ``` js 64 | 65 | var rs = stream.Readable() 66 | var cnt = 0 67 | 68 | rs._read = function (cb) { 69 | cb(null, Buffer('' + (cnt++))) 70 | } 71 | 72 | rs.on('data', function (data) { 73 | console.log(data) // first 0, then 1, ... 74 | }) 75 | ``` 76 | 77 | You can also pass the read function as an option in the constructor. 78 | 79 | ``` js 80 | var rs = stream.Readable({ 81 | read: function (cb) { 82 | ... 83 | } 84 | }) 85 | ``` 86 | 87 | #### `Readable.destroy([err])` 88 | 89 | Destroys the readable stream. No more data will be emitted. If an error is passed an error will be emitted as well. 90 | 91 | #### `Readable._destroy(callback)` 92 | 93 | Called when the stream is being destroyed. Override this with your custom destroy logic. After the callback is called `close` will be emitted. 94 | This method is always called at the end of the streams lifecycle and you can use this to deallocate any resources you have opened. 95 | 96 | ``` js 97 | rs._destroy = function (cb) { 98 | fs.close(fd, cb) // close an open file descriptor fx 99 | } 100 | ``` 101 | 102 | You can also pass the destroy function as an option in the constructor 103 | 104 | ``` js 105 | var rs = stream.Readable({ 106 | destroy: function (cb) { 107 | ... 108 | } 109 | }) 110 | ``` 111 | 112 | #### `writableStream = Readable.pipe(writableStream, [callback])` 113 | 114 | Pipe the stream to a writable stream. Compatible with node core streams. When the pipe is finished (the writable stream has gracefully ended) the callback is called. If the pipe failed the callback is called with an error. If either the writable stream or the readable stream is destroyed or experiences an error both streams in the pipeline are destroyed. 115 | 116 | #### `Readable.pause()` 117 | 118 | Pauses the stream. No events will be emitted while the stream is paused unless it is destroyed or resumed. All streams start out paused. 119 | 120 | #### `Readable.resume()` 121 | 122 | Unpauses the stream. 123 | 124 | #### `var data = Readable.read()` 125 | 126 | Similar to the core `.read()` method it returns the first data item available in the buffer. If the buffer is empty `null` is returned. Added mostly to be compatible with node core. 127 | 128 | #### `Readable.on('readable')` 129 | 130 | Emitted when there is data in the buffer available to read. 131 | 132 | #### `Readable.on('data', data)` 133 | 134 | Emitted when there is data to read. Similar to node code all streams start out paused and adding a data listener will automatically resume it. 135 | 136 | #### `Readable.on('end')` 137 | 138 | Emitted when the stream has ended gracefully and all data has been read. `end` is not guaranteed to be emitted if the stream is forcefully closed using destroy. 139 | 140 | #### `Readable.on('close')` 141 | 142 | Emitted when the stream is fully closed. This is always the last event emitted and is guaranteed to be emitted. 143 | 144 | #### `Readable.on('error', err)` 145 | 146 | Emitted when an error has occurred. Guaranteed to only be emitted once and is followed by the close event. 147 | 148 | ### `streams.Writable` 149 | 150 | A writable stream represents a destination you can write data to. An example of this could be a stream the writes the data written to it to a file. 151 | 152 | To make a writable stream use the base class provided by `streams.Writable`. 153 | 154 | #### `flushed = Writable.write(data)` 155 | 156 | Call this method to write data. Similary to `.push` on readable streams it returns `true` if you continue writing or `false` if you should wait for the stream to drain. When a writable stream has drained it will emit a `drain` event. 157 | 158 | #### `Writable._write(data, callback)` 159 | 160 | The `_write` method is called every time the writable stream has some data it wants you to write. Call the callback when you are done writing it. 161 | 162 | ``` js 163 | ws._write = function (data, callback) { 164 | console.log('someone is writing', data) 165 | callback() 166 | } 167 | ``` 168 | 169 | You can also pass the write function as an option in the constructor 170 | 171 | ``` js 172 | var ws = stream.Writable({ 173 | write: function (data, cb) { 174 | ... 175 | } 176 | }) 177 | ``` 178 | 179 | #### `Writable.end([data])` 180 | 181 | Ends the stream. Optionally you can a data value that is written before the stream is ended. 182 | 183 | #### `Writable._end(callback)` 184 | 185 | The `._end` method is called when the stream is about to end. Override this method if you want to do some custom logic before the stream emits finish. 186 | 187 | ``` js 188 | ws._end = function (cb) { 189 | console.log('stream is about to end') 190 | cb() 191 | } 192 | ``` 193 | 194 | You can also pass the end function as an option in the constructor 195 | 196 | ``` js 197 | var ws = streams.Writable({ 198 | end: function (cb) { 199 | ... 200 | } 201 | }) 202 | ``` 203 | 204 | #### `Writable.destroy([error])` 205 | 206 | Destroys the writable stream. No more data will be written. If an error is passed an error will be emitted as well. 207 | 208 | #### `Writable._destroy(callback)` 209 | 210 | Similar to `_destroy` on a readable stream. Use this to deallocate any resources. Will emit close after the callback has been called. 211 | 212 | #### `Writable.cork()` 213 | 214 | Pauses the writable stream. No data will be written and no events will emitted while the stream is corked unless it is destroyed. 215 | 216 | #### `Writable.uncork()` 217 | 218 | Unpauses the writable stream. 219 | 220 | #### `Writable.on('drain')` 221 | 222 | Emitted when the stream has drained and you should start writing more data to it. 223 | 224 | #### `Writable.on('finish')` 225 | 226 | Emitted when the last data chunk has been written after `.end()` has been called. If the stream has been destroyed this is not guaranteed to be emitted. 227 | 228 | #### `Writable.on('error')` 229 | 230 | Emitted if the writable stream experiences an error. Will only be emitted once. 231 | 232 | #### `Writable.on('close')` 233 | 234 | Emitted when the writable stream is fully closed and has been destroyed. Similary to readable streams this is guaranteed to be emitted and is the last event. 235 | 236 | ### `streams.Duplex` 237 | 238 | A duplex stream is a stream that is both readable and writable. An example of this could a be a TCP stream that allows you to read and write from a network connection. 239 | 240 | To make a duplex stream use the base class provided by `streams.Duplex`. The duplex stream inherits all methods from both readable and writable streams. See the API for those streams. 241 | 242 | ### `streams.Transform` 243 | 244 | A transform stream is a special duplex stream that transforms the data written to into a readable source. A zip/unzip stream is a good example of this. 245 | 246 | To make a transform stream use the base class provided by `streams.Transform`. 247 | 248 | #### `Transform._transform(data, callback)` 249 | 250 | This method is called when there is a piece of data that you should transform. 251 | 252 | ``` js 253 | ts._transform = function (data, cb) { 254 | this.push(Buffer(data.toString().toUpperCase())) // uppercase the string 255 | cb() 256 | } 257 | ``` 258 | 259 | Optionally you can set the transform option in the constructor as well. 260 | 261 | ``` js 262 | var ts = streams.Transform({ 263 | transform: function (data, cb) { 264 | ... 265 | } 266 | }) 267 | ``` 268 | 269 | ## License 270 | 271 | MIT 272 | -------------------------------------------------------------------------------- /duplex.js: -------------------------------------------------------------------------------- 1 | var readable = require('./readable') 2 | var writable = require('./writable') 3 | 4 | module.exports = readable._duplex(writable) 5 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | exports.Readable = require('./readable') 2 | exports.Writable = require('./writable') 3 | exports.Transform = require('./transform') 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vintage-streams", 3 | "version": "1.0.3", 4 | "description": "Userland streams implementation", 5 | "main": "index.js", 6 | "dependencies": { 7 | "inherits": "^2.0.1" 8 | }, 9 | "devDependencies": { 10 | "standard": "^6.0.8", 11 | "tape": "^4.5.1" 12 | }, 13 | "scripts": { 14 | "test": "standard && tape test/*.js" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/mafintosh/vintage-streams.git" 19 | }, 20 | "author": "Mathias Buus (@mafintosh)", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/mafintosh/vintage-streams/issues" 24 | }, 25 | "homepage": "https://github.com/mafintosh/vintage-streams" 26 | } 27 | -------------------------------------------------------------------------------- /readable.js: -------------------------------------------------------------------------------- 1 | var events = require('events') 2 | var inherits = require('inherits') 3 | 4 | module.exports = duplex(events.EventEmitter, false) 5 | 6 | function duplex (From, isWritable) { 7 | function ReadableState () { 8 | this.toBuffer = false 9 | this.vintageStream = true // feature flag 10 | this.syncRead = false 11 | this.destroyed = false 12 | this.destroyReason = null 13 | this.reading = true 14 | this.paused = true 15 | this.ended = false 16 | this.endEmitted = false 17 | this.buffer = [] 18 | this.destination = null 19 | this.afterRead = null 20 | this.afterDestroy = null 21 | this.afterPipe = null 22 | } 23 | 24 | function Readable (opts) { 25 | if (!(this instanceof Readable)) return new Readable(opts) 26 | if (!opts) opts = {} 27 | 28 | if (isWritable) From.call(this, opts) 29 | else From.call(this) 30 | 31 | if (opts.read) this._read = opts.read 32 | if (opts.destroy) this._destroy = opts.destroy 33 | 34 | var self = this 35 | var state = this._readableState = new ReadableState() 36 | 37 | state.toBuffer = !opts.toBuffer && !opts.readableToBuffer 38 | state.afterRead = afterRead 39 | state.afterDestroy = afterDestroy 40 | 41 | this.on('newListener', newListener) 42 | process.nextTick(afterRead) 43 | 44 | function afterRead (err, data) { 45 | if (err) return self.destroy(err) 46 | if (data || data === null) self.push(data) 47 | state.reading = false 48 | if (!state.syncRead) read(self, state) 49 | } 50 | 51 | function afterDestroy (err) { 52 | if (!err) err = state.destroyReason 53 | if (err) self.emit('error', err) 54 | self.emit('close') 55 | } 56 | } 57 | 58 | inherits(Readable, From) 59 | 60 | Readable._duplex = function (Writable) { 61 | return duplex(Writable, true) 62 | } 63 | 64 | Readable.prototype.pipe = function (dest, cb) { 65 | var state = this._readableState 66 | var wstate = dest._writableState 67 | 68 | if (state.destination) throw new Error('Can only pipe a stream once') 69 | state.destination = dest 70 | 71 | if (!wstate || !wstate.vintageStream) { 72 | state.afterPipe = cb || null 73 | fallbackPipe(this, state, dest) 74 | } else { 75 | wstate.afterPipe = cb || null 76 | if (wstate.source) throw new Error('Can only pipe a single stream to a writable one') 77 | wstate.source = this 78 | this.on('error', noop) 79 | dest.on('error', noop) 80 | } 81 | 82 | this.resume() 83 | return dest 84 | } 85 | 86 | Readable.prototype.push = function (data) { 87 | var state = this._readableState 88 | if (state.destroyed) return false 89 | if (data === null) return end(this, state) 90 | return push(this, state, !state.toBuffer && typeof data === 'string' ? Buffer(data) : data) 91 | } 92 | 93 | Readable.prototype.pause = function () { 94 | var state = this._readableState 95 | if (state.destroyed || state.paused) return 96 | state.paused = true 97 | this.emit('pause') 98 | } 99 | 100 | Readable.prototype.resume = function () { 101 | var state = this._readableState 102 | if (state.destroyed || !state.paused) return 103 | state.paused = false 104 | this.emit('resume') 105 | while (!state.paused && state.buffer.length) ondata(this, state, state.buffer.shift()) 106 | emitEndMaybe(this, state) 107 | read(this, state) 108 | } 109 | 110 | Readable.prototype.destroyMaybe = function () { 111 | if (isWritable && !this._writableState.finishEmitted) return 112 | if (!this._readableState.endEmitted) return 113 | this.destroy() 114 | } 115 | 116 | Readable.prototype.destroy = function (err) { 117 | var state = this._readableState 118 | if (state.destroyed) return 119 | state.destroyed = true 120 | 121 | if (err) state.destroyReason = err 122 | state.reading = true // set these to stop reading 123 | state.paused = true 124 | state.endEmitted = true 125 | 126 | if (isWritable) this.destroyWritable() 127 | 128 | if (state.destination) { 129 | var dest = state.destination 130 | state.destination = null 131 | if (dest.destroy) dest.destroy() 132 | } 133 | 134 | this._destroy(state.afterDestroy) 135 | } 136 | 137 | Readable.prototype.read = function () { 138 | var state = this._readableState 139 | var data = state.buffer.shift() || null 140 | if (data) this.emit('data', data) 141 | read(this, state) 142 | emitEndMaybe(this, state) 143 | return data 144 | } 145 | 146 | Readable.prototype._destroy = function (cb) { 147 | // Override me 148 | cb(null) 149 | } 150 | 151 | Readable.prototype._read = function (cb) { 152 | // Override me 153 | cb(null, null) 154 | } 155 | 156 | function read (self, state) { 157 | while (!state.destroyed && !state.ended && !state.reading && state.buffer.length < 16) { 158 | state.reading = true 159 | state.syncRead = true 160 | self._read(state.afterRead) 161 | state.syncRead = false 162 | } 163 | } 164 | 165 | function end (self, state) { 166 | state.ended = true 167 | emitEndMaybe(self, state) 168 | return false 169 | } 170 | 171 | function emitEndMaybe (self, state) { 172 | if (!state.paused && state.ended && !state.buffer.length && !state.endEmitted) { 173 | state.endEmitted = true 174 | state.paused = true 175 | self.emit('end') 176 | 177 | if (state.destination) { 178 | var dest = state.destination 179 | state.destination = null 180 | dest.end() 181 | } 182 | 183 | self.destroyMaybe() 184 | } 185 | } 186 | 187 | function ondata (self, state, data) { 188 | self.emit('data', data) 189 | if (state.destination) { 190 | if (!state.destination.write(data)) self.pause() 191 | } 192 | } 193 | 194 | function push (self, state, data) { 195 | if (!state.paused && !state.buffer.length) { 196 | ondata(self, state, data) 197 | return true 198 | } 199 | 200 | var length = state.buffer.push(data) 201 | if (length === 1) self.emit('readable') 202 | return length < 16 203 | } 204 | 205 | function newListener (name) { 206 | if (name === 'data') this.resume() 207 | } 208 | 209 | function fallbackPipe (self, state, dest) { 210 | dest.on('finish', onclose) 211 | dest.on('close', onclose) 212 | dest.on('error', done) 213 | dest.on('drain', ondrain) 214 | 215 | function ondrain () { 216 | self.resume() 217 | } 218 | 219 | function done (err) { 220 | var cb = state.afterPipe 221 | state.afterPipe = null 222 | state.destination = null 223 | if (!state.ended) self.destroy() 224 | if (cb) cb(err) 225 | } 226 | 227 | function onclose () { 228 | if (!state.ended) done(state.destroyReason || new Error('Stream closed prematurely')) 229 | else done(null) 230 | } 231 | } 232 | 233 | function noop () {} 234 | 235 | return Readable 236 | } 237 | -------------------------------------------------------------------------------- /test/pipe.js: -------------------------------------------------------------------------------- 1 | var tape = require('tape') 2 | var stream = require('../') 3 | 4 | tape('pipe', function (t) { 5 | t.plan(7 + 2) 6 | 7 | var datas = [ 8 | Buffer('a'), 9 | Buffer('b'), 10 | Buffer('c'), 11 | Buffer('d'), 12 | Buffer('e'), 13 | Buffer('f'), 14 | Buffer('g') 15 | ] 16 | 17 | var i = 0 18 | var j = 0 19 | 20 | var rs = stream.Readable({ 21 | read: function (cb) { 22 | cb(null, datas[i++] || null) 23 | } 24 | }) 25 | 26 | var ws = stream.Writable({ 27 | write: function (data, cb) { 28 | t.same(data, datas[j++], 'getting expected data') 29 | cb() 30 | } 31 | }) 32 | 33 | rs.on('end', function () { 34 | t.pass('readable ends') 35 | }) 36 | 37 | ws.on('finish', function () { 38 | t.pass('writable finishes') 39 | }) 40 | 41 | rs.pipe(ws) 42 | }) 43 | 44 | tape('pipe with callback', function (t) { 45 | var datas = [ 46 | Buffer('a'), 47 | Buffer('b'), 48 | Buffer('c'), 49 | Buffer('d'), 50 | Buffer('e'), 51 | Buffer('f'), 52 | Buffer('g') 53 | ] 54 | 55 | var i = 0 56 | var j = 0 57 | 58 | var rs = stream.Readable({ 59 | read: function (cb) { 60 | cb(null, datas[i++] || null) 61 | } 62 | }) 63 | 64 | var ws = stream.Writable({ 65 | write: function (data, cb) { 66 | t.same(data, datas[j++], 'getting expected data') 67 | cb() 68 | } 69 | }) 70 | 71 | rs.pipe(ws, function (err) { 72 | t.ok(!err, 'no error') 73 | t.end() 74 | }) 75 | }) 76 | 77 | tape('pipe with transform', function (t) { 78 | t.plan(7) 79 | 80 | var datas = [ 81 | Buffer('a'), 82 | Buffer('b'), 83 | Buffer('c'), 84 | Buffer('d'), 85 | Buffer('e'), 86 | Buffer('f'), 87 | Buffer('g') 88 | ] 89 | 90 | var expected = [ 91 | Buffer('A'), 92 | Buffer('B'), 93 | Buffer('C'), 94 | Buffer('D'), 95 | Buffer('E'), 96 | Buffer('F'), 97 | Buffer('G') 98 | ] 99 | 100 | var i = 0 101 | var j = 0 102 | 103 | var rs = stream.Readable({ 104 | read: function (cb) { 105 | cb(null, datas[i++] || null) 106 | } 107 | }) 108 | 109 | var ts = stream.Transform({ 110 | transform: function (data, cb) { 111 | cb(null, Buffer(data.toString().toUpperCase())) 112 | } 113 | }) 114 | 115 | var ws = stream.Writable({ 116 | write: function (data, cb) { 117 | t.same(data, expected[j++], 'getting expected data') 118 | cb() 119 | } 120 | }) 121 | 122 | rs.pipe(ts).pipe(ws) 123 | }) 124 | 125 | tape('pipe with transform and callback', function (t) { 126 | var datas = [ 127 | Buffer('a'), 128 | Buffer('b'), 129 | Buffer('c'), 130 | Buffer('d'), 131 | Buffer('e'), 132 | Buffer('f'), 133 | Buffer('g') 134 | ] 135 | 136 | var expected = [ 137 | Buffer('A'), 138 | Buffer('B'), 139 | Buffer('C'), 140 | Buffer('D'), 141 | Buffer('E'), 142 | Buffer('F'), 143 | Buffer('G') 144 | ] 145 | 146 | var i = 0 147 | var j = 0 148 | 149 | var rs = stream.Readable({ 150 | read: function (cb) { 151 | cb(null, datas[i++] || null) 152 | } 153 | }) 154 | 155 | var ts = stream.Transform({ 156 | transform: function (data, cb) { 157 | cb(null, Buffer(data.toString().toUpperCase())) 158 | } 159 | }) 160 | 161 | var ws = stream.Writable({ 162 | write: function (data, cb) { 163 | t.same(data, expected[j++], 'getting expected data') 164 | cb() 165 | } 166 | }) 167 | 168 | rs.pipe(ts).pipe(ws, function (err) { 169 | t.ok(!err, 'no error') 170 | t.end() 171 | }) 172 | }) 173 | 174 | tape('pipe with callback and destroy rs', function (t) { 175 | var rs = stream.Readable({ 176 | read: function (cb) { 177 | this.destroy() 178 | } 179 | }) 180 | 181 | var ws = stream.Writable() 182 | 183 | rs.pipe(ws, function (err) { 184 | t.ok(err, 'had error') 185 | t.end() 186 | }) 187 | }) 188 | 189 | tape('pipe with callback and destroy ws', function (t) { 190 | var rs = stream.Readable({ 191 | read: function (cb) { 192 | cb(null, Buffer('hello')) 193 | } 194 | }) 195 | 196 | var ws = stream.Writable({ 197 | write: function (data, cb) { 198 | this.destroy() 199 | } 200 | }) 201 | 202 | rs.pipe(ws, function (err) { 203 | t.ok(err, 'had error') 204 | t.end() 205 | }) 206 | }) 207 | -------------------------------------------------------------------------------- /test/readable.js: -------------------------------------------------------------------------------- 1 | var tape = require('tape') 2 | var streams = require('../') 3 | 4 | tape('read data', function (t) { 5 | var rs = streams.Readable({ 6 | read: function (cb) { 7 | cb(null, Buffer('hello')) 8 | } 9 | }) 10 | 11 | rs.once('data', function (data) { 12 | rs.pause() 13 | t.same(data, Buffer('hello'), 'same data') 14 | t.end() 15 | }) 16 | }) 17 | 18 | tape('read data and resume', function (t) { 19 | var rs = streams.Readable({ 20 | read: function (cb) { 21 | cb(null, Buffer('hello')) 22 | } 23 | }) 24 | 25 | rs.once('data', function (data) { 26 | rs.pause() 27 | t.same(data, Buffer('hello'), 'same data') 28 | rs.once('data', function (data) { 29 | rs.pause() 30 | t.same(data, Buffer('hello'), 'same data') 31 | t.end() 32 | }) 33 | }) 34 | }) 35 | 36 | tape('read and destroy', function (t) { 37 | var rs = streams.Readable({ 38 | read: function (cb) { 39 | cb(null, Buffer('hello')) 40 | } 41 | }) 42 | 43 | rs.once('data', function (data) { 44 | rs.destroy() 45 | t.same(data, Buffer('hello'), 'same data') 46 | }) 47 | 48 | rs.on('close', function () { 49 | t.pass('closed') 50 | t.end() 51 | }) 52 | }) 53 | 54 | tape('read and destroy with custom teardown', function (t) { 55 | var closed = false 56 | var rs = streams.Readable({ 57 | read: function (cb) { 58 | cb(null, Buffer('hello')) 59 | }, 60 | destroy: function (cb) { 61 | t.pass('close is called') 62 | closed = true 63 | setTimeout(cb, 100) 64 | } 65 | }) 66 | 67 | rs.once('data', function (data) { 68 | rs.destroy() 69 | t.same(data, Buffer('hello'), 'same data') 70 | }) 71 | 72 | rs.on('close', function () { 73 | t.ok(closed, 'was closed') 74 | t.end() 75 | }) 76 | }) 77 | 78 | tape('stream can end', function (t) { 79 | var data = Buffer('hello') 80 | var rs = streams.Readable({ 81 | read: function (cb) { 82 | var buf = data 83 | data = null 84 | cb(null, buf) 85 | } 86 | }) 87 | 88 | var once = true 89 | 90 | rs.on('data', function (data) { 91 | t.ok(once, 'only once') 92 | t.same(data, Buffer('hello')) 93 | once = false 94 | }) 95 | rs.on('end', function () { 96 | t.end() 97 | }) 98 | }) 99 | 100 | tape('pause pauses end', function (t) { 101 | var data = Buffer('hello') 102 | var rs = streams.Readable({ 103 | read: function (cb) { 104 | var buf = data 105 | data = null 106 | cb(null, buf) 107 | } 108 | }) 109 | 110 | var paused = false 111 | var once = true 112 | 113 | rs.on('data', function (data) { 114 | t.ok(once, 'only once') 115 | t.same(data, Buffer('hello')) 116 | once = false 117 | paused = true 118 | rs.pause() 119 | setTimeout(function () { 120 | paused = false 121 | rs.resume() 122 | }, 100) 123 | }) 124 | 125 | rs.on('end', function () { 126 | t.ok(!paused, 'not paused') 127 | t.end() 128 | }) 129 | }) 130 | 131 | tape('push', function (t) { 132 | var expected = [ 133 | Buffer('hello'), 134 | Buffer('how'), 135 | Buffer('are'), 136 | Buffer('you?') 137 | ] 138 | 139 | var rs = streams.Readable({ 140 | read: function (cb) { 141 | this.push(Buffer('hello')) 142 | this.push(Buffer('how')) 143 | this.push(Buffer('are')) 144 | this.push(Buffer('you?')) 145 | cb(null, null) 146 | } 147 | }) 148 | 149 | rs.on('data', function (data) { 150 | t.same(data, expected.shift(), 'expected data') 151 | }) 152 | 153 | rs.on('end', function () { 154 | t.same(expected.length, 0, 'no more data') 155 | t.end() 156 | }) 157 | }) 158 | -------------------------------------------------------------------------------- /test/writable.js: -------------------------------------------------------------------------------- 1 | var tape = require('tape') 2 | var streams = require('../') 3 | 4 | tape('write', function (t) { 5 | var ws = streams.Writable({ 6 | write: function (data, cb) { 7 | t.same(data, Buffer('hello'), 'wrote hello') 8 | t.end() 9 | cb() 10 | } 11 | }) 12 | 13 | ws.write(Buffer('hello')) 14 | }) 15 | 16 | tape('write multiple times', function (t) { 17 | t.plan(5) 18 | 19 | var ws = streams.Writable({ 20 | write: function (data, cb) { 21 | t.same(data, Buffer('hello'), 'wrote hello') 22 | cb() 23 | } 24 | }) 25 | 26 | ws.write(Buffer('hello')) 27 | ws.write(Buffer('hello')) 28 | ws.write(Buffer('hello')) 29 | ws.write(Buffer('hello')) 30 | ws.write(Buffer('hello')) 31 | }) 32 | 33 | tape('write one at a time', function (t) { 34 | t.plan(10) 35 | 36 | var writing = false 37 | var ws = streams.Writable({ 38 | write: function (data, cb) { 39 | t.ok(!writing, 'not writing') 40 | writing = true 41 | t.same(data, Buffer('hello'), 'wrote hello') 42 | setTimeout(function () { 43 | writing = false 44 | cb() 45 | }, 50) 46 | } 47 | }) 48 | 49 | ws.write(Buffer('hello')) 50 | ws.write(Buffer('hello')) 51 | ws.write(Buffer('hello')) 52 | ws.write(Buffer('hello')) 53 | ws.write(Buffer('hello')) 54 | }) 55 | 56 | tape('emits finish', function (t) { 57 | var writing = false 58 | var ws = streams.Writable({ 59 | write: function (data, cb) { 60 | t.ok(!writing, 'not writing') 61 | writing = true 62 | t.same(data, Buffer('hello'), 'wrote hello') 63 | setTimeout(function () { 64 | writing = false 65 | cb() 66 | }, 50) 67 | } 68 | }) 69 | 70 | ws.on('finish', function () { 71 | t.ok(!writing, 'not writing') 72 | t.end() 73 | }) 74 | 75 | ws.write(Buffer('hello')) 76 | ws.write(Buffer('hello')) 77 | ws.end() 78 | }) 79 | 80 | tape('emits close after finish', function (t) { 81 | var finished = false 82 | var ws = streams.Writable({ 83 | write: function (data, cb) { 84 | t.same(data, Buffer('hello'), 'wrote hello') 85 | cb() 86 | } 87 | }) 88 | 89 | ws.on('finish', function () { 90 | t.ok(!finished, 'not finished') 91 | finished = true 92 | }) 93 | 94 | ws.on('close', function () { 95 | t.ok(finished, 'was finished') 96 | t.end() 97 | }) 98 | 99 | ws.write(Buffer('hello')) 100 | ws.write(Buffer('hello')) 101 | ws.end() 102 | }) 103 | -------------------------------------------------------------------------------- /transform.js: -------------------------------------------------------------------------------- 1 | var duplex = require('./duplex') 2 | var inherits = require('inherits') 3 | 4 | module.exports = Transform 5 | 6 | function TransformState () { 7 | this.afterTransform = null 8 | this.writeData = null 9 | this.writeCallback = null 10 | this.readCallback = null 11 | } 12 | 13 | function Transform (opts) { 14 | if (!(this instanceof Transform)) return new Transform(opts) 15 | if (!opts) opts = {} 16 | 17 | duplex.call(this) 18 | this._writableState.halfOpen = false 19 | 20 | if (opts.transform) this._transform = opts.transform 21 | if (opts.end) this._end = opts.end 22 | if (opts.destroy) this._destroy = opts.destroy 23 | 24 | var self = this 25 | var state = this._transformState = new TransformState() 26 | 27 | state.afterTransform = afterTransform 28 | 29 | function afterTransform (err, data) { 30 | if (err) return self.destroy(err) 31 | if (data) self.push(data) 32 | 33 | var writeCallback = state.writeCallback 34 | var readCallback = state.readCallback 35 | 36 | state.writeCallback = null 37 | state.readCallback = null 38 | 39 | writeCallback() 40 | readCallback() 41 | } 42 | } 43 | 44 | inherits(Transform, duplex) 45 | 46 | Transform.prototype._transform = function (data, cb) { 47 | cb(null, data) 48 | } 49 | 50 | Transform.prototype._write = function (data, cb) { 51 | var state = this._transformState 52 | state.writeData = data 53 | state.writeCallback = cb 54 | transform(this, state) 55 | } 56 | 57 | Transform.prototype._read = function (cb) { 58 | var state = this._transformState 59 | state.readCallback = cb 60 | transform(this, state) 61 | } 62 | 63 | function transform (self, state) { 64 | if (!state.writeData || !state.readCallback || !state.writeCallback) return 65 | var data = state.writeData 66 | state.writeData = null 67 | self._transform(data, state.afterTransform) 68 | } 69 | -------------------------------------------------------------------------------- /writable.js: -------------------------------------------------------------------------------- 1 | var events = require('events') 2 | var inherits = require('inherits') 3 | 4 | module.exports = Writable 5 | 6 | function WritableState () { 7 | this.vintageStream = true 8 | this.destroyed = false 9 | this.destroyReason = null 10 | this.toBuffer = false 11 | this.halfOpen = true 12 | this.syncWrite = false 13 | this.corked = false 14 | this.drained = true 15 | this.ended = false 16 | this.finishEmitted = false 17 | this.writing = false 18 | this.buffer = [] 19 | this.source = null 20 | this.afterPipe = null 21 | this.afterDestroy = null 22 | this.afterWrite = null 23 | this.afterEnd = null 24 | } 25 | 26 | function Writable (opts) { 27 | if (!(this instanceof Writable)) return new Writable(opts) 28 | if (!opts) opts = {} 29 | 30 | events.EventEmitter.call(this) 31 | 32 | if (opts.write) this._write = opts.write 33 | if (opts.end) this._end = opts.end 34 | if (opts.destroy) this._destroy = opts.destroy 35 | 36 | var self = this 37 | var state = this._writableState = new WritableState() 38 | 39 | state.toBuffer = !opts.toBuffer && !opts.writableToBuffer 40 | state.afterDestroy = afterDestroy 41 | state.afterWrite = afterWrite 42 | state.afterEnd = afterEnd 43 | 44 | function afterWrite (err) { 45 | if (err) return self.destroy(err) 46 | state.writing = false 47 | 48 | if (!state.drained) { 49 | state.drained = true 50 | if (state.source) state.source.resume() 51 | self.emit('drain') 52 | } 53 | 54 | if (!state.writing && !state.syncWrite && state.buffer.length) { 55 | write(self, state, state.buffer.shift()) 56 | } 57 | 58 | emitFinishMaybe(self, state) 59 | } 60 | 61 | function afterDestroy (err) { 62 | if (!err) err = state.destroyReason 63 | 64 | if (state.afterPipe) { 65 | var cb = state.afterPipe 66 | state.afterPipe = null 67 | cb(err || (state.finishEmitted ? null : (state.destroyReason || new Error('Stream closed prematurely')))) 68 | } 69 | 70 | if (err) self.emit('error', err) 71 | self.emit('close') 72 | } 73 | 74 | function afterEnd (err) { 75 | if (err) return self.destroy(err) 76 | self.emit('finish') 77 | if (!state.halfOpen) self.push(null) 78 | self.destroyMaybe() 79 | } 80 | } 81 | 82 | inherits(Writable, events.EventEmitter) 83 | 84 | Writable.prototype.cork = function () { 85 | var state = this._writableState 86 | if (state.corked) return 87 | state.corked = true 88 | this.emit('cork') 89 | } 90 | 91 | Writable.prototype.uncork = function () { 92 | var state = this._writableState 93 | if (!state.corked) return 94 | state.corked = false 95 | this.emit('uncork') 96 | if (!state.writing) state.afterWrite(null) 97 | } 98 | 99 | Writable.prototype.write = function (data) { 100 | var state = this._writableState 101 | return writeMaybe(this, state, (!state.objectMode && typeof data === 'string') ? Buffer(data) : data) 102 | } 103 | 104 | Writable.prototype.end = function (data) { 105 | var state = this._writableState 106 | if (state.ended) return 107 | if (data) this.write(data) 108 | state.ended = true 109 | emitFinishMaybe(this, state) 110 | } 111 | 112 | Writable.prototype._write = function (data, cb) { 113 | // Override me 114 | cb(null) 115 | } 116 | 117 | Writable.prototype._end = function (cb) { 118 | // Override me 119 | cb(null) 120 | } 121 | 122 | Writable.prototype.destroyMaybe = function () { 123 | if (this._writableState.finishEmitted) this.destroy() 124 | } 125 | 126 | Writable.prototype.destroy = 127 | Writable.prototype.destroyWritable = function (err) { 128 | var state = this._writableState 129 | if (state.destroyed) return 130 | state.destroyed = true 131 | 132 | if (err) state.destroyReason = err 133 | state.writing = true // set these to stop reading 134 | 135 | if (state.source) { 136 | var src = state.source 137 | state.source = null 138 | if (src.destroy) src.destroy() 139 | } 140 | 141 | this._destroy(state.afterDestroy) 142 | } 143 | 144 | Writable.prototype._destroy = function (cb) { 145 | // Override me 146 | cb(null) 147 | } 148 | 149 | function writeMaybe (self, state, data) { 150 | if (state.destroyed || state.ended) return false 151 | 152 | if (!state.writing && !state.buffer.length && !state.corked) { 153 | write(self, state, data) 154 | return true 155 | } 156 | 157 | var length = state.buffer.push(data) 158 | if (state.drained) state.drained = length < 16 159 | return state.drained 160 | } 161 | 162 | function write (self, state, data) { 163 | while (!state.destroyed && !state.writing) { 164 | state.writing = true 165 | state.syncWrite = true 166 | self._write(data, state.afterWrite) 167 | state.syncWrite = false 168 | if (state.writing) return 169 | if (!state.buffer.length) break 170 | data = state.buffer 171 | } 172 | 173 | emitFinishMaybe(self, state) 174 | } 175 | 176 | function emitFinishMaybe (self, state) { 177 | if (!state.ended || state.buffer.length || state.finishEmitted || state.destroyed || state.corked || state.writing) return 178 | state.finishEmitted = true 179 | state.writing = true 180 | self._end(state.afterEnd) 181 | } 182 | --------------------------------------------------------------------------------