diff --git a/package-lock.json b/package-lock.json index 312ece6a26c..3fffdb6d2f5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1376,9 +1376,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.11.16", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.16.tgz", - "integrity": "sha512-gKb0enTmRCzXSSUJDq6/sPcqrfCv2mkkG6Jt/clpn5eiCbKTY+SgZUxo+p8ZKMof5dCp9vHQUAB7wOUTod22wQ==", + "version": "20.12.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.2.tgz", + "integrity": "sha512-zQ0NYO87hyN6Xrclcqp7f8ZbXNbRfoGWNcMvHTPQp9UUrwI0mI7XBz+cu7/W6/VClYo2g63B0cjull/srU7LgQ==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -8127,16 +8127,21 @@ } }, "packages/bloom": { + "name": "@redis/bloom", "version": "2.0.0-next.3", "license": "MIT", "devDependencies": { "@redis/test-utils": "*" }, + "engines": { + "node": ">= 18" + }, "peerDependencies": { "@redis/client": "^2.0.0-next.4" } }, "packages/client": { + "name": "@redis/client", "version": "2.0.0-next.4", "license": "MIT", "dependencies": { @@ -8144,29 +8149,38 @@ }, "devDependencies": { "@redis/test-utils": "*", + "@types/node": "^20.12.2", "@types/sinon": "^17.0.3", "sinon": "^17.0.1" }, "engines": { - "node": ">=14" + "node": ">= 18" } }, "packages/graph": { + "name": "@redis/graph", "version": "2.0.0-next.2", "license": "MIT", "devDependencies": { "@redis/test-utils": "*" }, + "engines": { + "node": ">= 18" + }, "peerDependencies": { "@redis/client": "^2.0.0-next.4" } }, "packages/json": { + "name": "@redis/json", "version": "2.0.0-next.2", "license": "MIT", "devDependencies": { "@redis/test-utils": "*" }, + "engines": { + "node": ">= 18" + }, "peerDependencies": { "@redis/client": "^2.0.0-next.4" } @@ -8181,19 +8195,27 @@ "@redis/json": "2.0.0-next.2", "@redis/search": "2.0.0-next.2", "@redis/time-series": "2.0.0-next.2" + }, + "engines": { + "node": ">= 18" } }, "packages/search": { + "name": "@redis/search", "version": "2.0.0-next.2", "license": "MIT", "devDependencies": { "@redis/test-utils": "*" }, + "engines": { + "node": ">= 18" + }, "peerDependencies": { "@redis/client": "^2.0.0-next.4" } }, "packages/test-utils": { + "name": "@redis/test-utils", "devDependencies": { "@types/yargs": "^17.0.32", "yargs": "^17.7.2" @@ -8261,11 +8283,15 @@ } }, "packages/time-series": { + "name": "@redis/time-series", "version": "2.0.0-next.2", "license": "MIT", "devDependencies": { "@redis/test-utils": "*" }, + "engines": { + "node": ">= 18" + }, "peerDependencies": { "@redis/client": "^2.0.0-next.4" } diff --git a/packages/client/lib/RESP/encoder.ts b/packages/client/lib/RESP/encoder.ts index af857711dc3..995650627f1 100644 --- a/packages/client/lib/RESP/encoder.ts +++ b/packages/client/lib/RESP/encoder.ts @@ -2,7 +2,7 @@ import { RedisArgument } from './types'; const CRLF = '\r\n'; -export default function encodeCommand(args: Array): Array { +export default function encodeCommand(args: ReadonlyArray): ReadonlyArray { const toWrite: Array = []; let strings = '*' + args.length + CRLF; diff --git a/packages/client/lib/RESP/types.ts b/packages/client/lib/RESP/types.ts index 9f0e9217345..a1dd236151e 100644 --- a/packages/client/lib/RESP/types.ts +++ b/packages/client/lib/RESP/types.ts @@ -1,3 +1,4 @@ +import { CommandParser } from '../client/parser'; import { BlobError, SimpleError } from '../errors'; import { RedisScriptConfig, SHA1 } from '../lua-script'; import { RESP_TYPES } from './decoder'; @@ -272,6 +273,7 @@ export type Command = { */ IS_FORWARD_COMMAND?: boolean; // POLICIES?: CommandPolicies; + parseCommand?(this: void, parser: CommandParser, ...args: Array): void; transformArguments(this: void, ...args: Array): CommandArguments; TRANSFORM_LEGACY_REPLY?: boolean; transformReply: TransformReply | Record; diff --git a/packages/client/lib/client/commands-queue.ts b/packages/client/lib/client/commands-queue.ts index a4029779fc8..15e8a747b98 100644 --- a/packages/client/lib/client/commands-queue.ts +++ b/packages/client/lib/client/commands-queue.ts @@ -1,7 +1,7 @@ import { SinglyLinkedList, DoublyLinkedNode, DoublyLinkedList } from './linked-list'; import encodeCommand from '../RESP/encoder'; import { Decoder, PUSH_TYPE_MAPPING, RESP_TYPES } from '../RESP/decoder'; -import { CommandArguments, TypeMapping, ReplyUnion, RespVersions } from '../RESP/types'; +import { TypeMapping, ReplyUnion, RespVersions, RedisArgument } from '../RESP/types'; import { ChannelListeners, PubSub, PubSubCommand, PubSubListener, PubSubType, PubSubTypeListeners } from './pub-sub'; import { AbortError, ErrorReply } from '../errors'; import { MonitorCallback } from '.'; @@ -17,7 +17,7 @@ export interface CommandOptions { } export interface CommandToWrite extends CommandWaitingForReply { - args: CommandArguments; + args: ReadonlyArray; chainId: symbol | undefined; abort: { signal: AbortSignal; @@ -117,7 +117,7 @@ export default class RedisCommandsQueue { } addCommand( - args: CommandArguments, + args: ReadonlyArray, options?: CommandOptions ): Promise { if (this.#maxLength && this.#toWrite.length + this.#waitingForReply.length >= this.#maxLength) { @@ -346,7 +346,7 @@ export default class RedisCommandsQueue { *commandsToWrite() { let toSend = this.#toWrite.shift(); while (toSend) { - let encoded: CommandArguments; + let encoded: ReadonlyArray try { encoded = encodeCommand(toSend.args); } catch (err) { diff --git a/packages/client/lib/client/index.ts b/packages/client/lib/client/index.ts index 3efa793eeb9..e1f7fa0592a 100644 --- a/packages/client/lib/client/index.ts +++ b/packages/client/lib/client/index.ts @@ -7,7 +7,7 @@ import { ClientClosedError, ClientOfflineError, DisconnectsClientError, WatchErr import { URL } from 'node:url'; import { TcpSocketConnectOpts } from 'node:net'; import { PUBSUB_TYPE, PubSubType, PubSubListener, PubSubTypeListeners, ChannelListeners } from './pub-sub'; -import { Command, CommandSignature, TypeMapping, CommanderConfig, RedisFunction, RedisFunctions, RedisModules, RedisScript, RedisScripts, ReplyUnion, RespVersions, RedisArgument, ReplyWithTypeMapping, SimpleStringReply } from '../RESP/types'; +import { Command, CommandSignature, TypeMapping, CommanderConfig, RedisFunction, RedisFunctions, RedisModules, RedisScript, RedisScripts, ReplyUnion, RespVersions, RedisArgument, ReplyWithTypeMapping, SimpleStringReply, TransformReply } from '../RESP/types'; import RedisClientMultiCommand, { RedisClientMultiCommandType } from './multi-command'; import { RedisMultiQueuedCommand } from '../multi-command'; import HELLO, { HelloOptions } from '../commands/HELLO'; @@ -15,6 +15,7 @@ import { ScanOptions, ScanCommonOptions } from '../commands/SCAN'; import { RedisLegacyClient, RedisLegacyClientType } from './legacy-mode'; import { RedisPoolOptions, RedisClientPool } from './pool'; import { RedisVariadicArgument, pushVariadicArguments } from '../commands/generic-transformers'; +import { BasicCommandParser, CommandParser } from './parser'; export interface RedisClientOptions< M extends RedisModules = RedisModules, @@ -151,52 +152,87 @@ export default class RedisClient< > extends EventEmitter { static #createCommand(command: Command, resp: RespVersions) { const transformReply = getTransformReply(command, resp); + return async function (this: ProxyClient, ...args: Array) { - const redisArgs = command.transformArguments(...args), - reply = await this.sendCommand(redisArgs, this._commandOptions); - return transformReply ? - transformReply(reply, redisArgs.preserve) : - reply; - }; + if (command.parseCommand) { + const parser = new BasicCommandParser(resp); + + command.parseCommand(parser, ...args); + + return this.executeCommand(parser, this._commandOptions, transformReply); + } else { + const redisArgs = command.transformArguments(...args), + reply = await this.sendCommand(redisArgs, this._commandOptions); + return transformReply ? + transformReply(reply, redisArgs.preserve) : + reply; + }; + } } static #createModuleCommand(command: Command, resp: RespVersions) { const transformReply = getTransformReply(command, resp); + return async function (this: NamespaceProxyClient, ...args: Array) { - const redisArgs = command.transformArguments(...args), - reply = await this._self.sendCommand(redisArgs, this._self._commandOptions); - return transformReply ? - transformReply(reply, redisArgs.preserve) : - reply; + if (command.parseCommand) { + const parser = new BasicCommandParser(resp); + command.parseCommand(parser, ...args); + + return this._self.executeCommand(parser, this._self._commandOptions, transformReply); + } else { + const redisArgs = command.transformArguments(...args), + reply = await this._self.sendCommand(redisArgs, this._self._commandOptions); + return transformReply ? + transformReply(reply, redisArgs.preserve) : + reply; + } }; } static #createFunctionCommand(name: string, fn: RedisFunction, resp: RespVersions) { - const prefix = functionArgumentsPrefix(name, fn), - transformReply = getTransformReply(fn, resp); + const prefix = functionArgumentsPrefix(name, fn); + const transformReply = getTransformReply(fn, resp); + return async function (this: NamespaceProxyClient, ...args: Array) { - const fnArgs = fn.transformArguments(...args), - reply = await this._self.sendCommand( - prefix.concat(fnArgs), - this._self._commandOptions - ); - return transformReply ? - transformReply(reply, fnArgs.preserve) : - reply; + if (fn.parseCommand) { + const parser = new BasicCommandParser(resp); + parser.pushVariadic(prefix); + fn.parseCommand(parser, ...args); + + return this._self.executeCommand(parser, this._self._commandOptions, transformReply); + } else { + const fnArgs = fn.transformArguments(...args), + reply = await this._self.sendCommand( + prefix.concat(fnArgs), + this._self._commandOptions + ); + return transformReply ? + transformReply(reply, fnArgs.preserve) : + reply; + } }; } static #createScriptCommand(script: RedisScript, resp: RespVersions) { - const prefix = scriptArgumentsPrefix(script), - transformReply = getTransformReply(script, resp); + const prefix = scriptArgumentsPrefix(script); + const transformReply = getTransformReply(script, resp); + return async function (this: ProxyClient, ...args: Array) { - const scriptArgs = script.transformArguments(...args), - redisArgs = prefix.concat(scriptArgs), - reply = await this.executeScript(script, redisArgs, this._commandOptions); - return transformReply ? - transformReply(reply, scriptArgs.preserve) : - reply; - }; + if (script.parseCommand) { + const parser = new BasicCommandParser(resp); + parser.pushVariadic(prefix); + script.parseCommand(parser, ...args); + + return this.executeCommand(parser, this._commandOptions, transformReply); + } else { + const scriptArgs = script.transformArguments(...args), + redisArgs = prefix.concat(scriptArgs), + reply = await this.executeScript(script, redisArgs, this._commandOptions); + return transformReply ? + transformReply(reply, scriptArgs.preserve) : + reply; + }; + } } static factory< @@ -573,8 +609,22 @@ export default class RedisClient< return this as unknown as RedisClientType; } + async executeCommand( + parser: CommandParser, + commandOptions: CommandOptions | undefined, + transformReply: TransformReply | undefined + ) { + const reply = await this.sendCommand(parser.redisArgs, commandOptions); + + if (transformReply) { + return transformReply(reply, parser.preserve); + } + + return reply; + } + sendCommand( - args: Array, + args: ReadonlyArray, options?: CommandOptions ): Promise { if (!this._self.#socket.isOpen) { diff --git a/packages/client/lib/client/multi-command.ts b/packages/client/lib/client/multi-command.ts index ef65144d56b..9b800599b43 100644 --- a/packages/client/lib/client/multi-command.ts +++ b/packages/client/lib/client/multi-command.ts @@ -1,7 +1,8 @@ import COMMANDS from '../commands'; import RedisMultiCommand, { MULTI_REPLY, MultiReply, MultiReplyType, RedisMultiQueuedCommand } from '../multi-command'; import { ReplyWithTypeMapping, CommandReply, Command, CommandArguments, CommanderConfig, RedisFunctions, RedisModules, RedisScripts, RespVersions, TransformReply, RedisScript, RedisFunction, TypeMapping } from '../RESP/types'; -import { attachConfig, functionArgumentsPrefix, getTransformReply } from '../commander'; +import { attachConfig, functionArgumentsPrefix, getTransformReply, scriptArgumentsPrefix } from '../commander'; +import { BasicCommandParser } from './parser'; type CommandSignature< REPLIES extends Array, @@ -88,9 +89,22 @@ type ExecuteMulti = (commands: Array, selectedDB?: numb export default class RedisClientMultiCommand { static #createCommand(command: Command, resp: RespVersions) { const transformReply = getTransformReply(command, resp); + return function (this: RedisClientMultiCommand, ...args: Array) { + let redisArgs: CommandArguments; + + if (command.parseCommand) { + const parser = new BasicCommandParser(resp); + command.parseCommand(parser, ...args); + + redisArgs = parser.redisArgs; + redisArgs.preserve = parser.preserve; + } else { + redisArgs = command.transformArguments(...args); + } + return this.addCommand( - command.transformArguments(...args), + redisArgs, transformReply ); }; @@ -98,21 +112,48 @@ export default class RedisClientMultiCommand { static #createModuleCommand(command: Command, resp: RespVersions) { const transformReply = getTransformReply(command, resp); + return function (this: { _self: RedisClientMultiCommand }, ...args: Array) { + let redisArgs: CommandArguments; + + if (command.parseCommand) { + const parser = new BasicCommandParser(resp); + command.parseCommand(parser, ...args); + + redisArgs = parser.redisArgs; + redisArgs.preserve = parser.preserve; + } else { + redisArgs = command.transformArguments(...args); + } + return this._self.addCommand( - command.transformArguments(...args), + redisArgs, transformReply ); }; } static #createFunctionCommand(name: string, fn: RedisFunction, resp: RespVersions) { - const prefix = functionArgumentsPrefix(name, fn), - transformReply = getTransformReply(fn, resp); + const prefix = functionArgumentsPrefix(name, fn); + const transformReply = getTransformReply(fn, resp); + return function (this: { _self: RedisClientMultiCommand }, ...args: Array) { - const fnArgs = fn.transformArguments(...args), - redisArgs: CommandArguments = prefix.concat(fnArgs); + let fnArgs: CommandArguments; + + if (fn.parseCommand) { + const parser = new BasicCommandParser(resp); + parser.pushVariadic(prefix); + fn.parseCommand(parser, ...args); + + fnArgs = parser.redisArgs; + fnArgs.preserve = parser.preserve; + } else { + fnArgs = fn.transformArguments(...args); + } + + const redisArgs: CommandArguments = prefix.concat(fnArgs); redisArgs.preserve = fnArgs.preserve; + return this._self.addCommand( redisArgs, transformReply @@ -121,14 +162,28 @@ export default class RedisClientMultiCommand { } static #createScriptCommand(script: RedisScript, resp: RespVersions) { + const prefix = scriptArgumentsPrefix(script); const transformReply = getTransformReply(script, resp); + return function (this: RedisClientMultiCommand, ...args: Array) { - this.#multi.addScript( - script, - script.transformArguments(...args), + let redisArgs: CommandArguments; + + if (script.parseCommand) { + const parser = new BasicCommandParser(resp); + parser.pushVariadic(prefix); + script.parseCommand(parser, ...args); + + redisArgs = parser.redisArgs; + redisArgs.preserve = parser.preserve; + } else { + redisArgs = prefix; + redisArgs.push(...script.transformArguments(...args)); + } + + return this.addCommand( + redisArgs, transformReply ); - return this; }; } diff --git a/packages/client/lib/client/parser.ts b/packages/client/lib/client/parser.ts new file mode 100644 index 00000000000..ae96045d146 --- /dev/null +++ b/packages/client/lib/client/parser.ts @@ -0,0 +1,113 @@ +import { RedisArgument, RespVersions } from "../.."; +import { RedisVariadicArgument } from "../commands/generic-transformers"; + +export interface CommandParser { + redisArgs: ReadonlyArray; + keys: ReadonlyArray; + firstKey: RedisArgument | undefined; + respVersion: RespVersions; + preserve: unknown; + cachable: boolean; + + push: (arg: RedisArgument) => unknown; + pushVariadic: (vals: RedisVariadicArgument) => unknown; + pushVariadicNumber: (vals: number | Array) => unknown; + pushKey: (key: RedisArgument) => unknown; // normal push of keys + pushKeys: (keys: RedisVariadicArgument) => unknown; // push multiple keys at a time + pushKeysLength: (keys: RedisVariadicArgument) => unknown; // push multiple keys at a time + setCachable: () => unknown; + setPreserve: (val: unknown) => unknown; +} + +// TODO: make multiple parsers to improve performance? +export class BasicCommandParser implements CommandParser { + #redisArgs: Array = []; + #keys: Array = []; + #respVersion: RespVersions; + #preserve: unknown; + #cachable: boolean = false; + + constructor(respVersion: RespVersions = 2) { + this.#respVersion = respVersion; + } + + get redisArgs() { + return this.#redisArgs; + } + + get keys() { + return this.#keys; + } + + get firstKey() { + return this.#keys.length != 0 ? this.#keys[0] : undefined; + } + + get respVersion() { + return this.#respVersion; + } + + get preserve() { + return this.#preserve; + } + + get cachable() { + return this.#cachable + } + + push(arg: RedisArgument) { + this.#redisArgs.push(arg); + }; + + pushVariadic(vals: RedisVariadicArgument) { + if (Array.isArray(vals)) { + for (const val of vals) { + this.push(val); + } + } else { + this.push(vals); + } + } + + pushVariadicNumber(vals: number | number[]) { + if (Array.isArray(vals)) { + for (const val of vals) { + this.push(val.toString()); + } + } else { + this.push(vals.toString()); + } + } + + pushKey(key: RedisArgument) { + this.#keys.push(key); + this.#redisArgs.push(key); + }; + + pushKeysLength(keys: RedisVariadicArgument) { + if (Array.isArray(keys)) { + this.#redisArgs.push(keys.length.toString()); + } else { + this.#redisArgs.push('1'); + } + this.pushKeys(keys); + } + + pushKeys(keys: RedisVariadicArgument) { + if (Array.isArray(keys)) { + this.#keys.push(...keys); + this.#redisArgs.push(...keys); + } else { + this.#keys.push(keys); + this.#redisArgs.push(keys); + } + } + + setPreserve(val: unknown) { + this.#preserve = val; + } + + setCachable() { + this.#cachable = true; + }; +} diff --git a/packages/client/lib/client/pool.ts b/packages/client/lib/client/pool.ts index fc996e07625..5822e418b1b 100644 --- a/packages/client/lib/client/pool.ts +++ b/packages/client/lib/client/pool.ts @@ -7,6 +7,7 @@ import { TimeoutError } from '../errors'; import { attachConfig, functionArgumentsPrefix, getTransformReply, scriptArgumentsPrefix } from '../commander'; import { CommandOptions } from './commands-queue'; import RedisClientMultiCommand, { RedisClientMultiCommandType } from './multi-command'; +import { BasicCommandParser } from './parser'; export interface RedisPoolOptions { /** @@ -60,51 +61,85 @@ export class RedisClientPool< > extends EventEmitter { static #createCommand(command: Command, resp: RespVersions) { const transformReply = getTransformReply(command, resp); + return async function (this: ProxyPool, ...args: Array) { - const redisArgs = command.transformArguments(...args), - reply = await this.sendCommand(redisArgs, this._commandOptions); - return transformReply ? - transformReply(reply, redisArgs.preserve) : - reply; + if (command.parseCommand) { + const parser = new BasicCommandParser(resp); + command.parseCommand(parser, ...args); + + return this.execute(client => client.executeCommand(parser, this._commandOptions, transformReply)) + } else { + const redisArgs = command.transformArguments(...args), + reply = await this.sendCommand(redisArgs, this._commandOptions); + return transformReply ? + transformReply(reply, redisArgs.preserve) : + reply; + } }; } static #createModuleCommand(command: Command, resp: RespVersions) { const transformReply = getTransformReply(command, resp); + return async function (this: NamespaceProxyPool, ...args: Array) { - const redisArgs = command.transformArguments(...args), - reply = await this._self.sendCommand(redisArgs, this._self._commandOptions); - return transformReply ? - transformReply(reply, redisArgs.preserve) : - reply; + if (command.parseCommand) { + const parser = new BasicCommandParser(resp); + command.parseCommand(parser, ...args); + + return this._self.execute(client => client.executeCommand(parser, this._self._commandOptions, transformReply)) + } else { + const redisArgs = command.transformArguments(...args), + reply = await this._self.sendCommand(redisArgs, this._self._commandOptions); + return transformReply ? + transformReply(reply, redisArgs.preserve) : + reply; + } }; } static #createFunctionCommand(name: string, fn: RedisFunction, resp: RespVersions) { - const prefix = functionArgumentsPrefix(name, fn), - transformReply = getTransformReply(fn, resp); + const prefix = functionArgumentsPrefix(name, fn); + const transformReply = getTransformReply(fn, resp); + return async function (this: NamespaceProxyPool, ...args: Array) { - const fnArgs = fn.transformArguments(...args), - reply = await this._self.sendCommand( - prefix.concat(fnArgs), - this._self._commandOptions - ); - return transformReply ? - transformReply(reply, fnArgs.preserve) : - reply; + if (fn.parseCommand) { + const parser = new BasicCommandParser(resp); + parser.pushVariadic(prefix); + fn.parseCommand(parser, ...args); + + return this._self.execute(client => client.executeCommand(parser, this._self._commandOptions, transformReply)) + } else { + const fnArgs = fn.transformArguments(...args), + reply = await this._self.sendCommand( + prefix.concat(fnArgs), + this._self._commandOptions + ); + return transformReply ? + transformReply(reply, fnArgs.preserve) : + reply; + } }; } static #createScriptCommand(script: RedisScript, resp: RespVersions) { - const prefix = scriptArgumentsPrefix(script), - transformReply = getTransformReply(script, resp); + const prefix = scriptArgumentsPrefix(script); + const transformReply = getTransformReply(script, resp); + return async function (this: ProxyPool, ...args: Array) { - const scriptArgs = script.transformArguments(...args), - redisArgs = prefix.concat(scriptArgs), - reply = await this.executeScript(script, redisArgs, this._commandOptions); - return transformReply ? - transformReply(reply, scriptArgs.preserve) : - reply; + if (script.parseCommand) { + const parser = new BasicCommandParser(resp); + parser.pushVariadic(prefix); + script.parseCommand(parser, ...args); + + return this.execute(client => client.executeCommand(parser, this._commandOptions, transformReply)) + } else { + const scriptArgs = script.transformArguments(...args), + redisArgs = prefix.concat(scriptArgs), + reply = await this.executeScript(script, redisArgs, this._commandOptions); + return transformReply ? + transformReply(reply, scriptArgs.preserve) : + reply; + } }; } @@ -207,6 +242,7 @@ export class RedisClientPool< return this._self.#isClosing; } + /** * You are probably looking for {@link RedisClient.createPool `RedisClient.createPool`}, * {@link RedisClientPool.fromClient `RedisClientPool.fromClient`}, diff --git a/packages/client/lib/client/socket.ts b/packages/client/lib/client/socket.ts index dcadad4c3dd..384dd7364e9 100644 --- a/packages/client/lib/client/socket.ts +++ b/packages/client/lib/client/socket.ts @@ -271,7 +271,7 @@ export default class RedisSocket extends EventEmitter { }); } - write(iterable: Iterable>) { + write(iterable: Iterable>) { if (!this.#socket) return; this.#socket.cork(); diff --git a/packages/client/lib/cluster/index.ts b/packages/client/lib/cluster/index.ts index d6018fc270e..65c009bb6a6 100644 --- a/packages/client/lib/cluster/index.ts +++ b/packages/client/lib/cluster/index.ts @@ -9,6 +9,10 @@ import RedisClusterMultiCommand, { RedisClusterMultiCommandType } from './multi- import { PubSubListener } from '../client/pub-sub'; import { ErrorReply } from '../errors'; import { RedisTcpSocketOptions } from '../client/socket'; +import ASKING from '../commands/ASKING'; +import { BasicCommandParser } from '../client/parser'; +import { parseArgs } from '../commands/generic-transformers'; +; interface ClusterCommander< M extends RedisModules, @@ -169,14 +173,27 @@ export default class RedisCluster< static #createCommand(command: Command, resp: RespVersions) { const transformReply = getTransformReply(command, resp); + return async function (this: ProxyCluster, ...args: Array) { - const redisArgs = command.transformArguments(...args), - firstKey = RedisCluster.extractFirstKey( + if (command.parseCommand) { + const parser = new BasicCommandParser(resp); + command.parseCommand(parser, ...args); + + return this._self.#execute( + parser.firstKey, + command.IS_READ_ONLY, + this._commandOptions, + (client, opts) => client.executeCommand(parser, opts, transformReply) + ); + } else { + const redisArgs = command.transformArguments(...args); + const firstKey = RedisCluster.extractFirstKey( command, args, redisArgs - ), - reply = await this.sendCommand( + ); + + const reply = await this.sendCommand( firstKey, command.IS_READ_ONLY, redisArgs, @@ -184,83 +201,125 @@ export default class RedisCluster< // command.POLICIES ); - return transformReply ? - transformReply(reply, redisArgs.preserve) : - reply; + return transformReply ? + transformReply(reply, redisArgs.preserve) : + reply; + } }; } static #createModuleCommand(command: Command, resp: RespVersions) { const transformReply = getTransformReply(command, resp); + return async function (this: NamespaceProxyCluster, ...args: Array) { - const redisArgs = command.transformArguments(...args), - firstKey = RedisCluster.extractFirstKey( - command, - args, - redisArgs - ), - reply = await this._self.sendCommand( - firstKey, + if (command.parseCommand) { + const parser = new BasicCommandParser(resp); + command.parseCommand(parser, ...args); + + return this._self.#execute( + parser.firstKey, command.IS_READ_ONLY, - redisArgs, this._self._commandOptions, - // command.POLICIES + (client, opts) => client.executeCommand(parser, opts, transformReply) ); - - return transformReply ? - transformReply(reply, redisArgs.preserve) : - reply; + } else { + const redisArgs = command.transformArguments(...args); + const firstKey = RedisCluster.extractFirstKey( + command, + args, + redisArgs + ), + reply = await this._self.sendCommand( + firstKey, + command.IS_READ_ONLY, + redisArgs, + this._self._commandOptions, + // command.POLICIES + ); + + return transformReply ? + transformReply(reply, redisArgs.preserve) : + reply; + } }; } static #createFunctionCommand(name: string, fn: RedisFunction, resp: RespVersions) { - const prefix = functionArgumentsPrefix(name, fn), - transformReply = getTransformReply(fn, resp); + const prefix = functionArgumentsPrefix(name, fn); + const transformReply = getTransformReply(fn, resp); + return async function (this: NamespaceProxyCluster, ...args: Array) { - const fnArgs = fn.transformArguments(...args), - firstKey = RedisCluster.extractFirstKey( - fn, - args, - fnArgs - ), - redisArgs = prefix.concat(fnArgs), - reply = await this._self.sendCommand( - firstKey, + if (fn.parseCommand) { + const parser = new BasicCommandParser(resp); + parser.pushVariadic(prefix); + fn.parseCommand(parser, ...args); + + return this._self.#execute( + parser.firstKey, fn.IS_READ_ONLY, - redisArgs, this._self._commandOptions, - // fn.POLICIES + (client, opts) => client.executeCommand(parser, opts, transformReply) ); - - return transformReply ? - transformReply(reply, fnArgs.preserve) : - reply; + } else { + const fnArgs = fn.transformArguments(...args); + const firstKey = RedisCluster.extractFirstKey( + fn, + args, + fnArgs + ), + redisArgs = prefix.concat(fnArgs), + reply = await this._self.sendCommand( + firstKey, + fn.IS_READ_ONLY, + redisArgs, + this._self._commandOptions, + // fn.POLICIES + ); + + return transformReply ? + transformReply(reply, fnArgs.preserve) : + reply; + } }; } static #createScriptCommand(script: RedisScript, resp: RespVersions) { - const prefix = scriptArgumentsPrefix(script), - transformReply = getTransformReply(script, resp); + const prefix = scriptArgumentsPrefix(script); + const transformReply = getTransformReply(script, resp); + return async function (this: ProxyCluster, ...args: Array) { - const scriptArgs = script.transformArguments(...args), - firstKey = RedisCluster.extractFirstKey( - script, - args, - scriptArgs - ), - redisArgs = prefix.concat(scriptArgs), - reply = await this.executeScript( - script, - firstKey, + if (script.parseCommand) { + const parser = new BasicCommandParser(resp); + parser.pushVariadic(prefix); + script.parseCommand(parser, ...args); + + return this._self.#execute( + parser.firstKey, script.IS_READ_ONLY, - redisArgs, this._commandOptions, - // script.POLICIES + (client, opts) => client.executeCommand(parser, opts, transformReply) ); - - return transformReply ? - transformReply(reply, scriptArgs.preserve) : - reply; + } else { + const scriptArgs = script.transformArguments(...args), + firstKey = RedisCluster.extractFirstKey( + script, + args, + scriptArgs + ), + redisArgs = prefix.concat(scriptArgs), + reply = await this.executeScript( + script, + firstKey, + script.IS_READ_ONLY, + redisArgs, + this._commandOptions, + // script.POLICIES + ); + + return transformReply ? + transformReply(reply, scriptArgs.preserve) : + reply; + } }; } @@ -436,15 +495,20 @@ export default class RedisCluster< async #execute( firstKey: RedisArgument | undefined, isReadonly: boolean | undefined, - fn: (client: RedisClientType) => Promise + options: ClusterCommandOptions | undefined, + fn: (client: RedisClientType, opts?: ClusterCommandOptions) => Promise ): Promise { const maxCommandRedirections = this.#options.maxCommandRedirections ?? 16; - let client = await this.#slots.getClient(firstKey, isReadonly), - i = 0; + let client = await this.#slots.getClient(firstKey, isReadonly); + let i = 0; + let myOpts = options; + while (true) { try { - return await fn(client); + return await fn(client, myOpts); } catch (err) { + // reset to passed in options, if changed by an ask request + myOpts = options; // TODO: error class if (++i > maxCommandRedirections || !(err instanceof Error)) { throw err; @@ -462,8 +526,14 @@ export default class RedisCluster< throw new Error(`Cannot find node ${address}`); } - await redirectTo.asking(); client = redirectTo; + + const chainId = Symbol('Asking Chain'); + const myOpts = options ? {...options} : {}; + myOpts.chainId = chainId; + + client.sendCommand(parseArgs(ASKING), {chainId: chainId}).catch(err => { console.log(`Asking Failed: ${err}`) } ); + continue; } @@ -488,7 +558,8 @@ export default class RedisCluster< return this._self.#execute( firstKey, isReadonly, - client => client.sendCommand(args, options) + options, + (client, opts) => client.sendCommand(args, opts) ); } @@ -502,7 +573,8 @@ export default class RedisCluster< return this._self.#execute( firstKey, isReadonly, - client => client.executeScript(script, args, options) + options, + (client, opts) => client.executeScript(script, args, options) ); } diff --git a/packages/client/lib/cluster/multi-command.ts b/packages/client/lib/cluster/multi-command.ts index 225d1624653..3f7df07b085 100644 --- a/packages/client/lib/cluster/multi-command.ts +++ b/packages/client/lib/cluster/multi-command.ts @@ -3,6 +3,7 @@ import RedisMultiCommand, { MULTI_REPLY, MultiReply, MultiReplyType, RedisMultiQ import { ReplyWithTypeMapping, CommandReply, Command, CommandArguments, CommanderConfig, RedisFunctions, RedisModules, RedisScripts, RespVersions, TransformReply, RedisScript, RedisFunction, TypeMapping, RedisArgument } from '../RESP/types'; import { attachConfig, functionArgumentsPrefix, getTransformReply } from '../commander'; import RedisCluster from '.'; +import { BasicCommandParser } from '../client/parser'; type CommandSignature< REPLIES extends Array, @@ -93,13 +94,26 @@ export type ClusterMultiExecute = ( export default class RedisClusterMultiCommand { static #createCommand(command: Command, resp: RespVersions) { const transformReply = getTransformReply(command, resp); + return function (this: RedisClusterMultiCommand, ...args: Array) { - const redisArgs = command.transformArguments(...args), + let redisArgs: CommandArguments = []; + let firstKey: RedisArgument | undefined; + + if (command.parseCommand) { + const parser = new BasicCommandParser(resp); + command.parseCommand(parser, ...args); + redisArgs = parser.redisArgs; + redisArgs.preserve = parser.preserve; + firstKey = parser.firstKey; + } else { + redisArgs = command.transformArguments(...args); firstKey = RedisCluster.extractFirstKey( command, args, redisArgs ); + } + return this.addCommand( firstKey, command.IS_READ_ONLY, @@ -111,13 +125,25 @@ export default class RedisClusterMultiCommand { static #createModuleCommand(command: Command, resp: RespVersions) { const transformReply = getTransformReply(command, resp); + return function (this: { _self: RedisClusterMultiCommand }, ...args: Array) { - const redisArgs = command.transformArguments(...args), + let redisArgs: CommandArguments = []; + let firstKey: RedisArgument | undefined; + + if (command.parseCommand) { + const parser = new BasicCommandParser(resp); + command.parseCommand(parser, ...args); + redisArgs = parser.redisArgs; + redisArgs.preserve = parser.preserve; + firstKey = parser.firstKey; + } else { + redisArgs = command.transformArguments(...args); firstKey = RedisCluster.extractFirstKey( command, args, redisArgs ); + } return this._self.addCommand( firstKey, command.IS_READ_ONLY, @@ -128,17 +154,31 @@ export default class RedisClusterMultiCommand { } static #createFunctionCommand(name: string, fn: RedisFunction, resp: RespVersions) { - const prefix = functionArgumentsPrefix(name, fn), - transformReply = getTransformReply(fn, resp); + const prefix = functionArgumentsPrefix(name, fn); + const transformReply = getTransformReply(fn, resp); + return function (this: { _self: RedisClusterMultiCommand }, ...args: Array) { - const fnArgs = fn.transformArguments(...args), - redisArgs: CommandArguments = prefix.concat(fnArgs), + let fnArgs: CommandArguments = []; + let firstKey: RedisArgument | undefined; + + if (fn.parseCommand) { + const parser = new BasicCommandParser(resp); + fn.parseCommand(parser, ...args); + fnArgs = parser.redisArgs; + fnArgs.preserve = parser.preserve; + firstKey = parser.firstKey; + } else { + fnArgs = fn.transformArguments(...args); firstKey = RedisCluster.extractFirstKey( fn, args, fnArgs ); + } + + const redisArgs: CommandArguments = prefix.concat(fnArgs); redisArgs.preserve = fnArgs.preserve; + return this._self.addCommand( firstKey, fn.IS_READ_ONLY, @@ -150,14 +190,28 @@ export default class RedisClusterMultiCommand { static #createScriptCommand(script: RedisScript, resp: RespVersions) { const transformReply = getTransformReply(script, resp); + return function (this: RedisClusterMultiCommand, ...args: Array) { - const scriptArgs = script.transformArguments(...args); - this.#setState( - RedisCluster.extractFirstKey( + let scriptArgs: CommandArguments = []; + let firstKey: RedisArgument | undefined; + + if (script.parseCommand) { + const parser = new BasicCommandParser(resp); + script.parseCommand(parser, ...args); + scriptArgs = parser.redisArgs; + scriptArgs.preserve = parser.preserve; + firstKey = parser.firstKey; + } else { + scriptArgs = script.transformArguments(...args); + firstKey = RedisCluster.extractFirstKey( script, args, scriptArgs - ), + ) + } + + this.#setState( + firstKey, script.IS_READ_ONLY ); this.#multi.addScript( @@ -165,6 +219,7 @@ export default class RedisClusterMultiCommand { scriptArgs, transformReply ); + return this; }; } diff --git a/packages/client/lib/commander.ts b/packages/client/lib/commander.ts index d96aaa7128e..b3b4204698f 100644 --- a/packages/client/lib/commander.ts +++ b/packages/client/lib/commander.ts @@ -1,4 +1,4 @@ -import { Command, CommanderConfig, RedisCommands, RedisFunction, RedisFunctions, RedisModules, RedisScript, RedisScripts, RespVersions } from './RESP/types'; +import { Command, CommanderConfig, RedisCommands, RedisFunction, RedisFunctions, RedisModules, RedisScript, RedisScripts, RespVersions, TransformReply } from './RESP/types'; interface AttachConfigOptions< M extends RedisModules, @@ -78,7 +78,7 @@ function attachNamespace(prototype: any, name: PropertyKey, fns: any) { }); } -export function getTransformReply(command: Command, resp: RespVersions) { +export function getTransformReply(command: Command, resp: RespVersions): TransformReply | undefined { switch (typeof command.transformReply) { case 'function': return command.transformReply; diff --git a/packages/client/lib/commands/GET.spec.ts b/packages/client/lib/commands/GET.spec.ts index 4bd74183222..3e630d03e0b 100644 --- a/packages/client/lib/commands/GET.spec.ts +++ b/packages/client/lib/commands/GET.spec.ts @@ -1,11 +1,12 @@ import { strict as assert } from 'node:assert'; import testUtils, { GLOBAL } from '../test-utils'; +import { parseArgs } from './generic-transformers'; import GET from './GET'; describe('GET', () => { it('transformArguments', () => { assert.deepEqual( - GET.transformArguments('key'), + parseArgs(GET, 'key'), ['GET', 'key'] ); }); diff --git a/packages/client/lib/commands/GET.ts b/packages/client/lib/commands/GET.ts index bb3db4f76d9..bfd6ed6d39e 100644 --- a/packages/client/lib/commands/GET.ts +++ b/packages/client/lib/commands/GET.ts @@ -1,10 +1,14 @@ import { RedisArgument, BlobStringReply, NullReply, Command } from '../RESP/types'; +import { CommandParser } from '../client/parser'; export default { FIRST_KEY_INDEX: 1, IS_READ_ONLY: true, - transformArguments(key: RedisArgument) { - return ['GET', key]; + parseCommand(parser: CommandParser, key: RedisArgument) { + parser.setCachable(); + parser.push('GET'); + parser.pushKey(key); }, + transformArguments: (key: RedisArgument) => { return [] }, transformReply: undefined as unknown as () => BlobStringReply | NullReply } as const satisfies Command; diff --git a/packages/client/lib/commands/generic-transformers.ts b/packages/client/lib/commands/generic-transformers.ts index a96e79a9c51..d16e7b4cd32 100644 --- a/packages/client/lib/commands/generic-transformers.ts +++ b/packages/client/lib/commands/generic-transformers.ts @@ -1,4 +1,5 @@ -import { UnwrapReply, ArrayReply, BlobStringReply, BooleanReply, CommandArguments, DoubleReply, NullReply, NumberReply, RedisArgument, TuplesReply } from '../RESP/types'; +import { UnwrapReply, ArrayReply, BlobStringReply, BooleanReply, CommandArguments, DoubleReply, NullReply, NumberReply, RedisArgument, TuplesReply, Command } from '../RESP/types'; +import { BasicCommandParser } from '../client/parser'; export function isNullReply(reply: unknown): reply is NullReply { return reply === null; @@ -446,3 +447,30 @@ function isPlainKey(key: RedisArgument | ZKeyAndWeight): key is RedisArgument { function isPlainKeys(keys: Array | Array): keys is Array { return isPlainKey(keys[0]); } + +/** + * @deprecated + */ +export function parseArgs(command: Command, ...args: Array) { + if (command.parseCommand) { + const parser = new BasicCommandParser(); + command.parseCommand!(parser, ...args); + + const redisArgs: CommandArguments = parser.redisArgs; + if (parser.preserve) { + redisArgs.preserve = parser.preserve; + } + return redisArgs; + } else { + return command.transformArguments(...args); + } +} + +/** + * @deprecated + */ +export function parseArgsWith(command: Command, ...args: Array) { + const parser = new BasicCommandParser(); + command.parseCommand!(parser, ...args); + return parser.preserve; +} diff --git a/packages/client/lib/sentinel/multi-commands.ts b/packages/client/lib/sentinel/multi-commands.ts index 12da9b2c97c..ebf856a5381 100644 --- a/packages/client/lib/sentinel/multi-commands.ts +++ b/packages/client/lib/sentinel/multi-commands.ts @@ -3,6 +3,7 @@ import RedisMultiCommand, { MULTI_REPLY, MultiReply, MultiReplyType } from '../m import { ReplyWithTypeMapping, CommandReply, Command, CommandArguments, CommanderConfig, RedisFunctions, RedisModules, RedisScripts, RespVersions, TransformReply, RedisScript, RedisFunction, TypeMapping } from '../RESP/types'; import { attachConfig, functionArgumentsPrefix, getTransformReply } from '../commander'; import { RedisSentinelType } from './types'; +import { BasicCommandParser } from '../client/parser'; type CommandSignature< REPLIES extends Array, @@ -87,8 +88,18 @@ export type RedisSentinelMultiCommandType< export default class RedisSentinelMultiCommand { private static _createCommand(command: Command, resp: RespVersions) { const transformReply = getTransformReply(command, resp); + return function (this: RedisSentinelMultiCommand, ...args: Array) { - const redisArgs = command.transformArguments(...args); + let redisArgs: CommandArguments = []; + if (command.parseCommand) { + const parser = new BasicCommandParser(resp); + command.parseCommand(parser, ...args); + redisArgs = parser.redisArgs; + redisArgs.preserve = parser.preserve; + } else { + redisArgs = command.transformArguments(...args); + } + return this.addCommand( command.IS_READ_ONLY, redisArgs, @@ -99,8 +110,19 @@ export default class RedisSentinelMultiCommand { private static _createModuleCommand(command: Command, resp: RespVersions) { const transformReply = getTransformReply(command, resp); + return function (this: { _self: RedisSentinelMultiCommand }, ...args: Array) { - const redisArgs = command.transformArguments(...args); + let redisArgs: CommandArguments = []; + + if (command.parseCommand) { + const parser = new BasicCommandParser(resp); + command.parseCommand(parser, ...args); + redisArgs = parser.redisArgs; + redisArgs.preserve = parser.preserve; + } else { + redisArgs = command.transformArguments(...args); + } + return this._self.addCommand( command.IS_READ_ONLY, redisArgs, @@ -110,12 +132,24 @@ export default class RedisSentinelMultiCommand { } private static _createFunctionCommand(name: string, fn: RedisFunction, resp: RespVersions) { - const prefix = functionArgumentsPrefix(name, fn), - transformReply = getTransformReply(fn, resp); + const prefix = functionArgumentsPrefix(name, fn); + const transformReply = getTransformReply(fn, resp); + return function (this: { _self: RedisSentinelMultiCommand }, ...args: Array) { - const fnArgs = fn.transformArguments(...args); + let fnArgs: CommandArguments = []; + + if (fn.parseCommand) { + const parser = new BasicCommandParser(resp); + fn.parseCommand(parser, ...args); + fnArgs = parser.redisArgs; + fnArgs.preserve = parser.preserve; + } else { + fnArgs = fn.transformArguments(...args); + } + const redisArgs: CommandArguments = prefix.concat(fnArgs); redisArgs.preserve = fnArgs.preserve; + return this._self.addCommand( fn.IS_READ_ONLY, redisArgs, @@ -126,8 +160,19 @@ export default class RedisSentinelMultiCommand { private static _createScriptCommand(script: RedisScript, resp: RespVersions) { const transformReply = getTransformReply(script, resp); + return function (this: RedisSentinelMultiCommand, ...args: Array) { - const scriptArgs = script.transformArguments(...args); + let scriptArgs: CommandArguments = []; + + if (script.parseCommand) { + const parser = new BasicCommandParser(resp); + script.parseCommand(parser, ...args); + scriptArgs = parser.redisArgs; + scriptArgs.preserve = parser.preserve; + } else { + scriptArgs = script.transformArguments(...args); + } + this._setState( script.IS_READ_ONLY ); @@ -136,6 +181,7 @@ export default class RedisSentinelMultiCommand { scriptArgs, transformReply ); + return this; }; } diff --git a/packages/client/lib/sentinel/utils.ts b/packages/client/lib/sentinel/utils.ts index 4ae829183a2..b8f15e30e76 100644 --- a/packages/client/lib/sentinel/utils.ts +++ b/packages/client/lib/sentinel/utils.ts @@ -1,4 +1,5 @@ import { Command, RedisFunction, RedisScript, RespVersions } from '../RESP/types'; +import { BasicCommandParser } from '../client/parser'; import { RedisSocketOptions, RedisTcpSocketOptions } from '../client/socket'; import { functionArgumentsPrefix, getTransformReply, scriptArgumentsPrefix } from '../commander'; import { NamespaceProxySentinel, NamespaceProxySentinelClient, NodeInfo, ProxySentinel, ProxySentinelClient, RedisNode } from './types'; @@ -37,69 +38,115 @@ export function clientSocketToNode(socket: RedisSocketOptions): RedisNode { export function createCommand(command: Command, resp: RespVersions) { const transformReply = getTransformReply(command, resp); + return async function (this: T, ...args: Array) { - const redisArgs = command.transformArguments(...args), - reply = await this._self.sendCommand( + if (command.parseCommand) { + const parser = new BasicCommandParser(resp); + command.parseCommand(parser, ...args); + + return this._self._execute( command.IS_READ_ONLY, - redisArgs, - this._self.commandOptions + client => client.executeCommand(parser, this.commandOptions, transformReply) ); + } else { + const redisArgs = command.transformArguments(...args); + const reply = await this._self.sendCommand( + command.IS_READ_ONLY, + redisArgs, + this._self.commandOptions + ); - return transformReply ? - transformReply(reply, redisArgs.preserve) : - reply; + return transformReply ? + transformReply(reply, redisArgs.preserve) : + reply; + } }; } export function createFunctionCommand(name: string, fn: RedisFunction, resp: RespVersions) { - const prefix = functionArgumentsPrefix(name, fn), - transformReply = getTransformReply(fn, resp); + const prefix = functionArgumentsPrefix(name, fn); + const transformReply = getTransformReply(fn, resp); + return async function (this: T, ...args: Array) { - const fnArgs = fn.transformArguments(...args), - redisArgs = prefix.concat(fnArgs), - reply = await this._self._self.sendCommand( + if (fn.parseCommand) { + const parser = new BasicCommandParser(resp); + parser.pushVariadic(prefix); + fn.parseCommand(parser, ...args); + + return this._self._execute( fn.IS_READ_ONLY, - redisArgs, - this._self._self.commandOptions + client => client.executeCommand(parser, this._self.commandOptions, transformReply) ); + } else { + const fnArgs = fn.transformArguments(...args), + redisArgs = prefix.concat(fnArgs), + reply = await this._self._self.sendCommand( + fn.IS_READ_ONLY, + redisArgs, + this._self._self.commandOptions + ); - return transformReply ? - transformReply(reply, fnArgs.preserve) : - reply; + return transformReply ? + transformReply(reply, fnArgs.preserve) : + reply; + } } }; export function createModuleCommand(command: Command, resp: RespVersions) { const transformReply = getTransformReply(command, resp); + return async function (this: T, ...args: Array) { - const redisArgs = command.transformArguments(...args), - reply = await this._self._self.sendCommand( + if (command.parseCommand) { + const parser = new BasicCommandParser(resp); + command.parseCommand(parser, ...args); + + return this._self._execute( command.IS_READ_ONLY, - redisArgs, - this._self._self.commandOptions + client => client.executeCommand(parser, this._self.commandOptions, transformReply) ); + } else { + const redisArgs = command.transformArguments(...args), + reply = await this._self._self.sendCommand( + command.IS_READ_ONLY, + redisArgs, + this._self._self.commandOptions + ); - return transformReply ? - transformReply(reply, redisArgs.preserve) : - reply; + return transformReply ? + transformReply(reply, redisArgs.preserve) : + reply; + } } }; export function createScriptCommand(script: RedisScript, resp: RespVersions) { - const prefix = scriptArgumentsPrefix(script), - transformReply = getTransformReply(script, resp); + const prefix = scriptArgumentsPrefix(script); + const transformReply = getTransformReply(script, resp); + return async function (this: T, ...args: Array) { - const scriptArgs = script.transformArguments(...args), - redisArgs = prefix.concat(scriptArgs), - reply = await this._self.executeScript( - script, + if (script.parseCommand) { + const parser = new BasicCommandParser(resp); + parser.pushVariadic(prefix); + script.parseCommand(parser, ...args); + + return this._self._execute( script.IS_READ_ONLY, - redisArgs, - this._self.commandOptions + client => client.executeCommand(parser, this.commandOptions, transformReply) ); + } else { + const scriptArgs = script.transformArguments(...args), + redisArgs = prefix.concat(scriptArgs), + reply = await this._self.executeScript( + script, + script.IS_READ_ONLY, + redisArgs, + this._self.commandOptions + ); - return transformReply ? - transformReply(reply, scriptArgs.preserve) : - reply; + return transformReply ? + transformReply(reply, scriptArgs.preserve) : + reply; + } }; } diff --git a/packages/client/package.json b/packages/client/package.json index cb82f67bd53..3a5fb67902e 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -16,6 +16,7 @@ }, "devDependencies": { "@redis/test-utils": "*", + "@types/node": "^20.12.2", "@types/sinon": "^17.0.3", "sinon": "^17.0.1" },