1
0
mirror of https://github.com/redis/node-redis.git synced 2025-08-04 15:02:09 +03:00

Add support for redis functions (#2020)

* fix #1906 - implement BITFIELD_RO

* initial support for redis functions

* fix test utils

* redis functions commands and tests

* upgrade deps

* fix "Property 'uninstall' does not exist on type 'SinonFakeTimers'"

* upgrade dockers version

* Merge branch 'master' of github.com:redis/node-redis into functions

* fix FUNCTION LIST WITHCODE and FUNCTION STATS

* upgrade deps

* set minimum version for FCALL and FCALL_RO

* fix FUNCTION LOAD

* FUNCTION LOAD

* fix FUNCTION LOAD & FUNCTION LIST & FUNCTION LOAD WITHCODE

* fix FUNCTION_LIST_WITHCODE test
This commit is contained in:
Leibale Eidelman
2022-04-25 09:09:23 -04:00
committed by GitHub
parent 23b65133c9
commit 11c6c24881
51 changed files with 1406 additions and 324 deletions

View File

@@ -1,7 +1,7 @@
import RedisClient, { InstantiableRedisClient, RedisClientType } from '../client';
import { RedisClusterMasterNode, RedisClusterReplicaNode } from '../commands/CLUSTER_NODES';
import { RedisClusterClientOptions, RedisClusterOptions } from '.';
import { RedisCommandArgument, RedisModules, RedisScripts } from '../commands';
import { RedisCommandArgument, RedisFunctions, RedisModules, RedisScripts } from '../commands';
import { RootNodesUnavailableError } from '../errors';
// We need to use 'require', because it's not possible with Typescript to import
@@ -9,9 +9,13 @@ import { RootNodesUnavailableError } from '../errors';
// set to true.
const calculateSlot = require('cluster-key-slot');
export interface ClusterNode<M extends RedisModules, S extends RedisScripts> {
export interface ClusterNode<
M extends RedisModules,
F extends RedisFunctions,
S extends RedisScripts
> {
id: string;
client: RedisClientType<M, S>;
client: RedisClientType<M, F, S>;
}
interface NodeAddress {
@@ -23,22 +27,30 @@ export type NodeAddressMap = {
[address: string]: NodeAddress;
} | ((address: string) => NodeAddress | undefined);
interface SlotNodes<M extends RedisModules, S extends RedisScripts> {
master: ClusterNode<M, S>;
replicas: Array<ClusterNode<M, S>>;
clientIterator: IterableIterator<RedisClientType<M, S>> | undefined;
interface SlotNodes<
M extends RedisModules,
F extends RedisFunctions,
S extends RedisScripts
> {
master: ClusterNode<M, F, S>;
replicas: Array<ClusterNode<M, F, S>>;
clientIterator: IterableIterator<RedisClientType<M, F, S>> | undefined;
}
type OnError = (err: unknown) => void;
export default class RedisClusterSlots<M extends RedisModules, S extends RedisScripts> {
readonly #options: RedisClusterOptions<M, S>;
readonly #Client: InstantiableRedisClient<M, S>;
export default class RedisClusterSlots<
M extends RedisModules,
F extends RedisFunctions,
S extends RedisScripts
> {
readonly #options: RedisClusterOptions<M, F, S>;
readonly #Client: InstantiableRedisClient<M, F, S>;
readonly #onError: OnError;
readonly #nodeByAddress = new Map<string, ClusterNode<M, S>>();
readonly #slots: Array<SlotNodes<M, S>> = [];
readonly #nodeByAddress = new Map<string, ClusterNode<M, F, S>>();
readonly #slots: Array<SlotNodes<M, F, S>> = [];
constructor(options: RedisClusterOptions<M, S>, onError: OnError) {
constructor(options: RedisClusterOptions<M, F, S>, onError: OnError) {
this.#options = options;
this.#Client = RedisClient.extend(options);
this.#onError = onError;
@@ -72,7 +84,7 @@ export default class RedisClusterSlots<M extends RedisModules, S extends RedisSc
#runningRediscoverPromise?: Promise<void>;
async rediscover(startWith: RedisClientType<M, S>): Promise<void> {
async rediscover(startWith: RedisClientType<M, F, S>): Promise<void> {
if (!this.#runningRediscoverPromise) {
this.#runningRediscoverPromise = this.#rediscover(startWith)
.finally(() => this.#runningRediscoverPromise = undefined);
@@ -81,7 +93,7 @@ export default class RedisClusterSlots<M extends RedisModules, S extends RedisSc
return this.#runningRediscoverPromise;
}
async #rediscover(startWith: RedisClientType<M, S>): Promise<void> {
async #rediscover(startWith: RedisClientType<M, F, S>): Promise<void> {
if (await this.#discoverNodes(startWith.options)) return;
for (const { client } of this.#nodeByAddress.values()) {
@@ -137,7 +149,7 @@ export default class RedisClusterSlots<M extends RedisModules, S extends RedisSc
};
}
#initiateClient(options?: RedisClusterClientOptions): RedisClientType<M, S> {
#initiateClient(options?: RedisClusterClientOptions): RedisClientType<M, F, S> {
return new this.#Client(this.#clientOptionsDefaults(options))
.on('error', this.#onError);
}
@@ -152,7 +164,12 @@ export default class RedisClusterSlots<M extends RedisModules, S extends RedisSc
}
}
#initiateClientForNode(nodeData: RedisClusterMasterNode | RedisClusterReplicaNode, readonly: boolean, clientsInUse: Set<string>, promises: Array<Promise<void>>): ClusterNode<M, S> {
#initiateClientForNode(
nodeData: RedisClusterMasterNode | RedisClusterReplicaNode,
readonly: boolean,
clientsInUse: Set<string>,
promises: Array<Promise<void>>
): ClusterNode<M, F, S> {
const address = `${nodeData.host}:${nodeData.port}`;
clientsInUse.add(address);
@@ -175,11 +192,11 @@ export default class RedisClusterSlots<M extends RedisModules, S extends RedisSc
return node;
}
getSlotMaster(slot: number): ClusterNode<M, S> {
getSlotMaster(slot: number): ClusterNode<M, F, S> {
return this.#slots[slot].master;
}
*#slotClientIterator(slotNumber: number): IterableIterator<RedisClientType<M, S>> {
*#slotClientIterator(slotNumber: number): IterableIterator<RedisClientType<M, F, S>> {
const slot = this.#slots[slotNumber];
yield slot.master.client;
@@ -188,7 +205,7 @@ export default class RedisClusterSlots<M extends RedisModules, S extends RedisSc
}
}
#getSlotClient(slotNumber: number): RedisClientType<M, S> {
#getSlotClient(slotNumber: number): RedisClientType<M, F, S> {
const slot = this.#slots[slotNumber];
if (!slot.clientIterator) {
slot.clientIterator = this.#slotClientIterator(slotNumber);
@@ -203,9 +220,9 @@ export default class RedisClusterSlots<M extends RedisModules, S extends RedisSc
return value;
}
#randomClientIterator?: IterableIterator<ClusterNode<M, S>>;
#randomClientIterator?: IterableIterator<ClusterNode<M, F, S>>;
#getRandomClient(): RedisClientType<M, S> {
#getRandomClient(): RedisClientType<M, F, S> {
if (!this.#nodeByAddress.size) {
throw new Error('Cluster is not connected');
}
@@ -223,7 +240,7 @@ export default class RedisClusterSlots<M extends RedisModules, S extends RedisSc
return value.client;
}
getClient(firstKey?: RedisCommandArgument, isReadonly?: boolean): RedisClientType<M, S> {
getClient(firstKey?: RedisCommandArgument, isReadonly?: boolean): RedisClientType<M, F, S> {
if (!firstKey) {
return this.#getRandomClient();
}
@@ -236,7 +253,7 @@ export default class RedisClusterSlots<M extends RedisModules, S extends RedisSc
return this.#getSlotClient(slot);
}
getMasters(): Array<ClusterNode<M, S>> {
getMasters(): Array<ClusterNode<M, F, S>> {
const masters = [];
for (const node of this.#nodeByAddress.values()) {
if (node.client.options?.readonly) continue;
@@ -247,7 +264,7 @@ export default class RedisClusterSlots<M extends RedisModules, S extends RedisSc
return masters;
}
getNodeByAddress(address: string): ClusterNode<M, S> | undefined {
getNodeByAddress(address: string): ClusterNode<M, F, S> | undefined {
const mappedAddress = this.#getNodeAddress(address);
return this.#nodeByAddress.get(
mappedAddress ? `${mappedAddress.host}:${mappedAddress.port}` : address
@@ -262,7 +279,7 @@ export default class RedisClusterSlots<M extends RedisModules, S extends RedisSc
return this.#destroy(client => client.disconnect());
}
async #destroy(fn: (client: RedisClientType<M, S>) => Promise<unknown>): Promise<void> {
async #destroy(fn: (client: RedisClientType<M, F, S>) => Promise<unknown>): Promise<void> {
const promises = [];
for (const { client } of this.#nodeByAddress.values()) {
promises.push(fn(client));

View File

@@ -18,12 +18,16 @@ import * as DECR from '../commands/DECR';
import * as DECRBY from '../commands/DECRBY';
import * as DEL from '../commands/DEL';
import * as DUMP from '../commands/DUMP';
import * as EVAL_RO from '../commands/EVAL_RO';
import * as EVAL from '../commands/EVAL';
import * as EVALSHA_RO from '../commands/EVALSHA_RO';
import * as EVALSHA from '../commands/EVALSHA';
import * as EXISTS from '../commands/EXISTS';
import * as EXPIRE from '../commands/EXPIRE';
import * as EXPIREAT from '../commands/EXPIREAT';
import * as EXPIRETIME from '../commands/EXPIRETIME';
import * as FCALL_RO from '../commands/FCALL_RO';
import * as FCALL from '../commands/FCALL';
import * as GEOADD from '../commands/GEOADD';
import * as GEODIST from '../commands/GEODIST';
import * as GEOHASH from '../commands/GEOHASH';
@@ -230,10 +234,14 @@ export default {
del: DEL,
DUMP,
dump: DUMP,
EVAL_RO,
evalRo: EVAL_RO,
EVAL,
eval: EVAL,
EVALSHA,
evalSha: EVALSHA,
EVALSHA_RO,
evalShaRo: EVALSHA_RO,
EXISTS,
exists: EXISTS,
EXPIRE,
@@ -242,6 +250,10 @@ export default {
expireAt: EXPIREAT,
EXPIRETIME,
expireTime: EXPIRETIME,
FCALL_RO,
fCallRo: FCALL_RO,
FCALL,
fCall: FCALL,
GEOADD,
geoAdd: GEOADD,
GEODIST,

View File

@@ -1,18 +1,22 @@
import COMMANDS from './commands';
import { RedisCommand, RedisCommandArgument, RedisCommandArguments, RedisCommandRawReply, RedisCommandReply, RedisModules, RedisPlugins, RedisScript, RedisScripts } from '../commands';
import { ClientCommandOptions, RedisClientCommandSignature, RedisClientOptions, RedisClientType, WithModules, WithScripts } from '../client';
import { RedisCommand, RedisCommandArgument, RedisCommandArguments, RedisCommandRawReply, RedisCommandReply, RedisFunctions, RedisModules, RedisExtensions, RedisScript, RedisScripts, RedisCommandSignature, RedisFunction } from '../commands';
import { ClientCommandOptions, RedisClientOptions, RedisClientType, WithFunctions, WithModules, WithScripts } from '../client';
import RedisClusterSlots, { ClusterNode, NodeAddressMap } from './cluster-slots';
import { extendWithModulesAndScripts, transformCommandArguments, transformCommandReply, extendWithCommands } from '../commander';
import { attachExtensions, transformCommandReply, attachCommands, transformCommandArguments } from '../commander';
import { EventEmitter } from 'events';
import RedisClusterMultiCommand, { RedisClusterMultiCommandType } from './multi-command';
import RedisClusterMultiCommand, { InstantiableRedisClusterMultiCommandType, RedisClusterMultiCommandType } from './multi-command';
import { RedisMultiQueuedCommand } from '../multi-command';
export type RedisClusterClientOptions = Omit<RedisClientOptions, 'modules' | 'scripts'>;
export type RedisClusterClientOptions = Omit<
RedisClientOptions,
'modules' | 'functions' | 'scripts' | 'database'
>;
export interface RedisClusterOptions<
M extends RedisModules = Record<string, never>,
F extends RedisFunctions = Record<string, never>,
S extends RedisScripts = Record<string, never>
> extends RedisPlugins<M, S> {
> extends RedisExtensions<M, F, S> {
rootNodes: Array<RedisClusterClientOptions>;
defaults?: Partial<RedisClusterClientOptions>;
useReplicas?: boolean;
@@ -21,16 +25,25 @@ export interface RedisClusterOptions<
}
type WithCommands = {
[P in keyof typeof COMMANDS]: RedisClientCommandSignature<(typeof COMMANDS)[P]>;
[P in keyof typeof COMMANDS]: RedisCommandSignature<(typeof COMMANDS)[P]>;
};
export type RedisClusterType<
M extends RedisModules = Record<string, never>,
F extends RedisFunctions = Record<string, never>,
S extends RedisScripts = Record<string, never>
> = RedisCluster<M, S> & WithCommands & WithModules<M> & WithScripts<S>;
> = RedisCluster<M, F, S> & WithCommands & WithModules<M> & WithFunctions<F> & WithScripts<S>;
export default class RedisCluster<M extends RedisModules, S extends RedisScripts> extends EventEmitter {
static extractFirstKey(command: RedisCommand, originalArgs: Array<unknown>, redisArgs: RedisCommandArguments): RedisCommandArgument | undefined {
export default class RedisCluster<
M extends RedisModules,
F extends RedisFunctions,
S extends RedisScripts
> extends EventEmitter {
static extractFirstKey(
command: RedisCommand,
originalArgs: Array<unknown>,
redisArgs: RedisCommandArguments
): RedisCommandArgument | undefined {
if (command.FIRST_KEY_INDEX === undefined) {
return undefined;
} else if (typeof command.FIRST_KEY_INDEX === 'number') {
@@ -40,21 +53,27 @@ export default class RedisCluster<M extends RedisModules, S extends RedisScripts
return command.FIRST_KEY_INDEX(...originalArgs);
}
static create<M extends RedisModules, S extends RedisScripts>(options?: RedisClusterOptions<M, S>): RedisClusterType<M, S> {
return new (<any>extendWithModulesAndScripts({
static create<
M extends RedisModules,
F extends RedisFunctions,
S extends RedisScripts
>(options?: RedisClusterOptions<M, F, S>): RedisClusterType<M, F, S> {
return new (attachExtensions({
BaseClass: RedisCluster,
modulesExecutor: RedisCluster.prototype.commandsExecutor,
modules: options?.modules,
modulesCommandsExecutor: RedisCluster.prototype.commandsExecutor,
scripts: options?.scripts,
scriptsExecutor: RedisCluster.prototype.scriptsExecutor
functionsExecutor: RedisCluster.prototype.functionsExecutor,
functions: options?.functions,
scriptsExecutor: RedisCluster.prototype.scriptsExecutor,
scripts: options?.scripts
}))(options);
}
readonly #options: RedisClusterOptions<M, S>;
readonly #slots: RedisClusterSlots<M, S>;
readonly #Multi: new (...args: ConstructorParameters<typeof RedisClusterMultiCommand>) => RedisClusterMultiCommandType<M, S>;
readonly #options: RedisClusterOptions<M, F, S>;
readonly #slots: RedisClusterSlots<M, F, S>;
readonly #Multi: InstantiableRedisClusterMultiCommandType<M, F, S>;
constructor(options: RedisClusterOptions<M, S>) {
constructor(options: RedisClusterOptions<M, F, S>) {
super();
this.#options = options;
@@ -62,7 +81,7 @@ export default class RedisCluster<M extends RedisModules, S extends RedisScripts
this.#Multi = RedisClusterMultiCommand.extend(options);
}
duplicate(overrides?: Partial<RedisClusterOptions<M, S>>): RedisClusterType<M, S> {
duplicate(overrides?: Partial<RedisClusterOptions<M, F, S>>): RedisClusterType<M, F, S> {
return new (Object.getPrototypeOf(this).constructor)({
...this.#options,
...overrides
@@ -73,9 +92,11 @@ export default class RedisCluster<M extends RedisModules, S extends RedisScripts
return this.#slots.connect();
}
async commandsExecutor(command: RedisCommand, args: Array<unknown>): Promise<RedisCommandReply<typeof command>> {
const { args: redisArgs, options } = transformCommandArguments<ClientCommandOptions>(command, args);
async commandsExecutor<C extends RedisCommand>(
command: C,
args: Array<unknown>
): Promise<RedisCommandReply<C>> {
const { args: redisArgs, options } = transformCommandArguments(command, args);
return transformCommandReply(
command,
await this.sendCommand(
@@ -101,9 +122,38 @@ export default class RedisCluster<M extends RedisModules, S extends RedisScripts
);
}
async scriptsExecutor(script: RedisScript, args: Array<unknown>): Promise<RedisCommandReply<typeof script>> {
const { args: redisArgs, options } = transformCommandArguments<ClientCommandOptions>(script, args);
async functionsExecutor<F extends RedisFunction>(
fn: F,
args: Array<unknown>
): Promise<RedisCommandReply<F>> {
const { args: redisArgs, options } = transformCommandArguments(fn, args);
return transformCommandReply(
fn,
await this.executeFunction(
fn,
args,
redisArgs,
options
),
redisArgs.preserve
);
}
async executeFunction(
fn: RedisFunction,
originalArgs: Array<unknown>,
redisArgs: RedisCommandArguments,
options?: ClientCommandOptions
): Promise<RedisCommandRawReply> {
return this.#execute(
RedisCluster.extractFirstKey(fn, originalArgs, redisArgs),
fn.IS_READ_ONLY,
client => client.executeFunction(fn, redisArgs, options)
);
}
async scriptsExecutor<S extends RedisScript>(script: S, args: Array<unknown>): Promise<RedisCommandReply<S>> {
const { args: redisArgs, options } = transformCommandArguments(script, args);
return transformCommandReply(
script,
await this.executeScript(
@@ -121,7 +171,7 @@ export default class RedisCluster<M extends RedisModules, S extends RedisScripts
originalArgs: Array<unknown>,
redisArgs: RedisCommandArguments,
options?: ClientCommandOptions
): Promise<RedisCommandReply<typeof script>> {
): Promise<RedisCommandRawReply> {
return this.#execute(
RedisCluster.extractFirstKey(script, originalArgs, redisArgs),
script.IS_READ_ONLY,
@@ -132,7 +182,7 @@ export default class RedisCluster<M extends RedisModules, S extends RedisScripts
async #execute<Reply>(
firstKey: RedisCommandArgument | undefined,
isReadonly: boolean | undefined,
executor: (client: RedisClientType<M, S>) => Promise<Reply>
executor: (client: RedisClientType<M, F, S>) => Promise<Reply>
): Promise<Reply> {
const maxCommandRedirections = this.#options.maxCommandRedirections ?? 16;
let client = this.#slots.getClient(firstKey, isReadonly);
@@ -171,7 +221,7 @@ export default class RedisCluster<M extends RedisModules, S extends RedisScripts
}
}
multi(routing?: RedisCommandArgument): RedisClusterMultiCommandType<M, S> {
multi(routing?: RedisCommandArgument): RedisClusterMultiCommandType<M, F, S> {
return new this.#Multi(
(commands: Array<RedisMultiQueuedCommand>, firstKey?: RedisCommandArgument, chainId?: symbol) => {
return this.#execute(
@@ -184,11 +234,11 @@ export default class RedisCluster<M extends RedisModules, S extends RedisScripts
);
}
getMasters(): Array<ClusterNode<M, S>> {
getMasters(): Array<ClusterNode<M, F, S>> {
return this.#slots.getMasters();
}
getSlotMaster(slot: number): ClusterNode<M, S> {
getSlotMaster(slot: number): ClusterNode<M, F, S> {
return this.#slots.getSlotMaster(slot);
}
@@ -201,7 +251,7 @@ export default class RedisCluster<M extends RedisModules, S extends RedisScripts
}
}
extendWithCommands({
attachCommands({
BaseClass: RedisCluster,
commands: COMMANDS,
executor: RedisCluster.prototype.commandsExecutor

View File

@@ -1,29 +1,63 @@
import COMMANDS from './commands';
import { RedisCommand, RedisCommandArgument, RedisCommandArguments, RedisCommandRawReply, RedisModules, RedisPlugins, RedisScript, RedisScripts } from '../commands';
import { RedisCommand, RedisCommandArgument, RedisCommandArguments, RedisCommandRawReply, RedisFunctions, RedisModules, RedisExtensions, RedisScript, RedisScripts, ExcludeMappedString, RedisFunction } from '../commands';
import RedisMultiCommand, { RedisMultiQueuedCommand } from '../multi-command';
import { extendWithCommands, extendWithModulesAndScripts } from '../commander';
import { attachCommands, attachExtensions } from '../commander';
import RedisCluster from '.';
import { ExcludeMappedString } from '../client';
type RedisClusterMultiCommandSignature<C extends RedisCommand, M extends RedisModules, S extends RedisScripts> =
(...args: Parameters<C['transformArguments']>) => RedisClusterMultiCommandType<M, S>;
type RedisClusterMultiCommandSignature<
C extends RedisCommand,
M extends RedisModules,
F extends RedisFunctions,
S extends RedisScripts
> = (...args: Parameters<C['transformArguments']>) => RedisClusterMultiCommandType<M, F, S>;
type WithCommands<M extends RedisModules, S extends RedisScripts> = {
[P in keyof typeof COMMANDS]: RedisClusterMultiCommandSignature<(typeof COMMANDS)[P], M, S>
type WithCommands<
M extends RedisModules,
F extends RedisFunctions,
S extends RedisScripts
> = {
[P in keyof typeof COMMANDS]: RedisClusterMultiCommandSignature<(typeof COMMANDS)[P], M, F, S>;
};
type WithModules<M extends RedisModules, S extends RedisScripts> = {
type WithModules<
M extends RedisModules,
F extends RedisFunctions,
S extends RedisScripts
> = {
[P in keyof M as ExcludeMappedString<P>]: {
[C in keyof M[P] as ExcludeMappedString<C>]: RedisClusterMultiCommandSignature<M[P][C], M, S>;
[C in keyof M[P] as ExcludeMappedString<C>]: RedisClusterMultiCommandSignature<M[P][C], M, F, S>;
};
};
type WithScripts<M extends RedisModules, S extends RedisScripts> = {
[P in keyof S as ExcludeMappedString<P>]: RedisClusterMultiCommandSignature<S[P], M, S>
type WithFunctions<
M extends RedisModules,
F extends RedisFunctions,
S extends RedisScripts
> = {
[P in keyof F as ExcludeMappedString<P>]: {
[FF in keyof F[P] as ExcludeMappedString<FF>]: RedisClusterMultiCommandSignature<F[P][FF], M, F, S>;
};
};
export type RedisClusterMultiCommandType<M extends RedisModules, S extends RedisScripts> =
RedisClusterMultiCommand & WithCommands<M, S> & WithModules<M, S> & WithScripts<M, S>;
type WithScripts<
M extends RedisModules,
F extends RedisFunctions,
S extends RedisScripts
> = {
[P in keyof S as ExcludeMappedString<P>]: RedisClusterMultiCommandSignature<S[P], M, F, S>;
};
export type RedisClusterMultiCommandType<
M extends RedisModules,
F extends RedisFunctions,
S extends RedisScripts
> = RedisClusterMultiCommand & WithCommands<M, F, S> & WithModules<M, F, S> & WithFunctions<M, F, S> & WithScripts<M, F, S>;
export type InstantiableRedisClusterMultiCommandType<
M extends RedisModules,
F extends RedisFunctions,
S extends RedisScripts
> = new (...args: ConstructorParameters<typeof RedisClusterMultiCommand>) => RedisClusterMultiCommandType<M, F, S>;
export type RedisClusterMultiExecutor = (queue: Array<RedisMultiQueuedCommand>, firstKey?: RedisCommandArgument, chainId?: symbol) => Promise<Array<RedisCommandRawReply>>;
@@ -32,15 +66,19 @@ export default class RedisClusterMultiCommand {
readonly #executor: RedisClusterMultiExecutor;
#firstKey: RedisCommandArgument | undefined;
static extend<M extends RedisModules, S extends RedisScripts>(
plugins?: RedisPlugins<M, S>
): new (...args: ConstructorParameters<typeof RedisMultiCommand>) => RedisClusterMultiCommandType<M, S> {
return <any>extendWithModulesAndScripts({
static extend<
M extends RedisModules,
F extends RedisFunctions,
S extends RedisScripts
>(extensions?: RedisExtensions<M, F, S>): InstantiableRedisClusterMultiCommandType<M, F, S> {
return attachExtensions({
BaseClass: RedisClusterMultiCommand,
modules: plugins?.modules,
modulesCommandsExecutor: RedisClusterMultiCommand.prototype.commandsExecutor,
scripts: plugins?.scripts,
scriptsExecutor: RedisClusterMultiCommand.prototype.scriptsExecutor
modulesExecutor: RedisClusterMultiCommand.prototype.commandsExecutor,
modules: extensions?.modules,
functionsExecutor: RedisClusterMultiCommand.prototype.functionsExecutor,
functions: extensions?.functions,
scriptsExecutor: RedisClusterMultiCommand.prototype.scriptsExecutor,
scripts: extensions?.scripts
});
}
@@ -51,15 +89,8 @@ export default class RedisClusterMultiCommand {
commandsExecutor(command: RedisCommand, args: Array<unknown>): this {
const transformedArguments = command.transformArguments(...args);
if (!this.#firstKey) {
this.#firstKey = RedisCluster.extractFirstKey(command, args, transformedArguments);
}
return this.addCommand(
undefined,
transformedArguments,
command.transformReply
);
this.#firstKey ??= RedisCluster.extractFirstKey(command, args, transformedArguments);
return this.addCommand(undefined, transformedArguments, command.transformReply);
}
addCommand(
@@ -67,21 +98,21 @@ export default class RedisClusterMultiCommand {
args: RedisCommandArguments,
transformReply?: RedisCommand['transformReply']
): this {
if (!this.#firstKey) {
this.#firstKey = firstKey;
}
this.#firstKey ??= firstKey;
this.#multi.addCommand(args, transformReply);
return this;
}
functionsExecutor(fn: RedisFunction, args: Array<unknown>): this {
const transformedArguments = this.#multi.addFunction(fn, args);
this.#firstKey ??= RedisCluster.extractFirstKey(fn, args, transformedArguments);
return this;
}
scriptsExecutor(script: RedisScript, args: Array<unknown>): this {
const transformedArguments = this.#multi.addScript(script, args);
if (!this.#firstKey) {
this.#firstKey = RedisCluster.extractFirstKey(script, args, transformedArguments);
}
return this.addCommand(undefined, transformedArguments);
this.#firstKey ??= RedisCluster.extractFirstKey(script, args, transformedArguments);
return this;
}
async exec(execAsPipeline = false): Promise<Array<RedisCommandRawReply>> {
@@ -106,7 +137,7 @@ export default class RedisClusterMultiCommand {
}
}
extendWithCommands({
attachCommands({
BaseClass: RedisClusterMultiCommand,
commands: COMMANDS,
executor: RedisClusterMultiCommand.prototype.commandsExecutor