2019-06-23 03:35:23 -07:00
import { IExecuteFunctions } from 'n8n-core' ;
import {
GenericValue ,
IDataObject ,
INodeExecutionData ,
INodeType ,
INodeTypeDescription ,
2021-04-16 09:33:36 -07:00
NodeOperationError ,
2019-06-23 03:35:23 -07:00
} from 'n8n-workflow' ;
import { set } from 'lodash' ;
import * as redis from 'redis' ;
import * as util from 'util' ;
export class Redis implements INodeType {
description : INodeTypeDescription = {
displayName : 'Redis' ,
name : 'redis' ,
2021-03-25 09:10:02 -07:00
icon : 'file:redis.svg' ,
2019-06-23 03:35:23 -07:00
group : [ 'input' ] ,
version : 1 ,
2021-07-03 05:40:16 -07:00
description : 'Get, send and update data in Redis' ,
2019-06-23 03:35:23 -07:00
defaults : {
name : 'Redis' ,
} ,
inputs : [ 'main' ] ,
outputs : [ 'main' ] ,
credentials : [
{
name : 'redis' ,
required : true ,
2020-10-22 06:46:03 -07:00
} ,
2019-06-23 03:35:23 -07:00
] ,
properties : [
{
displayName : 'Operation' ,
name : 'operation' ,
type : 'options' ,
options : [
{
name : 'Delete' ,
value : 'delete' ,
2020-09-14 00:58:01 -07:00
description : 'Delete a key from Redis.' ,
2019-06-23 03:35:23 -07:00
} ,
{
name : 'Get' ,
value : 'get' ,
2020-09-14 00:58:01 -07:00
description : 'Get the value of a key from Redis.' ,
2019-06-23 03:35:23 -07:00
} ,
{
name : 'Info' ,
value : 'info' ,
description : 'Returns generic information about the Redis instance.' ,
} ,
2021-05-07 16:33:14 -07:00
{
name : 'Increment' ,
value : 'incr' ,
description : 'Atomically increments a key by 1. Creates the key if it does not exist.' ,
} ,
2019-06-23 03:35:23 -07:00
{
name : 'Keys' ,
value : 'keys' ,
description : 'Returns all the keys matching a pattern.' ,
} ,
{
name : 'Set' ,
value : 'set' ,
2020-09-14 00:58:01 -07:00
description : 'Set the value of a key in redis.' ,
2019-06-23 03:35:23 -07:00
} ,
] ,
default : 'info' ,
description : 'The operation to perform.' ,
} ,
// ----------------------------------
// get
// ----------------------------------
{
displayName : 'Name' ,
name : 'propertyName' ,
type : 'string' ,
displayOptions : {
show : {
operation : [
2020-10-22 06:46:03 -07:00
'get' ,
2019-06-23 03:35:23 -07:00
] ,
} ,
} ,
default : 'propertyName' ,
required : true ,
2021-11-25 09:10:06 -08:00
description : 'Name of the property to write received data to. Supports dot-notation. Example: "data.person[0].name"' ,
2019-06-23 03:35:23 -07:00
} ,
{
displayName : 'Key' ,
name : 'key' ,
type : 'string' ,
displayOptions : {
show : {
operation : [
2020-10-22 06:46:03 -07:00
'delete' ,
2019-06-23 03:35:23 -07:00
] ,
} ,
} ,
default : '' ,
required : true ,
description : 'Name of the key to delete from Redis.' ,
} ,
{
displayName : 'Key' ,
name : 'key' ,
type : 'string' ,
displayOptions : {
show : {
operation : [
2020-10-22 06:46:03 -07:00
'get' ,
2019-06-23 03:35:23 -07:00
] ,
} ,
} ,
default : '' ,
required : true ,
description : 'Name of the key to get from Redis.' ,
} ,
{
displayName : 'Key Type' ,
name : 'keyType' ,
type : 'options' ,
displayOptions : {
show : {
operation : [
2020-10-22 06:46:03 -07:00
'get' ,
2019-06-23 03:35:23 -07:00
] ,
} ,
} ,
options : [
{
name : 'Automatic' ,
value : 'automatic' ,
description : 'Requests the type before requesting the data (slower).' ,
} ,
{
name : 'Hash' ,
value : 'hash' ,
description : 'Data in key is of type "hash".' ,
} ,
{
name : 'String' ,
value : 'string' ,
description : 'Data in key is of type "string".' ,
} ,
{
name : 'List' ,
value : 'list' ,
description : 'Data in key is of type "lists".' ,
} ,
{
name : 'Sets' ,
value : 'sets' ,
description : 'Data in key is of type "sets".' ,
} ,
] ,
default : 'automatic' ,
description : 'The type of the key to get.' ,
} ,
2020-05-05 06:52:47 -07:00
{
displayName : 'Options' ,
name : 'options' ,
type : 'collection' ,
displayOptions : {
show : {
operation : [
2020-10-22 06:46:03 -07:00
'get' ,
2020-05-05 06:52:47 -07:00
] ,
} ,
} ,
placeholder : 'Add Option' ,
default : { } ,
options : [
{
displayName : 'Dot Notation' ,
name : 'dotNotation' ,
type : 'boolean' ,
default : true ,
2021-11-25 09:10:06 -08:00
description : ` <p>By default, dot-notation is used in property names. This means that "a.b" will set the property "b" underneath "a" so { "a": { "b": value} }.<p></p>If that is not intended this can be deactivated, it will then set { "a.b": value } instead.</p>
2020-05-05 06:52:47 -07:00
` ,
} ,
] ,
} ,
2021-05-07 16:33:14 -07:00
// ----------------------------------
// incr
// ----------------------------------
{
displayName : 'Key' ,
name : 'key' ,
type : 'string' ,
displayOptions : {
show : {
operation : [
'incr' ,
] ,
} ,
} ,
default : '' ,
required : true ,
description : 'Name of the key to increment.' ,
} ,
{
displayName : 'Expire' ,
name : 'expire' ,
type : 'boolean' ,
displayOptions : {
show : {
operation : [
'incr' ,
] ,
} ,
} ,
default : false ,
description : 'Set a timeout on key?' ,
} ,
{
displayName : 'TTL' ,
name : 'ttl' ,
type : 'number' ,
typeOptions : {
minValue : 1 ,
} ,
displayOptions : {
show : {
operation : [
'incr' ,
] ,
expire : [
true ,
] ,
} ,
} ,
default : 60 ,
description : 'Number of seconds before key expiration.' ,
} ,
2019-06-23 03:35:23 -07:00
// ----------------------------------
// keys
// ----------------------------------
{
displayName : 'Key Pattern' ,
name : 'keyPattern' ,
type : 'string' ,
displayOptions : {
show : {
operation : [
2020-10-22 06:46:03 -07:00
'keys' ,
2019-06-23 03:35:23 -07:00
] ,
} ,
} ,
default : '' ,
required : true ,
description : 'The key pattern for the keys to return.' ,
} ,
// ----------------------------------
// set
// ----------------------------------
{
displayName : 'Key' ,
name : 'key' ,
type : 'string' ,
displayOptions : {
show : {
operation : [
2020-10-22 06:46:03 -07:00
'set' ,
2019-06-23 03:35:23 -07:00
] ,
} ,
} ,
default : '' ,
required : true ,
description : 'Name of the key to set in Redis.' ,
} ,
{
displayName : 'Value' ,
name : 'value' ,
type : 'string' ,
displayOptions : {
show : {
operation : [
2020-10-22 06:46:03 -07:00
'set' ,
2019-06-23 03:35:23 -07:00
] ,
} ,
} ,
default : '' ,
description : 'The value to write in Redis.' ,
} ,
{
displayName : 'Key Type' ,
name : 'keyType' ,
type : 'options' ,
displayOptions : {
show : {
operation : [
2020-10-22 06:46:03 -07:00
'set' ,
2019-06-23 03:35:23 -07:00
] ,
} ,
} ,
options : [
{
name : 'Automatic' ,
value : 'automatic' ,
description : 'Tries to figure out the type automatically depending on the data.' ,
} ,
{
name : 'Hash' ,
value : 'hash' ,
description : 'Data in key is of type "hash".' ,
} ,
{
name : 'String' ,
value : 'string' ,
description : 'Data in key is of type "string".' ,
} ,
{
name : 'List' ,
value : 'list' ,
description : 'Data in key is of type "lists".' ,
} ,
{
name : 'Sets' ,
value : 'sets' ,
description : 'Data in key is of type "sets".' ,
} ,
] ,
default : 'automatic' ,
description : 'The type of the key to set.' ,
} ,
2020-01-08 02:27:39 -08:00
{
displayName : 'Expire' ,
name : 'expire' ,
type : 'boolean' ,
2020-01-08 03:22:18 -08:00
displayOptions : {
show : {
operation : [
2020-10-22 06:46:03 -07:00
'set' ,
2020-01-08 03:22:18 -08:00
] ,
} ,
} ,
2020-01-08 02:27:39 -08:00
default : false ,
description : 'Set a timeout on key ?' ,
} ,
{
displayName : 'TTL' ,
name : 'ttl' ,
type : 'number' ,
typeOptions : {
minValue : 1 ,
} ,
displayOptions : {
show : {
operation : [
2020-10-22 06:46:03 -07:00
'set' ,
2020-01-08 02:27:39 -08:00
] ,
expire : [
true ,
] ,
} ,
} ,
default : 60 ,
description : 'Number of seconds before key expiration.' ,
2020-10-22 06:46:03 -07:00
} ,
] ,
2019-06-23 03:35:23 -07:00
} ;
execute ( this : IExecuteFunctions ) : Promise < INodeExecutionData [ ] [ ] > {
// Parses the given value in a number if it is one else returns a string
2020-05-05 06:52:47 -07:00
function getParsedValue ( value : string ) : string | number {
2019-06-23 03:35:23 -07:00
if ( value . match ( /^[\d\.]+$/ ) === null ) {
// Is a string
return value ;
} else {
// Is a number
return parseFloat ( value ) ;
}
}
// Converts the Redis Info String into an object
function convertInfoToObject ( stringData : string ) : IDataObject {
const returnData : IDataObject = { } ;
2020-05-05 06:52:47 -07:00
let key : string , value : string ;
2019-06-23 03:35:23 -07:00
for ( const line of stringData . split ( '\n' ) ) {
if ( [ '#' , '' ] . includes ( line . charAt ( 0 ) ) ) {
continue ;
}
[ key , value ] = line . split ( ':' ) ;
if ( key === undefined || value === undefined ) {
continue ;
}
value = value . trim ( ) ;
if ( value . includes ( '=' ) ) {
returnData [ key ] = { } ;
let key2 : string , value2 : string ;
for ( const keyValuePair of value . split ( ',' ) ) {
[ key2 , value2 ] = keyValuePair . split ( '=' ) ;
( returnData [ key ] as IDataObject ) [ key2 ] = getParsedValue ( value2 ) ;
}
} else {
returnData [ key ] = getParsedValue ( value ) ;
}
}
return returnData ;
}
async function getValue ( client : redis.RedisClient , keyName : string , type ? : string ) {
if ( type === undefined || type === 'automatic' ) {
// Request the type first
const clientType = util . promisify ( client . type ) . bind ( client ) ;
type = await clientType ( keyName ) ;
}
if ( type === 'string' ) {
const clientGet = util . promisify ( client . get ) . bind ( client ) ;
return await clientGet ( keyName ) ;
} else if ( type === 'hash' ) {
const clientHGetAll = util . promisify ( client . hgetall ) . bind ( client ) ;
return await clientHGetAll ( keyName ) ;
} else if ( type === 'list' ) {
const clientLRange = util . promisify ( client . lrange ) . bind ( client ) ;
return await clientLRange ( keyName , 0 , - 1 ) ;
} else if ( type === 'sets' ) {
const clientSMembers = util . promisify ( client . smembers ) . bind ( client ) ;
return await clientSMembers ( keyName ) ;
}
}
2021-04-16 09:33:36 -07:00
const setValue = async ( client : redis.RedisClient , keyName : string , value : string | number | object | string [ ] | number [ ] , expire : boolean , ttl : number , type ? : string ) = > {
2019-06-23 03:35:23 -07:00
if ( type === undefined || type === 'automatic' ) {
// Request the type first
if ( typeof value === 'string' ) {
type = 'string' ;
} else if ( Array . isArray ( value ) ) {
type = 'list' ;
} else if ( typeof value === 'object' ) {
type = 'hash' ;
} else {
2021-04-16 09:33:36 -07:00
throw new NodeOperationError ( this . getNode ( ) , 'Could not identify the type to set. Please set it manually!' ) ;
2019-06-23 03:35:23 -07:00
}
}
if ( type === 'string' ) {
const clientSet = util . promisify ( client . set ) . bind ( client ) ;
2020-01-08 02:27:39 -08:00
await clientSet ( keyName , value . toString ( ) ) ;
2019-06-23 03:35:23 -07:00
} else if ( type === 'hash' ) {
const clientHset = util . promisify ( client . hset ) . bind ( client ) ;
for ( const key of Object . keys ( value ) ) {
2020-07-02 22:31:08 -07:00
// @ts-ignore
2019-06-23 03:35:23 -07:00
await clientHset ( keyName , key , ( value as IDataObject ) [ key ] ! . toString ( ) ) ;
}
} else if ( type === 'list' ) {
const clientLset = util . promisify ( client . lset ) . bind ( client ) ;
for ( let index = 0 ; index < ( value as string [ ] ) . length ; index ++ ) {
await clientLset ( keyName , index , ( value as IDataObject ) [ index ] ! . toString ( ) ) ;
}
}
2020-01-08 02:27:39 -08:00
if ( expire === true ) {
const clientExpire = util . promisify ( client . expire ) . bind ( client ) ;
await clientExpire ( keyName , ttl ) ;
}
return ;
2021-04-16 09:33:36 -07:00
} ;
2019-06-23 03:35:23 -07:00
2021-08-20 09:57:30 -07:00
return new Promise ( async ( resolve , reject ) = > {
2019-06-23 03:35:23 -07:00
// TODO: For array and object fields it should not have a "value" field it should
// have a parameter field for a path. Because it is not possible to set
// array, object via parameter directly (should maybe be possible?!?!)
// Should maybe have a parameter which is JSON.
2021-08-20 09:57:30 -07:00
const credentials = await this . getCredentials ( 'redis' ) ;
2019-06-23 03:35:23 -07:00
if ( credentials === undefined ) {
2021-04-16 09:33:36 -07:00
throw new NodeOperationError ( this . getNode ( ) , 'No credentials got returned!' ) ;
2019-06-23 03:35:23 -07:00
}
const redisOptions : redis.ClientOpts = {
host : credentials.host as string ,
port : credentials.port as number ,
} ;
if ( credentials . password ) {
redisOptions . password = credentials . password as string ;
}
const client = redis . createClient ( redisOptions ) ;
const operation = this . getNodeParameter ( 'operation' , 0 ) as string ;
client . on ( 'error' , ( err : Error ) = > {
2021-03-26 11:02:08 -07:00
client . quit ( ) ;
2019-06-23 03:35:23 -07:00
reject ( err ) ;
} ) ;
client . on ( 'ready' , async ( err : Error | null ) = > {
2021-04-30 13:38:51 -07:00
try {
if ( operation === 'info' ) {
const clientInfo = util . promisify ( client . info ) . bind ( client ) ;
const result = await clientInfo ( ) ;
resolve ( this . prepareOutputData ( [ { json : convertInfoToObject ( result as unknown as string ) } ] ) ) ;
client . quit ( ) ;
2021-05-07 16:33:14 -07:00
} else if ( [ 'delete' , 'get' , 'keys' , 'set' , 'incr' ] . includes ( operation ) ) {
2021-04-30 13:38:51 -07:00
const items = this . getInputData ( ) ;
const returnItems : INodeExecutionData [ ] = [ ] ;
let item : INodeExecutionData ;
for ( let itemIndex = 0 ; itemIndex < items . length ; itemIndex ++ ) {
item = { json : { } } ;
if ( operation === 'delete' ) {
const keyDelete = this . getNodeParameter ( 'key' , itemIndex ) as string ;
const clientDel = util . promisify ( client . del ) . bind ( client ) ;
// @ts-ignore
await clientDel ( keyDelete ) ;
returnItems . push ( items [ itemIndex ] ) ;
} else if ( operation === 'get' ) {
const propertyName = this . getNodeParameter ( 'propertyName' , itemIndex ) as string ;
const keyGet = this . getNodeParameter ( 'key' , itemIndex ) as string ;
const keyType = this . getNodeParameter ( 'keyType' , itemIndex ) as string ;
const value = await getValue ( client , keyGet , keyType ) || null ;
const options = this . getNodeParameter ( 'options' , itemIndex , { } ) as IDataObject ;
if ( options . dotNotation === false ) {
item . json [ propertyName ] = value ;
} else {
set ( item . json , propertyName , value ) ;
}
returnItems . push ( item ) ;
} else if ( operation === 'keys' ) {
const keyPattern = this . getNodeParameter ( 'keyPattern' , itemIndex ) as string ;
const clientKeys = util . promisify ( client . keys ) . bind ( client ) ;
const keys = await clientKeys ( keyPattern ) ;
const promises : {
[ key : string ] : GenericValue ;
} = { } ;
for ( const keyName of keys ) {
promises [ keyName ] = await getValue ( client , keyName ) ;
}
for ( const keyName of keys ) {
item . json [ keyName ] = await promises [ keyName ] ;
}
returnItems . push ( item ) ;
} else if ( operation === 'set' ) {
const keySet = this . getNodeParameter ( 'key' , itemIndex ) as string ;
const value = this . getNodeParameter ( 'value' , itemIndex ) as string ;
const keyType = this . getNodeParameter ( 'keyType' , itemIndex ) as string ;
const expire = this . getNodeParameter ( 'expire' , itemIndex , false ) as boolean ;
const ttl = this . getNodeParameter ( 'ttl' , itemIndex , - 1 ) as number ;
await setValue ( client , keySet , value , expire , ttl , keyType ) ;
returnItems . push ( items [ itemIndex ] ) ;
2021-05-07 16:33:14 -07:00
} else if ( operation === 'incr' ) {
const keyIncr = this . getNodeParameter ( 'key' , itemIndex ) as string ;
const expire = this . getNodeParameter ( 'expire' , itemIndex , false ) as boolean ;
const ttl = this . getNodeParameter ( 'ttl' , itemIndex , - 1 ) as number ;
const clientIncr = util . promisify ( client . incr ) . bind ( client ) ;
// @ts-ignore
const incrementVal = await clientIncr ( keyIncr ) ;
if ( expire === true && ttl > 0 ) {
const clientExpire = util . promisify ( client . expire ) . bind ( client ) ;
await clientExpire ( keyIncr , ttl ) ;
}
returnItems . push ( { json : { [ keyIncr ] : incrementVal } } ) ;
2020-05-05 06:52:47 -07:00
}
2019-06-23 03:35:23 -07:00
}
2019-08-01 13:55:33 -07:00
2021-04-30 13:38:51 -07:00
client . quit ( ) ;
resolve ( this . prepareOutputData ( returnItems ) ) ;
}
} catch ( error ) {
reject ( error ) ;
2019-06-23 03:35:23 -07:00
}
} ) ;
} ) ;
}
}