pax_global_header00006660000000000000000000000064125775575110014531gustar00rootroot0000000000000052 comment=867c900661f90b4f50d1b2494f714f80933fc696 first-chunk-stream-2.0.0/000077500000000000000000000000001257755751100152565ustar00rootroot00000000000000first-chunk-stream-2.0.0/.editorconfig000066400000000000000000000003471257755751100177370ustar00rootroot00000000000000root = true [*] indent_style = tab end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [{package.json,*.yml}] indent_style = space indent_size = 2 [*.md] trim_trailing_whitespace = false first-chunk-stream-2.0.0/.gitattributes000066400000000000000000000000141257755751100201440ustar00rootroot00000000000000* text=auto first-chunk-stream-2.0.0/.gitignore000066400000000000000000000000151257755751100172420ustar00rootroot00000000000000node_modules first-chunk-stream-2.0.0/.travis.yml000066400000000000000000000001121257755751100173610ustar00rootroot00000000000000sudo: false language: node_js node_js: - 'stable' - '0.12' - '0.10' first-chunk-stream-2.0.0/index.js000066400000000000000000000103731257755751100167270ustar00rootroot00000000000000'use strict'; var util = require('util'); var Duplex = require('readable-stream').Duplex; function FirstChunkStream(options, cb) { var _this = this; var _state = { sent: false, chunks: [], size: 0 }; if (!(this instanceof FirstChunkStream)) { return new FirstChunkStream(options, cb); } options = options || {}; if (!(cb instanceof Function)) { throw new Error('FirstChunkStream constructor requires a callback as its second argument.'); } if (typeof options.chunkLength !== 'number') { throw new Error('FirstChunkStream constructor requires `options.chunkLength` to be a number.'); } if (options.objectMode) { throw new Error('FirstChunkStream doesn\'t support `objectMode` yet.'); } Duplex.call(this, options); // Initialize the internal state _state.manager = createReadStreamBackpressureManager(this); // Errors management // We need to execute the callback or emit en error dependending on the fact // the firstChunk is sent or not _state.errorHandler = function firstChunkStreamErrorHandler(err) { processCallback(err, Buffer.concat(_state.chunks, _state.size), _state.encoding, function () {}); }; this.on('error', _state.errorHandler); // Callback management function processCallback(err, buf, encoding, done) { // When doing sync writes + emiting an errror it can happen that // Remove the error listener on the next tick if an error where fired // to avoid unwanted error throwing if (err) { setImmediate(function () { _this.removeListener('error', _state.errorHandler); }); } else { _this.removeListener('error', _state.errorHandler); } _state.sent = true; cb(err, buf, encoding, function (err, buf, encoding) { if (err) { setImmediate(function () { _this.emit('error', err); }); return; } if (!buf) { done(); return; } _state.manager.programPush(buf, encoding, done); }); } // Writes management this._write = function firstChunkStreamWrite(chunk, encoding, done) { _state.encoding = encoding; if (_state.sent) { _state.manager.programPush(chunk, _state.encoding, done); } else if (chunk.length < options.chunkLength - _state.size) { _state.chunks.push(chunk); _state.size += chunk.length; done(); } else { _state.chunks.push(chunk.slice(0, options.chunkLength - _state.size)); chunk = chunk.slice(options.chunkLength - _state.size); _state.size += _state.chunks[_state.chunks.length - 1].length; processCallback(null, Buffer.concat(_state.chunks, _state.size), _state.encoding, function () { if (!chunk.length) { done(); return; } _state.manager.programPush(chunk, _state.encoding, done); }); } }; this.on('finish', function firstChunkStreamFinish() { if (!_state.sent) { return processCallback(null, Buffer.concat(_state.chunks, _state.size), _state.encoding, function () { _state.manager.programPush(null, _state.encoding); }); } _state.manager.programPush(null, _state.encoding); }); } util.inherits(FirstChunkStream, Duplex); // Utils to manage readable stream backpressure function createReadStreamBackpressureManager(readableStream) { var manager = { waitPush: true, programmedPushs: [], programPush: function programPush(chunk, encoding, done) { done = done || function () {}; // Store the current write manager.programmedPushs.push([chunk, encoding, done]); // Need to be async to avoid nested push attempts // Programm a push attempt setImmediate(manager.attemptPush); // Let's say we're ready for a read readableStream.emit('readable'); readableStream.emit('drain'); }, attemptPush: function () { var nextPush; if (manager.waitPush) { if (manager.programmedPushs.length) { nextPush = manager.programmedPushs.shift(); manager.waitPush = readableStream.push(nextPush[0], nextPush[1]); (nextPush[2])(); } } else { setImmediate(function () { // Need to be async to avoid nested push attempts readableStream.emit('readable'); }); } } }; // Patch the readable stream to manage reads readableStream._read = function streamFilterRestoreRead() { manager.waitPush = true; // Need to be async to avoid nested push attempts setImmediate(manager.attemptPush); }; return manager; } module.exports = FirstChunkStream; first-chunk-stream-2.0.0/license000066400000000000000000000021371257755751100166260ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) Sindre Sorhus (sindresorhus.com) 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. first-chunk-stream-2.0.0/package.json000066400000000000000000000015301257755751100175430ustar00rootroot00000000000000{ "name": "first-chunk-stream", "version": "2.0.0", "description": "Transform the first chunk in a stream", "license": "MIT", "repository": "sindresorhus/first-chunk-stream", "author": { "name": "Sindre Sorhus", "email": "sindresorhus@gmail.com", "url": "sindresorhus.com" }, "engines": { "node": ">=0.10.0" }, "scripts": { "test": "xo && mocha", "cover": "istanbul cover --report html _mocha -- test.js -R spec -t 5000" }, "files": [ "index.js" ], "keywords": [ "buffer", "stream", "streams", "transform", "first", "chunk", "size", "min", "minimum" ], "dependencies": { "readable-stream": "^2.0.2" }, "devDependencies": { "istanbul": "^0.3.19", "mocha": "*", "mocha-lcov-reporter": "0.0.2", "streamtest": "^1.2.1", "xo": "*" } } first-chunk-stream-2.0.0/readme.md000066400000000000000000000032311257755751100170340ustar00rootroot00000000000000# first-chunk-stream [![Build Status](https://travis-ci.org/sindresorhus/first-chunk-stream.svg?branch=master)](https://travis-ci.org/sindresorhus/first-chunk-stream) > Buffer and transform the n first bytes of a stream ## Install ``` $ npm install --save first-chunk-stream ``` ## Usage ```js const fs = require('fs'); const concatStream = require('concat-stream'); const firstChunkStream = require('first-chunk-stream'); // unicorn.txt => unicorn rainbow fs.createReadStream('unicorn.txt') .pipe(firstChunkStream({chunkLength: 7}, function (chunk, enc, cb) { this.push(chunk.toUpperCase()); cb(); })) .pipe(concatStream(function (data) { if (data.length < 7) { throw new Error('Couldn\'t get the minimum required first chunk length'); } console.log(data); //=> 'UNICORN rainbow' })); ``` ## API ### firstChunkStream([options], transform) Returns a `FirstChunkStream` instance. #### options The options object is passed to the [`Duplex` stream](https://nodejs.org/api/stream.html#stream_class_stream_duplex) constructor allowing you to customize your stream behavior. In addition you can specify the following option: ##### chunkLength Type: `number` How many bytes you want to buffer. #### transform(chunk, encoding, callback) Type: `function` The function that gets the required `options.chunkLength` bytes. Note that the buffer can have a smaller length than the required one. In that case, it will be due to the fact that the complete stream contents has a length less than the `òptions.chunkLength` value. You should check for this yourself if you strictly depend on the length. ## License MIT © [Sindre Sorhus](http://sindresorhus.com) first-chunk-stream-2.0.0/test.js000066400000000000000000000230241257755751100165740ustar00rootroot00000000000000/* eslint-env mocha */ 'use strict'; var assert = require('assert'); var streamtest = require('streamtest'); var firstChunkStream = require('./'); describe('firstChunk()', function () { var content = 'unicorn rainbows \ncake'; describe('should fail', function () { it('when the callback is not providen', function () { assert.throws(function () { firstChunkStream({ chunkLength: 7 }); }); }); it('when trying to use it in objectMode', function () { assert.throws(function () { firstChunkStream({ chunkLength: 7, objectMode: true }, function () {}); }); }); it('when firstChunk size is bad or missing', function () { assert.throws(function () { firstChunkStream({ chunkLength: 'feferf' }, function () {}); }); assert.throws(function () { firstChunkStream({}.undef, function () {}); }); }); }); streamtest.versions.forEach(function (version) { describe('for ' + version + ' streams', function () { describe('emitting errors', function () { it('should report error in the callback before first chunk is sent and allow recovery', function (done) { var callbackCalled = false; var stream = firstChunkStream({chunkLength: 7}, function (err, chunk, enc, cb) { assert.equal(err.message, 'Hey!'); assert.equal(chunk.toString('utf-8'), content.substr(0, 2)); callbackCalled = true; cb(null, new Buffer(content.substr(0, 7))); }); stream.pipe(streamtest[version].toText(function (err, text) { if (err) { done(err); return; } assert.deepEqual(text, content); assert(callbackCalled, 'Callback has been called.'); done(); })); stream.write(new Buffer(content[0])); stream.write(new Buffer(content[1])); stream.emit('error', new Error('Hey!')); stream.write(new Buffer(content.substr(7))); stream.end(); }); it('should report error in the callback before first chunk is sent and reemit passed errors', function (done) { var callbackCalled = false; var errEmitted = false; var stream = firstChunkStream({chunkLength: 7}, function (err, chunk, enc, cb) { assert.equal(err.message, 'Hey!'); callbackCalled = true; stream.on('error', function (err) { assert.equal(err.message, 'Ho!'); errEmitted = true; }); cb(new Error('Ho!')); }); stream.pipe(streamtest[version].toText(function (err, text) { if (err) { done(err); return; } assert.deepEqual(text, content.substr(7)); assert(callbackCalled, 'Callback has been called.'); assert(errEmitted, 'Error has been emitted.'); done(); })); stream.write(new Buffer(content[0])); stream.write(new Buffer(content[1])); stream.emit('error', new Error('Hey!')); stream.write(new Buffer(content.substr(7))); stream.end(); }); it('should just emit errors when first chunk is sent', function (done) { var callbackCalled = false; var errEmitted = false; var stream = firstChunkStream({chunkLength: 7}, function (err, chunk, enc, cb) { callbackCalled = true; cb(null, chunk); }); stream.on('error', function (err) { assert.equal(err.message, 'Hey!'); errEmitted = true; }); stream.pipe(streamtest[version].toText(function (err, text) { if (err) { done(err); return; } assert.deepEqual(text, content); assert(callbackCalled, 'Callback has been called.'); assert(errEmitted, 'Error has been emitted.'); done(); })); stream.write(new Buffer(content.substr(0, 7))); stream.emit('error', new Error('Hey!')); stream.write(new Buffer(content.substr(7))); stream.end(); }); }); describe('and require a 0 length first chunk', function () { it('should work', function (done) { var callbackCalled = false; streamtest[version].fromChunks([content]) .pipe(firstChunkStream({chunkLength: 0}, function (err, chunk, enc, cb) { if (err) { done(err); return; } assert.equal(chunk.toString('utf-8'), ''); callbackCalled = true; cb(null, new Buffer('popop')); })) .pipe(streamtest[version].toText(function (err, text) { if (err) { done(err); return; } assert.deepEqual(text, 'popop' + content); assert(callbackCalled, 'Callback has been called.'); done(); })); }); }); describe('and leaving content as is', function () { it('should work for a single oversized chunk', function (done) { var callbackCalled = false; streamtest[version].fromChunks([content]) .pipe(firstChunkStream({chunkLength: 7}, function (err, chunk, enc, cb) { if (err) { done(err); return; } assert.equal(chunk.toString('utf-8'), content.substr(0, 7)); callbackCalled = true; cb(null, chunk); })) .pipe(streamtest[version].toText(function (err, text) { if (err) { done(err); return; } assert.deepEqual(text, content); assert(callbackCalled, 'Callback has been called.'); done(); })); }); it('should work for required size chunk', function (done) { var callbackCalled = false; streamtest[version].fromChunks([content.substr(0, 7), content.substr(7)]) .pipe(firstChunkStream({chunkLength: 7}, function (err, chunk, enc, cb) { if (err) { done(err); return; } assert.equal(chunk.toString('utf-8'), content.substr(0, 7)); callbackCalled = true; cb(null, chunk); })) .pipe(streamtest[version].toText(function (err, text) { if (err) { done(err); return; } assert.deepEqual(text, content); assert(callbackCalled, 'Callback has been called.'); done(); })); }); it('should work for several small chunks', function (done) { var callbackCalled = false; streamtest[version].fromChunks(content.split('')) .pipe(firstChunkStream({chunkLength: 7}, function (err, chunk, enc, cb) { if (err) { done(err); return; } assert.equal(chunk.toString('utf-8'), content.substr(0, 7)); callbackCalled = true; cb(null, chunk); })) .pipe(streamtest[version].toText(function (err, text) { if (err) { done(err); return; } assert.deepEqual(text, content); assert(callbackCalled, 'Callback has been called.'); done(); })); }); }); describe('and insufficient content', function () { it('should work', function (done) { var callbackCalled = false; streamtest[version].fromChunks(['a', 'b', 'c']) .pipe(firstChunkStream({chunkLength: 7}, function (err, chunk, enc, cb) { if (err) { done(err); return; } assert.equal(chunk.toString('utf-8'), 'abc'); callbackCalled = true; cb(null, new Buffer('b')); })) .pipe(streamtest[version].toText(function (err, text) { if (err) { done(err); return; } assert.deepEqual(text, 'b'); assert(callbackCalled, 'Callback has been called.'); done(); })); }); }); describe('and changing content', function () { it('should work when removing the first chunk', function (done) { var callbackCalled = false; streamtest[version].fromChunks([content]) .pipe(firstChunkStream({chunkLength: 7}, function (err, chunk, enc, cb) { if (err) { done(err); return; } assert.equal(chunk.toString('utf-8'), content.substr(0, 7)); callbackCalled = true; cb(null, new Buffer(0)); })) .pipe(streamtest[version].toText(function (err, text) { if (err) { done(err); return; } assert.deepEqual(text, content.substr(7)); assert(callbackCalled, 'Callback has been called.'); done(); })); }); it('should work when replacing per a larger chunk', function (done) { var callbackCalled = false; streamtest[version].fromChunks([content.substr(0, 7), content.substr(7)]) .pipe(firstChunkStream({chunkLength: 7}, function (err, chunk, enc, cb) { if (err) { done(err); return; } assert.equal(chunk.toString('utf-8'), content.substr(0, 7)); callbackCalled = true; cb(null, Buffer.concat([chunk, new Buffer('plop')])); })) .pipe(streamtest[version].toText(function (err, text) { if (err) { done(err); return; } assert.deepEqual(text, content.substr(0, 7) + 'plop' + content.substr(7)); assert(callbackCalled, 'Callback has been called.'); done(); })); }); it('should work when replacing per a smaller chunk', function (done) { var callbackCalled = false; streamtest[version].fromChunks(content.split('')) .pipe(firstChunkStream({chunkLength: 7}, function (err, chunk, enc, cb) { if (err) { done(err); return; } assert.equal(chunk.toString('utf-8'), content.substr(0, 7)); callbackCalled = true; cb(null, new Buffer('plop')); })) .pipe(streamtest[version].toText(function (err, text) { if (err) { done(err); return; } assert.deepEqual(text, 'plop' + content.substr(7)); assert(callbackCalled, 'Callback has been called.'); done(); })); }); }); }); }); });