diff --git a/nodes/StateMachine/Statemachine.node.ts b/nodes/StateMachine/Statemachine.node.ts index a9039c0..0d9743f 100644 --- a/nodes/StateMachine/Statemachine.node.ts +++ b/nodes/StateMachine/Statemachine.node.ts @@ -48,6 +48,18 @@ export class Statemachine implements INodeType { description: 'Set the value of you want to store in state', action: 'Set the value of you want to store in state', }, + { + name: 'Clean', + value: 'clean', + description: 'Clean the stored value from the state', + action: 'Clean the stored value from the state', + }, + { + name: 'Error Handling', + value: 'error_handling', + description: 'If your workflow has an error, this mode can handle it', + action: 'If your workflow has an error this mode can handle it', + }, ], default: 'store', }, @@ -61,20 +73,39 @@ export class Statemachine implements INodeType { type: 'string', displayOptions: { show: { - operation: ['store'], + operation: ['store', 'clean'], }, }, default: '', required: true, description: 'Name of the key to get from Redis', }, + { + displayName: 'Execution ID', + name: 'executionId', + type: 'hidden', + required: true, + default: '={{ $execution.id }}', + }, + { + displayName: 'Previous Execution ID', + name: 'previousExecutionId', + type: 'string', + displayOptions: { + show: { + operation: ['error_handling'], + }, + }, + required: true, + default: '', + }, { displayName: 'Global Statement', name: 'global', type: 'boolean', displayOptions: { show: { - operation: ['store'], + operation: ['store', 'clean'], }, }, default: true, @@ -195,7 +226,8 @@ export class Statemachine implements INodeType { } const client = redis.createClient(redisOptions); - + const operation = this.getNodeParameter('operation', 0); + const executionId = this.getNodeParameter('executionId', 0); client.on('error', (err: Error) => { client.quit(); reject(err); @@ -210,34 +242,63 @@ export class Statemachine implements INodeType { let item: INodeExecutionData; for (let itemIndex = 0; itemIndex < items.length; itemIndex++) { item = { json: {} }; - const value = this.getNodeParameter('value', itemIndex) as string; - const expire = this.getNodeParameter('expire', itemIndex, false) as boolean; - const global = this.getNodeParameter('global', itemIndex, false) as boolean; - - const ttl = this.getNodeParameter('ttl', itemIndex, -1) as number; - - const hash = crypto.createHash('sha256'); - hash.update(JSON.stringify(value)); - let key; - if (global) { - key = `n8n-state-global-${hash.digest('hex')}`; - } else { - key = `n8n-state-workflow-${meta.id}-${hash.digest('hex')}`; + if (operation === 'store' || operation === 'clean') { + const value = this.getNodeParameter('value', itemIndex) as string; + const global = this.getNodeParameter('global', itemIndex, false) as boolean; + const hash = crypto.createHash('sha256'); + hash.update(JSON.stringify(value)); + let key; + if (global) { + key = `n8n-state-global-${hash.digest('hex')}`; + } else { + key = `n8n-state-workflow-${meta.id}-${hash.digest('hex')}`; + } + + if (operation === 'store') { + const data = (await getValue(client, key)) || null; + + if (data === null) { + const expire = this.getNodeParameter('expire', itemIndex, false) as boolean; + const ttl = this.getNodeParameter('ttl', itemIndex, -1) as number; + + await setValue(client, key, value, expire, ttl); + item.json.state = value; + const clientPush = util.promisify(client.LPUSH).bind(client); + // @ts-ignore: typescript not understanding generic function signatures + await clientPush(`${meta.id}-${executionId}`, key); + returnItems.push(item); + } else { + break; + } + } else if (operation === 'clean') { + const clientDel = util.promisify(client.del).bind(client); + // @ts-ignore + await clientDel(key); + } } + if (operation === 'error_handling') { + const previousExecutionId = this.getNodeParameter( + 'previousExecutionId', + itemIndex, + ) as string; + + const clientLLen = util.promisify(client.LLEN).bind(client); + const keysRange = await clientLLen(`${meta.id}-${previousExecutionId}`); - const data = (await getValue(client, key)) || null; + for (let keyIndex = 0; keyIndex < keysRange; keyIndex++) { + const clientLindex = util.promisify(client.lindex).bind(client); + const keyName = await clientLindex(`${meta.id}-${previousExecutionId}`, keyIndex); - if (data === null) { - await setValue(client, key, value, expire, ttl); - item.json.state = value; - returnItems.push(item); - } else { - break; + const clientDel = util.promisify(client.del).bind(client); + // @ts-ignore + await clientDel(keyName); + } } } client.quit(); resolve(this.prepareOutputData(returnItems)); } catch (error) { + console.log(error); reject(error); } }); diff --git a/package.json b/package.json index 6f0ec75..60c9660 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "n8n-nodes-statemachine", - "version": "0.1.0", + "version": "0.1.1", "description": "A simple state machine refers to a programming concept where an application or workflow is divided into a series of states or steps, and the program progresses from one state to the next based on certain conditions or triggers", "keywords": [ "n8n-community-node-package", @@ -8,7 +8,7 @@ "statemachine", "state" ], - "license": "Apache-2.0", + "license": "MIT", "homepage": "https://github.com/pigri/n8n-nodes-statemachine", "author": { "name": "David Papp", @@ -22,10 +22,10 @@ "scripts": { "build": "tsc && gulp build:icons", "dev": "tsc --watch", - "format": "prettier nodes credentials --write", - "lint": "eslint nodes credentials package.json", - "lintfix": "eslint nodes credentials package.json --fix", - "prepublishOnly": "npm run build && npm run lint -c .eslintrc.prepublish.js nodes credentials package.json" + "format": "prettier nodes --write", + "lint": "eslint nodes package.json", + "lintfix": "eslint nodes package.json --fix", + "prepublishOnly": "npm run build && npm run lint -c .eslintrc.prepublish.js nodes package.json" }, "files": [ "dist"