UNPKG

noflo

Version:

Flow-Based Programming environment for JavaScript

974 lines (923 loc) 29.7 kB
(function() { var EventEmitter, IP, Network, componentLoader, graph, internalSocket, platform, utils, extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, hasProp = {}.hasOwnProperty; internalSocket = require("./InternalSocket"); graph = require("fbp-graph"); EventEmitter = require('events').EventEmitter; platform = require('./Platform'); componentLoader = require('./ComponentLoader'); utils = require('./Utils'); IP = require('./IP'); Network = (function(superClass) { extend(Network, superClass); Network.prototype.processes = {}; Network.prototype.connections = []; Network.prototype.initials = []; Network.prototype.defaults = []; Network.prototype.graph = null; Network.prototype.startupDate = null; function Network(graph, options) { this.options = options != null ? options : {}; this.processes = {}; this.connections = []; this.initials = []; this.nextInitials = []; this.defaults = []; this.graph = graph; this.started = false; this.debug = true; this.eventBuffer = []; if (!platform.isBrowser()) { this.baseDir = graph.baseDir || process.cwd(); } else { this.baseDir = graph.baseDir || '/'; } this.startupDate = null; if (graph.componentLoader) { this.loader = graph.componentLoader; } else { this.loader = new componentLoader.ComponentLoader(this.baseDir, this.options); } } Network.prototype.uptime = function() { if (!this.startupDate) { return 0; } return new Date() - this.startupDate; }; Network.prototype.getActiveProcesses = function() { var active, name, process, ref; active = []; if (!this.started) { return active; } ref = this.processes; for (name in ref) { process = ref[name]; if (process.component.load > 0) { active.push(name); } if (process.component.__openConnections > 0) { active.push(name); } } return active; }; Network.prototype.bufferedEmit = function(event, payload) { var ev, i, len, ref; if (event === 'error' || event === 'process-error' || event === 'end') { this.emit(event, payload); return; } if (!this.isStarted() && event !== 'end') { this.eventBuffer.push({ type: event, payload: payload }); return; } this.emit(event, payload); if (event === 'start') { ref = this.eventBuffer; for (i = 0, len = ref.length; i < len; i++) { ev = ref[i]; this.emit(ev.type, ev.payload); } return this.eventBuffer = []; } }; Network.prototype.load = function(component, metadata, callback) { return this.loader.load(component, callback, metadata); }; Network.prototype.addNode = function(node, callback) { var process; if (this.processes[node.id]) { callback(null, this.processes[node.id]); return; } process = { id: node.id }; if (!node.component) { this.processes[process.id] = process; callback(null, process); return; } return this.load(node.component, node.metadata, (function(_this) { return function(err, instance) { var inPorts, name, outPorts, port; if (err) { return callback(err); } instance.nodeId = node.id; process.component = instance; process.componentName = node.component; inPorts = process.component.inPorts.ports || process.component.inPorts; outPorts = process.component.outPorts.ports || process.component.outPorts; for (name in inPorts) { port = inPorts[name]; port.node = node.id; port.nodeInstance = instance; port.name = name; } for (name in outPorts) { port = outPorts[name]; port.node = node.id; port.nodeInstance = instance; port.name = name; } if (instance.isSubgraph()) { _this.subscribeSubgraph(process); } _this.subscribeNode(process); _this.processes[process.id] = process; return callback(null, process); }; })(this)); }; Network.prototype.removeNode = function(node, callback) { if (!this.processes[node.id]) { return callback(new Error("Node " + node.id + " not found")); } return this.processes[node.id].component.shutdown((function(_this) { return function(err) { if (err) { return callback(err); } delete _this.processes[node.id]; return callback(null); }; })(this)); }; Network.prototype.renameNode = function(oldId, newId, callback) { var inPorts, name, outPorts, port, process; process = this.getNode(oldId); if (!process) { return callback(new Error("Process " + oldId + " not found")); } process.id = newId; inPorts = process.component.inPorts.ports || process.component.inPorts; outPorts = process.component.outPorts.ports || process.component.outPorts; for (name in inPorts) { port = inPorts[name]; if (!port) { continue; } port.node = newId; } for (name in outPorts) { port = outPorts[name]; if (!port) { continue; } port.node = newId; } this.processes[newId] = process; delete this.processes[oldId]; return callback(null); }; Network.prototype.getNode = function(id) { return this.processes[id]; }; Network.prototype.connect = function(done) { var callStack, edges, initializers, nodes, serialize, setDefaults, subscribeGraph; if (done == null) { done = function() {}; } callStack = 0; serialize = (function(_this) { return function(next, add) { return function(type) { return _this["add" + type](add, function(err) { if (err) { return done(err); } callStack++; if (callStack % 100 === 0) { setTimeout(function() { return next(type); }, 0); return; } return next(type); }); }; }; })(this); subscribeGraph = (function(_this) { return function() { _this.subscribeGraph(); return done(); }; })(this); setDefaults = utils.reduceRight(this.graph.nodes, serialize, subscribeGraph); initializers = utils.reduceRight(this.graph.initializers, serialize, function() { return setDefaults("Defaults"); }); edges = utils.reduceRight(this.graph.edges, serialize, function() { return initializers("Initial"); }); nodes = utils.reduceRight(this.graph.nodes, serialize, function() { return edges("Edge"); }); return nodes("Node"); }; Network.prototype.connectPort = function(socket, process, port, index, inbound) { if (inbound) { socket.to = { process: process, port: port, index: index }; if (!(process.component.inPorts && process.component.inPorts[port])) { throw new Error("No inport '" + port + "' defined in process " + process.id + " (" + (socket.getId()) + ")"); return; } if (process.component.inPorts[port].isAddressable()) { return process.component.inPorts[port].attach(socket, index); } return process.component.inPorts[port].attach(socket); } socket.from = { process: process, port: port, index: index }; if (!(process.component.outPorts && process.component.outPorts[port])) { throw new Error("No outport '" + port + "' defined in process " + process.id + " (" + (socket.getId()) + ")"); return; } if (process.component.outPorts[port].isAddressable()) { return process.component.outPorts[port].attach(socket, index); } return process.component.outPorts[port].attach(socket); }; Network.prototype.subscribeGraph = function() { var graphOps, processOps, processing, registerOp; graphOps = []; processing = false; registerOp = function(op, details) { return graphOps.push({ op: op, details: details }); }; processOps = (function(_this) { return function(err) { var cb, op; if (err) { if (_this.listeners('process-error').length === 0) { throw err; } _this.bufferedEmit('process-error', err); } if (!graphOps.length) { processing = false; return; } processing = true; op = graphOps.shift(); cb = processOps; switch (op.op) { case 'renameNode': return _this.renameNode(op.details.from, op.details.to, cb); default: return _this[op.op](op.details, cb); } }; })(this); this.graph.on('addNode', function(node) { registerOp('addNode', node); if (!processing) { return processOps(); } }); this.graph.on('removeNode', function(node) { registerOp('removeNode', node); if (!processing) { return processOps(); } }); this.graph.on('renameNode', function(oldId, newId) { registerOp('renameNode', { from: oldId, to: newId }); if (!processing) { return processOps(); } }); this.graph.on('addEdge', function(edge) { registerOp('addEdge', edge); if (!processing) { return processOps(); } }); this.graph.on('removeEdge', function(edge) { registerOp('removeEdge', edge); if (!processing) { return processOps(); } }); this.graph.on('addInitial', function(iip) { registerOp('addInitial', iip); if (!processing) { return processOps(); } }); return this.graph.on('removeInitial', function(iip) { registerOp('removeInitial', iip); if (!processing) { return processOps(); } }); }; Network.prototype.subscribeSubgraph = function(node) { var emitSub; if (!node.component.isReady()) { node.component.once('ready', (function(_this) { return function() { return _this.subscribeSubgraph(node); }; })(this)); return; } if (!node.component.network) { return; } node.component.network.setDebug(this.debug); emitSub = (function(_this) { return function(type, data) { if (type === 'process-error' && _this.listeners('process-error').length === 0) { if (data.id && data.metadata && data.error) { throw data.error; } throw data; } if (!data) { data = {}; } if (data.subgraph) { if (!data.subgraph.unshift) { data.subgraph = [data.subgraph]; } data.subgraph = data.subgraph.unshift(node.id); } else { data.subgraph = [node.id]; } return _this.bufferedEmit(type, data); }; })(this); node.component.network.on('connect', function(data) { return emitSub('connect', data); }); node.component.network.on('begingroup', function(data) { return emitSub('begingroup', data); }); node.component.network.on('data', function(data) { return emitSub('data', data); }); node.component.network.on('endgroup', function(data) { return emitSub('endgroup', data); }); node.component.network.on('disconnect', function(data) { return emitSub('disconnect', data); }); node.component.network.on('ip', function(data) { return emitSub('ip', data); }); return node.component.network.on('process-error', function(data) { return emitSub('process-error', data); }); }; Network.prototype.subscribeSocket = function(socket, source) { socket.on('ip', (function(_this) { return function(ip) { return _this.bufferedEmit('ip', { id: socket.getId(), type: ip.type, socket: socket, data: ip.data, metadata: socket.metadata }); }; })(this)); socket.on('connect', (function(_this) { return function() { if (source && source.component.isLegacy()) { if (!source.component.__openConnections) { source.component.__openConnections = 0; } source.component.__openConnections++; } return _this.bufferedEmit('connect', { id: socket.getId(), socket: socket, metadata: socket.metadata }); }; })(this)); socket.on('begingroup', (function(_this) { return function(group) { return _this.bufferedEmit('begingroup', { id: socket.getId(), socket: socket, group: group, metadata: socket.metadata }); }; })(this)); socket.on('data', (function(_this) { return function(data) { return _this.bufferedEmit('data', { id: socket.getId(), socket: socket, data: data, metadata: socket.metadata }); }; })(this)); socket.on('endgroup', (function(_this) { return function(group) { return _this.bufferedEmit('endgroup', { id: socket.getId(), socket: socket, group: group, metadata: socket.metadata }); }; })(this)); socket.on('disconnect', (function(_this) { return function() { _this.bufferedEmit('disconnect', { id: socket.getId(), socket: socket, metadata: socket.metadata }); if (source && source.component.isLegacy()) { source.component.__openConnections--; if (source.component.__openConnections < 0) { source.component.__openConnections = 0; } if (source.component.__openConnections === 0) { return _this.checkIfFinished(); } } }; })(this)); return socket.on('error', (function(_this) { return function(event) { if (_this.listeners('process-error').length === 0) { if (event.id && event.metadata && event.error) { throw event.error; } throw event; } return _this.bufferedEmit('process-error', event); }; })(this)); }; Network.prototype.subscribeNode = function(node) { node.component.on('deactivate', (function(_this) { return function(load) { if (load > 0) { return; } return _this.checkIfFinished(); }; })(this)); if (!node.component.getIcon) { return; } return node.component.on('icon', (function(_this) { return function() { return _this.bufferedEmit('icon', { id: node.id, icon: node.component.getIcon() }); }; })(this)); }; Network.prototype.addEdge = function(edge, callback) { var from, socket, to; socket = internalSocket.createSocket(edge.metadata); socket.setDebug(this.debug); from = this.getNode(edge.from.node); if (!from) { return callback(new Error("No process defined for outbound node " + edge.from.node)); } if (!from.component) { return callback(new Error("No component defined for outbound node " + edge.from.node)); } if (!from.component.isReady()) { from.component.once("ready", (function(_this) { return function() { return _this.addEdge(edge, callback); }; })(this)); return; } to = this.getNode(edge.to.node); if (!to) { return callback(new Error("No process defined for inbound node " + edge.to.node)); } if (!to.component) { return callback(new Error("No component defined for inbound node " + edge.to.node)); } if (!to.component.isReady()) { to.component.once("ready", (function(_this) { return function() { return _this.addEdge(edge, callback); }; })(this)); return; } this.subscribeSocket(socket, from); this.connectPort(socket, to, edge.to.port, edge.to.index, true); this.connectPort(socket, from, edge.from.port, edge.from.index, false); this.connections.push(socket); return callback(); }; Network.prototype.removeEdge = function(edge, callback) { var connection, i, len, ref, results; ref = this.connections; results = []; for (i = 0, len = ref.length; i < len; i++) { connection = ref[i]; if (!connection) { continue; } if (!(edge.to.node === connection.to.process.id && edge.to.port === connection.to.port)) { continue; } connection.to.process.component.inPorts[connection.to.port].detach(connection); if (edge.from.node) { if (connection.from && edge.from.node === connection.from.process.id && edge.from.port === connection.from.port) { connection.from.process.component.outPorts[connection.from.port].detach(connection); } } this.connections.splice(this.connections.indexOf(connection), 1); results.push(callback()); } return results; }; Network.prototype.addDefaults = function(node, callback) { var key, port, process, ref, socket; process = this.processes[node.id]; if (!process.component.isReady()) { if (process.component.setMaxListeners) { process.component.setMaxListeners(0); } process.component.once("ready", (function(_this) { return function() { return _this.addDefaults(process, callback); }; })(this)); return; } ref = process.component.inPorts.ports; for (key in ref) { port = ref[key]; if (typeof port.hasDefault === 'function' && port.hasDefault() && !port.isAttached()) { socket = internalSocket.createSocket(); socket.setDebug(this.debug); this.subscribeSocket(socket); this.connectPort(socket, process, key, void 0, true); this.connections.push(socket); this.defaults.push(socket); } } return callback(); }; Network.prototype.addInitial = function(initializer, callback) { var init, socket, to; socket = internalSocket.createSocket(initializer.metadata); socket.setDebug(this.debug); this.subscribeSocket(socket); to = this.getNode(initializer.to.node); if (!to) { return callback(new Error("No process defined for inbound node " + initializer.to.node)); } if (!(to.component.isReady() || to.component.inPorts[initializer.to.port])) { if (to.component.setMaxListeners) { to.component.setMaxListeners(0); } to.component.once("ready", (function(_this) { return function() { return _this.addInitial(initializer, callback); }; })(this)); return; } this.connectPort(socket, to, initializer.to.port, initializer.to.index, true); this.connections.push(socket); init = { socket: socket, data: initializer.from.data }; this.initials.push(init); this.nextInitials.push(init); if (this.isStarted()) { this.sendInitials(); } return callback(); }; Network.prototype.removeInitial = function(initializer, callback) { var connection, i, init, j, k, len, len1, len2, ref, ref1, ref2; ref = this.connections; for (i = 0, len = ref.length; i < len; i++) { connection = ref[i]; if (!connection) { continue; } if (!(initializer.to.node === connection.to.process.id && initializer.to.port === connection.to.port)) { continue; } connection.to.process.component.inPorts[connection.to.port].detach(connection); this.connections.splice(this.connections.indexOf(connection), 1); ref1 = this.initials; for (j = 0, len1 = ref1.length; j < len1; j++) { init = ref1[j]; if (!init) { continue; } if (init.socket !== connection) { continue; } this.initials.splice(this.initials.indexOf(init), 1); } ref2 = this.nextInitials; for (k = 0, len2 = ref2.length; k < len2; k++) { init = ref2[k]; if (!init) { continue; } if (init.socket !== connection) { continue; } this.nextInitials.splice(this.nextInitials.indexOf(init), 1); } } return callback(); }; Network.prototype.sendInitial = function(initial) { return initial.socket.post(new IP('data', initial.data, { initial: true })); }; Network.prototype.sendInitials = function(callback) { var send; if (!callback) { callback = function() {}; } send = (function(_this) { return function() { var i, initial, len, ref; ref = _this.initials; for (i = 0, len = ref.length; i < len; i++) { initial = ref[i]; _this.sendInitial(initial); } _this.initials = []; return callback(); }; })(this); if (typeof process !== 'undefined' && process.execPath && process.execPath.indexOf('node') !== -1) { return process.nextTick(send); } else { return setTimeout(send, 0); } }; Network.prototype.isStarted = function() { return this.started; }; Network.prototype.isRunning = function() { if (!this.started) { return false; } return this.getActiveProcesses().length > 0; }; Network.prototype.startComponents = function(callback) { var count, id, length, onProcessStart, process, ref, results; if (!callback) { callback = function() {}; } count = 0; length = this.processes ? Object.keys(this.processes).length : 0; onProcessStart = function(err) { if (err) { return callback(err); } count++; if (count === length) { return callback(); } }; if (!(this.processes && Object.keys(this.processes).length)) { return callback(); } ref = this.processes; results = []; for (id in ref) { process = ref[id]; if (process.component.isStarted()) { onProcessStart(); continue; } if (process.component.start.length === 0) { platform.deprecated('component.start method without callback is deprecated'); process.component.start(); onProcessStart(); continue; } results.push(process.component.start(onProcessStart)); } return results; }; Network.prototype.sendDefaults = function(callback) { var i, len, ref, socket; if (!callback) { callback = function() {}; } if (!this.defaults.length) { return callback(); } ref = this.defaults; for (i = 0, len = ref.length; i < len; i++) { socket = ref[i]; if (socket.to.process.component.inPorts[socket.to.port].sockets.length !== 1) { continue; } socket.connect(); socket.send(); socket.disconnect(); } return callback(); }; Network.prototype.start = function(callback) { if (!callback) { platform.deprecated('Calling network.start() without callback is deprecated'); callback = function() {}; } if (this.debouncedEnd) { this.abortDebounce = true; } if (this.started) { this.stop((function(_this) { return function(err) { if (err) { return callback(err); } return _this.start(callback); }; })(this)); return; } this.initials = this.nextInitials.slice(0); this.eventBuffer = []; return this.startComponents((function(_this) { return function(err) { if (err) { return callback(err); } return _this.sendInitials(function(err) { if (err) { return callback(err); } return _this.sendDefaults(function(err) { if (err) { return callback(err); } _this.setStarted(true); return callback(null); }); }); }; })(this)); }; Network.prototype.stop = function(callback) { var connection, count, i, id, len, length, onProcessEnd, process, ref, ref1, results; if (!callback) { platform.deprecated('Calling network.stop() without callback is deprecated'); callback = function() {}; } if (this.debouncedEnd) { this.abortDebounce = true; } if (!this.started) { return callback(null); } ref = this.connections; for (i = 0, len = ref.length; i < len; i++) { connection = ref[i]; if (!connection.isConnected()) { continue; } connection.disconnect(); } count = 0; length = this.processes ? Object.keys(this.processes).length : 0; onProcessEnd = (function(_this) { return function(err) { if (err) { return callback(err); } count++; if (count === length) { _this.setStarted(false); return callback(); } }; })(this); if (!(this.processes && Object.keys(this.processes).length)) { this.setStarted(false); return callback(); } ref1 = this.processes; results = []; for (id in ref1) { process = ref1[id]; if (!process.component.isStarted()) { onProcessEnd(); continue; } if (process.component.shutdown.length === 0) { platform.deprecated('component.shutdown method without callback is deprecated'); process.component.shutdown(); onProcessEnd(); continue; } results.push(process.component.shutdown(onProcessEnd)); } return results; }; Network.prototype.setStarted = function(started) { if (this.started === started) { return; } if (!started) { this.started = false; this.bufferedEmit('end', { start: this.startupDate, end: new Date, uptime: this.uptime() }); return; } if (!this.startupDate) { this.startupDate = new Date; } this.started = true; return this.bufferedEmit('start', { start: this.startupDate }); }; Network.prototype.checkIfFinished = function() { if (this.isRunning()) { return; } delete this.abortDebounce; if (!this.debouncedEnd) { this.debouncedEnd = utils.debounce((function(_this) { return function() { if (_this.abortDebounce) { return; } return _this.setStarted(false); }; })(this), 50); } return this.debouncedEnd(); }; Network.prototype.getDebug = function() { return this.debug; }; Network.prototype.setDebug = function(active) { var i, instance, len, process, processId, ref, ref1, results, socket; if (active === this.debug) { return; } this.debug = active; ref = this.connections; for (i = 0, len = ref.length; i < len; i++) { socket = ref[i]; socket.setDebug(active); } ref1 = this.processes; results = []; for (processId in ref1) { process = ref1[processId]; instance = process.component; if (instance.isSubgraph()) { results.push(instance.network.setDebug(active)); } else { results.push(void 0); } } return results; }; return Network; })(EventEmitter); exports.Network = Network; }).call(this);