1
0
mirror of https://github.com/redis/node-redis.git synced 2025-08-07 13:22:56 +03:00

cluster multi

This commit is contained in:
Leibale
2023-05-30 19:23:14 +03:00
parent 2fa33dbbac
commit fcb3a011b5
6 changed files with 352 additions and 230 deletions

View File

@@ -71,6 +71,13 @@ legacyClient.set('key', 'value', (err, reply) => {
TODO TODO
The `isolationPool` has been moved to it's on class `ClientPool`. You can create pool from a client using `client.createPool()`. The `isolationPool` has been moved to it's on class `ClientPool`. You can create pool from a client using `client.createPool()`.
## Cluster MULTI
Cluster MULTI supports readonly/replicas
`cluster.multi.addCommand` now requires `isReadonly` as the second argument, to match `cluster.sendCommand`
TODO
## Commands ## Commands
Some command arguments/replies have changed to align more closely to data types returned by Redis: Some command arguments/replies have changed to align more closely to data types returned by Redis:

View File

@@ -3,7 +3,7 @@ import RedisSocket, { RedisSocketOptions, RedisTlsSocketOptions } from './socket
import RedisCommandsQueue, { QueueCommandOptions } from './commands-queue'; import RedisCommandsQueue, { QueueCommandOptions } from './commands-queue';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { attachConfig, functionArgumentsPrefix, getTransformReply, scriptArgumentsPrefix } from '../commander'; import { attachConfig, functionArgumentsPrefix, getTransformReply, scriptArgumentsPrefix } from '../commander';
import { ClientClosedError, ClientOfflineError, DisconnectsClientError } from '../errors'; import { ClientClosedError, ClientOfflineError, DisconnectsClientError, WatchError } from '../errors';
import { URL } from 'url'; import { URL } from 'url';
import { TcpSocketConnectOpts } from 'net'; import { TcpSocketConnectOpts } from 'net';
import { PubSubType, PubSubListener, PubSubTypeListeners, ChannelListeners } from './pub-sub'; import { PubSubType, PubSubListener, PubSubTypeListeners, ChannelListeners } from './pub-sub';
@@ -692,7 +692,7 @@ export default class RedisClient<
/** /**
* @internal * @internal
*/ */
async executePipeline(commands: Array<RedisMultiQueuedCommand>) { executePipeline(commands: Array<RedisMultiQueuedCommand>) {
if (!this._socket.isOpen) { if (!this._socket.isOpen) {
return Promise.reject(new ClientClosedError()); return Promise.reject(new ClientClosedError());
} }

View File

@@ -1,5 +1,5 @@
import COMMANDS from '../commands'; import COMMANDS from '../commands';
import RedisMultiCommand, { RedisMultiQueuedCommand } from '../multi-command'; import RedisMultiCommand, { MULTI_REPLY, MultiReply, MultiReplyType } from '../multi-command';
import { ReplyWithFlags, CommandReply, Command, CommandArguments, CommanderConfig, RedisFunctions, RedisModules, RedisScripts, RespVersions, TransformReply, RedisScript, RedisFunction, Flags, ReplyUnion } from '../RESP/types'; import { ReplyWithFlags, CommandReply, Command, CommandArguments, CommanderConfig, RedisFunctions, RedisModules, RedisScripts, RespVersions, TransformReply, RedisScript, RedisFunction, Flags, ReplyUnion } from '../RESP/types';
import { attachConfig, functionArgumentsPrefix, getTransformReply } from '../commander'; import { attachConfig, functionArgumentsPrefix, getTransformReply } from '../commander';
import { RedisClientType } from '.'; import { RedisClientType } from '.';
@@ -84,64 +84,50 @@ export type RedisClientMultiCommandType<
WithScripts<REPLIES, M, F, S, RESP, FLAGS> WithScripts<REPLIES, M, F, S, RESP, FLAGS>
); );
type MULTI_REPLY = { export default class RedisClientMultiCommand<REPLIES = []> {
GENERIC: 'generic'; private static _createCommand(command: Command, resp: RespVersions) {
TYPED: 'typed';
};
type MultiReply = MULTI_REPLY[keyof MULTI_REPLY];
type ReplyType<T extends MultiReply, REPLIES> = T extends MULTI_REPLY['TYPED'] ? REPLIES : Array<unknown>;
export type RedisClientMultiExecutor = (
queue: Array<RedisMultiQueuedCommand>,
selectedDB?: number,
chainId?: symbol
) => Promise<Array<unknown>>;
export default class RedisClientMultiCommand<REPLIES = []> extends RedisMultiCommand {
static #createCommand(command: Command, resp: RespVersions) {
const transformReply = getTransformReply(command, resp); const transformReply = getTransformReply(command, resp);
return function (this: RedisClientMultiCommand) { return function (this: RedisClientMultiCommand, ...args: Array<unknown>) {
return this.addCommand( return this._multi.addCommand(
command.transformArguments.apply(undefined, arguments as any), command.transformArguments(...args),
transformReply transformReply
); );
}; };
} }
static #createModuleCommand(command: Command, resp: RespVersions) { private static _createModuleCommand(command: Command, resp: RespVersions) {
const transformReply = getTransformReply(command, resp); const transformReply = getTransformReply(command, resp);
return function (this: { self: RedisClientMultiCommand }) { return function (this: { self: RedisClientMultiCommand }, ...args: Array<unknown>) {
return this.self.addCommand( return this.self._multi.addCommand(
command.transformArguments.apply(undefined, arguments as any), command.transformArguments(...args),
transformReply transformReply
); );
}; };
} }
static #createFunctionCommand(name: string, fn: RedisFunction, resp: RespVersions) { private static _createFunctionCommand(name: string, fn: RedisFunction, resp: RespVersions) {
const prefix = functionArgumentsPrefix(name, fn), const prefix = functionArgumentsPrefix(name, fn),
transformReply = getTransformReply(fn, resp); transformReply = getTransformReply(fn, resp);
return function (this: { self: RedisClientMultiCommand }) { return function (this: { self: RedisClientMultiCommand }, ...args: Array<unknown>) {
const fnArgs = fn.transformArguments.apply(undefined, arguments as any), const fnArgs = fn.transformArguments(...args),
args: CommandArguments = prefix.concat(fnArgs); redisArgs: CommandArguments = prefix.concat(fnArgs);
args.preserve = fnArgs.preserve; redisArgs.preserve = fnArgs.preserve;
return this.self.addCommand( return this.self._multi.addCommand(
args, redisArgs,
transformReply transformReply
); );
}; };
} }
static #createScriptCommand(script: RedisScript, resp: RespVersions) { private static _createScriptCommand(script: RedisScript, resp: RespVersions) {
const transformReply = getTransformReply(script, resp); const transformReply = getTransformReply(script, resp);
return function (this: RedisClientMultiCommand) { return function (this: RedisClientMultiCommand, ...args: Array<unknown>) {
return this.addScript( this._multi.addScript(
script, script,
script.transformArguments.apply(undefined, arguments as any), script.transformArguments(...args),
transformReply transformReply
); );
return this;
}; };
} }
@@ -154,35 +140,36 @@ export default class RedisClientMultiCommand<REPLIES = []> extends RedisMultiCom
return attachConfig({ return attachConfig({
BaseClass: RedisClientMultiCommand, BaseClass: RedisClientMultiCommand,
commands: COMMANDS, commands: COMMANDS,
createCommand: RedisClientMultiCommand.#createCommand, createCommand: RedisClientMultiCommand._createCommand,
createModuleCommand: RedisClientMultiCommand.#createModuleCommand, createModuleCommand: RedisClientMultiCommand._createModuleCommand,
createFunctionCommand: RedisClientMultiCommand.#createFunctionCommand, createFunctionCommand: RedisClientMultiCommand._createFunctionCommand,
createScriptCommand: RedisClientMultiCommand.#createScriptCommand, createScriptCommand: RedisClientMultiCommand._createScriptCommand,
config config
}); });
} }
readonly #client: RedisClientType; private readonly _multi = new RedisMultiCommand();
#selectedDB?: number; private readonly _client: RedisClientType;
private _selectedDB?: number;
constructor(client: RedisClientType) { constructor(client: RedisClientType) {
super(); this._client = client;
this.#client = client;
} }
SELECT(db: number, transformReply?: TransformReply): this { SELECT(db: number, transformReply?: TransformReply): this {
this.#selectedDB = db; this._selectedDB = db;
return this.addCommand(['SELECT', db.toString()], transformReply); this._multi.addCommand(['SELECT', db.toString()], transformReply);
return this;
} }
select = this.SELECT; select = this.SELECT;
async exec<T extends MultiReply = MULTI_REPLY['GENERIC']>(execAsPipeline = false): Promise<ReplyType<T, REPLIES>> { async exec<T extends MultiReply = MULTI_REPLY['GENERIC']>(execAsPipeline = false): Promise<MultiReplyType<T, REPLIES>> {
if (execAsPipeline) return this.execAsPipeline<T>(); if (execAsPipeline) return this.execAsPipeline<T>();
return this.transformReplies( return this._multi.transformReplies(
await this.#client.executeMulti(this.queue, this.#selectedDB) await this._client.executeMulti(this._multi.queue, this._selectedDB)
) as ReplyType<T, REPLIES>; ) as MultiReplyType<T, REPLIES>;
} }
EXEC = this.exec; EXEC = this.exec;
@@ -191,12 +178,12 @@ export default class RedisClientMultiCommand<REPLIES = []> extends RedisMultiCom
return this.exec<MULTI_REPLY['TYPED']>(execAsPipeline); return this.exec<MULTI_REPLY['TYPED']>(execAsPipeline);
} }
async execAsPipeline<T extends MultiReply = MULTI_REPLY['GENERIC']>(): Promise<ReplyType<T, REPLIES>> { async execAsPipeline<T extends MultiReply = MULTI_REPLY['GENERIC']>(): Promise<MultiReplyType<T, REPLIES>> {
if (this.queue.length === 0) return [] as ReplyType<T, REPLIES>; if (this._multi.queue.length === 0) return [] as MultiReplyType<T, REPLIES>;
return this.transformReplies( return this._multi.transformReplies(
await this.#client.executePipeline(this.queue) await this._client.executePipeline(this._multi.queue)
) as ReplyType<T, REPLIES>; ) as MultiReplyType<T, REPLIES>;
} }
execAsPipelineTyped() { execAsPipelineTyped() {

View File

@@ -1,11 +1,11 @@
import { ClientCommandOptions, RedisClientOptions, RedisClientType } from '../client'; import { ClientCommandOptions, RedisClientOptions } from '../client';
import { Command, CommandArguments, CommanderConfig, CommandPolicies, CommandSignature, CommandWithPoliciesSignature, Flags, RedisArgument, RedisFunction, RedisFunctions, RedisModules, RedisScript, RedisScripts, ReplyUnion, RespVersions, TransformReply } from '../RESP/types'; import { Command, CommandArguments, CommanderConfig, CommandPolicies, CommandWithPoliciesSignature, Flags, RedisArgument, RedisFunction, RedisFunctions, RedisModules, RedisScript, RedisScripts, ReplyUnion, RespVersions } from '../RESP/types';
import COMMANDS from '../commands'; import COMMANDS from '../commands';
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import { attachConfig, functionArgumentsPrefix, getTransformReply, scriptArgumentsPrefix } from '../commander'; import { attachConfig, functionArgumentsPrefix, getTransformReply, scriptArgumentsPrefix } from '../commander';
import RedisClusterSlots, { NodeAddressMap, ShardNode } from './cluster-slots'; import RedisClusterSlots, { NodeAddressMap, ShardNode } from './cluster-slots';
// import RedisClusterMultiCommand, { InstantiableRedisClusterMultiCommandType, RedisClusterMultiCommandType } from './multi-command'; import RedisClusterMultiCommand, { RedisClusterMultiCommandType } from './multi-command';
// import { RedisMultiQueuedCommand } from '../multi-command'; import { RedisMultiQueuedCommand } from '../multi-command';
import { PubSubListener } from '../client/pub-sub'; import { PubSubListener } from '../client/pub-sub';
import { ErrorReply } from '../errors'; import { ErrorReply } from '../errors';
@@ -85,7 +85,7 @@ export default class RedisCluster<
FLAGS extends Flags, FLAGS extends Flags,
POLICIES extends CommandPolicies POLICIES extends CommandPolicies
> extends EventEmitter { > extends EventEmitter {
private static _extractFirstKey<C extends Command>( static extractFirstKey<C extends Command>(
command: C, command: C,
args: Parameters<C['transformArguments']>, args: Parameters<C['transformArguments']>,
redisArgs: Array<RedisArgument> redisArgs: Array<RedisArgument>
@@ -101,46 +101,46 @@ export default class RedisCluster<
private static _createCommand(command: Command, resp: RespVersions) { private static _createCommand(command: Command, resp: RespVersions) {
const transformReply = getTransformReply(command, resp); const transformReply = getTransformReply(command, resp);
return async function (this: ProxyCluster) { return async function (this: ProxyCluster, ...args: Array<unknown>) {
const args = command.transformArguments.apply(undefined, arguments as any), const redisArgs = command.transformArguments(...args),
firstKey = RedisCluster._extractFirstKey( firstKey = RedisCluster.extractFirstKey(
command, command,
arguments as any, args,
args redisArgs
), ),
reply = await this.sendCommand( reply = await this.sendCommand(
firstKey, firstKey,
command.IS_READ_ONLY, command.IS_READ_ONLY,
args, redisArgs,
this.commandOptions, this.commandOptions,
command.POLICIES command.POLICIES
); );
return transformReply ? return transformReply ?
transformReply(reply, args.preserve) : transformReply(reply, redisArgs.preserve) :
reply; reply;
}; };
} }
private static _createModuleCommand(command: Command, resp: RespVersions) { private static _createModuleCommand(command: Command, resp: RespVersions) {
const transformReply = getTransformReply(command, resp); const transformReply = getTransformReply(command, resp);
return async function (this: NamespaceProxyCluster) { return async function (this: NamespaceProxyCluster, ...args: Array<unknown>) {
const args = command.transformArguments.apply(undefined, arguments as any), const redisArgs = command.transformArguments(...args),
firstKey = RedisCluster._extractFirstKey( firstKey = RedisCluster.extractFirstKey(
command, command,
arguments as any, args,
args redisArgs
), ),
reply = await this.self.sendCommand( reply = await this.self.sendCommand(
firstKey, firstKey,
command.IS_READ_ONLY, command.IS_READ_ONLY,
args, redisArgs,
this.self.commandOptions, this.self.commandOptions,
command.POLICIES command.POLICIES
); );
return transformReply ? return transformReply ?
transformReply(reply, args.preserve) : transformReply(reply, redisArgs.preserve) :
reply; reply;
}; };
} }
@@ -148,18 +148,18 @@ export default class RedisCluster<
private static _createFunctionCommand(name: string, fn: RedisFunction, resp: RespVersions) { private static _createFunctionCommand(name: string, fn: RedisFunction, resp: RespVersions) {
const prefix = functionArgumentsPrefix(name, fn), const prefix = functionArgumentsPrefix(name, fn),
transformReply = getTransformReply(fn, resp); transformReply = getTransformReply(fn, resp);
return async function (this: NamespaceProxyCluster) { return async function (this: NamespaceProxyCluster, ...args: Array<unknown>) {
const fnArgs = fn.transformArguments.apply(undefined, arguments as any), const fnArgs = fn.transformArguments(...args),
args = prefix.concat(fnArgs), redisArgs = prefix.concat(fnArgs),
firstKey = RedisCluster._extractFirstKey( firstKey = RedisCluster.extractFirstKey(
fn, fn,
arguments as any, fnArgs,
args redisArgs
), ),
reply = await this.self.sendCommand( reply = await this.self.sendCommand(
firstKey, firstKey,
fn.IS_READ_ONLY, fn.IS_READ_ONLY,
args, redisArgs,
this.self.commandOptions, this.self.commandOptions,
fn.POLICIES fn.POLICIES
); );
@@ -173,18 +173,18 @@ export default class RedisCluster<
private static _createScriptCommand(script: RedisScript, resp: RespVersions) { private static _createScriptCommand(script: RedisScript, resp: RespVersions) {
const prefix = scriptArgumentsPrefix(script), const prefix = scriptArgumentsPrefix(script),
transformReply = getTransformReply(script, resp); transformReply = getTransformReply(script, resp);
return async function (this: ProxyCluster) { return async function (this: ProxyCluster, ...args: Array<unknown>) {
const scriptArgs = script.transformArguments.apply(undefined, arguments as any), const scriptArgs = script.transformArguments(...args),
args = prefix.concat(scriptArgs), redisArgs = prefix.concat(scriptArgs),
firstKey = RedisCluster._extractFirstKey( firstKey = RedisCluster.extractFirstKey(
script, script,
arguments as any, scriptArgs,
args redisArgs
), ),
reply = await this.sendCommand( reply = await this.sendCommand(
firstKey, firstKey,
script.IS_READ_ONLY, script.IS_READ_ONLY,
args, redisArgs,
this.commandOptions, this.commandOptions,
script.POLICIES script.POLICIES
); );
@@ -211,7 +211,7 @@ export default class RedisCluster<
config config
}); });
// Client.prototype.Multi = RedisClientMultiCommand.extend(config); Cluster.prototype.Multi = RedisClusterMultiCommand.extend(config);
return (options?: Omit<RedisClusterOptions, keyof Exclude<typeof config, undefined>>) => { return (options?: Omit<RedisClusterOptions, keyof Exclude<typeof config, undefined>>) => {
// returning a proxy of the client to prevent the namespaces.self to leak between proxies // returning a proxy of the client to prevent the namespaces.self to leak between proxies
@@ -280,8 +280,6 @@ export default class RedisCluster<
return this._slots.pubSubNode; return this._slots.pubSubNode;
} }
// readonly #Multi: InstantiableRedisClusterMultiCommandType<M, F, S>;
get isOpen() { get isOpen() {
return this._slots.isOpen; return this._slots.isOpen;
} }
@@ -291,7 +289,6 @@ export default class RedisCluster<
this._options = options; this._options = options;
this._slots = new RedisClusterSlots(options, this.emit.bind(this)); this._slots = new RedisClusterSlots(options, this.emit.bind(this));
// this.#Multi = RedisClusterMultiCommand.extend(options);
} }
duplicate(overrides?: Partial<RedisClusterOptions<M, F, S>>): RedisClusterType<M, F, S> { duplicate(overrides?: Partial<RedisClusterOptions<M, F, S>>): RedisClusterType<M, F, S> {
@@ -400,20 +397,38 @@ export default class RedisCluster<
} }
} }
// MULTI(routing?: RedisCommandArgument): RedisClusterMultiCommandType<M, F, S> { /**
// return new this.#Multi( * @internal
// (commands: Array<RedisMultiQueuedCommand>, firstKey?: RedisCommandArgument, chainId?: symbol) => { */
// return this.#execute( async executePipeline(
// firstKey, firstKey: RedisArgument | undefined,
// false, isReadonly: boolean | undefined,
// client => client.multiExecutor(commands, undefined, chainId) commands: Array<RedisMultiQueuedCommand>
// ); ) {
// }, const client = await this._slots.getClient(firstKey, isReadonly);
// routing return client.executePipeline(commands);
// ); }
// }
// multi = this.MULTI; /**
* @internal
*/
async executeMulti(
firstKey: RedisArgument | undefined,
isReadonly: boolean | undefined,
commands: Array<RedisMultiQueuedCommand>
) {
const client = await this._slots.getClient(firstKey, isReadonly);
return client.executeMulti(commands);
}
MULTI(routing?: RedisArgument): RedisClusterMultiCommandType<[], M, F, S, RESP, FLAGS> {
return new (this as any).Multi(
this,
routing
);
}
multi = this.MULTI;
async SUBSCRIBE<T extends boolean = false>( async SUBSCRIBE<T extends boolean = false>(
channels: string | Array<string>, channels: string | Array<string>,

View File

@@ -1,141 +1,245 @@
// import COMMANDS from './commands'; import COMMANDS from '../commands';
// import { RedisCommand, RedisCommandArgument, RedisCommandArguments, RedisCommandRawReply, RedisFunctions, RedisModules, RedisExtensions, RedisScript, RedisScripts, ExcludeMappedString, RedisFunction } from '../commands'; import RedisMultiCommand, { MULTI_REPLY, MultiReply, MultiReplyType } from '../multi-command';
// import RedisMultiCommand, { RedisMultiQueuedCommand } from '../multi-command'; import { ReplyWithFlags, CommandReply, Command, CommandArguments, CommanderConfig, RedisFunctions, RedisModules, RedisScripts, RespVersions, TransformReply, RedisScript, RedisFunction, Flags, ReplyUnion, RedisArgument } from '../RESP/types';
// import { attachCommands, attachExtensions } from '../commander'; import { attachConfig, functionArgumentsPrefix, getTransformReply } from '../commander';
// import RedisCluster from '.'; import RedisCluster, { RedisClusterType } from '.';
// type RedisClusterMultiCommandSignature< type CommandSignature<
// C extends RedisCommand, REPLIES extends Array<unknown>,
// M extends RedisModules, C extends Command,
// F extends RedisFunctions, M extends RedisModules,
// S extends RedisScripts F extends RedisFunctions,
// > = (...args: Parameters<C['transformArguments']>) => RedisClusterMultiCommandType<M, F, S>; S extends RedisScripts,
RESP extends RespVersions,
FLAGS extends Flags
> = (...args: Parameters<C['transformArguments']>) => RedisClusterMultiCommandType<
[...REPLIES, ReplyWithFlags<CommandReply<C, RESP>, FLAGS>],
M,
F,
S,
RESP,
FLAGS
>;
// type WithCommands< type WithCommands<
// M extends RedisModules, REPLIES extends Array<unknown>,
// F extends RedisFunctions, M extends RedisModules,
// S extends RedisScripts F extends RedisFunctions,
// > = { S extends RedisScripts,
// [P in keyof typeof COMMANDS]: RedisClusterMultiCommandSignature<(typeof COMMANDS)[P], M, F, S>; RESP extends RespVersions,
// }; FLAGS extends Flags
> = {
[P in keyof typeof COMMANDS]: CommandSignature<REPLIES, (typeof COMMANDS)[P], M, F, S, RESP, FLAGS>;
};
// type WithModules< type WithModules<
// M extends RedisModules, REPLIES extends Array<unknown>,
// F extends RedisFunctions, M extends RedisModules,
// S extends RedisScripts F extends RedisFunctions,
// > = { S extends RedisScripts,
// [P in keyof M as ExcludeMappedString<P>]: { RESP extends RespVersions,
// [C in keyof M[P] as ExcludeMappedString<C>]: RedisClusterMultiCommandSignature<M[P][C], M, F, S>; FLAGS extends Flags
// }; > = {
// }; [P in keyof M]: {
[C in keyof M[P]]: CommandSignature<REPLIES, M[P][C], M, F, S, RESP, FLAGS>;
};
};
// type WithFunctions< type WithFunctions<
// M extends RedisModules, REPLIES extends Array<unknown>,
// F extends RedisFunctions, M extends RedisModules,
// S extends RedisScripts F extends RedisFunctions,
// > = { S extends RedisScripts,
// [P in keyof F as ExcludeMappedString<P>]: { RESP extends RespVersions,
// [FF in keyof F[P] as ExcludeMappedString<FF>]: RedisClusterMultiCommandSignature<F[P][FF], M, F, S>; FLAGS extends Flags
// }; > = {
// }; [L in keyof F]: {
[C in keyof F[L]]: CommandSignature<REPLIES, F[L][C], M, F, S, RESP, FLAGS>;
};
};
// type WithScripts< type WithScripts<
// M extends RedisModules, REPLIES extends Array<unknown>,
// F extends RedisFunctions, M extends RedisModules,
// S extends RedisScripts F extends RedisFunctions,
// > = { S extends RedisScripts,
// [P in keyof S as ExcludeMappedString<P>]: RedisClusterMultiCommandSignature<S[P], M, F, S>; RESP extends RespVersions,
// }; FLAGS extends Flags
> = {
[P in keyof S]: CommandSignature<REPLIES, S[P], M, F, S, RESP, FLAGS>;
};
// export type RedisClusterMultiCommandType< export type RedisClusterMultiCommandType<
// M extends RedisModules, REPLIES extends Array<any>,
// F extends RedisFunctions, M extends RedisModules,
// S extends RedisScripts F extends RedisFunctions,
// > = RedisClusterMultiCommand & WithCommands<M, F, S> & WithModules<M, F, S> & WithFunctions<M, F, S> & WithScripts<M, F, S>; S extends RedisScripts,
RESP extends RespVersions,
FLAGS extends Flags
> = (
RedisClusterMultiCommand<REPLIES> &
WithCommands<REPLIES, M, F, S, RESP, FLAGS> &
WithModules<REPLIES, M, F, S, RESP, FLAGS> &
WithFunctions<REPLIES, M, F, S, RESP, FLAGS> &
WithScripts<REPLIES, M, F, S, RESP, FLAGS>
);
// export type InstantiableRedisClusterMultiCommandType< export default class RedisClusterMultiCommand<REPLIES = []> {
// M extends RedisModules, private static _createCommand(command: Command, resp: RespVersions) {
// F extends RedisFunctions, const transformReply = getTransformReply(command, resp);
// S extends RedisScripts return function (this: RedisClusterMultiCommand, ...args: Array<unknown>) {
// > = new (...args: ConstructorParameters<typeof RedisClusterMultiCommand>) => RedisClusterMultiCommandType<M, F, S>; const redisArgs = command.transformArguments(...args),
firstKey = RedisCluster.extractFirstKey(
command,
args,
redisArgs
);
return this.addCommand(
firstKey,
command.IS_READ_ONLY,
redisArgs,
transformReply
);
};
}
// export type RedisClusterMultiExecutor = (queue: Array<RedisMultiQueuedCommand>, firstKey?: RedisCommandArgument, chainId?: symbol) => Promise<Array<RedisCommandRawReply>>; private static _createModuleCommand(command: Command, resp: RespVersions) {
const transformReply = getTransformReply(command, resp);
return function (this: { self: RedisClusterMultiCommand }, ...args: Array<unknown>) {
const redisArgs = command.transformArguments(...args),
firstKey = RedisCluster.extractFirstKey(
command,
args,
redisArgs
);
return this.self.addCommand(
firstKey,
command.IS_READ_ONLY,
redisArgs,
transformReply
);
};
}
// export default class RedisClusterMultiCommand { private static _createFunctionCommand(name: string, fn: RedisFunction, resp: RespVersions) {
// readonly #multi = new RedisMultiCommand(); const prefix = functionArgumentsPrefix(name, fn),
// readonly #executor: RedisClusterMultiExecutor; transformReply = getTransformReply(fn, resp);
// #firstKey: RedisCommandArgument | undefined; return function (this: { self: RedisClusterMultiCommand }, ...args: Array<unknown>) {
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 extend< private static _createScriptCommand(script: RedisScript, resp: RespVersions) {
// M extends RedisModules, const transformReply = getTransformReply(script, resp);
// F extends RedisFunctions, return function (this: RedisClusterMultiCommand, ...args: Array<unknown>) {
// S extends RedisScripts const scriptArgs = script.transformArguments(...args);
// >(extensions?: RedisExtensions<M, F, S>): InstantiableRedisClusterMultiCommandType<M, F, S> { this._setState(
// return attachExtensions({ RedisCluster.extractFirstKey(
// BaseClass: RedisClusterMultiCommand, script,
// modulesExecutor: RedisClusterMultiCommand.prototype.commandsExecutor, args,
// modules: extensions?.modules, scriptArgs
// functionsExecutor: RedisClusterMultiCommand.prototype.functionsExecutor, ),
// functions: extensions?.functions, script.IS_READ_ONLY
// scriptsExecutor: RedisClusterMultiCommand.prototype.scriptsExecutor, );
// scripts: extensions?.scripts this._multi.addScript(
// }); script,
// } scriptArgs,
transformReply
);
return this;
};
}
// constructor(executor: RedisClusterMultiExecutor, firstKey?: RedisCommandArgument) { static extend<
// this.#executor = executor; M extends RedisModules = Record<string, never>,
// this.#firstKey = firstKey; F extends RedisFunctions = Record<string, never>,
// } S extends RedisScripts = Record<string, never>,
RESP extends RespVersions = 2
>(config?: CommanderConfig<M, F, S, RESP>) {
return attachConfig({
BaseClass: RedisClusterMultiCommand,
commands: COMMANDS,
createCommand: RedisClusterMultiCommand._createCommand,
createModuleCommand: RedisClusterMultiCommand._createModuleCommand,
createFunctionCommand: RedisClusterMultiCommand._createFunctionCommand,
createScriptCommand: RedisClusterMultiCommand._createScriptCommand,
config
});
}
// commandsExecutor(command: RedisCommand, args: Array<unknown>): this { private readonly _multi = new RedisMultiCommand();
// const transformedArguments = command.transformArguments(...args); private readonly _cluster: RedisClusterType;
// this.#firstKey ??= RedisCluster.extractFirstKey(command, args, transformedArguments); private _firstKey: RedisArgument | undefined;
// return this.addCommand(undefined, transformedArguments, command.transformReply); private _isReadonly: boolean | undefined = true;
// }
// addCommand( constructor(cluster: RedisClusterType, routing: RedisArgument | undefined) {
// firstKey: RedisCommandArgument | undefined, this._cluster = cluster;
// args: RedisCommandArguments, this._firstKey = routing;
// transformReply?: RedisCommand['transformReply'] }
// ): this {
// this.#firstKey ??= firstKey;
// this.#multi.addCommand(args, transformReply);
// return this;
// }
// functionsExecutor(fn: RedisFunction, args: Array<unknown>, name: string): this { private _setState(
// const transformedArguments = this.#multi.addFunction(name, fn, args); firstKey: RedisArgument | undefined,
// this.#firstKey ??= RedisCluster.extractFirstKey(fn, args, transformedArguments); isReadonly: boolean | undefined,
// return this; ) {
// } this._firstKey ??= firstKey;
this._isReadonly &&= isReadonly;
}
// scriptsExecutor(script: RedisScript, args: Array<unknown>): this { addCommand(
// const transformedArguments = this.#multi.addScript(script, args); firstKey: RedisArgument | undefined,
// this.#firstKey ??= RedisCluster.extractFirstKey(script, args, transformedArguments); isReadonly: boolean | undefined,
// return this; args: CommandArguments,
// } transformReply?: TransformReply
) {
this._setState(firstKey, isReadonly);
this._multi.addCommand(args, transformReply);
return this;
}
// async exec(execAsPipeline = false): Promise<Array<RedisCommandRawReply>> { async exec<T extends MultiReply = MULTI_REPLY['GENERIC']>(execAsPipeline = false) {
// if (execAsPipeline) { if (execAsPipeline) return this.execAsPipeline<T>();
// return this.execAsPipeline();
// }
// return this.#multi.handleExecReplies( return this._multi.transformReplies(
// await this.#executor(this.#multi.queue, this.#firstKey, RedisMultiCommand.generateChainId()) await this._cluster.executeMulti(
// ); this._firstKey,
// } this._isReadonly,
this._multi.queue
)
) as MultiReplyType<T, REPLIES>;
}
// EXEC = this.exec; EXEC = this.exec;
// async execAsPipeline(): Promise<Array<RedisCommandRawReply>> { execTyped(execAsPipeline = false) {
// return this.#multi.transformReplies( return this.exec<MULTI_REPLY['TYPED']>(execAsPipeline);
// await this.#executor(this.#multi.queue, this.#firstKey) }
// );
// }
// }
// attachCommands({ async execAsPipeline<T extends MultiReply = MULTI_REPLY['GENERIC']>() {
// BaseClass: RedisClusterMultiCommand, if (this._multi.queue.length === 0) return [] as MultiReplyType<T, REPLIES>;
// commands: COMMANDS,
// executor: RedisClusterMultiCommand.prototype.commandsExecutor return this._multi.transformReplies(
// }); await this._cluster.executePipeline(
this._firstKey,
this._isReadonly,
this._multi.queue
)
) as MultiReplyType<T, REPLIES>;
}
execAsPipelineTyped() {
return this.execAsPipeline<MULTI_REPLY['TYPED']>();
}
}

View File

@@ -1,5 +1,15 @@
import { CommandArguments, RedisScript, TransformReply } from './RESP/types'; import { CommandArguments, RedisScript, TransformReply } from './RESP/types';
// TODO: enum?
export type MULTI_REPLY = {
GENERIC: 'generic';
TYPED: 'typed';
};
export type MultiReply = MULTI_REPLY[keyof MULTI_REPLY];
export type MultiReplyType<T extends MultiReply, REPLIES> = T extends MULTI_REPLY['TYPED'] ? REPLIES : Array<unknown>;
export interface RedisMultiQueuedCommand { export interface RedisMultiQueuedCommand {
args: CommandArguments; args: CommandArguments;
transformReply?: TransformReply; transformReply?: TransformReply;
@@ -15,7 +25,6 @@ export default class RedisMultiCommand {
args, args,
transformReply transformReply
}); });
return this;
} }
addScript(script: RedisScript, args: CommandArguments, transformReply?: TransformReply) { addScript(script: RedisScript, args: CommandArguments, transformReply?: TransformReply) {
@@ -34,7 +43,7 @@ export default class RedisMultiCommand {
redisArgs.push(...args); redisArgs.push(...args);
redisArgs.preserve = args.preserve; redisArgs.preserve = args.preserve;
return this.addCommand(redisArgs, transformReply); this.addCommand(redisArgs, transformReply);
} }
transformReplies(rawReplies: Array<unknown>): Array<unknown> { transformReplies(rawReplies: Array<unknown>): Array<unknown> {