UNPKG

rpc-stream

Version:

very simple rpc. use with any thing that has .pipe()

128 lines (114 loc) 3.29 kB
var through = require('through') var serialize = require('stream-serializer')() function get(obj, path) { if(Array.isArray(path)) { for(var i in path) obj = obj[path[i]] return obj } return obj[path] } module.exports = function (obj, opts) { if('boolean' == typeof opts) opts = { raw: opts } opts = opts || {} var cbs = {}, count = 1, local = obj || {} var flattenError = opts.flattenError || function (err) { if(!(err instanceof Error)) return err var err2 = { message: err.message } for(var k in err) err2[k] = err[k] return err2 } function expandError(err) { if (!err || !err.message) return err var err2 = new Error(err.message) for(var k in err) err2[k] = err[k] return err2 } if(obj) { local = {} function callable (k) { return function (args, cb) { return obj[k].apply(obj, cb ? args.concat(cb) : args) } } for(var k in obj) local[k] = callable(k) } var s = through(function (data) { //write - on incoming call data = data.slice() var i = data.pop(), args = data.pop(), name = data.pop() //if(~i) then there was no callback. if (args[0]) args[0] = expandError(args[0]) if(name != null) { var called = 0 var cb = function () { if (called++) return var args = [].slice.call(arguments) args[0] = flattenError(args[0]) if(~i) s.emit('data', [args, i]) //responses don't have a name. } try { local[name].call(obj, args, cb) } catch (err) { if(~i) s.emit('data', [[flattenError(err)], i]) } } else if(!cbs[i]) { //there is no callback with that id. //either one end mixed up the id or //it was called twice. //log this error, but don't throw. //this process shouldn't crash because another did wrong s.emit('invalid callback id') return console.error('ERROR: unknown callback id: '+i, data) } else { //call the callback. var cb = cbs[i] delete cbs[i] //delete cb before calling it, incase cb throws. cb.apply(null, args) } }) var rpc = s.rpc = function (name, args, cb) { if(cb) cbs[++count] = cb if('string' !== typeof name) throw new Error('name *must* be string') s.emit('data', [name, args, cb ? count : -1]) if(cb && count == 9007199254740992) count = 0 //reset if max //that is 900 million million. //if you reach that, dm me, //i'll buy you a beer. @dominictarr } function keys (obj) { var keys = [] for(var k in obj) keys.push(k) return keys } s.createRemoteCall = function (name) { return function () { var args = [].slice.call(arguments) var cb = ('function' == typeof args[args.length - 1]) ? args.pop() : null rpc(name, args, cb) } } s.createLocalCall = function (name, fn) { local[name] = fn } s.wrap = function (remote, _path) { _path = _path || [] var w = {} ;(Array.isArray(remote) ? remote : 'string' == typeof remote ? [remote] : remote = keys(remote) ).forEach(function (k) { w[k] = s.createRemoteCall(k) }) return w } if(opts.raw) return s return serialize(s) }