From f4680f0849a0516453bcab1a23785cbb2888f287 Mon Sep 17 00:00:00 2001 From: Leibale Eidelman Date: Mon, 18 Dec 2023 15:15:21 -0500 Subject: [PATCH 01/10] fix #2665 - handle errors in multi/pipeline replies (#2666) * fix #2665 - handle errors in multi/pipeline replies * fix MultiErrorReply replies type * run tests on all versions, remove console.log, fix bug * add errors iterator helper * test `.errors()` as well --- packages/client/lib/client/index.spec.ts | 19 ++++++++++++++++++- packages/client/lib/errors.ts | 19 +++++++++++++++++++ packages/client/lib/multi-command.ts | 22 +++++++++++++++------- 3 files changed, 52 insertions(+), 8 deletions(-) diff --git a/packages/client/lib/client/index.spec.ts b/packages/client/lib/client/index.spec.ts index 3278d27775..4442d3adb8 100644 --- a/packages/client/lib/client/index.spec.ts +++ b/packages/client/lib/client/index.spec.ts @@ -3,7 +3,7 @@ import testUtils, { GLOBAL, waitTillBeenCalled } from '../test-utils'; import RedisClient, { RedisClientType } from '.'; import { RedisClientMultiCommandType } from './multi-command'; import { RedisCommandRawReply, RedisModules, RedisFunctions, RedisScripts } from '../commands'; -import { AbortError, ClientClosedError, ClientOfflineError, ConnectionTimeoutError, DisconnectsClientError, SocketClosedUnexpectedlyError, WatchError } from '../errors'; +import { AbortError, ClientClosedError, ClientOfflineError, ConnectionTimeoutError, DisconnectsClientError, ErrorReply, MultiErrorReply, SocketClosedUnexpectedlyError, WatchError } from '../errors'; import { defineScript } from '../lua-script'; import { spy } from 'sinon'; import { once } from 'events'; @@ -602,6 +602,23 @@ describe('Client', () => { ...GLOBAL.SERVERS.OPEN, minimumDockerVersion: [6, 2] // CLIENT INFO }); + + testUtils.testWithClient('should handle error replies (#2665)', async client => { + await assert.rejects( + client.multi() + .set('key', 'value') + .hGetAll('key') + .exec(), + err => { + assert.ok(err instanceof MultiErrorReply); + assert.equal(err.replies.length, 2); + assert.deepEqual(err.errorIndexes, [1]); + assert.ok(err.replies[1] instanceof ErrorReply); + assert.deepEqual([...err.errors()], [err.replies[1]]); + return true; + } + ); + }, GLOBAL.SERVERS.OPEN); }); testUtils.testWithClient('scripts', async client => { diff --git a/packages/client/lib/errors.ts b/packages/client/lib/errors.ts index 3070970315..aa97d9cf26 100644 --- a/packages/client/lib/errors.ts +++ b/packages/client/lib/errors.ts @@ -1,3 +1,5 @@ +import { RedisCommandRawReply } from './commands'; + export class AbortError extends Error { constructor() { super('The command was aborted'); @@ -63,3 +65,20 @@ export class ErrorReply extends Error { this.stack = undefined; } } + +export class MultiErrorReply extends ErrorReply { + replies; + errorIndexes; + + constructor(replies: Array, errorIndexes: Array) { + super(`${errorIndexes.length} commands failed, see .replies and .errorIndexes for more information`); + this.replies = replies; + this.errorIndexes = errorIndexes; + } + + *errors() { + for (const index of this.errorIndexes) { + yield this.replies[index]; + } + } +} diff --git a/packages/client/lib/multi-command.ts b/packages/client/lib/multi-command.ts index 08f23ffa45..642c2ea36c 100644 --- a/packages/client/lib/multi-command.ts +++ b/packages/client/lib/multi-command.ts @@ -1,6 +1,6 @@ import { fCallArguments } from './commander'; import { RedisCommand, RedisCommandArguments, RedisCommandRawReply, RedisFunction, RedisScript } from './commands'; -import { WatchError } from './errors'; +import { ErrorReply, MultiErrorReply, WatchError } from './errors'; export interface RedisMultiQueuedCommand { args: RedisCommandArguments; @@ -69,7 +69,7 @@ export default class RedisMultiCommand { return transformedArguments; } - handleExecReplies(rawReplies: Array): Array { + handleExecReplies(rawReplies: Array): Array { const execReply = rawReplies[rawReplies.length - 1] as (null | Array); if (execReply === null) { throw new WatchError(); @@ -78,10 +78,18 @@ export default class RedisMultiCommand { return this.transformReplies(execReply); } - transformReplies(rawReplies: Array): Array { - return rawReplies.map((reply, i) => { - const { transformReply, args } = this.queue[i]; - return transformReply ? transformReply(reply, args.preserve) : reply; - }); + transformReplies(rawReplies: Array): Array { + const errorIndexes: Array = [], + replies = rawReplies.map((reply, i) => { + if (reply instanceof ErrorReply) { + errorIndexes.push(i); + return reply; + } + const { transformReply, args } = this.queue[i]; + return transformReply ? transformReply(reply, args.preserve) : reply; + }); + + if (errorIndexes.length) throw new MultiErrorReply(replies, errorIndexes); + return replies; } } From bb6f14cf7e5f68d1658531db5eec4d0ca635570b Mon Sep 17 00:00:00 2001 From: Leibale Date: Mon, 18 Dec 2023 15:18:29 -0500 Subject: [PATCH 02/10] Release client@1.5.13 --- packages/client/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/package.json b/packages/client/package.json index aa1ce57b11..15ab1c5f1e 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@redis/client", - "version": "1.5.12", + "version": "1.5.13", "license": "MIT", "main": "./dist/index.js", "types": "./dist/index.d.ts", From 55d07d276790edfd7d46b6297226c93da4d4d23b Mon Sep 17 00:00:00 2001 From: Leibale Date: Mon, 18 Dec 2023 15:20:16 -0500 Subject: [PATCH 03/10] upgrade @redis/client --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1557c39877..f71f608e9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ ], "dependencies": { "@redis/bloom": "1.2.0", - "@redis/client": "1.5.12", + "@redis/client": "1.5.13", "@redis/graph": "1.1.1", "@redis/json": "1.0.6", "@redis/search": "1.1.6", @@ -8838,7 +8838,7 @@ }, "packages/client": { "name": "@redis/client", - "version": "1.5.12", + "version": "1.5.13", "license": "MIT", "dependencies": { "cluster-key-slot": "1.1.2", diff --git a/package.json b/package.json index 2924233d9c..5845f6faff 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ }, "dependencies": { "@redis/bloom": "1.2.0", - "@redis/client": "1.5.12", + "@redis/client": "1.5.13", "@redis/graph": "1.1.1", "@redis/json": "1.0.6", "@redis/search": "1.1.6", From ccd96101773c7332c01a2eb45cfbd21868a7490b Mon Sep 17 00:00:00 2001 From: Leibale Date: Mon, 18 Dec 2023 15:20:52 -0500 Subject: [PATCH 04/10] Release redis@4.6.12 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index f71f608e9d..683c1e7c28 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "redis", - "version": "4.6.11", + "version": "4.6.12", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "redis", - "version": "4.6.11", + "version": "4.6.12", "license": "MIT", "workspaces": [ "./packages/*" diff --git a/package.json b/package.json index 5845f6faff..08413982c9 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "redis", "description": "A modern, high performance Redis client", - "version": "4.6.11", + "version": "4.6.12", "license": "MIT", "main": "./dist/index.js", "types": "./dist/index.d.ts", From 5a96058c2f77c1278a0438ca5923f0772cf74790 Mon Sep 17 00:00:00 2001 From: Chayim Date: Wed, 20 Dec 2023 12:32:49 +0200 Subject: [PATCH 05/10] Linking to Redis learning resources (#2628) --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 95816f51e9..a590372b1b 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,19 @@ node-redis is a modern, high performance [Redis](https://redis.io) client for Node.js. +## How do I Redis? + +[Learn for free at Redis University](https://university.redis.com/) + +[Build faster with the Redis Launchpad](https://launchpad.redis.com/) + +[Try the Redis Cloud](https://redis.com/try-free/) + +[Dive in developer tutorials](https://developer.redis.com/) + +[Join the Redis community](https://redis.com/community/) + +[Work at Redis](https://redis.com/company/careers/jobs/) ## Packages From 295647cf9d7ddc93b9b516a45098765084a0c2d8 Mon Sep 17 00:00:00 2001 From: Brent Layne Date: Mon, 29 Jan 2024 03:25:26 -0500 Subject: [PATCH 06/10] fix(clustered pubsub): check that `client.isOpen` before calling `client.disconnect()` when unsubscribing (#2687) * Confirm the client isOpen before disconnecting * Write tests * fix tests * fix tests --------- Co-authored-by: Leibale Eidelman --- packages/client/lib/cluster/cluster-slots.ts | 4 +-- packages/client/lib/cluster/index.spec.ts | 27 ++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/packages/client/lib/cluster/cluster-slots.ts b/packages/client/lib/cluster/cluster-slots.ts index b540c2fa85..b1cc49b4c8 100644 --- a/packages/client/lib/cluster/cluster-slots.ts +++ b/packages/client/lib/cluster/cluster-slots.ts @@ -562,7 +562,7 @@ export default class RedisClusterSlots< const client = await this.getPubSubClient(); await unsubscribe(client); - if (!client.isPubSubActive) { + if (!client.isPubSubActive && client.isOpen) { await client.disconnect(); this.pubSubNode = undefined; } @@ -613,7 +613,7 @@ export default class RedisClusterSlots< const client = await master.pubSubClient; await unsubscribe(client); - if (!client.isPubSubActive) { + if (!client.isPubSubActive && client.isOpen) { await client.disconnect(); master.pubSubClient = undefined; } diff --git a/packages/client/lib/cluster/index.spec.ts b/packages/client/lib/cluster/index.spec.ts index 8200375056..569d716272 100644 --- a/packages/client/lib/cluster/index.spec.ts +++ b/packages/client/lib/cluster/index.spec.ts @@ -235,6 +235,18 @@ describe('Cluster', () => { assert.equal(cluster.pubSubNode, undefined); }, GLOBAL.CLUSTERS.OPEN); + + testUtils.testWithCluster('concurrent UNSUBSCRIBE does not throw an error (#2685)', async cluster => { + const listener = spy(); + await Promise.all([ + cluster.subscribe('1', listener), + cluster.subscribe('2', listener) + ]); + await Promise.all([ + cluster.unsubscribe('1', listener), + cluster.unsubscribe('2', listener) + ]); + }, GLOBAL.CLUSTERS.OPEN); testUtils.testWithCluster('psubscribe & punsubscribe', async cluster => { const listener = spy(); @@ -323,6 +335,21 @@ describe('Cluster', () => { minimumDockerVersion: [7] }); + testUtils.testWithCluster('concurrent SUNSUBCRIBE does not throw an error (#2685)', async cluster => { + const listener = spy(); + await Promise.all([ + await cluster.sSubscribe('1', listener), + await cluster.sSubscribe('2', listener) + ]); + await Promise.all([ + cluster.sUnsubscribe('1', listener), + cluster.sUnsubscribe('2', listener) + ]); + }, { + ...GLOBAL.CLUSTERS.OPEN, + minimumDockerVersion: [7] + }); + testUtils.testWithCluster('should handle sharded-channel-moved events', async cluster => { const SLOT = 10328, migrating = cluster.slots[SLOT].master, From 0f29f4238f9eebea9fc0f5267c0f424870b7c526 Mon Sep 17 00:00:00 2001 From: Leibale Eidelman Date: Mon, 5 Feb 2024 09:14:24 -0500 Subject: [PATCH 07/10] Change json docker version to 2.6.9 --- packages/json/lib/test-utils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/json/lib/test-utils.ts b/packages/json/lib/test-utils.ts index f4c4e4eb20..55426890e0 100644 --- a/packages/json/lib/test-utils.ts +++ b/packages/json/lib/test-utils.ts @@ -3,7 +3,8 @@ import RedisJSON from '.'; export default new TestUtils({ dockerImageName: 'redislabs/rejson', - dockerImageVersionArgument: 'rejson-version' + dockerImageVersionArgument: 'rejson-version', + defaultDockerVersion: '2.6.9' }); export const GLOBAL = { From 03ab4fbd6c3ceb7500cf95a3465685b0fd8c7152 Mon Sep 17 00:00:00 2001 From: Leibale Date: Mon, 5 Feb 2024 09:24:20 -0500 Subject: [PATCH 08/10] Release client@1.5.14 --- packages/client/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/package.json b/packages/client/package.json index 15ab1c5f1e..839ce6d8b6 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@redis/client", - "version": "1.5.13", + "version": "1.5.14", "license": "MIT", "main": "./dist/index.js", "types": "./dist/index.d.ts", From b6a31dd71f591cd0ff198e94e85b9c041d0e1ab0 Mon Sep 17 00:00:00 2001 From: Leibale Date: Mon, 5 Feb 2024 09:25:14 -0500 Subject: [PATCH 09/10] upgrade `@redis/client` --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 683c1e7c28..40301b362c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ ], "dependencies": { "@redis/bloom": "1.2.0", - "@redis/client": "1.5.13", + "@redis/client": "1.5.14", "@redis/graph": "1.1.1", "@redis/json": "1.0.6", "@redis/search": "1.1.6", @@ -8838,7 +8838,7 @@ }, "packages/client": { "name": "@redis/client", - "version": "1.5.13", + "version": "1.5.14", "license": "MIT", "dependencies": { "cluster-key-slot": "1.1.2", diff --git a/package.json b/package.json index 08413982c9..1f3037dbfe 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ }, "dependencies": { "@redis/bloom": "1.2.0", - "@redis/client": "1.5.13", + "@redis/client": "1.5.14", "@redis/graph": "1.1.1", "@redis/json": "1.0.6", "@redis/search": "1.1.6", From dbf8f59a47573e6a1c75b78e566af8c493015d5d Mon Sep 17 00:00:00 2001 From: Leibale Date: Mon, 5 Feb 2024 09:26:12 -0500 Subject: [PATCH 10/10] Release redis@4.6.13 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 40301b362c..5cae28a971 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "redis", - "version": "4.6.12", + "version": "4.6.13", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "redis", - "version": "4.6.12", + "version": "4.6.13", "license": "MIT", "workspaces": [ "./packages/*" diff --git a/package.json b/package.json index 1f3037dbfe..07e1f13484 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "redis", "description": "A modern, high performance Redis client", - "version": "4.6.12", + "version": "4.6.13", "license": "MIT", "main": "./dist/index.js", "types": "./dist/index.d.ts",