├── .editorconfig ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── example.js ├── index.js ├── lib ├── chunk.js ├── hook.js ├── interceptable.js └── queue.js ├── package.json └── test └── intercept.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | npm-debug.log 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - stable 4 | - "0.12" 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) Tomas Aparicio 4 | 5 | Permission is hereby granted, free of charge, to any person 6 | obtaining a copy of this software and associated documentation 7 | files (the "Software"), to deal in the Software without 8 | restriction, including without limitation the rights to use, 9 | copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the 11 | Software is furnished to do so, subject to the following 12 | conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | OTHER DEALINGS IN THE SOFTWARE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # stream-interceptor [![Build Status](https://api.travis-ci.org/h2non/stream-interceptor.svg?branch=master&style=flat)](https://travis-ci.org/h2non/stream-interceptor) [![NPM](https://img.shields.io/npm/v/stream-interceptor.svg)](https://www.npmjs.org/package/stream-interceptor) 2 | 3 | Tiny [node.js](https://nodejs.org) module to **intercept**, **modify** and/or **ignore** chunks of data and events in any [readable compatible stream](https://nodejs.org/api/stream.html#stream_class_stream_readable) before it's processed by other stream consumers (e.g: via `pipe()`). 4 | 5 | It becomes particularly useful to deal with net/http/fs streams. 6 | 7 | ## Installation 8 | 9 | ```bash 10 | npm install stream-interceptor 11 | ``` 12 | 13 | ## Examples 14 | 15 | ##### Existent stream 16 | 17 | ```js 18 | var Readable = require('stream').Readable 19 | var interceptor = require('stream-interceptor') 20 | 21 | // Create a new Readable stream 22 | var stream = new Readable 23 | stream._read = function () { /* ... */ } 24 | 25 | // Make it interceptable 26 | interceptor(stream) 27 | 28 | // Prepare to capture chunks 29 | stream.capture(function (chunk, next) { 30 | next(chunk + chunk) 31 | }) 32 | 33 | // We gonna handle strings 34 | stream.setEncoding('utf8') 35 | 36 | // Push chunks to the stream 37 | stream.push('Foo') 38 | stream.push('Bar') 39 | stream.push(null) // we're done! 40 | 41 | // Listen for events like a stream consumer 42 | stream.on('data', function (chunk) { 43 | console.log('Modified chunk:', chunk) 44 | }) 45 | 46 | stream.on('end', function () { 47 | console.log('We are done!') 48 | }) 49 | ``` 50 | 51 | ##### Capture HTTP response 52 | 53 | ```js 54 | var http = require('http') 55 | var interceptor = require('stream-interceptor') 56 | 57 | // Test server 58 | var server = http.createServer(function (req, res) { 59 | res.writeHead(200) 60 | res.write('Foo') 61 | res.write('Bar') 62 | res.end() 63 | }).listen(3000) 64 | 65 | http.get('http://localhost:3000', function (response) { 66 | // http.IncomingMessage implements a Readable stream 67 | var stream = interceptor(response) 68 | 69 | stream.capture(function (chunk, next) { 70 | next(chunk + chunk + '\n') 71 | }) 72 | 73 | stream.on('end', function () { 74 | console.log('Response status:', response.statusCode) 75 | server.close() 76 | }) 77 | 78 | stream.pipe(process.stdout) 79 | }) 80 | ``` 81 | 82 | ##### Capture asynchronously 83 | 84 | ```js 85 | var Readable = require('stream').Readable 86 | var interceptor = require('stream-interceptor') 87 | 88 | // Create a new Readable stream 89 | var stream = new Readable 90 | stream._read = function () { /* ... */ } 91 | 92 | // Make it interceptable 93 | interceptor(stream) 94 | 95 | // Prepare to capture chunks asyncronously 96 | // Chunks will be processed always as FIFO queue 97 | stream.capture(function (chunk, next) { 98 | setTimeout(function () { 99 | next(chunk + chunk + '\n') 100 | }, Math.random() * 1000) 101 | }) 102 | 103 | // We gonna handle strings 104 | stream.setEncoding('utf8') 105 | 106 | // Push chunks to the stream 107 | stream.push('Slow Foo') 108 | stream.push('Slow Bar') 109 | stream.push(null) // we're done! 110 | 111 | stream.pipe(process.stdout) 112 | ``` 113 | 114 | ##### Ignore chunks 115 | 116 | ```js 117 | var Readable = require('stream').Readable 118 | var interceptor = require('stream-interceptor') 119 | 120 | // Create a new Readable stream 121 | var stream = new Readable 122 | stream._read = function () { /* ... */ } 123 | 124 | // Make it interceptable 125 | interceptor(stream) 126 | 127 | // Prepare to capture chunks 128 | stream.capture(function (chunk, next) { 129 | if (chunk === 'Bad') { 130 | return next(true) // Ignore chunk 131 | } 132 | next(chunk + '\n') 133 | }) 134 | 135 | // We gonna handle strings 136 | stream.setEncoding('utf8') 137 | 138 | // Push chunks to the stream 139 | stream.push('Bad') 140 | stream.push('Ugly') 141 | stream.push('Good') 142 | stream.push(null) // we're done! 143 | 144 | stream.pipe(process.stdout) 145 | ``` 146 | 147 | ##### Interceptable stream 148 | 149 | ```js 150 | var Interceptable = require('stream-interceptor').Interceptable 151 | 152 | // Implements both Readable and Interceptable stream 153 | var stream = new Interceptable 154 | stream._read = function () { /* ... */ } 155 | 156 | // Prepate to capture chunks 157 | stream.capture(function (chunk, next) { 158 | next(chunk + chunk + '\n') 159 | }) 160 | 161 | // Push chunks to the stream 162 | stream.push('Foo') 163 | stream.push('Bar') 164 | stream.push(null) // we're done! 165 | 166 | stream.pipe(process.stdout) 167 | ``` 168 | 169 | ##### Event interceptor 170 | 171 | ```js 172 | var Interceptable = require('stream-interceptor').Interceptable 173 | 174 | // Implements both Readable and Interceptable stream 175 | var stream = new Interceptable 176 | stream._read = function () { /* ... */ } 177 | 178 | // Prepate to capture events 179 | stream.captureEvent('error', function (err, next) { 180 | next(true) // always ignore errors 181 | }) 182 | 183 | // Prepate to capture chunks 184 | stream.capture(function (chunk, next) { 185 | next(chunk + chunk + '\n') 186 | }) 187 | 188 | stream.on('error', function (err) { 189 | console.error('Error:', err) // won't be called 190 | }) 191 | 192 | // Push data in the stream 193 | stream.push('Foo') 194 | stream.push('Bar') 195 | stream.push(null) 196 | 197 | // Simulate an error 198 | stream.emit('error', 'Damn!') 199 | 200 | stream.pipe(process.stdout) 201 | ``` 202 | 203 | ## API 204 | 205 | ### streamIntercept(readableStream) => Interceptable 206 | 207 | Wraps any readable stream turning it an interceptable stream. 208 | `Interceptable` stream implements the same interface as `Readable`. 209 | 210 | ### Interceptable([ options ]) 211 | Alias: `Interceptor` 212 | 213 | Creates a new `Interceptable` stream. Inherits from `Readable` stream. 214 | 215 | #### Interceptable#interceptable => `boolean` 216 | 217 | Property to determine if the stream is interceptable. 218 | 219 | #### Interceptable#capture(fn) => `Interceptable` 220 | 221 | Subscribe to capture chunks of data emitted by the stream. 222 | 223 | #### Interceptable#captureEvent(event, fn) => `Interceptable` 224 | 225 | Capture data emitted by a specific event. 226 | `fn` argument expects two arguments: `chunk, callback`. 227 | 228 | When you're done, **you must call the callback** passing the new argument: `callback(chunk)` 229 | 230 | You can optionally ignore chunks passing `true` to the callback: `callback(true)`. 231 | 232 | ### isInterceptor(stream) => `boolean` 233 | 234 | ### Hook() 235 | 236 | Hook layer internally used capture and handle events of the stream. 237 | 238 | ### Queue() 239 | 240 | FIFO queue implementation internally used. 241 | 242 | ### Chunk() 243 | 244 | Internal chunk event structure. 245 | 246 | ## License 247 | 248 | MIT - Tomas Aparicio 249 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | var Readable = require('stream').Readable 2 | var interceptor = require('.') 3 | 4 | // Create a new Readable stream 5 | var stream = new Readable 6 | stream._read = function () { /* ... */ } 7 | 8 | // Make it interceptable 9 | interceptor(stream) 10 | 11 | // Prepare to capture chunks 12 | stream.capture(function (chunk, next) { 13 | if (chunk === 'Bad') { 14 | return next(true) // Ignore chunk 15 | } 16 | next(chunk + '\n') 17 | }) 18 | 19 | // We gonna handle strings 20 | stream.setEncoding('utf8') 21 | 22 | // Push chunks to the stream 23 | stream.push('Bad') 24 | stream.push('Ugly') 25 | stream.push('Good') 26 | stream.push(null) // we're done! 27 | 28 | stream.pipe(process.stdout) 29 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var Hook = require('./lib/hook') 2 | var Chunk = require('./lib/chunk') 3 | var Queue = require('./lib/queue') 4 | var Interceptable = require('./lib/interceptable') 5 | 6 | exports = module.exports = streamInterceptor 7 | 8 | function streamInterceptor(stream) { 9 | if (isInterceptor(stream)) return stream 10 | 11 | Object 12 | .keys(Interceptable.prototype) 13 | .forEach(function (key) { 14 | stream[key] = Interceptable.prototype[key] 15 | }) 16 | 17 | stream._constructor() 18 | return stream 19 | } 20 | 21 | exports.Hook = Hook 22 | exports.Chunk = Chunk 23 | exports.Queue = Queue 24 | exports.Interceptor = 25 | exports.Interceptable = Interceptable 26 | exports.isInterceptor = isInterceptor 27 | 28 | function isInterceptor(stream) { 29 | return Interceptable.prototype.isIntercepting === stream.isIntercepting 30 | } 31 | -------------------------------------------------------------------------------- /lib/chunk.js: -------------------------------------------------------------------------------- 1 | module.exports = Chunk 2 | 3 | function Chunk(data, fn) { 4 | this.ready = false 5 | this.ignore = false 6 | this.data = data 7 | this.resolver = fn 8 | } 9 | 10 | Chunk.prototype.resolve = function () { 11 | var data = this.data 12 | this.resolver(this.data) 13 | this.data = null 14 | } 15 | 16 | Chunk.prototype.flag = function (data) { 17 | this.ready = true 18 | this.data = data 19 | } 20 | -------------------------------------------------------------------------------- /lib/hook.js: -------------------------------------------------------------------------------- 1 | var Chunk = require('./chunk') 2 | var Queue = require('./queue') 3 | 4 | module.exports = Hook 5 | 6 | function Hook() { 7 | this.stack = [] 8 | this.queue = new Queue 9 | } 10 | 11 | Hook.prototype.push = function (hook) { 12 | this.stack.push(hook) 13 | } 14 | 15 | Hook.prototype.run = function (data, done) { 16 | var index = 0 17 | var stack = this.stack 18 | var queue = this.queue 19 | var chunk = new Chunk(data, done) 20 | 21 | function finish(data) { 22 | if (data === true) chunk.ignore = true 23 | // Flag as ready 24 | chunk.flag(data) 25 | // Empty queue in FIFO order 26 | queue.drain() 27 | // Clean reference 28 | chunk = null 29 | } 30 | 31 | function next(data) { 32 | if (data === true) return finish(true) 33 | if (index++ === (stack.length - 1)) return finish(data) 34 | run(data) 35 | } 36 | 37 | function run(data) { 38 | var job = stack[index] 39 | if (data === undefined) job(next) 40 | else job(data, next) 41 | } 42 | 43 | queue.push(chunk) 44 | run(data) 45 | } 46 | 47 | exports.Hook = Hook 48 | -------------------------------------------------------------------------------- /lib/interceptable.js: -------------------------------------------------------------------------------- 1 | var Readable = require('stream').Readable 2 | var ReadablePush = Readable.prototype.push 3 | var Emitter = require('events').EventEmitter 4 | var EmitterEmit = Emitter.prototype.emit 5 | var Hook = require('./hook') 6 | 7 | module.exports = Interceptable 8 | 9 | function Interceptable(opts) { 10 | if (!(this instanceof Interceptable)) 11 | return new Interceptable(opts) 12 | 13 | this._constructor() 14 | Readable.call(this, opts) 15 | } 16 | 17 | Interceptable.prototype = Object.create(Readable.prototype) 18 | 19 | Interceptable.prototype.constructor = Interceptable 20 | 21 | Interceptable.prototype._constructor = function () { 22 | this._hooks = {} 23 | this._ended = false 24 | } 25 | 26 | Interceptable.prototype.interceptable = true 27 | 28 | Interceptable.prototype._forward = function (event) { 29 | return function (chunk) { 30 | EmitterEmit.call(this, event, chunk) 31 | }.bind(this) 32 | } 33 | 34 | Interceptable.prototype.isIntercepting = function () { 35 | return this.push === Interceptable.prototype.push 36 | } 37 | 38 | Interceptable.prototype.capture = function (fn) { 39 | return this.captureEvent('data', fn) 40 | } 41 | 42 | Interceptable.prototype.captureEvent = function (event, fn) { 43 | var hook = this._hooks[event] 44 | 45 | if (!hook) { 46 | hook = this._hooks[event] = new Hook() 47 | } 48 | 49 | hook.push(fn) 50 | return this 51 | } 52 | 53 | Interceptable.prototype.emit = function (event, data) { 54 | var hook = this._hooks[event] 55 | 56 | if (!hook) { 57 | return EmitterEmit.call(this, event, data) 58 | } 59 | 60 | hook.run(data, this._forward(event)) 61 | return this 62 | } 63 | 64 | Interceptable.prototype.push = function (chunk, encoding) { 65 | if (this._ended) return false 66 | 67 | var self = this 68 | var hook = this._hooks.data 69 | 70 | if (chunk === null && !this._ended && hook) { 71 | this._ended = true 72 | hook.queue.end(function () { 73 | ReadablePush.call(self, null) 74 | }) 75 | return false 76 | } 77 | 78 | if (!this._ended && hook && chunk) { 79 | hook.queue.inc() // Increment queue counter 80 | } 81 | 82 | return ReadablePush.call(this, chunk, encoding) 83 | } 84 | -------------------------------------------------------------------------------- /lib/queue.js: -------------------------------------------------------------------------------- 1 | module.exports = Queue 2 | 3 | function Queue() { 4 | this.queue = [] 5 | this.count = 0 6 | this.ended = false 7 | this.closed = false 8 | } 9 | 10 | Queue.prototype.inc = function () { 11 | this.count += 1 12 | } 13 | 14 | Queue.prototype.dec = function () { 15 | this.count -= 1 16 | } 17 | 18 | Queue.prototype.push = function (chunk) { 19 | this.queue.push(chunk) 20 | } 21 | 22 | Queue.prototype.isEmpty = function () { 23 | return this.ended && !this.count 24 | } 25 | 26 | Queue.prototype.resolve = function (chunk, index) { 27 | this.remove(chunk, index) 28 | chunk.resolve() 29 | } 30 | 31 | Queue.prototype.remove = function (chunk, index) { 32 | this.dec() 33 | this.queue.splice(index, 1) 34 | } 35 | 36 | Queue.prototype.flush = function () { 37 | this.count = 0 38 | this.queue = [] 39 | this.closed = true 40 | this.onEnd && this.onEnd() 41 | this.onEnd = null 42 | } 43 | 44 | Queue.prototype.end = function (cb) { 45 | this.onEnd = cb 46 | this.ended = true 47 | if (!this.count) this.onEnd() 48 | } 49 | 50 | /** 51 | * Drain queue in FIFO order. 52 | * Only those chunks which are flagged as ready will be removed. 53 | */ 54 | Queue.prototype.drain = function () { 55 | if (this.closed) return 56 | 57 | var queue = this.queue 58 | for (var index = 0; index < queue.length; index += 1) { 59 | var chunk = queue[index] 60 | 61 | if (!chunk.ready) break 62 | 63 | if (chunk.ignore) { 64 | this.remove(chunk, index) 65 | index -= 1 66 | continue 67 | } 68 | 69 | this.resolve(chunk, index) 70 | index -= 1 71 | } 72 | 73 | if (this.isEmpty()) this.flush() 74 | } 75 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stream-interceptor", 3 | "version": "0.1.1", 4 | "description": "Intercept, modify and ignore chunks data and events in any readable stream", 5 | "repository": "h2non/stream-interceptor", 6 | "author": "Tomas Aparicio", 7 | "license": "MIT", 8 | "keywords": [ 9 | "stream", 10 | "intercept", 11 | "interceptor", 12 | "control", 13 | "modify", 14 | "capture", 15 | "sniff", 16 | "sniffer", 17 | "spy", 18 | "events", 19 | "emitter", 20 | "readable", 21 | "pipe", 22 | "http", 23 | "transform" 24 | ], 25 | "engines": { 26 | "node": ">= 0.12" 27 | }, 28 | "scripts": { 29 | "test": "./node_modules/.bin/mocha --reporter spec --ui tdd test/*" 30 | }, 31 | "devDependencies": { 32 | "mocha": "^2.2.5" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/intercept.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | const stream = require('stream') 3 | const Writable = stream.Writable 4 | const Readable = stream.Readable 5 | const http = require('http') 6 | const intercept = require('..') 7 | 8 | suite('intercept', function () { 9 | test('basic interceptor', function (done) { 10 | var stream = newInterceptableStream() 11 | 12 | stream.capture(function (chunk, next) { 13 | next(chunk + chunk) 14 | }) 15 | 16 | stream.push('Foo') 17 | stream.push('Bar') 18 | stream.push(null) 19 | 20 | var buf = [] 21 | stream.on('data', function (chunk) { 22 | buf.push(chunk) 23 | }) 24 | 25 | stream.on('end', function () { 26 | assert.equal(buf.length, 2) 27 | assert.equal(buf.shift(), 'FooFoo') 28 | assert.equal(buf.shift(), 'BarBar') 29 | done() 30 | }) 31 | }) 32 | 33 | test('async', function (done) { 34 | var stream = newInterceptableStream() 35 | 36 | stream.capture(function (chunk, next) { 37 | if (chunk.toString() === 'Foo') 38 | setTimeout(function () { next(chunk + chunk) }, 10) 39 | else if (chunk.toString() === 'Boo') 40 | setTimeout(function () { next(chunk + chunk) }, 30) 41 | else 42 | next(chunk + chunk) 43 | }) 44 | 45 | stream.push('Foo') 46 | stream.push('Bar') 47 | stream.push('Boo') 48 | stream.push(null) 49 | 50 | var buf = [] 51 | stream.on('data', function (chunk) { 52 | buf.push(chunk) 53 | }) 54 | 55 | stream.on('end', function () { 56 | assert.equal(buf.length, 3) 57 | assert.equal(buf.shift(), 'FooFoo') 58 | assert.equal(buf.shift(), 'BarBar') 59 | assert.equal(buf.shift(), 'BooBoo') 60 | done() 61 | }) 62 | }) 63 | 64 | test('ignore chunk', function (done) { 65 | var stream = newInterceptableStream() 66 | 67 | stream.capture(function (chunk, next) { 68 | if (chunk.toString() === 'Bar') next(true) 69 | else next(chunk + chunk) 70 | }) 71 | 72 | stream.push('Foo') 73 | stream.push('Bar') 74 | stream.push('Boo') 75 | stream.push(null) 76 | 77 | var buf = [] 78 | stream.on('data', function (chunk) { 79 | buf.push(chunk) 80 | }) 81 | 82 | stream.on('end', function () { 83 | assert.equal(buf.length, 2) 84 | assert.equal(buf.shift(), 'FooFoo') 85 | assert.equal(buf.shift(), 'BooBoo') 86 | done() 87 | }) 88 | }) 89 | 90 | test('capture event', function () { 91 | var stream = newInterceptableStream() 92 | 93 | var delay = 10 94 | var now = Date.now() 95 | stream.captureEvent('end', function (next) { 96 | setTimeout(next, 10) 97 | }) 98 | 99 | stream.push('Foo') 100 | stream.push('Bar') 101 | stream.push(null) 102 | 103 | stream.on('end', function () { 104 | assert.equal(buf.length, 2) 105 | assert.equal(buf.shift(), 'Foo') 106 | assert.equal(buf.shift(), 'Boo') 107 | assert.equal(Date.now() - now >= delay, true) 108 | done() 109 | }) 110 | }) 111 | 112 | test('pipe', function (done) { 113 | var stream = newInterceptableStream() 114 | var writable = new Writable 115 | 116 | var buf = [] 117 | writable._write = function (chunk, encoding, next) { 118 | buf.push(chunk) 119 | next() 120 | } 121 | 122 | stream.capture(function (chunk, next) { 123 | setTimeout(function () { 124 | next(chunk + chunk) 125 | }, Math.random() * 5) 126 | }) 127 | 128 | stream.push('Foo') 129 | stream.push('Bar') 130 | stream.push('Boo') 131 | stream.push(null) 132 | 133 | writable.on('finish', function () { 134 | assert.equal(buf.length, 3) 135 | assert.equal(buf.shift(), 'FooFoo') 136 | assert.equal(buf.shift(), 'BarBar') 137 | assert.equal(buf.shift(), 'BooBoo') 138 | done() 139 | }) 140 | 141 | stream.pipe(writable) 142 | }) 143 | 144 | test('http response', function (done) { 145 | var server = http.createServer(function (req, res) { 146 | res.writeHead(200) 147 | res.write('foo') 148 | 149 | setTimeout(function () { 150 | res.write('bar') 151 | }, 25) 152 | 153 | setTimeout(function () { 154 | res.write('boo') 155 | res.end() 156 | }, 50) 157 | }) 158 | server.listen(8989) 159 | 160 | http.get('http://localhost:8989/index.html', function (res) { 161 | var stream = intercept(res) 162 | stream.capture(function (chunk, next) { 163 | next(chunk + chunk) 164 | }) 165 | 166 | var buf = [] 167 | stream.on('data', function (chunk) { 168 | buf.push(chunk) 169 | }) 170 | 171 | stream.on('end', function () { 172 | assert.equal(buf.length, 3) 173 | assert.equal(buf.shift(), 'foofoo') 174 | assert.equal(buf.shift(), 'barbar') 175 | assert.equal(buf.shift(), 'booboo') 176 | server.close(done) 177 | }) 178 | }).on('error', done) 179 | }) 180 | }) 181 | 182 | function newInterceptableStream() { 183 | var stream = intercept(new Readable) 184 | stream._read = function () {} 185 | return stream 186 | } 187 | --------------------------------------------------------------------------------