UNPKG

noflo

Version:

Flow-Based Programming environment for JavaScript

181 lines (167 loc) 6.12 kB
# NoFlo - Flow-Based Programming for JavaScript # (c) 2017 Flowhub UG # NoFlo may be freely distributed under the MIT license # # asCallback helper embedding NoFlo components or graphs in other JavaScript programs. ComponentLoader = require('./ComponentLoader').ComponentLoader Network = require('./Network').Network IP = require('./IP') internalSocket = require './InternalSocket' Graph = require('fbp-graph').Graph normalizeOptions = (options, component) -> options = {} unless options options.name = component unless options.name if options.loader options.baseDir = options.loader.baseDir if not options.baseDir and process and process.cwd options.baseDir = process.cwd() unless options.loader options.loader = new ComponentLoader options.baseDir options.raw = false unless options.raw options prepareNetwork = (component, options, callback) -> # Start by loading the component options.loader.load component, (err, instance) -> return callback err if err # Prepare a graph wrapping the component graph = new Graph options.name nodeName = options.name graph.addNode nodeName, component # Expose ports # FIXME: direct process.component.inPorts/outPorts access is only for legacy compat inPorts = instance.inPorts.ports or instance.inPorts outPorts = instance.outPorts.ports or instance.outPorts for port, def of inPorts graph.addInport port, nodeName, port for port, def of outPorts graph.addOutport port, nodeName, port # Prepare network graph.componentLoader = options.loader network = new Network graph, options # Wire the network up and start execution network.connect (err) -> return callback err if err callback null, network runNetwork = (network, inputs, options, callback) -> process = network.getNode options.name # Prepare inports inPorts = Object.keys network.graph.inports inSockets = {} inPorts.forEach (inport) -> inSockets[inport] = internalSocket.createSocket() process.component.inPorts[inport].attach inSockets[inport] # Subscribe outports received = [] outPorts = Object.keys network.graph.outports outSockets = {} outPorts.forEach (outport) -> outSockets[outport] = internalSocket.createSocket() process.component.outPorts[outport].attach outSockets[outport] outSockets[outport].on 'ip', (ip) -> res = {} res[outport] = ip received.push res # Subscribe network finish network.once 'end', -> # Clear listeners for port, socket of outSockets process.component.outPorts[port].detach socket outSockets = {} inSockets = {} callback null, received # Start network network.start (err) -> return callback err if err # Send inputs for inputMap in inputs for port, value of inputMap if IP.isIP value inSockets[port].post value continue inSockets[port].post new IP 'data', value getType = (inputs, network) -> # Scalar values are always simple inputs return 'simple' unless typeof inputs is 'object' if Array.isArray inputs maps = inputs.filter (entry) -> getType(entry, network) is 'map' # If each member if the array is an input map, this is a sequence return 'sequence' if maps.length is inputs.length # Otherwise arrays must be simple inputs return 'simple' # Empty objects can't be maps return 'simple' unless Object.keys(inputs).length for key, value of inputs return 'simple' unless network.graph.inports[key] return 'map' prepareInputMap = (inputs, inputType, network) -> # Sequence we can use as-is return inputs if inputType is 'sequence' # We can turn a map to a sequence by wrapping it in an array return [inputs] if inputType is 'map' # Simple inputs need to be converted to a sequence inPort = Object.keys(network.graph.inports)[0] # If we have a port named "IN", send to that inPort = 'in' if network.graph.inports.in map = {} map[inPort] = inputs return [map] normalizeOutput = (values, options) -> return values if options.raw result = [] previous = null current = result for packet in values if packet.type is 'openBracket' previous = current current = [] previous.push current if packet.type is 'data' current.push packet.data if packet.type is 'closeBracket' current = previous if result.length is 1 return result[0] return result sendOutputMap = (outputs, resultType, options, callback) -> # First check if the output sequence contains errors errors = outputs.filter((map) -> map.error?).map (map) -> map.error return callback normalizeOutput errors, options if errors.length if resultType is 'sequence' return callback null, outputs.map (map) -> res = {} for key, val of map if options.raw res[key] = val continue res[key] = normalizeOutput [val], options return res # Flatten the sequence mappedOutputs = {} for map in outputs for key, val of map mappedOutputs[key] = [] unless mappedOutputs[key] mappedOutputs[key].push val outputKeys = Object.keys mappedOutputs withValue = outputKeys.filter (outport) -> mappedOutputs[outport].length > 0 if withValue.length is 0 # No output return callback null if withValue.length is 1 and resultType is 'simple' # Single outport return callback null, normalizeOutput mappedOutputs[withValue[0]], options result = {} for port, packets of mappedOutputs result[port] = normalizeOutput packets, options callback null, result exports.asCallback = (component, options) -> options = normalizeOptions options, component return (inputs, callback) -> prepareNetwork component, options, (err, network) -> return callback err if err resultType = getType inputs, network inputMap = prepareInputMap inputs, resultType, network runNetwork network, inputMap, options, (err, outputMap) -> return callback err if err sendOutputMap outputMap, resultType, options, callback