stream-flow-control
Version:
Stream Flow Control
185 lines (172 loc) • 6.24 kB
JavaScript
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;