diff --git a/packages/client/lib/client/commands.ts b/packages/client/lib/client/commands.ts index f4eb1f1e172..8b5320a5d80 100644 --- a/packages/client/lib/client/commands.ts +++ b/packages/client/lib/client/commands.ts @@ -21,6 +21,7 @@ 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_LIST from '../commands/CLIENT_LIST'; import * as CLIENT_NO_EVICT from '../commands/CLIENT_NO-EVICT'; import * as CLIENT_PAUSE from '../commands/CLIENT_PAUSE'; import * as CLIENT_SETNAME from '../commands/CLIENT_SETNAME'; @@ -164,6 +165,8 @@ export default { clientKill: CLIENT_KILL, 'CLIENT_NO-EVICT': CLIENT_NO_EVICT, clientNoEvict: CLIENT_NO_EVICT, + CLIENT_LIST, + clientList: CLIENT_LIST, CLIENT_PAUSE, clientPause: CLIENT_PAUSE, CLIENT_SETNAME, diff --git a/packages/client/lib/commands/CLIENT_INFO.spec.ts b/packages/client/lib/commands/CLIENT_INFO.spec.ts index ee87df4a199..ccb99017cf3 100644 --- a/packages/client/lib/commands/CLIENT_INFO.spec.ts +++ b/packages/client/lib/commands/CLIENT_INFO.spec.ts @@ -1,7 +1,10 @@ import { strict as assert } from 'assert'; import { transformArguments, transformReply } from './CLIENT_INFO'; +import testUtils, { GLOBAL } from '../test-utils'; describe('CLIENT INFO', () => { + testUtils.isVersionGreaterThanHook([6, 2]); + it('transformArguments', () => { assert.deepEqual( transformArguments(), @@ -9,34 +12,39 @@ describe('CLIENT INFO', () => { ); }); - it('transformReply', () => { - assert.deepEqual( - transformReply('id=526512 addr=127.0.0.1:36244 laddr=127.0.0.1:6379 fd=8 name= age=11213 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=26 qbuf-free=40928 argv-mem=10 obl=0 oll=0 omem=0 tot-mem=61466 events=r cmd=client user=default redir=-1\n'), - { - id: 526512, - addr: '127.0.0.1:36244', - laddr: '127.0.0.1:6379', - fd: 8, - name: '', - age: 11213, - idle: 0, - flags: 'N', - db: 0, - sub: 0, - psub: 0, - multi: -1, - qbuf: 26, - qbufFree: 40928, - argvMem: 10, - obl: 0, - oll: 0, - omem: 0, - totMem: 61466, - events: 'r', - cmd: 'client', - user: 'default', - redir: -1 - } - ); - }); + testUtils.testWithClient('client.clientInfo', async client => { + const reply = await client.clientInfo(); + assert.equal(typeof reply.id, 'number'); + assert.equal(typeof reply.addr, 'string'); + assert.equal(typeof reply.laddr, 'string'); + assert.equal(typeof reply.fd, 'number'); + assert.equal(typeof reply.name, 'string'); + assert.equal(typeof reply.age, 'number'); + assert.equal(typeof reply.idle, 'number'); + assert.equal(typeof reply.flags, 'string'); + assert.equal(typeof reply.db, 'number'); + assert.equal(typeof reply.sub, 'number'); + assert.equal(typeof reply.psub, 'number'); + assert.equal(typeof reply.multi, 'number'); + assert.equal(typeof reply.qbuf, 'number'); + assert.equal(typeof reply.qbufFree, 'number'); + assert.equal(typeof reply.argvMem, 'number'); + assert.equal(typeof reply.obl, 'number'); + assert.equal(typeof reply.oll, 'number'); + assert.equal(typeof reply.omem, 'number'); + assert.equal(typeof reply.totMem, 'number'); + assert.equal(typeof reply.events, 'string'); + assert.equal(typeof reply.cmd, 'string'); + assert.equal(typeof reply.user, 'string'); + assert.equal(typeof reply.redir, 'number'); + + if (testUtils.isVersionGreaterThan([7, 0])) { + assert.equal(typeof reply.multiMem, 'number'); + assert.equal(typeof reply.resp, 'number'); + } + + if (testUtils.isVersionGreaterThan([7, 0, 3])) { + assert.equal(typeof reply.ssub, 'number'); + } + }, GLOBAL.SERVERS.OPEN); }); diff --git a/packages/client/lib/commands/CLIENT_INFO.ts b/packages/client/lib/commands/CLIENT_INFO.ts index 8dd30b70590..7f6b6e1884e 100644 --- a/packages/client/lib/commands/CLIENT_INFO.ts +++ b/packages/client/lib/commands/CLIENT_INFO.ts @@ -1,11 +1,13 @@ +export const IS_READ_ONLY = true; + export function transformArguments(): Array { return ['CLIENT', 'INFO']; } -interface ClientInfoReply { +export interface ClientInfoReply { id: number; addr: string; - laddr: string; + laddr?: string; // 6.2 fd: number; name: string; age: number; @@ -14,72 +16,74 @@ interface ClientInfoReply { db: number; sub: number; psub: number; + ssub?: number; // 7.0.3 multi: number; qbuf: number; qbufFree: number; - argvMem: number; + argvMem?: number; // 6.0 + multiMem?: number; // 7.0 obl: number; oll: number; omem: number; - totMem: number; + totMem?: number; // 6.0 events: string; cmd: string; - user: string; - redir: number; + user?: string; // 6.0 + redir?: number; // 6.2 + resp?: number; // 7.0 } -const REGEX = /=([^\s]*)/g; +const CLIENT_INFO_REGEX = /([^\s=]+)=([^\s]*)/g; -export function transformReply(reply: string): ClientInfoReply { - const [ - [, id], - [, addr], - [, laddr], - [, fd], - [, name], - [, age], - [, idle], - [, flags], - [, db], - [, sub], - [, psub], - [, multi], - [, qbuf], - [, qbufFree], - [, argvMem], - [, obl], - [, oll], - [, omem], - [, totMem], - [, events], - [, cmd], - [, user], - [, redir] - ] = [...reply.matchAll(REGEX)]; +export function transformReply(rawReply: string): ClientInfoReply { + const map: Record = {}; + for (const item of rawReply.matchAll(CLIENT_INFO_REGEX)) { + map[item[1]] = item[2]; + } - return { - id: Number(id), - addr, - laddr, - fd: Number(fd), - name, - age: Number(age), - idle: Number(idle), - flags, - db: Number(db), - sub: Number(sub), - psub: Number(psub), - multi: Number(multi), - qbuf: Number(qbuf), - qbufFree: Number(qbufFree), - argvMem: Number(argvMem), - obl: Number(obl), - oll: Number(oll), - omem: Number(omem), - totMem: Number(totMem), - events, - cmd, - user, - redir: Number(redir) + const reply: ClientInfoReply = { + id: Number(map.id), + addr: map.addr, + fd: Number(map.fd), + name: map.name, + age: Number(map.age), + idle: Number(map.idle), + flags: map.flags, + db: Number(map.db), + sub: Number(map.sub), + psub: Number(map.psub), + multi: Number(map.multi), + qbuf: Number(map.qbuf), + qbufFree: Number(map['qbuf-free']), + argvMem: Number(map['argv-mem']), + obl: Number(map.obl), + oll: Number(map.oll), + omem: Number(map.omem), + totMem: Number(map['tot-mem']), + events: map.events, + cmd: map.cmd, + user: map.user }; + + if (map.laddr !== undefined) { + reply.laddr = map.laddr; + } + + if (map.redir !== undefined) { + reply.redir = Number(map.redir); + } + + if (map.ssub !== undefined) { + reply.ssub = Number(map.ssub); + } + + if (map['multi-mem'] !== undefined) { + reply.multiMem = Number(map['multi-mem']); + } + + if (map.resp !== undefined) { + reply.resp = Number(map.resp); + } + + return reply; } diff --git a/packages/client/lib/commands/CLIENT_LIST.spec.ts b/packages/client/lib/commands/CLIENT_LIST.spec.ts new file mode 100644 index 00000000000..c9c720e12ef --- /dev/null +++ b/packages/client/lib/commands/CLIENT_LIST.spec.ts @@ -0,0 +1,78 @@ +import { strict as assert } from 'assert'; +import { transformArguments, transformReply } from './CLIENT_LIST'; +import testUtils, { GLOBAL } from '../test-utils'; + +describe('CLIENT LIST', () => { + describe('transformArguments', () => { + it('simple', () => { + assert.deepEqual( + transformArguments(), + ['CLIENT', 'LIST'] + ); + }); + + it('with TYPE', () => { + assert.deepEqual( + transformArguments({ + TYPE: 'NORMAL' + }), + ['CLIENT', 'LIST', 'TYPE', 'NORMAL'] + ); + }); + + it('with ID', () => { + assert.deepEqual( + transformArguments({ + ID: ['1', '2'] + }), + ['CLIENT', 'LIST', 'ID', '1', '2'] + ); + }); + }); + + testUtils.testWithClient('client.clientList', async client => { + const reply = await client.clientList(); + assert.ok(Array.isArray(reply)); + + for (const item of reply) { + assert.equal(typeof item.id, 'number'); + assert.equal(typeof item.addr, 'string'); + assert.equal(typeof item.fd, 'number'); + assert.equal(typeof item.name, 'string'); + assert.equal(typeof item.age, 'number'); + assert.equal(typeof item.idle, 'number'); + assert.equal(typeof item.flags, 'string'); + assert.equal(typeof item.db, 'number'); + assert.equal(typeof item.sub, 'number'); + assert.equal(typeof item.psub, 'number'); + assert.equal(typeof item.multi, 'number'); + assert.equal(typeof item.qbuf, 'number'); + assert.equal(typeof item.qbufFree, 'number'); + assert.equal(typeof item.obl, 'number'); + assert.equal(typeof item.oll, 'number'); + assert.equal(typeof item.omem, 'number'); + assert.equal(typeof item.events, 'string'); + assert.equal(typeof item.cmd, 'string'); + + if (testUtils.isVersionGreaterThan([6, 0])) { + assert.equal(typeof item.argvMem, 'number'); + assert.equal(typeof item.totMem, 'number'); + assert.equal(typeof item.user, 'string'); + } + + if (testUtils.isVersionGreaterThan([6, 2])) { + assert.equal(typeof item.redir, 'number'); + assert.equal(typeof item.laddr, 'string'); + } + + if (testUtils.isVersionGreaterThan([7, 0])) { + assert.equal(typeof item.multiMem, 'number'); + assert.equal(typeof item.resp, 'number'); + } + + if (testUtils.isVersionGreaterThan([7, 0, 3])) { + assert.equal(typeof item.ssub, 'number'); + } + } + }, GLOBAL.SERVERS.OPEN); +}); diff --git a/packages/client/lib/commands/CLIENT_LIST.ts b/packages/client/lib/commands/CLIENT_LIST.ts new file mode 100644 index 00000000000..6f71dc7d999 --- /dev/null +++ b/packages/client/lib/commands/CLIENT_LIST.ts @@ -0,0 +1,43 @@ +import { RedisCommandArguments, RedisCommandArgument } from '.'; +import { pushVerdictArguments } from './generic-transformers'; +import { transformReply as transformClientInfoReply, ClientInfoReply } from './CLIENT_INFO'; + +interface ListFilterType { + TYPE: 'NORMAL' | 'MASTER' | 'REPLICA' | 'PUBSUB'; + ID?: never; +} + +interface ListFilterId { + ID: Array; + TYPE?: never; +} + +export type ListFilter = ListFilterType | ListFilterId; + +export const IS_READ_ONLY = true; + +export function transformArguments(filter?: ListFilter): RedisCommandArguments { + let args: RedisCommandArguments = ['CLIENT', 'LIST']; + + if (filter) { + if (filter.TYPE !== undefined) { + args.push('TYPE', filter.TYPE); + } else { + args.push('ID'); + args = pushVerdictArguments(args, filter.ID); + } + } + + return args; +} + +export function transformReply(rawReply: string): Array { + const split = rawReply.split('\n'), + length = split.length - 1, + reply: Array = []; + for (let i = 0; i < length; i++) { + reply.push(transformClientInfoReply(split[i])); + } + + return reply; +}