diff --git a/docs/client-configuration.md b/docs/client-configuration.md index f4aa8e99d6..4ea94fcab1 100644 --- a/docs/client-configuration.md +++ b/docs/client-configuration.md @@ -15,6 +15,7 @@ | socket.reconnectStrategy | `retries => Math.min(retries * 50, 500)` | A function containing the [Reconnect Strategy](#reconnect-strategy) logic | | username | | ACL username ([see ACL guide](https://redis.io/topics/acl)) | | password | | ACL password or the old "--requirepass" password | +| name | | Connection name ([see `CLIENT SETNAME`](https://redis.io/commands/client-setname)) | | database | | Database number to connect to (see [`SELECT`](https://redis.io/commands/select) command) | | modules | | Object defining which [Redis Modules](../README.md#packages) to include | | scripts | | Object defining Lua Scripts to use with this client (see [Lua Scripts](../README.md#lua-scripts)) | diff --git a/packages/client/lib/client/commands.ts b/packages/client/lib/client/commands.ts index de901152f1..029a5e60f6 100644 --- a/packages/client/lib/client/commands.ts +++ b/packages/client/lib/client/commands.ts @@ -15,7 +15,12 @@ import * as ASKING from '../commands/ASKING'; import * as AUTH from '../commands/AUTH'; import * as BGREWRITEAOF from '../commands/BGREWRITEAOF'; import * as BGSAVE from '../commands/BGSAVE'; +import * as CLIENT_CACHING from '../commands/CLIENT_CACHING'; +import * as CLIENT_GETNAME from '../commands/CLIENT_GETNAME'; +import * as CLIENT_GETREDIR from '../commands/CLIENT_GETREDIR'; import * as CLIENT_ID from '../commands/CLIENT_ID'; +import * as CLIENT_KILL from '../commands/CLIENT_KILL'; +import * as CLIENT_SETNAME from '../commands/CLIENT_SETNAME'; import * as CLIENT_INFO from '../commands/CLIENT_INFO'; import * as CLUSTER_ADDSLOTS from '../commands/CLUSTER_ADDSLOTS'; import * as CLUSTER_FLUSHSLOTS from '../commands/CLUSTER_FLUSHSLOTS'; @@ -110,8 +115,18 @@ export default { bgRewriteAof: BGREWRITEAOF, BGSAVE, bgSave: BGSAVE, + CLIENT_CACHING, + clientCaching: CLIENT_CACHING, + CLIENT_GETNAME, + clientGetName: CLIENT_GETNAME, + CLIENT_GETREDIR, + clientGetRedir: CLIENT_GETREDIR, CLIENT_ID, clientId: CLIENT_ID, + CLIENT_KILL, + clientKill: CLIENT_KILL, + CLIENT_SETNAME, + clientSetName: CLIENT_SETNAME, CLIENT_INFO, clientInfo: CLIENT_INFO, CLUSTER_ADDSLOTS, diff --git a/packages/client/lib/client/index.spec.ts b/packages/client/lib/client/index.spec.ts index 63bd9a1b46..01154e9dd1 100644 --- a/packages/client/lib/client/index.spec.ts +++ b/packages/client/lib/client/index.spec.ts @@ -7,6 +7,7 @@ import { AbortError, ClientClosedError, ConnectionTimeoutError, DisconnectsClien import { defineScript } from '../lua-script'; import { spy } from 'sinon'; import { once } from 'events'; +import { ClientKillFilters } from '../commands/CLIENT_KILL'; export const SQUARE_SCRIPT = defineScript({ NUMBER_OF_KEYS: 0, @@ -125,6 +126,18 @@ describe('Client', () => { }); }); + testUtils.testWithClient('should set connection name', async client => { + assert.equal( + await client.clientGetName(), + 'name' + ); + }, { + ...GLOBAL.SERVERS.OPEN, + clientOptions: { + name: 'name' + } + }); + describe('legacyMode', () => { function sendCommandAsync(client: RedisClientType, args: RedisCommandArguments): Promise { return new Promise((resolve, reject) => { @@ -445,14 +458,9 @@ describe('Client', () => { }); testUtils.testWithClient('executeIsolated', async client => { - await client.sendCommand(['CLIENT', 'SETNAME', 'client']); - - assert.equal( - await client.executeIsolated(isolatedClient => - isolatedClient.sendCommand(['CLIENT', 'GETNAME']) - ), - null - ); + const id = await client.clientId(), + isolatedId = await client.executeIsolated(isolatedClient => isolatedClient.clientId()); + assert.ok(id !== isolatedId); }, GLOBAL.SERVERS.OPEN); async function killClient(client: RedisClientType): Promise { @@ -644,7 +652,10 @@ describe('Client', () => { await Promise.all([ once(subscriber, 'error'), - publisher.sendCommand(['CLIENT', 'KILL', 'SKIPME', 'yes']) + publisher.clientKill({ + filter: ClientKillFilters.SKIP_ME, + skipMe: true + }) ]); await once(subscriber, 'ready'); diff --git a/packages/client/lib/client/index.ts b/packages/client/lib/client/index.ts index 6bc0aec74d..2bc5230580 100644 --- a/packages/client/lib/client/index.ts +++ b/packages/client/lib/client/index.ts @@ -20,6 +20,7 @@ export interface RedisClientOptions ); } + if (this.#options?.name) { + promises.push( + this.#queue.addCommand( + COMMANDS.CLIENT_SETNAME.transformArguments(this.#options.name), + { asap: true } + ) + ); + } + if (this.#options?.username || this.#options?.password) { promises.push( this.#queue.addCommand( diff --git a/packages/client/lib/commands/CLIENT_CACHING.spec.ts b/packages/client/lib/commands/CLIENT_CACHING.spec.ts new file mode 100644 index 0000000000..d9cb9a3f79 --- /dev/null +++ b/packages/client/lib/commands/CLIENT_CACHING.spec.ts @@ -0,0 +1,20 @@ +import { strict as assert } from 'assert'; +import { transformArguments } from './CLIENT_CACHING'; + +describe('CLIENT CACHING', () => { + describe('transformArguments', () => { + it('true', () => { + assert.deepEqual( + transformArguments(true), + ['CLIENT', 'CACHING', 'YES'] + ); + }); + + it('false', () => { + assert.deepEqual( + transformArguments(false), + ['CLIENT', 'CACHING', 'NO'] + ); + }); + }); +}); diff --git a/packages/client/lib/commands/CLIENT_CACHING.ts b/packages/client/lib/commands/CLIENT_CACHING.ts new file mode 100644 index 0000000000..62a46bad6c --- /dev/null +++ b/packages/client/lib/commands/CLIENT_CACHING.ts @@ -0,0 +1,11 @@ +import { RedisCommandArguments } from '.'; + +export function transformArguments(value: boolean): RedisCommandArguments { + return [ + 'CLIENT', + 'CACHING', + value ? 'YES' : 'NO' + ]; +} + +export declare function transformReply(): 'OK'; diff --git a/packages/client/lib/commands/CLIENT_GETNAME.spec.ts b/packages/client/lib/commands/CLIENT_GETNAME.spec.ts new file mode 100644 index 0000000000..0a09713882 --- /dev/null +++ b/packages/client/lib/commands/CLIENT_GETNAME.spec.ts @@ -0,0 +1,11 @@ +import { strict as assert } from 'assert'; +import { transformArguments } from './CLIENT_GETNAME'; + +describe('CLIENT GETNAME', () => { + it('transformArguments', () => { + assert.deepEqual( + transformArguments(), + ['CLIENT', 'GETNAME'] + ); + }); +}); diff --git a/packages/client/lib/commands/CLIENT_GETNAME.ts b/packages/client/lib/commands/CLIENT_GETNAME.ts new file mode 100644 index 0000000000..da00539d7f --- /dev/null +++ b/packages/client/lib/commands/CLIENT_GETNAME.ts @@ -0,0 +1,7 @@ +import { RedisCommandArguments } from '.'; + +export function transformArguments(): RedisCommandArguments { + return ['CLIENT', 'GETNAME']; +} + +export declare function transformReply(): string | null; diff --git a/packages/client/lib/commands/CLIENT_GETREDIR.spec.ts b/packages/client/lib/commands/CLIENT_GETREDIR.spec.ts new file mode 100644 index 0000000000..09dd9677e3 --- /dev/null +++ b/packages/client/lib/commands/CLIENT_GETREDIR.spec.ts @@ -0,0 +1,11 @@ +import { strict as assert } from 'assert'; +import { transformArguments } from './CLIENT_GETREDIR'; + +describe('CLIENT GETREDIR', () => { + it('transformArguments', () => { + assert.deepEqual( + transformArguments(), + ['CLIENT', 'GETREDIR'] + ); + }); +}); diff --git a/packages/client/lib/commands/CLIENT_GETREDIR.ts b/packages/client/lib/commands/CLIENT_GETREDIR.ts new file mode 100644 index 0000000000..d192adf284 --- /dev/null +++ b/packages/client/lib/commands/CLIENT_GETREDIR.ts @@ -0,0 +1,7 @@ +import { RedisCommandArguments } from '.'; + +export function transformArguments(): RedisCommandArguments { + return ['CLIENT', 'GETREDIR']; +} + +export declare function transformReply(): number; diff --git a/packages/client/lib/commands/CLIENT_KILL.spec.ts b/packages/client/lib/commands/CLIENT_KILL.spec.ts new file mode 100644 index 0000000000..0c38a0fb16 --- /dev/null +++ b/packages/client/lib/commands/CLIENT_KILL.spec.ts @@ -0,0 +1,97 @@ +import { strict as assert } from 'assert'; +import { ClientKillFilters, transformArguments } from './CLIENT_KILL'; + +describe('CLIENT KILL', () => { + describe('transformArguments', () => { + it('ADDRESS', () => { + assert.deepEqual( + transformArguments({ + filter: ClientKillFilters.ADDRESS, + address: 'ip:6379' + }), + ['CLIENT', 'KILL', 'ADDR', 'ip:6379'] + ); + }); + + it('LOCAL_ADDRESS', () => { + assert.deepEqual( + transformArguments({ + filter: ClientKillFilters.LOCAL_ADDRESS, + localAddress: 'ip:6379' + }), + ['CLIENT', 'KILL', 'LADDR', 'ip:6379'] + ); + }); + + describe('ID', () => { + it('string', () => { + assert.deepEqual( + transformArguments({ + filter: ClientKillFilters.ID, + id: '1' + }), + ['CLIENT', 'KILL', 'ID', '1'] + ); + }); + + it('number', () => { + assert.deepEqual( + transformArguments({ + filter: ClientKillFilters.ID, + id: 1 + }), + ['CLIENT', 'KILL', 'ID', '1'] + ); + }); + }); + + it('TYPE', () => { + assert.deepEqual( + transformArguments({ + filter: ClientKillFilters.TYPE, + type: 'master' + }), + ['CLIENT', 'KILL', 'TYPE', 'master'] + ); + }); + + it('USER', () => { + assert.deepEqual( + transformArguments({ + filter: ClientKillFilters.USER, + username: 'username' + }), + ['CLIENT', 'KILL', 'USER', 'username'] + ); + }); + + describe('SKIP_ME', () => { + it('undefined', () => { + assert.deepEqual( + transformArguments(ClientKillFilters.SKIP_ME), + ['CLIENT', 'KILL', 'SKIPME'] + ); + }); + + it('true', () => { + assert.deepEqual( + transformArguments({ + filter: ClientKillFilters.SKIP_ME, + skipMe: true + }), + ['CLIENT', 'KILL', 'SKIPME', 'yes'] + ); + }); + + it('false', () => { + assert.deepEqual( + transformArguments({ + filter: ClientKillFilters.SKIP_ME, + skipMe: false + }), + ['CLIENT', 'KILL', 'SKIPME', 'no'] + ); + }); + }); + }); +}); diff --git a/packages/client/lib/commands/CLIENT_KILL.ts b/packages/client/lib/commands/CLIENT_KILL.ts new file mode 100644 index 0000000000..adb2a5a656 --- /dev/null +++ b/packages/client/lib/commands/CLIENT_KILL.ts @@ -0,0 +1,95 @@ +import { RedisCommandArguments } from '.'; + +export enum ClientKillFilters { + ADDRESS = 'ADDR', + LOCAL_ADDRESS = 'LADDR', + ID = 'ID', + TYPE = 'TYPE', + USER = 'USER', + SKIP_ME = 'SKIPME' +} + +interface KillFilter { + filter: T; +} + +interface KillAddress extends KillFilter { + address: `${string}:${number}`; +} + +interface KillLocalAddress extends KillFilter { + localAddress: `${string}:${number}`; +} + +interface KillId extends KillFilter { + id: number | `${number}`; +} + +interface KillType extends KillFilter { + type: 'normal' | 'master' | 'replica' | 'pubsub'; +} + +interface KillUser extends KillFilter { + username: string; +} + +type KillSkipMe = ClientKillFilters.SKIP_ME | (KillFilter & { + skipMe: boolean; +}); + +type KillFilters = KillAddress | KillLocalAddress | KillId | KillType | KillUser | KillSkipMe; + +export function transformArguments(filters: KillFilters | Array): RedisCommandArguments { + const args = ['CLIENT', 'KILL']; + + if (Array.isArray(filters)) { + for (const filter of filters) { + pushFilter(args, filter); + } + } else { + pushFilter(args, filters); + } + + return args; +} + +function pushFilter(args: RedisCommandArguments, filter: KillFilters): void { + if (filter === ClientKillFilters.SKIP_ME) { + args.push('SKIPME'); + return; + } + + args.push(filter.filter); + + switch(filter.filter) { + case ClientKillFilters.ADDRESS: + args.push(filter.address); + break; + + case ClientKillFilters.LOCAL_ADDRESS: + args.push(filter.localAddress); + break; + + case ClientKillFilters.ID: + args.push( + typeof filter.id === 'number' ? + filter.id.toString() : + filter.id + ); + break; + + case ClientKillFilters.TYPE: + args.push(filter.type); + break; + + case ClientKillFilters.USER: + args.push(filter.username); + break; + + case ClientKillFilters.SKIP_ME: + args.push(filter.skipMe ? 'yes' : 'no'); + break; + } +} + +export declare function transformReply(): number; diff --git a/packages/client/lib/commands/CLIENT_SETNAME.spec.ts b/packages/client/lib/commands/CLIENT_SETNAME.spec.ts new file mode 100644 index 0000000000..96618f3f79 --- /dev/null +++ b/packages/client/lib/commands/CLIENT_SETNAME.spec.ts @@ -0,0 +1,11 @@ +import { strict as assert } from 'assert'; +import { transformArguments } from './CLIENT_SETNAME'; + +describe('CLIENT SETNAME', () => { + it('transformArguments', () => { + assert.deepEqual( + transformArguments('name'), + ['CLIENT', 'SETNAME', 'name'] + ); + }); +}); diff --git a/packages/client/lib/commands/CLIENT_SETNAME.ts b/packages/client/lib/commands/CLIENT_SETNAME.ts new file mode 100644 index 0000000000..562fa9f2e9 --- /dev/null +++ b/packages/client/lib/commands/CLIENT_SETNAME.ts @@ -0,0 +1,7 @@ +import { RedisCommandArguments } from '.'; + +export function transformArguments(name: string): RedisCommandArguments { + return ['CLIENT', 'SETNAME', name]; +} + +export declare function transformReply(): string | null;