UNPKG

stream-flow-control

Version:
171 lines (157 loc) 6 kB
const {Writable, Readable} = require('stream'); const {Manager} = require('../manager/manager.js'); const uuid = require('uuid'); /** * Check all piped streams, and flow data to the first that matches criteria. * * @extends Writable */ class FlowOne extends Writable { /** * Create a FlowOne stream * @param {object} [options] Global options * @param {string} [options.name] Name for this stream */ constructor(options) { options = {...options, objectMode: true}; super(options); this.options = options; this._shuffle = options.shuffle; this._readers = []; this._corking_readers={}; this._messages = {}; this._none_messages = []; this._none_reader = null; this.on('finish', () => { this._readers.forEach(rdr=>rdr.reader.push(null)); if(this._none_reader) this._none_reader.push(null); }); this.type = 'FlowOne'; if(this.options.name) Manager.set(this.type, this); } /** * Set stream(s) to pipe if criteria is match * @param {Condition} cond * @param {(Writable|Writable[])} dst * @returns {(Writable|FlowAll)} if dst is an array, then returns "this", else dst is returned */ when(cond, dst) { if(typeof cond != 'function') this.emit('error', '"when" first argument must be a function'); const id = uuid.v4(); const self = this; const reader = new Readable({ objectMode: true, read() { if(self._corking_readers[id]) { self.uncork(); delete self._corking_readers[id]; } } }); reader._readableState.sync = false; const pipes = Array.isArray(dst)?dst:[dst]; pipes.forEach(dst=>reader.pipe(dst)); const pos = this._readers.push({ cond, reader, id }); reader.options = {...reader.options||{}, name: this.options.name, port: 'when'+(pos-1)}; return Array.isArray(dst)?this:dst; } /** * Set stream(s) to pipe unconditionally * @param {(Writable|Writable[])} dst * @returns {(Writable|FlowAll)} if dst is an array, then returns "this", else dst is returned */ pipe(dst) { const id = uuid.v4(); const self = this; const reader = new Readable({ objectMode: true, read() { if(self._writableState.corked && self._corking_readers[id]) { self.uncork(); delete self._corking_readers[id]; } } }); reader._readableState.sync = false; const pipes = Array.isArray(dst)?dst:[dst]; pipes.forEach(dst=>reader.pipe(dst)); this._readers.push({ reader, id }); reader.options = {...reader.options||{}, name: this.options.name}; return Array.isArray(dst)?this:dst; } /** * Set stream(s) to pipe when no other stream matched criteria. If unconditional piping was set, this will never be used. * @param {(Writable|Writable[])} dst Destination stream * @returns {(Writable|FlowAll)} if dst is an array, then returns "this", else dst is returned */ none(dst) { if(!this._none_reader) { const self = this; this._none_reader = new Readable({ objectMode: true, read() { if(self._writableState.corked && self._corking_readers['none']) { self.uncork(); delete self._corking_readers['none']; } } }); this._none_reader._readableState.sync = false; this._none_reader.options = {...this._none_reader.options||{}, name: this.options.name, port: 'none'}; this._none_reader.on('resume', ()=>this.uncork()); } const pipes = Array.isArray(dst)?dst:[dst]; pipes.forEach(dst=>this._none_reader.pipe(dst)); return Array.isArray(dst)?this:dst; } /** * Internal _write method. Do not call directly. * Do not override unless you are sure of what you are doing * @param {(DataWrapper|*)} payload chunk of data to be written * @param {string} encoding encoding of data when objectMode=false. Never used. * @param {WriteCallback} cb Internal Writable callback */ _write(payload, encoding, cb) { var delivered = false; if(this.goal) { // Is goal player payload = payload.getChild(this); } if(this._shuffle) { for (let i = this._readers.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [this._readers[i], this._readers[j]] = [this._readers[j], this._readers[i]]; } } for (let i = 0; i < this._readers.length; i++) { const rdr = this._readers[i]; if(!rdr.hasOwnProperty('cond') || rdr.cond(payload)) { delivered = true; if(!rdr.reader.push(payload) && !this._corking_readers[rdr.id]) { this.cork(); this._corking_readers[rdr.id] = true; } break; } } if(!delivered && this._none_reader) { if(this.options.none_reason) { let data = typeof this.options.none_reason == 'function'?this.options.none_reason(payload):this.options.none_reason; payload = (this.goal)?payload.getChild(this).setData(data):data; } if(!this._none_reader.push(payload) && !this._corking_readers['none']) { this.cork(); this._corking_readers['none'] = true; } } cb(); } } module.exports.FlowOne = FlowOne;