Skip to content

Commit ec7ccaf

Browse files
committed
fix #1758 - implement some CLIENT commands, add name to RedisClientOptions
1 parent 82920ae commit ec7ccaf

14 files changed

+323
-9
lines changed

docs/client-configuration.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
| socket.reconnectStrategy | `retries => Math.min(retries * 50, 500)` | A function containing the [Reconnect Strategy](#reconnect-strategy) logic |
1616
| username | | ACL username ([see ACL guide](https://redis.io/topics/acl)) |
1717
| password | | ACL password or the old "--requirepass" password |
18+
| name | | Connection name ([see `CLIENT SETNAME`](https://redis.io/commands/client-setname)) |
1819
| database | | Database number to connect to (see [`SELECT`](https://redis.io/commands/select) command) |
1920
| modules | | Object defining which [Redis Modules](../README.md#packages) to include |
2021
| scripts | | Object defining Lua Scripts to use with this client (see [Lua Scripts](../README.md#lua-scripts)) |

packages/client/lib/client/commands.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ import * as ASKING from '../commands/ASKING';
1515
import * as AUTH from '../commands/AUTH';
1616
import * as BGREWRITEAOF from '../commands/BGREWRITEAOF';
1717
import * as BGSAVE from '../commands/BGSAVE';
18+
import * as CLIENT_CACHING from '../commands/CLIENT_CACHING';
19+
import * as CLIENT_GETNAME from '../commands/CLIENT_GETNAME';
20+
import * as CLIENT_GETREDIR from '../commands/CLIENT_GETREDIR';
1821
import * as CLIENT_ID from '../commands/CLIENT_ID';
22+
import * as CLIENT_KILL from '../commands/CLIENT_KILL';
23+
import * as CLIENT_SETNAME from '../commands/CLIENT_SETNAME';
1924
import * as CLIENT_INFO from '../commands/CLIENT_INFO';
2025
import * as CLUSTER_ADDSLOTS from '../commands/CLUSTER_ADDSLOTS';
2126
import * as CLUSTER_FLUSHSLOTS from '../commands/CLUSTER_FLUSHSLOTS';
@@ -110,8 +115,18 @@ export default {
110115
bgRewriteAof: BGREWRITEAOF,
111116
BGSAVE,
112117
bgSave: BGSAVE,
118+
CLIENT_CACHING,
119+
clientCaching: CLIENT_CACHING,
120+
CLIENT_GETNAME,
121+
clientGetName: CLIENT_GETNAME,
122+
CLIENT_GETREDIR,
123+
clientGetRedir: CLIENT_GETREDIR,
113124
CLIENT_ID,
114125
clientId: CLIENT_ID,
126+
CLIENT_KILL,
127+
clientKill: CLIENT_KILL,
128+
CLIENT_SETNAME,
129+
clientSetName: CLIENT_SETNAME,
115130
CLIENT_INFO,
116131
clientInfo: CLIENT_INFO,
117132
CLUSTER_ADDSLOTS,

packages/client/lib/client/index.spec.ts

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { AbortError, ClientClosedError, ConnectionTimeoutError, DisconnectsClien
77
import { defineScript } from '../lua-script';
88
import { spy } from 'sinon';
99
import { once } from 'events';
10+
import { ClientKillFilters } from '../commands/CLIENT_KILL';
1011

1112
export const SQUARE_SCRIPT = defineScript({
1213
NUMBER_OF_KEYS: 0,
@@ -125,6 +126,18 @@ describe('Client', () => {
125126
});
126127
});
127128

129+
testUtils.testWithClient('should set connection name', async client => {
130+
assert.equal(
131+
await client.clientGetName(),
132+
'name'
133+
);
134+
}, {
135+
...GLOBAL.SERVERS.OPEN,
136+
clientOptions: {
137+
name: 'name'
138+
}
139+
});
140+
128141
describe('legacyMode', () => {
129142
function sendCommandAsync<M extends RedisModules, S extends RedisScripts>(client: RedisClientType<M, S>, args: RedisCommandArguments): Promise<RedisCommandRawReply> {
130143
return new Promise((resolve, reject) => {
@@ -445,14 +458,9 @@ describe('Client', () => {
445458
});
446459

447460
testUtils.testWithClient('executeIsolated', async client => {
448-
await client.sendCommand(['CLIENT', 'SETNAME', 'client']);
449-
450-
assert.equal(
451-
await client.executeIsolated(isolatedClient =>
452-
isolatedClient.sendCommand(['CLIENT', 'GETNAME'])
453-
),
454-
null
455-
);
461+
const id = await client.clientId(),
462+
isolatedId = await client.executeIsolated(isolatedClient => isolatedClient.clientId());
463+
assert.ok(id !== isolatedId);
456464
}, GLOBAL.SERVERS.OPEN);
457465

458466
async function killClient<M extends RedisModules, S extends RedisScripts>(client: RedisClientType<M, S>): Promise<void> {
@@ -644,7 +652,10 @@ describe('Client', () => {
644652

645653
await Promise.all([
646654
once(subscriber, 'error'),
647-
publisher.sendCommand(['CLIENT', 'KILL', 'SKIPME', 'yes'])
655+
publisher.clientKill({
656+
filter: ClientKillFilters.SKIP_ME,
657+
skipMe: true
658+
})
648659
]);
649660

650661
await once(subscriber, 'ready');

packages/client/lib/client/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export interface RedisClientOptions<M extends RedisModules, S extends RedisScrip
2020
socket?: RedisSocketOptions;
2121
username?: string;
2222
password?: string;
23+
name?: string;
2324
database?: number;
2425
commandsQueueMaxLength?: number;
2526
readonly?: boolean;
@@ -201,6 +202,15 @@ export default class RedisClient<M extends RedisModules, S extends RedisScripts>
201202
);
202203
}
203204

205+
if (this.#options?.name) {
206+
promises.push(
207+
this.#queue.addCommand(
208+
COMMANDS.CLIENT_SETNAME.transformArguments(this.#options.name),
209+
{ asap: true }
210+
)
211+
);
212+
}
213+
204214
if (this.#options?.username || this.#options?.password) {
205215
promises.push(
206216
this.#queue.addCommand(
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { strict as assert } from 'assert';
2+
import { transformArguments } from './CLIENT_CACHING';
3+
4+
describe('CLIENT CACHING', () => {
5+
describe('transformArguments', () => {
6+
it('true', () => {
7+
assert.deepEqual(
8+
transformArguments(true),
9+
['CLIENT', 'CACHING', 'YES']
10+
);
11+
});
12+
13+
it('false', () => {
14+
assert.deepEqual(
15+
transformArguments(false),
16+
['CLIENT', 'CACHING', 'NO']
17+
);
18+
});
19+
});
20+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { RedisCommandArguments } from '.';
2+
3+
export function transformArguments(value: boolean): RedisCommandArguments {
4+
return [
5+
'CLIENT',
6+
'CACHING',
7+
value ? 'YES' : 'NO'
8+
];
9+
}
10+
11+
export declare function transformReply(): 'OK';
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { strict as assert } from 'assert';
2+
import { transformArguments } from './CLIENT_GETNAME';
3+
4+
describe('CLIENT GETNAME', () => {
5+
it('transformArguments', () => {
6+
assert.deepEqual(
7+
transformArguments(),
8+
['CLIENT', 'GETNAME']
9+
);
10+
});
11+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { RedisCommandArguments } from '.';
2+
3+
export function transformArguments(): RedisCommandArguments {
4+
return ['CLIENT', 'GETNAME'];
5+
}
6+
7+
export declare function transformReply(): string | null;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { strict as assert } from 'assert';
2+
import { transformArguments } from './CLIENT_GETREDIR';
3+
4+
describe('CLIENT GETREDIR', () => {
5+
it('transformArguments', () => {
6+
assert.deepEqual(
7+
transformArguments(),
8+
['CLIENT', 'GETREDIR']
9+
);
10+
});
11+
});
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { RedisCommandArguments } from '.';
2+
3+
export function transformArguments(): RedisCommandArguments {
4+
return ['CLIENT', 'GETREDIR'];
5+
}
6+
7+
export declare function transformReply(): number;
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import { strict as assert } from 'assert';
2+
import { ClientKillFilters, transformArguments } from './CLIENT_KILL';
3+
4+
describe('CLIENT KILL', () => {
5+
describe('transformArguments', () => {
6+
it('ADDRESS', () => {
7+
assert.deepEqual(
8+
transformArguments({
9+
filter: ClientKillFilters.ADDRESS,
10+
address: 'ip:6379'
11+
}),
12+
['CLIENT', 'KILL', 'ADDR', 'ip:6379']
13+
);
14+
});
15+
16+
it('LOCAL_ADDRESS', () => {
17+
assert.deepEqual(
18+
transformArguments({
19+
filter: ClientKillFilters.LOCAL_ADDRESS,
20+
localAddress: 'ip:6379'
21+
}),
22+
['CLIENT', 'KILL', 'LADDR', 'ip:6379']
23+
);
24+
});
25+
26+
describe('ID', () => {
27+
it('string', () => {
28+
assert.deepEqual(
29+
transformArguments({
30+
filter: ClientKillFilters.ID,
31+
id: '1'
32+
}),
33+
['CLIENT', 'KILL', 'ID', '1']
34+
);
35+
});
36+
37+
it('number', () => {
38+
assert.deepEqual(
39+
transformArguments({
40+
filter: ClientKillFilters.ID,
41+
id: 1
42+
}),
43+
['CLIENT', 'KILL', 'ID', '1']
44+
);
45+
});
46+
});
47+
48+
it('TYPE', () => {
49+
assert.deepEqual(
50+
transformArguments({
51+
filter: ClientKillFilters.TYPE,
52+
type: 'master'
53+
}),
54+
['CLIENT', 'KILL', 'TYPE', 'master']
55+
);
56+
});
57+
58+
it('USER', () => {
59+
assert.deepEqual(
60+
transformArguments({
61+
filter: ClientKillFilters.USER,
62+
username: 'username'
63+
}),
64+
['CLIENT', 'KILL', 'USER', 'username']
65+
);
66+
});
67+
68+
describe('SKIP_ME', () => {
69+
it('undefined', () => {
70+
assert.deepEqual(
71+
transformArguments(ClientKillFilters.SKIP_ME),
72+
['CLIENT', 'KILL', 'SKIPME']
73+
);
74+
});
75+
76+
it('true', () => {
77+
assert.deepEqual(
78+
transformArguments({
79+
filter: ClientKillFilters.SKIP_ME,
80+
skipMe: true
81+
}),
82+
['CLIENT', 'KILL', 'SKIPME', 'yes']
83+
);
84+
});
85+
86+
it('false', () => {
87+
assert.deepEqual(
88+
transformArguments({
89+
filter: ClientKillFilters.SKIP_ME,
90+
skipMe: false
91+
}),
92+
['CLIENT', 'KILL', 'SKIPME', 'no']
93+
);
94+
});
95+
});
96+
});
97+
});
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { RedisCommandArguments } from '.';
2+
3+
export enum ClientKillFilters {
4+
ADDRESS = 'ADDR',
5+
LOCAL_ADDRESS = 'LADDR',
6+
ID = 'ID',
7+
TYPE = 'TYPE',
8+
USER = 'USER',
9+
SKIP_ME = 'SKIPME'
10+
}
11+
12+
interface KillFilter<T extends ClientKillFilters> {
13+
filter: T;
14+
}
15+
16+
interface KillAddress extends KillFilter<ClientKillFilters.ADDRESS> {
17+
address: `${string}:${number}`;
18+
}
19+
20+
interface KillLocalAddress extends KillFilter<ClientKillFilters.LOCAL_ADDRESS> {
21+
localAddress: `${string}:${number}`;
22+
}
23+
24+
interface KillId extends KillFilter<ClientKillFilters.ID> {
25+
id: number | `${number}`;
26+
}
27+
28+
interface KillType extends KillFilter<ClientKillFilters.TYPE> {
29+
type: 'normal' | 'master' | 'replica' | 'pubsub';
30+
}
31+
32+
interface KillUser extends KillFilter<ClientKillFilters.USER> {
33+
username: string;
34+
}
35+
36+
type KillSkipMe = ClientKillFilters.SKIP_ME | (KillFilter<ClientKillFilters.SKIP_ME> & {
37+
skipMe: boolean;
38+
});
39+
40+
type KillFilters = KillAddress | KillLocalAddress | KillId | KillType | KillUser | KillSkipMe;
41+
42+
export function transformArguments(filters: KillFilters | Array<KillFilters>): RedisCommandArguments {
43+
const args = ['CLIENT', 'KILL'];
44+
45+
if (Array.isArray(filters)) {
46+
for (const filter of filters) {
47+
pushFilter(args, filter);
48+
}
49+
} else {
50+
pushFilter(args, filters);
51+
}
52+
53+
return args;
54+
}
55+
56+
function pushFilter(args: RedisCommandArguments, filter: KillFilters): void {
57+
if (filter === ClientKillFilters.SKIP_ME) {
58+
args.push('SKIPME');
59+
return;
60+
}
61+
62+
args.push(filter.filter);
63+
64+
switch(filter.filter) {
65+
case ClientKillFilters.ADDRESS:
66+
args.push(filter.address);
67+
break;
68+
69+
case ClientKillFilters.LOCAL_ADDRESS:
70+
args.push(filter.localAddress);
71+
break;
72+
73+
case ClientKillFilters.ID:
74+
args.push(
75+
typeof filter.id === 'number' ?
76+
filter.id.toString() :
77+
filter.id
78+
);
79+
break;
80+
81+
case ClientKillFilters.TYPE:
82+
args.push(filter.type);
83+
break;
84+
85+
case ClientKillFilters.USER:
86+
args.push(filter.username);
87+
break;
88+
89+
case ClientKillFilters.SKIP_ME:
90+
args.push(filter.skipMe ? 'yes' : 'no');
91+
break;
92+
}
93+
}
94+
95+
export declare function transformReply(): number;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { strict as assert } from 'assert';
2+
import { transformArguments } from './CLIENT_SETNAME';
3+
4+
describe('CLIENT SETNAME', () => {
5+
it('transformArguments', () => {
6+
assert.deepEqual(
7+
transformArguments('name'),
8+
['CLIENT', 'SETNAME', 'name']
9+
);
10+
});
11+
});

0 commit comments

Comments
 (0)