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, RedisArgument } from '../RESP/types'; import { attachConfig, functionArgumentsPrefix, getTransformReply } from '../commander'; import { BasicCommandParser } from '../client/parser'; import { Tail } from '../commands/generic-transformers'; type CommandSignature< REPLIES extends Array, C extends Command, M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping > = (...args: Tail>) => RedisClusterMultiCommandType< [...REPLIES, ReplyWithTypeMapping, TYPE_MAPPING>], M, F, S, RESP, TYPE_MAPPING >; type WithCommands< REPLIES extends Array, M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping > = { [P in keyof typeof COMMANDS]: CommandSignature; }; type WithModules< REPLIES extends Array, M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping > = { [P in keyof M]: { [C in keyof M[P]]: CommandSignature; }; }; type WithFunctions< REPLIES extends Array, M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping > = { [L in keyof F]: { [C in keyof F[L]]: CommandSignature; }; }; type WithScripts< REPLIES extends Array, M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping > = { [P in keyof S]: CommandSignature; }; export type RedisClusterMultiCommandType< REPLIES extends Array, M extends RedisModules, F extends RedisFunctions, S extends RedisScripts, RESP extends RespVersions, TYPE_MAPPING extends TypeMapping > = ( RedisClusterMultiCommand & WithCommands & WithModules & WithFunctions & WithScripts ); export type ClusterMultiExecute = ( firstKey: RedisArgument | undefined, isReadonly: boolean | undefined, commands: Array ) => Promise>; export default class RedisClusterMultiCommand { static #createCommand(command: Command, resp: RespVersions) { const transformReply = getTransformReply(command, resp); return function (this: RedisClusterMultiCommand, ...args: Array) { const parser = new BasicCommandParser(); command.parseCommand(parser, ...args); const redisArgs: CommandArguments = parser.redisArgs; redisArgs.preserve = parser.preserve; const firstKey = parser.firstKey; return this.addCommand( firstKey, command.IS_READ_ONLY, redisArgs, transformReply ); }; } static #createModuleCommand(command: Command, resp: RespVersions) { const transformReply = getTransformReply(command, resp); return function (this: { _self: RedisClusterMultiCommand }, ...args: Array) { const parser = new BasicCommandParser(); command.parseCommand(parser, ...args); const redisArgs: CommandArguments = parser.redisArgs; redisArgs.preserve = parser.preserve; const firstKey = parser.firstKey; return this._self.addCommand( firstKey, command.IS_READ_ONLY, redisArgs, transformReply ); }; } static #createFunctionCommand(name: string, fn: RedisFunction, resp: RespVersions) { const prefix = functionArgumentsPrefix(name, fn); const transformReply = getTransformReply(fn, resp); return function (this: { _self: RedisClusterMultiCommand }, ...args: Array) { const parser = new BasicCommandParser(); parser.push(...prefix); fn.parseCommand(parser, ...args); const redisArgs: CommandArguments = parser.redisArgs; redisArgs.preserve = parser.preserve; const firstKey = parser.firstKey; return this._self.addCommand( firstKey, fn.IS_READ_ONLY, redisArgs, transformReply ); }; } static #createScriptCommand(script: RedisScript, resp: RespVersions) { const transformReply = getTransformReply(script, resp); return function (this: RedisClusterMultiCommand, ...args: Array) { const parser = new BasicCommandParser(); script.parseCommand(parser, ...args); const scriptArgs: CommandArguments = parser.redisArgs; scriptArgs.preserve = parser.preserve; const firstKey = parser.firstKey; return this.#addScript( firstKey, script.IS_READ_ONLY, script, scriptArgs, transformReply ); }; } static extend< M extends RedisModules = Record, F extends RedisFunctions = Record, S extends RedisScripts = Record, RESP extends RespVersions = 2 >(config?: CommanderConfig) { return attachConfig({ BaseClass: RedisClusterMultiCommand, commands: COMMANDS, createCommand: RedisClusterMultiCommand.#createCommand, createModuleCommand: RedisClusterMultiCommand.#createModuleCommand, createFunctionCommand: RedisClusterMultiCommand.#createFunctionCommand, createScriptCommand: RedisClusterMultiCommand.#createScriptCommand, config }); } readonly #multi: RedisMultiCommand readonly #executeMulti: ClusterMultiExecute; readonly #executePipeline: ClusterMultiExecute; #firstKey: RedisArgument | undefined; #isReadonly: boolean | undefined = true; constructor( executeMulti: ClusterMultiExecute, executePipeline: ClusterMultiExecute, routing: RedisArgument | undefined, typeMapping?: TypeMapping ) { this.#multi = new RedisMultiCommand(typeMapping); this.#executeMulti = executeMulti; this.#executePipeline = executePipeline; this.#firstKey = routing; } #setState( firstKey: RedisArgument | undefined, isReadonly: boolean | undefined, ) { this.#firstKey ??= firstKey; this.#isReadonly &&= isReadonly; } addCommand( firstKey: RedisArgument | undefined, isReadonly: boolean | undefined, args: CommandArguments, transformReply?: TransformReply ) { this.#setState(firstKey, isReadonly); this.#multi.addCommand(args, transformReply); return this; } #addScript( firstKey: RedisArgument | undefined, isReadonly: boolean | undefined, script: RedisScript, args: CommandArguments, transformReply?: TransformReply ) { this.#setState(firstKey, isReadonly); this.#multi.addScript(script, args, transformReply); return this; } async exec(execAsPipeline = false) { if (execAsPipeline) return this.execAsPipeline(); return this.#multi.transformReplies( await this.#executeMulti( this.#firstKey, this.#isReadonly, this.#multi.queue ) ) as MultiReplyType; } EXEC = this.exec; execTyped(execAsPipeline = false) { return this.exec(execAsPipeline); } async execAsPipeline() { if (this.#multi.queue.length === 0) return [] as MultiReplyType; return this.#multi.transformReplies( await this.#executePipeline( this.#firstKey, this.#isReadonly, this.#multi.queue ) ) as MultiReplyType; } execAsPipelineTyped() { return this.execAsPipeline(); } }