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 RedisCluster from '.'; 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: Parameters) => 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 redisArgs = command.transformArguments(...args), firstKey = RedisCluster.extractFirstKey( command, args, redisArgs ); 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 redisArgs = command.transformArguments(...args), firstKey = RedisCluster.extractFirstKey( command, args, redisArgs ); return this._self.addCommand( firstKey, command.IS_READ_ONLY, redisArgs, transformReply ); }; } static #createFunctionCommand(name: string, fn: RedisFunction, resp: RespVersions) { const prefix = functionArgumentsPrefix(name, fn), transformReply = getTransformReply(fn, resp); return function (this: { _self: RedisClusterMultiCommand }, ...args: Array) { const fnArgs = fn.transformArguments(...args), redisArgs: CommandArguments = prefix.concat(fnArgs), firstKey = RedisCluster.extractFirstKey( fn, args, fnArgs ); redisArgs.preserve = fnArgs.preserve; 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 scriptArgs = script.transformArguments(...args); this.#setState( RedisCluster.extractFirstKey( script, args, scriptArgs ), script.IS_READ_ONLY ); this.#multi.addScript( script, scriptArgs, transformReply ); return this; }; } 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 = new RedisMultiCommand(); readonly #executeMulti: ClusterMultiExecute; readonly #executePipeline: ClusterMultiExecute; #firstKey: RedisArgument | undefined; #isReadonly: boolean | undefined = true; constructor( executeMulti: ClusterMultiExecute, executePipeline: ClusterMultiExecute, routing: RedisArgument | undefined ) { 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; } 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(); } }