diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/context/index.js b/packages/node_modules/@node-red/runtime/lib/nodes/context/index.js index 967fae295..aff44db04 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/context/index.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/context/index.js @@ -14,30 +14,30 @@ * limitations under the License. **/ -var clone = require("clone"); -var log = require("@node-red/util").log; -var util = require("@node-red/util").util; -var memory = require("./memory"); +const clone = require("clone"); +const log = require("@node-red/util").log; +const util = require("@node-red/util").util; +const memory = require("./memory"); -var settings; +let settings; // A map of scope id to context instance -var contexts = {}; +let contexts = {}; // A map of store name to instance -var stores = {}; -var storeList = []; -var defaultStore; +let stores = {}; +let storeList = []; +let defaultStore; // Whether there context storage has been configured or left as default -var hasConfiguredStore = false; +let hasConfiguredStore = false; // Unknown Stores -var unknownStores = {}; +let unknownStores = {}; function logUnknownStore(name) { if (name) { - var count = unknownStores[name] || 0; + let count = unknownStores[name] || 0; if (count == 0) { log.warn(log._("context.unknown-store", {name: name})); count++; @@ -52,8 +52,8 @@ function init(_settings) { stores = {}; storeList = []; hasConfiguredStore = false; - var seed = settings.functionGlobalContext || {}; - contexts['global'] = createContext("global",seed); + initialiseGlobalContext(); + // create a default memory store - used by the unit tests that skip the full // `load()` initialisation sequence. // If the user has any stores configured, this will be disgarded @@ -61,6 +61,11 @@ function init(_settings) { defaultStore = "memory"; } +function initialiseGlobalContext() { + const seed = settings.functionGlobalContext || {}; + contexts['global'] = createContext("global",seed); +} + function load() { return new Promise(function(resolve,reject) { // load & init plugins in settings.contextStorage @@ -233,12 +238,15 @@ function validateContextKey(key) { function createContext(id,seed,parent) { // Seed is only set for global context - sourced from functionGlobalContext - var scope = id; - var obj = seed || {}; - var seedKeys; - var insertSeedValues; + const scope = id; + const obj = {}; + let seedKeys; + let insertSeedValues; if (seed) { seedKeys = Object.keys(seed); + seedKeys.forEach(key => { + obj[key] = seed[key]; + }) insertSeedValues = function(keys,values) { if (!Array.isArray(keys)) { if (values[0] === undefined) { @@ -540,8 +548,28 @@ function getContext(nodeId, flowId) { return newContext; } +/** + * Delete the context of the given node/flow/global + * + * If the user has configured a context store, this + * will no-op a request to delete node/flow context. + */ function deleteContext(id,flowId) { - if(!hasConfiguredStore){ + if (id === "global") { + // 1. delete global from all configured stores + var promises = []; + for(var plugin in stores){ + if(stores.hasOwnProperty(plugin)){ + promises.push(stores[plugin].delete('global')); + } + } + return Promise.all(promises).then(function() { + // 2. delete global context + delete contexts['global']; + // 3. reinitialise global context + initialiseGlobalContext(); + }) + } else if (!hasConfiguredStore) { // only delete context if there's no configured storage. var contextId = id; if (flowId) { @@ -549,12 +577,19 @@ function deleteContext(id,flowId) { } delete contexts[contextId]; return stores["_"].delete(contextId); - }else{ + } else { return Promise.resolve(); } } +/** + * Delete any contexts that are no longer in use + * @param flowConfig object includes allNodes as object of id->node + * + * If flowConfig is undefined, all flow/node contexts will be removed + **/ function clean(flowConfig) { + flowConfig = flowConfig || { allNodes: {} }; var promises = []; for(var plugin in stores){ if(stores.hasOwnProperty(plugin)){ @@ -572,6 +607,16 @@ function clean(flowConfig) { return Promise.all(promises); } +/** + * Deletes all contexts, including global and reinitialises global to + * initial state. + */ +function clear() { + return clean().then(function() { + return deleteContext('global') + }) +} + function close() { var promises = []; for(var plugin in stores){ @@ -594,5 +639,6 @@ module.exports = { getFlowContext:getFlowContext, delete: deleteContext, clean: clean, + clear: clear, close: close }; diff --git a/packages/node_modules/@node-red/runtime/lib/nodes/index.js b/packages/node_modules/@node-red/runtime/lib/nodes/index.js index b2346176f..5b859a5f8 100644 --- a/packages/node_modules/@node-red/runtime/lib/nodes/index.js +++ b/packages/node_modules/@node-red/runtime/lib/nodes/index.js @@ -206,6 +206,7 @@ module.exports = { eachNode: flows.eachNode, getContext: context.get, + clearContext: context.clear, installerEnabled: registry.installerEnabled, installModule: installModule, diff --git a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/index.js b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/index.js index b98900f49..c87be015b 100644 --- a/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/index.js +++ b/packages/node_modules/@node-red/runtime/lib/storage/localfilesystem/projects/index.js @@ -378,15 +378,23 @@ function getActiveProject(user) { } function reloadActiveProject(action) { + // Stop the current flows return runtime.nodes.stopFlows().then(function() { - return runtime.nodes.loadFlows(true).then(function() { - events.emit("runtime-event",{id:"project-update", payload:{ project: activeProject.name, action:action}}); - }).catch(function(err) { - // We're committed to the project change now, so notify editors - // that it has changed. - events.emit("runtime-event",{id:"project-update", payload:{ project: activeProject.name, action:action}}); - throw err; - }); + // Reset context to remove any old values + return runtime.nodes.clearContext().then(function() { + // Load the new project flows and start them + return runtime.nodes.loadFlows(true).then(function() { + events.emit("runtime-event",{id:"project-update", payload:{ project: activeProject.name, action:action}}); + }).catch(function(err) { + // We're committed to the project change now, so notify editors + // that it has changed. + events.emit("runtime-event",{id:"project-update", payload:{ project: activeProject.name, action:action}}); + throw err; + }); + }) + }).catch(function(err) { + console.log(err.stack); + throw err; }); } function createProject(user, metadata) { diff --git a/test/unit/@node-red/runtime/lib/nodes/context/index_spec.js b/test/unit/@node-red/runtime/lib/nodes/context/index_spec.js index 7e05eba8f..591504e55 100644 --- a/test/unit/@node-red/runtime/lib/nodes/context/index_spec.js +++ b/test/unit/@node-red/runtime/lib/nodes/context/index_spec.js @@ -156,6 +156,22 @@ describe('context', function() { }); }); + it('deletes global context',function() { + Context.init({functionGlobalContext: {foo:"bar"}}); + return Context.load().then(function(){ + var globalContextA = Context.get("global") + + globalContextA.get('foo').should.eql('bar') + globalContextA.set("another","value"); + + return Context.delete("global").then(function(){ + var globalContextB = Context.get("global") + globalContextB.get('foo').should.eql('bar') + should.not.exist(globalContextB.get("another")); + }); + }); + }); + it('enumerates context keys - sync', function() { var flowContextA = Context.getFlowContext("flowA") var context = Context.get("1","flowA"); @@ -316,6 +332,31 @@ describe('context', function() { }); }); + + describe("clear", function() { + it('clears all context',function() { + Context.init({functionGlobalContext: {foo:"bar"}}); + return Context.load().then(function(){ + var globalContextA = Context.get("global") + globalContextA.get('foo').should.eql('bar') + globalContextA.set("another","value"); + + var flowContextA = Context.getFlowContext("flowA") + flowContextA.set("foo","abc"); + flowContextA.get("foo").should.equal("abc"); + + return Context.clear().then(function(){ + var globalContextB = Context.getFlowContext("global") + globalContextB.get('foo').should.eql('bar') + should.not.exist(globalContextB.get("another")); + + flowContextA = Context.getFlowContext("flowA") + should.not.exist(flowContextA.get("foo")) + + }); + }); + }); + }) }); describe('external context storage',function() {