You've already forked node-redis
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:
@@ -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));
|
||||
|
@@ -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,
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user