UNPKG

stream-flow-control

Version:
196 lines (181 loc) 6.55 kB
const {Writable, Readable} = require('stream'); const {Manager} = require('../manager/manager.js'); const {DataWrapper} = require('../wrapper/data_wrapper.js'); const vm = require('vm'); /** * Goal wrapps many streams within, hidding complex business logic. Represents a goal to achieve. * * @extends Writable */ class Goal extends Writable { /** * Create a Goal stream * @param {object} [options] Global options * @param {string} [options.name] Name for this stream */ constructor(options, cb) { options = {...options, objectMode: true}; super(options); this.options = options; this.type = 'Goal'; if(this.options.name) Manager.set(this.type, this); const self = this; this._start = new Readable({ objectMode: true, read() { self.uncork(); } }); this._start.options = {...this._start.options||{}, name: this.options.name, port: 'start'}; this._start._readableState.sync = false; this.on('prefinish', ()=>{ this._start.push(null); }) this._error_reader = new Readable({ objectMode: true, read() {} }); this._error_reader.options = {...this._error_reader.options||{}, name: this.options.name, port: 'error'}; this._error_reader._readableState.sync = false; this._resolve_reader = null; this._reject_reader = null; this._resolve_writer =new Writable({ objectMode: true, write: (payload, encoding, cb) => { let data = payload instanceof DataWrapper?payload.data:payload; if(self._cb) self._cb(null, data); if(self._resolve_reader) { //process.nextTick(()=>self._resolve_reader.push(data)); let goOn = self._resolve_reader.push(data); if(!self._resolve_writer.corked && !goOn) self._resolve_writer.cork(); } self.emit('resolve', data); cb(); } }); this._resolve_writer.on('prefinish', ()=>{ if(this._resolve_reader) this._resolve_reader.push(null); }); this._reject_writer = new Writable({ objectMode: true, write: (payload, encoding, cb) => { // TODO - save payload for statistics let data = payload instanceof DataWrapper?payload.data:payload; if(self._cb) self._cb(data); if(self._reject_reader) { //process.nextTick(()=>self._resolve_reader.push(data)); let goOn = self._reject_reader.push(data); if(!self._reject_writer.corked && !goOn) self._reject_writer.cork(); } self.emit('reject', data); cb(); } }); this._reject_writer.on('prefinish', ()=>{ if(this._reject_reader) this._reject_reader.push(null); }); this._error_reader.pipe(this._reject_writer); this._cb = cb; this.on('pipe', src => { this._src = src; }) } /** * Pipe to a destination stream when goal was achieved * @param {Writable} dst */ resolve(dst) { if(!this._resolve_reader) { const self = this; this._resolve_reader = new Readable({ objectMode: true, read() { if(self._resolve_writer) self._resolve_writer.uncork() } }); 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 a node style callback, called when goal is resolved or rejected * @param {NodeCallback} cb */ callback(cb) { this._cb = cb; return this; } _registerChild(src) { src.on('error', (error)=>{ this._error_reader.push(error); }) } /** * Pipe to a destination stream when goal was not achieved * @param {Writable} dst */ reject(dst) { if(!this._reject_reader) { this._reject_reader = new Readable({ objectMode: true, read() { if(self._reject_writer) self._reject_writer.uncork() } }); 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; } /** * Thenable function to work with promises. * @param {ThenCallback} thenCallback called when goal is achieved * @param {CatchCallback} [catchCallback] called when goal is not achieved * @returns {Promise} */ then(rescb, rejcb) { return new Promise((resolve, reject) => { this.on('resolve', data => { if(!rescb) return resolve(data); try { resolve(rescb(data)); } catch(e) { reject(e); } }); this.on('reject', data => { if(!rejcb) return reject(data); try { resolve(rejcb(data)); } catch(e) { reject(e); } }) }); } /** * Thenable function to work with promises. * @param {CatchCallback} catchCallback called when goal is not achieved * @returns {Promise} */ catch(rejcb) { return this.then(null, rejcb); } build(chain, options) { Manager._build(chain, {...options, goal: this}); } _write(payload, encoding, cb) { var data = new DataWrapper(payload, this); //process.nextTick(()=>this._start.push(data)); let goOn = this._start.push(data); if(!this._writableState.corked && !goOn) this.cork(); cb(); } } module.exports.Goal = Goal; // module.exports.GoalGroup = GoalGroup;