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:
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node-version: ['12', '14', '16']
|
||||
redis-version: ['5', '6.0', '6.2', '7.0-rc2']
|
||||
redis-version: ['5', '6.0', '6.2', '7.0-rc3']
|
||||
steps:
|
||||
- uses: actions/checkout@v2.3.4
|
||||
with:
|
||||
|
27
index.ts
27
index.ts
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
RedisModules,
|
||||
RedisFunctions,
|
||||
RedisScripts,
|
||||
createClient as _createClient,
|
||||
RedisClientOptions,
|
||||
@@ -33,12 +34,17 @@ export type RedisDefaultModules = typeof modules;
|
||||
|
||||
export type RedisClientType<
|
||||
M extends RedisModules = RedisDefaultModules,
|
||||
F extends RedisFunctions = Record<string, never>,
|
||||
S extends RedisScripts = Record<string, never>
|
||||
> = _RedisClientType<M, S>;
|
||||
> = _RedisClientType<M, F, S>;
|
||||
|
||||
export function createClient<M extends RedisModules, S extends RedisScripts>(
|
||||
options?: RedisClientOptions<M, S>
|
||||
): _RedisClientType<RedisDefaultModules & M, S> {
|
||||
export function createClient<
|
||||
M extends RedisModules,
|
||||
F extends RedisFunctions,
|
||||
S extends RedisScripts
|
||||
>(
|
||||
options?: RedisClientOptions<M, F, S>
|
||||
): _RedisClientType<RedisDefaultModules & M, F, S> {
|
||||
return _createClient({
|
||||
...options,
|
||||
modules: {
|
||||
@@ -50,12 +56,17 @@ export function createClient<M extends RedisModules, S extends RedisScripts>(
|
||||
|
||||
export type RedisClusterType<
|
||||
M extends RedisModules = RedisDefaultModules,
|
||||
F extends RedisFunctions = Record<string, never>,
|
||||
S extends RedisScripts = Record<string, never>
|
||||
> = _RedisClusterType<M, S>;
|
||||
> = _RedisClusterType<M, F, S>;
|
||||
|
||||
export function createCluster<M extends RedisModules, S extends RedisScripts>(
|
||||
options: RedisClusterOptions<M, S>
|
||||
): RedisClusterType<RedisDefaultModules & M, S> {
|
||||
export function createCluster<
|
||||
M extends RedisModules,
|
||||
F extends RedisFunctions,
|
||||
S extends RedisScripts
|
||||
>(
|
||||
options: RedisClusterOptions<M, F, S>
|
||||
): RedisClusterType<RedisDefaultModules & M, F, S> {
|
||||
return _createCluster({
|
||||
...options,
|
||||
modules: {
|
||||
|
45
package-lock.json
generated
45
package-lock.json
generated
@@ -1865,6 +1865,19 @@
|
||||
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/compress-brotli": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/compress-brotli/-/compress-brotli-1.3.6.tgz",
|
||||
"integrity": "sha512-au99/GqZtUtiCBliqLFbWlhnCxn+XSYjwZ77q6mKN4La4qOXDoLVPZ50iXr0WmAyMxl8yqoq3Yq4OeQNPPkyeQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/json-buffer": "~3.0.0",
|
||||
"json-buffer": "~3.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
@@ -3332,9 +3345,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/inquirer": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.0.tgz",
|
||||
"integrity": "sha512-0crLweprevJ02tTuA6ThpoAERAGyVILC4sS74uib58Xf/zSr1/ZWtmm7D5CI+bSQEaA04f0K7idaHpQbSWgiVQ==",
|
||||
"version": "8.2.2",
|
||||
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.2.tgz",
|
||||
"integrity": "sha512-pG7I/si6K/0X7p1qU+rfWnpTE1UIkTONN1wxtzh0d+dHXtT/JG6qBgLxoyHVsQa8cFABxAPh0pD6uUUHiAoaow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-escapes": "^4.2.1",
|
||||
@@ -3347,13 +3360,13 @@
|
||||
"mute-stream": "0.0.8",
|
||||
"ora": "^5.4.1",
|
||||
"run-async": "^2.4.0",
|
||||
"rxjs": "^7.2.0",
|
||||
"rxjs": "^7.5.5",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"through": "^2.3.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/internal-slot": {
|
||||
@@ -5453,7 +5466,7 @@
|
||||
"globby": "11.0.4",
|
||||
"got": "9.6.0",
|
||||
"import-cwd": "3.0.0",
|
||||
"inquirer": "8.2.0",
|
||||
"inquirer": "8.2.2",
|
||||
"is-ci": "3.0.1",
|
||||
"lodash": "4.17.21",
|
||||
"mime-types": "2.1.35",
|
||||
@@ -8382,6 +8395,16 @@
|
||||
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
|
||||
"dev": true
|
||||
},
|
||||
"compress-brotli": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/compress-brotli/-/compress-brotli-1.3.6.tgz",
|
||||
"integrity": "sha512-au99/GqZtUtiCBliqLFbWlhnCxn+XSYjwZ77q6mKN4La4qOXDoLVPZ50iXr0WmAyMxl8yqoq3Yq4OeQNPPkyeQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/json-buffer": "~3.0.0",
|
||||
"json-buffer": "~3.0.1"
|
||||
}
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
@@ -9474,9 +9497,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"inquirer": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.0.tgz",
|
||||
"integrity": "sha512-0crLweprevJ02tTuA6ThpoAERAGyVILC4sS74uib58Xf/zSr1/ZWtmm7D5CI+bSQEaA04f0K7idaHpQbSWgiVQ==",
|
||||
"version": "8.2.2",
|
||||
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.2.tgz",
|
||||
"integrity": "sha512-pG7I/si6K/0X7p1qU+rfWnpTE1UIkTONN1wxtzh0d+dHXtT/JG6qBgLxoyHVsQa8cFABxAPh0pD6uUUHiAoaow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-escapes": "^4.2.1",
|
||||
@@ -9489,7 +9512,7 @@
|
||||
"mute-stream": "0.0.8",
|
||||
"ora": "^5.4.1",
|
||||
"run-async": "^2.4.0",
|
||||
"rxjs": "^7.2.0",
|
||||
"rxjs": "^7.5.5",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"through": "^2.3.6"
|
||||
@@ -11034,7 +11057,7 @@
|
||||
"globby": "11.0.4",
|
||||
"got": "9.6.0",
|
||||
"import-cwd": "3.0.0",
|
||||
"inquirer": "8.2.0",
|
||||
"inquirer": "8.2.2",
|
||||
"is-ci": "3.0.1",
|
||||
"lodash": "4.17.21",
|
||||
"mime-types": "2.1.35",
|
||||
|
@@ -3,7 +3,7 @@ import RedisCluster from './lib/cluster';
|
||||
|
||||
export { RedisClientType, RedisClientOptions } from './lib/client';
|
||||
|
||||
export { RedisModules, RedisScripts } from './lib/commands';
|
||||
export { RedisModules, RedisFunctions, RedisScripts } from './lib/commands';
|
||||
|
||||
export const createClient = RedisClient.create;
|
||||
|
||||
|
@@ -62,6 +62,15 @@ import * as ECHO from '../commands/ECHO';
|
||||
import * as FAILOVER from '../commands/FAILOVER';
|
||||
import * as FLUSHALL from '../commands/FLUSHALL';
|
||||
import * as FLUSHDB from '../commands/FLUSHDB';
|
||||
import * as FUNCTION_DELETE from '../commands/FUNCTION_DELETE';
|
||||
import * as FUNCTION_DUMP from '../commands/FUNCTION_DUMP';
|
||||
import * as FUNCTION_FLUSH from '../commands/FUNCTION_FLUSH';
|
||||
import * as FUNCTION_KILL from '../commands/FUNCTION_KILL';
|
||||
import * as FUNCTION_LIST_WITHCODE from '../commands/FUNCTION_LIST_WITHCODE';
|
||||
import * as FUNCTION_LIST from '../commands/FUNCTION_LIST';
|
||||
import * as FUNCTION_LOAD from '../commands/FUNCTION_LOAD';
|
||||
import * as FUNCTION_RESTORE from '../commands/FUNCTION_RESTORE';
|
||||
import * as FUNCTION_STATS from '../commands/FUNCTION_STATS';
|
||||
import * as HELLO from '../commands/HELLO';
|
||||
import * as INFO from '../commands/INFO';
|
||||
import * as KEYS from '../commands/KEYS';
|
||||
@@ -228,6 +237,24 @@ export default {
|
||||
flushAll: FLUSHALL,
|
||||
FLUSHDB,
|
||||
flushDb: FLUSHDB,
|
||||
FUNCTION_DELETE,
|
||||
functionDelete: FUNCTION_DELETE,
|
||||
FUNCTION_DUMP,
|
||||
functionDump: FUNCTION_DUMP,
|
||||
FUNCTION_FLUSH,
|
||||
functionFlush: FUNCTION_FLUSH,
|
||||
FUNCTION_KILL,
|
||||
functionKill: FUNCTION_KILL,
|
||||
FUNCTION_LIST_WITHCODE,
|
||||
functionListWithCode: FUNCTION_LIST_WITHCODE,
|
||||
FUNCTION_LIST,
|
||||
functionList: FUNCTION_LIST,
|
||||
FUNCTION_LOAD,
|
||||
functionLoad: FUNCTION_LOAD,
|
||||
FUNCTION_RESTORE,
|
||||
functionRestore: FUNCTION_RESTORE,
|
||||
FUNCTION_STATS,
|
||||
functionStats: FUNCTION_STATS,
|
||||
HELLO,
|
||||
hello: HELLO,
|
||||
INFO,
|
||||
|
@@ -2,7 +2,7 @@ import { strict as assert } from 'assert';
|
||||
import testUtils, { GLOBAL, waitTillBeenCalled } from '../test-utils';
|
||||
import RedisClient, { RedisClientType } from '.';
|
||||
import { RedisClientMultiCommandType } from './multi-command';
|
||||
import { RedisCommandArguments, RedisCommandRawReply, RedisModules, RedisScripts } from '../commands';
|
||||
import { RedisCommandArguments, RedisCommandRawReply, RedisModules, RedisFunctions, RedisScripts } from '../commands';
|
||||
import { AbortError, ClientClosedError, ConnectionTimeoutError, DisconnectsClientError, SocketClosedUnexpectedlyError, WatchError } from '../errors';
|
||||
import { defineScript } from '../lua-script';
|
||||
import { spy } from 'sinon';
|
||||
@@ -10,16 +10,42 @@ import { once } from 'events';
|
||||
import { ClientKillFilters } from '../commands/CLIENT_KILL';
|
||||
|
||||
export const SQUARE_SCRIPT = defineScript({
|
||||
NUMBER_OF_KEYS: 0,
|
||||
SCRIPT: 'return ARGV[1] * ARGV[1];',
|
||||
NUMBER_OF_KEYS: 0,
|
||||
transformArguments(number: number): Array<string> {
|
||||
return [number.toString()];
|
||||
},
|
||||
transformReply(reply: number): number {
|
||||
return reply;
|
||||
}
|
||||
});
|
||||
|
||||
export const MATH_FUNCTION = {
|
||||
name: 'math',
|
||||
engine: 'LUA',
|
||||
code: `#!LUA name=math
|
||||
redis.register_function{
|
||||
function_name = "square",
|
||||
callback = function(keys, args) return args[1] * args[1] end,
|
||||
flags = { "no-writes" }
|
||||
}`,
|
||||
library: {
|
||||
square: {
|
||||
NAME: 'square',
|
||||
NUMBER_OF_KEYS: 0,
|
||||
transformArguments(number: number): Array<string> {
|
||||
return [number.toString()];
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export async function loadMathFunction(
|
||||
client: RedisClientType<RedisModules, RedisFunctions, RedisScripts>
|
||||
): Promise<void> {
|
||||
await client.functionLoad(
|
||||
MATH_FUNCTION.code,
|
||||
{ REPLACE: true }
|
||||
);
|
||||
}
|
||||
|
||||
describe('Client', () => {
|
||||
describe('parseURL', () => {
|
||||
it('redis://user:secret@localhost:6379/0', () => {
|
||||
@@ -115,7 +141,14 @@ describe('Client', () => {
|
||||
});
|
||||
|
||||
describe('legacyMode', () => {
|
||||
function sendCommandAsync<M extends RedisModules, S extends RedisScripts>(client: RedisClientType<M, S>, args: RedisCommandArguments): Promise<RedisCommandRawReply> {
|
||||
function sendCommandAsync<
|
||||
M extends RedisModules,
|
||||
F extends RedisFunctions,
|
||||
S extends RedisScripts
|
||||
>(
|
||||
client: RedisClientType<M, F, S>,
|
||||
args: RedisCommandArguments
|
||||
): Promise<RedisCommandRawReply> {
|
||||
return new Promise((resolve, reject) => {
|
||||
(client as any).sendCommand(args, (err: Error | undefined, reply: RedisCommandRawReply) => {
|
||||
if (err) return reject(err);
|
||||
@@ -159,7 +192,14 @@ describe('Client', () => {
|
||||
}
|
||||
});
|
||||
|
||||
function setAsync<M extends RedisModules, S extends RedisScripts>(client: RedisClientType<M, S>, ...args: Array<any>): Promise<RedisCommandRawReply> {
|
||||
function setAsync<
|
||||
M extends RedisModules,
|
||||
F extends RedisFunctions,
|
||||
S extends RedisScripts
|
||||
>(
|
||||
client: RedisClientType<M, F, S>,
|
||||
...args: Array<any>
|
||||
): Promise<RedisCommandRawReply> {
|
||||
return new Promise((resolve, reject) => {
|
||||
(client as any).set(...args, (err: Error | undefined, reply: RedisCommandRawReply) => {
|
||||
if (err) return reject(err);
|
||||
@@ -205,7 +245,11 @@ describe('Client', () => {
|
||||
}
|
||||
});
|
||||
|
||||
function multiExecAsync<M extends RedisModules, S extends RedisScripts>(multi: RedisClientMultiCommandType<M, S>): Promise<Array<RedisCommandRawReply>> {
|
||||
function multiExecAsync<
|
||||
M extends RedisModules,
|
||||
F extends RedisFunctions,
|
||||
S extends RedisScripts
|
||||
>(multi: RedisClientMultiCommandType<M, F, S>): Promise<Array<RedisCommandRawReply>> {
|
||||
return new Promise((resolve, reject) => {
|
||||
(multi as any).exec((err: Error | undefined, replies: Array<RedisCommandRawReply>) => {
|
||||
if (err) return reject(err);
|
||||
@@ -439,16 +483,7 @@ describe('Client', () => {
|
||||
}
|
||||
});
|
||||
|
||||
testUtils.testWithClient('modules', async client => {
|
||||
// assert.equal(
|
||||
// await client.module.echo('message'),
|
||||
// 'message'
|
||||
// );
|
||||
}, {
|
||||
...GLOBAL.SERVERS.OPEN,
|
||||
clientOptions: {
|
||||
modules: {
|
||||
module: {
|
||||
const module = {
|
||||
echo: {
|
||||
transformArguments(message: string): Array<string> {
|
||||
return ['ECHO', message];
|
||||
@@ -457,8 +492,36 @@ describe('Client', () => {
|
||||
return reply;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
testUtils.testWithClient('modules', async client => {
|
||||
assert.equal(
|
||||
await client.module.echo('message'),
|
||||
'message'
|
||||
);
|
||||
}, {
|
||||
...GLOBAL.SERVERS.OPEN,
|
||||
clientOptions: {
|
||||
modules: {
|
||||
module
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
testUtils.testWithClient('functions', async client => {
|
||||
await loadMathFunction(client);
|
||||
|
||||
assert.equal(
|
||||
await client.math.square(2),
|
||||
4
|
||||
);
|
||||
}, {
|
||||
...GLOBAL.SERVERS.OPEN,
|
||||
minimumDockerVersion: [7, 0],
|
||||
clientOptions: {
|
||||
functions: {
|
||||
math: MATH_FUNCTION.library
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -468,9 +531,13 @@ describe('Client', () => {
|
||||
assert.ok(id !== isolatedId);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
|
||||
async function killClient<M extends RedisModules, S extends RedisScripts>(
|
||||
client: RedisClientType<M, S>,
|
||||
errorClient: RedisClientType<M, S> = client
|
||||
async function killClient<
|
||||
M extends RedisModules,
|
||||
F extends RedisFunctions,
|
||||
S extends RedisScripts
|
||||
>(
|
||||
client: RedisClientType<M, F, S>,
|
||||
errorClient: RedisClientType<M, F, S> = client
|
||||
): Promise<void> {
|
||||
const onceErrorPromise = once(errorClient, 'error');
|
||||
await client.sendCommand(['QUIT']);
|
||||
@@ -684,7 +751,9 @@ describe('Client', () => {
|
||||
|
||||
try {
|
||||
await assert.doesNotReject(Promise.all([
|
||||
subscriber.subscribe('channel', () => {}),
|
||||
subscriber.subscribe('channel', () => {
|
||||
// noop
|
||||
}),
|
||||
publisher.publish('channel', 'message')
|
||||
]));
|
||||
} finally {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import COMMANDS from './commands';
|
||||
import { RedisCommand, RedisCommandArgument, RedisCommandArguments, RedisCommandRawReply, RedisCommandReply, RedisModules, RedisPlugins, RedisScript, RedisScripts } from '../commands';
|
||||
import { RedisCommand, RedisCommandArguments, RedisCommandRawReply, RedisCommandReply, RedisFunctions, RedisModules, RedisExtensions, RedisScript, RedisScripts, RedisCommandSignature, ConvertArgumentType, RedisFunction, ExcludeMappedString } from '../commands';
|
||||
import RedisSocket, { RedisSocketOptions, RedisTlsSocketOptions } from './socket';
|
||||
import RedisCommandsQueue, { PubSubListener, PubSubSubscribeCommands, PubSubUnsubscribeCommands, QueueCommandOptions } from './commands-queue';
|
||||
import RedisClientMultiCommand, { RedisClientMultiCommandType } from './multi-command';
|
||||
@@ -9,16 +9,17 @@ import { CommandOptions, commandOptions, isCommandOptions } from '../command-opt
|
||||
import { ScanOptions, ZMember } from '../commands/generic-transformers';
|
||||
import { ScanCommandOptions } from '../commands/SCAN';
|
||||
import { HScanTuple } from '../commands/HSCAN';
|
||||
import { extendWithCommands, extendWithModulesAndScripts, transformCommandArguments, transformCommandReply, transformLegacyCommandArguments } from '../commander';
|
||||
import { attachCommands, attachExtensions, fCallArguments, transformCommandArguments, transformCommandReply, transformLegacyCommandArguments } from '../commander';
|
||||
import { Pool, Options as PoolOptions, createPool } from 'generic-pool';
|
||||
import { ClientClosedError, DisconnectsClientError } from '../errors';
|
||||
import { URL } from 'url';
|
||||
import { TcpSocketConnectOpts } from 'net';
|
||||
|
||||
export interface RedisClientOptions<
|
||||
M extends RedisModules = Record<string, never>,
|
||||
S extends RedisScripts = Record<string, never>
|
||||
> extends RedisPlugins<M, S> {
|
||||
M extends RedisModules = RedisModules,
|
||||
F extends RedisFunctions = RedisFunctions,
|
||||
S extends RedisScripts = RedisScripts
|
||||
> extends RedisExtensions<M, F, S> {
|
||||
url?: string;
|
||||
socket?: RedisSocketOptions;
|
||||
username?: string;
|
||||
@@ -32,58 +33,37 @@ export interface RedisClientOptions<
|
||||
isolationPoolOptions?: PoolOptions;
|
||||
}
|
||||
|
||||
type ConvertArgumentType<Type, ToType> =
|
||||
Type extends RedisCommandArgument ? (
|
||||
Type extends (string & ToType) ? Type : ToType
|
||||
) : (
|
||||
Type extends Set<infer Member> ? Set<ConvertArgumentType<Member, ToType>> : (
|
||||
Type extends Map<infer Key, infer Value> ? Map<Key, ConvertArgumentType<Value, ToType>> : (
|
||||
Type extends Array<infer Member> ? Array<ConvertArgumentType<Member, ToType>> : (
|
||||
Type extends Date ? Type : (
|
||||
Type extends Record<keyof any, any> ? {
|
||||
[Property in keyof Type]: ConvertArgumentType<Type[Property], ToType>
|
||||
} : Type
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
export type RedisClientCommandSignature<
|
||||
Command extends RedisCommand,
|
||||
Params extends Array<unknown> = Parameters<Command['transformArguments']>
|
||||
> = <Options extends CommandOptions<ClientCommandOptions>>(
|
||||
...args: Params | [options: Options, ...rest: Params]
|
||||
) => Promise<
|
||||
ConvertArgumentType<
|
||||
RedisCommandReply<Command>,
|
||||
Options['returnBuffers'] extends true ? Buffer : string
|
||||
>
|
||||
>;
|
||||
|
||||
type WithCommands = {
|
||||
[P in keyof typeof COMMANDS]: RedisClientCommandSignature<(typeof COMMANDS)[P]>;
|
||||
[P in keyof typeof COMMANDS]: RedisCommandSignature<(typeof COMMANDS)[P]>;
|
||||
};
|
||||
|
||||
export type ExcludeMappedString<S> = string extends S ? never : S;
|
||||
|
||||
export type WithModules<M extends RedisModules> = {
|
||||
[P in keyof M as ExcludeMappedString<P>]: {
|
||||
[C in keyof M[P] as ExcludeMappedString<C>]: RedisClientCommandSignature<M[P][C]>;
|
||||
[C in keyof M[P] as ExcludeMappedString<C>]: RedisCommandSignature<M[P][C]>;
|
||||
};
|
||||
};
|
||||
|
||||
export type WithFunctions<F extends RedisFunctions> = {
|
||||
[P in keyof F as ExcludeMappedString<P>]: {
|
||||
[FF in keyof F[P] as ExcludeMappedString<FF>]: RedisCommandSignature<F[P][FF]>;
|
||||
};
|
||||
};
|
||||
|
||||
export type WithScripts<S extends RedisScripts> = {
|
||||
[P in keyof S as ExcludeMappedString<P>]: RedisClientCommandSignature<S[P]>;
|
||||
[P in keyof S as ExcludeMappedString<P>]: RedisCommandSignature<S[P]>;
|
||||
};
|
||||
|
||||
export type RedisClientType<
|
||||
M extends RedisModules = Record<string, never>,
|
||||
F extends RedisFunctions = Record<string, never>,
|
||||
S extends RedisScripts = Record<string, never>
|
||||
> = RedisClient<M, S> & WithCommands & WithModules<M> & WithScripts<S>;
|
||||
> = RedisClient<M, F, S> & WithCommands & WithModules<M> & WithFunctions<F> & WithScripts<S>;
|
||||
|
||||
export type InstantiableRedisClient<M extends RedisModules, S extends RedisScripts> =
|
||||
new (options?: RedisClientOptions<M, S>) => RedisClientType<M, S>;
|
||||
export type InstantiableRedisClient<
|
||||
M extends RedisModules,
|
||||
F extends RedisFunctions,
|
||||
S extends RedisScripts
|
||||
> = new (options?: RedisClientOptions<M, F, S>) => RedisClientType<M, F, S>;
|
||||
|
||||
export interface ClientCommandOptions extends QueueCommandOptions {
|
||||
isolated?: boolean;
|
||||
@@ -91,30 +71,44 @@ export interface ClientCommandOptions extends QueueCommandOptions {
|
||||
|
||||
type ClientLegacyCallback = (err: Error | null, reply?: RedisCommandRawReply) => void;
|
||||
|
||||
export default class RedisClient<M extends RedisModules, S extends RedisScripts> extends EventEmitter {
|
||||
export default class RedisClient<
|
||||
M extends RedisModules,
|
||||
F extends RedisFunctions,
|
||||
S extends RedisScripts
|
||||
> extends EventEmitter {
|
||||
static commandOptions<T extends ClientCommandOptions>(options: T): CommandOptions<T> {
|
||||
return commandOptions(options);
|
||||
}
|
||||
|
||||
commandOptions = RedisClient.commandOptions;
|
||||
|
||||
static extend<M extends RedisModules, S extends RedisScripts>(plugins?: RedisPlugins<M, S>): InstantiableRedisClient<M, S> {
|
||||
const Client = <any>extendWithModulesAndScripts({
|
||||
static extend<
|
||||
M extends RedisModules,
|
||||
F extends RedisFunctions,
|
||||
S extends RedisScripts
|
||||
>(extensions?: RedisExtensions<M, F, S>): InstantiableRedisClient<M, F, S> {
|
||||
const Client = attachExtensions({
|
||||
BaseClass: RedisClient,
|
||||
modules: plugins?.modules,
|
||||
modulesCommandsExecutor: RedisClient.prototype.commandsExecutor,
|
||||
scripts: plugins?.scripts,
|
||||
scriptsExecutor: RedisClient.prototype.scriptsExecutor
|
||||
modulesExecutor: RedisClient.prototype.commandsExecutor,
|
||||
modules: extensions?.modules,
|
||||
functionsExecutor: RedisClient.prototype.functionsExecuter,
|
||||
functions: extensions?.functions,
|
||||
scriptsExecutor: RedisClient.prototype.scriptsExecuter,
|
||||
scripts: extensions?.scripts
|
||||
});
|
||||
|
||||
if (Client !== RedisClient) {
|
||||
Client.prototype.Multi = RedisClientMultiCommand.extend(plugins);
|
||||
Client.prototype.Multi = RedisClientMultiCommand.extend(extensions);
|
||||
}
|
||||
|
||||
return Client;
|
||||
}
|
||||
|
||||
static create<M extends RedisModules, S extends RedisScripts>(options?: RedisClientOptions<M, S>): RedisClientType<M, S> {
|
||||
static create<
|
||||
M extends RedisModules,
|
||||
F extends RedisFunctions,
|
||||
S extends RedisScripts
|
||||
>(options?: RedisClientOptions<M, F, S>): RedisClientType<M, F, S> {
|
||||
return new (RedisClient.extend(options))(options);
|
||||
}
|
||||
|
||||
@@ -157,14 +151,14 @@ export default class RedisClient<M extends RedisModules, S extends RedisScripts>
|
||||
return parsed;
|
||||
}
|
||||
|
||||
readonly #options?: RedisClientOptions<M, S>;
|
||||
readonly #queue: RedisCommandsQueue;
|
||||
readonly #options?: RedisClientOptions<M, F, S>;
|
||||
readonly #socket: RedisSocket;
|
||||
readonly #isolationPool: Pool<RedisClientType<M, S>>;
|
||||
readonly #queue: RedisCommandsQueue;
|
||||
readonly #isolationPool: Pool<RedisClientType<M, F, S>>;
|
||||
readonly #v4: Record<string, any> = {};
|
||||
#selectedDB = 0;
|
||||
|
||||
get options(): RedisClientOptions<M, S> | undefined {
|
||||
get options(): RedisClientOptions<M, F, S> | undefined {
|
||||
return this.#options;
|
||||
}
|
||||
|
||||
@@ -180,7 +174,7 @@ export default class RedisClient<M extends RedisModules, S extends RedisScripts>
|
||||
return this.#v4;
|
||||
}
|
||||
|
||||
constructor(options?: RedisClientOptions<M, S>) {
|
||||
constructor(options?: RedisClientOptions<M, F, S>) {
|
||||
super();
|
||||
this.#options = this.#initiateOptions(options);
|
||||
this.#queue = this.#initiateQueue();
|
||||
@@ -198,7 +192,7 @@ export default class RedisClient<M extends RedisModules, S extends RedisScripts>
|
||||
this.#legacyMode();
|
||||
}
|
||||
|
||||
#initiateOptions(options?: RedisClientOptions<M, S>): RedisClientOptions<M, S> | undefined {
|
||||
#initiateOptions(options?: RedisClientOptions<M, F, S>): RedisClientOptions<M, F, S> | undefined {
|
||||
if (options?.url) {
|
||||
const parsed = RedisClient.parseURL(options.url);
|
||||
if (options.socket) {
|
||||
@@ -350,7 +344,7 @@ export default class RedisClient<M extends RedisModules, S extends RedisScripts>
|
||||
(...args: Array<unknown>): void => (this as any).sendCommand(name, ...args);
|
||||
}
|
||||
|
||||
duplicate(overrides?: Partial<RedisClientOptions<M, S>>): RedisClientType<M, S> {
|
||||
duplicate(overrides?: Partial<RedisClientOptions<M, F, S>>): RedisClientType<M, F, S> {
|
||||
return new (Object.getPrototypeOf(this).constructor)({
|
||||
...this.#options,
|
||||
...overrides
|
||||
@@ -361,9 +355,11 @@ export default class RedisClient<M extends RedisModules, S extends RedisScripts>
|
||||
await this.#socket.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(redisArgs, options),
|
||||
@@ -371,12 +367,18 @@ export default class RedisClient<M extends RedisModules, S extends RedisScripts>
|
||||
);
|
||||
}
|
||||
|
||||
sendCommand<T = RedisCommandRawReply>(args: RedisCommandArguments, options?: ClientCommandOptions): Promise<T> {
|
||||
sendCommand<T = RedisCommandRawReply>(
|
||||
args: RedisCommandArguments,
|
||||
options?: ClientCommandOptions
|
||||
): Promise<T> {
|
||||
return this.#sendCommand(args, options);
|
||||
}
|
||||
|
||||
// using `#sendCommand` cause `sendCommand` is overwritten in legacy mode
|
||||
#sendCommand<T = RedisCommandRawReply>(args: RedisCommandArguments, options?: ClientCommandOptions): Promise<T> {
|
||||
#sendCommand<T = RedisCommandRawReply>(
|
||||
args: RedisCommandArguments,
|
||||
options?: ClientCommandOptions
|
||||
): Promise<T> {
|
||||
if (!this.#socket.isOpen) {
|
||||
return Promise.reject(new ClientClosedError());
|
||||
}
|
||||
@@ -395,9 +397,34 @@ export default class RedisClient<M extends RedisModules, S extends RedisScripts>
|
||||
return promise;
|
||||
}
|
||||
|
||||
async scriptsExecutor(script: RedisScript, args: Array<unknown>): Promise<RedisCommandReply<typeof script>> {
|
||||
const { args: redisArgs, options } = transformCommandArguments<ClientCommandOptions>(script, args);
|
||||
async functionsExecuter<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, redisArgs, options),
|
||||
redisArgs.preserve
|
||||
);
|
||||
}
|
||||
|
||||
executeFunction(
|
||||
fn: RedisFunction,
|
||||
args: RedisCommandArguments,
|
||||
options?: ClientCommandOptions
|
||||
): Promise<RedisCommandRawReply> {
|
||||
return this.#sendCommand(
|
||||
fCallArguments(fn, args),
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
async scriptsExecuter<S extends RedisScript>(
|
||||
script: S,
|
||||
args: Array<unknown>
|
||||
): Promise<RedisCommandReply<S>> {
|
||||
const { args: redisArgs, options } = transformCommandArguments(script, args);
|
||||
return transformCommandReply(
|
||||
script,
|
||||
await this.executeScript(script, redisArgs, options),
|
||||
@@ -405,25 +432,29 @@ export default class RedisClient<M extends RedisModules, S extends RedisScripts>
|
||||
);
|
||||
}
|
||||
|
||||
async executeScript(script: RedisScript, args: RedisCommandArguments, options?: ClientCommandOptions): Promise<RedisCommandReply<typeof script>> {
|
||||
async executeScript(
|
||||
script: RedisScript,
|
||||
args: RedisCommandArguments,
|
||||
options?: ClientCommandOptions
|
||||
): Promise<RedisCommandRawReply> {
|
||||
const redisArgs: RedisCommandArguments = ['EVALSHA', script.SHA1];
|
||||
|
||||
if (script.NUMBER_OF_KEYS !== undefined) {
|
||||
redisArgs.push(script.NUMBER_OF_KEYS.toString());
|
||||
}
|
||||
|
||||
redisArgs.push(...args);
|
||||
|
||||
try {
|
||||
return await this.#sendCommand([
|
||||
'EVALSHA',
|
||||
script.SHA1,
|
||||
script.NUMBER_OF_KEYS.toString(),
|
||||
...args
|
||||
], options);
|
||||
return await this.#sendCommand(redisArgs, options);
|
||||
} catch (err: any) {
|
||||
if (!err?.message?.startsWith?.('NOSCRIPT')) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
return await this.#sendCommand([
|
||||
'EVAL',
|
||||
script.SCRIPT,
|
||||
script.NUMBER_OF_KEYS.toString(),
|
||||
...args
|
||||
], options);
|
||||
redisArgs[0] = 'EVAL';
|
||||
redisArgs[1] = script.SCRIPT;
|
||||
return this.#sendCommand(redisArgs, options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -558,11 +589,11 @@ export default class RedisClient<M extends RedisModules, S extends RedisScripts>
|
||||
}
|
||||
}
|
||||
|
||||
executeIsolated<T>(fn: (client: RedisClientType<M, S>) => T | Promise<T>): Promise<T> {
|
||||
executeIsolated<T>(fn: (client: RedisClientType<M, F, S>) => T | Promise<T>): Promise<T> {
|
||||
return this.#isolationPool.use(fn);
|
||||
}
|
||||
|
||||
multi(): RedisClientMultiCommandType<M, S> {
|
||||
multi(): RedisClientMultiCommandType<M, F, S> {
|
||||
return new (this as any).Multi(
|
||||
this.multiExecutor.bind(this),
|
||||
this.#options?.legacyMode
|
||||
@@ -639,7 +670,7 @@ export default class RedisClient<M extends RedisModules, S extends RedisScripts>
|
||||
}
|
||||
}
|
||||
|
||||
extendWithCommands({
|
||||
attachCommands({
|
||||
BaseClass: RedisClient,
|
||||
commands: COMMANDS,
|
||||
executor: RedisClient.prototype.commandsExecutor
|
||||
|
@@ -1,28 +1,63 @@
|
||||
import COMMANDS from './commands';
|
||||
import { RedisCommand, RedisCommandArguments, RedisCommandRawReply, RedisModules, RedisPlugins, RedisScript, RedisScripts } from '../commands';
|
||||
import { RedisCommand, RedisCommandArguments, RedisCommandRawReply, RedisFunctions, RedisModules, RedisExtensions, RedisScript, RedisScripts, ExcludeMappedString, RedisFunction } from '../commands';
|
||||
import RedisMultiCommand, { RedisMultiQueuedCommand } from '../multi-command';
|
||||
import { extendWithCommands, extendWithModulesAndScripts, transformLegacyCommandArguments } from '../commander';
|
||||
import { ExcludeMappedString } from '.';
|
||||
import { attachCommands, attachExtensions, transformLegacyCommandArguments } from '../commander';
|
||||
|
||||
type RedisClientMultiCommandSignature<C extends RedisCommand, M extends RedisModules, S extends RedisScripts> =
|
||||
(...args: Parameters<C['transformArguments']>) => RedisClientMultiCommandType<M, S>;
|
||||
type CommandSignature<
|
||||
C extends RedisCommand,
|
||||
M extends RedisModules,
|
||||
F extends RedisFunctions,
|
||||
S extends RedisScripts
|
||||
> = (...args: Parameters<C['transformArguments']>) => RedisClientMultiCommandType<M, F, S>;
|
||||
|
||||
type WithCommands<M extends RedisModules, S extends RedisScripts> = {
|
||||
[P in keyof typeof COMMANDS]: RedisClientMultiCommandSignature<(typeof COMMANDS)[P], M, S>;
|
||||
type WithCommands<
|
||||
M extends RedisModules,
|
||||
F extends RedisFunctions,
|
||||
S extends RedisScripts
|
||||
> = {
|
||||
[P in keyof typeof COMMANDS]: CommandSignature<(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>]: RedisClientMultiCommandSignature<M[P][C], M, S>;
|
||||
[C in keyof M[P] as ExcludeMappedString<C>]: CommandSignature<M[P][C], M, F, S>;
|
||||
};
|
||||
};
|
||||
|
||||
type WithScripts<M extends RedisModules, S extends RedisScripts> = {
|
||||
[P in keyof S as ExcludeMappedString<P>]: RedisClientMultiCommandSignature<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>]: CommandSignature<F[P][FF], M, F, S>;
|
||||
};
|
||||
};
|
||||
|
||||
export type RedisClientMultiCommandType<M extends RedisModules, S extends RedisScripts> =
|
||||
RedisClientMultiCommand & 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>]: CommandSignature<S[P], M, F, S>;
|
||||
};
|
||||
|
||||
export type RedisClientMultiCommandType<
|
||||
M extends RedisModules,
|
||||
F extends RedisFunctions,
|
||||
S extends RedisScripts
|
||||
> = RedisClientMultiCommand & WithCommands<M, F, S> & WithModules<M, F, S> & WithFunctions<M, F, S> & WithScripts<M, F, S>;
|
||||
|
||||
type InstantiableRedisMultiCommand<
|
||||
M extends RedisModules,
|
||||
F extends RedisFunctions,
|
||||
S extends RedisScripts
|
||||
> = new (...args: ConstructorParameters<typeof RedisClientMultiCommand>) => RedisClientMultiCommandType<M, F, S>;
|
||||
|
||||
|
||||
export type RedisClientMultiExecutor = (queue: Array<RedisMultiQueuedCommand>, chainId?: symbol) => Promise<Array<RedisCommandRawReply>>;
|
||||
|
||||
@@ -30,15 +65,19 @@ export default class RedisClientMultiCommand {
|
||||
readonly #multi = new RedisMultiCommand();
|
||||
readonly #executor: RedisClientMultiExecutor;
|
||||
|
||||
static extend<M extends RedisModules, S extends RedisScripts>(
|
||||
plugins?: RedisPlugins<M, S>
|
||||
): new (...args: ConstructorParameters<typeof RedisMultiCommand>) => RedisClientMultiCommandType<M, S> {
|
||||
return <any>extendWithModulesAndScripts({
|
||||
static extend<
|
||||
M extends RedisModules,
|
||||
F extends RedisFunctions,
|
||||
S extends RedisScripts
|
||||
>(extensions?: RedisExtensions<M, F, S>): InstantiableRedisMultiCommand<M, F, S> {
|
||||
return attachExtensions({
|
||||
BaseClass: RedisClientMultiCommand,
|
||||
modules: plugins?.modules,
|
||||
modulesCommandsExecutor: RedisClientMultiCommand.prototype.commandsExecutor,
|
||||
scripts: plugins?.scripts,
|
||||
scriptsExecutor: RedisClientMultiCommand.prototype.scriptsExecutor
|
||||
modulesExecutor: RedisClientMultiCommand.prototype.commandsExecutor,
|
||||
modules: extensions?.modules,
|
||||
functionsExecutor: RedisClientMultiCommand.prototype.functionsExecutor,
|
||||
functions: extensions?.functions,
|
||||
scriptsExecutor: RedisClientMultiCommand.prototype.scriptsExecutor,
|
||||
scripts: extensions?.scripts
|
||||
});
|
||||
}
|
||||
|
||||
@@ -102,6 +141,11 @@ export default class RedisClientMultiCommand {
|
||||
return this;
|
||||
}
|
||||
|
||||
functionsExecutor(fn: RedisFunction, args: Array<unknown>): this {
|
||||
this.#multi.addFunction(fn, args);
|
||||
return this;
|
||||
}
|
||||
|
||||
scriptsExecutor(script: RedisScript, args: Array<unknown>): this {
|
||||
this.#multi.addScript(script, args);
|
||||
return this;
|
||||
@@ -123,15 +167,13 @@ export default class RedisClientMultiCommand {
|
||||
EXEC = this.exec;
|
||||
|
||||
async execAsPipeline(): Promise<Array<RedisCommandRawReply>> {
|
||||
if (!this.#multi.queue.length) return [];
|
||||
|
||||
return this.#multi.transformReplies(
|
||||
await this.#executor(this.#multi.queue)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extendWithCommands({
|
||||
attachCommands({
|
||||
BaseClass: RedisClientMultiCommand,
|
||||
commands: COMMANDS,
|
||||
executor: RedisClientMultiCommand.prototype.commandsExecutor
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
this.#multi.addCommand(args, transformReply);
|
||||
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
|
||||
|
@@ -1,16 +1,23 @@
|
||||
|
||||
import { CommandOptions, isCommandOptions } from './command-options';
|
||||
import { RedisCommand, RedisCommandArguments, RedisCommandRawReply, RedisCommandReply, RedisCommands, RedisModules, RedisScript, RedisScripts } from './commands';
|
||||
import { RedisCommand, RedisCommandArguments, RedisCommandReply, RedisFunction, RedisFunctions, RedisModules, RedisScript, RedisScripts } from './commands';
|
||||
|
||||
type Instantiable<T = any> = new (...args: Array<any>) => T;
|
||||
|
||||
interface ExtendWithCommandsConfig<T extends Instantiable> {
|
||||
BaseClass: T;
|
||||
commands: RedisCommands;
|
||||
executor(command: RedisCommand, args: Array<unknown>): unknown;
|
||||
type CommandsExecutor<C extends RedisCommand = RedisCommand> =
|
||||
(command: C, args: Array<unknown>) => unknown;
|
||||
|
||||
interface AttachCommandsConfig<C extends RedisCommand> {
|
||||
BaseClass: Instantiable;
|
||||
commands: Record<string, C>;
|
||||
executor: CommandsExecutor<C>;
|
||||
}
|
||||
|
||||
export function extendWithCommands<T extends Instantiable>({ BaseClass, commands, executor }: ExtendWithCommandsConfig<T>): void {
|
||||
export function attachCommands<C extends RedisCommand>({
|
||||
BaseClass,
|
||||
commands,
|
||||
executor
|
||||
}: AttachCommandsConfig<C>): void {
|
||||
for (const [name, command] of Object.entries(commands)) {
|
||||
BaseClass.prototype[name] = function (...args: Array<unknown>): unknown {
|
||||
return executor.call(this, command, args);
|
||||
@@ -18,56 +25,82 @@ export function extendWithCommands<T extends Instantiable>({ BaseClass, commands
|
||||
}
|
||||
}
|
||||
|
||||
interface ExtendWithModulesAndScriptsConfig<T extends Instantiable> {
|
||||
interface AttachExtensionsConfig<T extends Instantiable = Instantiable> {
|
||||
BaseClass: T;
|
||||
modulesExecutor: CommandsExecutor;
|
||||
modules?: RedisModules;
|
||||
modulesCommandsExecutor(this: InstanceType<T>, command: RedisCommand, args: Array<unknown>): unknown;
|
||||
functionsExecutor: CommandsExecutor<RedisFunction>;
|
||||
functions?: RedisFunctions;
|
||||
scriptsExecutor: CommandsExecutor<RedisScript>;
|
||||
scripts?: RedisScripts;
|
||||
scriptsExecutor(this: InstanceType<T>, script: RedisScript, args: Array<unknown>): unknown;
|
||||
}
|
||||
|
||||
export function extendWithModulesAndScripts<T extends Instantiable>(config: ExtendWithModulesAndScriptsConfig<T>): T {
|
||||
let Commander: T | undefined;
|
||||
export function attachExtensions(config: AttachExtensionsConfig): any {
|
||||
let Commander;
|
||||
|
||||
if (config.modules) {
|
||||
Commander = class extends config.BaseClass {
|
||||
constructor(...args: Array<any>) {
|
||||
super(...args);
|
||||
Commander = attachWithNamespaces({
|
||||
BaseClass: config.BaseClass,
|
||||
namespaces: config.modules,
|
||||
executor: config.modulesExecutor
|
||||
});
|
||||
}
|
||||
|
||||
for (const module of Object.keys(config.modules!)) {
|
||||
this[module] = new this[module](this);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (const [moduleName, module] of Object.entries(config.modules)) {
|
||||
Commander.prototype[moduleName] = class {
|
||||
readonly self: T;
|
||||
|
||||
constructor(self: InstanceType<T>) {
|
||||
this.self = self;
|
||||
}
|
||||
};
|
||||
|
||||
for (const [commandName, command] of Object.entries(module)) {
|
||||
Commander.prototype[moduleName].prototype[commandName] = function (...args: Array<unknown>): unknown {
|
||||
return config.modulesCommandsExecutor.call(this.self, command, args);
|
||||
};
|
||||
}
|
||||
}
|
||||
if (config.functions) {
|
||||
Commander = attachWithNamespaces({
|
||||
BaseClass: Commander ?? config.BaseClass,
|
||||
namespaces: config.functions,
|
||||
executor: config.functionsExecutor
|
||||
});
|
||||
}
|
||||
|
||||
if (config.scripts) {
|
||||
Commander ??= class extends config.BaseClass {};
|
||||
attachCommands({
|
||||
BaseClass: Commander,
|
||||
commands: config.scripts,
|
||||
executor: config.scriptsExecutor
|
||||
});
|
||||
}
|
||||
|
||||
for (const [name, script] of Object.entries(config.scripts)) {
|
||||
Commander.prototype[name] = function (...args: Array<unknown>): unknown {
|
||||
return config.scriptsExecutor.call(this, script, args);
|
||||
return Commander ?? config.BaseClass;
|
||||
}
|
||||
|
||||
interface AttachWithNamespacesConfig<C extends RedisCommand> {
|
||||
BaseClass: Instantiable;
|
||||
namespaces: Record<string, Record<string, C>>;
|
||||
executor: CommandsExecutor<C>;
|
||||
}
|
||||
|
||||
function attachWithNamespaces<C extends RedisCommand>({
|
||||
BaseClass,
|
||||
namespaces,
|
||||
executor
|
||||
}: AttachWithNamespacesConfig<C>): any {
|
||||
const Commander = class extends BaseClass {
|
||||
constructor(...args: Array<any>) {
|
||||
super(...args);
|
||||
|
||||
for (const namespace of Object.keys(namespaces)) {
|
||||
this[namespace] = Object.create(this[namespace], {
|
||||
self: {
|
||||
value: this
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (const [namespace, commands] of Object.entries(namespaces)) {
|
||||
Commander.prototype[namespace] = {};
|
||||
for (const [name, command] of Object.entries(commands)) {
|
||||
Commander.prototype[namespace][name] = function (...args: Array<unknown>): unknown {
|
||||
return executor.call(this.self, command, args);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return (Commander ?? config.BaseClass) as any;
|
||||
return Commander;
|
||||
}
|
||||
|
||||
export function transformCommandArguments<T>(
|
||||
@@ -93,14 +126,29 @@ export function transformLegacyCommandArguments(args: Array<any>): Array<any> {
|
||||
return args.flat().map(x => x?.toString?.());
|
||||
}
|
||||
|
||||
export function transformCommandReply(
|
||||
command: RedisCommand,
|
||||
rawReply: RedisCommandRawReply,
|
||||
export function transformCommandReply<C extends RedisCommand>(
|
||||
command: C,
|
||||
rawReply: unknown,
|
||||
preserved: unknown
|
||||
): RedisCommandReply<typeof command> {
|
||||
): RedisCommandReply<C> {
|
||||
if (!command.transformReply) {
|
||||
return rawReply;
|
||||
return rawReply as RedisCommandReply<C>;
|
||||
}
|
||||
|
||||
return command.transformReply(rawReply, preserved);
|
||||
}
|
||||
|
||||
export function fCallArguments(fn: RedisFunction, args: RedisCommandArguments): RedisCommandArguments {
|
||||
const actualArgs: RedisCommandArguments = [
|
||||
fn.IS_READ_ONLY ? 'FCALL_RO' : 'FCALL',
|
||||
fn.NAME
|
||||
];
|
||||
|
||||
if (fn.NUMBER_OF_KEYS !== undefined) {
|
||||
actualArgs.push(fn.NUMBER_OF_KEYS.toString());
|
||||
}
|
||||
|
||||
actualArgs.push(...args);
|
||||
|
||||
return actualArgs;
|
||||
}
|
||||
|
@@ -1,4 +1,6 @@
|
||||
import { EvalOptions, pushEvalArguments } from './generic-transformers';
|
||||
import { evalFirstKeyIndex, EvalOptions, pushEvalArguments } from './generic-transformers';
|
||||
|
||||
export const FIRST_KEY_INDEX = evalFirstKeyIndex;
|
||||
|
||||
export function transformArguments(script: string, options?: EvalOptions): Array<string> {
|
||||
return pushEvalArguments(['EVAL', script], options);
|
||||
|
@@ -1,4 +1,6 @@
|
||||
import { EvalOptions, pushEvalArguments } from './generic-transformers';
|
||||
import { evalFirstKeyIndex, EvalOptions, pushEvalArguments } from './generic-transformers';
|
||||
|
||||
export const FIRST_KEY_INDEX = evalFirstKeyIndex;
|
||||
|
||||
export function transformArguments(sha1: string, options?: EvalOptions): Array<string> {
|
||||
return pushEvalArguments(['EVALSHA', sha1], options);
|
||||
|
17
packages/client/lib/commands/EVALSHA_RO.spec.ts
Normal file
17
packages/client/lib/commands/EVALSHA_RO.spec.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import testUtils from '../test-utils';
|
||||
import { transformArguments } from './EVALSHA_RO';
|
||||
|
||||
describe('EVALSHA_RO', () => {
|
||||
testUtils.isVersionGreaterThanHook([7, 0]);
|
||||
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('sha1', {
|
||||
keys: ['key'],
|
||||
arguments: ['argument']
|
||||
}),
|
||||
['EVALSHA_RO', 'sha1', '1', 'key', 'argument']
|
||||
);
|
||||
});
|
||||
});
|
9
packages/client/lib/commands/EVALSHA_RO.ts
Normal file
9
packages/client/lib/commands/EVALSHA_RO.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { evalFirstKeyIndex, EvalOptions, pushEvalArguments } from './generic-transformers';
|
||||
|
||||
export const FIRST_KEY_INDEX = evalFirstKeyIndex;
|
||||
|
||||
export const IS_READ_ONLY = true;
|
||||
|
||||
export function transformArguments(sha1: string, options?: EvalOptions): Array<string> {
|
||||
return pushEvalArguments(['EVALSHA_RO', sha1], options);
|
||||
}
|
31
packages/client/lib/commands/EVAL_RO.spec.ts
Normal file
31
packages/client/lib/commands/EVAL_RO.spec.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './EVAL_RO';
|
||||
|
||||
describe('EVAL_RO', () => {
|
||||
testUtils.isVersionGreaterThanHook([7, 0]);
|
||||
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('return KEYS[1] + ARGV[1]', {
|
||||
keys: ['key'],
|
||||
arguments: ['argument']
|
||||
}),
|
||||
['EVAL_RO', 'return KEYS[1] + ARGV[1]', '1', 'key', 'argument']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.evalRo', async client => {
|
||||
assert.equal(
|
||||
await client.evalRo('return 1'),
|
||||
1
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
|
||||
testUtils.testWithCluster('cluster.evalRo', async cluster => {
|
||||
assert.equal(
|
||||
await cluster.evalRo('return 1'),
|
||||
1
|
||||
);
|
||||
}, GLOBAL.CLUSTERS.OPEN);
|
||||
});
|
9
packages/client/lib/commands/EVAL_RO.ts
Normal file
9
packages/client/lib/commands/EVAL_RO.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { evalFirstKeyIndex, EvalOptions, pushEvalArguments } from './generic-transformers';
|
||||
|
||||
export const FIRST_KEY_INDEX = evalFirstKeyIndex;
|
||||
|
||||
export const IS_READ_ONLY = true;
|
||||
|
||||
export function transformArguments(script: string, options?: EvalOptions): Array<string> {
|
||||
return pushEvalArguments(['EVAL_RO', script], options);
|
||||
}
|
29
packages/client/lib/commands/FCALL.spec.ts
Normal file
29
packages/client/lib/commands/FCALL.spec.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { MATH_FUNCTION, loadMathFunction } from '../client/index.spec';
|
||||
import { transformArguments } from './FCALL';
|
||||
|
||||
describe('FCALL', () => {
|
||||
testUtils.isVersionGreaterThanHook([7, 0]);
|
||||
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('function', {
|
||||
keys: ['key'],
|
||||
arguments: ['argument']
|
||||
}),
|
||||
['FCALL', 'function', '1', 'key', 'argument']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.fCall', async client => {
|
||||
await loadMathFunction(client);
|
||||
|
||||
assert.equal(
|
||||
await client.fCall(MATH_FUNCTION.library.square.NAME, {
|
||||
arguments: ['2']
|
||||
}),
|
||||
4
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
7
packages/client/lib/commands/FCALL.ts
Normal file
7
packages/client/lib/commands/FCALL.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { evalFirstKeyIndex, EvalOptions, pushEvalArguments } from './generic-transformers';
|
||||
|
||||
export const FIRST_KEY_INDEX = evalFirstKeyIndex;
|
||||
|
||||
export function transformArguments(fn: string, options?: EvalOptions): Array<string> {
|
||||
return pushEvalArguments(['FCALL', fn], options);
|
||||
}
|
29
packages/client/lib/commands/FCALL_RO.spec.ts
Normal file
29
packages/client/lib/commands/FCALL_RO.spec.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { MATH_FUNCTION, loadMathFunction } from '../client/index.spec';
|
||||
import { transformArguments } from './FCALL_RO';
|
||||
|
||||
describe('FCALL_RO', () => {
|
||||
testUtils.isVersionGreaterThanHook([7, 0]);
|
||||
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('function', {
|
||||
keys: ['key'],
|
||||
arguments: ['argument']
|
||||
}),
|
||||
['FCALL_RO', 'function', '1', 'key', 'argument']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.fCallRo', async client => {
|
||||
await loadMathFunction(client);
|
||||
|
||||
assert.equal(
|
||||
await client.fCallRo(MATH_FUNCTION.library.square.NAME, {
|
||||
arguments: ['2']
|
||||
}),
|
||||
4
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
9
packages/client/lib/commands/FCALL_RO.ts
Normal file
9
packages/client/lib/commands/FCALL_RO.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { evalFirstKeyIndex, EvalOptions, pushEvalArguments } from './generic-transformers';
|
||||
|
||||
export const FIRST_KEY_INDEX = evalFirstKeyIndex;
|
||||
|
||||
export const IS_READ_ONLY = true;
|
||||
|
||||
export function transformArguments(fn: string, options?: EvalOptions): Array<string> {
|
||||
return pushEvalArguments(['FCALL_RO', fn], options);
|
||||
}
|
24
packages/client/lib/commands/FUNCTION_DELETE.spec.ts
Normal file
24
packages/client/lib/commands/FUNCTION_DELETE.spec.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import { MATH_FUNCTION, loadMathFunction } from '../client/index.spec';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './FUNCTION_DELETE';
|
||||
|
||||
describe('FUNCTION DELETE', () => {
|
||||
testUtils.isVersionGreaterThanHook([7, 0]);
|
||||
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('library'),
|
||||
['FUNCTION', 'DELETE', 'library']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.functionDelete', async client => {
|
||||
await loadMathFunction(client);
|
||||
|
||||
assert.equal(
|
||||
await client.functionDelete(MATH_FUNCTION.name),
|
||||
'OK'
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
7
packages/client/lib/commands/FUNCTION_DELETE.ts
Normal file
7
packages/client/lib/commands/FUNCTION_DELETE.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { RedisCommandArguments } from '.';
|
||||
|
||||
export function transformArguments(library: string): RedisCommandArguments {
|
||||
return ['FUNCTION', 'DELETE', library];
|
||||
}
|
||||
|
||||
export declare function transformReply(): 'OK';
|
21
packages/client/lib/commands/FUNCTION_DUMP.spec.ts
Normal file
21
packages/client/lib/commands/FUNCTION_DUMP.spec.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './FUNCTION_DUMP';
|
||||
|
||||
describe('FUNCTION DUMP', () => {
|
||||
testUtils.isVersionGreaterThanHook([7, 0]);
|
||||
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments(),
|
||||
['FUNCTION', 'DUMP']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.functionDump', async client => {
|
||||
assert.equal(
|
||||
typeof await client.functionDump(),
|
||||
'string'
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
7
packages/client/lib/commands/FUNCTION_DUMP.ts
Normal file
7
packages/client/lib/commands/FUNCTION_DUMP.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { RedisCommandArgument, RedisCommandArguments } from '.';
|
||||
|
||||
export function transformArguments(): RedisCommandArguments {
|
||||
return ['FUNCTION', 'DUMP'];
|
||||
}
|
||||
|
||||
export declare function transformReply(): RedisCommandArgument;
|
30
packages/client/lib/commands/FUNCTION_FLUSH.spec.ts
Normal file
30
packages/client/lib/commands/FUNCTION_FLUSH.spec.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './FUNCTION_FLUSH';
|
||||
|
||||
describe('FUNCTION FLUSH', () => {
|
||||
testUtils.isVersionGreaterThanHook([7, 0]);
|
||||
|
||||
describe('transformArguments', () => {
|
||||
it('simple', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments(),
|
||||
['FUNCTION', 'FLUSH']
|
||||
);
|
||||
});
|
||||
|
||||
it('with mode', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('SYNC'),
|
||||
['FUNCTION', 'FLUSH', 'SYNC']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.functionFlush', async client => {
|
||||
assert.equal(
|
||||
await client.functionFlush(),
|
||||
'OK'
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
13
packages/client/lib/commands/FUNCTION_FLUSH.ts
Normal file
13
packages/client/lib/commands/FUNCTION_FLUSH.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { RedisCommandArguments } from '.';
|
||||
|
||||
export function transformArguments(mode?: 'ASYNC' | 'SYNC'): RedisCommandArguments {
|
||||
const args = ['FUNCTION', 'FLUSH'];
|
||||
|
||||
if (mode) {
|
||||
args.push(mode);
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
export declare function transformReply(): 'OK';
|
14
packages/client/lib/commands/FUNCTION_KILL.spec.ts
Normal file
14
packages/client/lib/commands/FUNCTION_KILL.spec.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import testUtils from '../test-utils';
|
||||
import { transformArguments } from './FUNCTION_KILL';
|
||||
|
||||
describe('FUNCTION KILL', () => {
|
||||
testUtils.isVersionGreaterThanHook([7, 0]);
|
||||
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments(),
|
||||
['FUNCTION', 'KILL']
|
||||
);
|
||||
});
|
||||
});
|
7
packages/client/lib/commands/FUNCTION_KILL.ts
Normal file
7
packages/client/lib/commands/FUNCTION_KILL.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { RedisCommandArguments } from '.';
|
||||
|
||||
export function transformArguments(): RedisCommandArguments {
|
||||
return ['FUNCTION', 'KILL'];
|
||||
}
|
||||
|
||||
export declare function transformReply(): 'OK';
|
41
packages/client/lib/commands/FUNCTION_LIST.spec.ts
Normal file
41
packages/client/lib/commands/FUNCTION_LIST.spec.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { MATH_FUNCTION, loadMathFunction } from '../client/index.spec';
|
||||
import { transformArguments } from './FUNCTION_LIST';
|
||||
|
||||
describe('FUNCTION LIST', () => {
|
||||
testUtils.isVersionGreaterThanHook([7, 0]);
|
||||
|
||||
describe('transformArguments', () => {
|
||||
it('simple', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments(),
|
||||
['FUNCTION', 'LIST']
|
||||
);
|
||||
});
|
||||
|
||||
it('with pattern', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('patter*'),
|
||||
['FUNCTION', 'LIST', 'patter*']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.functionList', async client => {
|
||||
await loadMathFunction(client);
|
||||
|
||||
assert.deepEqual(
|
||||
await client.functionList(),
|
||||
[{
|
||||
libraryName: MATH_FUNCTION.name,
|
||||
engine: MATH_FUNCTION.engine,
|
||||
functions: [{
|
||||
name: MATH_FUNCTION.library.square.NAME,
|
||||
description: null,
|
||||
flags: ['no-writes']
|
||||
}]
|
||||
}]
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
16
packages/client/lib/commands/FUNCTION_LIST.ts
Normal file
16
packages/client/lib/commands/FUNCTION_LIST.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { RedisCommandArguments } from '.';
|
||||
import { FunctionListItemReply, FunctionListRawItemReply, transformFunctionListItemReply } from './generic-transformers';
|
||||
|
||||
export function transformArguments(pattern?: string): RedisCommandArguments {
|
||||
const args = ['FUNCTION', 'LIST'];
|
||||
|
||||
if (pattern) {
|
||||
args.push(pattern);
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
export function transformReply(reply: Array<FunctionListRawItemReply>): Array<FunctionListItemReply> {
|
||||
return reply.map(transformFunctionListItemReply);
|
||||
}
|
42
packages/client/lib/commands/FUNCTION_LIST_WITHCODE.spec.ts
Normal file
42
packages/client/lib/commands/FUNCTION_LIST_WITHCODE.spec.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { MATH_FUNCTION, loadMathFunction } from '../client/index.spec';
|
||||
import { transformArguments } from './FUNCTION_LIST_WITHCODE';
|
||||
|
||||
describe('FUNCTION LIST WITHCODE', () => {
|
||||
testUtils.isVersionGreaterThanHook([7, 0]);
|
||||
|
||||
describe('transformArguments', () => {
|
||||
it('simple', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments(),
|
||||
['FUNCTION', 'LIST', 'WITHCODE']
|
||||
);
|
||||
});
|
||||
|
||||
it('with pattern', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('patter*'),
|
||||
['FUNCTION', 'LIST', 'patter*', 'WITHCODE']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.functionListWithCode', async client => {
|
||||
await loadMathFunction(client);
|
||||
|
||||
assert.deepEqual(
|
||||
await client.functionListWithCode(),
|
||||
[{
|
||||
libraryName: MATH_FUNCTION.name,
|
||||
engine: MATH_FUNCTION.engine,
|
||||
functions: [{
|
||||
name: MATH_FUNCTION.library.square.NAME,
|
||||
description: null,
|
||||
flags: ['no-writes']
|
||||
}],
|
||||
libraryCode: MATH_FUNCTION.code
|
||||
}]
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
26
packages/client/lib/commands/FUNCTION_LIST_WITHCODE.ts
Normal file
26
packages/client/lib/commands/FUNCTION_LIST_WITHCODE.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { RedisCommandArguments } from '.';
|
||||
import { transformArguments as transformFunctionListArguments } from './FUNCTION_LIST';
|
||||
import { FunctionListItemReply, FunctionListRawItemReply, transformFunctionListItemReply } from './generic-transformers';
|
||||
|
||||
export function transformArguments(pattern?: string): RedisCommandArguments {
|
||||
const args = transformFunctionListArguments(pattern);
|
||||
args.push('WITHCODE');
|
||||
return args;
|
||||
}
|
||||
|
||||
type FunctionListWithCodeRawItemReply = [
|
||||
...FunctionListRawItemReply,
|
||||
'library_code',
|
||||
string
|
||||
];
|
||||
|
||||
interface FunctionListWithCodeItemReply extends FunctionListItemReply {
|
||||
libraryCode: string;
|
||||
}
|
||||
|
||||
export function transformReply(reply: Array<FunctionListWithCodeRawItemReply>): Array<FunctionListWithCodeItemReply> {
|
||||
return reply.map(library => ({
|
||||
...transformFunctionListItemReply(library as unknown as FunctionListRawItemReply),
|
||||
libraryCode: library[7]
|
||||
}));
|
||||
}
|
36
packages/client/lib/commands/FUNCTION_LOAD.spec.ts
Normal file
36
packages/client/lib/commands/FUNCTION_LOAD.spec.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { MATH_FUNCTION } from '../client/index.spec';
|
||||
import { transformArguments } from './FUNCTION_LOAD';
|
||||
|
||||
describe('FUNCTION LOAD', () => {
|
||||
testUtils.isVersionGreaterThanHook([7, 0]);
|
||||
|
||||
describe('transformArguments', () => {
|
||||
it('simple', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments( 'code'),
|
||||
['FUNCTION', 'LOAD', 'code']
|
||||
);
|
||||
});
|
||||
|
||||
it('with REPLACE', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('code', {
|
||||
REPLACE: true
|
||||
}),
|
||||
['FUNCTION', 'LOAD', 'REPLACE', 'code']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.functionLoad', async client => {
|
||||
assert.equal(
|
||||
await client.functionLoad(
|
||||
MATH_FUNCTION.code,
|
||||
{ REPLACE: true }
|
||||
),
|
||||
MATH_FUNCTION.name
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
22
packages/client/lib/commands/FUNCTION_LOAD.ts
Normal file
22
packages/client/lib/commands/FUNCTION_LOAD.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { RedisCommandArguments } from '.';
|
||||
|
||||
interface FunctionLoadOptions {
|
||||
REPLACE?: boolean;
|
||||
}
|
||||
|
||||
export function transformArguments(
|
||||
code: string,
|
||||
options?: FunctionLoadOptions
|
||||
): RedisCommandArguments {
|
||||
const args = ['FUNCTION', 'LOAD'];
|
||||
|
||||
if (options?.REPLACE) {
|
||||
args.push('REPLACE');
|
||||
}
|
||||
|
||||
args.push(code);
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
export declare function transformReply(): string;
|
37
packages/client/lib/commands/FUNCTION_RESTORE.spec.ts
Normal file
37
packages/client/lib/commands/FUNCTION_RESTORE.spec.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './FUNCTION_RESTORE';
|
||||
|
||||
describe('FUNCTION RESTORE', () => {
|
||||
testUtils.isVersionGreaterThanHook([7, 0]);
|
||||
|
||||
describe('transformArguments', () => {
|
||||
it('simple', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('dump'),
|
||||
['FUNCTION', 'RESTORE', 'dump']
|
||||
);
|
||||
});
|
||||
|
||||
it('with mode', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments('dump', 'APPEND'),
|
||||
['FUNCTION', 'RESTORE', 'dump', 'APPEND']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.functionRestore', async client => {
|
||||
assert.equal(
|
||||
await client.functionRestore(
|
||||
await client.functionDump(
|
||||
client.commandOptions({
|
||||
returnBuffers: true
|
||||
})
|
||||
),
|
||||
'FLUSH'
|
||||
),
|
||||
'OK'
|
||||
);
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
16
packages/client/lib/commands/FUNCTION_RESTORE.ts
Normal file
16
packages/client/lib/commands/FUNCTION_RESTORE.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { RedisCommandArgument, RedisCommandArguments } from '.';
|
||||
|
||||
export function transformArguments(
|
||||
dump: RedisCommandArgument,
|
||||
mode?: 'FLUSH' | 'APPEND' | 'REPLACE'
|
||||
): RedisCommandArguments {
|
||||
const args = ['FUNCTION', 'RESTORE', dump];
|
||||
|
||||
if (mode) {
|
||||
args.push(mode);
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
export declare function transformReply(): 'OK';
|
25
packages/client/lib/commands/FUNCTION_STATS.spec.ts
Normal file
25
packages/client/lib/commands/FUNCTION_STATS.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { strict as assert } from 'assert';
|
||||
import testUtils, { GLOBAL } from '../test-utils';
|
||||
import { transformArguments } from './FUNCTION_STATS';
|
||||
|
||||
describe('FUNCTION STATS', () => {
|
||||
testUtils.isVersionGreaterThanHook([7, 0]);
|
||||
|
||||
it('transformArguments', () => {
|
||||
assert.deepEqual(
|
||||
transformArguments(),
|
||||
['FUNCTION', 'STATS']
|
||||
);
|
||||
});
|
||||
|
||||
testUtils.testWithClient('client.functionStats', async client => {
|
||||
const stats = await client.functionStats();
|
||||
assert.equal(stats.runningScript, null);
|
||||
assert.equal(typeof stats.engines, 'object');
|
||||
for (const [engine, { librariesCount, functionsCount }] of Object.entries(stats.engines)) {
|
||||
assert.equal(typeof engine, 'string');
|
||||
assert.equal(typeof librariesCount, 'number');
|
||||
assert.equal(typeof functionsCount, 'number');
|
||||
}
|
||||
}, GLOBAL.SERVERS.OPEN);
|
||||
});
|
56
packages/client/lib/commands/FUNCTION_STATS.ts
Normal file
56
packages/client/lib/commands/FUNCTION_STATS.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { RedisCommandArguments } from '.';
|
||||
|
||||
export function transformArguments(): RedisCommandArguments {
|
||||
return ['FUNCTION', 'STATS'];
|
||||
}
|
||||
|
||||
type FunctionStatsRawReply = [
|
||||
'running_script',
|
||||
null | [
|
||||
'name',
|
||||
string,
|
||||
'command',
|
||||
string,
|
||||
'duration_ms',
|
||||
number
|
||||
],
|
||||
'engines',
|
||||
Array<any> // "flat tuples" (there is no way to type that)
|
||||
// ...[string, [
|
||||
// 'libraries_count',
|
||||
// number,
|
||||
// 'functions_count',
|
||||
// number
|
||||
// ]]
|
||||
];
|
||||
|
||||
interface FunctionStatsReply {
|
||||
runningScript: null | {
|
||||
name: string;
|
||||
command: string;
|
||||
durationMs: number;
|
||||
};
|
||||
engines: Record<string, {
|
||||
librariesCount: number;
|
||||
functionsCount: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
export function transformReply(reply: FunctionStatsRawReply): FunctionStatsReply {
|
||||
const engines = Object.create(null);
|
||||
for (let i = 0; i < reply[3].length; i++) {
|
||||
engines[reply[3][i]] = {
|
||||
librariesCount: reply[3][++i][1],
|
||||
functionsCount: reply[3][i][3]
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
runningScript: reply[1] === null ? null : {
|
||||
name: reply[1][1],
|
||||
command: reply[1][3],
|
||||
durationMs: reply[1][5]
|
||||
},
|
||||
engines
|
||||
};
|
||||
}
|
@@ -348,6 +348,10 @@ export interface EvalOptions {
|
||||
arguments?: Array<string>;
|
||||
}
|
||||
|
||||
export function evalFirstKeyIndex(options?: EvalOptions): string | undefined {
|
||||
return options?.keys?.[0];
|
||||
}
|
||||
|
||||
export function pushEvalArguments(args: Array<string>, options?: EvalOptions): Array<string> {
|
||||
if (options?.keys) {
|
||||
args.push(
|
||||
@@ -491,6 +495,51 @@ export function transformCommandReply(
|
||||
};
|
||||
}
|
||||
|
||||
export enum RedisFunctionFlags {
|
||||
NO_WRITES = 'no-writes',
|
||||
ALLOW_OOM = 'allow-oom',
|
||||
ALLOW_STALE = 'allow-stale',
|
||||
NO_CLUSTER = 'no-cluster'
|
||||
}
|
||||
|
||||
export type FunctionListRawItemReply = [
|
||||
'library_name',
|
||||
string,
|
||||
'engine',
|
||||
string,
|
||||
'functions',
|
||||
Array<[
|
||||
'name',
|
||||
string,
|
||||
'description',
|
||||
string | null,
|
||||
'flags',
|
||||
Array<RedisFunctionFlags>
|
||||
]>
|
||||
];
|
||||
|
||||
export interface FunctionListItemReply {
|
||||
libraryName: string;
|
||||
engine: string;
|
||||
functions: Array<{
|
||||
name: string;
|
||||
description: string | null;
|
||||
flags: Array<RedisFunctionFlags>;
|
||||
}>;
|
||||
}
|
||||
|
||||
export function transformFunctionListItemReply(reply: FunctionListRawItemReply): FunctionListItemReply {
|
||||
return {
|
||||
libraryName: reply[1],
|
||||
engine: reply[3],
|
||||
functions: reply[5].map(fn => ({
|
||||
name: fn[1],
|
||||
description: fn[3],
|
||||
flags: fn[5]
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
export interface SortOptions {
|
||||
BY?: string;
|
||||
LIMIT?: {
|
||||
|
@@ -1,22 +1,51 @@
|
||||
import { ClientCommandOptions } from '../client';
|
||||
import { CommandOptions } from '../command-options';
|
||||
import { RedisScriptConfig, SHA1 } from '../lua-script';
|
||||
|
||||
// https://github.com/Microsoft/TypeScript/issues/3496#issuecomment-128553540
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
interface RedisCommandRawReplyArray extends Array<RedisCommandRawReply> {}
|
||||
export type RedisCommandRawReply = string | number | Buffer | null | undefined | RedisCommandRawReplyArray;
|
||||
export type RedisCommandRawReply = string | number | Buffer | null | undefined | Array<RedisCommandRawReply>;
|
||||
|
||||
export type RedisCommandArgument = string | Buffer;
|
||||
|
||||
export type RedisCommandArguments = Array<RedisCommandArgument> & { preserve?: unknown };
|
||||
|
||||
export interface RedisCommand {
|
||||
FIRST_KEY_INDEX?: number | ((...args: Array<any>) => RedisCommandArgument);
|
||||
FIRST_KEY_INDEX?: number | ((...args: Array<any>) => RedisCommandArgument | undefined);
|
||||
IS_READ_ONLY?: boolean;
|
||||
transformArguments(this: void, ...args: Array<any>): RedisCommandArguments;
|
||||
transformReply?(this: void, reply: any, preserved?: any): any;
|
||||
}
|
||||
|
||||
export type RedisCommandReply<C extends RedisCommand> = C['transformReply'] extends (...args: any) => infer T ? T : RedisCommandRawReply;
|
||||
export type RedisCommandReply<C extends RedisCommand> =
|
||||
C['transformReply'] extends (...args: any) => infer T ? T : RedisCommandRawReply;
|
||||
|
||||
export type ConvertArgumentType<Type, ToType> =
|
||||
Type extends RedisCommandArgument ? (
|
||||
Type extends (string & ToType) ? Type : ToType
|
||||
) : (
|
||||
Type extends Set<infer Member> ? Set<ConvertArgumentType<Member, ToType>> : (
|
||||
Type extends Map<infer Key, infer Value> ? Map<Key, ConvertArgumentType<Value, ToType>> : (
|
||||
Type extends Array<infer Member> ? Array<ConvertArgumentType<Member, ToType>> : (
|
||||
Type extends Date ? Type : (
|
||||
Type extends Record<PropertyKey, any> ? {
|
||||
[Property in keyof Type]: ConvertArgumentType<Type[Property], ToType>
|
||||
} : Type
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
export type RedisCommandSignature<
|
||||
Command extends RedisCommand,
|
||||
Params extends Array<unknown> = Parameters<Command['transformArguments']>
|
||||
> = <Options extends CommandOptions<ClientCommandOptions>>(
|
||||
...args: Params | [options: Options, ...rest: Params]
|
||||
) => Promise<
|
||||
ConvertArgumentType<
|
||||
RedisCommandReply<Command>,
|
||||
Options['returnBuffers'] extends true ? Buffer : string
|
||||
>
|
||||
>;
|
||||
|
||||
export interface RedisCommands {
|
||||
[command: string]: RedisCommand;
|
||||
@@ -30,13 +59,33 @@ export interface RedisModules {
|
||||
[module: string]: RedisModule;
|
||||
}
|
||||
|
||||
export interface RedisFunction extends RedisCommand {
|
||||
NAME: string;
|
||||
NUMBER_OF_KEYS?: number;
|
||||
}
|
||||
|
||||
export interface RedisFunctionLibrary {
|
||||
[fn: string]: RedisFunction;
|
||||
}
|
||||
|
||||
export interface RedisFunctions {
|
||||
[library: string]: RedisFunctionLibrary;
|
||||
}
|
||||
|
||||
export type RedisScript = RedisScriptConfig & SHA1;
|
||||
|
||||
export interface RedisScripts {
|
||||
[script: string]: RedisScript;
|
||||
}
|
||||
|
||||
export interface RedisPlugins<M extends RedisModules, S extends RedisScripts> {
|
||||
export interface RedisExtensions<
|
||||
M extends RedisModules = RedisModules,
|
||||
F extends RedisFunctions = RedisFunctions,
|
||||
S extends RedisScripts = RedisScripts
|
||||
> {
|
||||
modules?: M;
|
||||
functions?: F;
|
||||
scripts?: S;
|
||||
}
|
||||
|
||||
export type ExcludeMappedString<S> = string extends S ? never : S;
|
||||
|
@@ -3,7 +3,7 @@ import { RedisCommand } from './commands';
|
||||
|
||||
export interface RedisScriptConfig extends RedisCommand {
|
||||
SCRIPT: string;
|
||||
NUMBER_OF_KEYS: number;
|
||||
NUMBER_OF_KEYS?: number;
|
||||
}
|
||||
|
||||
export interface SHA1 {
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { RedisCommand, RedisCommandArguments, RedisCommandRawReply, RedisScript } from './commands';
|
||||
import { fCallArguments } from './commander';
|
||||
import { RedisCommand, RedisCommandArguments, RedisCommandRawReply, RedisFunction, RedisScript } from './commands';
|
||||
import { WatchError } from './errors';
|
||||
|
||||
export interface RedisMultiQueuedCommand {
|
||||
@@ -22,6 +23,18 @@ export default class RedisMultiCommand {
|
||||
});
|
||||
}
|
||||
|
||||
addFunction(fn: RedisFunction, args: Array<unknown>): RedisCommandArguments {
|
||||
const transformedArguments = fCallArguments(
|
||||
fn,
|
||||
fn.transformArguments(...args)
|
||||
);
|
||||
this.queue.push({
|
||||
args: transformedArguments,
|
||||
transformReply: fn.transformReply
|
||||
});
|
||||
return transformedArguments;
|
||||
}
|
||||
|
||||
addScript(script: RedisScript, args: Array<unknown>): RedisCommandArguments {
|
||||
const transformedArguments: RedisCommandArguments = [];
|
||||
if (this.scriptsInUse.has(script.SHA1)) {
|
||||
@@ -37,7 +50,9 @@ export default class RedisMultiCommand {
|
||||
);
|
||||
}
|
||||
|
||||
if (script.NUMBER_OF_KEYS !== undefined) {
|
||||
transformedArguments.push(script.NUMBER_OF_KEYS.toString());
|
||||
}
|
||||
|
||||
const scriptArguments = script.transformArguments(...args);
|
||||
transformedArguments.push(...scriptArguments);
|
||||
|
@@ -3,7 +3,7 @@ import { SinonSpy } from 'sinon';
|
||||
import { promiseTimeout } from './utils';
|
||||
|
||||
export default new TestUtils({
|
||||
defaultDockerVersion: '6.2',
|
||||
defaultDockerVersion: '7.0-rc3',
|
||||
dockerImageName: 'redis',
|
||||
dockerImageVersionArgument: 'redis-version'
|
||||
});
|
||||
|
@@ -4,7 +4,7 @@ import RedisGraph from '.';
|
||||
export default new TestUtils({
|
||||
dockerImageName: 'redislabs/redisgraph',
|
||||
dockerImageVersionArgument: 'redisgraph-version',
|
||||
defaultDockerVersion: '2.8.7'
|
||||
defaultDockerVersion: '2.8.9'
|
||||
});
|
||||
|
||||
export const GLOBAL = {
|
||||
|
@@ -4,7 +4,7 @@ import RedisJSON from '.';
|
||||
export default new TestUtils({
|
||||
dockerImageName: 'redislabs/rejson',
|
||||
dockerImageVersionArgument: 'rejson-version',
|
||||
defaultDockerVersion: '2.0.6'
|
||||
defaultDockerVersion: '2.0.7'
|
||||
});
|
||||
|
||||
export const GLOBAL = {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import { createConnection } from 'net';
|
||||
import { once } from 'events';
|
||||
import { RedisModules, RedisScripts } from '@node-redis/client/lib/commands';
|
||||
import { RedisModules, RedisFunctions, RedisScripts } from '@node-redis/client/lib/commands';
|
||||
import RedisClient, { RedisClientType } from '@node-redis/client/lib/client';
|
||||
import { promiseTimeout } from '@node-redis/client/lib/utils';
|
||||
import * as path from 'path';
|
||||
@@ -152,7 +152,11 @@ async function spawnRedisClusterNodeDocker(
|
||||
}
|
||||
}
|
||||
|
||||
async function waitForClusterState<M extends RedisModules, S extends RedisScripts>(client: RedisClientType<M, S>): Promise<void> {
|
||||
async function waitForClusterState<
|
||||
M extends RedisModules,
|
||||
F extends RedisFunctions,
|
||||
S extends RedisScripts
|
||||
>(client: RedisClientType<M, F, S>): Promise<void> {
|
||||
while ((await client.clusterInfo()).state !== 'ok') {
|
||||
await promiseTimeout(500);
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { RedisModules, RedisScripts } from '@node-redis/client/lib/commands';
|
||||
import { RedisModules, RedisFunctions, RedisScripts } from '@node-redis/client/lib/commands';
|
||||
import RedisClient, { RedisClientOptions, RedisClientType } from '@node-redis/client/lib/client';
|
||||
import RedisCluster, { RedisClusterOptions, RedisClusterType } from '@node-redis/client/lib/cluster';
|
||||
import { RedisServerDockerConfig, spawnRedisServer, spawnRedisCluster } from './dockers';
|
||||
@@ -15,15 +15,23 @@ interface CommonTestOptions {
|
||||
minimumDockerVersion?: Array<number>;
|
||||
}
|
||||
|
||||
interface ClientTestOptions<M extends RedisModules, S extends RedisScripts> extends CommonTestOptions {
|
||||
interface ClientTestOptions<
|
||||
M extends RedisModules,
|
||||
F extends RedisFunctions,
|
||||
S extends RedisScripts
|
||||
> extends CommonTestOptions {
|
||||
serverArguments: Array<string>;
|
||||
clientOptions?: Partial<RedisClientOptions<M, S>>;
|
||||
clientOptions?: Partial<RedisClientOptions<M, F, S>>;
|
||||
disableClientSetup?: boolean;
|
||||
}
|
||||
|
||||
interface ClusterTestOptions<M extends RedisModules, S extends RedisScripts> extends CommonTestOptions {
|
||||
interface ClusterTestOptions<
|
||||
M extends RedisModules,
|
||||
F extends RedisFunctions,
|
||||
S extends RedisScripts
|
||||
> extends CommonTestOptions {
|
||||
serverArguments: Array<string>;
|
||||
clusterConfiguration?: Partial<RedisClusterOptions<M, S>>;
|
||||
clusterConfiguration?: Partial<RedisClusterOptions<M, F, S>>;
|
||||
numberOfNodes?: number;
|
||||
}
|
||||
|
||||
@@ -93,10 +101,14 @@ export default class TestUtils {
|
||||
});
|
||||
}
|
||||
|
||||
testWithClient<M extends RedisModules, S extends RedisScripts>(
|
||||
testWithClient<
|
||||
M extends RedisModules,
|
||||
F extends RedisFunctions,
|
||||
S extends RedisScripts
|
||||
>(
|
||||
title: string,
|
||||
fn: (client: RedisClientType<M, S>) => Promise<unknown>,
|
||||
options: ClientTestOptions<M, S>
|
||||
fn: (client: RedisClientType<M, F, S>) => Promise<unknown>,
|
||||
options: ClientTestOptions<M, F, S>
|
||||
): void {
|
||||
let dockerPromise: ReturnType<typeof spawnRedisServer>;
|
||||
if (this.isVersionGreaterThan(options.minimumDockerVersion)) {
|
||||
@@ -138,16 +150,24 @@ export default class TestUtils {
|
||||
});
|
||||
}
|
||||
|
||||
static async #clusterFlushAll<M extends RedisModules, S extends RedisScripts>(cluster: RedisClusterType<M, S>): Promise<void> {
|
||||
static async #clusterFlushAll<
|
||||
M extends RedisModules,
|
||||
F extends RedisFunctions,
|
||||
S extends RedisScripts
|
||||
>(cluster: RedisClusterType<M, F, S>): Promise<void> {
|
||||
await Promise.all(
|
||||
cluster.getMasters().map(({ client }) => client.flushAll())
|
||||
);
|
||||
}
|
||||
|
||||
testWithCluster<M extends RedisModules, S extends RedisScripts>(
|
||||
testWithCluster<
|
||||
M extends RedisModules,
|
||||
F extends RedisFunctions,
|
||||
S extends RedisScripts
|
||||
>(
|
||||
title: string,
|
||||
fn: (cluster: RedisClusterType<M, S>) => Promise<void>,
|
||||
options: ClusterTestOptions<M, S>
|
||||
fn: (cluster: RedisClusterType<M, F, S>) => Promise<void>,
|
||||
options: ClusterTestOptions<M, F, S>
|
||||
): void {
|
||||
let dockersPromise: ReturnType<typeof spawnRedisCluster>;
|
||||
if (this.isVersionGreaterThan(options.minimumDockerVersion)) {
|
||||
|
@@ -4,7 +4,7 @@ import TimeSeries from '.';
|
||||
export default new TestUtils({
|
||||
dockerImageName: 'redislabs/redistimeseries',
|
||||
dockerImageVersionArgument: 'timeseries-version',
|
||||
defaultDockerVersion: '1.6.8'
|
||||
defaultDockerVersion: '1.6.9'
|
||||
});
|
||||
|
||||
export const GLOBAL = {
|
||||
|
Reference in New Issue
Block a user