n8n/packages/nodes-base/nodes/Redis/Redis.node.ts
2020-01-08 11:27:39 +01:00

484 lines
12 KiB
TypeScript

import { IExecuteFunctions } from 'n8n-core';
import {
GenericValue,
IDataObject,
INodeExecutionData,
INodeType,
INodeTypeDescription,
} 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',
icon: 'file:redis.png',
group: ['input'],
version: 1,
description: 'Gets, sends data to Redis and receives generic information.',
defaults: {
name: 'Redis',
color: '#0033AA',
},
inputs: ['main'],
outputs: ['main'],
credentials: [
{
name: 'redis',
required: true,
}
],
properties: [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
options: [
{
name: 'Delete',
value: 'delete',
description: 'Deletes a key from Redis.',
},
{
name: 'Get',
value: 'get',
description: 'Returns the value of a key from Redis.',
},
{
name: 'Info',
value: 'info',
description: 'Returns generic information about the Redis instance.',
},
{
name: 'Keys',
value: 'keys',
description: 'Returns all the keys matching a pattern.',
},
{
name: 'Set',
value: 'set',
description: 'Sets the value of a key in redis.',
},
],
default: 'info',
description: 'The operation to perform.',
},
// ----------------------------------
// 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.<br />Supports dot-notation.<br />Example: "data.person[0].name"',
},
{
displayName: 'Key',
name: 'key',
type: 'string',
displayOptions: {
show: {
operation: [
'delete'
],
},
},
default: '',
required: true,
description: 'Name of the key to delete from Redis.',
},
{
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: '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.',
},
// ----------------------------------
// keys
// ----------------------------------
{
displayName: 'Key Pattern',
name: 'keyPattern',
type: 'string',
displayOptions: {
show: {
operation: [
'keys'
],
},
},
default: '',
required: true,
description: 'The key pattern for the keys to return.',
},
// ----------------------------------
// 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: '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.',
},
{
displayName: 'Expire',
name: 'expire',
type: 'boolean',
default: false,
description: '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.',
}
]
};
execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
// Parses the given value in a number if it is one else returns a string
function getParsedValue (value: string): string | number {
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 = {};
let key:string, value:string;
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);
}
}
async function setValue(client: redis.RedisClient, keyName: string, value: string | number | object | string[] | number[], expire: boolean, ttl: number, type?: string) {
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 {
throw new Error('Could not identify the type to set. Please set it manually!');
}
}
if (type === 'string') {
const clientSet = util.promisify(client.set).bind(client);
await clientSet(keyName, value.toString());
} else if (type === 'hash') {
const clientHset = util.promisify(client.hset).bind(client);
for (const key of Object.keys(value)) {
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());
}
}
if (expire === true) {
const clientExpire = util.promisify(client.expire).bind(client);
await clientExpire(keyName, ttl);
}
return;
}
return new Promise((resolve, reject) => {
// 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 = this.getCredentials('redis');
if (credentials === undefined) {
throw new Error('No credentials got returned!');
}
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) => {
reject(err);
});
client.on('ready', async (err: Error | null) => {
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();
} else if (['delete', 'get', 'keys', 'set'].includes(operation)) {
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);
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) {
set(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]);
}
}
resolve(this.prepareOutputData(returnItems));
}
});
});
}
}