diff --git a/docs/v4-to-v5.md b/docs/v4-to-v5.md index ad06eec97a..fde8e28379 100644 --- a/docs/v4-to-v5.md +++ b/docs/v4-to-v5.md @@ -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` -> `Array` (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) \ No newline at end of file +[^map-keys]: [TODO](https://github.com/redis/node-redis/discussions/2506) + +[^future-proofing]: TODO \ No newline at end of file diff --git a/packages/client/index.ts b/packages/client/index.ts index c9b93f3377..09874984de 100644 --- a/packages/client/index.ts +++ b/packages/client/index.ts @@ -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'; diff --git a/packages/client/lib/client/commands-queue.ts b/packages/client/lib/client/commands-queue.ts index b3ede1ccd6..58699174cd 100644 --- a/packages/client/lib/client/commands-queue.ts +++ b/packages/client/lib/client/commands-queue.ts @@ -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 { chainId?: symbol; asap?: boolean; abortSignal?: AbortSignal; - typeMapping?: TypeMapping; + /** + * Maps bettween RESP and JavaScript types + */ + typeMapping?: T; } export interface CommandWaitingToBeSent extends CommandWaitingForReply { diff --git a/packages/client/lib/client/index.spec.ts b/packages/client/lib/client/index.spec.ts index f1e88bb140..7a259720ef 100644 --- a/packages/client/lib/client/index.spec.ts +++ b/packages/client/lib/client/index.spec.ts @@ -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, }); diff --git a/packages/client/lib/client/index.ts b/packages/client/lib/client/index.ts index 75f94333f7..3d43ae55b8 100644 --- a/packages/client/lib/client/index.ts +++ b/packages/client/lib/client/index.ts @@ -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{ + commandOptions?: CommandOptions; +} + 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, TypeMappingOption { +> extends ClientCommander { /** * `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 { - /** - * 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 ); -type ProxyClient = RedisClient & { commandOptions?: CommandOptions }; +type ProxyClient = RedisClient; type NamespaceProxyClient = { self: ProxyClient }; @@ -153,7 +155,7 @@ export default class RedisClient< const transformReply = getTransformReply(command, resp); return async function (this: ProxyClient, ...args: Array) { 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) { 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) { 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 & TypeMappingOption) { + >(config?: ClientCommander) { const Client = attachConfig({ BaseClass: RedisClient, commands: COMMANDS, @@ -282,10 +284,11 @@ export default class RedisClient< self = this; - private readonly _options?: RedisClientOptions; + private readonly _options?: RedisClientOptions; private readonly _socket: RedisSocket; private readonly _queue: RedisCommandsQueue; private _selectedDB = 0; + private _commandOptions?: CommandOptions; get options(): RedisClientOptions | undefined { return this._options; @@ -303,14 +306,14 @@ export default class RedisClient< return this._queue.isPubSubActive; } - constructor(options?: RedisClientOptions) { + constructor(options?: RedisClientOptions) { super(); this._options = this._initiateOptions(options); this._queue = this._initiateQueue(); this._socket = this._initiateSocket(); } - private _initiateOptions(options?: RedisClientOptions): RedisClientOptions | undefined { + private _initiateOptions(options?: RedisClientOptions): RedisClientOptions | 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(options: T) { + withCommandOptions< + OPTIONS extends CommandOptions, + 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>) { - 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 }), diff --git a/packages/client/lib/client/socket.spec.ts b/packages/client/lib/client/socket.spec.ts index eb555351ac..ad874f06c7 100644 --- a/packages/client/lib/client/socket.spec.ts +++ b/packages/client/lib/client/socket.spec.ts @@ -4,84 +4,84 @@ import { once } from 'events'; import RedisSocket, { RedisSocketOptions } from './socket'; describe('Socket', () => { - function createSocket(options: RedisSocketOptions): RedisSocket { - const socket = new RedisSocket( - () => Promise.resolve(), - options - ); + function createSocket(options: RedisSocketOptions): RedisSocket { + const socket = new RedisSocket( + () => Promise.resolve(), + options + ); - socket.on('error', () => { - // ignore errors - }); - - return socket; - } - - describe('reconnectStrategy', () => { - it('false', async () => { - const socket = createSocket({ - host: 'error', - connectTimeout: 1, - reconnectStrategy: false - }); - - await assert.rejects(socket.connect()); - - assert.equal(socket.isOpen, false); - }); - - it('0', async () => { - const socket = createSocket({ - host: 'error', - connectTimeout: 1, - reconnectStrategy: 0 - }); - - socket.connect(); - await once(socket, 'error'); - assert.equal(socket.isOpen, true); - assert.equal(socket.isReady, false); - socket.disconnect(); - assert.equal(socket.isOpen, false); - }); - - it('custom strategy', async () => { - const numberOfRetries = 3; - - const reconnectStrategy = spy((retries: number) => { - assert.equal(retries + 1, reconnectStrategy.callCount); - - if (retries === numberOfRetries) return new Error(`${numberOfRetries}`); - - return 0; - }); - - const socket = createSocket({ - host: 'error', - connectTimeout: 1, - reconnectStrategy - }); - - await assert.rejects(socket.connect(), { - message: `${numberOfRetries}` - }); - - assert.equal(socket.isOpen, false); - }); - - it('should handle errors', async () => { - const socket = createSocket({ - host: 'error', - connectTimeout: 1, - reconnectStrategy(retries: number) { - if (retries === 1) return new Error('done'); - throw new Error(); - } - }); - - await assert.rejects(socket.connect()); - - assert.equal(socket.isOpen, false); - }); + socket.on('error', () => { + // ignore errors }); + + return socket; + } + + describe('reconnectStrategy', () => { + it('false', async () => { + const socket = createSocket({ + host: 'error', + connectTimeout: 1, + reconnectStrategy: false + }); + + await assert.rejects(socket.connect()); + + assert.equal(socket.isOpen, false); + }); + + it('0', async () => { + const socket = createSocket({ + host: 'error', + connectTimeout: 1, + reconnectStrategy: 0 + }); + + socket.connect(); + await once(socket, 'error'); + assert.equal(socket.isOpen, true); + assert.equal(socket.isReady, false); + socket.destroy(); + assert.equal(socket.isOpen, false); + }); + + it('custom strategy', async () => { + const numberOfRetries = 3; + + const reconnectStrategy = spy((retries: number) => { + assert.equal(retries + 1, reconnectStrategy.callCount); + + if (retries === numberOfRetries) return new Error(`${numberOfRetries}`); + + return 0; + }); + + const socket = createSocket({ + host: 'error', + connectTimeout: 1, + reconnectStrategy + }); + + await assert.rejects(socket.connect(), { + message: `${numberOfRetries}` + }); + + assert.equal(socket.isOpen, false); + }); + + it('should handle errors', async () => { + const socket = createSocket({ + host: 'error', + connectTimeout: 1, + reconnectStrategy(retries: number) { + if (retries === 1) return new Error('done'); + throw new Error(); + } + }); + + await assert.rejects(socket.connect()); + + assert.equal(socket.isOpen, false); + }); + }); }); diff --git a/packages/client/lib/cluster/index.ts b/packages/client/lib/cluster/index.ts index 17a3dc0904..e0386e74c5 100644 --- a/packages/client/lib/cluster/index.ts +++ b/packages/client/lib/cluster/index.ts @@ -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{ + commandOptions?: ClusterCommandOptions; +} + export type RedisClusterClientOptions = Omit< RedisClientOptions, - 'modules' | 'functions' | 'scripts' | 'database' | 'RESP' + keyof ClusterCommander >; export interface RedisClusterOptions< M extends RedisModules = RedisModules, F extends RedisFunctions = RedisFunctions, S extends RedisScripts = RedisScripts, - RESP extends RespVersions = RespVersions -> extends CommanderConfig { + RESP extends RespVersions = RespVersions, + TYPE_MAPPING extends TypeMapping = TypeMapping, + POLICIES extends CommandPolicies = CommandPolicies +> extends ClusterCommander { /** * 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 & WithCommands; // & WithModules & WithFunctions & WithScripts -export interface ClusterCommandOptions extends CommandOptions { - policies?: CommandPolicies; +export interface ClusterCommandOptions< + TYPE_MAPPING extends TypeMapping = TypeMapping, + POLICIES extends CommandPolicies = CommandPolicies +> extends CommandOptions { + policies?: POLICIES; } -type ProxyCluster = RedisCluster & { commandOptions?: ClusterCommandOptions }; +type ProxyCluster = RedisCluster; 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) { + RESP extends RespVersions = 2, + TYPE_MAPPING extends TypeMapping = {}, + POLICIES extends CommandPolicies = {} + >(config?: ClusterCommander) { const Cluster = attachConfig({ BaseClass: RedisCluster, commands: COMMANDS, @@ -217,7 +235,7 @@ export default class RedisCluster< return (options?: Omit>) => { // 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; + return Object.create(new Cluster(options)) as RedisClusterType; }; } @@ -225,15 +243,19 @@ export default class RedisCluster< M extends RedisModules = {}, F extends RedisFunctions = {}, S extends RedisScripts = {}, - RESP extends RespVersions = 2 - >(options?: RedisClusterOptions) { + RESP extends RespVersions = 2, + TYPE_MAPPING extends TypeMapping = {}, + POLICIES extends CommandPolicies = {} + >(options?: RedisClusterOptions) { return RedisCluster.factory(options)(options); } - private readonly _options: RedisClusterOptions; + private readonly _options: RedisClusterOptions; private readonly _slots: RedisClusterSlots; + private _commandOptions?: ClusterCommandOptions; + /** * 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) { + constructor(options: RedisClusterOptions) { super(); this._options = options; this._slots = new RedisClusterSlots(options, this.emit.bind(this)); + + if (options?.commandOptions) { + this._commandOptions = options.commandOptions; + } } - duplicate(overrides?: Partial>): RedisClusterType { + 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>) { 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(options: T) { + withCommandOptions< + OPTIONS extends ClusterCommandOptions, + 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, diff --git a/packages/client/lib/commands/ACL_LOG.spec.ts b/packages/client/lib/commands/ACL_LOG.spec.ts index b2f6235230..8e66ea063a 100644 --- a/packages/client/lib/commands/ACL_LOG.spec.ts +++ b/packages/client/lib/commands/ACL_LOG.spec.ts @@ -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); }); diff --git a/packages/client/lib/commands/ACL_LOG.ts b/packages/client/lib/commands/ACL_LOG.ts index 4140111ecf..55172095d1 100644 --- a/packages/client/lib/commands/ACL_LOG.ts +++ b/packages/client/lib/commands/ACL_LOG.ts @@ -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, 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) => ({ - 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) => 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; diff --git a/packages/client/lib/commands/ACL_WHOAMI.ts b/packages/client/lib/commands/ACL_WHOAMI.ts index 5aa56c4d35..81a1c84a3c 100644 --- a/packages/client/lib/commands/ACL_WHOAMI.ts +++ b/packages/client/lib/commands/ACL_WHOAMI.ts @@ -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; diff --git a/packages/client/lib/commands/CLIENT_TRACKING.spec.ts b/packages/client/lib/commands/CLIENT_TRACKING.spec.ts index bbd0b13e77..0a8ea58e08 100644 --- a/packages/client/lib/commands/CLIENT_TRACKING.spec.ts +++ b/packages/client/lib/commands/CLIENT_TRACKING.spec.ts @@ -1,101 +1,101 @@ 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]); + testUtils.isVersionGreaterThanHook([6]); - describe('transformArguments', () => { - describe('true', () => { - it('simple', () => { - assert.deepEqual( - transformArguments(true), - ['CLIENT', 'TRACKING', 'ON'] - ); - }); + describe('transformArguments', () => { + describe('true', () => { + it('simple', () => { + assert.deepEqual( + CLIENT_TRACKING.transformArguments(true), + ['CLIENT', 'TRACKING', 'ON'] + ); + }); - it('with REDIRECT', () => { - assert.deepEqual( - transformArguments(true, { - REDIRECT: 1 - }), - ['CLIENT', 'TRACKING', 'ON', 'REDIRECT', '1'] - ); - }); + it('with REDIRECT', () => { + assert.deepEqual( + CLIENT_TRACKING.transformArguments(true, { + REDIRECT: 1 + }), + ['CLIENT', 'TRACKING', 'ON', 'REDIRECT', '1'] + ); + }); - describe('with BCAST', () => { - it('simple', () => { - assert.deepEqual( - transformArguments(true, { - BCAST: true - }), - ['CLIENT', 'TRACKING', 'ON', 'BCAST'] - ); - }); - - describe('with PREFIX', () => { - it('string', () => { - assert.deepEqual( - transformArguments(true, { - BCAST: true, - PREFIX: 'prefix' - }), - ['CLIENT', 'TRACKING', 'ON', 'BCAST', 'PREFIX', 'prefix'] - ); - }); - - it('array', () => { - assert.deepEqual( - transformArguments(true, { - BCAST: true, - PREFIX: ['1', '2'] - }), - ['CLIENT', 'TRACKING', 'ON', 'BCAST', 'PREFIX', '1', 'PREFIX', '2'] - ); - }); - }); - }); - - it('with OPTIN', () => { - assert.deepEqual( - transformArguments(true, { - OPTIN: true - }), - ['CLIENT', 'TRACKING', 'ON', 'OPTIN'] - ); - }); - - it('with OPTOUT', () => { - assert.deepEqual( - transformArguments(true, { - OPTOUT: true - }), - ['CLIENT', 'TRACKING', 'ON', 'OPTOUT'] - ); - }); - - it('with NOLOOP', () => { - assert.deepEqual( - transformArguments(true, { - NOLOOP: true - }), - ['CLIENT', 'TRACKING', 'ON', 'NOLOOP'] - ); - }); + describe('with BCAST', () => { + it('simple', () => { + assert.deepEqual( + CLIENT_TRACKING.transformArguments(true, { + BCAST: true + }), + ['CLIENT', 'TRACKING', 'ON', 'BCAST'] + ); }); - it('false', () => { + describe('with PREFIX', () => { + it('string', () => { assert.deepEqual( - transformArguments(false), - ['CLIENT', 'TRACKING', 'OFF'] + CLIENT_TRACKING.transformArguments(true, { + BCAST: true, + PREFIX: 'prefix' + }), + ['CLIENT', 'TRACKING', 'ON', 'BCAST', 'PREFIX', 'prefix'] ); + }); + + it('array', () => { + assert.deepEqual( + CLIENT_TRACKING.transformArguments(true, { + BCAST: true, + PREFIX: ['1', '2'] + }), + ['CLIENT', 'TRACKING', 'ON', 'BCAST', 'PREFIX', '1', 'PREFIX', '2'] + ); + }); }); + }); + + it('with OPTIN', () => { + assert.deepEqual( + CLIENT_TRACKING.transformArguments(true, { + OPTIN: true + }), + ['CLIENT', 'TRACKING', 'ON', 'OPTIN'] + ); + }); + + it('with OPTOUT', () => { + assert.deepEqual( + CLIENT_TRACKING.transformArguments(true, { + OPTOUT: true + }), + ['CLIENT', 'TRACKING', 'ON', 'OPTOUT'] + ); + }); + + it('with NOLOOP', () => { + assert.deepEqual( + CLIENT_TRACKING.transformArguments(true, { + NOLOOP: true + }), + ['CLIENT', 'TRACKING', 'ON', 'NOLOOP'] + ); + }); }); - testUtils.testWithClient('client.clientTracking', async client => { - assert.equal( - await client.clientTracking(false), - 'OK' - ); - }, GLOBAL.SERVERS.OPEN); + it('false', () => { + assert.deepEqual( + CLIENT_TRACKING.transformArguments(false), + ['CLIENT', 'TRACKING', 'OFF'] + ); + }); + }); + + testUtils.testWithClient('client.clientTracking', async client => { + assert.equal( + await client.clientTracking(false), + 'OK' + ); + }, GLOBAL.SERVERS.OPEN); }); diff --git a/packages/client/lib/commands/CLIENT_TRACKING.ts b/packages/client/lib/commands/CLIENT_TRACKING.ts index 03926b5300..5eebb8492a 100644 --- a/packages/client/lib/commands/CLIENT_TRACKING.ts +++ b/packages/client/lib/commands/CLIENT_TRACKING.ts @@ -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; -// } +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( + mode: M, + options?: M extends true ? ClientTrackingOptions : never + ) { + const args: Array = [ + 'CLIENT', + 'TRACKING', + mode ? 'ON' : 'OFF' + ]; -// export function transformArguments( -// 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; +} diff --git a/packages/client/lib/commands/CLIENT_TRACKINGINFO.spec.ts b/packages/client/lib/commands/CLIENT_TRACKINGINFO.spec.ts index 49bffe7612..beee6649f7 100644 --- a/packages/client/lib/commands/CLIENT_TRACKINGINFO.spec.ts +++ b/packages/client/lib/commands/CLIENT_TRACKINGINFO.spec.ts @@ -1,25 +1,26 @@ 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]); + testUtils.isVersionGreaterThanHook([6, 2]); - it('transformArguments', () => { - assert.deepEqual( - transformArguments(), - ['CLIENT', 'TRACKINGINFO'] - ); - }); + it('transformArguments', () => { + assert.deepEqual( + CLIENT_TRACKINGINFO.transformArguments(), + ['CLIENT', 'TRACKINGINFO'] + ); + }); - testUtils.testWithClient('client.clientTrackingInfo', async client => { - assert.deepEqual( - await client.clientTrackingInfo(), - { - flags: new Set(['off']), - redirect: -1, - prefixes: [] - } - ); - }, GLOBAL.SERVERS.OPEN); + testUtils.testWithClient('client.clientTrackingInfo', async client => { + assert.deepEqual( + await client.clientTrackingInfo(), + { + flags: ['off'], + redirect: -1, + prefixes: [] + } + ); + }, GLOBAL.SERVERS.OPEN); }); diff --git a/packages/client/lib/commands/CLIENT_TRACKINGINFO.ts b/packages/client/lib/commands/CLIENT_TRACKINGINFO.ts index fd897bb67c..4c443532f5 100644 --- a/packages/client/lib/commands/CLIENT_TRACKINGINFO.ts +++ b/packages/client/lib/commands/CLIENT_TRACKINGINFO.ts @@ -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<'redirect'>, NumberReply], + [BlobStringReply<'prefixes'>, ArrayReply] +]>; -// type RawReply = [ -// 'flags', -// Array, -// 'redirect', -// number, -// 'prefixes', -// Array -// ]; - -// interface Reply { -// flags: Set; -// redirect: number; -// prefixes: Array; -// } - -// 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) => ({ + flags: reply[1], + redirect: reply[3], + prefixes: reply[5] + }), + 3: undefined as unknown as () => TrackingInfo + } +} as const satisfies Command; diff --git a/packages/client/lib/commands/CLIENT_UNPAUSE.spec.ts b/packages/client/lib/commands/CLIENT_UNPAUSE.spec.ts index 73c731ee87..1c59c3b82c 100644 --- a/packages/client/lib/commands/CLIENT_UNPAUSE.spec.ts +++ b/packages/client/lib/commands/CLIENT_UNPAUSE.spec.ts @@ -1,21 +1,21 @@ 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]); + testUtils.isVersionGreaterThanHook([6, 2]); - it('transformArguments', () => { - assert.deepEqual( - transformArguments(), - ['CLIENT', 'UNPAUSE'] - ); - }); + it('transformArguments', () => { + assert.deepEqual( + CLIENT_UNPAUSE.transformArguments(), + ['CLIENT', 'UNPAUSE'] + ); + }); - testUtils.testWithClient('client.unpause', async client => { - assert.equal( - await client.clientUnpause(), - 'OK' - ); - }, GLOBAL.SERVERS.OPEN); + testUtils.testWithClient('client.clientUnpause', async client => { + assert.equal( + await client.clientUnpause(), + 'OK' + ); + }, GLOBAL.SERVERS.OPEN); }); diff --git a/packages/client/lib/commands/CLUSTER_ADDSLOTS.spec.ts b/packages/client/lib/commands/CLUSTER_ADDSLOTS.spec.ts index c16476de43..4faed75422 100644 --- a/packages/client/lib/commands/CLUSTER_ADDSLOTS.spec.ts +++ b/packages/client/lib/commands/CLUSTER_ADDSLOTS.spec.ts @@ -1,20 +1,20 @@ 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', '0'] - ); - }); - - it('multiple', () => { - assert.deepEqual( - transformArguments([0, 1]), - ['CLUSTER', 'ADDSLOTS', '0', '1'] - ); - }); + describe('transformArguments', () => { + it('single', () => { + assert.deepEqual( + CLUSTER_ADDSLOTS.transformArguments(0), + ['CLUSTER', 'ADDSLOTS', '0'] + ); }); + + it('multiple', () => { + assert.deepEqual( + CLUSTER_ADDSLOTS.transformArguments([0, 1]), + ['CLUSTER', 'ADDSLOTS', '0', '1'] + ); + }); + }); }); diff --git a/packages/client/lib/commands/CLUSTER_ADDSLOTSRANGE.spec.ts b/packages/client/lib/commands/CLUSTER_ADDSLOTSRANGE.spec.ts index ebd1e3445f..496d52bd59 100644 --- a/packages/client/lib/commands/CLUSTER_ADDSLOTSRANGE.spec.ts +++ b/packages/client/lib/commands/CLUSTER_ADDSLOTSRANGE.spec.ts @@ -1,29 +1,32 @@ 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', () => { - describe('transformArguments', () => { - it('single', () => { - assert.deepEqual( - transformArguments({ - start: 0, - end: 1 - }), - ['CLUSTER', 'ADDSLOTSRANGE', '0', '1'] - ); - }); + testUtils.isVersionGreaterThanHook([7, 0]); - it('multiple', () => { - assert.deepEqual( - transformArguments([{ - start: 0, - end: 1 - }, { - start: 2, - end: 3 - }]), - ['CLUSTER', 'ADDSLOTSRANGE', '0', '1', '2', '3'] - ); - }); + describe('transformArguments', () => { + it('single', () => { + assert.deepEqual( + CLUSTER_ADDSLOTSRANGE.transformArguments({ + start: 0, + end: 1 + }), + ['CLUSTER', 'ADDSLOTSRANGE', '0', '1'] + ); }); + + it('multiple', () => { + assert.deepEqual( + CLUSTER_ADDSLOTSRANGE.transformArguments([{ + start: 0, + end: 1 + }, { + start: 2, + end: 3 + }]), + ['CLUSTER', 'ADDSLOTSRANGE', '0', '1', '2', '3'] + ); + }); + }); }); diff --git a/packages/client/lib/commands/CLUSTER_BUMPEPOCH.spec.ts b/packages/client/lib/commands/CLUSTER_BUMPEPOCH.spec.ts index edb68b3b3b..4c064a0812 100644 --- a/packages/client/lib/commands/CLUSTER_BUMPEPOCH.spec.ts +++ b/packages/client/lib/commands/CLUSTER_BUMPEPOCH.spec.ts @@ -1,20 +1,20 @@ 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'] - ); - }); + it('transformArguments', () => { + assert.deepEqual( + CLUSTER_BUMPEPOCH.transformArguments(), + ['CLUSTER', 'BUMPEPOCH'] + ); + }); - testUtils.testWithCluster('clusterNode.clusterBumpEpoch', async cluster => { - const client = await cluster.nodeClient(cluster.masters[0]); - assert.equal( - typeof await client.clusterBumpEpoch(), - 'string' - ); - }, GLOBAL.SERVERS.OPEN); + testUtils.testWithCluster('clusterNode.clusterBumpEpoch', async cluster => { + const client = await cluster.nodeClient(cluster.masters[0]); + assert.equal( + typeof await client.clusterBumpEpoch(), + 'string' + ); + }, GLOBAL.SERVERS.OPEN); }); diff --git a/packages/client/lib/commands/CLUSTER_KEYSLOT.spec.ts b/packages/client/lib/commands/CLUSTER_KEYSLOT.spec.ts index 3bbc9f9cb2..1af795be5e 100644 --- a/packages/client/lib/commands/CLUSTER_KEYSLOT.spec.ts +++ b/packages/client/lib/commands/CLUSTER_KEYSLOT.spec.ts @@ -1,20 +1,20 @@ 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', 'key'] - ); - }); + it('transformArguments', () => { + assert.deepEqual( + CLUSTER_KEYSLOT.transformArguments('key'), + ['CLUSTER', 'KEYSLOT', 'key'] + ); + }); - testUtils.testWithCluster('clusterNode.clusterKeySlot', async cluster => { - const client = await cluster.nodeClient(cluster.masters[0]); - assert.equal( - typeof await client.clusterKeySlot('key'), - 'number' - ); - }, GLOBAL.CLUSTERS.OPEN); + testUtils.testWithCluster('clusterNode.clusterKeySlot', async cluster => { + const client = await cluster.nodeClient(cluster.masters[0]); + assert.equal( + typeof await client.clusterKeySlot('key'), + 'number' + ); + }, GLOBAL.CLUSTERS.OPEN); }); diff --git a/packages/client/lib/commands/CLUSTER_MEET.spec.ts b/packages/client/lib/commands/CLUSTER_MEET.spec.ts index 50a5393efa..283c996cf8 100644 --- a/packages/client/lib/commands/CLUSTER_MEET.spec.ts +++ b/packages/client/lib/commands/CLUSTER_MEET.spec.ts @@ -1,11 +1,11 @@ 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', '127.0.0.1', '6379'] - ); - }); + it('transformArguments', () => { + assert.deepEqual( + CLUSTER_MEET.transformArguments('127.0.0.1', 6379), + ['CLUSTER', 'MEET', '127.0.0.1', '6379'] + ); + }); }); diff --git a/packages/client/lib/commands/CLUSTER_MEET.ts b/packages/client/lib/commands/CLUSTER_MEET.ts index 3e5d343b6f..949412702d 100644 --- a/packages/client/lib/commands/CLUSTER_MEET.ts +++ b/packages/client/lib/commands/CLUSTER_MEET.ts @@ -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; diff --git a/packages/client/lib/commands/CLUSTER_REPLICATE.spec.ts b/packages/client/lib/commands/CLUSTER_REPLICATE.spec.ts index 926b7dd0a7..bb6335a711 100644 --- a/packages/client/lib/commands/CLUSTER_REPLICATE.spec.ts +++ b/packages/client/lib/commands/CLUSTER_REPLICATE.spec.ts @@ -1,11 +1,11 @@ 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', '0'] - ); - }); + it('transformArguments', () => { + assert.deepEqual( + CLUSTER_REPLICATE.transformArguments('0'), + ['CLUSTER', 'REPLICATE', '0'] + ); + }); }); diff --git a/packages/client/lib/commands/CLUSTER_RESET.spec.ts b/packages/client/lib/commands/CLUSTER_RESET.spec.ts index 340da7457c..c9186ca7b1 100644 --- a/packages/client/lib/commands/CLUSTER_RESET.spec.ts +++ b/packages/client/lib/commands/CLUSTER_RESET.spec.ts @@ -1,20 +1,22 @@ 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'] - ); - }); - - it('with mode', () => { - assert.deepEqual( - transformArguments('HARD'), - ['CLUSTER', 'RESET', 'HARD'] - ); - }); + describe('transformArguments', () => { + it('simple', () => { + assert.deepEqual( + CLUSTER_RESET.transformArguments(), + ['CLUSTER', 'RESET'] + ); }); + + it('with mode', () => { + assert.deepEqual( + CLUSTER_RESET.transformArguments({ + mode: 'HARD' + }), + ['CLUSTER', 'RESET', 'HARD'] + ); + }); + }); }); diff --git a/packages/client/lib/commands/CLUSTER_RESET.ts b/packages/client/lib/commands/CLUSTER_RESET.ts index c6901e045d..7aaac9d3b0 100644 --- a/packages/client/lib/commands/CLUSTER_RESET.ts +++ b/packages/client/lib/commands/CLUSTER_RESET.ts @@ -1,11 +1,20 @@ -export function transformArguments(mode?: 'HARD' | 'SOFT'): Array { +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; diff --git a/packages/client/lib/commands/CLUSTER_SAVECONFIG.spec.ts b/packages/client/lib/commands/CLUSTER_SAVECONFIG.spec.ts index 81ba4aa250..d49b32a426 100644 --- a/packages/client/lib/commands/CLUSTER_SAVECONFIG.spec.ts +++ b/packages/client/lib/commands/CLUSTER_SAVECONFIG.spec.ts @@ -1,20 +1,20 @@ 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'] - ); - }); + it('transformArguments', () => { + assert.deepEqual( + CLUSTER_SAVECONFIG.transformArguments(), + ['CLUSTER', 'SAVECONFIG'] + ); + }); - testUtils.testWithCluster('clusterNode.clusterSaveConfig', async cluster => { - const client = await cluster.nodeClient(cluster.masters[0]); - assert.equal( - await client.clusterSaveConfig(), - 'OK' - ); - }, GLOBAL.CLUSTERS.OPEN); + testUtils.testWithCluster('clusterNode.clusterSaveConfig', async cluster => { + const client = await cluster.nodeClient(cluster.masters[0]); + assert.equal( + await client.clusterSaveConfig(), + 'OK' + ); + }, GLOBAL.CLUSTERS.OPEN); }); diff --git a/packages/client/lib/commands/CLUSTER_SAVECONFIG.ts b/packages/client/lib/commands/CLUSTER_SAVECONFIG.ts index 7e7fb181cc..489ffd27e4 100644 --- a/packages/client/lib/commands/CLUSTER_SAVECONFIG.ts +++ b/packages/client/lib/commands/CLUSTER_SAVECONFIG.ts @@ -1,5 +1,11 @@ -export function transformArguments(): Array { - 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'; diff --git a/packages/client/lib/commands/CLUSTER_SET-CONFIG-EPOCH.spec.ts b/packages/client/lib/commands/CLUSTER_SET-CONFIG-EPOCH.spec.ts index dd24157416..b4c26b659c 100644 --- a/packages/client/lib/commands/CLUSTER_SET-CONFIG-EPOCH.spec.ts +++ b/packages/client/lib/commands/CLUSTER_SET-CONFIG-EPOCH.spec.ts @@ -1,11 +1,11 @@ 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', '0'] - ); - }); + it('transformArguments', () => { + assert.deepEqual( + CLUSTER_SET_CONFIG_EPOCH.transformArguments(0), + ['CLUSTER', 'SET-CONFIG-EPOCH', '0'] + ); + }); }); diff --git a/packages/client/lib/commands/CLUSTER_SET-CONFIG-EPOCH.ts b/packages/client/lib/commands/CLUSTER_SET-CONFIG-EPOCH.ts index c50a6b9d3a..2a650840c4 100644 --- a/packages/client/lib/commands/CLUSTER_SET-CONFIG-EPOCH.ts +++ b/packages/client/lib/commands/CLUSTER_SET-CONFIG-EPOCH.ts @@ -1,5 +1,10 @@ -export function transformArguments(configEpoch: number): Array { - 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; diff --git a/packages/client/lib/commands/CLUSTER_SETSLOT.spec.ts b/packages/client/lib/commands/CLUSTER_SETSLOT.spec.ts index 0f46aafd13..cbdbaa8929 100644 --- a/packages/client/lib/commands/CLUSTER_SETSLOT.spec.ts +++ b/packages/client/lib/commands/CLUSTER_SETSLOT.spec.ts @@ -1,20 +1,20 @@ 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', '0', 'IMPORTING'] - ); - }); - - it('with nodeId', () => { - assert.deepEqual( - transformArguments(0, ClusterSlotStates.IMPORTING, 'nodeId'), - ['CLUSTER', 'SETSLOT', '0', 'IMPORTING', 'nodeId'] - ); - }); + describe('transformArguments', () => { + it('simple', () => { + assert.deepEqual( + CLUSTER_SETSLOT.transformArguments(0, CLUSTER_SLOT_STATES.IMPORTING), + ['CLUSTER', 'SETSLOT', '0', 'IMPORTING'] + ); }); + + it('with nodeId', () => { + assert.deepEqual( + CLUSTER_SETSLOT.transformArguments(0, CLUSTER_SLOT_STATES.IMPORTING, 'nodeId'), + ['CLUSTER', 'SETSLOT', '0', 'IMPORTING', 'nodeId'] + ); + }); + }); }); diff --git a/packages/client/lib/commands/CLUSTER_SETSLOT.ts b/packages/client/lib/commands/CLUSTER_SETSLOT.ts index c01505c71a..e79cf88b4a 100644 --- a/packages/client/lib/commands/CLUSTER_SETSLOT.ts +++ b/packages/client/lib/commands/CLUSTER_SETSLOT.ts @@ -1,22 +1,25 @@ -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 { +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) { - args.push(nodeId); + args.push(nodeId); } return args; -} - -export declare function transformReply(): 'OK'; + }, + transformReply: undefined as unknown as () => SimpleStringReply<'OK'> +} as const satisfies Command; diff --git a/packages/client/lib/commands/CLUSTER_SLOTS.spec.ts b/packages/client/lib/commands/CLUSTER_SLOTS.spec.ts index 6efbfe13ce..4650fe83b2 100644 --- a/packages/client/lib/commands/CLUSTER_SLOTS.spec.ts +++ b/packages/client/lib/commands/CLUSTER_SLOTS.spec.ts @@ -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'] - ); - }); + it('transformArguments', () => { + assert.deepEqual( + 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); }); diff --git a/packages/client/lib/commands/FUNCTION_RESTORE.spec.ts b/packages/client/lib/commands/FUNCTION_RESTORE.spec.ts index 546bc4a363..09f46b32db 100644 --- a/packages/client/lib/commands/FUNCTION_RESTORE.spec.ts +++ b/packages/client/lib/commands/FUNCTION_RESTORE.spec.ts @@ -30,7 +30,9 @@ describe('FUNCTION RESTORE', () => { await client.withTypeMapping({ [RESP_TYPES.BLOB_STRING]: Buffer }).functionDump(), - 'FLUSH' + { + mode: 'REPLACE' + } ), 'OK' ); diff --git a/packages/client/lib/commands/index.ts b/packages/client/lib/commands/index.ts index 81cf34b525..aca729b4e1 100644 --- a/packages/client/lib/commands/index.ts +++ b/packages/client/lib/commands/index.ts @@ -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 as Commands; +} satisfies Record as Commands; diff --git a/packages/client/lib/test-utils.ts b/packages/client/lib/test-utils.ts index fa40d0a548..4aca735c92 100644 --- a/packages/client/lib/test-utils.ts +++ b/packages/client/lib/test-utils.ts @@ -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; diff --git a/packages/test-utils/lib/index.ts b/packages/test-utils/lib/index.ts index f424535297..7ccc2a2d48 100644 --- a/packages/test-utils/lib/index.ts +++ b/packages/test-utils/lib/index.ts @@ -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; - clientOptions?: Partial>; + clientOptions?: Partial>; 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; - clusterConfiguration?: Partial>; + clusterConfiguration?: Partial>; 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; - cluster: ClusterTestOptions; + client: ClientTestOptions; + cluster: ClusterTestOptions; } 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) => unknown, - options: ClientTestOptions + fn: (client: RedisClientType) => unknown, + options: ClientTestOptions ): void { let dockerPromise: ReturnType; 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): Promise { + RESP extends RespVersions, + TYPE_MAPPING extends TypeMapping, + POLICIES extends CommandPolicies + >(cluster: RedisClusterType): Promise { 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) => unknown, - options: ClusterTestOptions + fn: (cluster: RedisClusterType) => unknown, + options: ClusterTestOptions ): void { let dockersPromise: ReturnType; if (this.isVersionGreaterThan(options.minimumDockerVersion)) { @@ -225,7 +238,7 @@ export default class TestUtils { it(title, async function () { if (!dockersPromise) return this.skip(); - + const dockers = await dockersPromise, cluster = createCluster({ rootNodes: dockers.map(({ port }) => ({ @@ -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 | RedisClusterType) => unknown, - options: AllTestOptions + fn: (client: RedisClientType | RedisClusterType) => unknown, + options: AllTestOptions ) { this.testWithClient(`client.${title}`, fn, options.client); this.testWithCluster(`cluster.${title}`, fn, options.cluster);