diff --git a/packages/client/lib/client/index.spec.ts b/packages/client/lib/client/index.spec.ts index fa3fbe55c4..9590ed6868 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 'node:events'; @@ -282,6 +282,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); + 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 33a2129b2b..1453305523 100644 --- a/packages/client/lib/errors.ts +++ b/packages/client/lib/errors.ts @@ -69,3 +69,14 @@ export class SimpleError extends ErrorReply {} export class BlobError extends ErrorReply {} export class TimeoutError extends Error {} + +export class MultiErrorReply extends ErrorReply { + replies: Array; + errorIndexes: Array; + + constructor(replies: Array, errorIndexes: Array) { + super(`${errorIndexes.length} commands failed, see .replies and .errorIndexes for more information`); + this.replies = replies; + this.errorIndexes = errorIndexes; + } +} diff --git a/packages/client/lib/multi-command.ts b/packages/client/lib/multi-command.ts index 7396b1219a..019a020328 100644 --- a/packages/client/lib/multi-command.ts +++ b/packages/client/lib/multi-command.ts @@ -1,4 +1,5 @@ import { CommandArguments, RedisScript, ReplyUnion, TransformReply } from './RESP/types'; +import { ErrorReply, MultiErrorReply } from './errors'; export type MULTI_REPLY = { GENERIC: 'generic'; @@ -46,9 +47,18 @@ export default class RedisMultiCommand { } transformReplies(rawReplies: Array): Array { - return rawReplies.map((reply, i) => { - const { transformReply, args } = this.queue[i]; - return transformReply ? transformReply(reply, args.preserve) : reply; - }); + 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; } }