package/package.json000644 001750 001750 0000001431 12566522437013032 0ustar00000000 000000 { "name": "stream-splicer", "version": "2.0.0", "description": "streaming pipeline with a mutable configuration", "main": "index.js", "dependencies": { "inherits": "^2.0.1", "readable-stream": "^2.0.2" }, "devDependencies": { "JSONStream": "^1.0.4", "concat-stream": "^1.4.6", "split": "^1.0.0", "tape": "^4.2.0", "through2": "^2.0.0" }, "scripts": { "test": "tape test/*.js" }, "repository": { "type": "git", "url": "git://github.com/substack/stream-splicer.git" }, "homepage": "https://github.com/substack/stream-splicer", "keywords": [ "stream", "mutable", "pipeline" ], "author": { "name": "James Halliday", "email": "mail@substack.net", "url": "http://substack.net" }, "license": "MIT" } package/LICENSE000644 001750 001750 0000002061 12566522063011544 0ustar00000000 000000 This software is released under the MIT license: Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package/index.js000644 001750 001750 0000012740 12566522233012210 0ustar00000000 000000 var Duplex = require('readable-stream').Duplex; var PassThrough = require('readable-stream').PassThrough; var Readable = require('readable-stream').Readable; var inherits = require('inherits'); var nextTick = typeof setImmediate !== 'undefined' ? setImmediate : process.nextTick ; module.exports = Pipeline; inherits(Pipeline, Duplex); module.exports.obj = function (streams, opts) { if (!opts && !Array.isArray(streams)) { opts = streams; streams = []; } if (!streams) streams = []; if (!opts) opts = {}; opts.objectMode = true; return new Pipeline(streams, opts); }; function Pipeline (streams, opts) { if (!(this instanceof Pipeline)) return new Pipeline(streams, opts); if (!opts && !Array.isArray(streams)) { opts = streams; streams = []; } if (!streams) streams = []; if (!opts) opts = {}; Duplex.call(this, opts); var self = this; this._options = opts; this._wrapOptions = { objectMode: opts.objectMode !== false }; this._streams = []; this.splice.apply(this, [ 0, 0 ].concat(streams)); this.once('finish', function () { self._notEmpty(); self._streams[0].end(); }); } Pipeline.prototype._read = function () { var self = this; this._notEmpty(); var r = this._streams[this._streams.length-1]; var buf, reads = 0; while ((buf = r.read()) !== null) { Duplex.prototype.push.call(this, buf); reads ++; } if (reads === 0) { var onreadable = function () { r.removeListener('readable', onreadable); self.removeListener('_mutate', onreadable); self._read() }; r.once('readable', onreadable); self.once('_mutate', onreadable); } }; Pipeline.prototype._write = function (buf, enc, next) { this._notEmpty(); this._streams[0]._write(buf, enc, next); }; Pipeline.prototype._notEmpty = function () { var self = this; if (this._streams.length > 0) return; var stream = new PassThrough(this._options); stream.once('end', function () { var ix = self._streams.indexOf(stream); if (ix >= 0 && ix === self._streams.length - 1) { Duplex.prototype.push.call(self, null); } }); this._streams.push(stream); this.length = this._streams.length; }; Pipeline.prototype.push = function (stream) { var args = [ this._streams.length, 0 ].concat([].slice.call(arguments)); this.splice.apply(this, args); return this._streams.length; }; Pipeline.prototype.pop = function () { return this.splice(this._streams.length-1,1)[0]; }; Pipeline.prototype.shift = function () { return this.splice(0,1)[0]; }; Pipeline.prototype.unshift = function () { this.splice.apply(this, [0,0].concat([].slice.call(arguments))); return this._streams.length; }; Pipeline.prototype.splice = function (start, removeLen) { var self = this; var len = this._streams.length; start = start < 0 ? len - start : start; if (removeLen === undefined) removeLen = len - start; removeLen = Math.max(0, Math.min(len - start, removeLen)); for (var i = start; i < start + removeLen; i++) { if (self._streams[i-1]) { self._streams[i-1].unpipe(self._streams[i]); } } if (self._streams[i-1] && self._streams[i]) { self._streams[i-1].unpipe(self._streams[i]); } var end = i; var reps = [], args = arguments; for (var j = 2; j < args.length; j++) (function (stream) { if (Array.isArray(stream)) { stream = new Pipeline(stream, self._options); } stream.on('error', function (err) { err.stream = this; self.emit('error', err); }); stream = self._wrapStream(stream); stream.once('end', function () { var ix = self._streams.indexOf(stream); if (ix >= 0 && ix === self._streams.length - 1) { Duplex.prototype.push.call(self, null); } }); reps.push(stream); })(arguments[j]); for (var i = 0; i < reps.length - 1; i++) { reps[i].pipe(reps[i+1]); } if (reps.length && self._streams[end]) { reps[reps.length-1].pipe(self._streams[end]); } if (reps[0] && self._streams[start-1]) { self._streams[start-1].pipe(reps[0]); } var sargs = [start,removeLen].concat(reps); var removed = self._streams.splice.apply(self._streams, sargs); for (var i = 0; i < reps.length; i++) { reps[i].read(0); } this.emit('_mutate'); this.length = this._streams.length; return removed; }; Pipeline.prototype.get = function () { if (arguments.length === 0) return undefined; var base = this; for (var i = 0; i < arguments.length; i++) { var index = arguments[i]; if (index < 0) { base = base._streams[base._streams.length + index]; } else { base = base._streams[index]; } if (!base) return undefined; } return base; }; Pipeline.prototype.indexOf = function (stream) { return this._streams.indexOf(stream); }; Pipeline.prototype._wrapStream = function (stream) { if (typeof stream.read === 'function') return stream; var w = new Readable(this._wrapOptions).wrap(stream); w._write = function (buf, enc, next) { if (stream.write(buf) === false) { stream.once('drain', next); } else nextTick(next); }; return w; }; package/.travis.yml000644 001750 001750 0000000164 12566522233012651 0ustar00000000 000000 language: node_js node_js: - "0.8" - "0.10" - "0.12" - "iojs" before_install: - npm install -g npm@~1.4.6 package/example/header.js000644 001750 001750 0000001255 12566522063013764 0ustar00000000 000000 var splicer = require('../'); var through = require('through2'); var JSONStream = require('JSONStream'); var split = require('split'); var headerData = {}; var headers = through.obj(function (buf, enc, next) { var line = buf.toString('utf8'); if (line === '') { this.push(headerData); pipeline.splice(1, 1, JSONStream.parse([ 'rows', true ])); } else { var m = /^(\S+):(.+)/.exec(line); var key = m && m[1].trim(); var value = m && m[2].trim(); if (m) headerData[key] = value; } next(); }); var pipeline = splicer([ split(), headers, JSONStream.stringify() ]); process.stdin.pipe(pipeline).pipe(process.stdout); package/readme.markdown000644 001750 001750 0000006361 12566522233013546 0ustar00000000 000000 # stream-splicer streaming pipeline with a mutable configuration This module is similar to [stream-combiner](https://npmjs.org/package/stream-combiner), but with a pipeline configuration that can be changed at runtime. [![build status](https://travis-ci.org/substack/stream-splicer.png?branch=master)](http://travis-ci.org/substack/stream-splicer) # example This example begins with an HTTP header parser that waits for an empty line to signify the end of the header. At that point, it switches to a streaming json parser to operate on the HTTP body. ``` js var splicer = require('stream-splicer'); var through = require('through2'); var JSONStream = require('JSONStream'); var split = require('split'); var headerData = {}; var headers = through.obj(function (buf, enc, next) { var line = buf.toString('utf8'); if (line === '') { this.push(headerData); pipeline.splice(1, 1, JSONStream.parse([ 'rows', true ])); } else { var m = /^(\S+):(.+)/.exec(line); var key = m && m[1].trim(); var value = m && m[2].trim(); if (m) headerData[key] = value; } next(); }); var pipeline = splicer([ split(), headers, JSONStream.stringify() ]); process.stdin.pipe(pipeline).pipe(process.stdout); ``` intput: ``` GET / HTTP/1.1 Host: substack.net User-Agent: echo {"rows":["beep","boop"]} ``` output: ``` $ echo -ne 'GET / HTTP/1.1\nHost: substack.net\nUser-Agent: echo\n\n{"rows":["beep","boop"]}\n' | node example/header.js [ {"Host":"substack.net","User-Agent":"echo"} , "beep" , "boop" ] ``` # methods ``` js var splicer = require('stream-splicer') ``` ## var pipeline = splicer(streams, opts) Create a `pipeline` duplex stream given an array of `streams`. Each `stream` will be piped to the next. Writes to `pipeline` get written to the first stream and data for reads from `pipeline` come from the last stream. For example, for streams `[ a, b, c, d ]`, this pipeline is constructed internally: ``` a.pipe(b).pipe(c).pipe(d) ``` Input will get written into `a`. Output will be read from `d`. If any of the elements in `streams` are arrays, they will be converted into nested pipelines. This is useful if you want to expose a hookable pipeline with grouped insertion points. ## var pipeline = splicer.obj(streams, opts) Create a `pipeline` with `opts.objectMode` set to true for convenience. ## var removed = pipeline.splice(index, howMany, stream, ...) Splice the pipeline starting at `index`, removing `howMany` streams and replacing them with each additional `stream` argument provided. The streams that were removed from the splice and returned. ## pipeline.push(stream, ...) Push one or more streams to the end of the pipeline. ## var stream = pipeline.pop() Pop a stream from the end of the pipeline. ## pipeline.unshift(stream, ...) Unshift one or more streams to the begining of the pipeline. ## var stream = pipeline.shift() Shift a stream from the begining of the pipeline. ## var stream = pipeline.get(index, ...) Return the stream at index `index, ...`. Indexes can be negative. Multiple indexes will traverse into nested pipelines. # attributes ## pipeline.length The number of streams in the pipeline # install With [npm](https://npmjs.org) do: ``` npm install stream-splicer ``` # license MIT package/test/combiner.js000644 001750 001750 0000001636 12566522063013661 0ustar00000000 000000 var pipeline = require('../'); var through = require('through2'); var stringify = require('JSONStream').stringify; var split = require('split'); var concat = require('concat-stream'); var test = require('tape'); test('combiner', function (t) { t.plan(1); var a = split(); var b = through.obj(function (row, enc, next) { this.push(JSON.parse(row)); next(); }); var c = through.obj(function (row, enc, next) { this.push(row.x); next() }); var d = through.obj(function (x, enc, next) { this.push(x * 111); next() }); var e = stringify(); var input = through(); var output = through(); output.pipe(concat(function (body) { t.deepEqual(body.toString(), '[\n333\n,\n444\n,\n555\n]\n'); })); pipeline([ input, a, b, c, d, e, output ]); input.write('{"x":3}\n'); input.write('{"x":4}\n'); input.write('{"x":5}'); input.end(); }); package/test/empty_no_data.js000644 001750 001750 0000000462 12566522063014702 0ustar00000000 000000 var pipeline = require('../'); var concat = require('concat-stream'); var test = require('tape'); test('empty with no data', function (t) { t.plan(1); var stream = pipeline([]); stream.end(); stream.pipe(concat(function (body) { t.deepEqual(body.toString(), ''); })); }); package/test/get.js000644 001750 001750 0000002233 12566522063012634 0ustar00000000 000000 var pipeline = require('../'); var through = require('through2'); var test = require('tape'); test('get', function (t) { var a = through.obj(); var b = through.obj(); var c = through.obj(); var pipe = pipeline([ a, b, c ]); t.equal(pipe.get(0), a, '0'); t.equal(pipe.get(1), b, '1'); t.equal(pipe.get(2), c, '2'); t.equal(pipe.get(3), undefined, '3'); t.equal(pipe.get(4), undefined, '4'); t.equal(pipe.get(5), undefined, '5'); t.equal(pipe.get(-1), c, '-1'); t.equal(pipe.get(-1), c, '-1'); t.equal(pipe.get(-2), b, '-2'); t.equal(pipe.get(-3), a, '-3'); t.equal(pipe.get(-4), undefined, '-4'); t.equal(pipe.get(-5), undefined, '-5'); t.end(); }); test('nested get', function (t) { var a = through.obj(); var b = through.obj(); var c = through.obj(); var d = through.obj(); var e = through.obj(); var f = through.obj(); var g = through.obj(); var pipe = pipeline([ a, [ b, c, [ d, [ e ], f ] ], g ]); t.equal(pipe.get(0), a); t.equal(pipe.get(1, -1, 1, 0), e); t.equal(pipe.get(1, 3), undefined); t.equal(pipe.get(4, 3), undefined); t.end(); }); package/test/multipush.js000644 001750 001750 0000001612 12566522063014107 0ustar00000000 000000 var pipeline = require('../'); var through = require('through2'); var stringify = require('JSONStream').stringify; var split = require('split'); var concat = require('concat-stream'); var test = require('tape'); test('multipush', function (t) { t.plan(1); var a = split(); var b = through.obj(function (row, enc, next) { this.push(JSON.parse(row)); next(); }); var c = through.obj(function (row, enc, next) { this.push(row.x); next() }); var d = through.obj(function (x, enc, next) { this.push(x * 111); next() }); var e = stringify(); var stream = pipeline(); stream.push(a, b, c); stream.push(d, e); stream.pipe(concat(function (body) { t.deepEqual(body.toString(), '[\n333\n,\n444\n,\n555\n]\n'); })); stream.write('{"x":3}\n'); stream.write('{"x":4}\n'); stream.write('{"x":5}'); stream.end(); }); package/test/multiunshift.js000644 001750 001750 0000001623 12566522063014612 0ustar00000000 000000 var pipeline = require('../'); var through = require('through2'); var stringify = require('JSONStream').stringify; var split = require('split'); var concat = require('concat-stream'); var test = require('tape'); test('multiunshift', function (t) { t.plan(1); var a = split(); var b = through.obj(function (row, enc, next) { this.push(JSON.parse(row)); next(); }); var c = through.obj(function (row, enc, next) { this.push(row.x); next() }); var d = through.obj(function (x, enc, next) { this.push(x * 111); next() }); var e = stringify(); var stream = pipeline(); stream.unshift(d, e); stream.unshift(a, b, c); stream.pipe(concat(function (body) { t.deepEqual(body.toString(), '[\n333\n,\n444\n,\n555\n]\n'); })); stream.write('{"x":3}\n'); stream.write('{"x":4}\n'); stream.write('{"x":5}'); stream.end(); }); package/test/empty.js000644 001750 001750 0000000605 12566522063013214 0ustar00000000 000000 var pipeline = require('../'); var concat = require('concat-stream'); var test = require('tape'); test('empty passthrough stream', function (t) { t.plan(1); var stream = pipeline([]); stream.pipe(concat(function (body) { t.deepEqual(body.toString(), 'abc'); })); stream.write('a'); stream.write('b'); stream.write('c'); stream.end(); }); package/test/nested_middle.js000644 001750 001750 0000002122 12566522233014651 0ustar00000000 000000 var pipeline = require('../'); var through = require('through2'); var split = require('split'); var concat = require('concat-stream'); var test = require('tape'); test('nested middle splicer', function (t) { t.plan(1); var addNewLines = through(function (buf, enc, next) { this.push(buf + '\n'); next(); }); var stream = pipeline.obj([ through.obj(function (str, enc, next) { this.push(str.replace(/^./, function (c) { return String.fromCharCode(c.charCodeAt(0) + 5); })); next(); }), [ split(), addNewLines ], through(function (buf, enc, next) { this.push('> ' + buf); next() }) ]); stream.get(1).unshift(through(function (buf, enc, next) { this.push(buf.toString('utf8').toUpperCase()); next(); })); stream.pipe(concat(function (body) { t.deepEqual(body.toString(), '> F\n> G\n> H\n'); })); stream.write('a\n'); stream.write('b\n'); stream.write('c'); stream.end(); }); package/test/pop.js000644 001750 001750 0000002324 12566522063012654 0ustar00000000 000000 var pipeline = require('../'); var through = require('through2'); var split = require('split'); var concat = require('concat-stream'); var test = require('tape'); test('pop', function (t) { var expected = {}; expected.replacer = [ '333', '444' ]; t.plan(3); var a = split(); var b = through.obj(function (row, enc, next) { this.push(JSON.parse(row)); next(); }); var c = through.obj(function (row, enc, next) { this.push(row.x); next(); }); var d = through.obj(function (x, enc, next) { this.push(String(x * 111)); next(); }); var replacer = through(function (buf, enc, next) { var ex = expected.replacer.shift(); t.equal(buf.toString(), ex); this.push(buf.toString('hex') + '\n'); if (expected.replacer.length === 0) { stream.pop(); } next(); }); var stream = pipeline([ a, b, c, d, replacer ]); stream.pipe(concat(function (body) { t.deepEqual(body.toString(), '333333\n343434\n555666'); })); stream.write('{"x":3}\n'); stream.write('{"x":4}\n'); stream.write('{"x":5}\n'); stream.write('{"x":6}'); stream.end(); }); package/test/push.js000644 001750 001750 0000003050 12566522233013031 0ustar00000000 000000 var pipeline = require('../'); var through = require('through2'); var split = require('split'); var test = require('tape'); test('push', function (t) { var expected = {}; expected.first = [ 333, 444, 555, 666, 777 ]; expected.second = [ 6.66, 7.77 ]; expected.output = [ 3.33, 4.44, 5.55, 3, 2 ]; t.plan(5 + 2 + 5 + 3); var a = split(); var b = through.obj(function (row, enc, next) { this.push(JSON.parse(row)); next(); }); var c = through.obj(function (row, enc, next) { this.push(row.x); next() }); var d = through.obj(function (x, enc, next) { this.push(x * 111); next() }); var first = through.obj(function (row, enc, next) { if (expected.first.length === 2) { t.equal(p.length, 5); p.push(second); t.equal(p.length, 6); } var ex = expected.first.shift(); t.deepEqual(row, ex); this.push(row / 100); next(); }); var second = through.obj(function (row, enc, next) { var ex = expected.second.shift(); t.deepEqual(row, ex); this.push(Math.floor(10 - row)); next(); }); var p = pipeline.obj([ a, b, c, d, first ]); t.equal(p.length, 5); p.pipe(through.obj(function (row, enc, next) { var ex = expected.output.shift(); t.deepEqual(row, ex); next(); })); p.write('{"x":3}\n'); p.write('{"x":4}\n'); p.write('{"x":5}\n'); p.write('{"x":6}\n'); p.write('{"x":7}'); p.end(); }); package/test/shift.js000644 001750 001750 0000002176 12566522233013177 0ustar00000000 000000 var pipeline = require('../'); var through = require('through2'); var test = require('tape'); test('shift', function (t) { var expected = {}; expected.a = [ 3, 4 ]; expected.b = [ 300, 400, 5, 6 ]; expected.c = [ 310, 410, 15, 16 ]; expected.output = [ 155, 205, 15/2, 8 ]; t.plan(2 + 4 + 4 + 4); var a = through.obj(function (x, enc, next) { var ex = expected.a.shift(); t.equal(x, ex, 'a'); this.push(x * 100); next(); }); var b = through.obj(function (x, enc, next) { var ex = expected.b.shift(); t.equal(x, ex, 'b'); if (expected.b.length === 2) p.shift() this.push(x + 10); next(); }); var c = through.obj(function (x, enc, next) { var ex = expected.c.shift(); t.equal(x, ex, 'c'); this.push(x / 2); next(); }); var p = pipeline.obj([ a, b, c ]); p.pipe(through.obj(function (x, enc, next) { var ex = expected.output.shift(); t.equal(x, ex); next(); })); p.write(3); p.write(4); p.write(5); p.write(6); p.end(); }); package/test/splice.js000644 001750 001750 0000003136 12566522063013337 0ustar00000000 000000 var pipeline = require('../'); var through = require('through2'); var split = require('split'); var concat = require('concat-stream'); var test = require('tape'); test('splice', function (t) { var expected = {}; expected.replacer = [ '333', '444', '5000', '6000' ]; expected.d = [ 3, 4 ]; expected.thousander = [ 5, 6 ]; t.plan(4 + 2 + 2 + 1); var a = split(); var b = through.obj(function (row, enc, next) { this.push(JSON.parse(row)); next(); }); var c = through.obj(function (row, enc, next) { this.push(row.x); next(); }); var d = through.obj(function (x, enc, next) { t.equal(x, expected.d.shift(), 'd'); this.push(String(x * 111)); next(); }); var thousander = through.obj(function (x, enc, next) { t.equal(x, expected.thousander.shift(), 'thousander'); this.push(String(x * 1000)); next(); }); var replacer = through(function (buf, enc, next) { var ex = expected.replacer.shift(); t.equal(buf.toString(), ex); if (expected.replacer.length === 2) { stream.splice(3, 1, thousander); } this.push(buf.toString('hex') + '\n'); next(); }); var stream = pipeline([ a, b, c, d, replacer ]); stream.pipe(concat(function (body) { t.deepEqual( body.toString(), '333333\n343434\n35303030\n36303030\n' ); })); stream.write('{"x":3}\n'); stream.write('{"x":4}\n'); stream.write('{"x":5}\n'); stream.write('{"x":6}'); stream.end(); }); package/test/combiner_stream.js000644 001750 001750 0000001571 12566522063015232 0ustar00000000 000000 var pipeline = require('../'); var through = require('through2'); var stringify = require('JSONStream').stringify; var split = require('split'); var concat = require('concat-stream'); var test = require('tape'); test('combiner returned stream', function (t) { t.plan(1); var a = split(); var b = through.obj(function (row, enc, next) { this.push(JSON.parse(row)); next(); }); var c = through.obj(function (row, enc, next) { this.push(row.x); next() }); var d = through.obj(function (x, enc, next) { this.push(x * 111); next() }); var e = stringify(); var stream = pipeline([ a, b, c, d, e ]); stream.pipe(concat(function (body) { t.deepEqual(body.toString(), '[\n333\n,\n444\n,\n555\n]\n'); })); stream.write('{"x":3}\n'); stream.write('{"x":4}\n'); stream.write('{"x":5}'); stream.end(); }); package/test/unshift.js000644 001750 001750 0000002200 12566522233013526 0ustar00000000 000000 var pipeline = require('../'); var through = require('through2'); var test = require('tape'); test('unshift', function (t) { var expected = {}; expected.a = [ 5, 6 ]; expected.b = [ 3, 4, 500, 600 ]; expected.c = [ 13, 14, 510, 610 ]; expected.output = [ 13/2, 7, 255, 305 ]; t.plan(2 + 4 + 4 + 4); var a = through.obj(function (x, enc, next) { var ex = expected.a.shift(); t.equal(x, ex, 'a'); this.push(x * 100); next(); }); var b = through.obj(function (x, enc, next) { var ex = expected.b.shift(); t.equal(x, ex, 'b'); if (expected.b.length === 2) p.unshift(a) this.push(x + 10); next(); }); var c = through.obj(function (x, enc, next) { var ex = expected.c.shift(); t.equal(x, ex, 'c'); this.push(x / 2); next(); }); var p = pipeline.obj([ b, c ]); p.pipe(through.obj(function (x, enc, next) { var ex = expected.output.shift(); t.equal(x, ex); next(); })); p.write(3); p.write(4); p.write(5); p.write(6); p.end(); }); package/test/nested.js000644 001750 001750 0000001562 12566522233013342 0ustar00000000 000000 var pipeline = require('../'); var through = require('through2'); var split = require('split'); var concat = require('concat-stream'); var test = require('tape'); test('nested splicer', function (t) { t.plan(1); var addNewLines = through(function (buf, enc, next) { this.push(buf + '\n'); next(); }); var stream = pipeline.obj([ [ split(), addNewLines ], through(function (buf, enc, next) { this.push('> ' + buf); next() }) ]); stream.get(0).unshift(through(function (buf, enc, next) { this.push(buf.toString('utf8').toUpperCase()); next(); })); stream.pipe(concat(function (body) { t.deepEqual(body.toString(), '> A\n> B\n> C\n'); })); stream.write('a\n'); stream.write('b\n'); stream.write('c'); stream.end(); });