do-red
Version:
A do-node and corresponding return-node for creating loops and task-lists.
105 lines (97 loc) • 4.01 kB
JavaScript
const initNextValue = (msg, config, tasks, RED) => {
const _do = msg._do
_do.index = _do.index + 1
delete _do.valueDeleted
// first time the task stack has values from general _do initaliziation
if (!_do.stack.length) _do.isFirstMsg = false
_do.stack = [...tasks].reverse()
if (_do.collectionType === 'Array') {
if (config.yield === 'value') {
msg.payload = _do.collection[_do.index]
} else {
msg.payload = { [_do.index]: _do.collection[_do.index] }
}
} else if (_do.collectionType === 'Set') {
msg.payload = _do.setIterator.next().value
_do.currentSetValue = RED.util.cloneMessage(msg.payload)
} else if (_do.collectionType === 'Object' || _do.collectionType === 'Map') {
const currentKey = _do.keys[_do.index]
let currentValue
if (_do.collectionType === 'Object') currentValue = _do.collection[currentKey]
else if (_do.collectionType === 'Map') currentValue = _do.collection.get(currentKey)
if (config.yield === 'keyValue') {
msg.payload = { [currentKey]: currentValue }
} else if (config.yield === 'key') {
msg.payload = currentKey
} else if (config.yield === 'value') {
msg.payload = currentValue
} else {
throw new Error("Can't put value into payload (" + config.yield + ' is not valid for Yield).')
}
} else {
throw new Error('No valid collection set!')
}
return msg
}
const initEachDo = (RED, msg, node, config, _do) => {
_do.index = -1
_do.isFirstMsg = true
_do.isLastMsg = false
_do.eachType = config.eachType
_do.eachPath = config.each
_do.yieldType = config.yield
// clone the collection and payload. The collection will always be written when done is reached.
// if the collection is not (within) msg.payload, msg.payload will be replaced with it's original value after all tasks are done.
if (config.eachType === 'msg') {
_do.collection = RED.util.cloneMessage(RED.util.getObjectProperty(msg, _do.eachPath))
} else {
_do.collection = RED.util.evaluateNodeProperty(config.each, config.eachType, node, msg)
}
_do.payload = RED.util.cloneMessage(msg.payload)
return _do
}
const handleDone = (RED, msg, config, send, done) => {
// return original payload after each is done
// if (msg?._do?.payload) { // use optional chaining when latest supported version from node-red is allowing
if (msg._do && msg._do.payload) {
msg.payload = msg._do.payload
}
// get parent do or delete if done
// if (msg?._do?._do) {
if (msg._do && msg._do._do) {
msg._do = msg._do._do
} else {
delete msg._do
}
// send to output or event to other do-node
if (config.doneOutput) {
const out = new Array(config.outputs - 1)
out.push(msg) // must be last output
send(out)
// } else if (msg?._do?.returnTo) {
} else if (msg._do && msg._do.returnTo) {
// if we return to a previous do-node then a task was completed
if (Array.isArray(msg._do.stack)) msg._do.stack.pop()
const event = 'do:' + msg._do.returnTo
msg._event = event
RED.events.emit(event, msg)
}
if (done) return done()
}
const checkCollectionType = (config, collection) => {
if (typeof collection === 'undefined') throw new Error(`Collection ${config.eachType}.${config.each} is undefined!`)
else if (Array.isArray(collection)) return 'Array'
// instanceof Set/Map seems not to work. Maybe proxy variable through Node-Red?
// else if (collection?.constructor?.name === 'Map') return 'Map'
// else if (collection?.constructor?.name === 'Set') return 'Set'
else if (collection.constructor && collection.constructor.name === 'Map') return 'Map'
else if (collection.constructor && collection.constructor.name === 'Set') return 'Set'
else if (!!collection && typeof collection === 'object' && Array.isArray(collection) === false) return 'Object'
throw new Error(`Collection ${config.eachType}.${config.each} could not identified!`)
}
module.exports = {
initNextValue,
initEachDo,
handleDone,
checkCollectionType
}