noflo
Version:
Flow-Based Programming environment for JavaScript
263 lines (237 loc) • 7.22 kB
JavaScript
// NoFlo - Flow-Based Programming for JavaScript
// (c) 2013-2017 Flowhub UG
// (c) 2011-2012 Henri Bergius, Nemein
// NoFlo may be freely distributed under the MIT license
/* eslint-disable
class-methods-use-this,
import/no-unresolved,
import/prefer-default-export,
*/
import * as noflo from '../lib/NoFlo';
import { deprecated } from '../lib/Platform';
// The Graph component is used to wrap NoFlo Networks into components inside
// another network.
export class Graph extends noflo.Component {
/**
* @param {Object} metadata
*/
constructor(metadata) {
super();
this.metadata = metadata;
/** @type {import("../lib/Network").Network|null} */
this.network = null;
this.ready = true;
this.started = false;
this.starting = false;
/** @type {string|null} */
this.baseDir = null;
/** @type {noflo.ComponentLoader|null} */
this.loader = null;
this.load = 0;
this.inPorts = new noflo.InPorts({
graph: {
datatype: 'all',
description: 'NoFlo graph definition to be used with the subgraph component',
required: true,
},
});
this.outPorts = new noflo.OutPorts();
this.inPorts.ports.graph.on('ip', (packet) => {
if (packet.type !== 'data') { return; }
// TODO: Port this part to Process API and use output.error method instead
this.setGraph(packet.data).catch(this.error);
});
}
setGraph(graph, callback) {
this.ready = false;
let promise;
if (typeof graph === 'object') {
if (typeof graph.addNode === 'function') {
// Existing Graph object
promise = this.createNetwork(graph);
} else {
// JSON definition of a graph
promise = noflo.graph.loadJSON(graph)
.then((instance) => this.createNetwork(instance));
}
} else {
let graphName = graph;
if ((graphName.substr(0, 1) !== '/') && (graphName.substr(1, 1) !== ':') && process && process.cwd) {
graphName = `${process.cwd()}/${graphName}`;
}
promise = noflo.graph.loadFile(graphName)
.then((instance) => this.createNetwork(instance));
}
if (callback) {
deprecated('Providing a callback to Graph.setGraph is deprecated, use Promises');
promise.then(() => {
callback(null);
}, callback);
}
return promise;
}
createNetwork(graph, callback) {
this.description = graph.properties.description || '';
this.icon = graph.properties.icon || this.icon;
const graphObj = graph;
if (!graphObj.name) { graphObj.name = this.nodeId; }
const promise = noflo.createNetwork(graphObj, {
delay: true,
subscribeGraph: false,
componentLoader: this.loader,
baseDir: this.baseDir,
})
.then((network) => {
this.network = network;
this.emit('network', this.network);
// Subscribe to network lifecycle
this.subscribeNetwork(this.network);
// Wire the network up
return network.connect();
})
.then(() => {
Object.keys(this.network.processes).forEach((name) => {
// Map exported ports to local component
const node = this.network.processes[name];
this.findEdgePorts(name, node);
});
// Finally set ourselves as "ready"
this.setToReady();
});
if (callback) {
deprecated('Providing a callback to Graph.createNetwork is deprecated, use Promises');
promise.then(() => {
callback(null);
}, callback);
}
return promise;
}
subscribeNetwork(network) {
const contexts = [];
network.on('start', () => {
const ctx = {
activated: false,
deactivated: false,
result: {},
};
contexts.push(ctx);
this.activate(ctx);
});
network.on('end', () => {
const ctx = contexts.pop();
if (!ctx) { return; }
this.deactivate(ctx);
});
}
isExportedInport(port, nodeName, portName) {
// First we check disambiguated exported ports
const keys = Object.keys(this.network.graph.inports);
for (let i = 0; i < keys.length; i += 1) {
const pub = keys[i];
const priv = this.network.graph.inports[pub];
if (priv.process === nodeName && priv.port === portName) {
return pub;
}
}
// Component has exported ports and this isn't one of them
return false;
}
isExportedOutport(port, nodeName, portName) {
// First we check disambiguated exported ports
const keys = Object.keys(this.network.graph.outports);
for (let i = 0; i < keys.length; i += 1) {
const pub = keys[i];
const priv = this.network.graph.outports[pub];
if (priv.process === nodeName && priv.port === portName) {
return pub;
}
}
// Component has exported ports and this isn't one of them
return false;
}
setToReady() {
if ((typeof process !== 'undefined') && process.execPath && (process.execPath.indexOf('node') !== -1)) {
process.nextTick(() => {
this.ready = true;
return this.emit('ready');
});
} else {
setTimeout(() => {
this.ready = true;
return this.emit('ready');
},
0);
}
}
findEdgePorts(name, process) {
const inPorts = process.component.inPorts.ports;
const outPorts = process.component.outPorts.ports;
Object.keys(inPorts).forEach((portName) => {
const port = inPorts[portName];
const targetPortName = this.isExportedInport(port, name, portName);
if (targetPortName === false) { return; }
this.inPorts.add(targetPortName, port);
this.inPorts[targetPortName].on('connect', () => {
// Start the network implicitly if we're starting to get data
if (this.starting) { return; }
if (this.network.isStarted()) { return; }
if (this.network.startupDate) {
// Network was started, but did finish. Re-start simply
this.network.setStarted(true);
return;
}
// Network was never started, start properly
this.setUp(() => {});
});
});
Object.keys(outPorts).forEach((portName) => {
const port = outPorts[portName];
const targetPortName = this.isExportedOutport(port, name, portName);
if (targetPortName === false) { return; }
this.outPorts.add(targetPortName, port);
});
return true;
}
isReady() {
return this.ready;
}
isSubgraph() {
return true;
}
isLegacy() {
return false;
}
setUp(callback) {
this.starting = true;
if (!this.isReady()) {
this.once('ready', () => {
this.setUp(callback);
});
return;
}
if (!this.network) {
callback(null);
return;
}
this.network.start()
.then(() => {
this.starting = false;
callback(null);
}, callback);
}
tearDown(callback) {
this.starting = false;
if (!this.network) {
callback(null);
return;
}
this.network.stop()
.then(() => callback(), callback);
}
}
/**
* @params {Object} metadata
*/
export function getComponent(metadata) {
return new Graph(metadata);
}