You've already forked node-redis
mirror of
https://github.com/redis/node-redis.git
synced 2025-08-06 02:15:48 +03:00
fix #1758 - implement some CLIENT commands, add name
to RedisClientOptions
This commit is contained in:
@@ -15,6 +15,7 @@
|
|||||||
| socket.reconnectStrategy | `retries => Math.min(retries * 50, 500)` | A function containing the [Reconnect Strategy](#reconnect-strategy) logic |
|
| 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)) |
|
| username | | ACL username ([see ACL guide](https://redis.io/topics/acl)) |
|
||||||
| password | | ACL password or the old "--requirepass" password |
|
| 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) |
|
| 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 |
|
| 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)) |
|
| scripts | | Object defining Lua Scripts to use with this client (see [Lua Scripts](../README.md#lua-scripts)) |
|
||||||
|
@@ -15,7 +15,12 @@ import * as ASKING from '../commands/ASKING';
|
|||||||
import * as AUTH from '../commands/AUTH';
|
import * as AUTH from '../commands/AUTH';
|
||||||
import * as BGREWRITEAOF from '../commands/BGREWRITEAOF';
|
import * as BGREWRITEAOF from '../commands/BGREWRITEAOF';
|
||||||
import * as BGSAVE from '../commands/BGSAVE';
|
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_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 CLIENT_INFO from '../commands/CLIENT_INFO';
|
||||||
import * as CLUSTER_ADDSLOTS from '../commands/CLUSTER_ADDSLOTS';
|
import * as CLUSTER_ADDSLOTS from '../commands/CLUSTER_ADDSLOTS';
|
||||||
import * as CLUSTER_FLUSHSLOTS from '../commands/CLUSTER_FLUSHSLOTS';
|
import * as CLUSTER_FLUSHSLOTS from '../commands/CLUSTER_FLUSHSLOTS';
|
||||||
@@ -110,8 +115,18 @@ export default {
|
|||||||
bgRewriteAof: BGREWRITEAOF,
|
bgRewriteAof: BGREWRITEAOF,
|
||||||
BGSAVE,
|
BGSAVE,
|
||||||
bgSave: BGSAVE,
|
bgSave: BGSAVE,
|
||||||
|
CLIENT_CACHING,
|
||||||
|
clientCaching: CLIENT_CACHING,
|
||||||
|
CLIENT_GETNAME,
|
||||||
|
clientGetName: CLIENT_GETNAME,
|
||||||
|
CLIENT_GETREDIR,
|
||||||
|
clientGetRedir: CLIENT_GETREDIR,
|
||||||
CLIENT_ID,
|
CLIENT_ID,
|
||||||
clientId: CLIENT_ID,
|
clientId: CLIENT_ID,
|
||||||
|
CLIENT_KILL,
|
||||||
|
clientKill: CLIENT_KILL,
|
||||||
|
CLIENT_SETNAME,
|
||||||
|
clientSetName: CLIENT_SETNAME,
|
||||||
CLIENT_INFO,
|
CLIENT_INFO,
|
||||||
clientInfo: CLIENT_INFO,
|
clientInfo: CLIENT_INFO,
|
||||||
CLUSTER_ADDSLOTS,
|
CLUSTER_ADDSLOTS,
|
||||||
|
@@ -7,6 +7,7 @@ import { AbortError, ClientClosedError, ConnectionTimeoutError, DisconnectsClien
|
|||||||
import { defineScript } from '../lua-script';
|
import { defineScript } from '../lua-script';
|
||||||
import { spy } from 'sinon';
|
import { spy } from 'sinon';
|
||||||
import { once } from 'events';
|
import { once } from 'events';
|
||||||
|
import { ClientKillFilters } from '../commands/CLIENT_KILL';
|
||||||
|
|
||||||
export const SQUARE_SCRIPT = defineScript({
|
export const SQUARE_SCRIPT = defineScript({
|
||||||
NUMBER_OF_KEYS: 0,
|
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', () => {
|
describe('legacyMode', () => {
|
||||||
function sendCommandAsync<M extends RedisModules, S extends RedisScripts>(client: RedisClientType<M, S>, args: RedisCommandArguments): Promise<RedisCommandRawReply> {
|
function sendCommandAsync<M extends RedisModules, S extends RedisScripts>(client: RedisClientType<M, S>, args: RedisCommandArguments): Promise<RedisCommandRawReply> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@@ -445,14 +458,9 @@ describe('Client', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
testUtils.testWithClient('executeIsolated', async client => {
|
testUtils.testWithClient('executeIsolated', async client => {
|
||||||
await client.sendCommand(['CLIENT', 'SETNAME', 'client']);
|
const id = await client.clientId(),
|
||||||
|
isolatedId = await client.executeIsolated(isolatedClient => isolatedClient.clientId());
|
||||||
assert.equal(
|
assert.ok(id !== isolatedId);
|
||||||
await client.executeIsolated(isolatedClient =>
|
|
||||||
isolatedClient.sendCommand(['CLIENT', 'GETNAME'])
|
|
||||||
),
|
|
||||||
null
|
|
||||||
);
|
|
||||||
}, GLOBAL.SERVERS.OPEN);
|
}, GLOBAL.SERVERS.OPEN);
|
||||||
|
|
||||||
async function killClient<M extends RedisModules, S extends RedisScripts>(client: RedisClientType<M, S>): Promise<void> {
|
async function killClient<M extends RedisModules, S extends RedisScripts>(client: RedisClientType<M, S>): Promise<void> {
|
||||||
@@ -644,7 +652,10 @@ describe('Client', () => {
|
|||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
once(subscriber, 'error'),
|
once(subscriber, 'error'),
|
||||||
publisher.sendCommand(['CLIENT', 'KILL', 'SKIPME', 'yes'])
|
publisher.clientKill({
|
||||||
|
filter: ClientKillFilters.SKIP_ME,
|
||||||
|
skipMe: true
|
||||||
|
})
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await once(subscriber, 'ready');
|
await once(subscriber, 'ready');
|
||||||
|
@@ -20,6 +20,7 @@ export interface RedisClientOptions<M extends RedisModules, S extends RedisScrip
|
|||||||
socket?: RedisSocketOptions;
|
socket?: RedisSocketOptions;
|
||||||
username?: string;
|
username?: string;
|
||||||
password?: string;
|
password?: string;
|
||||||
|
name?: string;
|
||||||
database?: number;
|
database?: number;
|
||||||
commandsQueueMaxLength?: number;
|
commandsQueueMaxLength?: number;
|
||||||
readonly?: boolean;
|
readonly?: boolean;
|
||||||
@@ -201,6 +202,15 @@ export default class RedisClient<M extends RedisModules, S extends RedisScripts>
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
if (this.#options?.username || this.#options?.password) {
|
||||||
promises.push(
|
promises.push(
|
||||||
this.#queue.addCommand(
|
this.#queue.addCommand(
|
||||||
|
20
packages/client/lib/commands/CLIENT_CACHING.spec.ts
Normal file
20
packages/client/lib/commands/CLIENT_CACHING.spec.ts
Normal file
@@ -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']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
11
packages/client/lib/commands/CLIENT_CACHING.ts
Normal file
11
packages/client/lib/commands/CLIENT_CACHING.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { RedisCommandArguments } from '.';
|
||||||
|
|
||||||
|
export function transformArguments(value: boolean): RedisCommandArguments {
|
||||||
|
return [
|
||||||
|
'CLIENT',
|
||||||
|
'CACHING',
|
||||||
|
value ? 'YES' : 'NO'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
export declare function transformReply(): 'OK';
|
11
packages/client/lib/commands/CLIENT_GETNAME.spec.ts
Normal file
11
packages/client/lib/commands/CLIENT_GETNAME.spec.ts
Normal file
@@ -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']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
7
packages/client/lib/commands/CLIENT_GETNAME.ts
Normal file
7
packages/client/lib/commands/CLIENT_GETNAME.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { RedisCommandArguments } from '.';
|
||||||
|
|
||||||
|
export function transformArguments(): RedisCommandArguments {
|
||||||
|
return ['CLIENT', 'GETNAME'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export declare function transformReply(): string | null;
|
11
packages/client/lib/commands/CLIENT_GETREDIR.spec.ts
Normal file
11
packages/client/lib/commands/CLIENT_GETREDIR.spec.ts
Normal file
@@ -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']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
7
packages/client/lib/commands/CLIENT_GETREDIR.ts
Normal file
7
packages/client/lib/commands/CLIENT_GETREDIR.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { RedisCommandArguments } from '.';
|
||||||
|
|
||||||
|
export function transformArguments(): RedisCommandArguments {
|
||||||
|
return ['CLIENT', 'GETREDIR'];
|
||||||
|
}
|
||||||
|
|
||||||
|
export declare function transformReply(): number;
|
97
packages/client/lib/commands/CLIENT_KILL.spec.ts
Normal file
97
packages/client/lib/commands/CLIENT_KILL.spec.ts
Normal file
@@ -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']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
95
packages/client/lib/commands/CLIENT_KILL.ts
Normal file
95
packages/client/lib/commands/CLIENT_KILL.ts
Normal file
@@ -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<T extends ClientKillFilters> {
|
||||||
|
filter: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface KillAddress extends KillFilter<ClientKillFilters.ADDRESS> {
|
||||||
|
address: `${string}:${number}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface KillLocalAddress extends KillFilter<ClientKillFilters.LOCAL_ADDRESS> {
|
||||||
|
localAddress: `${string}:${number}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface KillId extends KillFilter<ClientKillFilters.ID> {
|
||||||
|
id: number | `${number}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface KillType extends KillFilter<ClientKillFilters.TYPE> {
|
||||||
|
type: 'normal' | 'master' | 'replica' | 'pubsub';
|
||||||
|
}
|
||||||
|
|
||||||
|
interface KillUser extends KillFilter<ClientKillFilters.USER> {
|
||||||
|
username: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type KillSkipMe = ClientKillFilters.SKIP_ME | (KillFilter<ClientKillFilters.SKIP_ME> & {
|
||||||
|
skipMe: boolean;
|
||||||
|
});
|
||||||
|
|
||||||
|
type KillFilters = KillAddress | KillLocalAddress | KillId | KillType | KillUser | KillSkipMe;
|
||||||
|
|
||||||
|
export function transformArguments(filters: KillFilters | Array<KillFilters>): 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;
|
11
packages/client/lib/commands/CLIENT_SETNAME.spec.ts
Normal file
11
packages/client/lib/commands/CLIENT_SETNAME.spec.ts
Normal file
@@ -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']
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
7
packages/client/lib/commands/CLIENT_SETNAME.ts
Normal file
7
packages/client/lib/commands/CLIENT_SETNAME.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import { RedisCommandArguments } from '.';
|
||||||
|
|
||||||
|
export function transformArguments(name: string): RedisCommandArguments {
|
||||||
|
return ['CLIENT', 'SETNAME', name];
|
||||||
|
}
|
||||||
|
|
||||||
|
export declare function transformReply(): string | null;
|
Reference in New Issue
Block a user