├── .gitignore ├── package.json ├── index.min.js ├── LICENSE.txt └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flatmap-stream", 3 | "version": "0.1.0", 4 | "description": "", 5 | "main": "index.min.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/hugeglass/flatmap-stream.git" 12 | }, 13 | "author": "Antonio Macias", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/hugeglass/flatmap-stream/issues" 17 | }, 18 | "homepage": "https://github.com/hugeglass/flatmap-stream#readme" 19 | } 20 | -------------------------------------------------------------------------------- /index.min.js: -------------------------------------------------------------------------------- 1 | var Stream=require("stream").Stream;module.exports=function(e,n){var i=new Stream,a=0,o=0,u=!1,f=!1,l=!1,c=0,s=!1,d=(n=n||{}).failures?"failure":"error",m={};function w(r,e){var t=c+1;if(e===t?(void 0!==r&&i.emit.apply(i,["data",r]),c++,t++):m[e]=r,m.hasOwnProperty(t)){var n=m[t];return delete m[t],w(n,t)}a===++o&&(f&&(f=!1,i.emit("drain")),u&&v())}function p(r,e,t){l||(s=!0,r&&!n.failures||w(e,t),r&&i.emit.apply(i,[d,r]),s=!1)}function b(r,t,n){return e.call(null,r,function(r,e){n(r,e,t)})}function v(r){if(u=!0,i.writable=!1,void 0!==r)return w(r,a);a==o&&(i.readable=!1,i.emit("end"),i.destroy())}return i.writable=!0,i.readable=!0,i.write=function(r){if(u)throw new Error("flatmap stream is not writable");s=!1;try{for(var e in r){a++;var t=b(r[e],a,p);if(f=!1===t)break}return!f}catch(r){if(s)throw r;return p(r),!f}},i.end=function(r){u||v(r)},i.destroy=function(){u=l=!0,i.writable=i.readable=f=!1,process.nextTick(function(){i.emit("close")})},i.pause=function(){f=!0},i.resume=function(){f=!1},i}; -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Antonio Macias 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 all 13 | 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 THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var Stream = require('stream').Stream 2 | 3 | module.exports = function (mapper, opts) { 4 | 5 | var stream = new Stream() 6 | , inputs = 0 7 | , outputs = 0 8 | , ended = false 9 | , paused = false 10 | , destroyed = false 11 | , lastWritten = 0 12 | , inNext = false 13 | 14 | opts = opts || {}; 15 | var errorEventName = opts.failures ? 'failure' : 'error'; 16 | 17 | // Items that are not ready to be written yet (because they would come out of 18 | // order) get stuck in a queue for later. 19 | var writeQueue = {} 20 | 21 | stream.writable = true 22 | stream.readable = true 23 | 24 | function queueData (data, number) { 25 | var nextToWrite = lastWritten + 1 26 | 27 | if (number === nextToWrite) { 28 | // If it's next, and its not undefined write it 29 | if (data !== undefined) { 30 | stream.emit.apply(stream, ['data', data]) 31 | } 32 | lastWritten ++ 33 | nextToWrite ++ 34 | } else { 35 | // Otherwise queue it for later. 36 | writeQueue[number] = data 37 | } 38 | 39 | // If the next value is in the queue, write it 40 | if (writeQueue.hasOwnProperty(nextToWrite)) { 41 | var dataToWrite = writeQueue[nextToWrite] 42 | delete writeQueue[nextToWrite] 43 | return queueData(dataToWrite, nextToWrite) 44 | } 45 | 46 | outputs ++ 47 | if(inputs === outputs) { 48 | if(paused) paused = false, stream.emit('drain') //written all the incoming events 49 | if(ended) end() 50 | } 51 | } 52 | 53 | function next (err, data, number) { 54 | if(destroyed) return 55 | inNext = true 56 | 57 | if (!err || opts.failures) { 58 | queueData(data, number) 59 | } 60 | 61 | if (err) { 62 | stream.emit.apply(stream, [ errorEventName, err ]); 63 | } 64 | 65 | inNext = false; 66 | } 67 | 68 | // Wrap the mapper function by calling its callback with the order number of 69 | // the item in the stream. 70 | function wrappedMapper (input, number, callback) { 71 | return mapper.call(null, input, function(err, data){ 72 | callback(err, data, number) 73 | }) 74 | } 75 | 76 | stream.write = function (data) { 77 | if(ended) throw new Error('flatmap stream is not writable') 78 | inNext = false 79 | 80 | try { 81 | //catch sync errors and handle them like async errors 82 | for (var i in data) { 83 | inputs ++ 84 | var written = wrappedMapper(data[i], inputs, next) 85 | paused = (written === false) 86 | if (paused) 87 | break 88 | } 89 | return !paused 90 | } catch (err) { 91 | //if the callback has been called syncronously, and the error 92 | //has occured in an listener, throw it again. 93 | if(inNext) 94 | throw err 95 | next(err) 96 | return !paused 97 | } 98 | } 99 | 100 | function end (data) { 101 | //if end was called with args, write it, 102 | ended = true //write will emit 'end' if ended is true 103 | stream.writable = false 104 | if(data !== undefined) { 105 | return queueData(data, inputs) 106 | } else if (inputs == outputs) { //wait for processing 107 | stream.readable = false, stream.emit('end'), stream.destroy() 108 | } 109 | } 110 | 111 | stream.end = function (data) { 112 | if(ended) return 113 | end(data) 114 | } 115 | 116 | stream.destroy = function () { 117 | ended = destroyed = true 118 | stream.writable = stream.readable = paused = false 119 | process.nextTick(function () { 120 | stream.emit('close') 121 | }) 122 | } 123 | stream.pause = function () { 124 | paused = true 125 | } 126 | 127 | stream.resume = function () { 128 | paused = false 129 | } 130 | 131 | return stream 132 | } 133 | --------------------------------------------------------------------------------