UNPKG

stream-flow-control

Version:
185 lines (172 loc) 6.24 kB
const {Writable, Readable} = require('stream'); const {Manager} = require('../manager/manager.js'); const vm = require('vm'); // Leaving this class here just in case, but I think this is // replaceable with a more generic class as Goal class RuleGroup extends Writable { constructor(options) { options = {...options, objectMode: true}; super(options); this.options = options; this.type = 'RuleGroup'; if(this.options.name) Manager.set(this.type, this); if(this.options.chain) this.setChain(this.options.chain); this._start = new Readable({ objectMode: true, read() {} }); this._start._readableState.sync = false; this._resolve_reader = null; this._reject_reader = null; this._resolve_writer = null; this._reject_writer = null; } resolve(dst) { if(!this._resolve_reader) { this._resolve_reader = new Readable({ objectMode: true, read() {} }); 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; } reject(dst) { if(!this._reject_reader) { this._reject_reader = new Readable({ objectMode: true, read() {} }); 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; } /** * * @param {Object} chain * * { * __group__: [ * 'rule_1', * 'rule_2' * ], * rule_1: { * chain: { * event_1: ['rule_3', 'rule_4'], * event_2: ['rule_5'] * }, * reject: ['rule_5'] * }, * ... * rule_5: { * resolve: '__resolve__', * reject: '__reject__' * }, * rule_6: { * chain: { * event_3: '__reject__' * } * } * } */ build(chain) { if(!chain.__group__) throw 'Rules group chain must have "__group__" key to get a starting point'; let _getDst = (dst) => { if(dst == '__resolve__') { if(!this._resolve_writer) { let self = this; this._resolve_writer = new Writable({ objectMode: true, write: (payload) => { if(self._resolve_reader) { if(self.goal) { payload = payload.getChild(self); } process.nextTick(()=>self._resolve_reader.push(payload)); } } }); } return this._resolve_writer; } else if (dst == '__reject__') { if(!this._reject_writer) { let self = this; this._reject_writer = new Writable({ objectMode: true, write: (payload) => { if(self._reject_reader) { if(self.goal) { payload = payload.getChild(self); } process.nextTick(()=>self._reject_reader.push(payload)); } } }); } return this._reject_writer; } else { return Manager.get('rule', key); } }; Object.keys(chain).forEach(key => { if(key == '__group__') return; let link = chain[key]; let rule = Manager.get('rule', key); if(this.goal) rule.goal = this.goal; if(link.chain) { Object.keys(link.chain).forEach(event => { let chained = link.chain[event]; if(!Array.isArray(chained)) chained = [chained]; chained.forEach(chain => { let dst = _getDst(chain); if(dst) rule.chain(event, dst); }); }); } if(link.resolve) { let resolved = link.resolve; if(!Array.isArray(resolved)) resolved = [resolved]; resolved.forEach(chain => { let dst = _getDst(chain); if(dst) rule.resolve(event, dst); }); } if(link.reject) { let rejected = link.reject; if(!Array.isArray(rejected)) rejected = [rejected]; rejected.forEach(chain => { let dst = _getDst(chain); if(dst) rule.resolve(event, dst); }); } if(link.rule) { let code = link.rule; switch(typeof code) { case 'array': code = code.join('\n'); case 'string': code = vm.compileFunction(code, ['data']); break; } if(typeof code == 'function') { rule._rule = code.bind(rule); } } }); let startRules = chain.__group__; if(!Array.isArray(startRules)) startRules = [startRules]; startRules = startRules.map(chain => _getDst(chain)).filer(chain => chain); if(!startRules.length) throw "Manager could not resolve any of the following rules: "+chain.__group__.join(', '); this._start.unpipe(); startRules.forEach(dst => this._start.pipe(dst)); } _write(payload, encoding, cb) { process.nextTick(()=>this._start.push(payload)); cb(); } } module.exports.RuleGroup = RuleGroup;