UNPKG

stream-flow-control

Version:
193 lines (175 loc) 7 kB
const {Writable, Readable} = require('stream'); const {Manager} = require('../manager/manager.js'); const { DataWrapper } = require('../wrapper/data_wrapper.js'); /** * A rule represents a piece of business logic. Streams can be chained by an arbitrary event name, or by resolve/reject functions. * @extends Writable */ class Rule extends Writable { /** * Create a Rule stream * @param {object} [options] Global options * @param {string} [options.name] Name for this stream * @param {RuleProcess} options.rule Method that defines the rule logic. * * If inside the rule you emit an event with data, it will be sent to streams attached to the corresponding event on chain method. * * If this function returns exactly _true_, incoming payload will be sent to streams attached in _resolve_ function. * * If this function returns exactly _false_, incoming payload will be sent to streams attached in _reject_ function. * * If this function returns _undefined_, no message will be sent to either streams attached to _resolve_ or _reject_. * * If anything else is returned, or an exception is thrown, that will be sent to streams attached in _reject_ function. */ constructor(options) { options = {...options, objectMode: true}; super(options); this.options = options; if(this.options.rule && typeof this.options.rule == 'function') this._rule = this.options.rule; this._chains = {}; this._resolve_reader = null; this._reject_reader = null; this._corking_readers = {}; this.type = 'Rule'; this._needsEnd = false; if(this.options.name) Manager.set(this.type, this); } /** * Register streams to pipe to when _event_ is fired. * @param {string} event event name. * @param {Writable} dst stream to pipe to. */ chain(event, dst) { if(!this._chains[event+""]) { const self = this; this._chains[event+""] = new Readable({ objectMode: true, read() { if(self._corking_readers[event+""]) { self.uncork(); delete self._corking_readers[event+""]; } } }); this._chains[event+""].options = {...this._chains[event+""].options||{}, name: this.options.name, port: event}; this._chains[event+""]._readableState.sync = false; this.on(event+"", (data) => { let goOn = this._chains[event+""].push(data); if(!goOn && !self._corking_readers[event+""]) { this.cork(); self._corking_readers[event+""] = true; } }); this._chains[event+""].on('resume', ()=>this.uncork()); } const pipes = Array.isArray(dst)?dst:[dst]; pipes.forEach(dst=>{ this._chains[event+""].pipe(dst); dst.emit && dst.emit('chained', {event, src: this}); }); return Array.isArray(dst)?this:dst; } /** * Register streams to pipe to when _options.rule_ returns _true_ * @param {Writable} dst stream to pipe to */ resolve(dst) { if(!this._resolve_reader) { const self = this; this._resolve_reader = new Readable({ objectMode: true, read() { if(self._corking_readers["resolve"]) { self.uncork(); delete self._corking_readers["resolve"]; } } }); this._resolve_reader.options = {...this._resolve_reader.options||{}, name: this.options.name, port: 'resolve'}; this._resolve_reader._readableState.sync = false; } const pipes = Array.isArray(dst)?dst:[dst]; pipes.forEach(dst=>this._resolve_reader.pipe(dst)); return Array.isArray(dst)?this:dst; } /** * Register streams to pipe to when _options.rule_ returns anything else __but__ _true_, or when an exception is thrown. * @param {Writable} dst stream to pipe to */ reject(dst) { if(!this._reject_reader) { const self = this; this._reject_reader = new Readable({ objectMode: true, read() { if(self._corking_readers["reject"]) { self.uncork(); delete self._corking_readers["reject"]; } } }); this._reject_reader.options = {...this._reject_reader.options||{}, name: this.options.name, port: 'reject'}; this._reject_reader._readableState.sync = false; } const pipes = Array.isArray(dst)?dst:[dst]; pipes.forEach(dst=>this._reject_reader.pipe(dst)); return Array.isArray(dst)?this:dst; } _rule(data) { throw 'METHOD NOT IMPLEMENTED _rule()'; } _write(payload, encoding, cb) { try {throw "a";} catch(e) {} var result; if(this.goal) { payload = payload.getChild(this); } try { this._rulling = true; result = this._rule(payload); this._rulling = false; if(this._needsEnd) { for(let i in this._chains) { this._chains[i].push(null); } this._reject_reader && this._reject_reader.push(null); this._resolve_reader && this._resolve_reader.push(null); } } catch(e) { result = e; } if(result !== undefined) { if(this._resolve_reader && result === true) { let goOn = this._resolve_reader.push(payload); if(!goOn && !this._corking_readers['resolve']) { this.cork(); this._corking_readers['resolve']=true; } } if(result !== true && this._reject_reader) { payload = result===false?payload:result; if(!payload instanceof DataWrapper && this.goal) payload = new DataWrapper(payload, this); let goOn = this._reject_reader.push(payload); if(!goOn && !this._corking_readers['reject']) { this.cork(); this._corking_readers['reject']=true; } } } cb(); } end() { try {throw "a";} catch(e) {} if(this._rulling) { this._needsEnd = true; } else { for(let i in this._chains) { this._chains[i].push(null); } this._reject_reader && this._reject_reader.push(null); this._resolve_reader && this._resolve_reader.push(null); } } } module.exports.Rule = Rule;