n8n/packages/nodes-base/nodes/Redis/Redis.node.ts

639 lines
16 KiB
TypeScript

import type {
IExecuteFunctions,
INodeExecutionData,
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';
import { NodeConnectionType } from 'n8n-workflow';
import set from 'lodash/set';
import {
setupRedisClient,
redisConnectionTest,
convertInfoToObject,
getValue,
setValue,
} from './utils';
export class Redis implements INodeType {
description: INodeTypeDescription = {
displayName: 'Redis',
name: 'redis',
icon: 'file:redis.svg',
group: ['input'],
version: 1,
description: 'Get, send and update data in Redis',
defaults: {
name: 'Redis',
},
inputs: [NodeConnectionType.Main],
outputs: [NodeConnectionType.Main],
usableAsTool: true,
credentials: [
{
name: 'redis',
required: true,
testedBy: 'redisConnectionTest',
},
],
properties: [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
options: [
{
name: 'Delete',
value: 'delete',
description: 'Delete a key from Redis',
action: 'Delete a key from Redis',
},
{
name: 'Get',
value: 'get',
description: 'Get the value of a key from Redis',
action: 'Get the value of a key from Redis',
},
{
name: 'Increment',
value: 'incr',
description: 'Atomically increments a key by 1. Creates the key if it does not exist.',
action: 'Atomically increment a key by 1. Creates the key if it does not exist.',
},
{
name: 'Info',
value: 'info',
description: 'Returns generic information about the Redis instance',
action: 'Return generic information about the Redis instance',
},
{
name: 'Keys',
value: 'keys',
description: 'Returns all the keys matching a pattern',
action: 'Return all keys matching a pattern',
},
{
name: 'Pop',
value: 'pop',
description: 'Pop data from a redis list',
action: 'Pop data from a redis list',
},
{
name: 'Publish',
value: 'publish',
description: 'Publish message to redis channel',
action: 'Publish message to redis channel',
},
{
name: 'Push',
value: 'push',
description: 'Push data to a redis list',
action: 'Push data to a redis list',
},
{
name: 'Set',
value: 'set',
description: 'Set the value of a key in redis',
action: 'Set the value of a key in redis',
},
],
default: 'info',
},
// ----------------------------------
// delete
// ----------------------------------
{
displayName: 'Key',
name: 'key',
type: 'string',
displayOptions: {
show: {
operation: ['delete'],
},
},
default: '',
required: true,
description: 'Name of the key to delete from Redis',
},
// ----------------------------------
// get
// ----------------------------------
{
displayName: 'Name',
name: 'propertyName',
type: 'string',
displayOptions: {
show: {
operation: ['get'],
},
},
default: 'propertyName',
required: true,
description:
'Name of the property to write received data to. Supports dot-notation. Example: "data.person[0].name".',
},
{
displayName: 'Key',
name: 'key',
type: 'string',
displayOptions: {
show: {
operation: ['get'],
},
},
default: '',
required: true,
description: 'Name of the key to get from Redis',
},
{
displayName: 'Key Type',
name: 'keyType',
type: 'options',
displayOptions: {
show: {
operation: ['get'],
},
},
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: 'List',
value: 'list',
description: "Data in key is of type 'lists'",
},
{
name: 'Sets',
value: 'sets',
description: "Data in key is of type 'sets'",
},
{
name: 'String',
value: 'string',
description: "Data in key is of type 'string'",
},
],
default: 'automatic',
description: 'The type of the key to get',
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
displayOptions: {
show: {
operation: ['get'],
},
},
placeholder: 'Add option',
default: {},
options: [
{
displayName: 'Dot Notation',
name: 'dotNotation',
type: 'boolean',
default: true,
// eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether
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>.',
},
],
},
// ----------------------------------
// 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: 'Whether to 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',
},
// ----------------------------------
// keys
// ----------------------------------
{
displayName: 'Key Pattern',
name: 'keyPattern',
type: 'string',
displayOptions: {
show: {
operation: ['keys'],
},
},
default: '',
required: true,
description: 'The key pattern for the keys to return',
},
{
displayName: 'Get Values',
name: 'getValues',
type: 'boolean',
displayOptions: {
show: {
operation: ['keys'],
},
},
default: true,
description: 'Whether to get the value of matching keys',
},
// ----------------------------------
// set
// ----------------------------------
{
displayName: 'Key',
name: 'key',
type: 'string',
displayOptions: {
show: {
operation: ['set'],
},
},
default: '',
required: true,
description: 'Name of the key to set in Redis',
},
{
displayName: 'Value',
name: 'value',
type: 'string',
displayOptions: {
show: {
operation: ['set'],
},
},
default: '',
description: 'The value to write in Redis',
},
{
displayName: 'Key Type',
name: 'keyType',
type: 'options',
displayOptions: {
show: {
operation: ['set'],
},
},
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: 'List',
value: 'list',
description: "Data in key is of type 'lists'",
},
{
name: 'Sets',
value: 'sets',
description: "Data in key is of type 'sets'",
},
{
name: 'String',
value: 'string',
description: "Data in key is of type 'string'",
},
],
default: 'automatic',
description: 'The type of the key to set',
},
{
displayName: 'Value Is JSON',
name: 'valueIsJSON',
type: 'boolean',
displayOptions: {
show: {
keyType: ['hash'],
},
},
default: true,
description: 'Whether the value is JSON or key value pairs',
},
{
displayName: 'Expire',
name: 'expire',
type: 'boolean',
displayOptions: {
show: {
operation: ['set'],
},
},
default: false,
description: 'Whether to set a timeout on key',
},
{
displayName: 'TTL',
name: 'ttl',
type: 'number',
typeOptions: {
minValue: 1,
},
displayOptions: {
show: {
operation: ['set'],
expire: [true],
},
},
default: 60,
description: 'Number of seconds before key expiration',
},
// ----------------------------------
// publish
// ----------------------------------
{
displayName: 'Channel',
name: 'channel',
type: 'string',
displayOptions: {
show: {
operation: ['publish'],
},
},
default: '',
required: true,
description: 'Channel name',
},
{
displayName: 'Data',
name: 'messageData',
type: 'string',
displayOptions: {
show: {
operation: ['publish'],
},
},
default: '',
required: true,
description: 'Data to publish',
},
// ----------------------------------
// push/pop
// ----------------------------------
{
displayName: 'List',
name: 'list',
type: 'string',
displayOptions: {
show: {
operation: ['push', 'pop'],
},
},
default: '',
required: true,
description: 'Name of the list in Redis',
},
{
displayName: 'Data',
name: 'messageData',
type: 'string',
displayOptions: {
show: {
operation: ['push'],
},
},
default: '',
required: true,
description: 'Data to push',
},
{
displayName: 'Tail',
name: 'tail',
type: 'boolean',
displayOptions: {
show: {
operation: ['push', 'pop'],
},
},
default: false,
description: 'Whether to push or pop data from the end of the list',
},
{
displayName: 'Name',
name: 'propertyName',
type: 'string',
displayOptions: {
show: {
operation: ['pop'],
},
},
default: 'propertyName',
description:
'Optional name of the property to write received data to. Supports dot-notation. Example: "data.person[0].name".',
},
{
displayName: 'Options',
name: 'options',
type: 'collection',
displayOptions: {
show: {
operation: ['pop'],
},
},
placeholder: 'Add option',
default: {},
options: [
{
displayName: 'Dot Notation',
name: 'dotNotation',
type: 'boolean',
default: true,
// eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether
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>.',
},
],
},
],
};
methods = {
credentialTest: { redisConnectionTest },
};
async execute(this: IExecuteFunctions) {
// 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.
const credentials = await this.getCredentials('redis');
const client = setupRedisClient(credentials);
await client.connect();
await client.ping();
const operation = this.getNodeParameter('operation', 0);
const returnItems: INodeExecutionData[] = [];
try {
if (operation === 'info') {
const result = await client.info();
returnItems.push({ json: convertInfoToObject(result) });
} else if (
['delete', 'get', 'keys', 'set', 'incr', 'publish', 'push', 'pop'].includes(operation)
) {
const items = this.getInputData();
let item: INodeExecutionData;
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
item = { json: {}, pairedItem: { item: itemIndex } };
if (operation === 'delete') {
const keyDelete = this.getNodeParameter('key', itemIndex) as string;
await client.del(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, {});
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 getValues = this.getNodeParameter('getValues', itemIndex, true) as boolean;
const keys = await client.keys(keyPattern);
if (!getValues) {
returnItems.push({ json: { keys } });
continue;
}
for (const keyName of keys) {
item.json[keyName] = await getValue(client, 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 valueIsJSON = this.getNodeParameter('valueIsJSON', itemIndex, true) as boolean;
const expire = this.getNodeParameter('expire', itemIndex, false) as boolean;
const ttl = this.getNodeParameter('ttl', itemIndex, -1) as number;
await setValue.call(this, client, keySet, value, expire, ttl, keyType, valueIsJSON);
returnItems.push(items[itemIndex]);
} 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 incrementVal = await client.incr(keyIncr);
if (expire && ttl > 0) {
await client.expire(keyIncr, ttl);
}
returnItems.push({ json: { [keyIncr]: incrementVal } });
} else if (operation === 'publish') {
const channel = this.getNodeParameter('channel', itemIndex) as string;
const messageData = this.getNodeParameter('messageData', itemIndex) as string;
await client.publish(channel, messageData);
returnItems.push(items[itemIndex]);
} else if (operation === 'push') {
const redisList = this.getNodeParameter('list', itemIndex) as string;
const messageData = this.getNodeParameter('messageData', itemIndex) as string;
const tail = this.getNodeParameter('tail', itemIndex, false) as boolean;
await client[tail ? 'rPush' : 'lPush'](redisList, messageData);
returnItems.push(items[itemIndex]);
} else if (operation === 'pop') {
const redisList = this.getNodeParameter('list', itemIndex) as string;
const tail = this.getNodeParameter('tail', itemIndex, false) as boolean;
const propertyName = this.getNodeParameter(
'propertyName',
itemIndex,
'propertyName',
) as string;
const value = await client[tail ? 'rPop' : 'lPop'](redisList);
let outputValue;
try {
outputValue = value && JSON.parse(value);
} catch {
outputValue = value;
}
const options = this.getNodeParameter('options', itemIndex, {});
if (options.dotNotation === false) {
item.json[propertyName] = outputValue;
} else {
set(item.json, propertyName, outputValue);
}
returnItems.push(item);
}
}
}
} catch (error) {
throw error;
} finally {
await client.quit();
}
return [returnItems];
}
}