do-red
Version:
A do-node and corresponding return-node for creating loops and task-lists.
143 lines (135 loc) • 5.41 kB
JavaScript
const doHelper = require('./utils/doHelper')
module.exports = function (RED) {
function DoNode (config) {
RED.nodes.createNode(this, config)
const node = this
node.tasks = config.tasks
// Add event-handling
const event = 'do:' + node.id
const handler = function (msg) {
msg._event = node.event
node.receive(msg)
}
RED.events.on(event, handler)
// Clean up event handler on close
node.on('close', function () {
RED.events.removeListener(event, handler)
})
node.on('input', function (msg, send, done) {
// Fallback for older versions of Node-RED
send = send || function () { node.send.apply(node, arguments) }
// Fallback older do node
if (typeof config.doneOutput === 'undefined') config.doneOutput = true
if (typeof config.mode === 'undefined') config.mode = 'msg'
const handleDo = (hasTasksOrMoreValues) => {
if (hasTasksOrMoreValues) {
let outputMsgIndex = node.tasks.indexOf(_do.stack[_do.stack.length - 1])
if (config.mode === 'each') {
// consider possible firstValueOutput
if (config.firstValueOutput) {
outputMsg.push(undefined)
outputMsgIndex++
}
if (_do.isFirstMsg && config.firstValueOutput) {
return send(msg) // go to first output
} else if (_do.isLastMsg && config.lastValueOutput) {
outputMsg.push(msg) // separate output after all tasks (+ possible first output)
return send(outputMsg)
}
}
outputMsg[outputMsgIndex] = msg
return send(outputMsg)
} else {
return doHelper.handleDone(RED, msg, config, send, done)
}
}
// no-op
if (node.tasks.length === 0) {
send(msg)
done()
return
}
// first time do node or nested do node
if (!msg._do || msg._do.returnTo !== node.id) {
const nestedDo = msg._do
msg._do = {
returnTo: node.id,
stack: [...node.tasks].reverse()
}
if (nestedDo) msg._do._do = nestedDo
}
const outputMsg = new Array(node.tasks.length)
let _do = msg._do
try {
if (config.mode === 'msg') {
handleDo(_do.stack.length > 0)
done()
} else if (config.mode === 'each') {
// first each or first nested each
if (typeof _do.index === 'undefined') {
_do = doHelper.initEachDo(RED, msg, node, config, _do)
}
_do.collectionType = doHelper.checkCollectionType(config, _do.collection)
let hasMoreValues
switch (_do.collectionType) {
case 'Array':
if (_do.yieldType !== 'value' && _do.yieldType !== 'indexValue') {
throw new Error(`Collection is an ${msg._do.collectionType} but should return ${config.yield}!`)
}
// We will only handle the collection length which exists when the each node started first time!
if (typeof _do.collectionLength === 'undefined') {
_do.collectionLength = [..._do.collection].length
}
if (_do.stack.length === 0 || (_do.index === -1 && !_do.valueDeleted)) {
msg = doHelper.initNextValue(msg, config, node.tasks)
}
if (_do.collectionLength === _do.index + 1) {
_do.isLastMsg = true
}
hasMoreValues = (_do.collectionLength >= _do.index + 1)
break
case 'Set':
if (_do.yieldType !== 'value' && _do.yieldType !== 'entries') {
throw new Error(`Collection is an ${msg._do.collectionType} but should return ${config.yield}!`)
}
// init on first run
if (!_do.setIterator) {
_do.setIterator = _do.collection.values()
_do.collectionLength = _do.collection.size
}
if (_do.collectionLength === _do.index + 1) {
_do.isLastMsg = true
}
if (_do.stack.length === 0 || _do.index === -1) {
msg = doHelper.initNextValue(msg, config, node.tasks, RED)
}
// if (_do.yieldType === 'entries') msg.payload = [msg.payload, msg.payload]
hasMoreValues = (_do.collectionLength >= _do.index + 1)
break
case 'Object':
case 'Map':
if (!_do.keys) {
if (_do.collectionType === 'Object') _do.keys = Object.keys(_do.collection)
if (_do.collectionType === 'Map') _do.keys = [..._do.collection.keys()]
}
if (_do.stack.length === 0 || _do.index === -1) {
msg = doHelper.initNextValue(msg, config, node.tasks)
}
if (_do.index + 1 === _do.keys.length) {
_do.isLastMsg = true
}
// only keys from first run will be iterated
hasMoreValues = (_do.keys.length && _do.keys.length !== _do.index)
break
default:
throw new Error('No valid collection type')
}
handleDo(hasMoreValues)
}
} catch (e) {
done(e)
}
})
}
RED.nodes.registerType('do', DoNode)
}