UNPKG

stream-flow-control

Version:
220 lines (200 loc) 7.69 kB
const {Writable, Readable} = require('stream'); const {Manager} = require('../manager/manager.js'); const uuid = require('uuid'); // /* // * Example of yaml representation: // * // * ````yaml // * stream_name: // * type: FlowAll // * options: // * none_message: 'some message' #name of the option // * methods: // * write: #Override default write (not recomended) // * code: [array of strings] // * params: [array of param names] // * when: #Conditional piping // * cond: [array of strings] #Conditional piping (always receives payload as parameter) // * dst: #Where to pipe (can be an array of destinations) // * type: {destination type} // * name: {destination name} // * pipe: #unconditional piping // * type: {destination type} // * name: {destination name} // * none: #Where to pipe when no matches where found // * type: {destination type} // * name: {destination name} // * on: #add listener // * finish: #event name // * code: [array of strings] // * params: [array of param names] // * pon: #prepend listener // * prefinish: #event name // * code: [array of strings] // * params: [array of param names] // * once: #add once listener // * finish: #event name // * code: [array of strings] // * params: [array of param names] // * ponce: #prepend once listener // * prefinish: #event name // * code: [array of strings] // * params: [array of param names] // * ```` // * // */ /** * Check all piped streams, and flow data to all which matches criteria. * * @extends Writable */ class FlowAll extends Writable { /** * Create a FlowAll 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._cnd_readers = []; this._uncnd_reader = null; this._none_reader = null; this.on('finish', () => { this._cnd_readers.forEach(rdr=>rdr.reader.push(null)); if(this._uncnd_reader) this._uncnd_reader.push(null); if(this._none_reader) this._none_reader.push(null); }); this._corking_readers={}; this.type = 'FlowAll'; 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 self = this; const id = uuid.v4(); 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._cnd_readers.push({ cond, id, reader }); 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) { if(!this._uncnd_reader) { const self = this; this._uncnd_reader = new Readable({ objectMode: true, read() { if(self._writableState.corked && self._corking_readers['uncnd']) { self.uncork(); delete self._corking_readers['uncnd']; } } }); this._uncnd_reader._readableState.sync = false; this._uncnd_reader.options = {...this._uncnd_reader.options||{}, name: this.options.name}; this._uncnd_reader.on('resume', ()=>this.uncork()); } const pipes = Array.isArray(dst)?dst:[dst]; pipes.forEach(dst=>this._uncnd_reader.pipe(dst)); 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.options = {...this._none_reader.options||{}, name: this.options.name, port: 'none'}; this._none_reader._readableState.sync = false; 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); } this._cnd_readers.forEach(cr => { if(cr.cond(payload)) { delivered = true; if(!cr.reader.push(payload) && !this._corking_readers[cr.id]) { this.cork(); this._corking_readers[cr.id] = true; } } }); if(this._uncnd_reader) { delivered = true; if(!this._uncnd_reader.push(payload) && !this._corking_readers['uncnd']) { this.cork(); this._corking_readers['uncnd'] = true; } } 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(); } end() { this._cnd_readers.forEach(cnd => cnd.reader.push(null)); if(this._uncnd_reader) this._uncnd_reader.push(null); if(this._none_reader) this._none_reader.push(null); } } module.exports.FlowAll = FlowAll;