1
0
mirror of https://github.com/redis/node-redis.git synced 2025-08-07 13:22:56 +03:00
This commit is contained in:
Leibale
2023-06-20 20:04:05 -04:00
parent f22879dffa
commit 4894c26458
35 changed files with 848 additions and 662 deletions

View File

@@ -161,9 +161,15 @@ Some command arguments/replies have changed to align more closely to data types
- `HELLO`: `protover` moved from the options object to it's own argument, `auth` -> `AUTH`, `clientName` -> `SETNAME`
- `MODULE LIST`: `version` -> `ver` [^map-keys]
- `MEMORY STATS`: [^map-keys]
- `CLIENT TRACKINGINFO`: `flags` in RESP2 - `Set<string>` -> `Array<string>` (to match RESP3 default type mapping)
- `CLUSETER SETSLOT`: `ClusterSlotStates` -> `CLUSTER_SLOT_STATES` [^enum-to-constants]
- `FUNCTION RESTORE`: the second argument is `{ mode: string; }` instead of `string` [^future-proofing]
- `CLUSTER RESET`: the second argument is `{ mode: string; }` instead of `string` [^future-proofing]
[^enum-to-constants]: TODO
[^boolean-to-number]: TODO
[^map-keys]: [TODO](https://github.com/redis/node-redis/discussions/2506)
[^future-proofing]: TODO

View File

@@ -1,4 +1,4 @@
export { RedisModules, RedisFunctions, RedisScripts, RespVersions } from './lib/RESP/types';
export { RedisModules, RedisFunctions, RedisScripts, RespVersions, TypeMapping, CommandPolicies } from './lib/RESP/types';
export { RESP_TYPES } from './lib/RESP/decoder';
export { VerbatimString } from './lib/RESP/verbatim-string';
export { defineScript } from './lib/lua-script';

View File

@@ -6,11 +6,14 @@ import { ChannelListeners, PubSub, PubSubCommand, PubSubListener, PubSubType, Pu
import { AbortError, ErrorReply } from '../errors';
import { EventEmitter } from 'stream';
export interface CommandOptions {
export interface CommandOptions<T = TypeMapping> {
chainId?: symbol;
asap?: boolean;
abortSignal?: AbortSignal;
typeMapping?: TypeMapping;
/**
* Maps bettween RESP and JavaScript types
*/
typeMapping?: T;
}
export interface CommandWaitingToBeSent extends CommandWaitingForReply {

View File

@@ -122,10 +122,10 @@ describe('Client', () => {
client.connect()
]);
const promise = once(client, 'end');
console.log('listen to end', client.listeners('end'));
client.close();
await promise;
await Promise.all([
once(client, 'end'),
client.close()
]);
}, {
...GLOBAL.SERVERS.OPEN,
disableClientSetup: true
@@ -335,9 +335,7 @@ describe('Client', () => {
});
testUtils.testWithClient('duplicate should reuse command options', async client => {
const duplicate = client.withTypeMapping({
[RESP_TYPES.SIMPLE_STRING]: Buffer
}).duplicate();
const duplicate = client.duplicate();
await duplicate.connect();
@@ -351,6 +349,13 @@ describe('Client', () => {
}
}, {
...GLOBAL.SERVERS.OPEN,
clientOptions: {
commandOptions: {
typeMapping: {
[RESP_TYPES.SIMPLE_STRING]: Buffer
}
}
},
disableClientSetup: true,
});

View File

@@ -11,18 +11,27 @@ import { Command, CommandArguments, CommandSignature, TypeMapping, CommanderConf
import RedisClientMultiCommand, { RedisClientMultiCommandType } from './multi-command';
import { RedisMultiQueuedCommand } from '../multi-command';
import HELLO, { HelloOptions } from '../commands/HELLO';
import { ReplyWithTypeMapping, CommandReply } from '../RESP/types';
import { ScanOptions, ScanCommonOptions } from '../commands/SCAN';
import { RedisLegacyClient, RedisLegacyClientType } from './legacy-mode';
// import { RedisClientPool } from './pool';
interface ClientCommander<
M extends RedisModules,
F extends RedisFunctions,
S extends RedisScripts,
RESP extends RespVersions,
TYPE_MAPPING extends TypeMapping
> extends CommanderConfig<M, F, S, RESP>{
commandOptions?: CommandOptions<TYPE_MAPPING>;
}
export interface RedisClientOptions<
M extends RedisModules = RedisModules,
F extends RedisFunctions = RedisFunctions,
S extends RedisScripts = RedisScripts,
RESP extends RespVersions = RespVersions,
TYPE_MAPPING extends TypeMapping = TypeMapping
> extends CommanderConfig<M, F, S, RESP>, TypeMappingOption<TYPE_MAPPING> {
> extends ClientCommander<M, F, S, RESP, TYPE_MAPPING> {
/**
* `redis[s]://[[username][:password]@][host][:port][/db-number]`
* See [`redis`](https://www.iana.org/assignments/uri-schemes/prov/redis) and [`rediss`](https://www.iana.org/assignments/uri-schemes/prov/rediss) IANA registration for more details
@@ -68,13 +77,6 @@ export interface RedisClientOptions<
pingInterval?: number;
}
export interface TypeMappingOption<TYPE_MAPPING extends TypeMapping> {
/**
* Maps bettwen RESP types to JavaScript types
*/
typeMapping?: TYPE_MAPPING;
}
type WithCommands<
RESP extends RespVersions,
TYPE_MAPPING extends TypeMapping
@@ -134,7 +136,7 @@ export type RedisClientType<
RedisClientExtensions<M, F, S, RESP, TYPE_MAPPING>
);
type ProxyClient = RedisClient<any, any, any, any, any> & { commandOptions?: CommandOptions };
type ProxyClient = RedisClient<any, any, any, any, any>;
type NamespaceProxyClient = { self: ProxyClient };
@@ -153,7 +155,7 @@ export default class RedisClient<
const transformReply = getTransformReply(command, resp);
return async function (this: ProxyClient, ...args: Array<unknown>) {
const redisArgs = command.transformArguments(...args),
reply = await this.sendCommand(redisArgs, this.commandOptions);
reply = await this.sendCommand(redisArgs, this._commandOptions);
return transformReply ?
transformReply(reply, redisArgs.preserve) :
reply;
@@ -164,7 +166,7 @@ export default class RedisClient<
const transformReply = getTransformReply(command, resp);
return async function (this: NamespaceProxyClient, ...args: Array<unknown>) {
const redisArgs = command.transformArguments(...args),
reply = await this.self.sendCommand(redisArgs, this.self.commandOptions);
reply = await this.self.sendCommand(redisArgs, this.self._commandOptions);
return transformReply ?
transformReply(reply, redisArgs.preserve) :
reply;
@@ -178,7 +180,7 @@ export default class RedisClient<
const fnArgs = fn.transformArguments(...args),
reply = await this.self.sendCommand(
prefix.concat(fnArgs),
this.self.commandOptions
this.self._commandOptions
);
return transformReply ?
transformReply(reply, fnArgs.preserve) :
@@ -192,12 +194,12 @@ export default class RedisClient<
return async function (this: ProxyClient, ...args: Array<unknown>) {
const scriptArgs = script.transformArguments(...args),
redisArgs = prefix.concat(scriptArgs),
reply = await this.sendCommand(redisArgs, this.commandOptions).catch((err: unknown) => {
reply = await this.sendCommand(redisArgs, this._commandOptions).catch((err: unknown) => {
if (!(err as Error)?.message?.startsWith?.('NOSCRIPT')) throw err;
redisArgs[0] = 'EVAL';
redisArgs[1] = script.SCRIPT;
return this.sendCommand(redisArgs, this.commandOptions);
return this.sendCommand(redisArgs, this._commandOptions);
});
return transformReply ?
transformReply(reply, scriptArgs.preserve) :
@@ -211,7 +213,7 @@ export default class RedisClient<
S extends RedisScripts = {},
RESP extends RespVersions = 2,
TYPE_MAPPING extends TypeMapping = {}
>(config?: CommanderConfig<M, F, S, RESP> & TypeMappingOption<TYPE_MAPPING>) {
>(config?: ClientCommander<M, F, S, RESP, TYPE_MAPPING>) {
const Client = attachConfig({
BaseClass: RedisClient,
commands: COMMANDS,
@@ -282,10 +284,11 @@ export default class RedisClient<
self = this;
private readonly _options?: RedisClientOptions<M, F, S, RESP>;
private readonly _options?: RedisClientOptions<M, F, S, RESP, TYPE_MAPPING>;
private readonly _socket: RedisSocket;
private readonly _queue: RedisCommandsQueue;
private _selectedDB = 0;
private _commandOptions?: CommandOptions<TYPE_MAPPING>;
get options(): RedisClientOptions<M, F, S, RESP> | undefined {
return this._options;
@@ -303,14 +306,14 @@ export default class RedisClient<
return this._queue.isPubSubActive;
}
constructor(options?: RedisClientOptions<M, F, S, RESP>) {
constructor(options?: RedisClientOptions<M, F, S, RESP, TYPE_MAPPING>) {
super();
this._options = this._initiateOptions(options);
this._queue = this._initiateQueue();
this._socket = this._initiateSocket();
}
private _initiateOptions(options?: RedisClientOptions<M, F, S, RESP>): RedisClientOptions<M, F, S, RESP> | undefined {
private _initiateOptions(options?: RedisClientOptions<M, F, S, RESP, TYPE_MAPPING>): RedisClientOptions<M, F, S, RESP, TYPE_MAPPING> | undefined {
if (options?.url) {
const parsed = RedisClient.parseURL(options.url);
if (options.socket) {
@@ -324,10 +327,8 @@ export default class RedisClient<
this._selectedDB = options.database;
}
if (options?.typeMapping) {
(this as unknown as ProxyClient).commandOptions = {
typeMapping: options.typeMapping
};
if (options?.commandOptions) {
this._commandOptions = options.commandOptions;
}
return options;
@@ -462,15 +463,18 @@ export default class RedisClient<
}, this._options.pingInterval);
}
withCommandOptions<T extends CommandOptions>(options: T) {
withCommandOptions<
OPTIONS extends CommandOptions<TYPE_MAPPING>,
TYPE_MAPPING extends TypeMapping
>(options: OPTIONS) {
const proxy = Object.create(this.self);
proxy.commandOptions = options;
proxy._commandOptions = options;
return proxy as RedisClientType<
M,
F,
S,
RESP,
T['typeMapping'] extends TypeMapping ? T['typeMapping'] : {}
TYPE_MAPPING extends TypeMapping ? TYPE_MAPPING : {}
>;
}
@@ -482,8 +486,8 @@ export default class RedisClient<
value: V
) {
const proxy = Object.create(this.self);
proxy.commandOptions = Object.create((this as unknown as ProxyClient).commandOptions ?? null);
proxy.commandOptions[key] = value;
proxy._commandOptions = Object.create(this._commandOptions ?? null);
proxy._commandOptions[key] = value;
return proxy as RedisClientType<
M,
F,
@@ -539,17 +543,11 @@ export default class RedisClient<
_RESP extends RespVersions = RESP,
_TYPE_MAPPING extends TypeMapping = TYPE_MAPPING
>(overrides?: Partial<RedisClientOptions<_M, _F, _S, _RESP, _TYPE_MAPPING>>) {
const client = new (Object.getPrototypeOf(this).constructor)({
return new (Object.getPrototypeOf(this).constructor)({
...this._options,
commandOptions: this._commandOptions,
...overrides
}) as RedisClientType<_M, _F, _S, _RESP, _TYPE_MAPPING>;
const { commandOptions } = this as ProxyClient;
if (commandOptions) {
return client.withCommandOptions(commandOptions);
}
return client;
}
connect() {
@@ -732,7 +730,7 @@ export default class RedisClient<
const promise = Promise.all(
commands.map(({ args }) => this._queue.addCommand(args, {
typeMapping: (this as ProxyClient).commandOptions?.typeMapping
typeMapping: this._commandOptions?.typeMapping
}))
);
this._scheduleWrite();
@@ -750,7 +748,7 @@ export default class RedisClient<
return Promise.reject(new ClientClosedError());
}
const typeMapping = (this as ProxyClient).commandOptions?.typeMapping,
const typeMapping = this._commandOptions?.typeMapping,
chainId = Symbol('MULTI Chain'),
promises = [
this._queue.addCommand(['MULTI'], { chainId }),

View File

@@ -41,7 +41,7 @@ describe('Socket', () => {
await once(socket, 'error');
assert.equal(socket.isOpen, true);
assert.equal(socket.isReady, false);
socket.disconnect();
socket.destroy();
assert.equal(socket.isOpen, false);
});

View File

@@ -10,17 +10,30 @@ import { RedisMultiQueuedCommand } from '../multi-command';
import { PubSubListener } from '../client/pub-sub';
import { ErrorReply } from '../errors';
interface ClusterCommander<
M extends RedisModules,
F extends RedisFunctions,
S extends RedisScripts,
RESP extends RespVersions,
TYPE_MAPPING extends TypeMapping,
POLICIES extends CommandPolicies
> extends CommanderConfig<M, F, S, RESP>{
commandOptions?: ClusterCommandOptions<TYPE_MAPPING, POLICIES>;
}
export type RedisClusterClientOptions = Omit<
RedisClientOptions,
'modules' | 'functions' | 'scripts' | 'database' | 'RESP'
keyof ClusterCommander<RedisModules, RedisFunctions, RedisScripts, RespVersions, TypeMapping, CommandPolicies>
>;
export interface RedisClusterOptions<
M extends RedisModules = RedisModules,
F extends RedisFunctions = RedisFunctions,
S extends RedisScripts = RedisScripts,
RESP extends RespVersions = RespVersions
> extends CommanderConfig<M, F, S, RESP> {
RESP extends RespVersions = RespVersions,
TYPE_MAPPING extends TypeMapping = TypeMapping,
POLICIES extends CommandPolicies = CommandPolicies
> extends ClusterCommander<M, F, S, RESP, TYPE_MAPPING, POLICIES> {
/**
* Should contain details for some of the cluster nodes that the client will use to discover
* the "cluster topology". We recommend including details for at least 3 nodes here.
@@ -70,11 +83,14 @@ export type RedisClusterType<
> = RedisCluster<M, F, S, RESP, TYPE_MAPPING, POLICIES> & WithCommands<RESP, TYPE_MAPPING, POLICIES>;
// & WithModules<M> & WithFunctions<F> & WithScripts<S>
export interface ClusterCommandOptions extends CommandOptions {
policies?: CommandPolicies;
export interface ClusterCommandOptions<
TYPE_MAPPING extends TypeMapping = TypeMapping,
POLICIES extends CommandPolicies = CommandPolicies
> extends CommandOptions<TYPE_MAPPING> {
policies?: POLICIES;
}
type ProxyCluster = RedisCluster<RedisModules, RedisFunctions, RedisScripts, RespVersions, TypeMapping, CommandPolicies> & { commandOptions?: ClusterCommandOptions };
type ProxyCluster = RedisCluster<any, any, any, any, any, any>;
type NamespaceProxyCluster = { self: ProxyCluster };
@@ -113,7 +129,7 @@ export default class RedisCluster<
firstKey,
command.IS_READ_ONLY,
redisArgs,
this.commandOptions,
this._commandOptions,
command.POLICIES
);
@@ -136,7 +152,7 @@ export default class RedisCluster<
firstKey,
command.IS_READ_ONLY,
redisArgs,
this.self.commandOptions,
this.self._commandOptions,
command.POLICIES
);
@@ -161,7 +177,7 @@ export default class RedisCluster<
firstKey,
fn.IS_READ_ONLY,
redisArgs,
this.self.commandOptions,
this.self._commandOptions,
fn.POLICIES
);
@@ -186,7 +202,7 @@ export default class RedisCluster<
firstKey,
script.IS_READ_ONLY,
redisArgs,
this.commandOptions,
this._commandOptions,
script.POLICIES
);
@@ -200,8 +216,10 @@ export default class RedisCluster<
M extends RedisModules = {},
F extends RedisFunctions = {},
S extends RedisScripts = {},
RESP extends RespVersions = 2
>(config?: CommanderConfig<M, F, S, RESP>) {
RESP extends RespVersions = 2,
TYPE_MAPPING extends TypeMapping = {},
POLICIES extends CommandPolicies = {}
>(config?: ClusterCommander<M, F, S, RESP, TYPE_MAPPING, POLICIES>) {
const Cluster = attachConfig({
BaseClass: RedisCluster,
commands: COMMANDS,
@@ -217,7 +235,7 @@ export default class RedisCluster<
return (options?: Omit<RedisClusterOptions, keyof Exclude<typeof config, undefined>>) => {
// returning a proxy of the client to prevent the namespaces.self to leak between proxies
// namespaces will be bootstraped on first access per proxy
return Object.create(new Cluster(options)) as RedisClusterType<M, F, S, RESP>;
return Object.create(new Cluster(options)) as RedisClusterType<M, F, S, RESP, TYPE_MAPPING, POLICIES>;
};
}
@@ -225,15 +243,19 @@ export default class RedisCluster<
M extends RedisModules = {},
F extends RedisFunctions = {},
S extends RedisScripts = {},
RESP extends RespVersions = 2
>(options?: RedisClusterOptions<M, F, S, RESP>) {
RESP extends RespVersions = 2,
TYPE_MAPPING extends TypeMapping = {},
POLICIES extends CommandPolicies = {}
>(options?: RedisClusterOptions<M, F, S, RESP, TYPE_MAPPING, POLICIES>) {
return RedisCluster.factory(options)(options);
}
private readonly _options: RedisClusterOptions<M, F, S, RESP>;
private readonly _options: RedisClusterOptions<M, F, S, RESP, TYPE_MAPPING, POLICIES>;
private readonly _slots: RedisClusterSlots<M, F, S, RESP>;
private _commandOptions?: ClusterCommandOptions<TYPE_MAPPING, POLICIES>;
/**
* An array of the cluster slots, each slot contain its `master` and `replicas`.
* Use with {@link RedisCluster.prototype.nodeClient} to get the client for a specific node (master or replica).
@@ -285,34 +307,49 @@ export default class RedisCluster<
return this._slots.isOpen;
}
constructor(options: RedisClusterOptions<M, F, S, RESP>) {
constructor(options: RedisClusterOptions<M, F, S, RESP, TYPE_MAPPING, POLICIES>) {
super();
this._options = options;
this._slots = new RedisClusterSlots(options, this.emit.bind(this));
if (options?.commandOptions) {
this._commandOptions = options.commandOptions;
}
}
duplicate(overrides?: Partial<RedisClusterOptions<M, F, S>>): RedisClusterType<M, F, S> {
duplicate<
_M extends RedisModules = M,
_F extends RedisFunctions = F,
_S extends RedisScripts = S,
_RESP extends RespVersions = RESP,
_TYPE_MAPPING extends TypeMapping = TYPE_MAPPING
>(overrides?: Partial<RedisClusterOptions<_M, _F, _S, _RESP, _TYPE_MAPPING>>) {
return new (Object.getPrototypeOf(this).constructor)({
...this._options,
commandOptions: this._commandOptions,
...overrides
});
}) as RedisClusterType<_M, _F, _S, _RESP, _TYPE_MAPPING>;
}
connect() {
return this._slots.connect();
}
withCommandOptions<T extends ClusterCommandOptions>(options: T) {
withCommandOptions<
OPTIONS extends ClusterCommandOptions<TYPE_MAPPING, CommandPolicies>,
TYPE_MAPPING extends TypeMapping,
POLICIES extends CommandPolicies
>(options: OPTIONS) {
const proxy = Object.create(this);
proxy.commandOptions = options;
proxy._commandOptions = options;
return proxy as RedisClusterType<
M,
F,
S,
RESP,
T['typeMapping'] extends TypeMapping ? T['typeMapping'] : {},
T['policies'] extends CommandPolicies ? T['policies'] : {}
TYPE_MAPPING extends TypeMapping ? TYPE_MAPPING : {},
POLICIES extends CommandPolicies ? POLICIES : {}
>;
}
@@ -324,8 +361,8 @@ export default class RedisCluster<
value: V
) {
const proxy = Object.create(this);
proxy.commandOptions = Object.create((this as unknown as ProxyCluster).commandOptions ?? null);
proxy.commandOptions[key] = value;
proxy._commandOptions = Object.create(this._commandOptions ?? null);
proxy._commandOptions[key] = value;
return proxy as RedisClusterType<
M,
F,

View File

@@ -22,32 +22,29 @@ describe('ACL LOG', () => {
});
testUtils.testWithClient('client.aclLog', async client => {
// make sure to create at least one log
await Promise.all([
client.aclSetUser('test', 'on +@all'),
// make sure to create one log
await assert.rejects(
client.auth({
username: 'test',
password: 'test'
}),
client.auth({
username: 'default',
password: ''
username: 'incorrect',
password: 'incorrect'
})
]);
);
const logs = await client.aclLog();
assert.ok(Array.isArray(logs));
for (const log of logs) {
assert.equal(typeof log.count, 'number');
assert.equal(typeof log.timestamp, 'number');
assert.equal(typeof log.reason, 'string');
assert.equal(typeof log.context, 'string');
assert.equal(typeof log.object, 'string');
assert.equal(typeof log.username, 'string');
assert.equal(typeof log.clientId, 'string');
assert.equal(typeof log.command, 'string');
assert.equal(typeof log.args, 'string');
assert.equal(typeof log.key, 'string');
assert.equal(typeof log.result, 'number');
assert.equal(typeof log.duration, 'number');
assert.equal(typeof log['age-seconds'], 'number');
assert.equal(typeof log['client-info'], 'string');
if (testUtils.isVersionGreaterThan([7, 2])) {
assert.equal(typeof log['entry-id'], 'number');
assert.equal(typeof log['timestamp-created'], 'number');
assert.equal(typeof log['timestamp-last-updated'], 'number');
}
}
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -1,4 +1,4 @@
import { Resp2Reply } from '../RESP/types';
import { DoubleReply, Resp2Reply } from '../RESP/types';
import { ArrayReply, BlobStringReply, Command, NumberReply, TuplesToMapReply } from '../RESP/types';
export type AclLogReply = ArrayReply<TuplesToMapReply<[
@@ -7,8 +7,14 @@ export type AclLogReply = ArrayReply<TuplesToMapReply<[
[BlobStringReply<'context'>, BlobStringReply],
[BlobStringReply<'object'>, BlobStringReply],
[BlobStringReply<'username'>, BlobStringReply],
[BlobStringReply<'age-seconds'>, BlobStringReply],
[BlobStringReply<'client-info'>, BlobStringReply]
[BlobStringReply<'age-seconds'>, DoubleReply],
[BlobStringReply<'client-info'>, BlobStringReply],
/** added in 7.0 */
[BlobStringReply<'entry-id'>, NumberReply],
/** added in 7.0 */
[BlobStringReply<'timestamp-created'>, NumberReply],
/** added in 7.0 */
[BlobStringReply<'timestamp-last-updated'>, NumberReply]
]>>;
export default {
@@ -24,15 +30,18 @@ export default {
return args;
},
transformReply: {
2: (reply: Resp2Reply<AclLogReply>) => ({
count: Number(reply[1]),
reason: reply[3],
context: reply[5],
object: reply[7],
username: reply[9],
'age-seconds': Number(reply[11]),
'client-info': reply[13]
}),
2: (reply: Resp2Reply<AclLogReply>) => reply.map(item => ({
count: item[1],
reason: item[3],
context: item[5],
object: item[7],
username: item[9],
'age-seconds': Number(item[11]),
'client-info': item[13],
'entry-id': item[15],
'timestamp-created': item[17],
'timestamp-last-updated': item[19]
})),
3: undefined as unknown as () => AclLogReply
}
} as const satisfies Command;

View File

@@ -4,7 +4,7 @@ export default {
FIRST_KEY_INDEX: undefined,
IS_READ_ONLY: true,
transformArguments() {
return ['ACL', 'USERS'];
return ['ACL', 'WHOAMI'];
},
transformReply: undefined as unknown as () => BlobStringReply
} as const satisfies Command;

View File

@@ -1,6 +1,6 @@
import { strict as assert } from 'assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './CLIENT_TRACKING';
import CLIENT_TRACKING from './CLIENT_TRACKING';
describe('CLIENT TRACKING', () => {
testUtils.isVersionGreaterThanHook([6]);
@@ -9,14 +9,14 @@ describe('CLIENT TRACKING', () => {
describe('true', () => {
it('simple', () => {
assert.deepEqual(
transformArguments(true),
CLIENT_TRACKING.transformArguments(true),
['CLIENT', 'TRACKING', 'ON']
);
});
it('with REDIRECT', () => {
assert.deepEqual(
transformArguments(true, {
CLIENT_TRACKING.transformArguments(true, {
REDIRECT: 1
}),
['CLIENT', 'TRACKING', 'ON', 'REDIRECT', '1']
@@ -26,7 +26,7 @@ describe('CLIENT TRACKING', () => {
describe('with BCAST', () => {
it('simple', () => {
assert.deepEqual(
transformArguments(true, {
CLIENT_TRACKING.transformArguments(true, {
BCAST: true
}),
['CLIENT', 'TRACKING', 'ON', 'BCAST']
@@ -36,7 +36,7 @@ describe('CLIENT TRACKING', () => {
describe('with PREFIX', () => {
it('string', () => {
assert.deepEqual(
transformArguments(true, {
CLIENT_TRACKING.transformArguments(true, {
BCAST: true,
PREFIX: 'prefix'
}),
@@ -46,7 +46,7 @@ describe('CLIENT TRACKING', () => {
it('array', () => {
assert.deepEqual(
transformArguments(true, {
CLIENT_TRACKING.transformArguments(true, {
BCAST: true,
PREFIX: ['1', '2']
}),
@@ -58,7 +58,7 @@ describe('CLIENT TRACKING', () => {
it('with OPTIN', () => {
assert.deepEqual(
transformArguments(true, {
CLIENT_TRACKING.transformArguments(true, {
OPTIN: true
}),
['CLIENT', 'TRACKING', 'ON', 'OPTIN']
@@ -67,7 +67,7 @@ describe('CLIENT TRACKING', () => {
it('with OPTOUT', () => {
assert.deepEqual(
transformArguments(true, {
CLIENT_TRACKING.transformArguments(true, {
OPTOUT: true
}),
['CLIENT', 'TRACKING', 'ON', 'OPTOUT']
@@ -76,7 +76,7 @@ describe('CLIENT TRACKING', () => {
it('with NOLOOP', () => {
assert.deepEqual(
transformArguments(true, {
CLIENT_TRACKING.transformArguments(true, {
NOLOOP: true
}),
['CLIENT', 'TRACKING', 'ON', 'NOLOOP']
@@ -86,7 +86,7 @@ describe('CLIENT TRACKING', () => {
it('false', () => {
assert.deepEqual(
transformArguments(false),
CLIENT_TRACKING.transformArguments(false),
['CLIENT', 'TRACKING', 'OFF']
);
});

View File

@@ -1,83 +1,86 @@
// import { RedisCommandArgument, RedisCommandArguments } from '.';
import { RedisArgument, SimpleStringReply, Command } from '../RESP/types';
import { RedisVariadicArgument } from './generic-transformers';
interface CommonOptions {
REDIRECT?: number;
NOLOOP?: boolean;
}
// interface CommonOptions {
// REDIRECT?: number;
// NOLOOP?: boolean;
// }
interface BroadcastOptions {
BCAST?: boolean;
PREFIX?: RedisVariadicArgument;
}
// interface BroadcastOptions {
// BCAST?: boolean;
// PREFIX?: RedisCommandArgument | Array<RedisCommandArgument>;
// }
interface OptInOptions {
OPTIN?: boolean;
}
// interface OptInOptions {
// OPTIN?: boolean;
// }
interface OptOutOptions {
OPTOUT?: boolean;
}
// interface OptOutOptions {
// OPTOUT?: boolean;
// }
type ClientTrackingOptions = CommonOptions & (
BroadcastOptions |
OptInOptions |
OptOutOptions
);
// type ClientTrackingOptions = CommonOptions & (
// BroadcastOptions |
// OptInOptions |
// OptOutOptions
// );
export default {
FIRST_KEY_INDEX: undefined,
IS_READ_ONLY: true,
transformArguments<M extends boolean>(
mode: M,
options?: M extends true ? ClientTrackingOptions : never
) {
const args: Array<RedisArgument> = [
'CLIENT',
'TRACKING',
mode ? 'ON' : 'OFF'
];
// export function transformArguments<M extends boolean>(
// mode: M,
// options?: M extends true ? ClientTrackingOptions : undefined
// ): RedisCommandArguments {
// const args: RedisCommandArguments = [
// 'CLIENT',
// 'TRACKING',
// mode ? 'ON' : 'OFF'
// ];
if (mode) {
if (options?.REDIRECT) {
args.push(
'REDIRECT',
options.REDIRECT.toString()
);
}
// if (mode) {
// if (options?.REDIRECT) {
// args.push(
// 'REDIRECT',
// options.REDIRECT.toString()
// );
// }
if (isBroadcast(options)) {
args.push('BCAST');
// if (isBroadcast(options)) {
// args.push('BCAST');
if (options?.PREFIX) {
if (Array.isArray(options.PREFIX)) {
for (const prefix of options.PREFIX) {
args.push('PREFIX', prefix);
}
} else {
args.push('PREFIX', options.PREFIX);
}
}
} else if (isOptIn(options)) {
args.push('OPTIN');
} else if (isOptOut(options)) {
args.push('OPTOUT');
}
// if (options?.PREFIX) {
// if (Array.isArray(options.PREFIX)) {
// for (const prefix of options.PREFIX) {
// args.push('PREFIX', prefix);
// }
// } else {
// args.push('PREFIX', options.PREFIX);
// }
// }
// } else if (isOptIn(options)) {
// args.push('OPTIN');
// } else if (isOptOut(options)) {
// args.push('OPTOUT');
// }
if (options?.NOLOOP) {
args.push('NOLOOP');
}
}
// if (options?.NOLOOP) {
// args.push('NOLOOP');
// }
// }
return args;
},
transformReply: undefined as unknown as () => SimpleStringReply<'OK'>
} as const satisfies Command;
// return args;
// }
function isBroadcast(options?: ClientTrackingOptions): options is BroadcastOptions {
return (options as BroadcastOptions)?.BCAST === true;
}
// function isBroadcast(options?: ClientTrackingOptions): options is BroadcastOptions {
// return (options as BroadcastOptions)?.BCAST === true;
// }
function isOptIn(options?: ClientTrackingOptions): options is OptInOptions {
return (options as OptInOptions)?.OPTIN === true;
}
// function isOptIn(options?: ClientTrackingOptions): options is OptInOptions {
// return (options as OptInOptions)?.OPTIN === true;
// }
// function isOptOut(options?: ClientTrackingOptions): options is OptOutOptions {
// return (options as OptOutOptions)?.OPTOUT === true;
// }
// export declare function transformReply(): 'OK' | Buffer;
function isOptOut(options?: ClientTrackingOptions): options is OptOutOptions {
return (options as OptOutOptions)?.OPTOUT === true;
}

View File

@@ -1,13 +1,14 @@
import { strict as assert } from 'assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './CLIENT_TRACKINGINFO';
import CLIENT_TRACKINGINFO from './CLIENT_TRACKINGINFO';
import { RESP_TYPES } from '../RESP/decoder';
describe('CLIENT TRACKINGINFO', () => {
testUtils.isVersionGreaterThanHook([6, 2]);
it('transformArguments', () => {
assert.deepEqual(
transformArguments(),
CLIENT_TRACKINGINFO.transformArguments(),
['CLIENT', 'TRACKINGINFO']
);
});
@@ -16,7 +17,7 @@ describe('CLIENT TRACKINGINFO', () => {
assert.deepEqual(
await client.clientTrackingInfo(),
{
flags: new Set(['off']),
flags: ['off'],
redirect: -1,
prefixes: []
}

View File

@@ -1,28 +1,23 @@
// import { RedisCommandArguments } from '.';
import { TuplesToMapReply, BlobStringReply, SetReply, NumberReply, ArrayReply, Resp2Reply, Command } from '../RESP/types';
// export function transformArguments(): RedisCommandArguments {
// return ['CLIENT', 'TRACKINGINFO'];
// }
type TrackingInfo = TuplesToMapReply<[
[BlobStringReply<'flags'>, SetReply<BlobStringReply>],
[BlobStringReply<'redirect'>, NumberReply],
[BlobStringReply<'prefixes'>, ArrayReply<BlobStringReply>]
]>;
// type RawReply = [
// 'flags',
// Array<string>,
// 'redirect',
// number,
// 'prefixes',
// Array<string>
// ];
// interface Reply {
// flags: Set<string>;
// redirect: number;
// prefixes: Array<string>;
// }
// export function transformReply(reply: RawReply): Reply {
// return {
// flags: new Set(reply[1]),
// redirect: reply[3],
// prefixes: reply[5]
// };
// }
export default {
FIRST_KEY_INDEX: undefined,
IS_READ_ONLY: true,
transformArguments() {
return ['CLIENT', 'TRACKINGINFO'];
},
transformReply: {
2: (reply: Resp2Reply<TrackingInfo>) => ({
flags: reply[1],
redirect: reply[3],
prefixes: reply[5]
}),
3: undefined as unknown as () => TrackingInfo
}
} as const satisfies Command;

View File

@@ -1,18 +1,18 @@
import { strict as assert } from 'assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './CLIENT_UNPAUSE';
import CLIENT_UNPAUSE from './CLIENT_UNPAUSE';
describe('CLIENT UNPAUSE', () => {
testUtils.isVersionGreaterThanHook([6, 2]);
it('transformArguments', () => {
assert.deepEqual(
transformArguments(),
CLIENT_UNPAUSE.transformArguments(),
['CLIENT', 'UNPAUSE']
);
});
testUtils.testWithClient('client.unpause', async client => {
testUtils.testWithClient('client.clientUnpause', async client => {
assert.equal(
await client.clientUnpause(),
'OK'

View File

@@ -1,18 +1,18 @@
import { strict as assert } from 'assert';
import { transformArguments } from './CLUSTER_ADDSLOTS';
import CLUSTER_ADDSLOTS from './CLUSTER_ADDSLOTS';
describe('CLUSTER ADDSLOTS', () => {
describe('transformArguments', () => {
it('single', () => {
assert.deepEqual(
transformArguments(0),
CLUSTER_ADDSLOTS.transformArguments(0),
['CLUSTER', 'ADDSLOTS', '0']
);
});
it('multiple', () => {
assert.deepEqual(
transformArguments([0, 1]),
CLUSTER_ADDSLOTS.transformArguments([0, 1]),
['CLUSTER', 'ADDSLOTS', '0', '1']
);
});

View File

@@ -1,11 +1,14 @@
import { strict as assert } from 'assert';
import { transformArguments } from './CLUSTER_ADDSLOTSRANGE';
import testUtils from '../test-utils';
import CLUSTER_ADDSLOTSRANGE from './CLUSTER_ADDSLOTSRANGE';
describe('CLUSTER ADDSLOTSRANGE', () => {
testUtils.isVersionGreaterThanHook([7, 0]);
describe('transformArguments', () => {
it('single', () => {
assert.deepEqual(
transformArguments({
CLUSTER_ADDSLOTSRANGE.transformArguments({
start: 0,
end: 1
}),
@@ -15,7 +18,7 @@ describe('CLUSTER ADDSLOTSRANGE', () => {
it('multiple', () => {
assert.deepEqual(
transformArguments([{
CLUSTER_ADDSLOTSRANGE.transformArguments([{
start: 0,
end: 1
}, {

View File

@@ -1,11 +1,11 @@
import { strict as assert } from 'assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './CLUSTER_BUMPEPOCH';
import CLUSTER_BUMPEPOCH from './CLUSTER_BUMPEPOCH';
describe('CLUSTER BUMPEPOCH', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments(),
CLUSTER_BUMPEPOCH.transformArguments(),
['CLUSTER', 'BUMPEPOCH']
);
});

View File

@@ -1,11 +1,11 @@
import { strict as assert } from 'assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './CLUSTER_KEYSLOT';
import CLUSTER_KEYSLOT from './CLUSTER_KEYSLOT';
describe('CLUSTER KEYSLOT', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments('key'),
CLUSTER_KEYSLOT.transformArguments('key'),
['CLUSTER', 'KEYSLOT', 'key']
);
});

View File

@@ -1,10 +1,10 @@
import { strict as assert } from 'assert';
import { transformArguments } from './CLUSTER_MEET';
import CLUSTER_MEET from './CLUSTER_MEET';
describe('CLUSTER MEET', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments('127.0.0.1', 6379),
CLUSTER_MEET.transformArguments('127.0.0.1', 6379),
['CLUSTER', 'MEET', '127.0.0.1', '6379']
);
});

View File

@@ -4,5 +4,5 @@ export default {
transformArguments(host: string, port: number) {
return ['CLUSTER', 'MEET', host, port.toString()];
},
transformReply: undefined as unknown as () => SimpleStringReply
transformReply: undefined as unknown as () => SimpleStringReply<'OK'>
} as const satisfies Command;

View File

@@ -1,10 +1,10 @@
import { strict as assert } from 'assert';
import { transformArguments } from './CLUSTER_REPLICATE';
import CLUSTER_REPLICATE from './CLUSTER_REPLICATE';
describe('CLUSTER REPLICATE', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments('0'),
CLUSTER_REPLICATE.transformArguments('0'),
['CLUSTER', 'REPLICATE', '0']
);
});

View File

@@ -1,18 +1,20 @@
import { strict as assert } from 'assert';
import { transformArguments } from './CLUSTER_RESET';
import CLUSTER_RESET from './CLUSTER_RESET';
describe('CLUSTER RESET', () => {
describe('transformArguments', () => {
it('simple', () => {
assert.deepEqual(
transformArguments(),
CLUSTER_RESET.transformArguments(),
['CLUSTER', 'RESET']
);
});
it('with mode', () => {
assert.deepEqual(
transformArguments('HARD'),
CLUSTER_RESET.transformArguments({
mode: 'HARD'
}),
['CLUSTER', 'RESET', 'HARD']
);
});

View File

@@ -1,11 +1,20 @@
export function transformArguments(mode?: 'HARD' | 'SOFT'): Array<string> {
import { SimpleStringReply, Command } from '../RESP/types';
export interface ClusterResetOptions {
mode?: 'HARD' | 'SOFT';
}
export default {
FIRST_KEY_INDEX: undefined,
IS_READ_ONLY: true,
transformArguments(options?: ClusterResetOptions) {
const args = ['CLUSTER', 'RESET'];
if (mode) {
args.push(mode);
if (options?.mode) {
args.push(options.mode);
}
return args;
}
export declare function transformReply(): string;
},
transformReply: undefined as unknown as () => SimpleStringReply<'OK'>
} as const satisfies Command;

View File

@@ -1,11 +1,11 @@
import { strict as assert } from 'assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './CLUSTER_SAVECONFIG';
import CLUSTER_SAVECONFIG from './CLUSTER_SAVECONFIG';
describe('CLUSTER SAVECONFIG', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments(),
CLUSTER_SAVECONFIG.transformArguments(),
['CLUSTER', 'SAVECONFIG']
);
});

View File

@@ -1,5 +1,11 @@
export function transformArguments(): Array<string> {
return ['CLUSTER', 'SAVECONFIG'];
}
import { SimpleStringReply, Command } from '../RESP/types';
export default {
FIRST_KEY_INDEX: undefined,
IS_READ_ONLY: true,
transformArguments() {
return ['CLUSTER', 'SAVECONFIG'];
},
transformReply: undefined as unknown as () => SimpleStringReply<'OK'>
} as const satisfies Command;
export declare function transformReply(): 'OK';

View File

@@ -1,10 +1,10 @@
import { strict as assert } from 'assert';
import { transformArguments } from './CLUSTER_SET-CONFIG-EPOCH';
import CLUSTER_SET_CONFIG_EPOCH from './CLUSTER_SET-CONFIG-EPOCH';
describe('CLUSTER SET-CONFIG-EPOCH', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments(0),
CLUSTER_SET_CONFIG_EPOCH.transformArguments(0),
['CLUSTER', 'SET-CONFIG-EPOCH', '0']
);
});

View File

@@ -1,5 +1,10 @@
export function transformArguments(configEpoch: number): Array<string> {
return ['CLUSTER', 'SET-CONFIG-EPOCH', configEpoch.toString()];
}
import { SimpleStringReply, Command } from '../RESP/types';
export declare function transformReply(): 'OK';
export default {
FIRST_KEY_INDEX: undefined,
IS_READ_ONLY: true,
transformArguments(configEpoch: number) {
return ['CLUSTER', 'SET-CONFIG-EPOCH', configEpoch.toString() ];
},
transformReply: undefined as unknown as () => SimpleStringReply<'OK'>
} as const satisfies Command;

View File

@@ -1,18 +1,18 @@
import { strict as assert } from 'assert';
import { ClusterSlotStates, transformArguments } from './CLUSTER_SETSLOT';
import CLUSTER_SETSLOT, { CLUSTER_SLOT_STATES } from './CLUSTER_SETSLOT';
describe('CLUSTER SETSLOT', () => {
describe('transformArguments', () => {
it('simple', () => {
assert.deepEqual(
transformArguments(0, ClusterSlotStates.IMPORTING),
CLUSTER_SETSLOT.transformArguments(0, CLUSTER_SLOT_STATES.IMPORTING),
['CLUSTER', 'SETSLOT', '0', 'IMPORTING']
);
});
it('with nodeId', () => {
assert.deepEqual(
transformArguments(0, ClusterSlotStates.IMPORTING, 'nodeId'),
CLUSTER_SETSLOT.transformArguments(0, CLUSTER_SLOT_STATES.IMPORTING, 'nodeId'),
['CLUSTER', 'SETSLOT', '0', 'IMPORTING', 'nodeId']
);
});

View File

@@ -1,15 +1,18 @@
export enum ClusterSlotStates {
IMPORTING = 'IMPORTING',
MIGRATING = 'MIGRATING',
STABLE = 'STABLE',
NODE = 'NODE'
}
import { SimpleStringReply, Command } from '../RESP/types';
export function transformArguments(
slot: number,
state: ClusterSlotStates,
nodeId?: string
): Array<string> {
export const CLUSTER_SLOT_STATES = {
IMPORTING: 'IMPORTING',
MIGRATING: 'MIGRATING',
STABLE: 'STABLE',
NODE: 'NODE'
} as const;
export type ClusterSlotStates = typeof CLUSTER_SLOT_STATES[keyof typeof CLUSTER_SLOT_STATES];
export default {
FIRST_KEY_INDEX: undefined,
IS_READ_ONLY: true,
transformArguments(slot: number, state: ClusterSlotStates, nodeId?: string) {
const args = ['CLUSTER', 'SETSLOT', slot.toString(), state];
if (nodeId) {
@@ -17,6 +20,6 @@ export function transformArguments(
}
return args;
}
export declare function transformReply(): 'OK';
},
transformReply: undefined as unknown as () => SimpleStringReply<'OK'>
} as const satisfies Command;

View File

@@ -1,76 +1,30 @@
import { strict as assert } from 'assert';
import { transformArguments, transformReply } from './CLUSTER_SLOTS';
import testUtils, { GLOBAL } from '../test-utils';
import CLUSTER_SLOTS from './CLUSTER_SLOTS';
describe('CLUSTER SLOTS', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments(),
CLUSTER_SLOTS.transformArguments(),
['CLUSTER', 'SLOTS']
);
});
it('transformReply', () => {
assert.deepEqual(
transformReply([
[
0,
5460,
['127.0.0.1', 30001, '09dbe9720cda62f7865eabc5fd8857c5d2678366'],
['127.0.0.1', 30004, '821d8ca00d7ccf931ed3ffc7e3db0599d2271abf']
],
[
5461,
10922,
['127.0.0.1', 30002, 'c9d93d9f2c0c524ff34cc11838c2003d8c29e013'],
['127.0.0.1', 30005, 'faadb3eb99009de4ab72ad6b6ed87634c7ee410f']
],
[
10923,
16383,
['127.0.0.1', 30003, '044ec91f325b7595e76dbcb18cc688b6a5b434a1'],
['127.0.0.1', 30006, '58e6e48d41228013e5d9c1c37c5060693925e97e']
]
]),
[{
from: 0,
to: 5460,
master: {
ip: '127.0.0.1',
port: 30001,
id: '09dbe9720cda62f7865eabc5fd8857c5d2678366'
},
replicas: [{
ip: '127.0.0.1',
port: 30004,
id: '821d8ca00d7ccf931ed3ffc7e3db0599d2271abf'
}]
}, {
from: 5461,
to: 10922,
master: {
ip: '127.0.0.1',
port: 30002,
id: 'c9d93d9f2c0c524ff34cc11838c2003d8c29e013'
},
replicas: [{
ip: '127.0.0.1',
port: 30005,
id: 'faadb3eb99009de4ab72ad6b6ed87634c7ee410f'
}]
}, {
from: 10923,
to: 16383,
master: {
ip: '127.0.0.1',
port: 30003,
id: '044ec91f325b7595e76dbcb18cc688b6a5b434a1'
},
replicas: [{
ip: '127.0.0.1',
port: 30006,
id: '58e6e48d41228013e5d9c1c37c5060693925e97e'
}]
}]
);
});
testUtils.testWithCluster('clusterNode.clusterSlots', async cluster => {
const client = await cluster.nodeClient(cluster.masters[0]),
slots = await client.clusterSlots();
assert.ok(Array.isArray(slots));
for (const { from, to, master, replicas } of slots) {
assert.equal(typeof from, 'number');
assert.equal(typeof to, 'number');
assert.equal(typeof master.host, 'string');
assert.equal(typeof master.port, 'number');
assert.equal(typeof master.id, 'string');
for (const replica of replicas) {
assert.equal(typeof replica.host, 'string');
assert.equal(typeof replica.port, 'number');
assert.equal(typeof replica.id, 'string');
}
}
}, GLOBAL.CLUSTERS.OPEN);
});

View File

@@ -30,7 +30,9 @@ describe('FUNCTION RESTORE', () => {
await client.withTypeMapping({
[RESP_TYPES.BLOB_STRING]: Buffer
}).functionDump(),
'FLUSH'
{
mode: 'REPLACE'
}
),
'OK'
);

View File

@@ -36,11 +36,33 @@ import CLIENT_LIST from './CLIENT_LIST';
import CLIENT_NO_EVICT from './CLIENT_NO-EVICT';
import CLIENT_PAUSE from './CLIENT_PAUSE';
import CLIENT_SETNAME from './CLIENT_SETNAME';
import CLIENT_TRACKING from './CLIENT_TRACKING';
import CLIENT_TRACKINGINFO from './CLIENT_TRACKINGINFO';
import CLIENT_UNPAUSE from './CLIENT_UNPAUSE';
import CLUSTER_ADDSLOTS from './CLUSTER_ADDSLOTS';
import CLUSTER_SLOTS from './CLUSTER_SLOTS';
import CLUSTER_ADDSLOTSRANGE from './CLUSTER_ADDSLOTSRANGE';
import CLUSTER_BUMPEPOCH from './CLUSTER_BUMPEPOCH';
import CLUSTER_COUNT_FAILURE_REPORTS from './CLUSTER_COUNT-FAILURE-REPORTS';
import CLUSTER_COUNTKEYSINSLOT from './CLUSTER_COUNTKEYSINSLOT';
import CLUSTER_DELSLOTS from './CLUSTER_DELSLOTS';
import CLUSTER_DELSLOTSRANGE from './CLUSTER_DELSLOTSRANGE';
import CLUSTER_FAILOVER from './CLUSTER_FAILOVER';
import CLUSTER_FLUSHSLOTS from './CLUSTER_FLUSHSLOTS';
import CLUSTER_FORGET from './CLUSTER_FORGET';
import CLUSTER_GETKEYSINSLOT from './CLUSTER_GETKEYSINSLOT';
// import CLUSTER_INFO from './CLUSTER_INFO';
import CLUSTER_KEYSLOT from './CLUSTER_KEYSLOT';
// import CLUSTER_LINKS from './CLUSTER_LINKS';
import CLUSTER_MEET from './CLUSTER_MEET';
import CLUSTER_MYID from './CLUSTER_MYID';
// import CLUSTER_NODES from './CLUSTER_NODES';
// import CLUSTER_REPLICAS from './CLUSTER_REPLICAS';
import CLUSTER_REPLICATE from './CLUSTER_REPLICATE';
import CLUSTER_RESET from './CLUSTER_RESET';
import CLUSTER_SAVECONFIG from './CLUSTER_SAVECONFIG';
import CLUSTER_SET_CONFIG_EPOCH from './CLUSTER_SET-CONFIG-EPOCH';
import CLUSTER_SETSLOT from './CLUSTER_SETSLOT';
import CLUSTER_SLOTS from './CLUSTER_SLOTS';
import COPY from './COPY';
import DBSIZE from './DBSIZE';
import DECR from './DECR';
@@ -90,7 +112,7 @@ import FUNCTION_KILL from './FUNCTION_KILL';
import FUNCTION_LIST_WITHCODE from './FUNCTION_LIST_WITHCODE';
import FUNCTION_LIST from './FUNCTION_LIST';
import FUNCTION_LOAD from './FUNCTION_LOAD';
// import FUNCTION_RESTORE from './FUNCTION_RESTORE';
import FUNCTION_RESTORE from './FUNCTION_RESTORE';
// import FUNCTION_STATS from './FUNCTION_STATS';
import HDEL from './HDEL';
import HELLO from './HELLO';
@@ -297,11 +319,33 @@ type CLIENT_LIST = typeof import('./CLIENT_LIST').default;
type CLIENT_NO_EVICT = typeof import('./CLIENT_NO-EVICT').default;
type CLIENT_PAUSE = typeof import('./CLIENT_PAUSE').default;
type CLIENT_SETNAME = typeof import('./CLIENT_SETNAME').default;
type CLIENT_TRACKING = typeof import('./CLIENT_TRACKING').default;
type CLIENT_TRACKINGINFO = typeof import('./CLIENT_TRACKINGINFO').default;
type CLIENT_UNPAUSE = typeof import('./CLIENT_UNPAUSE').default;
type CLUSTER_ADDSLOTS = typeof import('./CLUSTER_ADDSLOTS').default;
type CLUSTER_SLOTS = typeof import('./CLUSTER_SLOTS').default;
type CLUSTER_ADDSLOTSRANGE = typeof import('./CLUSTER_ADDSLOTSRANGE').default;
type CLUSTER_BUMPEPOCH = typeof import('./CLUSTER_BUMPEPOCH').default;
type CLUSTER_COUNT_FAILURE_REPORTS = typeof import('./CLUSTER_COUNT-FAILURE-REPORTS').default;
type CLUSTER_COUNTKEYSINSLOT = typeof import('./CLUSTER_COUNTKEYSINSLOT').default;
type CLUSTER_DELSLOTS = typeof import('./CLUSTER_DELSLOTS').default;
type CLUSTER_DELSLOTSRANGE = typeof import('./CLUSTER_DELSLOTSRANGE').default;
type CLUSTER_FAILOVER = typeof import('./CLUSTER_FAILOVER').default;
type CLUSTER_FLUSHSLOTS = typeof import('./CLUSTER_FLUSHSLOTS').default;
type CLUSTER_FORGET = typeof import('./CLUSTER_FORGET').default;
type CLUSTER_GETKEYSINSLOT = typeof import('./CLUSTER_GETKEYSINSLOT').default;
// type CLUSTER_INFO = typeof import('./CLUSTER_INFO').default;
type CLUSTER_KEYSLOT = typeof import('./CLUSTER_KEYSLOT').default;
// type CLUSTER_LINKS = typeof import('./CLUSTER_LINKS').default;
type CLUSTER_MEET = typeof import('./CLUSTER_MEET').default;
type CLUSTER_MYID = typeof import('./CLUSTER_MYID').default;
// type CLUSTER_NODES = typeof import('./CLUSTER_NODES').default;
// type CLUSTER_REPLICAS = typeof import('./CLUSTER_REPLICAS').default;
type CLUSTER_REPLICATE = typeof import('./CLUSTER_REPLICATE').default;
type CLUSTER_RESET = typeof import('./CLUSTER_RESET').default;
type CLUSTER_SAVECONFIG = typeof import('./CLUSTER_SAVECONFIG').default;
type CLUSTER_SET_CONFIG_EPOCH = typeof import('./CLUSTER_SET-CONFIG-EPOCH').default;
type CLUSTER_SETSLOT = typeof import('./CLUSTER_SETSLOT').default;
type CLUSTER_SLOTS = typeof import('./CLUSTER_SLOTS').default;
type COPY = typeof import('./COPY').default;
type DBSIZE = typeof DBSIZE;
type DECR = typeof import('./DECR').default;
@@ -351,7 +395,7 @@ type FUNCTION_KILL = typeof import('./FUNCTION_KILL').default;
type FUNCTION_LIST_WITHCODE = typeof import('./FUNCTION_LIST_WITHCODE').default;
type FUNCTION_LIST = typeof import('./FUNCTION_LIST').default;
type FUNCTION_LOAD = typeof import('./FUNCTION_LOAD').default;
// type FUNCTION_RESTORE = typeof import('./FUNCTION_RESTORE').default;
type FUNCTION_RESTORE = typeof import('./FUNCTION_RESTORE').default;
// type FUNCTION_STATS = typeof import('./FUNCTION_STATS').default;
type HDEL = typeof import('./HDEL').default;
type HELLO = typeof import('./HELLO').default;
@@ -596,16 +640,60 @@ type Commands = {
clientPause: CLIENT_PAUSE;
CLIENT_SETNAME: CLIENT_SETNAME;
clientSetName: CLIENT_SETNAME;
CLIENT_TRACKING: CLIENT_TRACKING;
clientTracking: CLIENT_TRACKING;
CLIENT_TRACKINGINFO: CLIENT_TRACKINGINFO;
clientTrackingInfo: CLIENT_TRACKINGINFO;
CLIENT_UNPAUSE: CLIENT_UNPAUSE;
clientUnpause: CLIENT_UNPAUSE;
CLUSTER_ADDSLOTS: CLUSTER_ADDSLOTS;
clusterAddSlots: CLUSTER_ADDSLOTS;
CLUSTER_SLOTS: CLUSTER_SLOTS;
clusterSlots: CLUSTER_SLOTS;
CLUSTER_ADDSLOTSRANGE: CLUSTER_ADDSLOTSRANGE;
clusterAddSlotsRange: CLUSTER_ADDSLOTSRANGE;
CLUSTER_BUMPEPOCH: CLUSTER_BUMPEPOCH;
clusterBumpEpoch: CLUSTER_BUMPEPOCH;
'CLUSTER_COUNT-FAILURE-REPORTS': CLUSTER_COUNT_FAILURE_REPORTS;
clusterCountFailureReports: CLUSTER_COUNT_FAILURE_REPORTS;
CLUSTER_COUNTKEYSINSLOT: CLUSTER_COUNTKEYSINSLOT;
clusterCountKeysInSlot: CLUSTER_COUNTKEYSINSLOT;
CLUSTER_DELSLOTS: CLUSTER_DELSLOTS;
clusterDelSlots: CLUSTER_DELSLOTS;
CLUSTER_DELSLOTSRANGE: CLUSTER_DELSLOTSRANGE;
clusterDelSlotsRange: CLUSTER_DELSLOTSRANGE;
CLUSTER_FAILOVER: CLUSTER_FAILOVER;
clusterFailover: CLUSTER_FAILOVER;
CLUSTER_FLUSHSLOTS: CLUSTER_FLUSHSLOTS;
clusterFlushSlots: CLUSTER_FLUSHSLOTS;
CLUSTER_FORGET: CLUSTER_FORGET;
clusterForget: CLUSTER_FORGET;
CLUSTER_GETKEYSINSLOT: CLUSTER_GETKEYSINSLOT;
clusterGetKeysInSlot: CLUSTER_GETKEYSINSLOT;
// CLUSTER_INFO: CLUSTER_INFO;
// clusterInfo: CLUSTER_INFO;
CLUSTER_KEYSLOT: CLUSTER_KEYSLOT;
clusterKeySlot: CLUSTER_KEYSLOT;
// CLUSTER_LINKS: CLUSTER_LINKS;
// clusterLinks: CLUSTER_LINKS;
CLUSTER_MEET: CLUSTER_MEET;
clusterMeet: CLUSTER_MEET;
CLUSTER_MYID: CLUSTER_MYID;
clusterMyId: CLUSTER_MYID;
// CLUSTER_NODES: CLUSTER_NODES;
// clusterNodes: CLUSTER_NODES;
// CLUSTER_REPLICAS: CLUSTER_REPLICAS;
// clusterReplicas: CLUSTER_REPLICAS;
CLUSTER_REPLICATE: CLUSTER_REPLICATE;
clusterReplicate: CLUSTER_REPLICATE;
CLUSTER_RESET: CLUSTER_RESET;
clusterReset: CLUSTER_RESET;
CLUSTER_SAVECONFIG: CLUSTER_SAVECONFIG;
clusterSaveConfig: CLUSTER_SAVECONFIG;
'CLUSTER_SET-CONFIG-EPOCH': CLUSTER_SET_CONFIG_EPOCH;
clusterSetConfigEpoch: CLUSTER_SET_CONFIG_EPOCH;
CLUSTER_SETSLOT: CLUSTER_SETSLOT;
clusterSetSlot: CLUSTER_SETSLOT;
CLUSTER_SLOTS: CLUSTER_SLOTS;
clusterSlots: CLUSTER_SLOTS;
COPY: COPY;
copy: COPY;
DBSIZE: DBSIZE;
@@ -658,8 +746,8 @@ type Commands = {
functionList: FUNCTION_LIST;
FUNCTION_LOAD: FUNCTION_LOAD;
functionLoad: FUNCTION_LOAD;
// FUNCTION_RESTORE: FUNCTION_RESTORE;
// functionRestore: FUNCTION_RESTORE;
FUNCTION_RESTORE: FUNCTION_RESTORE;
functionRestore: FUNCTION_RESTORE;
// FUNCTION_STATS: FUNCTION_STATS;
// functionStats: FUNCTION_STATS;
GEOADD: GEOADD;
@@ -1119,16 +1207,60 @@ export default {
clientPause: CLIENT_PAUSE,
CLIENT_SETNAME,
clientSetName: CLIENT_SETNAME,
CLIENT_TRACKING,
clientTracking: CLIENT_TRACKING,
CLIENT_TRACKINGINFO,
clientTrackingInfo: CLIENT_TRACKINGINFO,
CLIENT_UNPAUSE,
clientUnpause: CLIENT_UNPAUSE,
CLUSTER_ADDSLOTS,
clusterAddSlots: CLUSTER_ADDSLOTS,
CLUSTER_SLOTS,
clusterSlots: CLUSTER_SLOTS,
CLUSTER_ADDSLOTSRANGE,
clusterAddSlotsRange: CLUSTER_ADDSLOTSRANGE,
CLUSTER_BUMPEPOCH,
clusterBumpEpoch: CLUSTER_BUMPEPOCH,
'CLUSTER_COUNT-FAILURE-REPORTS': CLUSTER_COUNT_FAILURE_REPORTS,
clusterCountFailureReports: CLUSTER_COUNT_FAILURE_REPORTS,
CLUSTER_COUNTKEYSINSLOT,
clusterCountKeysInSlot: CLUSTER_COUNTKEYSINSLOT,
CLUSTER_DELSLOTS,
clusterDelSlots: CLUSTER_DELSLOTS,
CLUSTER_DELSLOTSRANGE,
clusterDelSlotsRange: CLUSTER_DELSLOTSRANGE,
CLUSTER_FAILOVER,
clusterFailover: CLUSTER_FAILOVER,
CLUSTER_FLUSHSLOTS,
clusterFlushSlots: CLUSTER_FLUSHSLOTS,
CLUSTER_FORGET,
clusterForget: CLUSTER_FORGET,
CLUSTER_GETKEYSINSLOT,
clusterGetKeysInSlot: CLUSTER_GETKEYSINSLOT,
// CLUSTER_INFO,
// clusterInfo: CLUSTER_INFO,
CLUSTER_KEYSLOT,
clusterKeySlot: CLUSTER_KEYSLOT,
// CLUSTER_LINKS,
// clusterLinks: CLUSTER_LINKS,
CLUSTER_MEET,
clusterMeet: CLUSTER_MEET,
CLUSTER_MYID,
clusterMyId: CLUSTER_MYID,
// CLUSTER_NODES,
// clusterNodes: CLUSTER_NODES,
// CLUSTER_REPLICAS,
// clusterReplicas: CLUSTER_REPLICAS,
CLUSTER_REPLICATE,
clusterReplicate: CLUSTER_REPLICATE,
CLUSTER_RESET,
clusterReset: CLUSTER_RESET,
CLUSTER_SAVECONFIG,
clusterSaveConfig: CLUSTER_SAVECONFIG,
'CLUSTER_SET-CONFIG-EPOCH': CLUSTER_SET_CONFIG_EPOCH,
clusterSetConfigEpoch: CLUSTER_SET_CONFIG_EPOCH,
CLUSTER_SETSLOT,
clusterSetSlot: CLUSTER_SETSLOT,
CLUSTER_SLOTS,
clusterSlots: CLUSTER_SLOTS,
COPY,
copy: COPY,
DBSIZE,
@@ -1563,4 +1695,4 @@ export default {
zUnion: ZUNION,
ZUNIONSTORE,
zUnionStore: ZUNIONSTORE
} as const satisfies Record<string, Command> as Commands;
} satisfies Record<string, Command> as Commands;

View File

@@ -4,7 +4,8 @@ import { setTimeout } from 'timers/promises';
const utils = new TestUtils({
dockerImageName: 'redis',
dockerImageVersionArgument: 'redis-version'
dockerImageVersionArgument: 'redis-version',
defaultDockerVersion: '7.2-rc'
});
export default utils;

View File

@@ -3,12 +3,15 @@ import {
RedisFunctions,
RedisScripts,
RespVersions,
TypeMapping,
CommandPolicies,
createClient,
RedisClientOptions,
RedisClientType,
createCluster,
RedisClusterOptions,
RedisClusterType
RedisClusterType,
RESP_TYPES
} from '@redis/client/index';
import { RedisServerDockerConfig, spawnRedisServer, spawnRedisCluster } from './dockers';
import yargs from 'yargs';
@@ -28,10 +31,11 @@ interface ClientTestOptions<
M extends RedisModules,
F extends RedisFunctions,
S extends RedisScripts,
RESP extends RespVersions
RESP extends RespVersions,
TYPE_MAPPING extends TypeMapping
> extends CommonTestOptions {
serverArguments: Array<string>;
clientOptions?: Partial<RedisClientOptions<M, F, S, RESP>>;
clientOptions?: Partial<RedisClientOptions<M, F, S, RESP, TYPE_MAPPING>>;
disableClientSetup?: boolean;
}
@@ -39,10 +43,12 @@ interface ClusterTestOptions<
M extends RedisModules,
F extends RedisFunctions,
S extends RedisScripts,
RESP extends RespVersions
RESP extends RespVersions,
TYPE_MAPPING extends TypeMapping,
POLICIES extends CommandPolicies
> extends CommonTestOptions {
serverArguments: Array<string>;
clusterConfiguration?: Partial<RedisClusterOptions<M, F, S, RESP>>;
clusterConfiguration?: Partial<RedisClusterOptions<M, F, S, RESP, TYPE_MAPPING, POLICIES>>;
numberOfMasters?: number;
numberOfReplicas?: number;
}
@@ -51,10 +57,12 @@ interface AllTestOptions<
M extends RedisModules,
F extends RedisFunctions,
S extends RedisScripts,
RESP extends RespVersions
RESP extends RespVersions,
TYPE_MAPPING extends TypeMapping,
POLICIES extends CommandPolicies
> {
client: ClientTestOptions<M, F, S, RESP>;
cluster: ClusterTestOptions<M, F, S, RESP>;
client: ClientTestOptions<M, F, S, RESP, TYPE_MAPPING>;
cluster: ClusterTestOptions<M, F, S, RESP, TYPE_MAPPING, POLICIES>;
}
interface Version {
@@ -135,11 +143,12 @@ export default class TestUtils {
M extends RedisModules = {},
F extends RedisFunctions = {},
S extends RedisScripts = {},
RESP extends RespVersions = 2
RESP extends RespVersions = 2,
TYPE_MAPPING extends TypeMapping = {}
>(
title: string,
fn: (client: RedisClientType<M, F, S, RESP>) => unknown,
options: ClientTestOptions<M, F, S, RESP>
fn: (client: RedisClientType<M, F, S, RESP, TYPE_MAPPING>) => unknown,
options: ClientTestOptions<M, F, S, RESP, TYPE_MAPPING>
): void {
let dockerPromise: ReturnType<typeof spawnRedisServer>;
if (this.isVersionGreaterThan(options.minimumDockerVersion)) {
@@ -187,8 +196,10 @@ export default class TestUtils {
M extends RedisModules,
F extends RedisFunctions,
S extends RedisScripts,
RESP extends RespVersions
>(cluster: RedisClusterType<M, F, S, RESP>): Promise<unknown> {
RESP extends RespVersions,
TYPE_MAPPING extends TypeMapping,
POLICIES extends CommandPolicies
>(cluster: RedisClusterType<M, F, S, RESP, TYPE_MAPPING, POLICIES>): Promise<unknown> {
return Promise.all(
cluster.masters.map(async ({ client }) => {
if (client) {
@@ -202,11 +213,13 @@ export default class TestUtils {
M extends RedisModules = {},
F extends RedisFunctions = {},
S extends RedisScripts = {},
RESP extends RespVersions = 2
RESP extends RespVersions = 2,
TYPE_MAPPING extends TypeMapping = {},
POLICIES extends CommandPolicies = {}
>(
title: string,
fn: (cluster: RedisClusterType<M, F, S, RESP>) => unknown,
options: ClusterTestOptions<M, F, S, RESP>
fn: (cluster: RedisClusterType<M, F, S, RESP, TYPE_MAPPING, POLICIES>) => unknown,
options: ClusterTestOptions<M, F, S, RESP, TYPE_MAPPING, POLICIES>
): void {
let dockersPromise: ReturnType<typeof spawnRedisCluster>;
if (this.isVersionGreaterThan(options.minimumDockerVersion)) {
@@ -253,11 +266,13 @@ export default class TestUtils {
M extends RedisModules = {},
F extends RedisFunctions = {},
S extends RedisScripts = {},
RESP extends RespVersions = 2
RESP extends RespVersions = 2,
TYPE_MAPPING extends TypeMapping = {},
POLICIES extends CommandPolicies = {}
>(
title: string,
fn: (client: RedisClientType<M, F, S, RESP> | RedisClusterType<M, F, S, RESP>) => unknown,
options: AllTestOptions<M, F, S, RESP>
fn: (client: RedisClientType<M, F, S, RESP, TYPE_MAPPING> | RedisClusterType<M, F, S, RESP, TYPE_MAPPING, POLICIES>) => unknown,
options: AllTestOptions<M, F, S, RESP, TYPE_MAPPING, POLICIES>
) {
this.testWithClient(`client.${title}`, fn, options.client);
this.testWithCluster(`cluster.${title}`, fn, options.cluster);