mirror of
https://github.com/node-red/node-red.git
synced 2023-10-10 13:36:53 +02:00
Merge remote-tracking branch 'upstream/0.19' into json-schema
This commit is contained in:
commit
eea85485e6
@ -24,6 +24,10 @@ module.exports = function(grunt) {
|
|||||||
nodemonArgs.push(flowFile);
|
nodemonArgs.push(flowFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var nonHeadless = grunt.option('non-headless');
|
||||||
|
if (nonHeadless) {
|
||||||
|
process.env.NODE_RED_NON_HEADLESS = 'true';
|
||||||
|
}
|
||||||
grunt.initConfig({
|
grunt.initConfig({
|
||||||
pkg: grunt.file.readJSON('package.json'),
|
pkg: grunt.file.readJSON('package.json'),
|
||||||
paths: {
|
paths: {
|
||||||
|
@ -48,12 +48,15 @@ RED.popover = (function() {
|
|||||||
|
|
||||||
var openPopup = function(instant) {
|
var openPopup = function(instant) {
|
||||||
if (active) {
|
if (active) {
|
||||||
div = $('<div class="red-ui-popover red-ui-popover-'+direction+'"></div>').appendTo("body");
|
div = $('<div class="red-ui-popover red-ui-popover-'+direction+'"></div>');
|
||||||
if (size !== "default") {
|
if (size !== "default") {
|
||||||
div.addClass("red-ui-popover-size-"+size);
|
div.addClass("red-ui-popover-size-"+size);
|
||||||
}
|
}
|
||||||
if (typeof content === 'function') {
|
if (typeof content === 'function') {
|
||||||
var result = content.call(res);
|
var result = content.call(res);
|
||||||
|
if (result === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (typeof result === 'string') {
|
if (typeof result === 'string') {
|
||||||
div.text(result);
|
div.text(result);
|
||||||
} else {
|
} else {
|
||||||
@ -65,7 +68,7 @@ RED.popover = (function() {
|
|||||||
if (width !== "auto") {
|
if (width !== "auto") {
|
||||||
div.width(width);
|
div.width(width);
|
||||||
}
|
}
|
||||||
|
div.appendTo("body");
|
||||||
|
|
||||||
var targetPos = target.offset();
|
var targetPos = target.offset();
|
||||||
var targetWidth = target.outerWidth();
|
var targetWidth = target.outerWidth();
|
||||||
|
@ -14,10 +14,38 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
(function($) {
|
(function($) {
|
||||||
|
var contextParse = function(v) {
|
||||||
|
var parts = RED.utils.parseContextKey(v);
|
||||||
|
return {
|
||||||
|
option: parts.store,
|
||||||
|
value: parts.key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var contextExport = function(v,opt) {
|
||||||
|
if (!opt) {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
var store = ((typeof opt === "string")?opt:opt.value)
|
||||||
|
if (store !== RED.settings.context.default) {
|
||||||
|
return "#:("+store+")::"+v;
|
||||||
|
} else {
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
}
|
||||||
var allOptions = {
|
var allOptions = {
|
||||||
msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression},
|
msg: {value:"msg",label:"msg.",validate:RED.utils.validatePropertyExpression},
|
||||||
flow: {value:"flow",label:"flow.",validate:RED.utils.validatePropertyExpression},
|
flow: {value:"flow",label:"flow.",hasValue:true,
|
||||||
global: {value:"global",label:"global.",validate:RED.utils.validatePropertyExpression},
|
options:[],
|
||||||
|
validate:RED.utils.validatePropertyExpression,
|
||||||
|
parse: contextParse,
|
||||||
|
export: contextExport
|
||||||
|
},
|
||||||
|
global: {value:"global",label:"global.",hasValue:true,
|
||||||
|
options:[],
|
||||||
|
validate:RED.utils.validatePropertyExpression,
|
||||||
|
parse: contextParse,
|
||||||
|
export: contextExport
|
||||||
|
},
|
||||||
str: {value:"str",label:"string",icon:"red/images/typedInput/az.png"},
|
str: {value:"str",label:"string",icon:"red/images/typedInput/az.png"},
|
||||||
num: {value:"num",label:"number",icon:"red/images/typedInput/09.png",validate:/^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/},
|
num: {value:"num",label:"number",icon:"red/images/typedInput/09.png",validate:/^[+-]?[0-9]*\.?[0-9]*([eE][-+]?[0-9]+)?$/},
|
||||||
bool: {value:"bool",label:"boolean",icon:"red/images/typedInput/bool.png",options:["true","false"]},
|
bool: {value:"bool",label:"boolean",icon:"red/images/typedInput/bool.png",options:["true","false"]},
|
||||||
@ -87,12 +115,24 @@
|
|||||||
|
|
||||||
$.widget( "nodered.typedInput", {
|
$.widget( "nodered.typedInput", {
|
||||||
_create: function() {
|
_create: function() {
|
||||||
|
try {
|
||||||
if (!nlsd && RED && RED._) {
|
if (!nlsd && RED && RED._) {
|
||||||
for (var i in allOptions) {
|
for (var i in allOptions) {
|
||||||
if (allOptions.hasOwnProperty(i)) {
|
if (allOptions.hasOwnProperty(i)) {
|
||||||
allOptions[i].label = RED._("typedInput.type."+i,{defaultValue:allOptions[i].label});
|
allOptions[i].label = RED._("typedInput.type."+i,{defaultValue:allOptions[i].label});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var contextStores = RED.settings.context.stores;
|
||||||
|
var contextOptions = contextStores.map(function(store) {
|
||||||
|
return {value:store,label: store, icon:'<i class="red-ui-typedInput-icon fa fa-database" style="color: #'+(store==='memory'?'ddd':'777')+'"></i>'}
|
||||||
|
})
|
||||||
|
if (contextOptions.length < 2) {
|
||||||
|
allOptions.flow.options = [];
|
||||||
|
allOptions.global.options = [];
|
||||||
|
} else {
|
||||||
|
allOptions.flow.options = contextOptions;
|
||||||
|
allOptions.global.options = contextOptions;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
nlsd = true;
|
nlsd = true;
|
||||||
var that = this;
|
var that = this;
|
||||||
@ -170,6 +210,9 @@
|
|||||||
// explicitly set optionSelectTrigger display to inline-block otherwise jQ sets it to 'inline'
|
// explicitly set optionSelectTrigger display to inline-block otherwise jQ sets it to 'inline'
|
||||||
this.optionSelectTrigger = $('<button tabindex="0" class="red-ui-typedInput-option-trigger" style="display:inline-block"><span class="red-ui-typedInput-option-caret"><i class="red-ui-typedInput-icon fa fa-sort-desc"></i></span></button>').appendTo(this.uiSelect);
|
this.optionSelectTrigger = $('<button tabindex="0" class="red-ui-typedInput-option-trigger" style="display:inline-block"><span class="red-ui-typedInput-option-caret"><i class="red-ui-typedInput-icon fa fa-sort-desc"></i></span></button>').appendTo(this.uiSelect);
|
||||||
this.optionSelectLabel = $('<span class="red-ui-typedInput-option-label"></span>').prependTo(this.optionSelectTrigger);
|
this.optionSelectLabel = $('<span class="red-ui-typedInput-option-label"></span>').prependTo(this.optionSelectTrigger);
|
||||||
|
RED.popover.tooltip(this.optionSelectLabel,function() {
|
||||||
|
return that.optionValue;
|
||||||
|
});
|
||||||
this.optionSelectTrigger.click(function(event) {
|
this.optionSelectTrigger.click(function(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
that._showOptionSelectMenu();
|
that._showOptionSelectMenu();
|
||||||
@ -186,6 +229,9 @@
|
|||||||
|
|
||||||
this.optionExpandButton = $('<button tabindex="0" class="red-ui-typedInput-option-expand" style="display:inline-block"><i class="red-ui-typedInput-icon fa fa-ellipsis-h"></i></button>').appendTo(this.uiSelect);
|
this.optionExpandButton = $('<button tabindex="0" class="red-ui-typedInput-option-expand" style="display:inline-block"><i class="red-ui-typedInput-icon fa fa-ellipsis-h"></i></button>').appendTo(this.uiSelect);
|
||||||
this.type(this.options.default||this.typeList[0].value);
|
this.type(this.options.default||this.typeList[0].value);
|
||||||
|
}catch(err) {
|
||||||
|
console.log(err.stack);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
_showTypeMenu: function() {
|
_showTypeMenu: function() {
|
||||||
if (this.typeList.length > 1) {
|
if (this.typeList.length > 1) {
|
||||||
@ -467,7 +513,9 @@
|
|||||||
var opt = this.typeMap[type];
|
var opt = this.typeMap[type];
|
||||||
if (opt && this.propertyType !== type) {
|
if (opt && this.propertyType !== type) {
|
||||||
this.propertyType = type;
|
this.propertyType = type;
|
||||||
this.typeField.val(type);
|
if (this.typeField) {
|
||||||
|
this.typeField.val(type);
|
||||||
|
}
|
||||||
this.selectLabel.empty();
|
this.selectLabel.empty();
|
||||||
var image;
|
var image;
|
||||||
if (opt.icon) {
|
if (opt.icon) {
|
||||||
@ -539,19 +587,31 @@
|
|||||||
var parts = opt.parse(this.input.val());
|
var parts = opt.parse(this.input.val());
|
||||||
if (parts.option) {
|
if (parts.option) {
|
||||||
selectedOption = parts.option;
|
selectedOption = parts.option;
|
||||||
|
if (!this.activeOptions.hasOwnProperty(selectedOption)) {
|
||||||
|
parts.option = Object.keys(this.activeOptions)[0];
|
||||||
|
selectedOption = parts.option
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.input.val(parts.value);
|
this.input.val(parts.value);
|
||||||
if (opt.export) {
|
if (opt.export) {
|
||||||
this.element.val(opt.export(parts.value,parts.option||selectedOption));
|
this.element.val(opt.export(parts.value,parts.option||selectedOption));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof selectedOption === "string") {
|
if (typeof selectedOption === "string") {
|
||||||
this.optionValue = selectedOption;
|
this.optionValue = selectedOption;
|
||||||
this._updateOptionSelectLabel(this.activeOptions[selectedOption]);
|
if (!this.activeOptions.hasOwnProperty(selectedOption)) {
|
||||||
} else {
|
selectedOption = Object.keys(this.activeOptions)[0];
|
||||||
|
}
|
||||||
|
if (!selectedOption) {
|
||||||
|
this.optionSelectTrigger.hide();
|
||||||
|
} else {
|
||||||
|
this._updateOptionSelectLabel(this.activeOptions[selectedOption]);
|
||||||
|
}
|
||||||
|
} else if (selectedOption) {
|
||||||
this.optionValue = selectedOption.value;
|
this.optionValue = selectedOption.value;
|
||||||
this._updateOptionSelectLabel(selectedOption);
|
this._updateOptionSelectLabel(selectedOption);
|
||||||
|
} else {
|
||||||
|
this.optionSelectTrigger.hide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -574,15 +634,17 @@
|
|||||||
}
|
}
|
||||||
this.elementDiv.show();
|
this.elementDiv.show();
|
||||||
}
|
}
|
||||||
if (opt.expand && typeof opt.expand === 'function') {
|
if (this.optionExpandButton) {
|
||||||
this.optionExpandButton.show();
|
if (opt.expand && typeof opt.expand === 'function') {
|
||||||
this.optionExpandButton.off('click');
|
this.optionExpandButton.show();
|
||||||
this.optionExpandButton.on('click',function(evt) {
|
this.optionExpandButton.off('click');
|
||||||
evt.preventDefault();
|
this.optionExpandButton.on('click',function(evt) {
|
||||||
opt.expand.call(that);
|
evt.preventDefault();
|
||||||
})
|
opt.expand.call(that);
|
||||||
} else {
|
})
|
||||||
this.optionExpandButton.hide();
|
} else {
|
||||||
|
this.optionExpandButton.hide();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.input.trigger('change',this.propertyType,this.value());
|
this.input.trigger('change',this.propertyType,this.value());
|
||||||
}
|
}
|
||||||
|
@ -828,6 +828,20 @@ RED.utils = (function() {
|
|||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function parseContextKey(key) {
|
||||||
|
var parts = {};
|
||||||
|
var m = /^#:\((\S+?)\)::(.*)$/.exec(key);
|
||||||
|
if (m) {
|
||||||
|
parts.store = m[1];
|
||||||
|
parts.key = m[2];
|
||||||
|
} else {
|
||||||
|
parts.key = key;
|
||||||
|
if (RED.settings.context) {
|
||||||
|
parts.store = RED.settings.context.default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parts;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
createObjectElement: buildMessageElement,
|
createObjectElement: buildMessageElement,
|
||||||
@ -839,6 +853,7 @@ RED.utils = (function() {
|
|||||||
getNodeIcon: getNodeIcon,
|
getNodeIcon: getNodeIcon,
|
||||||
getNodeLabel: getNodeLabel,
|
getNodeLabel: getNodeLabel,
|
||||||
addSpinnerOverlay: addSpinnerOverlay,
|
addSpinnerOverlay: addSpinnerOverlay,
|
||||||
decodeObject: decodeObject
|
decodeObject: decodeObject,
|
||||||
|
parseContextKey: parseContextKey
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
@ -491,7 +491,7 @@ RED.view = (function() {
|
|||||||
var midX = Math.floor(destX-dx/2);
|
var midX = Math.floor(destX-dx/2);
|
||||||
var midY = Math.floor(destY-dy/2);
|
var midY = Math.floor(destY-dy/2);
|
||||||
//
|
//
|
||||||
if (dy == 0) {
|
if (dy === 0) {
|
||||||
midY = destY + node_height;
|
midY = destY + node_height;
|
||||||
}
|
}
|
||||||
var cp_height = node_height/2;
|
var cp_height = node_height/2;
|
||||||
|
@ -237,10 +237,13 @@ If you want every 20 minutes from now - use the <i>"interval"</i> option.</p>
|
|||||||
} else {
|
} else {
|
||||||
return this._("inject.timestamp")+suffix;
|
return this._("inject.timestamp")+suffix;
|
||||||
}
|
}
|
||||||
} else if (this.payloadType === 'flow' && this.payload.length < 19) {
|
} else if (this.payloadType === 'flow' || this.payloadType === 'global') {
|
||||||
return 'flow.'+this.payload+suffix;
|
var key = this.payload;
|
||||||
} else if (this.payloadType === 'global' && this.payload.length < 17) {
|
var m = /^#:\((\S+?)\)::(.*)$/.exec(key);
|
||||||
return 'global.'+this.payload+suffix;
|
if (m) {
|
||||||
|
key = m[2];
|
||||||
|
}
|
||||||
|
return 'flow.'+key+suffix;
|
||||||
} else {
|
} else {
|
||||||
return this._("inject.inject")+suffix;
|
return this._("inject.inject")+suffix;
|
||||||
}
|
}
|
||||||
|
@ -63,21 +63,33 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.on("input",function(msg) {
|
this.on("input",function(msg) {
|
||||||
try {
|
msg.topic = this.topic;
|
||||||
msg.topic = this.topic;
|
if (this.payloadType !== 'flow' && this.payloadType !== 'global') {
|
||||||
if ( (this.payloadType == null && this.payload === "") || this.payloadType === "date") {
|
try {
|
||||||
msg.payload = Date.now();
|
if ( (this.payloadType == null && this.payload === "") || this.payloadType === "date") {
|
||||||
} else if (this.payloadType == null) {
|
msg.payload = Date.now();
|
||||||
msg.payload = this.payload;
|
} else if (this.payloadType == null) {
|
||||||
} else if (this.payloadType === 'none') {
|
msg.payload = this.payload;
|
||||||
msg.payload = "";
|
} else if (this.payloadType === 'none') {
|
||||||
} else {
|
msg.payload = "";
|
||||||
msg.payload = RED.util.evaluateNodeProperty(this.payload,this.payloadType,this,msg);
|
} else {
|
||||||
|
msg.payload = RED.util.evaluateNodeProperty(this.payload,this.payloadType,this,msg);
|
||||||
|
}
|
||||||
|
this.send(msg);
|
||||||
|
msg = null;
|
||||||
|
} catch(err) {
|
||||||
|
this.error(err,msg);
|
||||||
}
|
}
|
||||||
this.send(msg);
|
} else {
|
||||||
msg = null;
|
RED.util.evaluateNodeProperty(this.payload,this.payloadType,this,msg, function(err,res) {
|
||||||
} catch(err) {
|
if (err) {
|
||||||
this.error(err,msg);
|
node.error(err,msg);
|
||||||
|
} else {
|
||||||
|
msg.payload = res;
|
||||||
|
node.send(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -76,8 +76,43 @@ module.exports = function(RED) {
|
|||||||
var node = this;
|
var node = this;
|
||||||
node.topics = {};
|
node.topics = {};
|
||||||
|
|
||||||
this.on("input", function(msg) {
|
var pendingMessages = [];
|
||||||
|
var activeMessagePromise = null;
|
||||||
|
var processMessageQueue = function(msg) {
|
||||||
|
if (msg) {
|
||||||
|
// A new message has arrived - add it to the message queue
|
||||||
|
pendingMessages.push(msg);
|
||||||
|
if (activeMessagePromise !== null) {
|
||||||
|
// The node is currently processing a message, so do nothing
|
||||||
|
// more with this message
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pendingMessages.length === 0) {
|
||||||
|
// There are no more messages to process, clear the active flag
|
||||||
|
// and return
|
||||||
|
activeMessagePromise = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// There are more messages to process. Get the next message and
|
||||||
|
// start processing it. Recurse back in to check for any more
|
||||||
|
var nextMsg = pendingMessages.shift();
|
||||||
|
activeMessagePromise = processMessage(nextMsg)
|
||||||
|
.then(processMessageQueue)
|
||||||
|
.catch((err) => {
|
||||||
|
node.error(err,nextMsg);
|
||||||
|
return processMessageQueue();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.on('input', function(msg) {
|
||||||
|
processMessageQueue(msg);
|
||||||
|
});
|
||||||
|
|
||||||
|
var processMessage = function(msg) {
|
||||||
var topic = msg.topic || "_none";
|
var topic = msg.topic || "_none";
|
||||||
|
var promise;
|
||||||
if (node.bytopic === "all") { topic = "_none"; }
|
if (node.bytopic === "all") { topic = "_none"; }
|
||||||
node.topics[topic] = node.topics[topic] || {};
|
node.topics[topic] = node.topics[topic] || {};
|
||||||
if (msg.hasOwnProperty("reset") || ((node.reset !== '') && msg.hasOwnProperty("payload") && (msg.payload !== null) && msg.payload.toString && (msg.payload.toString() == node.reset)) ) {
|
if (msg.hasOwnProperty("reset") || ((node.reset !== '') && msg.hasOwnProperty("payload") && (msg.payload !== null) && msg.payload.toString && (msg.payload.toString() == node.reset)) ) {
|
||||||
@ -88,48 +123,88 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (((!node.topics[topic].tout) && (node.topics[topic].tout !== 0)) || (node.loop === true)) {
|
if (((!node.topics[topic].tout) && (node.topics[topic].tout !== 0)) || (node.loop === true)) {
|
||||||
|
promise = Promise.resolve();
|
||||||
if (node.op2type === "pay" || node.op2type === "payl") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); }
|
if (node.op2type === "pay" || node.op2type === "payl") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); }
|
||||||
else if (node.op2Templated) { node.topics[topic].m2 = mustache.render(node.op2,msg); }
|
else if (node.op2Templated) { node.topics[topic].m2 = mustache.render(node.op2,msg); }
|
||||||
else if (node.op2type !== "nul") {
|
else if (node.op2type !== "nul") {
|
||||||
node.topics[topic].m2 = RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg);
|
promise = new Promise((resolve,reject) => {
|
||||||
}
|
RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg,(err,value) => {
|
||||||
|
if (err) {
|
||||||
if (node.op1type === "pay") { }
|
reject(err);
|
||||||
else if (node.op1Templated) { msg.payload = mustache.render(node.op1,msg); }
|
} else {
|
||||||
else if (node.op1type !== "nul") {
|
node.topics[topic].m2 = value;
|
||||||
msg.payload = RED.util.evaluateNodeProperty(node.op1,node.op1type,node,msg);
|
resolve();
|
||||||
}
|
|
||||||
|
|
||||||
if (node.duration === 0) { node.topics[topic].tout = 0; }
|
|
||||||
else if (node.loop === true) {
|
|
||||||
/* istanbul ignore else */
|
|
||||||
if (node.topics[topic].tout) { clearInterval(node.topics[topic].tout); }
|
|
||||||
/* istanbul ignore else */
|
|
||||||
if (node.op1type !== "nul") {
|
|
||||||
var msg2 = RED.util.cloneMessage(msg);
|
|
||||||
node.topics[topic].tout = setInterval(function() { node.send(RED.util.cloneMessage(msg2)); }, node.duration);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
if (!node.topics[topic].tout) {
|
|
||||||
node.topics[topic].tout = setTimeout(function() {
|
|
||||||
var msg2 = null;
|
|
||||||
if (node.op2type !== "nul") {
|
|
||||||
msg2 = RED.util.cloneMessage(msg);
|
|
||||||
if (node.op2type === "flow" || node.op2type === "global") {
|
|
||||||
node.topics[topic].m2 = RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg);
|
|
||||||
}
|
|
||||||
msg2.payload = node.topics[topic].m2;
|
|
||||||
delete node.topics[topic];
|
|
||||||
node.send(msg2);
|
|
||||||
}
|
}
|
||||||
else { delete node.topics[topic]; }
|
});
|
||||||
node.status({});
|
});
|
||||||
}, node.duration);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
node.status({fill:"blue",shape:"dot",text:" "});
|
|
||||||
if (node.op1type !== "nul") { node.send(RED.util.cloneMessage(msg)); }
|
return promise.then(() => {
|
||||||
|
promise = Promise.resolve();
|
||||||
|
if (node.op1type === "pay") { }
|
||||||
|
else if (node.op1Templated) { msg.payload = mustache.render(node.op1,msg); }
|
||||||
|
else if (node.op1type !== "nul") {
|
||||||
|
promise = new Promise((resolve,reject) => {
|
||||||
|
RED.util.evaluateNodeProperty(node.op1,node.op1type,node,msg,(err,value) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
msg.payload = value;
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return promise.then(() => {
|
||||||
|
if (node.duration === 0) { node.topics[topic].tout = 0; }
|
||||||
|
else if (node.loop === true) {
|
||||||
|
/* istanbul ignore else */
|
||||||
|
if (node.topics[topic].tout) { clearInterval(node.topics[topic].tout); }
|
||||||
|
/* istanbul ignore else */
|
||||||
|
if (node.op1type !== "nul") {
|
||||||
|
var msg2 = RED.util.cloneMessage(msg);
|
||||||
|
node.topics[topic].tout = setInterval(function() { node.send(RED.util.cloneMessage(msg2)); }, node.duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!node.topics[topic].tout) {
|
||||||
|
node.topics[topic].tout = setTimeout(function() {
|
||||||
|
var msg2 = null;
|
||||||
|
if (node.op2type !== "nul") {
|
||||||
|
var promise = Promise.resolve();
|
||||||
|
msg2 = RED.util.cloneMessage(msg);
|
||||||
|
if (node.op2type === "flow" || node.op2type === "global") {
|
||||||
|
promise = new Promise((resolve,reject) => {
|
||||||
|
RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg,(err,value) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
node.topics[topic].m2 = value;
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
promise.then(() => {
|
||||||
|
msg2.payload = node.topics[topic].m2;
|
||||||
|
delete node.topics[topic];
|
||||||
|
node.send(msg2);
|
||||||
|
node.status({});
|
||||||
|
}).catch(err => {
|
||||||
|
node.error(err);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
delete node.topics[topic];
|
||||||
|
node.status({});
|
||||||
|
}
|
||||||
|
|
||||||
|
}, node.duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node.status({fill:"blue",shape:"dot",text:" "});
|
||||||
|
if (node.op1type !== "nul") { node.send(RED.util.cloneMessage(msg)); }
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
else if ((node.extend === "true" || node.extend === true) && (node.duration > 0)) {
|
else if ((node.extend === "true" || node.extend === true) && (node.duration > 0)) {
|
||||||
/* istanbul ignore else */
|
/* istanbul ignore else */
|
||||||
@ -138,25 +213,43 @@ module.exports = function(RED) {
|
|||||||
if (node.topics[topic].tout) { clearTimeout(node.topics[topic].tout); }
|
if (node.topics[topic].tout) { clearTimeout(node.topics[topic].tout); }
|
||||||
node.topics[topic].tout = setTimeout(function() {
|
node.topics[topic].tout = setTimeout(function() {
|
||||||
var msg2 = null;
|
var msg2 = null;
|
||||||
|
var promise = Promise.resolve();
|
||||||
|
|
||||||
if (node.op2type !== "nul") {
|
if (node.op2type !== "nul") {
|
||||||
if (node.op2type === "flow" || node.op2type === "global") {
|
if (node.op2type === "flow" || node.op2type === "global") {
|
||||||
node.topics[topic].m2 = RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg);
|
promise = new Promise((resolve,reject) => {
|
||||||
}
|
RED.util.evaluateNodeProperty(node.op2,node.op2type,node,msg,(err,value) => {
|
||||||
if (node.topics[topic] !== undefined) {
|
if (err) {
|
||||||
msg2 = RED.util.cloneMessage(msg);
|
reject(err);
|
||||||
msg2.payload = node.topics[topic].m2;
|
} else {
|
||||||
|
node.topics[topic].m2 = value;
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
delete node.topics[topic];
|
promise.then(() => {
|
||||||
node.status({});
|
if (node.op2type !== "nul") {
|
||||||
node.send(msg2);
|
if (node.topics[topic] !== undefined) {
|
||||||
|
msg2 = RED.util.cloneMessage(msg);
|
||||||
|
msg2.payload = node.topics[topic].m2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delete node.topics[topic];
|
||||||
|
node.status({});
|
||||||
|
node.send(msg2);
|
||||||
|
}).catch(err => {
|
||||||
|
node.error(err);
|
||||||
|
});
|
||||||
}, node.duration);
|
}, node.duration);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (node.op2type === "payl") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); }
|
if (node.op2type === "payl") { node.topics[topic].m2 = RED.util.cloneMessage(msg.payload); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
return Promise.resolve();
|
||||||
|
}
|
||||||
this.on("close", function() {
|
this.on("close", function() {
|
||||||
for (var t in node.topics) {
|
for (var t in node.topics) {
|
||||||
/* istanbul ignore else */
|
/* istanbul ignore else */
|
||||||
|
@ -23,10 +23,6 @@ from time import sleep
|
|||||||
|
|
||||||
bounce = 25;
|
bounce = 25;
|
||||||
|
|
||||||
if sys.version_info >= (3,0):
|
|
||||||
print("Sorry - currently only configured to work with python 2.x")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
if len(sys.argv) > 2:
|
if len(sys.argv) > 2:
|
||||||
cmd = sys.argv[1].lower()
|
cmd = sys.argv[1].lower()
|
||||||
pin = int(sys.argv[2])
|
pin = int(sys.argv[2])
|
||||||
@ -34,7 +30,7 @@ if len(sys.argv) > 2:
|
|||||||
GPIO.setwarnings(False)
|
GPIO.setwarnings(False)
|
||||||
|
|
||||||
if cmd == "pwm":
|
if cmd == "pwm":
|
||||||
#print "Initialised pin "+str(pin)+" to PWM"
|
#print("Initialised pin "+str(pin)+" to PWM")
|
||||||
try:
|
try:
|
||||||
freq = int(sys.argv[3])
|
freq = int(sys.argv[3])
|
||||||
except:
|
except:
|
||||||
@ -54,10 +50,10 @@ if len(sys.argv) > 2:
|
|||||||
GPIO.cleanup(pin)
|
GPIO.cleanup(pin)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print "bad data: "+data
|
print("bad data: "+data)
|
||||||
|
|
||||||
elif cmd == "buzz":
|
elif cmd == "buzz":
|
||||||
#print "Initialised pin "+str(pin)+" to Buzz"
|
#print("Initialised pin "+str(pin)+" to Buzz")
|
||||||
GPIO.setup(pin,GPIO.OUT)
|
GPIO.setup(pin,GPIO.OUT)
|
||||||
p = GPIO.PWM(pin, 100)
|
p = GPIO.PWM(pin, 100)
|
||||||
p.stop()
|
p.stop()
|
||||||
@ -76,10 +72,10 @@ if len(sys.argv) > 2:
|
|||||||
GPIO.cleanup(pin)
|
GPIO.cleanup(pin)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
print "bad data: "+data
|
print("bad data: "+data)
|
||||||
|
|
||||||
elif cmd == "out":
|
elif cmd == "out":
|
||||||
#print "Initialised pin "+str(pin)+" to OUT"
|
#print("Initialised pin "+str(pin)+" to OUT")
|
||||||
GPIO.setup(pin,GPIO.OUT)
|
GPIO.setup(pin,GPIO.OUT)
|
||||||
if len(sys.argv) == 4:
|
if len(sys.argv) == 4:
|
||||||
GPIO.output(pin,int(sys.argv[3]))
|
GPIO.output(pin,int(sys.argv[3]))
|
||||||
@ -103,11 +99,11 @@ if len(sys.argv) > 2:
|
|||||||
GPIO.output(pin,data)
|
GPIO.output(pin,data)
|
||||||
|
|
||||||
elif cmd == "in":
|
elif cmd == "in":
|
||||||
#print "Initialised pin "+str(pin)+" to IN"
|
#print("Initialised pin "+str(pin)+" to IN")
|
||||||
bounce = float(sys.argv[4])
|
bounce = float(sys.argv[4])
|
||||||
def handle_callback(chan):
|
def handle_callback(chan):
|
||||||
sleep(bounce/1000.0)
|
sleep(bounce/1000.0)
|
||||||
print GPIO.input(chan)
|
print(GPIO.input(chan))
|
||||||
|
|
||||||
if sys.argv[3].lower() == "up":
|
if sys.argv[3].lower() == "up":
|
||||||
GPIO.setup(pin,GPIO.IN,GPIO.PUD_UP)
|
GPIO.setup(pin,GPIO.IN,GPIO.PUD_UP)
|
||||||
@ -116,7 +112,7 @@ if len(sys.argv) > 2:
|
|||||||
else:
|
else:
|
||||||
GPIO.setup(pin,GPIO.IN)
|
GPIO.setup(pin,GPIO.IN)
|
||||||
|
|
||||||
print GPIO.input(pin)
|
print(GPIO.input(pin))
|
||||||
GPIO.add_event_detect(pin, GPIO.BOTH, callback=handle_callback, bouncetime=int(bounce))
|
GPIO.add_event_detect(pin, GPIO.BOTH, callback=handle_callback, bouncetime=int(bounce))
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
@ -129,7 +125,7 @@ if len(sys.argv) > 2:
|
|||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
elif cmd == "byte":
|
elif cmd == "byte":
|
||||||
#print "Initialised BYTE mode - "+str(pin)+
|
#print("Initialised BYTE mode - "+str(pin)+)
|
||||||
list = [7,11,13,12,15,16,18,22]
|
list = [7,11,13,12,15,16,18,22]
|
||||||
GPIO.setup(list,GPIO.OUT)
|
GPIO.setup(list,GPIO.OUT)
|
||||||
|
|
||||||
@ -152,7 +148,7 @@ if len(sys.argv) > 2:
|
|||||||
GPIO.output(list[bit], data & mask)
|
GPIO.output(list[bit], data & mask)
|
||||||
|
|
||||||
elif cmd == "borg":
|
elif cmd == "borg":
|
||||||
#print "Initialised BORG mode - "+str(pin)+
|
#print("Initialised BORG mode - "+str(pin)+)
|
||||||
GPIO.setup(11,GPIO.OUT)
|
GPIO.setup(11,GPIO.OUT)
|
||||||
GPIO.setup(13,GPIO.OUT)
|
GPIO.setup(13,GPIO.OUT)
|
||||||
GPIO.setup(15,GPIO.OUT)
|
GPIO.setup(15,GPIO.OUT)
|
||||||
@ -190,7 +186,7 @@ if len(sys.argv) > 2:
|
|||||||
button = ord( buf[0] ) & pin # mask out just the required button(s)
|
button = ord( buf[0] ) & pin # mask out just the required button(s)
|
||||||
if button != oldbutt: # only send if changed
|
if button != oldbutt: # only send if changed
|
||||||
oldbutt = button
|
oldbutt = button
|
||||||
print button
|
print(button)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
@ -215,7 +211,7 @@ if len(sys.argv) > 2:
|
|||||||
# type,code,value
|
# type,code,value
|
||||||
print("%u,%u" % (code, value))
|
print("%u,%u" % (code, value))
|
||||||
event = file.read(EVENT_SIZE)
|
event = file.read(EVENT_SIZE)
|
||||||
print "0,0"
|
print("0,0")
|
||||||
file.close()
|
file.close()
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
except:
|
except:
|
||||||
@ -225,14 +221,14 @@ if len(sys.argv) > 2:
|
|||||||
elif len(sys.argv) > 1:
|
elif len(sys.argv) > 1:
|
||||||
cmd = sys.argv[1].lower()
|
cmd = sys.argv[1].lower()
|
||||||
if cmd == "rev":
|
if cmd == "rev":
|
||||||
print GPIO.RPI_REVISION
|
print(GPIO.RPI_REVISION)
|
||||||
elif cmd == "ver":
|
elif cmd == "ver":
|
||||||
print GPIO.VERSION
|
print(GPIO.VERSION)
|
||||||
elif cmd == "info":
|
elif cmd == "info":
|
||||||
print GPIO.RPI_INFO
|
print(GPIO.RPI_INFO)
|
||||||
else:
|
else:
|
||||||
print "Bad parameters - in|out|pwm|buzz|byte|borg|mouse|kbd|ver|info {pin} {value|up|down}"
|
print("Bad parameters - in|out|pwm|buzz|byte|borg|mouse|kbd|ver|info {pin} {value|up|down}")
|
||||||
print " only ver (gpio version) and info (board information) accept no pin parameter."
|
print(" only ver (gpio version) and info (board information) accept no pin parameter.")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print "Bad parameters - in|out|pwm|buzz|byte|borg|mouse|kbd|ver|info {pin} {value|up|down}"
|
print("Bad parameters - in|out|pwm|buzz|byte|borg|mouse|kbd|ver|info {pin} {value|up|down}")
|
||||||
|
@ -99,11 +99,17 @@
|
|||||||
}
|
}
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
function prop2name(key) {
|
||||||
|
var result = RED.utils.parseContextKey(key);
|
||||||
|
return result.key;
|
||||||
|
}
|
||||||
function getValueLabel(t,v) {
|
function getValueLabel(t,v) {
|
||||||
if (t === 'str') {
|
if (t === 'str') {
|
||||||
return '"'+clipValueLength(v)+'"';
|
return '"'+clipValueLength(v)+'"';
|
||||||
} else if (t === 'msg' || t==='flow' || t==='global') {
|
} else if (t === 'msg') {
|
||||||
return t+"."+clipValueLength(v);
|
return t+"."+clipValueLength(v);
|
||||||
|
} else if (t === 'flow' || t === 'global') {
|
||||||
|
return t+"."+clipValueLength(prop2name(v));
|
||||||
}
|
}
|
||||||
return clipValueLength(v);
|
return clipValueLength(v);
|
||||||
}
|
}
|
||||||
|
@ -59,21 +59,157 @@ module.exports = function(RED) {
|
|||||||
'else': function(a) { return a === true; }
|
'else': function(a) { return a === true; }
|
||||||
};
|
};
|
||||||
|
|
||||||
var _max_kept_msgs_count = undefined;
|
var _maxKeptCount;
|
||||||
|
|
||||||
function max_kept_msgs_count(node) {
|
function getMaxKeptCount() {
|
||||||
if (_max_kept_msgs_count === undefined) {
|
if (_maxKeptCount === undefined) {
|
||||||
var name = "nodeMessageBufferMaxLength";
|
var name = "nodeMessageBufferMaxLength";
|
||||||
if (RED.settings.hasOwnProperty(name)) {
|
if (RED.settings.hasOwnProperty(name)) {
|
||||||
_max_kept_msgs_count = RED.settings[name];
|
_maxKeptCount = RED.settings[name];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
_max_kept_msgs_count = 0;
|
_maxKeptCount = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return _max_kept_msgs_count;
|
return _maxKeptCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getProperty(node,msg) {
|
||||||
|
return new Promise((resolve,reject) => {
|
||||||
|
if (node.propertyType === 'jsonata') {
|
||||||
|
RED.util.evaluateJSONataExpression(node.property,msg,(err,value) => {
|
||||||
|
if (err) {
|
||||||
|
reject(RED._("switch.errors.invalid-expr",{error:err.message}));
|
||||||
|
} else {
|
||||||
|
resolve(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
RED.util.evaluateNodeProperty(node.property,node.propertyType,node,msg,(err,value) => {
|
||||||
|
if (err) {
|
||||||
|
resolve(undefined);
|
||||||
|
} else {
|
||||||
|
resolve(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getV1(node,msg,rule,hasParts) {
|
||||||
|
return new Promise( (resolve,reject) => {
|
||||||
|
if (rule.vt === 'prev') {
|
||||||
|
resolve(node.previousValue);
|
||||||
|
} else if (rule.vt === 'jsonata') {
|
||||||
|
var exp = rule.v;
|
||||||
|
if (rule.t === 'jsonata_exp') {
|
||||||
|
if (hasParts) {
|
||||||
|
exp.assign("I", msg.parts.index);
|
||||||
|
exp.assign("N", msg.parts.count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RED.util.evaluateJSONataExpression(exp,msg,(err,value) => {
|
||||||
|
if (err) {
|
||||||
|
reject(RED._("switch.errors.invalid-expr",{error:err.message}));
|
||||||
|
} else {
|
||||||
|
resolve(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (rule.vt === 'json') {
|
||||||
|
resolve("json");
|
||||||
|
} else if (rule.vt === 'null') {
|
||||||
|
resolve("null");
|
||||||
|
} else {
|
||||||
|
RED.util.evaluateNodeProperty(rule.v,rule.vt,node,msg, function(err,value) {
|
||||||
|
if (err) {
|
||||||
|
resolve(undefined);
|
||||||
|
} else {
|
||||||
|
resolve(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getV2(node,msg,rule) {
|
||||||
|
return new Promise((resolve,reject) => {
|
||||||
|
var v2 = rule.v2;
|
||||||
|
if (rule.v2t === 'prev') {
|
||||||
|
resolve(node.previousValue);
|
||||||
|
} else if (rule.v2t === 'jsonata') {
|
||||||
|
RED.util.evaluateJSONataExpression(rule.v2,msg,(err,value) => {
|
||||||
|
if (err) {
|
||||||
|
reject(RED._("switch.errors.invalid-expr",{error:err.message}));
|
||||||
|
} else {
|
||||||
|
resolve(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (typeof v2 !== 'undefined') {
|
||||||
|
RED.util.evaluateNodeProperty(rule.v2,rule.v2t,node,msg, function(err,value) {
|
||||||
|
if (err) {
|
||||||
|
resolve(undefined);
|
||||||
|
} else {
|
||||||
|
resolve(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
resolve(v2);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyRule(node, msg, property, state) {
|
||||||
|
return new Promise((resolve,reject) => {
|
||||||
|
|
||||||
|
var rule = node.rules[state.currentRule];
|
||||||
|
var v1,v2;
|
||||||
|
|
||||||
|
getV1(node,msg,rule,state.hasParts).then(value => {
|
||||||
|
v1 = value;
|
||||||
|
}).then(()=>getV2(node,msg,rule)).then(value => {
|
||||||
|
v2 = value;
|
||||||
|
}).then(() => {
|
||||||
|
if (rule.t == "else") {
|
||||||
|
property = state.elseflag;
|
||||||
|
state.elseflag = true;
|
||||||
|
}
|
||||||
|
if (operators[rule.t](property,v1,v2,rule.case,msg.parts)) {
|
||||||
|
state.onward.push(msg);
|
||||||
|
state.elseflag = false;
|
||||||
|
if (node.checkall == "false") {
|
||||||
|
return resolve(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
state.onward.push(null);
|
||||||
|
}
|
||||||
|
resolve(state.currentRule < node.rules.length - 1);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyRules(node, msg, property,state) {
|
||||||
|
if (!state) {
|
||||||
|
state = {
|
||||||
|
currentRule: 0,
|
||||||
|
elseflag: true,
|
||||||
|
onward: [],
|
||||||
|
hasParts: msg.hasOwnProperty("parts") &&
|
||||||
|
msg.parts.hasOwnProperty("id") &&
|
||||||
|
msg.parts.hasOwnProperty("index")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return applyRule(node,msg,property,state).then(hasMore => {
|
||||||
|
if (hasMore) {
|
||||||
|
state.currentRule++;
|
||||||
|
return applyRules(node,msg,property,state);
|
||||||
|
} else {
|
||||||
|
node.previousValue = property;
|
||||||
|
return state.onward;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function SwitchNode(n) {
|
function SwitchNode(n) {
|
||||||
RED.nodes.createNode(this, n);
|
RED.nodes.createNode(this, n);
|
||||||
this.rules = n.rules || [];
|
this.rules = n.rules || [];
|
||||||
@ -94,10 +230,10 @@ module.exports = function(RED) {
|
|||||||
var node = this;
|
var node = this;
|
||||||
var valid = true;
|
var valid = true;
|
||||||
var repair = n.repair;
|
var repair = n.repair;
|
||||||
var needs_count = repair;
|
var needsCount = repair;
|
||||||
for (var i=0; i<this.rules.length; i+=1) {
|
for (var i=0; i<this.rules.length; i+=1) {
|
||||||
var rule = this.rules[i];
|
var rule = this.rules[i];
|
||||||
needs_count = needs_count || ((rule.t === "tail") || (rule.t === "jsonata_exp"));
|
needsCount = needsCount || ((rule.t === "tail") || (rule.t === "jsonata_exp"));
|
||||||
if (!rule.vt) {
|
if (!rule.vt) {
|
||||||
if (!isNaN(Number(rule.v))) {
|
if (!isNaN(Number(rule.v))) {
|
||||||
rule.vt = 'num';
|
rule.vt = 'num';
|
||||||
@ -142,26 +278,26 @@ module.exports = function(RED) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var pending_count = 0;
|
var pendingCount = 0;
|
||||||
var pending_id = 0;
|
var pendingId = 0;
|
||||||
var pending_in = {};
|
var pendingIn = {};
|
||||||
var pending_out = {};
|
var pendingOut = {};
|
||||||
var received = {};
|
var received = {};
|
||||||
|
|
||||||
function add2group_in(id, msg, parts) {
|
function addMessageToGroup(id, msg, parts) {
|
||||||
if (!(id in pending_in)) {
|
if (!(id in pendingIn)) {
|
||||||
pending_in[id] = {
|
pendingIn[id] = {
|
||||||
count: undefined,
|
count: undefined,
|
||||||
msgs: [],
|
msgs: [],
|
||||||
seq_no: pending_id++
|
seq_no: pendingId++
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
var group = pending_in[id];
|
var group = pendingIn[id];
|
||||||
group.msgs.push(msg);
|
group.msgs.push(msg);
|
||||||
pending_count++;
|
pendingCount++;
|
||||||
var max_msgs = max_kept_msgs_count(node);
|
var max_msgs = getMaxKeptCount();
|
||||||
if ((max_msgs > 0) && (pending_count > max_msgs)) {
|
if ((max_msgs > 0) && (pendingCount > max_msgs)) {
|
||||||
clear_pending();
|
clearPending();
|
||||||
node.error(RED._("switch.errors.too-many"), msg);
|
node.error(RED._("switch.errors.too-many"), msg);
|
||||||
}
|
}
|
||||||
if (parts.hasOwnProperty("count")) {
|
if (parts.hasOwnProperty("count")) {
|
||||||
@ -170,32 +306,29 @@ module.exports = function(RED) {
|
|||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
function del_group_in(id, group) {
|
|
||||||
pending_count -= group.msgs.length;
|
|
||||||
delete pending_in[id];
|
|
||||||
}
|
|
||||||
|
|
||||||
function add2pending_in(msg) {
|
function addMessageToPending(msg) {
|
||||||
var parts = msg.parts;
|
var parts = msg.parts;
|
||||||
if (parts.hasOwnProperty("id") &&
|
// We've already checked the msg.parts has the require bits
|
||||||
parts.hasOwnProperty("index")) {
|
var group = addMessageToGroup(parts.id, msg, parts);
|
||||||
var group = add2group_in(parts.id, msg, parts);
|
var msgs = group.msgs;
|
||||||
var msgs = group.msgs;
|
var count = group.count;
|
||||||
var count = group.count;
|
if (count === msgs.length) {
|
||||||
if (count === msgs.length) {
|
// We have a complete group - send the individual parts
|
||||||
for (var i = 0; i < msgs.length; i++) {
|
return msgs.reduce((promise, msg) => {
|
||||||
var msg = msgs[i];
|
return promise.then((result) => {
|
||||||
msg.parts.count = count;
|
msg.parts.count = count;
|
||||||
process_msg(msg, false);
|
return processMessage(msg, false);
|
||||||
}
|
})
|
||||||
del_group_in(parts.id, group);
|
}, Promise.resolve()).then( () => {
|
||||||
}
|
pendingCount -= group.msgs.length;
|
||||||
return true;
|
delete pendingIn[parts.id];
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return false;
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
function send_group(onwards, port_count) {
|
function sendGroup(onwards, port_count) {
|
||||||
var counts = new Array(port_count).fill(0);
|
var counts = new Array(port_count).fill(0);
|
||||||
for (var i = 0; i < onwards.length; i++) {
|
for (var i = 0; i < onwards.length; i++) {
|
||||||
var onward = onwards[i];
|
var onward = onwards[i];
|
||||||
@ -230,141 +363,104 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function send2ports(onward, msg) {
|
function sendGroupMessages(onward, msg) {
|
||||||
var parts = msg.parts;
|
var parts = msg.parts;
|
||||||
var gid = parts.id;
|
var gid = parts.id;
|
||||||
received[gid] = ((gid in received) ? received[gid] : 0) +1;
|
received[gid] = ((gid in received) ? received[gid] : 0) +1;
|
||||||
var send_ok = (received[gid] === parts.count);
|
var send_ok = (received[gid] === parts.count);
|
||||||
|
|
||||||
if (!(gid in pending_out)) {
|
if (!(gid in pendingOut)) {
|
||||||
pending_out[gid] = {
|
pendingOut[gid] = {
|
||||||
onwards: []
|
onwards: []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
var group = pending_out[gid];
|
var group = pendingOut[gid];
|
||||||
var onwards = group.onwards;
|
var onwards = group.onwards;
|
||||||
onwards.push(onward);
|
onwards.push(onward);
|
||||||
pending_count++;
|
pendingCount++;
|
||||||
if (send_ok) {
|
if (send_ok) {
|
||||||
send_group(onwards, onward.length, msg);
|
sendGroup(onwards, onward.length, msg);
|
||||||
pending_count -= onward.length;
|
pendingCount -= onward.length;
|
||||||
delete pending_out[gid];
|
delete pendingOut[gid];
|
||||||
delete received[gid];
|
delete received[gid];
|
||||||
}
|
}
|
||||||
var max_msgs = max_kept_msgs_count(node);
|
var max_msgs = getMaxKeptCount();
|
||||||
if ((max_msgs > 0) && (pending_count > max_msgs)) {
|
if ((max_msgs > 0) && (pendingCount > max_msgs)) {
|
||||||
clear_pending();
|
clearPending();
|
||||||
node.error(RED._("switch.errors.too-many"), msg);
|
node.error(RED._("switch.errors.too-many"), msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function msg_has_parts(msg) {
|
|
||||||
if (msg.hasOwnProperty("parts")) {
|
|
||||||
var parts = msg.parts;
|
|
||||||
return (parts.hasOwnProperty("id") &&
|
|
||||||
parts.hasOwnProperty("index"));
|
function processMessage(msg, checkParts) {
|
||||||
|
var hasParts = msg.hasOwnProperty("parts") &&
|
||||||
|
msg.parts.hasOwnProperty("id") &&
|
||||||
|
msg.parts.hasOwnProperty("index");
|
||||||
|
|
||||||
|
if (needsCount && checkParts && hasParts) {
|
||||||
|
return addMessageToPending(msg);
|
||||||
}
|
}
|
||||||
return false;
|
return getProperty(node,msg)
|
||||||
|
.then(property => applyRules(node,msg,property))
|
||||||
|
.then(onward => {
|
||||||
|
if (!repair || !hasParts) {
|
||||||
|
node.send(onward);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
sendGroupMessages(onward, msg);
|
||||||
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
node.warn(err);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function process_msg(msg, check_parts) {
|
function clearPending() {
|
||||||
var has_parts = msg_has_parts(msg);
|
pendingCount = 0;
|
||||||
if (needs_count && check_parts && has_parts &&
|
pendingId = 0;
|
||||||
add2pending_in(msg)) {
|
pendingIn = {};
|
||||||
return;
|
pendingOut = {};
|
||||||
}
|
|
||||||
var onward = [];
|
|
||||||
try {
|
|
||||||
var prop;
|
|
||||||
if (node.propertyType === 'jsonata') {
|
|
||||||
prop = RED.util.evaluateJSONataExpression(node.property,msg);
|
|
||||||
} else {
|
|
||||||
prop = RED.util.evaluateNodeProperty(node.property,node.propertyType,node,msg);
|
|
||||||
}
|
|
||||||
var elseflag = true;
|
|
||||||
for (var i=0; i<node.rules.length; i+=1) {
|
|
||||||
var rule = node.rules[i];
|
|
||||||
var test = prop;
|
|
||||||
var v1,v2;
|
|
||||||
if (rule.vt === 'prev') {
|
|
||||||
v1 = node.previousValue;
|
|
||||||
} else if (rule.vt === 'jsonata') {
|
|
||||||
try {
|
|
||||||
var exp = rule.v;
|
|
||||||
if (rule.t === 'jsonata_exp') {
|
|
||||||
if (has_parts) {
|
|
||||||
exp.assign("I", msg.parts.index);
|
|
||||||
exp.assign("N", msg.parts.count);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
v1 = RED.util.evaluateJSONataExpression(exp,msg);
|
|
||||||
} catch(err) {
|
|
||||||
node.error(RED._("switch.errors.invalid-expr",{error:err.message}));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (rule.vt === 'json') {
|
|
||||||
v1 = "json";
|
|
||||||
} else if (rule.vt === 'null') {
|
|
||||||
v1 = "null";
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
v1 = RED.util.evaluateNodeProperty(rule.v,rule.vt,node,msg);
|
|
||||||
} catch(err) {
|
|
||||||
v1 = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
v2 = rule.v2;
|
|
||||||
if (rule.v2t === 'prev') {
|
|
||||||
v2 = node.previousValue;
|
|
||||||
} else if (rule.v2t === 'jsonata') {
|
|
||||||
try {
|
|
||||||
v2 = RED.util.evaluateJSONataExpression(rule.v2,msg);
|
|
||||||
} catch(err) {
|
|
||||||
node.error(RED._("switch.errors.invalid-expr",{error:err.message}));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (typeof v2 !== 'undefined') {
|
|
||||||
try {
|
|
||||||
v2 = RED.util.evaluateNodeProperty(rule.v2,rule.v2t,node,msg);
|
|
||||||
} catch(err) {
|
|
||||||
v2 = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (rule.t == "else") { test = elseflag; elseflag = true; }
|
|
||||||
if (operators[rule.t](test,v1,v2,rule.case,msg.parts)) {
|
|
||||||
onward.push(msg);
|
|
||||||
elseflag = false;
|
|
||||||
if (node.checkall == "false") { break; }
|
|
||||||
} else {
|
|
||||||
onward.push(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
node.previousValue = prop;
|
|
||||||
if (!repair || !has_parts) {
|
|
||||||
node.send(onward);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
send2ports(onward, msg);
|
|
||||||
}
|
|
||||||
} catch(err) {
|
|
||||||
node.warn(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function clear_pending() {
|
|
||||||
pending_count = 0;
|
|
||||||
pending_id = 0;
|
|
||||||
pending_in = {};
|
|
||||||
pending_out = {};
|
|
||||||
received = {};
|
received = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var pendingMessages = [];
|
||||||
|
var activeMessagePromise = null;
|
||||||
|
var processMessageQueue = function(msg) {
|
||||||
|
if (msg) {
|
||||||
|
// A new message has arrived - add it to the message queue
|
||||||
|
pendingMessages.push(msg);
|
||||||
|
if (activeMessagePromise !== null) {
|
||||||
|
// The node is currently processing a message, so do nothing
|
||||||
|
// more with this message
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pendingMessages.length === 0) {
|
||||||
|
// There are no more messages to process, clear the active flag
|
||||||
|
// and return
|
||||||
|
activeMessagePromise = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// There are more messages to process. Get the next message and
|
||||||
|
// start processing it. Recurse back in to check for any more
|
||||||
|
var nextMsg = pendingMessages.shift();
|
||||||
|
activeMessagePromise = processMessage(nextMsg,true)
|
||||||
|
.then(processMessageQueue)
|
||||||
|
.catch((err) => {
|
||||||
|
node.error(err,nextMsg);
|
||||||
|
return processMessageQueue();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.on('input', function(msg) {
|
this.on('input', function(msg) {
|
||||||
process_msg(msg, true);
|
processMessageQueue(msg);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.on('close', function() {
|
this.on('close', function() {
|
||||||
clear_pending();
|
clearPending();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,6 +54,10 @@
|
|||||||
outputs: 1,
|
outputs: 1,
|
||||||
icon: "swap.png",
|
icon: "swap.png",
|
||||||
label: function() {
|
label: function() {
|
||||||
|
function prop2name(type, key) {
|
||||||
|
var result = RED.utils.parseContextKey(key);
|
||||||
|
return type +"." +result.key;
|
||||||
|
}
|
||||||
if (this.name) {
|
if (this.name) {
|
||||||
return this.name;
|
return this.name;
|
||||||
}
|
}
|
||||||
@ -70,13 +74,13 @@
|
|||||||
} else {
|
} else {
|
||||||
if (this.rules.length == 1) {
|
if (this.rules.length == 1) {
|
||||||
if (this.rules[0].t === "set") {
|
if (this.rules[0].t === "set") {
|
||||||
return this._("change.label.set",{property:(this.rules[0].pt||"msg")+"."+this.rules[0].p});
|
return this._("change.label.set",{property:prop2name((this.rules[0].pt||"msg"), this.rules[0].p)});
|
||||||
} else if (this.rules[0].t === "change") {
|
} else if (this.rules[0].t === "change") {
|
||||||
return this._("change.label.change",{property:(this.rules[0].pt||"msg")+"."+this.rules[0].p});
|
return this._("change.label.change",{property:prop2name((this.rules[0].pt||"msg"), this.rules[0].p)});
|
||||||
} else if (this.rules[0].t === "move") {
|
} else if (this.rules[0].t === "move") {
|
||||||
return this._("change.label.move",{property:(this.rules[0].pt||"msg")+"."+this.rules[0].p});
|
return this._("change.label.move",{property:prop2name((this.rules[0].pt||"msg"), this.rules[0].p)});
|
||||||
} else {
|
} else {
|
||||||
return this._("change.label.delete",{property:(this.rules[0].pt||"msg")+"."+this.rules[0].p});
|
return this._("change.label.delete",{property:prop2name((this.rules[0].pt||"msg"), this.rules[0].p)});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return this._("change.label.changeCount",{count:this.rules.length});
|
return this._("change.label.changeCount",{count:this.rules.length});
|
||||||
|
@ -98,44 +98,53 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyRule(msg,rule) {
|
function getToValue(msg,rule) {
|
||||||
try {
|
var value = rule.to;
|
||||||
var property = rule.p;
|
if (rule.tot === 'json') {
|
||||||
var value = rule.to;
|
value = JSON.parse(rule.to);
|
||||||
if (rule.tot === 'json') {
|
} else if (rule.tot === 'bin') {
|
||||||
value = JSON.parse(rule.to);
|
value = Buffer.from(JSON.parse(rule.to))
|
||||||
} else if (rule.tot === 'bin') {
|
}
|
||||||
value = Buffer.from(JSON.parse(rule.to))
|
if (rule.tot === "msg") {
|
||||||
}
|
value = RED.util.getMessageProperty(msg,rule.to);
|
||||||
var current;
|
} else if (rule.tot === 'flow') {
|
||||||
var fromValue;
|
value = node.context().flow.get(rule.to);
|
||||||
var fromType;
|
} else if (rule.tot === 'global') {
|
||||||
var fromRE;
|
value = node.context().global.get(rule.to);
|
||||||
if (rule.tot === "msg") {
|
} else if (rule.tot === 'date') {
|
||||||
value = RED.util.getMessageProperty(msg,rule.to);
|
value = Date.now();
|
||||||
} else if (rule.tot === 'flow') {
|
} else if (rule.tot === 'jsonata') {
|
||||||
value = node.context().flow.get(rule.to);
|
return new Promise((resolve,reject) => {
|
||||||
} else if (rule.tot === 'global') {
|
RED.util.evaluateJSONataExpression(rule.to,msg, (err, value) => {
|
||||||
value = node.context().global.get(rule.to);
|
if (err) {
|
||||||
} else if (rule.tot === 'date') {
|
reject(RED._("change.errors.invalid-expr",{error:err.message}))
|
||||||
value = Date.now();
|
} else {
|
||||||
} else if (rule.tot === 'jsonata') {
|
resolve(value);
|
||||||
try{
|
|
||||||
value = RED.util.evaluateJSONataExpression(rule.to,msg);
|
|
||||||
} catch(err) {
|
|
||||||
node.error(RED._("change.errors.invalid-expr",{error:err.message}),msg);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (rule.t === 'change') {
|
|
||||||
if (rule.fromt === 'msg' || rule.fromt === 'flow' || rule.fromt === 'global') {
|
|
||||||
if (rule.fromt === "msg") {
|
|
||||||
fromValue = RED.util.getMessageProperty(msg,rule.from);
|
|
||||||
} else if (rule.fromt === 'flow') {
|
|
||||||
fromValue = node.context().flow.get(rule.from);
|
|
||||||
} else if (rule.fromt === 'global') {
|
|
||||||
fromValue = node.context().global.get(rule.from);
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.resolve(value);
|
||||||
|
}
|
||||||
|
function getFromValue(msg,rule) {
|
||||||
|
var fromValue;
|
||||||
|
var fromType;
|
||||||
|
var fromRE;
|
||||||
|
if (rule.t === 'change') {
|
||||||
|
if (rule.fromt === 'msg' || rule.fromt === 'flow' || rule.fromt === 'global') {
|
||||||
|
return new Promise((resolve,reject) => {
|
||||||
|
if (rule.fromt === "msg") {
|
||||||
|
resolve(RED.util.getMessageProperty(msg,rule.from));
|
||||||
|
} else if (rule.fromt === 'flow' || rule.fromt === 'global') {
|
||||||
|
node.context()[rule.fromt].get(rule.from,(err,fromValue) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(fromValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).then(fromValue => {
|
||||||
if (typeof fromValue === 'number' || fromValue instanceof Number) {
|
if (typeof fromValue === 'number' || fromValue instanceof Number) {
|
||||||
fromType = 'num';
|
fromType = 'num';
|
||||||
} else if (typeof fromValue === 'boolean') {
|
} else if (typeof fromValue === 'boolean') {
|
||||||
@ -149,108 +158,161 @@ module.exports = function(RED) {
|
|||||||
try {
|
try {
|
||||||
fromRE = new RegExp(fromRE, "g");
|
fromRE = new RegExp(fromRE, "g");
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
valid = false;
|
reject(new Error(RED._("change.errors.invalid-from",{error:e.message})));
|
||||||
node.error(RED._("change.errors.invalid-from",{error:e.message}),msg);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
node.error(RED._("change.errors.invalid-from",{error:"unsupported type: "+(typeof fromValue)}),msg);
|
reject(new Error(RED._("change.errors.invalid-from",{error:"unsupported type: "+(typeof fromValue)})));
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
return {
|
||||||
|
fromType,
|
||||||
|
fromValue,
|
||||||
|
fromRE
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
fromType = rule.fromt;
|
||||||
|
fromValue = rule.from;
|
||||||
|
fromRE = rule.fromRE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Promise.resolve({
|
||||||
|
fromType,
|
||||||
|
fromValue,
|
||||||
|
fromRE
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function applyRule(msg,rule) {
|
||||||
|
var property = rule.p;
|
||||||
|
var current;
|
||||||
|
var fromValue;
|
||||||
|
var fromType;
|
||||||
|
var fromRE;
|
||||||
|
try {
|
||||||
|
return getToValue(msg,rule).then(value => {
|
||||||
|
return getFromValue(msg,rule).then(fromParts => {
|
||||||
|
fromValue = fromParts.fromValue;
|
||||||
|
fromType = fromParts.fromType;
|
||||||
|
fromRE = fromParts.fromRE;
|
||||||
|
if (rule.pt === 'msg') {
|
||||||
|
try {
|
||||||
|
if (rule.t === 'delete') {
|
||||||
|
RED.util.setMessageProperty(msg,property,undefined);
|
||||||
|
} else if (rule.t === 'set') {
|
||||||
|
RED.util.setMessageProperty(msg,property,value);
|
||||||
|
} else if (rule.t === 'change') {
|
||||||
|
current = RED.util.getMessageProperty(msg,property);
|
||||||
|
if (typeof current === 'string') {
|
||||||
|
if ((fromType === 'num' || fromType === 'bool' || fromType === 'str') && current === fromValue) {
|
||||||
|
// str representation of exact from number/boolean
|
||||||
|
// only replace if they match exactly
|
||||||
|
RED.util.setMessageProperty(msg,property,value);
|
||||||
|
} else {
|
||||||
|
current = current.replace(fromRE,value);
|
||||||
|
RED.util.setMessageProperty(msg,property,current);
|
||||||
|
}
|
||||||
|
} else if ((typeof current === 'number' || current instanceof Number) && fromType === 'num') {
|
||||||
|
if (current == Number(fromValue)) {
|
||||||
|
RED.util.setMessageProperty(msg,property,value);
|
||||||
|
}
|
||||||
|
} else if (typeof current === 'boolean' && fromType === 'bool') {
|
||||||
|
if (current.toString() === fromValue) {
|
||||||
|
RED.util.setMessageProperty(msg,property,value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(err) {}
|
||||||
|
return msg;
|
||||||
|
} else if (rule.pt === 'flow' || rule.pt === 'global') {
|
||||||
|
return new Promise((resolve,reject) => {
|
||||||
|
var target = node.context()[rule.pt];
|
||||||
|
var callback = err => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rule.t === 'delete') {
|
||||||
|
target.set(property,undefined,callback);
|
||||||
|
} else if (rule.t === 'set') {
|
||||||
|
target.set(property,value,callback);
|
||||||
|
} else if (rule.t === 'change') {
|
||||||
|
target.get(property,(err,current) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (typeof current === 'string') {
|
||||||
|
if ((fromType === 'num' || fromType === 'bool' || fromType === 'str') && current === fromValue) {
|
||||||
|
// str representation of exact from number/boolean
|
||||||
|
// only replace if they match exactly
|
||||||
|
target.set(property,value,callback);
|
||||||
|
} else {
|
||||||
|
current = current.replace(fromRE,value);
|
||||||
|
target.set(property,current,callback);
|
||||||
|
}
|
||||||
|
} else if ((typeof current === 'number' || current instanceof Number) && fromType === 'num') {
|
||||||
|
if (current == Number(fromValue)) {
|
||||||
|
target.set(property,value,callback);
|
||||||
|
}
|
||||||
|
} else if (typeof current === 'boolean' && fromType === 'bool') {
|
||||||
|
if (current.toString() === fromValue) {
|
||||||
|
target.set(property,value,callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).catch(err => {
|
||||||
|
node.error(err, msg);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
} catch(err) {
|
||||||
|
return Promise.resolve(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function applyRules(msg, currentRule) {
|
||||||
|
var r = node.rules[currentRule];
|
||||||
|
var rulePromise;
|
||||||
|
if (r.t === "move") {
|
||||||
|
if ((r.tot !== r.pt) || (r.p.indexOf(r.to) !== -1)) {
|
||||||
|
rulePromise = applyRule(msg,{t:"set", p:r.to, pt:r.tot, to:r.p, tot:r.pt}).then(
|
||||||
|
msg => applyRule(msg,{t:"delete", p:r.p, pt:r.pt})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else { // 2 step move if we are moving from a child
|
||||||
|
rulePromise = applyRule(msg,{t:"set", p:"_temp_move", pt:r.tot, to:r.p, tot:r.pt}).then(
|
||||||
|
msg => applyRule(msg,{t:"delete", p:r.p, pt:r.pt})
|
||||||
|
).then(
|
||||||
|
msg => applyRule(msg,{t:"set", p:r.to, pt:r.tot, to:"_temp_move", tot:r.pt})
|
||||||
|
).then(
|
||||||
|
msg => applyRule(msg,{t:"delete", p:"_temp_move", pt:r.pt})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rulePromise = applyRule(msg,r);
|
||||||
|
}
|
||||||
|
return rulePromise.then(
|
||||||
|
msg => {
|
||||||
|
if (!msg) {
|
||||||
|
return
|
||||||
|
} else if (currentRule === node.rules.length - 1) {
|
||||||
|
return msg;
|
||||||
} else {
|
} else {
|
||||||
fromType = rule.fromt;
|
return applyRules(msg, currentRule+1);
|
||||||
fromValue = rule.from;
|
|
||||||
fromRE = rule.fromRE;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (rule.pt === 'msg') {
|
);
|
||||||
if (rule.t === 'delete') {
|
|
||||||
RED.util.setMessageProperty(msg,property,undefined);
|
|
||||||
} else if (rule.t === 'set') {
|
|
||||||
RED.util.setMessageProperty(msg,property,value);
|
|
||||||
} else if (rule.t === 'change') {
|
|
||||||
current = RED.util.getMessageProperty(msg,property);
|
|
||||||
if (typeof current === 'string') {
|
|
||||||
if ((fromType === 'num' || fromType === 'bool' || fromType === 'str') && current === fromValue) {
|
|
||||||
// str representation of exact from number/boolean
|
|
||||||
// only replace if they match exactly
|
|
||||||
RED.util.setMessageProperty(msg,property,value);
|
|
||||||
} else {
|
|
||||||
current = current.replace(fromRE,value);
|
|
||||||
RED.util.setMessageProperty(msg,property,current);
|
|
||||||
}
|
|
||||||
} else if ((typeof current === 'number' || current instanceof Number) && fromType === 'num') {
|
|
||||||
if (current == Number(fromValue)) {
|
|
||||||
RED.util.setMessageProperty(msg,property,value);
|
|
||||||
}
|
|
||||||
} else if (typeof current === 'boolean' && fromType === 'bool') {
|
|
||||||
if (current.toString() === fromValue) {
|
|
||||||
RED.util.setMessageProperty(msg,property,value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
var target;
|
|
||||||
if (rule.pt === 'flow') {
|
|
||||||
target = node.context().flow;
|
|
||||||
} else if (rule.pt === 'global') {
|
|
||||||
target = node.context().global;
|
|
||||||
}
|
|
||||||
if (target) {
|
|
||||||
if (rule.t === 'delete') {
|
|
||||||
target.set(property,undefined);
|
|
||||||
} else if (rule.t === 'set') {
|
|
||||||
target.set(property,value);
|
|
||||||
} else if (rule.t === 'change') {
|
|
||||||
current = target.get(property);
|
|
||||||
if (typeof current === 'string') {
|
|
||||||
if ((fromType === 'num' || fromType === 'bool' || fromType === 'str') && current === fromValue) {
|
|
||||||
// str representation of exact from number/boolean
|
|
||||||
// only replace if they match exactly
|
|
||||||
target.set(property,value);
|
|
||||||
} else {
|
|
||||||
current = current.replace(fromRE,value);
|
|
||||||
target.set(property,current);
|
|
||||||
}
|
|
||||||
} else if ((typeof current === 'number' || current instanceof Number) && fromType === 'num') {
|
|
||||||
if (current == Number(fromValue)) {
|
|
||||||
target.set(property,value);
|
|
||||||
}
|
|
||||||
} else if (typeof current === 'boolean' && fromType === 'bool') {
|
|
||||||
if (current.toString() === fromValue) {
|
|
||||||
target.set(property,value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch(err) {/*console.log(err.stack)*/}
|
|
||||||
return msg;
|
|
||||||
}
|
}
|
||||||
if (valid) {
|
if (valid) {
|
||||||
this.on('input', function(msg) {
|
this.on('input', function(msg) {
|
||||||
for (var i=0; i<this.rules.length; i++) {
|
applyRules(msg, 0)
|
||||||
if (this.rules[i].t === "move") {
|
.then( msg => { if (msg) { node.send(msg) }} )
|
||||||
var r = this.rules[i];
|
.catch( err => node.error(err, msg))
|
||||||
if ((r.tot !== r.pt) || (r.p.indexOf(r.to) !== -1)) {
|
|
||||||
msg = applyRule(msg,{t:"set", p:r.to, pt:r.tot, to:r.p, tot:r.pt});
|
|
||||||
applyRule(msg,{t:"delete", p:r.p, pt:r.pt});
|
|
||||||
}
|
|
||||||
else { // 2 step move if we are moving from a child
|
|
||||||
msg = applyRule(msg,{t:"set", p:"_temp_move", pt:r.tot, to:r.p, tot:r.pt});
|
|
||||||
applyRule(msg,{t:"delete", p:r.p, pt:r.pt});
|
|
||||||
msg = applyRule(msg,{t:"set", p:r.to, pt:r.tot, to:"_temp_move", tot:r.pt});
|
|
||||||
applyRule(msg,{t:"delete", p:"_temp_move", pt:r.pt});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
msg = applyRule(msg,this.rules[i]);
|
|
||||||
}
|
|
||||||
if (msg === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
node.send(msg);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -233,7 +233,7 @@ module.exports = function(RED) {
|
|||||||
RED.nodes.registerType("split",SplitNode);
|
RED.nodes.registerType("split",SplitNode);
|
||||||
|
|
||||||
|
|
||||||
var _max_kept_msgs_count = undefined;
|
var _max_kept_msgs_count;
|
||||||
|
|
||||||
function max_kept_msgs_count(node) {
|
function max_kept_msgs_count(node) {
|
||||||
if (_max_kept_msgs_count === undefined) {
|
if (_max_kept_msgs_count === undefined) {
|
||||||
@ -252,7 +252,15 @@ module.exports = function(RED) {
|
|||||||
exp.assign("I", index);
|
exp.assign("I", index);
|
||||||
exp.assign("N", count);
|
exp.assign("N", count);
|
||||||
exp.assign("A", accum);
|
exp.assign("A", accum);
|
||||||
return RED.util.evaluateJSONataExpression(exp, msg);
|
return new Promise((resolve,reject) => {
|
||||||
|
RED.util.evaluateJSONataExpression(exp, msg, (err, result) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function apply_f(exp, accum, count) {
|
function apply_f(exp, accum, count) {
|
||||||
@ -269,32 +277,37 @@ module.exports = function(RED) {
|
|||||||
return exp
|
return exp
|
||||||
}
|
}
|
||||||
|
|
||||||
function reduce_and_send_group(node, group) {
|
function reduceAndSendGroup(node, group) {
|
||||||
var is_right = node.reduce_right;
|
var is_right = node.reduce_right;
|
||||||
var flag = is_right ? -1 : 1;
|
var flag = is_right ? -1 : 1;
|
||||||
var msgs = group.msgs;
|
var msgs = group.msgs;
|
||||||
var accum = eval_exp(node, node.exp_init, node.exp_init_type);
|
return getInitialReduceValue(node, node.exp_init, node.exp_init_type).then(accum => {
|
||||||
var reduce_exp = node.reduce_exp;
|
var reduce_exp = node.reduce_exp;
|
||||||
var reduce_fixup = node.reduce_fixup;
|
var reduce_fixup = node.reduce_fixup;
|
||||||
var count = group.count;
|
var count = group.count;
|
||||||
msgs.sort(function(x,y) {
|
msgs.sort(function(x,y) {
|
||||||
var ix = x.parts.index;
|
var ix = x.parts.index;
|
||||||
var iy = y.parts.index;
|
var iy = y.parts.index;
|
||||||
if (ix < iy) return -flag;
|
if (ix < iy) {return -flag;}
|
||||||
if (ix > iy) return flag;
|
if (ix > iy) {return flag;}
|
||||||
return 0;
|
return 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
return msgs.reduce((promise, msg) => promise.then(accum => apply_r(reduce_exp, accum, msg, msg.parts.index, count)), Promise.resolve(accum))
|
||||||
|
.then(accum => {
|
||||||
|
if(reduce_fixup !== undefined) {
|
||||||
|
accum = apply_f(reduce_fixup, accum, count);
|
||||||
|
}
|
||||||
|
node.send({payload: accum});
|
||||||
|
});
|
||||||
|
}).catch(err => {
|
||||||
|
throw new Error(RED._("join.errors.invalid-expr",{error:e.message}));
|
||||||
});
|
});
|
||||||
for(var msg of msgs) {
|
|
||||||
accum = apply_r(reduce_exp, accum, msg, msg.parts.index, count);
|
|
||||||
}
|
|
||||||
if(reduce_fixup !== undefined) {
|
|
||||||
accum = apply_f(reduce_fixup, accum, count);
|
|
||||||
}
|
|
||||||
node.send({payload: accum});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function reduce_msg(node, msg) {
|
function reduce_msg(node, msg) {
|
||||||
if(msg.hasOwnProperty('parts')) {
|
var promise;
|
||||||
|
if (msg.hasOwnProperty('parts')) {
|
||||||
var parts = msg.parts;
|
var parts = msg.parts;
|
||||||
var pending = node.pending;
|
var pending = node.pending;
|
||||||
var pending_count = node.pending_count;
|
var pending_count = node.pending_count;
|
||||||
@ -312,65 +325,82 @@ module.exports = function(RED) {
|
|||||||
var group = pending[gid];
|
var group = pending[gid];
|
||||||
var msgs = group.msgs;
|
var msgs = group.msgs;
|
||||||
if(parts.hasOwnProperty('count') &&
|
if(parts.hasOwnProperty('count') &&
|
||||||
(group.count === undefined)) {
|
(group.count === undefined)) {
|
||||||
group.count = count;
|
group.count = count;
|
||||||
}
|
}
|
||||||
msgs.push(msg);
|
msgs.push(msg);
|
||||||
pending_count++;
|
pending_count++;
|
||||||
|
var completeProcess = function() {
|
||||||
|
node.pending_count = pending_count;
|
||||||
|
var max_msgs = max_kept_msgs_count(node);
|
||||||
|
if ((max_msgs > 0) && (pending_count > max_msgs)) {
|
||||||
|
node.pending = {};
|
||||||
|
node.pending_count = 0;
|
||||||
|
var promise = Promise.reject(RED._("join.too-many"));
|
||||||
|
promise.catch(()=>{});
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
if(msgs.length === group.count) {
|
if(msgs.length === group.count) {
|
||||||
delete pending[gid];
|
delete pending[gid];
|
||||||
try {
|
pending_count -= msgs.length;
|
||||||
pending_count -= msgs.length;
|
promise = reduceAndSendGroup(node, group).then(completeProcess);
|
||||||
reduce_and_send_group(node, group);
|
} else {
|
||||||
} catch(e) {
|
promise = completeProcess();
|
||||||
node.error(RED._("join.errors.invalid-expr",{error:e.message})); }
|
|
||||||
}
|
}
|
||||||
node.pending_count = pending_count;
|
} else {
|
||||||
var max_msgs = max_kept_msgs_count(node);
|
|
||||||
if ((max_msgs > 0) && (pending_count > max_msgs)) {
|
|
||||||
node.pending = {};
|
|
||||||
node.pending_count = 0;
|
|
||||||
node.error(RED._("join.too-many"), msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
node.send(msg);
|
node.send(msg);
|
||||||
}
|
}
|
||||||
|
if (!promise) {
|
||||||
|
promise = Promise.resolve();
|
||||||
|
}
|
||||||
|
return promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
function eval_exp(node, exp, exp_type) {
|
function getInitialReduceValue(node, exp, exp_type) {
|
||||||
if(exp_type === "flow") {
|
return new Promise((resolve,reject) => {
|
||||||
return node.context().flow.get(exp);
|
if(exp_type === "flow" || exp_type === "global") {
|
||||||
}
|
node.context()[exp_type].get(exp,(err,value) => {
|
||||||
else if(exp_type === "global") {
|
if (err) {
|
||||||
return node.context().global.get(exp);
|
reject(err);
|
||||||
}
|
} else {
|
||||||
else if(exp_type === "str") {
|
resolve(value);
|
||||||
return exp;
|
}
|
||||||
}
|
});
|
||||||
else if(exp_type === "num") {
|
return;
|
||||||
return Number(exp);
|
} else if(exp_type === "jsonata") {
|
||||||
}
|
var jexp = RED.util.prepareJSONataExpression(exp, node);
|
||||||
else if(exp_type === "bool") {
|
RED.util.evaluateJSONataExpression(jexp, {},(err,value) => {
|
||||||
if (exp === 'true') {
|
if (err) {
|
||||||
return true;
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else if (exp === 'false') {
|
var result;
|
||||||
return false;
|
if(exp_type === "str") {
|
||||||
|
result = exp;
|
||||||
|
} else if(exp_type === "num") {
|
||||||
|
result = Number(exp);
|
||||||
|
} else if(exp_type === "bool") {
|
||||||
|
if (exp === 'true') {
|
||||||
|
result = true;
|
||||||
|
} else if (exp === 'false') {
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
} else if ((exp_type === "bin") || (exp_type === "json")) {
|
||||||
|
result = JSON.parse(exp);
|
||||||
|
} else if(exp_type === "date") {
|
||||||
|
result = Date.now();
|
||||||
|
} else {
|
||||||
|
reject(new Error("unexpected initial value type"));
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
resolve(result);
|
||||||
else if ((exp_type === "bin") ||
|
});
|
||||||
(exp_type === "json")) {
|
|
||||||
return JSON.parse(exp);
|
|
||||||
}
|
|
||||||
else if(exp_type === "date") {
|
|
||||||
return Date.now();
|
|
||||||
}
|
|
||||||
else if(exp_type === "jsonata") {
|
|
||||||
var jexp = RED.util.prepareJSONataExpression(exp, node);
|
|
||||||
return RED.util.evaluateJSONataExpression(jexp, {});
|
|
||||||
}
|
|
||||||
throw new Error("unexpected initial value type");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function JoinNode(n) {
|
function JoinNode(n) {
|
||||||
@ -437,7 +467,8 @@ module.exports = function(RED) {
|
|||||||
newArray = newArray.concat(n);
|
newArray = newArray.concat(n);
|
||||||
})
|
})
|
||||||
group.payload = newArray;
|
group.payload = newArray;
|
||||||
} else if (group.type === 'buffer') {
|
}
|
||||||
|
else if (group.type === 'buffer') {
|
||||||
var buffers = [];
|
var buffers = [];
|
||||||
var bufferLen = 0;
|
var bufferLen = 0;
|
||||||
if (group.joinChar !== undefined) {
|
if (group.joinChar !== undefined) {
|
||||||
@ -450,7 +481,8 @@ module.exports = function(RED) {
|
|||||||
buffers.push(group.payload[i]);
|
buffers.push(group.payload[i]);
|
||||||
bufferLen += group.payload[i].length;
|
bufferLen += group.payload[i].length;
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
bufferLen = group.bufferLen;
|
bufferLen = group.bufferLen;
|
||||||
buffers = group.payload;
|
buffers = group.payload;
|
||||||
}
|
}
|
||||||
@ -463,7 +495,8 @@ module.exports = function(RED) {
|
|||||||
groupJoinChar = group.joinChar.toString();
|
groupJoinChar = group.joinChar.toString();
|
||||||
}
|
}
|
||||||
RED.util.setMessageProperty(group.msg,node.property,group.payload.join(groupJoinChar));
|
RED.util.setMessageProperty(group.msg,node.property,group.payload.join(groupJoinChar));
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
if (node.propertyType === 'full') {
|
if (node.propertyType === 'full') {
|
||||||
group.msg = RED.util.cloneMessage(group.msg);
|
group.msg = RED.util.cloneMessage(group.msg);
|
||||||
}
|
}
|
||||||
@ -471,13 +504,48 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
if (group.msg.hasOwnProperty('parts') && group.msg.parts.hasOwnProperty('parts')) {
|
if (group.msg.hasOwnProperty('parts') && group.msg.parts.hasOwnProperty('parts')) {
|
||||||
group.msg.parts = group.msg.parts.parts;
|
group.msg.parts = group.msg.parts.parts;
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
delete group.msg.parts;
|
delete group.msg.parts;
|
||||||
}
|
}
|
||||||
delete group.msg.complete;
|
delete group.msg.complete;
|
||||||
node.send(group.msg);
|
node.send(group.msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var pendingMessages = [];
|
||||||
|
var activeMessagePromise = null;
|
||||||
|
// In reduce mode, we must process messages fully in order otherwise
|
||||||
|
// groups may overlap and cause unexpected results. The use of JSONata
|
||||||
|
// means some async processing *might* occur if flow/global context is
|
||||||
|
// accessed.
|
||||||
|
var processReduceMessageQueue = function(msg) {
|
||||||
|
if (msg) {
|
||||||
|
// A new message has arrived - add it to the message queue
|
||||||
|
pendingMessages.push(msg);
|
||||||
|
if (activeMessagePromise !== null) {
|
||||||
|
// The node is currently processing a message, so do nothing
|
||||||
|
// more with this message
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pendingMessages.length === 0) {
|
||||||
|
// There are no more messages to process, clear the active flag
|
||||||
|
// and return
|
||||||
|
activeMessagePromise = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// There are more messages to process. Get the next message and
|
||||||
|
// start processing it. Recurse back in to check for any more
|
||||||
|
var nextMsg = pendingMessages.shift();
|
||||||
|
activeMessagePromise = reduce_msg(node, nextMsg)
|
||||||
|
.then(processReduceMessageQueue)
|
||||||
|
.catch((err) => {
|
||||||
|
node.error(err,nextMsg);
|
||||||
|
return processReduceMessageQueue();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.on("input", function(msg) {
|
this.on("input", function(msg) {
|
||||||
try {
|
try {
|
||||||
var property;
|
var property;
|
||||||
@ -516,8 +584,7 @@ module.exports = function(RED) {
|
|||||||
propertyIndex = msg.parts.index;
|
propertyIndex = msg.parts.index;
|
||||||
}
|
}
|
||||||
else if (node.mode === 'reduce') {
|
else if (node.mode === 'reduce') {
|
||||||
reduce_msg(node, msg);
|
return processReduceMessageQueue(msg);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Use the node configuration to identify all of the group information
|
// Use the node configuration to identify all of the group information
|
||||||
@ -525,7 +592,7 @@ module.exports = function(RED) {
|
|||||||
payloadType = node.build;
|
payloadType = node.build;
|
||||||
targetCount = node.count;
|
targetCount = node.count;
|
||||||
joinChar = node.joiner;
|
joinChar = node.joiner;
|
||||||
if (targetCount === 0 && msg.hasOwnProperty('parts')) {
|
if (n.count === "" && msg.hasOwnProperty('parts')) {
|
||||||
targetCount = msg.parts.count || 0;
|
targetCount = msg.parts.count || 0;
|
||||||
}
|
}
|
||||||
if (node.build === 'object') {
|
if (node.build === 'object') {
|
||||||
@ -554,7 +621,7 @@ module.exports = function(RED) {
|
|||||||
payload:{},
|
payload:{},
|
||||||
targetCount:targetCount,
|
targetCount:targetCount,
|
||||||
type:"object",
|
type:"object",
|
||||||
msg:msg
|
msg:RED.util.cloneMessage(msg)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
else if (node.accumulate === true) {
|
else if (node.accumulate === true) {
|
||||||
@ -564,7 +631,7 @@ module.exports = function(RED) {
|
|||||||
payload:{},
|
payload:{},
|
||||||
targetCount:targetCount,
|
targetCount:targetCount,
|
||||||
type:payloadType,
|
type:payloadType,
|
||||||
msg:msg
|
msg:RED.util.cloneMessage(msg)
|
||||||
}
|
}
|
||||||
if (payloadType === 'string' || payloadType === 'array' || payloadType === 'buffer') {
|
if (payloadType === 'string' || payloadType === 'array' || payloadType === 'buffer') {
|
||||||
inflight[partId].payload = [];
|
inflight[partId].payload = [];
|
||||||
@ -576,7 +643,7 @@ module.exports = function(RED) {
|
|||||||
payload:[],
|
payload:[],
|
||||||
targetCount:targetCount,
|
targetCount:targetCount,
|
||||||
type:payloadType,
|
type:payloadType,
|
||||||
msg:msg
|
msg:RED.util.cloneMessage(msg)
|
||||||
};
|
};
|
||||||
if (payloadType === 'string') {
|
if (payloadType === 'string') {
|
||||||
inflight[partId].joinChar = joinChar;
|
inflight[partId].joinChar = joinChar;
|
||||||
@ -627,14 +694,14 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO: currently reuse the last received - add option to pick first received
|
group.msg = Object.assign(group.msg, msg);
|
||||||
group.msg = msg;
|
|
||||||
var tcnt = group.targetCount;
|
var tcnt = group.targetCount;
|
||||||
if (msg.hasOwnProperty("parts")) { tcnt = group.targetCount || msg.parts.count; }
|
if (msg.hasOwnProperty("parts")) { tcnt = group.targetCount || msg.parts.count; }
|
||||||
if ((tcnt > 0 && group.currentCount >= tcnt) || msg.hasOwnProperty('complete')) {
|
if ((tcnt > 0 && group.currentCount >= tcnt) || msg.hasOwnProperty('complete')) {
|
||||||
completeSend(partId);
|
completeSend(partId);
|
||||||
}
|
}
|
||||||
} catch(err) {
|
}
|
||||||
|
catch(err) {
|
||||||
console.log(err.stack);
|
console.log(err.stack);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
module.exports = function(RED) {
|
module.exports = function(RED) {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var _max_kept_msgs_count = undefined;
|
var _max_kept_msgs_count;
|
||||||
|
|
||||||
function max_kept_msgs_count(node) {
|
function max_kept_msgs_count(node) {
|
||||||
if (_max_kept_msgs_count === undefined) {
|
if (_max_kept_msgs_count === undefined) {
|
||||||
@ -32,30 +32,20 @@ module.exports = function(RED) {
|
|||||||
return _max_kept_msgs_count;
|
return _max_kept_msgs_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
function eval_jsonata(node, code, val) {
|
// function get_context_val(node, name, dval) {
|
||||||
try {
|
// var context = node.context();
|
||||||
return RED.util.evaluateJSONataExpression(code, val);
|
// var val = context.get(name);
|
||||||
}
|
// if (val === undefined) {
|
||||||
catch (e) {
|
// context.set(name, dval);
|
||||||
node.error(RED._("sort.invalid-exp"));
|
// return dval;
|
||||||
throw e;
|
// }
|
||||||
}
|
// return val;
|
||||||
}
|
// }
|
||||||
|
|
||||||
function get_context_val(node, name, dval) {
|
|
||||||
var context = node.context();
|
|
||||||
var val = context.get(name);
|
|
||||||
if (val === undefined) {
|
|
||||||
context.set(name, dval);
|
|
||||||
return dval;
|
|
||||||
}
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
|
|
||||||
function SortNode(n) {
|
function SortNode(n) {
|
||||||
RED.nodes.createNode(this, n);
|
RED.nodes.createNode(this, n);
|
||||||
var node = this;
|
var node = this;
|
||||||
var pending = get_context_val(node, 'pending', {})
|
var pending = {};//get_context_val(node, 'pending', {})
|
||||||
var pending_count = 0;
|
var pending_count = 0;
|
||||||
var pending_id = 0;
|
var pending_id = 0;
|
||||||
var order = n.order || "ascending";
|
var order = n.order || "ascending";
|
||||||
@ -76,11 +66,10 @@ module.exports = function(RED) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
var dir = (order === "ascending") ? 1 : -1;
|
var dir = (order === "ascending") ? 1 : -1;
|
||||||
var conv = as_num
|
var conv = as_num ? function(x) { return Number(x); }
|
||||||
? function(x) { return Number(x); }
|
: function(x) { return x; };
|
||||||
: function(x) { return x; };
|
|
||||||
|
|
||||||
function gen_comp(key) {
|
function generateComparisonFunction(key) {
|
||||||
return function(x, y) {
|
return function(x, y) {
|
||||||
var xp = conv(key(x));
|
var xp = conv(key(x));
|
||||||
var yp = conv(key(y));
|
var yp = conv(key(y));
|
||||||
@ -90,74 +79,97 @@ module.exports = function(RED) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function send_group(group) {
|
function sortMessageGroup(group) {
|
||||||
var key = key_is_exp
|
var promise;
|
||||||
? function(msg) {
|
|
||||||
return eval_jsonata(node, key_exp, msg);
|
|
||||||
}
|
|
||||||
: function(msg) {
|
|
||||||
return RED.util.getMessageProperty(msg, key_prop);
|
|
||||||
};
|
|
||||||
var comp = gen_comp(key);
|
|
||||||
var msgs = group.msgs;
|
var msgs = group.msgs;
|
||||||
try {
|
if (key_is_exp) {
|
||||||
msgs.sort(comp);
|
var evaluatedDataPromises = msgs.map(msg => {
|
||||||
}
|
return new Promise((resolve,reject) => {
|
||||||
catch (e) {
|
RED.util.evaluateJSONataExpression(key_exp, msg, (err, result) => {
|
||||||
return; // not send when error
|
resolve({
|
||||||
}
|
item: msg,
|
||||||
for (var i = 0; i < msgs.length; i++) {
|
sortValue: result
|
||||||
var msg = msgs[i];
|
})
|
||||||
msg.parts.index = i;
|
});
|
||||||
node.send(msg);
|
})
|
||||||
}
|
});
|
||||||
}
|
promise = Promise.all(evaluatedDataPromises).then(evaluatedElements => {
|
||||||
|
// Once all of the sort keys are evaluated, sort by them
|
||||||
function sort_payload(msg) {
|
var comp = generateComparisonFunction(elem=>elem.sortValue);
|
||||||
var data = RED.util.getMessageProperty(msg, target_prop);
|
return evaluatedElements.sort(comp).map(elem=>elem.item);
|
||||||
if (Array.isArray(data)) {
|
});
|
||||||
var key = key_is_exp
|
} else {
|
||||||
? function(elem) {
|
var key = function(msg) {
|
||||||
return eval_jsonata(node, key_exp, elem);
|
return ;
|
||||||
}
|
}
|
||||||
: function(elem) { return elem; };
|
var comp = generateComparisonFunction(msg => RED.util.getMessageProperty(msg, key_prop));
|
||||||
var comp = gen_comp(key);
|
|
||||||
try {
|
try {
|
||||||
data.sort(comp);
|
msgs.sort(comp);
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
return false;
|
return; // not send when error
|
||||||
}
|
}
|
||||||
return true;
|
promise = Promise.resolve(msgs);
|
||||||
}
|
}
|
||||||
return false;
|
return promise.then(msgs => {
|
||||||
|
for (var i = 0; i < msgs.length; i++) {
|
||||||
|
var msg = msgs[i];
|
||||||
|
msg.parts.index = i;
|
||||||
|
node.send(msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function check_parts(parts) {
|
function sortMessageProperty(msg) {
|
||||||
if (parts.hasOwnProperty("id") &&
|
var data = RED.util.getMessageProperty(msg, target_prop);
|
||||||
parts.hasOwnProperty("index")) {
|
if (Array.isArray(data)) {
|
||||||
return true;
|
if (key_is_exp) {
|
||||||
|
// key is an expression. Evaluated the expression for each item
|
||||||
|
// to get its sort value. As this could be async, need to do
|
||||||
|
// it first.
|
||||||
|
var evaluatedDataPromises = data.map(elem => {
|
||||||
|
return new Promise((resolve,reject) => {
|
||||||
|
RED.util.evaluateJSONataExpression(key_exp, elem, (err, result) => {
|
||||||
|
resolve({
|
||||||
|
item: elem,
|
||||||
|
sortValue: result
|
||||||
|
})
|
||||||
|
});
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return Promise.all(evaluatedDataPromises).then(evaluatedElements => {
|
||||||
|
// Once all of the sort keys are evaluated, sort by them
|
||||||
|
// and reconstruct the original message item with the newly
|
||||||
|
// sorted values.
|
||||||
|
var comp = generateComparisonFunction(elem=>elem.sortValue);
|
||||||
|
data = evaluatedElements.sort(comp).map(elem=>elem.item);
|
||||||
|
RED.util.setMessageProperty(msg, target_prop,data);
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
var comp = generateComparisonFunction(elem=>elem);
|
||||||
|
try {
|
||||||
|
data.sort(comp);
|
||||||
|
} catch (e) {
|
||||||
|
return Promise.resolve(false);
|
||||||
|
}
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return Promise.resolve(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function clear_pending() {
|
function removeOldestPending() {
|
||||||
|
var oldest;
|
||||||
|
var oldest_key;
|
||||||
for(var key in pending) {
|
for(var key in pending) {
|
||||||
node.log(RED._("sort.clear"), pending[key].msgs[0]);
|
if (pending.hasOwnProperty(key)) {
|
||||||
delete pending[key];
|
var item = pending[key];
|
||||||
}
|
if((oldest === undefined) ||
|
||||||
pending_count = 0;
|
(oldest.seq_no > item.seq_no)) {
|
||||||
}
|
oldest = item;
|
||||||
|
oldest_key = key;
|
||||||
function remove_oldest_pending() {
|
}
|
||||||
var oldest = undefined;
|
|
||||||
var oldest_key = undefined;
|
|
||||||
for(var key in pending) {
|
|
||||||
var item = pending[key];
|
|
||||||
if((oldest === undefined) ||
|
|
||||||
(oldest.seq_no > item.seq_no)) {
|
|
||||||
oldest = item;
|
|
||||||
oldest_key = key;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(oldest !== undefined) {
|
if(oldest !== undefined) {
|
||||||
@ -167,15 +179,17 @@ module.exports = function(RED) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function process_msg(msg) {
|
function processMessage(msg) {
|
||||||
if (target_is_prop) {
|
if (target_is_prop) {
|
||||||
if (sort_payload(msg)) {
|
sortMessageProperty(msg).then(send => {
|
||||||
node.send(msg);
|
if (send) {
|
||||||
}
|
node.send(msg);
|
||||||
return;
|
}
|
||||||
|
}).catch(err => {
|
||||||
|
});
|
||||||
}
|
}
|
||||||
var parts = msg.parts;
|
var parts = msg.parts;
|
||||||
if (!check_parts(parts)) {
|
if (!parts.hasOwnProperty("id") || !parts.hasOwnProperty("index")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var gid = parts.id;
|
var gid = parts.id;
|
||||||
@ -195,23 +209,29 @@ module.exports = function(RED) {
|
|||||||
pending_count++;
|
pending_count++;
|
||||||
if (group.count === msgs.length) {
|
if (group.count === msgs.length) {
|
||||||
delete pending[gid]
|
delete pending[gid]
|
||||||
send_group(group);
|
sortMessageGroup(group);
|
||||||
pending_count -= msgs.length;
|
pending_count -= msgs.length;
|
||||||
}
|
} else {
|
||||||
var max_msgs = max_kept_msgs_count(node);
|
var max_msgs = max_kept_msgs_count(node);
|
||||||
if ((max_msgs > 0) && (pending_count > max_msgs)) {
|
if ((max_msgs > 0) && (pending_count > max_msgs)) {
|
||||||
pending_count -= remove_oldest_pending();
|
pending_count -= removeOldestPending();
|
||||||
node.error(RED._("sort.too-many"), msg);
|
node.error(RED._("sort.too-many"), msg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.on("input", function(msg) {
|
this.on("input", function(msg) {
|
||||||
process_msg(msg);
|
processMessage(msg);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.on("close", function() {
|
this.on("close", function() {
|
||||||
clear_pending();
|
for(var key in pending) {
|
||||||
})
|
if (pending.hasOwnProperty(key)) {
|
||||||
|
node.log(RED._("sort.clear"), pending[key].msgs[0]);
|
||||||
|
delete pending[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pending_count = 0; })
|
||||||
}
|
}
|
||||||
|
|
||||||
RED.nodes.registerType("sort", SortNode);
|
RED.nodes.registerType("sort", SortNode);
|
||||||
|
@ -29,7 +29,6 @@ module.exports = {
|
|||||||
var scope = req.params.scope;
|
var scope = req.params.scope;
|
||||||
var id = req.params.id;
|
var id = req.params.id;
|
||||||
var key = req.params[0];
|
var key = req.params[0];
|
||||||
var result = {};
|
|
||||||
var ctx;
|
var ctx;
|
||||||
if (scope === 'global') {
|
if (scope === 'global') {
|
||||||
ctx = redNodes.getContext('global');
|
ctx = redNodes.getContext('global');
|
||||||
@ -43,19 +42,31 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
if (ctx) {
|
if (ctx) {
|
||||||
if (key) {
|
if (key) {
|
||||||
result = util.encodeObject({msg:ctx.get(key)});
|
ctx.get(key,function(err, v) {
|
||||||
|
res.json(util.encodeObject({msg:v}));
|
||||||
|
});
|
||||||
|
return;
|
||||||
} else {
|
} else {
|
||||||
var keys = ctx.keys();
|
ctx.keys(function(err, keys) {
|
||||||
|
var result = {};
|
||||||
var i = 0;
|
var c = keys.length;
|
||||||
var l = keys.length;
|
if (c === 0) {
|
||||||
while(i < l) {
|
res.json(result);
|
||||||
var k = keys[i];
|
} else {
|
||||||
result[k] = util.encodeObject({msg:ctx.get(k)});
|
keys.forEach(function(key) {
|
||||||
i++;
|
ctx.get(key,function(err, v) {
|
||||||
}
|
result[key] = util.encodeObject({msg:v});
|
||||||
|
c--;
|
||||||
|
if (c === 0) {
|
||||||
|
res.json(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
res.json({});
|
||||||
}
|
}
|
||||||
res.json(result);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,6 +40,8 @@ module.exports = {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
safeSettings.context = runtime.nodes.listContextStores();
|
||||||
|
|
||||||
var themeSettings = theme.settings();
|
var themeSettings = theme.settings();
|
||||||
if (themeSettings) {
|
if (themeSettings) {
|
||||||
safeSettings.editorTheme = themeSettings;
|
safeSettings.editorTheme = themeSettings;
|
||||||
|
@ -19,30 +19,37 @@ var log = require("../../log");
|
|||||||
var memory = require("./memory");
|
var memory = require("./memory");
|
||||||
|
|
||||||
var settings;
|
var settings;
|
||||||
|
|
||||||
|
// A map of scope id to context instance
|
||||||
var contexts = {};
|
var contexts = {};
|
||||||
var globalContext = null;
|
|
||||||
var externalContexts = {};
|
// A map of store name to instance
|
||||||
var hasExternalContext = false;
|
var stores = {};
|
||||||
|
var storeList = [];
|
||||||
|
var defaultStore;
|
||||||
|
|
||||||
|
// Whether there context storage has been configured or left as default
|
||||||
|
var hasConfiguredStore = false;
|
||||||
|
|
||||||
|
|
||||||
function init(_settings) {
|
function init(_settings) {
|
||||||
settings = _settings;
|
settings = _settings;
|
||||||
externalContexts = {};
|
stores = {};
|
||||||
|
|
||||||
// init memory plugin
|
|
||||||
var seed = settings.functionGlobalContext || {};
|
var seed = settings.functionGlobalContext || {};
|
||||||
externalContexts["_"] = memory();
|
contexts['global'] = createContext("global",seed);
|
||||||
externalContexts["_"].setGlobalContext(seed);
|
stores["_"] = new memory();
|
||||||
globalContext = createContext("global",seed);
|
defaultStore = "memory";
|
||||||
contexts['global'] = globalContext;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function load() {
|
function load() {
|
||||||
return new Promise(function(resolve,reject) {
|
return new Promise(function(resolve,reject) {
|
||||||
// load & init plugins in settings.contextStorage
|
// load & init plugins in settings.contextStorage
|
||||||
var plugins = settings.contextStorage;
|
var plugins = settings.contextStorage || {};
|
||||||
var defaultIsAlias = false;
|
var defaultIsAlias = false;
|
||||||
var promises = [];
|
var promises = [];
|
||||||
if (plugins) {
|
if (plugins && Object.keys(plugins).length > 0) {
|
||||||
|
var hasDefault = plugins.hasOwnProperty('default');
|
||||||
|
var defaultName;
|
||||||
for (var pluginName in plugins) {
|
for (var pluginName in plugins) {
|
||||||
if (plugins.hasOwnProperty(pluginName)) {
|
if (plugins.hasOwnProperty(pluginName)) {
|
||||||
// "_" is a reserved name - do not allow it to be overridden
|
// "_" is a reserved name - do not allow it to be overridden
|
||||||
@ -59,6 +66,9 @@ function load() {
|
|||||||
defaultIsAlias = true;
|
defaultIsAlias = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (!hasDefault && !defaultName) {
|
||||||
|
defaultName = pluginName;
|
||||||
|
}
|
||||||
var plugin;
|
var plugin;
|
||||||
if (plugins[pluginName].hasOwnProperty("module")) {
|
if (plugins[pluginName].hasOwnProperty("module")) {
|
||||||
// Get the provided config and copy in the 'approved' top-level settings (eg userDir)
|
// Get the provided config and copy in the 'approved' top-level settings (eg userDir)
|
||||||
@ -79,7 +89,7 @@ function load() {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// Create a new instance of the plugin by calling its module function
|
// Create a new instance of the plugin by calling its module function
|
||||||
externalContexts[pluginName] = plugin(config);
|
stores[pluginName] = plugin(config);
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
return reject(new Error(log._("context.error-loading-module",{module:pluginName,message:err.toString()})));
|
return reject(new Error(log._("context.error-loading-module",{module:pluginName,message:err.toString()})));
|
||||||
}
|
}
|
||||||
@ -91,23 +101,41 @@ function load() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Open all of the configured contexts
|
// Open all of the configured contexts
|
||||||
for (var plugin in externalContexts) {
|
for (var plugin in stores) {
|
||||||
if (externalContexts.hasOwnProperty(plugin)) {
|
if (stores.hasOwnProperty(plugin)) {
|
||||||
promises.push(externalContexts[plugin].open());
|
promises.push(stores[plugin].open());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// There is a 'default' listed in the configuration
|
||||||
// If 'default' is an alias, point it at the right module - we have already
|
if (hasDefault) {
|
||||||
// checked that it exists
|
// If 'default' is an alias, point it at the right module - we have already
|
||||||
if (defaultIsAlias) {
|
// checked that it exists. If it isn't an alias, then it will
|
||||||
externalContexts["default"] = externalContexts[plugins["default"]];
|
// already be set to a configured store
|
||||||
|
if (defaultIsAlias) {
|
||||||
|
stores["_"] = stores[plugins["default"]];
|
||||||
|
defaultStore = plugins["default"];
|
||||||
|
} else {
|
||||||
|
stores["_"] = stores["default"];
|
||||||
|
defaultStore = "default";
|
||||||
|
}
|
||||||
|
} else if (defaultName) {
|
||||||
|
// No 'default' listed, so pick first in list as the default
|
||||||
|
stores["_"] = stores[defaultName];
|
||||||
|
defaultStore = defaultName;
|
||||||
|
defaultIsAlias = true;
|
||||||
|
} else {
|
||||||
|
// else there were no stores list the config object - fall through
|
||||||
|
// to below where we default to a memory store
|
||||||
|
storeList = ["memory"];
|
||||||
|
defaultStore = "memory";
|
||||||
}
|
}
|
||||||
}
|
hasConfiguredStore = true;
|
||||||
|
storeList = Object.keys(stores).filter(n=>!(defaultIsAlias && n==="default") && n!== "_");
|
||||||
if (promises.length === 0) {
|
|
||||||
promises.push(externalContexts["_"].open())
|
|
||||||
} else {
|
} else {
|
||||||
hasExternalContext = true;
|
// No configured plugins
|
||||||
|
promises.push(stores["_"].open())
|
||||||
|
storeList = ["memory"];
|
||||||
|
defaultStore = "memory";
|
||||||
}
|
}
|
||||||
return resolve(Promise.all(promises));
|
return resolve(Promise.all(promises));
|
||||||
});
|
});
|
||||||
@ -122,12 +150,12 @@ function copySettings(config, settings){
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getContextStorage(storage) {
|
function getContextStorage(storage) {
|
||||||
if (externalContexts.hasOwnProperty(storage)) {
|
if (stores.hasOwnProperty(storage)) {
|
||||||
// A known context
|
// A known context
|
||||||
return externalContexts[storage];
|
return stores[storage];
|
||||||
} else if (externalContexts.hasOwnProperty("default")) {
|
} else if (stores.hasOwnProperty("_")) {
|
||||||
// Not known, but we have a default to fall back to
|
// Not known, but we have a default to fall back to
|
||||||
return externalContexts["default"];
|
return stores["_"];
|
||||||
} else {
|
} else {
|
||||||
// Not known and no default configured
|
// Not known and no default configured
|
||||||
var contextError = new Error(log._("context.error-use-undefined-storage", {storage:storage}));
|
var contextError = new Error(log._("context.error-use-undefined-storage", {storage:storage}));
|
||||||
@ -136,34 +164,61 @@ function getContextStorage(storage) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function createContext(id,seed) {
|
function createContext(id,seed) {
|
||||||
|
// Seed is only set for global context - sourced from functionGlobalContext
|
||||||
var scope = id;
|
var scope = id;
|
||||||
var obj = seed || {};
|
var obj = seed || {};
|
||||||
|
var seedKeys;
|
||||||
|
if (seed) {
|
||||||
|
seedKeys = Object.keys(seed);
|
||||||
|
}
|
||||||
obj.get = function(key, storage, callback) {
|
obj.get = function(key, storage, callback) {
|
||||||
var context;
|
var context;
|
||||||
if (!storage && !callback) {
|
if (!storage && !callback) {
|
||||||
context = externalContexts["_"];
|
context = stores["_"];
|
||||||
} else {
|
} else {
|
||||||
if (typeof storage === 'function') {
|
if (typeof storage === 'function') {
|
||||||
callback = storage;
|
callback = storage;
|
||||||
storage = "default";
|
storage = "_";
|
||||||
}
|
}
|
||||||
if (typeof callback !== 'function'){
|
if (typeof callback !== 'function'){
|
||||||
throw new Error("Callback must be a function");
|
throw new Error("Callback must be a function");
|
||||||
}
|
}
|
||||||
context = getContextStorage(storage);
|
context = getContextStorage(storage);
|
||||||
}
|
}
|
||||||
return context.get(scope, key, callback);
|
if (seed) {
|
||||||
|
// Get the value from the underlying store. If it is undefined,
|
||||||
|
// check the seed for a default value.
|
||||||
|
if (callback) {
|
||||||
|
context.get(scope,key,function(err, v) {
|
||||||
|
if (v === undefined) {
|
||||||
|
callback(err, seed[key]);
|
||||||
|
} else {
|
||||||
|
callback(err, v);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// No callback, attempt to do this synchronously
|
||||||
|
var storeValue = context.get(scope,key);
|
||||||
|
if (storeValue === undefined) {
|
||||||
|
return seed[key];
|
||||||
|
} else {
|
||||||
|
return storeValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return context.get(scope, key, callback);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
obj.set = function(key, value, storage, callback) {
|
obj.set = function(key, value, storage, callback) {
|
||||||
var context;
|
var context;
|
||||||
if (!storage && !callback) {
|
if (!storage && !callback) {
|
||||||
context = externalContexts["_"];
|
context = stores["_"];
|
||||||
} else {
|
} else {
|
||||||
if (typeof storage === 'function') {
|
if (typeof storage === 'function') {
|
||||||
callback = storage;
|
callback = storage;
|
||||||
storage = "default";
|
storage = "_";
|
||||||
}
|
}
|
||||||
if (callback && typeof callback !== 'function') {
|
if (callback && typeof callback !== 'function') {
|
||||||
throw new Error("Callback must be a function");
|
throw new Error("Callback must be a function");
|
||||||
@ -175,18 +230,29 @@ function createContext(id,seed) {
|
|||||||
obj.keys = function(storage, callback) {
|
obj.keys = function(storage, callback) {
|
||||||
var context;
|
var context;
|
||||||
if (!storage && !callback) {
|
if (!storage && !callback) {
|
||||||
context = externalContexts["_"];
|
context = stores["_"];
|
||||||
} else {
|
} else {
|
||||||
if (typeof storage === 'function') {
|
if (typeof storage === 'function') {
|
||||||
callback = storage;
|
callback = storage;
|
||||||
storage = "default";
|
storage = "_";
|
||||||
}
|
}
|
||||||
if (typeof callback !== 'function') {
|
if (typeof callback !== 'function') {
|
||||||
throw new Error("Callback must be a function");
|
throw new Error("Callback must be a function");
|
||||||
}
|
}
|
||||||
context = getContextStorage(storage);
|
context = getContextStorage(storage);
|
||||||
}
|
}
|
||||||
return context.keys(scope, callback);
|
if (seed) {
|
||||||
|
if (callback) {
|
||||||
|
context.keys(scope, function(err,keys) {
|
||||||
|
callback(err,Array.from(new Set(seedKeys.concat(keys)).keys()));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
var keys = context.keys(scope);
|
||||||
|
return Array.from(new Set(seedKeys.concat(keys)).keys())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return context.keys(scope, callback);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
@ -203,21 +269,20 @@ function getContext(localId,flowId) {
|
|||||||
if (flowId) {
|
if (flowId) {
|
||||||
newContext.flow = getContext(flowId);
|
newContext.flow = getContext(flowId);
|
||||||
}
|
}
|
||||||
if (globalContext) {
|
newContext.global = contexts['global'];
|
||||||
newContext.global = globalContext;
|
|
||||||
}
|
|
||||||
contexts[contextId] = newContext;
|
contexts[contextId] = newContext;
|
||||||
return newContext;
|
return newContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteContext(id,flowId) {
|
function deleteContext(id,flowId) {
|
||||||
if(!hasExternalContext){
|
if(!hasConfiguredStore){
|
||||||
|
// only delete context if there's no configured storage.
|
||||||
var contextId = id;
|
var contextId = id;
|
||||||
if (flowId) {
|
if (flowId) {
|
||||||
contextId = id+":"+flowId;
|
contextId = id+":"+flowId;
|
||||||
}
|
}
|
||||||
delete contexts[contextId];
|
delete contexts[contextId];
|
||||||
return externalContexts["_"].delete(contextId);
|
return stores["_"].delete(contextId);
|
||||||
}else{
|
}else{
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
@ -225,9 +290,9 @@ function deleteContext(id,flowId) {
|
|||||||
|
|
||||||
function clean(flowConfig) {
|
function clean(flowConfig) {
|
||||||
var promises = [];
|
var promises = [];
|
||||||
for(var plugin in externalContexts){
|
for(var plugin in stores){
|
||||||
if(externalContexts.hasOwnProperty(plugin)){
|
if(stores.hasOwnProperty(plugin)){
|
||||||
promises.push(externalContexts[plugin].clean(Object.keys(flowConfig.allNodes)));
|
promises.push(stores[plugin].clean(Object.keys(flowConfig.allNodes)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (var id in contexts) {
|
for (var id in contexts) {
|
||||||
@ -243,17 +308,22 @@ function clean(flowConfig) {
|
|||||||
|
|
||||||
function close() {
|
function close() {
|
||||||
var promises = [];
|
var promises = [];
|
||||||
for(var plugin in externalContexts){
|
for(var plugin in stores){
|
||||||
if(externalContexts.hasOwnProperty(plugin)){
|
if(stores.hasOwnProperty(plugin)){
|
||||||
promises.push(externalContexts[plugin].close());
|
promises.push(stores[plugin].close());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function listStores() {
|
||||||
|
return {default:defaultStore,stores:storeList};
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
init: init,
|
init: init,
|
||||||
load: load,
|
load: load,
|
||||||
|
listStores: listStores,
|
||||||
get: getContext,
|
get: getContext,
|
||||||
delete: deleteContext,
|
delete: deleteContext,
|
||||||
clean: clean,
|
clean: clean,
|
||||||
|
@ -14,10 +14,39 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
**/
|
**/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Local file-system based context storage
|
||||||
|
*
|
||||||
|
* Configuration options:
|
||||||
|
* {
|
||||||
|
* base: "contexts", // the base directory to use
|
||||||
|
* // default: "contexts"
|
||||||
|
* dir: "/path/to/storage", // the directory to create the base directory in
|
||||||
|
* // default: settings.userDir
|
||||||
|
* cache: true // whether to cache contents in memory
|
||||||
|
* // default: true
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* $HOME/.node-red/contexts
|
||||||
|
* ├── global
|
||||||
|
* │ └── global_context.json
|
||||||
|
* ├── <id of Flow 1>
|
||||||
|
* │ ├── flow_context.json
|
||||||
|
* │ ├── <id of Node a>.json
|
||||||
|
* │ └── <id of Node b>.json
|
||||||
|
* └── <id of Flow 2>
|
||||||
|
* ├── flow_context.json
|
||||||
|
* ├── <id of Node x>.json
|
||||||
|
* └── <id of Node y>.json
|
||||||
|
*/
|
||||||
|
|
||||||
var fs = require('fs-extra');
|
var fs = require('fs-extra');
|
||||||
var path = require("path");
|
var path = require("path");
|
||||||
var util = require("../../util");
|
var util = require("../../util");
|
||||||
|
|
||||||
|
var MemoryStore = require("./memory");
|
||||||
|
|
||||||
function getStoragePath(storageBaseDir, scope) {
|
function getStoragePath(storageBaseDir, scope) {
|
||||||
if(scope.indexOf(":") === -1){
|
if(scope.indexOf(":") === -1){
|
||||||
if(scope === "global"){
|
if(scope === "global"){
|
||||||
@ -76,10 +105,54 @@ function loadFile(storagePath){
|
|||||||
function LocalFileSystem(config){
|
function LocalFileSystem(config){
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.storageBaseDir = getBasePath(this.config);
|
this.storageBaseDir = getBasePath(this.config);
|
||||||
|
if (config.hasOwnProperty('cache')?config.cache:true) {
|
||||||
|
this.cache = MemoryStore({});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalFileSystem.prototype.open = function(){
|
LocalFileSystem.prototype.open = function(){
|
||||||
return Promise.resolve();
|
var self = this;
|
||||||
|
if (this.cache) {
|
||||||
|
var scopes = [];
|
||||||
|
var promises = [];
|
||||||
|
var subdirs = [];
|
||||||
|
var subdirPromises = [];
|
||||||
|
return fs.readdir(self.storageBaseDir).then(function(dirs){
|
||||||
|
dirs.forEach(function(fn) {
|
||||||
|
var p = getStoragePath(self.storageBaseDir ,fn)+".json";
|
||||||
|
scopes.push(fn);
|
||||||
|
promises.push(loadFile(p));
|
||||||
|
subdirs.push(path.join(self.storageBaseDir,fn));
|
||||||
|
subdirPromises.push(fs.readdir(path.join(self.storageBaseDir,fn)));
|
||||||
|
})
|
||||||
|
return Promise.all(subdirPromises);
|
||||||
|
}).then(function(dirs) {
|
||||||
|
dirs.forEach(function(files,i) {
|
||||||
|
files.forEach(function(fn) {
|
||||||
|
if (fn !== 'flow.json' && fn !== 'global.json') {
|
||||||
|
scopes.push(fn.substring(0,fn.length-5)+":"+scopes[i]);
|
||||||
|
promises.push(loadFile(path.join(subdirs[i],fn)))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
return Promise.all(promises);
|
||||||
|
}).then(function(res) {
|
||||||
|
scopes.forEach(function(scope,i) {
|
||||||
|
var data = res[i]?JSON.parse(res[i]):{};
|
||||||
|
Object.keys(data).forEach(function(key) {
|
||||||
|
self.cache.set(scope,key,data[key]);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}).catch(function(err){
|
||||||
|
if(err.code == 'ENOENT') {
|
||||||
|
return fs.mkdir(self.storageBaseDir);
|
||||||
|
}else{
|
||||||
|
return Promise.reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalFileSystem.prototype.close = function(){
|
LocalFileSystem.prototype.close = function(){
|
||||||
@ -87,6 +160,9 @@ LocalFileSystem.prototype.close = function(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
LocalFileSystem.prototype.get = function(scope, key, callback) {
|
LocalFileSystem.prototype.get = function(scope, key, callback) {
|
||||||
|
if (this.cache) {
|
||||||
|
return this.cache.get(scope,key,callback);
|
||||||
|
}
|
||||||
if(typeof callback !== "function"){
|
if(typeof callback !== "function"){
|
||||||
throw new Error("Callback must be a function");
|
throw new Error("Callback must be a function");
|
||||||
}
|
}
|
||||||
@ -102,7 +178,7 @@ LocalFileSystem.prototype.get = function(scope, key, callback) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
LocalFileSystem.prototype.set =function(scope, key, value, callback) {
|
LocalFileSystem.prototype._set = function(scope, key, value, callback) {
|
||||||
var storagePath = getStoragePath(this.storageBaseDir ,scope);
|
var storagePath = getStoragePath(this.storageBaseDir ,scope);
|
||||||
loadFile(storagePath + ".json").then(function(data){
|
loadFile(storagePath + ".json").then(function(data){
|
||||||
var obj = data ? JSON.parse(data) : {}
|
var obj = data ? JSON.parse(data) : {}
|
||||||
@ -117,9 +193,25 @@ LocalFileSystem.prototype.set =function(scope, key, value, callback) {
|
|||||||
callback(err);
|
callback(err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalFileSystem.prototype.set = function(scope, key, value, callback) {
|
||||||
|
if (this.cache) {
|
||||||
|
this.cache.set(scope,key,value,callback);
|
||||||
|
// With cache enabled, no need to re-read the file prior to writing.
|
||||||
|
var newContext = this.cache._export()[scope];
|
||||||
|
var storagePath = getStoragePath(this.storageBaseDir ,scope);
|
||||||
|
fs.outputFile(storagePath + ".json", JSON.stringify(newContext, undefined, 4), "utf8").catch(function(err) {
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this._set(scope,key,value,callback);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
LocalFileSystem.prototype.keys = function(scope, callback){
|
LocalFileSystem.prototype.keys = function(scope, callback){
|
||||||
|
if (this.cache) {
|
||||||
|
return this.cache.keys(scope,callback);
|
||||||
|
}
|
||||||
if(typeof callback !== "function"){
|
if(typeof callback !== "function"){
|
||||||
throw new Error("Callback must be a function");
|
throw new Error("Callback must be a function");
|
||||||
}
|
}
|
||||||
@ -136,29 +228,45 @@ LocalFileSystem.prototype.keys = function(scope, callback){
|
|||||||
};
|
};
|
||||||
|
|
||||||
LocalFileSystem.prototype.delete = function(scope){
|
LocalFileSystem.prototype.delete = function(scope){
|
||||||
var storagePath = getStoragePath(this.storageBaseDir ,scope);
|
var cachePromise;
|
||||||
return fs.remove(storagePath + ".json");
|
if (this.cache) {
|
||||||
|
cachePromise = this.cache.delete(scope);
|
||||||
|
} else {
|
||||||
|
cachePromise = Promise.resolve();
|
||||||
|
}
|
||||||
|
var that = this;
|
||||||
|
return cachePromise.then(function() {
|
||||||
|
var storagePath = getStoragePath(that.storageBaseDir,scope);
|
||||||
|
return fs.remove(storagePath + ".json");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalFileSystem.prototype.clean = function(activeNodes){
|
LocalFileSystem.prototype.clean = function(activeNodes){
|
||||||
var self = this;
|
var self = this;
|
||||||
return fs.readdir(self.storageBaseDir).then(function(dirs){
|
var cachePromise;
|
||||||
return Promise.all(dirs.reduce(function(result, item){
|
if (this.cache) {
|
||||||
if(item !== "global" && activeNodes.indexOf(item) === -1){
|
cachePromise = this.cache.clean(activeNodes);
|
||||||
result.push(fs.remove(path.join(self.storageBaseDir,item)));
|
} else {
|
||||||
|
cachePromise = Promise.resolve();
|
||||||
|
}
|
||||||
|
return cachePromise.then(function() {
|
||||||
|
return fs.readdir(self.storageBaseDir).then(function(dirs){
|
||||||
|
return Promise.all(dirs.reduce(function(result, item){
|
||||||
|
if(item !== "global" && activeNodes.indexOf(item) === -1){
|
||||||
|
result.push(fs.remove(path.join(self.storageBaseDir,item)));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},[]));
|
||||||
|
}).catch(function(err){
|
||||||
|
if(err.code == 'ENOENT') {
|
||||||
|
return Promise.resolve();
|
||||||
|
}else{
|
||||||
|
return Promise.reject(err);
|
||||||
}
|
}
|
||||||
return result;
|
});
|
||||||
},[]));
|
|
||||||
}).catch(function(err){
|
|
||||||
if(err.code == 'ENOENT') {
|
|
||||||
return Promise.resolve();
|
|
||||||
}else{
|
|
||||||
return Promise.reject(err);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = function(config){
|
module.exports = function(config){
|
||||||
return new LocalFileSystem(config);
|
return new LocalFileSystem(config);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -109,9 +109,10 @@ Memory.prototype.clean = function(activeNodes){
|
|||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
Memory.prototype.setGlobalContext= function(seed){
|
Memory.prototype._export = function() {
|
||||||
this.data["global"] = seed;
|
return this.data;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
|
||||||
module.exports = function(config){
|
module.exports = function(config){
|
||||||
return new Memory(config);
|
return new Memory(config);
|
||||||
|
@ -222,5 +222,6 @@ module.exports = {
|
|||||||
|
|
||||||
// Contexts
|
// Contexts
|
||||||
loadContextsPlugin: context.load,
|
loadContextsPlugin: context.load,
|
||||||
closeContextsPlugin: context.close
|
closeContextsPlugin: context.close,
|
||||||
|
listContextStores: context.listStores
|
||||||
};
|
};
|
||||||
|
@ -322,35 +322,63 @@ function evaluteEnvProperty(value) {
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function evaluateNodeProperty(value, type, node, msg) {
|
var parseContextStore = function(key) {
|
||||||
|
var parts = {};
|
||||||
|
var m = /^#:\((\S+?)\)::(.*)$/.exec(key);
|
||||||
|
if (m) {
|
||||||
|
parts.store = m[1];
|
||||||
|
parts.key = m[2];
|
||||||
|
} else {
|
||||||
|
parts.key = key;
|
||||||
|
}
|
||||||
|
return parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
function evaluateNodeProperty(value, type, node, msg, callback) {
|
||||||
|
var result = value;
|
||||||
if (type === 'str') {
|
if (type === 'str') {
|
||||||
return ""+value;
|
result = ""+value;
|
||||||
} else if (type === 'num') {
|
} else if (type === 'num') {
|
||||||
return Number(value);
|
result = Number(value);
|
||||||
} else if (type === 'json') {
|
} else if (type === 'json') {
|
||||||
return JSON.parse(value);
|
result = JSON.parse(value);
|
||||||
} else if (type === 're') {
|
} else if (type === 're') {
|
||||||
return new RegExp(value);
|
result = new RegExp(value);
|
||||||
} else if (type === 'date') {
|
} else if (type === 'date') {
|
||||||
return Date.now();
|
result = Date.now();
|
||||||
} else if (type === 'bin') {
|
} else if (type === 'bin') {
|
||||||
var data = JSON.parse(value);
|
var data = JSON.parse(value);
|
||||||
return Buffer.from(data);
|
result = Buffer.from(data);
|
||||||
} else if (type === 'msg' && msg) {
|
} else if (type === 'msg' && msg) {
|
||||||
return getMessageProperty(msg,value);
|
try {
|
||||||
} else if (type === 'flow' && node) {
|
result = getMessageProperty(msg,value);
|
||||||
return node.context().flow.get(value);
|
} catch(err) {
|
||||||
} else if (type === 'global' && node) {
|
if (callback) {
|
||||||
return node.context().global.get(value);
|
callback(err);
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if ((type === 'flow' || type === 'global') && node) {
|
||||||
|
var contextKey = parseContextStore(value);
|
||||||
|
result = node.context()[type].get(contextKey.key,contextKey.store,callback);
|
||||||
|
if (callback) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
} else if (type === 'bool') {
|
} else if (type === 'bool') {
|
||||||
return /^true$/i.test(value);
|
result = /^true$/i.test(value);
|
||||||
} else if (type === 'jsonata') {
|
} else if (type === 'jsonata') {
|
||||||
var expr = prepareJSONataExpression(value,node);
|
var expr = prepareJSONataExpression(value,node);
|
||||||
return evaluateJSONataExpression(expr,msg);
|
result = evaluateJSONataExpression(expr,msg);
|
||||||
} else if (type === 'env') {
|
} else if (type === 'env') {
|
||||||
return evaluteEnvProperty(value);
|
result = evaluteEnvProperty(value);
|
||||||
|
}
|
||||||
|
if (callback) {
|
||||||
|
callback(null,result);
|
||||||
|
} else {
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
return value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareJSONataExpression(value,node) {
|
function prepareJSONataExpression(value,node) {
|
||||||
@ -366,15 +394,44 @@ function prepareJSONataExpression(value,node) {
|
|||||||
})
|
})
|
||||||
expr.registerFunction('clone', cloneMessage, '<(oa)-:o>');
|
expr.registerFunction('clone', cloneMessage, '<(oa)-:o>');
|
||||||
expr._legacyMode = /(^|[^a-zA-Z0-9_'"])msg([^a-zA-Z0-9_'"]|$)/.test(value);
|
expr._legacyMode = /(^|[^a-zA-Z0-9_'"])msg([^a-zA-Z0-9_'"]|$)/.test(value);
|
||||||
|
expr._node = node;
|
||||||
return expr;
|
return expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
function evaluateJSONataExpression(expr,msg) {
|
function evaluateJSONataExpression(expr,msg,callback) {
|
||||||
var context = msg;
|
var context = msg;
|
||||||
if (expr._legacyMode) {
|
if (expr._legacyMode) {
|
||||||
context = {msg:msg};
|
context = {msg:msg};
|
||||||
}
|
}
|
||||||
return expr.evaluate(context);
|
var bindings = {};
|
||||||
|
|
||||||
|
if (callback) {
|
||||||
|
// If callback provided, need to override the pre-assigned sync
|
||||||
|
// context functions to be their async variants
|
||||||
|
bindings.flowContext = function(val) {
|
||||||
|
return new Promise((resolve,reject) => {
|
||||||
|
expr._node.context().flow.get(val, function(err,value) {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(value);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
bindings.globalContext = function(val) {
|
||||||
|
return new Promise((resolve,reject) => {
|
||||||
|
expr._node.context().global.get(val, function(err,value) {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(value);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return expr.evaluate(context, bindings, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -62,10 +62,11 @@ exports.config = {
|
|||||||
//
|
//
|
||||||
browserName: 'chrome',
|
browserName: 'chrome',
|
||||||
chromeOptions: {
|
chromeOptions: {
|
||||||
// Runs tests without opening a broser.
|
args: process.env.NODE_RED_NON_HEADLESS
|
||||||
args: ['--headless', '--disable-gpu', 'window-size=1920,1080'],
|
// Runs tests with opening a browser.
|
||||||
// Runs tests with opening a broser.
|
? ['--disable-gpu']
|
||||||
// args: ['--disable-gpu'],
|
// Runs tests without opening a browser.
|
||||||
|
: ['--headless', '--disable-gpu', 'window-size=1920,1080']
|
||||||
},
|
},
|
||||||
}],
|
}],
|
||||||
//
|
//
|
||||||
|
@ -288,7 +288,7 @@ describe('trigger node', function() {
|
|||||||
|
|
||||||
it('should be able to return things from flow and global context variables', function(done) {
|
it('should be able to return things from flow and global context variables', function(done) {
|
||||||
var spy = sinon.stub(RED.util, 'evaluateNodeProperty',
|
var spy = sinon.stub(RED.util, 'evaluateNodeProperty',
|
||||||
function(arg1, arg2, arg3, arg4) { return arg1; }
|
function(arg1, arg2, arg3, arg4, arg5) { if (arg5) { arg5(null, arg1) } else { return arg1; } }
|
||||||
);
|
);
|
||||||
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1:"foo", op1type:"flow", op2:"bar", op2type:"global", duration:"20", wires:[["n2"]] },
|
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", op1:"foo", op1type:"flow", op2:"bar", op2type:"global", duration:"20", wires:[["n2"]] },
|
||||||
{id:"n2", type:"helper"} ];
|
{id:"n2", type:"helper"} ];
|
||||||
@ -386,7 +386,7 @@ describe('trigger node', function() {
|
|||||||
it('should be able to extend the delay', function(done) {
|
it('should be able to extend the delay', function(done) {
|
||||||
this.timeout(5000); // add extra time for flake
|
this.timeout(5000); // add extra time for flake
|
||||||
var spy = sinon.stub(RED.util, 'evaluateNodeProperty',
|
var spy = sinon.stub(RED.util, 'evaluateNodeProperty',
|
||||||
function(arg1, arg2, arg3, arg4) { return arg1; }
|
function(arg1, arg2, arg3, arg4, arg5) { if (arg5) { arg5(null, arg1) } else { return arg1; } }
|
||||||
);
|
);
|
||||||
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", extend:"true", op1type:"flow", op1:"foo", op2:"bar", op2type:"global", duration:"100", wires:[["n2"]] },
|
var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", extend:"true", op1type:"flow", op1:"foo", op2:"bar", op2type:"global", duration:"100", wires:[["n2"]] },
|
||||||
{id:"n2", type:"helper"} ];
|
{id:"n2", type:"helper"} ];
|
||||||
@ -428,12 +428,10 @@ describe('trigger node', function() {
|
|||||||
n2.on("input", function(msg) {
|
n2.on("input", function(msg) {
|
||||||
try {
|
try {
|
||||||
if (c === 0) {
|
if (c === 0) {
|
||||||
console.log(c,Date.now() - ss,msg);
|
|
||||||
msg.should.have.a.property("payload", "Hello");
|
msg.should.have.a.property("payload", "Hello");
|
||||||
c += 1;
|
c += 1;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
console.log(c,Date.now() - ss,msg);
|
|
||||||
msg.should.have.a.property("payload", "World");
|
msg.should.have.a.property("payload", "World");
|
||||||
(Date.now() - ss).should.be.greaterThan(150);
|
(Date.now() - ss).should.be.greaterThan(150);
|
||||||
done();
|
done();
|
||||||
|
@ -460,7 +460,7 @@ describe('switch Node', function() {
|
|||||||
} catch(err) {
|
} catch(err) {
|
||||||
done(err);
|
done(err);
|
||||||
}
|
}
|
||||||
},100)
|
},500)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -821,4 +821,24 @@ describe('switch Node', function() {
|
|||||||
n1.receive({payload:1, parts:{index:0, count:4, id:222}});
|
n1.receive({payload:1, parts:{index:0, count:4, id:222}});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should handle invalid jsonata expression', function(done) {
|
||||||
|
|
||||||
|
var flow = [{id:"switchNode1",type:"switch",name:"switchNode",property:"$invalidExpression(payload)",propertyType:"jsonata",rules:[{"t":"btwn","v":"$sqrt(16)","vt":"jsonata","v2":"$sqrt(36)","v2t":"jsonata"}],checkall:true,outputs:1,wires:[["helperNode1"]]},
|
||||||
|
{id:"helperNode1", type:"helper", wires:[]}];
|
||||||
|
helper.load(switchNode, flow, function() {
|
||||||
|
var n1 = helper.getNode("switchNode1");
|
||||||
|
setTimeout(function() {
|
||||||
|
var logEvents = helper.log().args.filter(function (evt) {
|
||||||
|
return evt[0].type == "switch";
|
||||||
|
});
|
||||||
|
var evt = logEvents[0][0];
|
||||||
|
evt.should.have.property('id', "switchNode1");
|
||||||
|
evt.should.have.property('type', "switch");
|
||||||
|
done();
|
||||||
|
}, 150);
|
||||||
|
n1.receive({payload:1});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
**/
|
**/
|
||||||
|
|
||||||
var should = require("should");
|
var should = require("should");
|
||||||
|
var sinon = require("sinon");
|
||||||
|
|
||||||
var changeNode = require("../../../../nodes/core/logic/15-change.js");
|
var changeNode = require("../../../../nodes/core/logic/15-change.js");
|
||||||
var helper = require("node-red-node-test-helper");
|
var helper = require("node-red-node-test-helper");
|
||||||
@ -454,6 +455,28 @@ describe('change Node', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('reports invalid jsonata expression', function(done) {
|
||||||
|
var flow = [{"id":"changeNode1","type":"change",rules:[{"t":"set","p":"payload","to":"$invalid(payload)","tot":"jsonata"}],"name":"changeNode","wires":[["helperNode1"]]},
|
||||||
|
{id:"helperNode1", type:"helper", wires:[]}];
|
||||||
|
helper.load(changeNode, flow, function() {
|
||||||
|
var changeNode1 = helper.getNode("changeNode1");
|
||||||
|
var helperNode1 = helper.getNode("helperNode1");
|
||||||
|
sinon.spy(changeNode1,"error");
|
||||||
|
helperNode1.on("input", function(msg) {
|
||||||
|
done("Invalid jsonata expression passed message through");
|
||||||
|
});
|
||||||
|
changeNode1.receive({payload:"Hello World!"});
|
||||||
|
setTimeout(function() {
|
||||||
|
try {
|
||||||
|
changeNode1.error.called.should.be.true();
|
||||||
|
done();
|
||||||
|
} catch(err) {
|
||||||
|
done(err);
|
||||||
|
}
|
||||||
|
},50);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
describe('#change', function() {
|
describe('#change', function() {
|
||||||
it('changes the value of the message property', function(done) {
|
it('changes the value of the message property', function(done) {
|
||||||
|
@ -59,7 +59,8 @@ describe("api/editor/settings", function() {
|
|||||||
},
|
},
|
||||||
nodes: {
|
nodes: {
|
||||||
paletteEditorEnabled: function() { return true; },
|
paletteEditorEnabled: function() { return true; },
|
||||||
getCredentialKeyType: function() { return "test-key-type"}
|
getCredentialKeyType: function() { return "test-key-type"},
|
||||||
|
listContextStores: function() { return {default: "foo", stores: ["foo","bar"]}}
|
||||||
},
|
},
|
||||||
log: { error: console.error },
|
log: { error: console.error },
|
||||||
storage: {}
|
storage: {}
|
||||||
@ -73,6 +74,7 @@ describe("api/editor/settings", function() {
|
|||||||
}
|
}
|
||||||
res.body.should.have.property("httpNodeRoot","testHttpNodeRoot");
|
res.body.should.have.property("httpNodeRoot","testHttpNodeRoot");
|
||||||
res.body.should.have.property("version","testVersion");
|
res.body.should.have.property("version","testVersion");
|
||||||
|
res.body.should.have.property("context",{default: "foo", stores: ["foo","bar"]});
|
||||||
res.body.should.have.property("paletteCategories",["red","blue","green"]);
|
res.body.should.have.property("paletteCategories",["red","blue","green"]);
|
||||||
res.body.should.have.property("editorTheme",{test:456});
|
res.body.should.have.property("editorTheme",{test:456});
|
||||||
res.body.should.have.property("testNodeSetting","helloWorld");
|
res.body.should.have.property("testNodeSetting","helloWorld");
|
||||||
@ -95,7 +97,8 @@ describe("api/editor/settings", function() {
|
|||||||
},
|
},
|
||||||
nodes: {
|
nodes: {
|
||||||
paletteEditorEnabled: function() { return true; },
|
paletteEditorEnabled: function() { return true; },
|
||||||
getCredentialKeyType: function() { return "test-key-type"}
|
getCredentialKeyType: function() { return "test-key-type"},
|
||||||
|
listContextStores: function() { return {default: "foo", stores: ["foo","bar"]}}
|
||||||
},
|
},
|
||||||
log: { error: console.error },
|
log: { error: console.error },
|
||||||
storage: {}
|
storage: {}
|
||||||
@ -130,7 +133,8 @@ describe("api/editor/settings", function() {
|
|||||||
},
|
},
|
||||||
nodes: {
|
nodes: {
|
||||||
paletteEditorEnabled: function() { return true; },
|
paletteEditorEnabled: function() { return true; },
|
||||||
getCredentialKeyType: function() { return "test-key-type"}
|
getCredentialKeyType: function() { return "test-key-type"},
|
||||||
|
listContextStores: function() { return {default: "foo", stores: ["foo","bar"]}}
|
||||||
},
|
},
|
||||||
log: { error: console.error },
|
log: { error: console.error },
|
||||||
storage: {
|
storage: {
|
||||||
@ -169,7 +173,8 @@ describe("api/editor/settings", function() {
|
|||||||
},
|
},
|
||||||
nodes: {
|
nodes: {
|
||||||
paletteEditorEnabled: function() { return true; },
|
paletteEditorEnabled: function() { return true; },
|
||||||
getCredentialKeyType: function() { return "test-key-type"}
|
getCredentialKeyType: function() { return "test-key-type"},
|
||||||
|
listContextStores: function() { return {default: "foo", stores: ["foo","bar"]}}
|
||||||
},
|
},
|
||||||
log: { error: console.error },
|
log: { error: console.error },
|
||||||
storage: {
|
storage: {
|
||||||
@ -211,7 +216,8 @@ describe("api/editor/settings", function() {
|
|||||||
},
|
},
|
||||||
nodes: {
|
nodes: {
|
||||||
paletteEditorEnabled: function() { return true; },
|
paletteEditorEnabled: function() { return true; },
|
||||||
getCredentialKeyType: function() { return "test-key-type"}
|
getCredentialKeyType: function() { return "test-key-type"},
|
||||||
|
listContextStores: function() { return {default: "foo", stores: ["foo","bar"]}}
|
||||||
},
|
},
|
||||||
log: { error: console.error },
|
log: { error: console.error },
|
||||||
storage: {
|
storage: {
|
||||||
@ -248,8 +254,8 @@ describe("api/editor/settings", function() {
|
|||||||
},
|
},
|
||||||
nodes: {
|
nodes: {
|
||||||
paletteEditorEnabled: function() { return false; },
|
paletteEditorEnabled: function() { return false; },
|
||||||
getCredentialKeyType: function() { return "test-key-type"}
|
getCredentialKeyType: function() { return "test-key-type"},
|
||||||
|
listContextStores: function() { return {default: "foo", stores: ["foo","bar"]}}
|
||||||
},
|
},
|
||||||
log: { error: console.error },
|
log: { error: console.error },
|
||||||
storage: {}
|
storage: {}
|
||||||
|
@ -23,7 +23,7 @@ describe('context', function() {
|
|||||||
describe('local memory',function() {
|
describe('local memory',function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
Context.init({});
|
Context.init({});
|
||||||
return Context.load();
|
Context.load();
|
||||||
});
|
});
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
Context.clean({allNodes:{}});
|
Context.clean({allNodes:{}});
|
||||||
@ -124,7 +124,7 @@ describe('context', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('enumerates context keys', function() {
|
it('enumerates context keys - sync', function() {
|
||||||
var context = Context.get("1","flowA");
|
var context = Context.get("1","flowA");
|
||||||
|
|
||||||
var keys = context.keys();
|
var keys = context.keys();
|
||||||
@ -142,15 +142,63 @@ describe('context', function() {
|
|||||||
keys[1].should.equal("abc");
|
keys[1].should.equal("abc");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should enumerate only context keys when GlobalContext was given', function() {
|
it('enumerates context keys - async', function(done) {
|
||||||
Context.init({functionGlobalContext: {foo:"bar"}});
|
var context = Context.get("1","flowA");
|
||||||
return Context.load().then(function(){
|
|
||||||
var context = Context.get("1","flowA");
|
var keys = context.keys(function(err,keys) {
|
||||||
var keys = context.global.keys();
|
keys.should.be.an.Array();
|
||||||
keys.should.have.length(1);
|
keys.should.be.empty();
|
||||||
keys[0].should.equal("foo");
|
context.set("foo","bar");
|
||||||
|
keys = context.keys(function(err,keys) {
|
||||||
|
keys.should.have.length(1);
|
||||||
|
keys[0].should.equal("foo");
|
||||||
|
|
||||||
|
context.set("abc.def","bar");
|
||||||
|
keys = context.keys(function(err,keys) {
|
||||||
|
keys.should.have.length(2);
|
||||||
|
keys[1].should.equal("abc");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should enumerate only context keys when GlobalContext was given - sync', function() {
|
||||||
|
Context.init({functionGlobalContext: {foo:"bar"}});
|
||||||
|
Context.load().then(function(){
|
||||||
|
var context = Context.get("1","flowA");
|
||||||
|
context.global.set("foo2","bar2");
|
||||||
|
var keys = context.global.keys();
|
||||||
|
keys.should.have.length(2);
|
||||||
|
keys[0].should.equal("foo");
|
||||||
|
keys[1].should.equal("foo2");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should enumerate only context keys when GlobalContext was given - async', function(done) {
|
||||||
|
Context.init({functionGlobalContext: {foo:"bar"}});
|
||||||
|
Context.load().then(function(){
|
||||||
|
var context = Context.get("1","flowA");
|
||||||
|
context.global.set("foo2","bar2");
|
||||||
|
context.global.keys(function(err,keys) {
|
||||||
|
keys.should.have.length(2);
|
||||||
|
keys[0].should.equal("foo");
|
||||||
|
keys[1].should.equal("foo2");
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
it('returns functionGlobalContext value if store value undefined', function() {
|
||||||
|
Context.init({functionGlobalContext: {foo:"bar"}});
|
||||||
|
Context.load().then(function(){
|
||||||
|
var context = Context.get("1","flowA");
|
||||||
|
var v = context.global.get('foo');
|
||||||
|
v.should.equal('bar');
|
||||||
|
});
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('external context storage',function() {
|
describe('external context storage',function() {
|
||||||
@ -215,6 +263,11 @@ describe('context', function() {
|
|||||||
config:{}
|
config:{}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
var memoryStorage ={
|
||||||
|
memory:{
|
||||||
|
module: "memory"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
sandbox.reset();
|
sandbox.reset();
|
||||||
@ -226,18 +279,18 @@ describe('context', function() {
|
|||||||
describe('load modules',function(){
|
describe('load modules',function(){
|
||||||
it('should call open()', function() {
|
it('should call open()', function() {
|
||||||
Context.init({contextStorage:contextDefaultStorage});
|
Context.init({contextStorage:contextDefaultStorage});
|
||||||
return Context.load().then(function(){
|
Context.load().then(function(){
|
||||||
stubOpen.called.should.be.true();
|
stubOpen.called.should.be.true();
|
||||||
stubOpen2.called.should.be.true();
|
stubOpen2.called.should.be.true();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('should load memory module', function() {
|
it('should load memory module', function() {
|
||||||
Context.init({contextStorage:{memory:{module:"memory"}}});
|
Context.init({contextStorage:{memory:{module:"memory"}}});
|
||||||
return Context.load();
|
Context.load();
|
||||||
});
|
});
|
||||||
it('should load localfilesystem module', function() {
|
it('should load localfilesystem module', function() {
|
||||||
Context.init({contextStorage:{file:{module:"localfilesystem",config:{dir:resourcesDir}}}});
|
Context.init({contextStorage:{file:{module:"localfilesystem",config:{dir:resourcesDir}}}});
|
||||||
return Context.load();
|
Context.load();
|
||||||
});
|
});
|
||||||
it('should accept special storage name', function(done) {
|
it('should accept special storage name', function(done) {
|
||||||
Context.init({
|
Context.init({
|
||||||
@ -257,7 +310,7 @@ describe('context', function() {
|
|||||||
stubSet.calledWithExactly("1:flow","file","file2",cb).should.be.true();
|
stubSet.calledWithExactly("1:flow","file","file2",cb).should.be.true();
|
||||||
stubSet.calledWithExactly("1:flow","num","num3",cb).should.be.true();
|
stubSet.calledWithExactly("1:flow","num","num3",cb).should.be.true();
|
||||||
done();
|
done();
|
||||||
});
|
}).catch(done);
|
||||||
});
|
});
|
||||||
it('should ignore reserved storage name `_`', function(done) {
|
it('should ignore reserved storage name `_`', function(done) {
|
||||||
Context.init({contextStorage:{_:{module:testPlugin}}});
|
Context.init({contextStorage:{_:{module:testPlugin}}});
|
||||||
@ -271,7 +324,7 @@ describe('context', function() {
|
|||||||
stubGet.called.should.be.false();
|
stubGet.called.should.be.false();
|
||||||
stubKeys.called.should.be.false();
|
stubKeys.called.should.be.false();
|
||||||
done();
|
done();
|
||||||
});
|
}).catch(done);
|
||||||
});
|
});
|
||||||
it('should fail when using invalid default context', function(done) {
|
it('should fail when using invalid default context', function(done) {
|
||||||
Context.init({contextStorage:{default:"noexist"}});
|
Context.init({contextStorage:{default:"noexist"}});
|
||||||
@ -300,14 +353,15 @@ describe('context', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('close modules',function(){
|
describe('close modules',function(){
|
||||||
it('should call close()', function() {
|
it('should call close()', function(done) {
|
||||||
Context.init({contextStorage:contextDefaultStorage});
|
Context.init({contextStorage:contextDefaultStorage});
|
||||||
return Context.load().then(function(){
|
Context.load().then(function(){
|
||||||
return Context.close().then(function(){
|
return Context.close().then(function(){
|
||||||
stubClose.called.should.be.true();
|
stubClose.called.should.be.true();
|
||||||
stubClose2.called.should.be.true();
|
stubClose2.called.should.be.true();
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
});
|
}).catch(done);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -324,7 +378,7 @@ describe('context', function() {
|
|||||||
stubGet.calledWithExactly("1:flow","foo",cb).should.be.true();
|
stubGet.calledWithExactly("1:flow","foo",cb).should.be.true();
|
||||||
stubKeys.calledWithExactly("1:flow",cb).should.be.true();
|
stubKeys.calledWithExactly("1:flow",cb).should.be.true();
|
||||||
done();
|
done();
|
||||||
});
|
}).catch(done);
|
||||||
});
|
});
|
||||||
it('should store flow property to external context storage',function(done) {
|
it('should store flow property to external context storage',function(done) {
|
||||||
Context.init({contextStorage:contextStorage});
|
Context.init({contextStorage:contextStorage});
|
||||||
@ -338,7 +392,7 @@ describe('context', function() {
|
|||||||
stubGet.calledWithExactly("flow","foo",cb).should.be.true();
|
stubGet.calledWithExactly("flow","foo",cb).should.be.true();
|
||||||
stubKeys.calledWithExactly("flow",cb).should.be.true();
|
stubKeys.calledWithExactly("flow",cb).should.be.true();
|
||||||
done();
|
done();
|
||||||
});
|
}).catch(done);
|
||||||
});
|
});
|
||||||
it('should store global property to external context storage',function(done) {
|
it('should store global property to external context storage',function(done) {
|
||||||
Context.init({contextStorage:contextStorage});
|
Context.init({contextStorage:contextStorage});
|
||||||
@ -349,10 +403,10 @@ describe('context', function() {
|
|||||||
context.global.get("foo","test",cb);
|
context.global.get("foo","test",cb);
|
||||||
context.global.keys("test",cb);
|
context.global.keys("test",cb);
|
||||||
stubSet.calledWithExactly("global","foo","bar",cb).should.be.true();
|
stubSet.calledWithExactly("global","foo","bar",cb).should.be.true();
|
||||||
stubGet.calledWithExactly("global","foo",cb).should.be.true();
|
stubGet.calledWith("global","foo").should.be.true();
|
||||||
stubKeys.calledWithExactly("global",cb).should.be.true();
|
stubKeys.calledWith("global").should.be.true();
|
||||||
done();
|
done();
|
||||||
});
|
}).catch(done);
|
||||||
});
|
});
|
||||||
it('should store data to the default context when non-existent context storage was specified', function(done) {
|
it('should store data to the default context when non-existent context storage was specified', function(done) {
|
||||||
Context.init({contextStorage:contextDefaultStorage});
|
Context.init({contextStorage:contextDefaultStorage});
|
||||||
@ -369,7 +423,7 @@ describe('context', function() {
|
|||||||
stubGet2.calledWithExactly("1:flow","foo",cb).should.be.true();
|
stubGet2.calledWithExactly("1:flow","foo",cb).should.be.true();
|
||||||
stubKeys2.calledWithExactly("1:flow",cb).should.be.true();
|
stubKeys2.calledWithExactly("1:flow",cb).should.be.true();
|
||||||
done();
|
done();
|
||||||
});
|
}).catch(done);
|
||||||
});
|
});
|
||||||
it('should use the default context', function(done) {
|
it('should use the default context', function(done) {
|
||||||
Context.init({contextStorage:contextDefaultStorage});
|
Context.init({contextStorage:contextDefaultStorage});
|
||||||
@ -386,7 +440,7 @@ describe('context', function() {
|
|||||||
stubGet2.calledWithExactly("1:flow","foo",cb).should.be.true();
|
stubGet2.calledWithExactly("1:flow","foo",cb).should.be.true();
|
||||||
stubKeys2.calledWithExactly("1:flow",cb).should.be.true();
|
stubKeys2.calledWithExactly("1:flow",cb).should.be.true();
|
||||||
done();
|
done();
|
||||||
});
|
}).catch(done);
|
||||||
});
|
});
|
||||||
it('should use the alias of default context', function(done) {
|
it('should use the alias of default context', function(done) {
|
||||||
Context.init({contextStorage:contextDefaultStorage});
|
Context.init({contextStorage:contextDefaultStorage});
|
||||||
@ -403,7 +457,7 @@ describe('context', function() {
|
|||||||
stubGet2.calledWithExactly("1:flow","foo",cb).should.be.true();
|
stubGet2.calledWithExactly("1:flow","foo",cb).should.be.true();
|
||||||
stubKeys2.calledWithExactly("1:flow",cb).should.be.true();
|
stubKeys2.calledWithExactly("1:flow",cb).should.be.true();
|
||||||
done();
|
done();
|
||||||
});
|
}).catch(done);
|
||||||
});
|
});
|
||||||
it('should use default as the alias of other context', function(done) {
|
it('should use default as the alias of other context', function(done) {
|
||||||
Context.init({contextStorage:contextAlias});
|
Context.init({contextStorage:contextAlias});
|
||||||
@ -417,22 +471,16 @@ describe('context', function() {
|
|||||||
stubGet.calledWithExactly("1:flow","foo",cb).should.be.true();
|
stubGet.calledWithExactly("1:flow","foo",cb).should.be.true();
|
||||||
stubKeys.calledWithExactly("1:flow",cb).should.be.true();
|
stubKeys.calledWithExactly("1:flow",cb).should.be.true();
|
||||||
done();
|
done();
|
||||||
});
|
}).catch(done);
|
||||||
});
|
});
|
||||||
it('should throw an error using undefined storage for local context', function(done) {
|
it('should not throw an error using undefined storage for local context', function(done) {
|
||||||
Context.init({contextStorage:contextStorage});
|
Context.init({contextStorage:contextStorage});
|
||||||
Context.load().then(function(){
|
Context.load().then(function(){
|
||||||
var context = Context.get("1","flow");
|
var context = Context.get("1","flow");
|
||||||
var cb = function(){done("An error occurred")}
|
var cb = function(){done("An error occurred")}
|
||||||
context.get("local","nonexist",cb);
|
context.get("local","nonexist",cb);
|
||||||
should.fail(null, null, "An error was not thrown using undefined storage for local context");
|
done()
|
||||||
}).catch(function(err) {
|
}).catch(done);
|
||||||
if (err.name === "ContextError") {
|
|
||||||
done();
|
|
||||||
} else {
|
|
||||||
done(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
it('should throw an error using undefined storage for flow context', function(done) {
|
it('should throw an error using undefined storage for flow context', function(done) {
|
||||||
Context.init({contextStorage:contextStorage});
|
Context.init({contextStorage:contextStorage});
|
||||||
@ -440,39 +488,89 @@ describe('context', function() {
|
|||||||
var context = Context.get("1","flow");
|
var context = Context.get("1","flow");
|
||||||
var cb = function(){done("An error occurred")}
|
var cb = function(){done("An error occurred")}
|
||||||
context.flow.get("flow","nonexist",cb);
|
context.flow.get("flow","nonexist",cb);
|
||||||
should.fail(null, null, "An error was not thrown using undefined storage for flow context");
|
done();
|
||||||
}).catch(function(err) {
|
}).catch(done);
|
||||||
if (err.name === "ContextError") {
|
|
||||||
done();
|
|
||||||
} else {
|
|
||||||
done(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return functionGlobalContext value as a default - synchronous', function(done) {
|
||||||
|
var fGC = { "foo": 456 };
|
||||||
|
Context.init({contextStorage:memoryStorage, functionGlobalContext:fGC });
|
||||||
|
Context.load().then(function() {
|
||||||
|
var context = Context.get("1","flow");
|
||||||
|
// Get foo - should be value from fGC
|
||||||
|
var v = context.global.get("foo");
|
||||||
|
v.should.equal(456);
|
||||||
|
|
||||||
|
// Update foo - should not touch fGC object
|
||||||
|
context.global.set("foo","new value");
|
||||||
|
fGC.foo.should.equal(456);
|
||||||
|
|
||||||
|
// Get foo - should be the updated value
|
||||||
|
v = context.global.get("foo");
|
||||||
|
v.should.equal("new value");
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return functionGlobalContext value as a default - async', function(done) {
|
||||||
|
var fGC = { "foo": 456 };
|
||||||
|
Context.init({contextStorage:memoryStorage, functionGlobalContext:fGC });
|
||||||
|
Context.load().then(function() {
|
||||||
|
var context = Context.get("1","flow");
|
||||||
|
// Get foo - should be value from fGC
|
||||||
|
context.global.get("foo", function(err, v) {
|
||||||
|
if (err) {
|
||||||
|
done(err)
|
||||||
|
} else {
|
||||||
|
v.should.equal(456);
|
||||||
|
// Update foo - should not touch fGC object
|
||||||
|
context.global.set("foo","new value", function(err) {
|
||||||
|
if (err) {
|
||||||
|
done(err)
|
||||||
|
} else {
|
||||||
|
fGC.foo.should.equal(456);
|
||||||
|
// Get foo - should be the updated value
|
||||||
|
context.global.get("foo", function(err, v) {
|
||||||
|
if (err) {
|
||||||
|
done(err)
|
||||||
|
} else {
|
||||||
|
v.should.equal("new value");
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).catch(done);
|
||||||
|
})
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('delete context',function(){
|
describe('delete context',function(){
|
||||||
it('should not call delete() when external context storage is used', function() {
|
it('should not call delete() when external context storage is used', function(done) {
|
||||||
Context.init({contextStorage:contextDefaultStorage});
|
Context.init({contextStorage:contextDefaultStorage});
|
||||||
return Context.load().then(function(){
|
Context.load().then(function(){
|
||||||
Context.get("flowA");
|
Context.get("flowA");
|
||||||
return Context.delete("flowA").then(function(){
|
return Context.delete("flowA").then(function(){
|
||||||
stubDelete.called.should.be.false();
|
stubDelete.called.should.be.false();
|
||||||
stubDelete2.called.should.be.false();
|
stubDelete2.called.should.be.false();
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
});
|
}).catch(done);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('clean context',function(){
|
describe('clean context',function(){
|
||||||
it('should call clean()', function() {
|
it('should call clean()', function(done) {
|
||||||
Context.init({contextStorage:contextDefaultStorage});
|
Context.init({contextStorage:contextDefaultStorage});
|
||||||
return Context.load().then(function(){
|
Context.load().then(function(){
|
||||||
return Context.clean({allNodes:{}}).then(function(){
|
return Context.clean({allNodes:{}}).then(function(){
|
||||||
stubClean.calledWithExactly([]).should.be.true();
|
stubClean.calledWithExactly([]).should.be.true();
|
||||||
stubClean2.calledWithExactly([]).should.be.true();
|
stubClean2.calledWithExactly([]).should.be.true();
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
});
|
}).catch(done);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -29,7 +29,7 @@ describe('localfilesystem',function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
context = LocalFileSystem({dir: resourcesDir});
|
context = LocalFileSystem({dir: resourcesDir, cache: false});
|
||||||
return context.open();
|
return context.open();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -308,7 +308,7 @@ describe('localfilesystem',function() {
|
|||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
}).catch(done);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -103,19 +103,6 @@ describe('memory',function() {
|
|||||||
keysY[0].should.equal("hoge");
|
keysY[0].should.equal("hoge");
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should enumerate only context keys when GlobalContext was given', function() {
|
|
||||||
var keys = context.keys("global");
|
|
||||||
keys.should.be.an.Array();
|
|
||||||
keys.should.be.empty();
|
|
||||||
|
|
||||||
var data = {
|
|
||||||
foo: "bar"
|
|
||||||
}
|
|
||||||
context.setGlobalContext(data);
|
|
||||||
keys = context.keys("global");
|
|
||||||
keys.should.have.length(1);
|
|
||||||
keys[0].should.equal("foo");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#delete',function() {
|
describe('#delete',function() {
|
||||||
@ -163,17 +150,4 @@ describe('memory',function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#setGlobalContext',function() {
|
|
||||||
it('should initialize global context with argument', function() {
|
|
||||||
var keys = context.keys("global");
|
|
||||||
keys.should.be.an.Array();
|
|
||||||
keys.should.be.empty();
|
|
||||||
|
|
||||||
var data = {
|
|
||||||
foo: "bar"
|
|
||||||
}
|
|
||||||
context.setGlobalContext(data);
|
|
||||||
context.get("global","foo").should.equal("bar");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
@ -307,6 +307,10 @@ describe("red/util", function() {
|
|||||||
},{});
|
},{});
|
||||||
result.should.eql("123");
|
result.should.eql("123");
|
||||||
});
|
});
|
||||||
|
it('returns null', function() {
|
||||||
|
var result = util.evaluateNodeProperty(null,'null');
|
||||||
|
(result === null).should.be.true();
|
||||||
|
})
|
||||||
describe('environment variable', function() {
|
describe('environment variable', function() {
|
||||||
before(function() {
|
before(function() {
|
||||||
process.env.NR_TEST_A = "foo";
|
process.env.NR_TEST_A = "foo";
|
||||||
@ -454,6 +458,30 @@ describe("red/util", function() {
|
|||||||
var result = util.evaluateJSONataExpression(expr,{payload:"hello"});
|
var result = util.evaluateJSONataExpression(expr,{payload:"hello"});
|
||||||
should.not.exist(result);
|
should.not.exist(result);
|
||||||
});
|
});
|
||||||
|
it('handles async flow context access', function(done) {
|
||||||
|
var expr = util.prepareJSONataExpression('$flowContext("foo")',{context:function() { return {flow:{get: function(key,callback) { setTimeout(()=>{callback(null,{'foo':'bar'}[key])},10)}}}}});
|
||||||
|
util.evaluateJSONataExpression(expr,{payload:"hello"},function(err,value) {
|
||||||
|
try {
|
||||||
|
should.not.exist(err);
|
||||||
|
value.should.eql("bar");
|
||||||
|
done();
|
||||||
|
} catch(err2) {
|
||||||
|
done(err2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
it('handles async global context access', function(done) {
|
||||||
|
var expr = util.prepareJSONataExpression('$globalContext("foo")',{context:function() { return {global:{get: function(key,callback) { setTimeout(()=>{callback(null,{'foo':'bar'}[key])},10)}}}}});
|
||||||
|
util.evaluateJSONataExpression(expr,{payload:"hello"},function(err,value) {
|
||||||
|
try {
|
||||||
|
should.not.exist(err);
|
||||||
|
value.should.eql("bar");
|
||||||
|
done();
|
||||||
|
} catch(err2) {
|
||||||
|
done(err2);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user