feat(Redis Node): Update node-redis (no-changelog) (#8269)

Co-authored-by: Michael Kret <michael.k@radency.com>
This commit is contained in:
कारतोफ्फेलस्क्रिप्ट™ 2024-01-17 10:15:20 +01:00 committed by GitHub
parent 3734c89cf6
commit ab52aaf7e9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 517 additions and 426 deletions

View file

@ -148,7 +148,7 @@
"openai": "4.20.0",
"pdf-parse": "1.1.1",
"pg": "8.11.3",
"redis": "4.6.11",
"redis": "4.6.12",
"sqlite3": "5.1.6",
"temp": "0.9.4",
"typeorm": "0.3.17",

View file

@ -1,19 +1,19 @@
import util from 'util';
import type {
IExecuteFunctions,
ICredentialDataDecryptedObject,
ICredentialsDecrypted,
ICredentialTestFunctions,
IDataObject,
INodeCredentialTestResult,
INodeExecutionData,
INodeType,
INodeTypeDescription,
} from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow';
import set from 'lodash/set';
import redis from 'redis';
import {
setupRedisClient,
redisConnectionTest,
convertInfoToObject,
getValue,
setValue,
} from './utils';
export class Redis implements INodeType {
description: INodeTypeDescription = {
@ -100,6 +100,23 @@ export class Redis implements INodeType {
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
// ----------------------------------
@ -117,19 +134,6 @@ export class Redis implements INodeType {
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: ['delete'],
},
},
default: '',
required: true,
description: 'Name of the key to delete from Redis',
},
{
displayName: 'Key',
name: 'key',
@ -498,345 +502,135 @@ export class Redis implements INodeType {
};
methods = {
credentialTest: {
async redisConnectionTest(
this: ICredentialTestFunctions,
credential: ICredentialsDecrypted,
): Promise<INodeCredentialTestResult> {
const credentials = credential.data as ICredentialDataDecryptedObject;
const redisOptions: redis.ClientOpts = {
host: credentials.host as string,
port: credentials.port as number,
db: credentials.database as number,
};
if (credentials.password) {
redisOptions.password = credentials.password as string;
}
try {
const client = redis.createClient(redisOptions);
await new Promise((resolve, reject): any => {
client.on('connect', async () => {
client.ping('ping', (error, pong) => {
if (error) reject(error);
resolve(pong);
client.quit();
});
});
client.on('error', async (err) => {
client.quit();
reject(err);
});
});
} catch (error) {
return {
status: 'Error',
message: error.message,
};
}
return {
status: 'OK',
message: 'Connection successful!',
};
},
},
credentialTest: { redisConnectionTest },
};
async 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);
}
}
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');
// Converts the Redis Info String into an object
function convertInfoToObject(stringData: string): IDataObject {
const returnData: IDataObject = {};
const client = setupRedisClient(credentials);
await client.connect();
await client.ping();
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();
const operation = this.getNodeParameter('operation', 0);
const returnItems: INodeExecutionData[] = [];
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);
}
}
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();
return returnData;
}
let item: INodeExecutionData;
for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
item = { json: {} };
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 (operation === 'delete') {
const keyDelete = this.getNodeParameter('key', itemIndex) as string;
if (type === 'string') {
const clientGet = util.promisify(client.get).bind(client);
return clientGet(keyName);
} else if (type === 'hash') {
const clientHGetAll = util.promisify(client.hgetall).bind(client);
return clientHGetAll(keyName);
} else if (type === 'list') {
const clientLRange = util.promisify(client.lrange).bind(client);
return clientLRange(keyName, 0, -1);
} else if (type === 'sets') {
const clientSMembers = util.promisify(client.smembers).bind(client);
return clientSMembers(keyName);
}
}
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 setValue = async (
client: redis.RedisClient,
keyName: string,
value: string | number | object | string[] | number[],
expire: boolean,
ttl: number,
type?: string,
valueIsJSON?: boolean,
) => {
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 NodeOperationError(
this.getNode(),
'Could not identify the type to set. Please set it manually!',
);
}
}
const value = (await getValue(client, keyGet, keyType)) ?? null;
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);
if (valueIsJSON) {
let values: unknown;
if (typeof value === 'string') {
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 {
values = JSON.parse(value);
outputValue = value && JSON.parse(value);
} catch {
// This is how we originally worked and prevents a breaking change
values = value;
outputValue = value;
}
} else {
values = value;
}
for (const key of Object.keys(values as object)) {
// @ts-ignore
await clientHset(keyName, key, (values as IDataObject)[key]!.toString());
}
} else {
const values = value.toString().split(' ');
//@ts-ignore
await clientHset(keyName, values);
}
} 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());
}
} else if (type === 'sets') {
const clientSadd = util.promisify(client.sadd).bind(client);
//@ts-ignore
await clientSadd(keyName, value);
}
if (expire) {
const clientExpire = util.promisify(client.expire).bind(client);
await clientExpire(keyName, ttl);
}
return;
};
return new Promise(async (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 = await this.getCredentials('redis');
const redisOptions: redis.ClientOpts = {
host: credentials.host as string,
port: credentials.port as number,
db: credentials.database as number,
};
if (credentials.password) {
redisOptions.password = credentials.password as string;
}
const client = redis.createClient(redisOptions);
const operation = this.getNodeParameter('operation', 0);
client.on('error', (err: Error) => {
client.quit();
reject(err);
});
client.on('ready', async (_err: Error | null) => {
client.select(credentials.database as number);
try {
if (operation === 'info') {
const clientInfo = util.promisify(client.info).bind(client);
const result = await clientInfo();
resolve([[{ json: convertInfoToObject(result as string) }]]);
client.quit();
} else if (
['delete', 'get', 'keys', 'set', 'incr', 'publish', 'push', 'pop'].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)) || 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 clientKeys = util.promisify(client.keys).bind(client);
const keys = await clientKeys(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(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 clientIncr = util.promisify(client.incr).bind(client);
// @ts-ignore
const incrementVal = await clientIncr(keyIncr);
if (expire && ttl > 0) {
const clientExpire = util.promisify(client.expire).bind(client);
await clientExpire(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;
const clientPublish = util.promisify(client.publish).bind(client);
await clientPublish(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;
const action = tail ? client.RPUSH : client.LPUSH;
const clientPush = util.promisify(action).bind(client);
// @ts-ignore: typescript not understanding generic function signatures
await clientPush(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 action = tail ? client.rpop : client.lpop;
const clientPop = util.promisify(action).bind(client);
const value = await clientPop(redisList);
let outputValue;
try {
outputValue = 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);
}
const options = this.getNodeParameter('options', itemIndex, {});
if (options.dotNotation === false) {
item.json[propertyName] = outputValue;
} else {
set(item.json, propertyName, outputValue);
}
client.quit();
resolve([returnItems]);
returnItems.push(item);
}
} catch (error) {
reject(error);
}
});
});
}
} catch (error) {
throw error;
} finally {
await client.quit();
}
return [returnItems];
}
}

View file

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-loop-func */
import type {
ITriggerFunctions,
IDataObject,
@ -7,7 +8,7 @@ import type {
} from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow';
import redis from 'redis';
import { redisConnectionTest, setupRedisClient } from './utils';
export class RedisTrigger implements INodeType {
description: INodeTypeDescription = {
@ -26,6 +27,7 @@ export class RedisTrigger implements INodeType {
{
name: 'redis',
required: true,
testedBy: 'redisConnectionTest',
},
],
properties: [
@ -64,36 +66,29 @@ export class RedisTrigger implements INodeType {
],
};
methods = {
credentialTest: { redisConnectionTest },
};
async trigger(this: ITriggerFunctions): Promise<ITriggerResponse> {
const credentials = await this.getCredentials('redis');
const redisOptions: redis.ClientOpts = {
host: credentials.host as string,
port: credentials.port as number,
db: credentials.database as number,
};
if (credentials.password) {
redisOptions.password = credentials.password as string;
}
const channels = (this.getNodeParameter('channels') as string).split(',');
const options = this.getNodeParameter('options') as IDataObject;
if (!channels) {
throw new NodeOperationError(this.getNode(), 'Channels are mandatory!');
}
const client = redis.createClient(redisOptions);
const client = setupRedisClient(credentials);
const manualTriggerFunction = async () => {
await new Promise((resolve, reject) => {
client.on('connect', () => {
for (const channel of channels) {
client.psubscribe(channel);
}
client.on('pmessage', (pattern: string, channel: string, message: string) => {
await client.connect();
await client.ping();
try {
for (const channel of channels) {
await client.pSubscribe(channel, (message) => {
if (options.jsonParseBody) {
try {
message = JSON.parse(message);
@ -102,19 +97,15 @@ export class RedisTrigger implements INodeType {
if (options.onlyMessage) {
this.emit([this.helpers.returnJsonArray({ message })]);
resolve(true);
return;
}
this.emit([this.helpers.returnJsonArray({ channel, message })]);
resolve(true);
});
});
client.on('error', (error) => {
reject(error);
});
});
}
} catch (error) {
throw new NodeOperationError(this.getNode(), error);
}
};
if (this.getMode() === 'trigger') {
@ -122,7 +113,7 @@ export class RedisTrigger implements INodeType {
}
async function closeFunction() {
client.quit();
await client.quit();
}
return {

View file

@ -0,0 +1,158 @@
import { mock } from 'jest-mock-extended';
import type { RedisClientType } from '@redis/client';
import type { IExecuteFunctions } from 'n8n-workflow';
import { Redis } from '../Redis.node';
const mockClient = mock<RedisClientType>();
jest.mock('redis', () => ({
createClient: () => mockClient,
}));
describe('Redis Node', () => {
const mockCredential = {
host: 'redis',
port: 1234,
database: 0,
password: 'random',
};
const node = new Redis();
const thisArg = mock<IExecuteFunctions>({});
thisArg.getCredentials.calledWith('redis').mockResolvedValue(mockCredential);
beforeEach(() => jest.clearAllMocks());
afterEach(() => {
expect(mockClient.connect).toHaveBeenCalled();
expect(mockClient.ping).toHaveBeenCalled();
expect(mockClient.quit).toHaveBeenCalled();
});
it('info operation', async () => {
thisArg.getNodeParameter.calledWith('operation', 0).mockReturnValue('info');
mockClient.info.mockResolvedValue(`
# Server
redis_version:6.2.14
redis_git_sha1:00000000
redis_git_dirty:0
redis_mode:standalone
arch_bits:64
tcp_port:6379
uptime_in_seconds:429905
uptime_in_days:4
# Clients
connected_clients:1
cluster_connections:0
max_clients:10000
# Memory
used_memory:876648
# Replication
role:master
connected_slaves:0
master_failover_state:no-failover
`);
const output = await node.execute.call(thisArg);
expect(mockClient.info).toHaveBeenCalled();
expect(output[0][0].json).toEqual({
redis_version: 6.2,
redis_git_sha1: 0,
redis_git_dirty: 0,
redis_mode: 'standalone',
arch_bits: 64,
tcp_port: 6379,
uptime_in_seconds: 429905,
uptime_in_days: 4,
connected_clients: 1,
cluster_connections: 0,
max_clients: 10000,
used_memory: 876648,
role: 'master',
connected_slaves: 0,
master_failover_state: 'no-failover',
});
});
it('delete operation', async () => {
thisArg.getInputData.mockReturnValue([{ json: { x: 1 } }]);
thisArg.getNodeParameter.calledWith('operation', 0).mockReturnValue('delete');
thisArg.getNodeParameter.calledWith('key', 0).mockReturnValue('key1');
mockClient.del.calledWith('key1').mockResolvedValue(1);
const output = await node.execute.call(thisArg);
expect(mockClient.del).toHaveBeenCalledWith('key1');
expect(output[0][0].json).toEqual({ x: 1 });
});
describe('get operation', () => {
beforeEach(() => {
thisArg.getInputData.mockReturnValue([{ json: { x: 1 } }]);
thisArg.getNodeParameter.calledWith('operation', 0).mockReturnValue('get');
thisArg.getNodeParameter.calledWith('options', 0).mockReturnValue({ dotNotation: true });
thisArg.getNodeParameter.calledWith('key', 0).mockReturnValue('key1');
thisArg.getNodeParameter.calledWith('propertyName', 0).mockReturnValue('x.y');
});
it('keyType = automatic', async () => {
thisArg.getNodeParameter.calledWith('keyType', 0).mockReturnValue('automatic');
mockClient.type.calledWith('key1').mockResolvedValue('string');
mockClient.get.calledWith('key1').mockResolvedValue('value');
const output = await node.execute.call(thisArg);
expect(mockClient.type).toHaveBeenCalledWith('key1');
expect(mockClient.get).toHaveBeenCalledWith('key1');
expect(output[0][0].json).toEqual({ x: { y: 'value' } });
});
it('keyType = hash', async () => {
thisArg.getNodeParameter.calledWith('keyType', 0).mockReturnValue('hash');
mockClient.hGetAll.calledWith('key1').mockResolvedValue({
field1: '1',
field2: '2',
});
const output = await node.execute.call(thisArg);
expect(mockClient.hGetAll).toHaveBeenCalledWith('key1');
expect(output[0][0].json).toEqual({
x: {
y: {
field1: '1',
field2: '2',
},
},
});
});
});
describe('keys operation', () => {
beforeEach(() => {
thisArg.getInputData.mockReturnValue([{ json: { x: 1 } }]);
thisArg.getNodeParameter.calledWith('operation', 0).mockReturnValue('keys');
thisArg.getNodeParameter.calledWith('keyPattern', 0).mockReturnValue('key*');
mockClient.keys.calledWith('key*').mockResolvedValue(['key1', 'key2']);
});
it('getValues = false', async () => {
thisArg.getNodeParameter.calledWith('getValues', 0).mockReturnValue(false);
const output = await node.execute.call(thisArg);
expect(mockClient.keys).toHaveBeenCalledWith('key*');
expect(output[0][0].json).toEqual({ keys: ['key1', 'key2'] });
});
it('getValues = true', async () => {
thisArg.getNodeParameter.calledWith('getValues', 0).mockReturnValue(true);
mockClient.type.mockResolvedValue('string');
mockClient.get.calledWith('key1').mockResolvedValue('value1');
mockClient.get.calledWith('key2').mockResolvedValue('value2');
const output = await node.execute.call(thisArg);
expect(mockClient.keys).toHaveBeenCalledWith('key*');
expect(output[0][0].json).toEqual({ key1: 'value1', key2: 'value2' });
});
});
});

View file

@ -0,0 +1,168 @@
import type {
ICredentialDataDecryptedObject,
ICredentialTestFunctions,
ICredentialsDecrypted,
IDataObject,
IExecuteFunctions,
INodeCredentialTestResult,
} from 'n8n-workflow';
import { NodeOperationError } from 'n8n-workflow';
import { createClient } from 'redis';
export type RedisClientType = ReturnType<typeof createClient>;
export function setupRedisClient(credentials: ICredentialDataDecryptedObject): RedisClientType {
const redisOptions = {
socket: {
host: credentials.host as string,
port: credentials.port as number,
},
database: credentials.database as number,
password: (credentials.password as string) || undefined,
};
return createClient(redisOptions);
}
export async function redisConnectionTest(
this: ICredentialTestFunctions,
credential: ICredentialsDecrypted,
): Promise<INodeCredentialTestResult> {
const credentials = credential.data as ICredentialDataDecryptedObject;
try {
const client = setupRedisClient(credentials);
await client.connect();
await client.ping();
return {
status: 'OK',
message: 'Connection successful!',
};
} catch (error) {
return {
status: 'Error',
message: error.message,
};
}
}
/** 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 */
export 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;
}
export async function getValue(client: RedisClientType, keyName: string, type?: string) {
if (type === undefined || type === 'automatic') {
// Request the type first
type = await client.type(keyName);
}
if (type === 'string') {
return client.get(keyName);
} else if (type === 'hash') {
return client.hGetAll(keyName);
} else if (type === 'list') {
return client.lRange(keyName, 0, -1);
} else if (type === 'sets') {
return client.sMembers(keyName);
}
}
export async function setValue(
this: IExecuteFunctions,
client: RedisClientType,
keyName: string,
value: string | number | object | string[] | number[],
expire: boolean,
ttl: number,
type?: string,
valueIsJSON?: boolean,
) {
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 NodeOperationError(
this.getNode(),
'Could not identify the type to set. Please set it manually!',
);
}
}
if (type === 'string') {
await client.set(keyName, value.toString());
} else if (type === 'hash') {
if (valueIsJSON) {
let values: unknown;
if (typeof value === 'string') {
try {
values = JSON.parse(value);
} catch {
// This is how we originally worked and prevents a breaking change
values = value;
}
} else {
values = value;
}
for (const key of Object.keys(values as object)) {
await client.hSet(keyName, key, (values as IDataObject)[key]!.toString());
}
} else {
const values = value.toString().split(' ');
await client.hSet(keyName, values);
}
} else if (type === 'list') {
for (let index = 0; index < (value as string[]).length; index++) {
await client.lSet(keyName, index, (value as IDataObject)[index]!.toString());
}
} else if (type === 'sets') {
//@ts-ignore
await client.sAdd(keyName, value);
}
if (expire) {
await client.expire(keyName, ttl);
}
return;
}

View file

@ -816,7 +816,6 @@
"@types/node-ssh": "^7.0.1",
"@types/nodemailer": "^6.4.0",
"@types/promise-ftp": "^1.3.4",
"@types/redis": "^2.8.11",
"@types/request-promise-native": "~1.0.15",
"@types/rfc2047": "^2.0.1",
"@types/showdown": "^1.9.4",
@ -877,7 +876,7 @@
"pretty-bytes": "5.6.0",
"promise-ftp": "1.3.5",
"pyodide": "0.23.4",
"redis": "3.1.2",
"redis": "4.6.12",
"rfc2047": "4.0.1",
"rhea": "1.0.24",
"rss-parser": "3.12.0",

View file

@ -231,7 +231,7 @@ importers:
version: 1.2.0
langchain:
specifier: 0.0.198
version: 0.0.198(@aws-sdk/client-bedrock-runtime@3.454.0)(@aws-sdk/credential-provider-node@3.451.0)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@0.2.1)(@huggingface/inference@2.6.4)(@pinecone-database/pinecone@1.1.2)(@qdrant/js-client-rest@1.7.0)(@supabase/supabase-js@2.38.5)(@xata.io/client@0.25.3)(axios@1.6.2)(cohere-ai@6.2.2)(d3-dsv@2.0.0)(epub2@3.0.1)(html-to-text@9.0.5)(lodash@4.17.21)(mammoth@1.6.0)(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.11)(typeorm@0.3.17)
version: 0.0.198(@aws-sdk/client-bedrock-runtime@3.454.0)(@aws-sdk/credential-provider-node@3.451.0)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@0.2.1)(@huggingface/inference@2.6.4)(@pinecone-database/pinecone@1.1.2)(@qdrant/js-client-rest@1.7.0)(@supabase/supabase-js@2.38.5)(@xata.io/client@0.25.3)(axios@1.6.2)(cohere-ai@6.2.2)(d3-dsv@2.0.0)(epub2@3.0.1)(html-to-text@9.0.5)(lodash@4.17.21)(mammoth@1.6.0)(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(typeorm@0.3.17)
lodash:
specifier: 4.17.21
version: 4.17.21
@ -257,8 +257,8 @@ importers:
specifier: 8.11.3
version: 8.11.3
redis:
specifier: 4.6.11
version: 4.6.11
specifier: 4.6.12
version: 4.6.12
sqlite3:
specifier: 5.1.6
version: 5.1.6
@ -267,7 +267,7 @@ importers:
version: 0.9.4
typeorm:
specifier: 0.3.17
version: 0.3.17(mssql@9.1.1)(pg@8.11.3)(redis@4.6.11)(sqlite3@5.1.6)
version: 0.3.17(mssql@9.1.1)(pg@8.11.3)(redis@4.6.12)(sqlite3@5.1.6)
zod:
specifier: 3.22.4
version: 3.22.4
@ -1363,8 +1363,8 @@ importers:
specifier: 0.23.4
version: 0.23.4(patch_hash=kzcwsjcayy5m6iezu7r4tdimjq)
redis:
specifier: 3.1.2
version: 3.1.2
specifier: 4.6.12
version: 4.6.12
rfc2047:
specifier: 4.0.1
version: 4.0.1
@ -1465,9 +1465,6 @@ importers:
'@types/promise-ftp':
specifier: ^1.3.4
version: 1.3.4
'@types/redis':
specifier: ^2.8.11
version: 2.8.32
'@types/request-promise-native':
specifier: ~1.0.15
version: 1.0.18
@ -7158,16 +7155,16 @@ packages:
'@babel/runtime': 7.22.6
dev: true
/@redis/bloom@1.2.0(@redis/client@1.5.12):
/@redis/bloom@1.2.0(@redis/client@1.5.13):
resolution: {integrity: sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==}
peerDependencies:
'@redis/client': ^1.0.0
dependencies:
'@redis/client': 1.5.12
'@redis/client': 1.5.13
dev: false
/@redis/client@1.5.12:
resolution: {integrity: sha512-/ZjE18HRzMd80eXIIUIPcH81UoZpwulbo8FmbElrjPqH0QC0SeIKu1BOU49bO5trM5g895kAjhvalt5h77q+4A==}
/@redis/client@1.5.13:
resolution: {integrity: sha512-epkUM9D0Sdmt93/8Ozk43PNjLi36RZzG+d/T1Gdu5AI8jvghonTeLYV69WVWdilvFo+PYxbP0TZ0saMvr6nscQ==}
engines: {node: '>=14'}
dependencies:
cluster-key-slot: 1.1.2
@ -7175,36 +7172,36 @@ packages:
yallist: 4.0.0
dev: false
/@redis/graph@1.1.1(@redis/client@1.5.12):
/@redis/graph@1.1.1(@redis/client@1.5.13):
resolution: {integrity: sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==}
peerDependencies:
'@redis/client': ^1.0.0
dependencies:
'@redis/client': 1.5.12
'@redis/client': 1.5.13
dev: false
/@redis/json@1.0.6(@redis/client@1.5.12):
/@redis/json@1.0.6(@redis/client@1.5.13):
resolution: {integrity: sha512-rcZO3bfQbm2zPRpqo82XbW8zg4G/w4W3tI7X8Mqleq9goQjAGLL7q/1n1ZX4dXEAmORVZ4s1+uKLaUOg7LrUhw==}
peerDependencies:
'@redis/client': ^1.0.0
dependencies:
'@redis/client': 1.5.12
'@redis/client': 1.5.13
dev: false
/@redis/search@1.1.6(@redis/client@1.5.12):
/@redis/search@1.1.6(@redis/client@1.5.13):
resolution: {integrity: sha512-mZXCxbTYKBQ3M2lZnEddwEAks0Kc7nauire8q20oA0oA/LoA+E/b5Y5KZn232ztPb1FkIGqo12vh3Lf+Vw5iTw==}
peerDependencies:
'@redis/client': ^1.0.0
dependencies:
'@redis/client': 1.5.12
'@redis/client': 1.5.13
dev: false
/@redis/time-series@1.0.5(@redis/client@1.5.12):
/@redis/time-series@1.0.5(@redis/client@1.5.13):
resolution: {integrity: sha512-IFjIgTusQym2B5IZJG3XKr5llka7ey84fw/NOYqESP5WUfQs9zz1ww/9+qoz4ka/S6KcGBodzlCeZ5UImKbscg==}
peerDependencies:
'@redis/client': ^1.0.0
dependencies:
'@redis/client': 1.5.12
'@redis/client': 1.5.13
dev: false
/@rollup/plugin-alias@5.1.0(rollup@3.29.4):
@ -10598,12 +10595,6 @@ packages:
csstype: 3.1.1
dev: true
/@types/redis@2.8.32:
resolution: {integrity: sha512-7jkMKxcGq9p242exlbsVzuJb57KqHRhNl4dHoQu2Y5v9bCAbtIXXH0R3HleSQW4CTOqpHIYUW3t6tpUj4BVQ+w==}
dependencies:
'@types/node': 18.16.16
dev: true
/@types/replacestream@4.0.1:
resolution: {integrity: sha512-3ecTmnzB90sgarVpIszCF1cX2cnxwqDovWb31jGrKfxAL0Knui1H7Reaz/zlT9zaE3u0un7L5cNy9fQPy0d2sg==}
dev: true
@ -18941,7 +18932,7 @@ packages:
resolution: {integrity: sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==}
dev: false
/langchain@0.0.198(@aws-sdk/client-bedrock-runtime@3.454.0)(@aws-sdk/credential-provider-node@3.451.0)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@0.2.1)(@huggingface/inference@2.6.4)(@pinecone-database/pinecone@1.1.2)(@qdrant/js-client-rest@1.7.0)(@supabase/supabase-js@2.38.5)(@xata.io/client@0.25.3)(axios@1.6.2)(cohere-ai@6.2.2)(d3-dsv@2.0.0)(epub2@3.0.1)(html-to-text@9.0.5)(lodash@4.17.21)(mammoth@1.6.0)(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.11)(typeorm@0.3.17):
/langchain@0.0.198(@aws-sdk/client-bedrock-runtime@3.454.0)(@aws-sdk/credential-provider-node@3.451.0)(@getzep/zep-js@0.9.0)(@google-ai/generativelanguage@0.2.1)(@huggingface/inference@2.6.4)(@pinecone-database/pinecone@1.1.2)(@qdrant/js-client-rest@1.7.0)(@supabase/supabase-js@2.38.5)(@xata.io/client@0.25.3)(axios@1.6.2)(cohere-ai@6.2.2)(d3-dsv@2.0.0)(epub2@3.0.1)(html-to-text@9.0.5)(lodash@4.17.21)(mammoth@1.6.0)(pdf-parse@1.1.1)(pg@8.11.3)(redis@4.6.12)(typeorm@0.3.17):
resolution: {integrity: sha512-YC0O1g8r61InCWyF5NmiQjdghdq6LKcgMrDZtqLbgDxAe4RoSldonm+5oNXS3yjCISG0j3s5Cty+yB7klqvUpg==}
engines: {node: '>=18'}
peerDependencies:
@ -19279,8 +19270,8 @@ packages:
p-retry: 4.6.2
pdf-parse: 1.1.1
pg: 8.11.3
redis: 4.6.11
typeorm: 0.3.17(mssql@9.1.1)(pg@8.11.3)(redis@4.6.11)(sqlite3@5.1.6)
redis: 4.6.12
typeorm: 0.3.17(mssql@9.1.1)(pg@8.11.3)(redis@4.6.12)(sqlite3@5.1.6)
uuid: 9.0.0
yaml: 2.3.4
zod: 3.22.4
@ -23233,25 +23224,15 @@ packages:
dependencies:
redis-errors: 1.2.0
/redis@3.1.2:
resolution: {integrity: sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw==}
engines: {node: '>=10'}
/redis@4.6.12:
resolution: {integrity: sha512-41Xuuko6P4uH4VPe5nE3BqXHB7a9lkFL0J29AlxKaIfD6eWO8VO/5PDF9ad2oS+mswMsfFxaM5DlE3tnXT+P8Q==}
dependencies:
denque: 1.5.1
redis-commands: 1.7.0
redis-errors: 1.2.0
redis-parser: 3.0.0
dev: false
/redis@4.6.11:
resolution: {integrity: sha512-kg1Lt4NZLYkAjPOj/WcyIGWfZfnyfKo1Wg9YKVSlzhFwxpFIl3LYI8BWy1Ab963LLDsTz2+OwdsesHKljB3WMQ==}
dependencies:
'@redis/bloom': 1.2.0(@redis/client@1.5.12)
'@redis/client': 1.5.12
'@redis/graph': 1.1.1(@redis/client@1.5.12)
'@redis/json': 1.0.6(@redis/client@1.5.12)
'@redis/search': 1.1.6(@redis/client@1.5.12)
'@redis/time-series': 1.0.5(@redis/client@1.5.12)
'@redis/bloom': 1.2.0(@redis/client@1.5.13)
'@redis/client': 1.5.13
'@redis/graph': 1.1.1(@redis/client@1.5.13)
'@redis/json': 1.0.6(@redis/client@1.5.13)
'@redis/search': 1.1.6(@redis/client@1.5.13)
'@redis/time-series': 1.0.5(@redis/client@1.5.13)
dev: false
/reflect-metadata@0.1.13:
@ -25740,7 +25721,7 @@ packages:
- supports-color
dev: false
/typeorm@0.3.17(mssql@9.1.1)(pg@8.11.3)(redis@4.6.11)(sqlite3@5.1.6):
/typeorm@0.3.17(mssql@9.1.1)(pg@8.11.3)(redis@4.6.12)(sqlite3@5.1.6):
resolution: {integrity: sha512-UDjUEwIQalO9tWw9O2A4GU+sT3oyoUXheHJy4ft+RFdnRdQctdQ34L9SqE2p7LdwzafHx1maxT+bqXON+Qnmig==}
engines: {node: '>= 12.9.0'}
hasBin: true
@ -25810,7 +25791,7 @@ packages:
mkdirp: 2.1.3
mssql: 9.1.1
pg: 8.11.3
redis: 4.6.11
redis: 4.6.12
reflect-metadata: 0.1.13
sha.js: 2.4.11
sqlite3: 5.1.6