import COMMANDS from '../commands'; import RedisMultiCommand, { MULTI_REPLY, MultiReply, MultiReplyType } from '../multi-command'; import { ReplyWithTypeMapping, CommandReply, Command, CommandArguments, CommanderConfig, RedisFunctions, RedisModules, RedisScripts, RespVersions, TransformReply, RedisScript, RedisFunction, TypeMapping, ReplyUnion, RedisArgument } from '../RESP/types'; import { attachConfig, functionArgumentsPrefix, getTransformReply } from '../commander'; import RedisCluster, { RedisClusterType } 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 default class RedisClusterMultiCommand { private 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 ); }; } private 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 ); }; } private 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 ); }; } private 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 }); } private readonly _multi = new RedisMultiCommand(); private readonly _cluster: RedisClusterType; private _firstKey: RedisArgument | undefined; private _isReadonly: boolean | undefined = true; constructor(cluster: RedisClusterType, routing: RedisArgument | undefined) { this._cluster = cluster; this._firstKey = routing; } private _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._cluster.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._cluster.executePipeline( this._firstKey, this._isReadonly, this._multi.queue ) ) as MultiReplyType; } execAsPipelineTyped() { return this.execAsPipeline(); } }