1
0
mirror of https://github.com/redis/node-redis.git synced 2025-08-09 00:22:08 +03:00
* init v4

* add .gitignore to benchmark

* spawn redis-servers for tests,
add some tests,
fix client auth on connect

* add tests coverage report

* add tests workflow, replace nyc text reporter with text-summary

* run tests with node 16.x & redis 6.x only (for now)

* add socket events on client,
stop reconnectiong when manually calling disconnect,
remove abort signal listener when a command is written on the socket

* add isOpen boolean getter on client, add maxLength option to command queue, add test for client.multi

* move to use CommonJS

* add MULTI and EXEC commands to when executing multi command, make client.multi return type innerit the module commands, clean some tests, exclute spec files from coverage report

* missing file from commit 61edd4f1b5

* exclude spec files from coverage report

* add support for options in a command function (.get, .set, ...), add support for the SELECT command, implement a couple of commands, fix client socket reconnection strategy, add support for using replicas (RO) in cluster, and more..

* fix client.blPop test

* use which to find redis-server path

* change command options to work with Symbol rather then WeakSet

* implement more commands

* Add support for lua scripts in client & muilti, fix client socket initiator, implement simple cluster nodes discovery strategy

* replace `callbackify` with `legacyMode`

* add the SCAN command and client.scanIterator

* rename scanIterator

* init benchmark workflow

* fix benchmark workflow

* fix benchmark workflow

* fix benchmark workflow

* push coverage report to Coveralls

* fix Coveralls

* generator lcov (for Coveralls)

* fix .nycrc.json

* PubSub

* add support for all set commands (including sScanIterator)

* support pipeline

* fix KEEPTTL in SET

* remove console.log

* add HyperLogLog commands

* update README.md (thanks to @guyroyse)

* add support for most of the "keys commands"

* fix EXPIREAT.spec.ts

* add support for date in both EXPIREAT & EXPIRE

* add tests

* better cluster nodes discorvery strategy after MOVED error, add PubSub test

* fix PubSub UNSUBSCRIBE/PUNSUBSCRIBE without channel and/or listener

* fix PubSub

* add release-it to dev dependencies

* Release 4.0.0-next.0

* fix .npmignore

* Release 4.0.0-next.1

* fix links in README.md

* fix .npmignore

* Release 4.0.0-next.2

* add support for all sorted set commands

* add support for most stream commands

* add missing file from commit 53de279afe

* lots of todo commends

* make PubSub test more stable

* clean ZPOPMAX

* add support for lua scripts and modules in cluster, spawn cluster for tests, add some cluster tests, fix pubsub listener arguments

* GET.spec.ts

* add support for List commands, fix some Sorted Set commands, add some cluster commands, spawn cluster for testing, add support for command options in cluster, and more

* add missing file from commit faab94fab2

* clean ZRANK and ZREVRANK

* add XREAD and XREADGROUP commands

* remove unused files

* implement a couple of more commands, make cluster random iterator be per node (instead of per slot)

* Release 4.0.0-next.3

* app spec files to npmignore

* fix some code analyzers (LGTM, deepsource, codeclimate) issues

* fix CLUSTER_NODES, add some tests

* add HSCAN, clean some commands, add tests for generic transformers

* add missing files from 0feb35a1fb

* update README.md (thanks to @guyroyse)

* handle ASK errors, add some commands and tests

* Release 4.0.0-next.4

* replace "modern" with "v4"

* remove unused imports

* add all ACL subcommands, all MODULE subcommands, and some other commands

* remove 2 unused imports

* fix BITFIELD command

* fix XTRIM spec file

* clean code

* fix package.json types field

* better modules support, fix some bugs in legacy mode, add some tests

* remove unused function

* add test for hScanIterator

* change node mimimum version to 12 (latest LTS)

* update tsconfig.json to support node 12, run tests on Redis 5 & 6 and on all node live versions

* remove future node releases :P

* remove "lib" from ts compiler options

* Update tsconfig.json

* fix build

* run some tests only on supported redis versions, use coveralls parallel mode

* fix tests

* Do not use "timers/promises", fix "isRedisVersionGreaterThan"

* skip AbortController tests when not available

* use 'fs'.promises instead of 'fs/promises'

* add some missing commands

* run GETDEL tests only if the redis version is greater than 6.2

* implement some GEO commands, improve scan generic transformer, expose RPUSHX

* fix GEOSEARCH & GEOSEARCHSTORE

* use socket.setNoDelay and queueMicrotask to improve latency

* commands-queue.ts: String length / byte length counting issue (#1630)

* Update commands-queue.ts

Hopefully fixing #1628

* Reverted 2fa5ea6, and implemented test for byte length check

* Changed back to Buffer.byteLength, due to issue author input. Updated test to look for 4 bytes.

* Fixed. There were two places that length was calculated.

* Removed redundant string assignment

* add 2 bytes test as well

Co-authored-by: Leibale Eidelman <leibale1998@gmail.com>

* fix scripts in multi

* do not hide bugs in redis

* fix for e7bf09644b

* remove unused import

* implement WATCH command, fix ZRANGESTORE & GEOSEARCHSTORE tests

* update README.md

Co-authored-by: @GuyRoyse

* use typedoc to auto generate documentation

* run "npm install" before "npm run documentation"

* clean documentation workflow

* fix WATCH spec file

* increase "CLUSTER_NODE_TIMEOUT" to 5000ms to avoid "CLUSTERDOWN" errors in tests

* pull cluster state every 100 ms

* await meetPromises before pulling the cluster state

* enhance the way commanders (client/multi/cluster) get extended with modules and scripts

* add test for socket retry strategy

* implement more commands

* set GETEX minimum version to 6.2

* remove unused imports

* add support for multi in cluster

* upgrade dependencies

* Release 4.0.0-next.5

* remove unused imports

* improve benchmarking

* use the same Multi with duplicated clients

* exclude some files from the documentation, add some exports, clean code

* fix #1636 - handle null in multi.exec

* remove unused import

* add supoprt for tuples in HSET

* add FIRST_KEY_INDEX to HSET

* add a bunch of missing commands, fix MSET and HELLO, add some tests

* add FIRST_KEY_INDEX to MSET and MSETNX

* upgrade actions

* fix coverallsapp/github-action version

* Update documentation.yml

* Update documentation.yml

* clean code

* remove unused imports

* use "npm ci" instead of "npm install"

* fix `self` binding on client modules, use connection pool for `duplicateConnection`

* add client.executeIsolated, rename "duplicateConnection" to "isolated", update README.md (thanks to @GuyRoyse and @SimonPrickett)

* update README (thanks to @GuyRoyse), add some tests

* try to fix "cluster is down" errors in tests

* try to fix "cluster is down" errors in tests

* upgrade dependencies

* update package-lock

* Release 4.0.0-next.6

* fix #1636 - fix WatchError

* fix for f1bf0beebf - remove .only from multi tests

* Release 4.0.0-next.7

* update README and other markdown files

Co-authored-by: @GuyRoyse & @SimonPrickett

* Doc updates. (#1640)

* update docs, upgrade dependencies

* fix README

* Release 4.0.0-rc.0

* Update README.md

* update docs, add `connectTimeout` options, fix tls

Co-authored-by: Guy Royse <guy@guyroyse.com>

* npm update, "fix" some tests, clean code

* fix AssertionError import

* fix #1642 - fix XREAD, XREADGROUP and XTRIM

* fix #1644 - add the QUIT command

* add socket.noDelay and socket.keepAlive configurations

* Update README.md (#1645)

* Update README.md

Fixed issue with how connection string was specified.
Now you can have user@host without having to specify a password, which just makes more sense

* Update client-configuration.md as well

Co-authored-by: Leibale Eidelman <leibale1998@gmail.com>

* update socket.reconnectStrategy description

* fix borken link in v3-to-v4.md

* increase test coverage, fix bug in cluster redirection strategy, implement CLIENT_ID, remove unused EXEC command

Co-authored-by: Nova <novaw@warrenservices.co.uk>
Co-authored-by: Simon Prickett <simon@crudworks.org>
Co-authored-by: Guy Royse <guy@guyroyse.com>
This commit is contained in:
Leibale Eidelman
2021-09-02 10:04:48 -04:00
committed by GitHub
parent 4f85030e42
commit 4e6d018d77
661 changed files with 28847 additions and 14559 deletions

View File

@@ -0,0 +1,23 @@
import { strict as assert } from 'assert';
import { describeHandleMinimumRedisVersion } from '../test-utils';
import { transformArguments } from './ACL_CAT';
describe('ACL CAT', () => {
describeHandleMinimumRedisVersion([6]);
describe('transformArguments', () => {
it('simple', () => {
assert.deepEqual(
transformArguments(),
['ACL', 'CAT']
);
});
it('with categoryName', () => {
assert.deepEqual(
transformArguments('dangerous'),
['ACL', 'CAT', 'dangerous']
);
});
});
});

13
lib/commands/ACL_CAT.ts Normal file
View File

@@ -0,0 +1,13 @@
import { transformReplyStringArray } from './generic-transformers';
export function transformArguments(categoryName?: string): Array<string> {
const args = ['ACL', 'CAT'];
if (categoryName) {
args.push(categoryName);
}
return args;
}
export const transformReply = transformReplyStringArray;

View File

@@ -0,0 +1,30 @@
import { strict as assert } from 'assert';
import { describeHandleMinimumRedisVersion, itWithClient, TestRedisServers } from '../test-utils';
import { transformArguments } from './ACL_DELUSER';
describe('ACL DELUSER', () => {
describeHandleMinimumRedisVersion([6]);
describe('transformArguments', () => {
it('string', () => {
assert.deepEqual(
transformArguments('username'),
['ACL', 'DELUSER', 'username']
);
});
it('array', () => {
assert.deepEqual(
transformArguments(['1', '2']),
['ACL', 'DELUSER', '1', '2']
);
});
});
itWithClient(TestRedisServers.OPEN, 'client.aclDelUser', async client => {
assert.equal(
await client.aclDelUser('dosenotexists'),
0
);
});
});

View File

@@ -0,0 +1,7 @@
import { pushVerdictArguments, transformReplyNumber } from './generic-transformers';
export function transformArguments(username: string | Array<string>): Array<string> {
return pushVerdictArguments(['ACL', 'DELUSER'], username);
}
export const transformReply = transformReplyNumber;

View File

@@ -0,0 +1,23 @@
import { strict as assert } from 'assert';
import { describeHandleMinimumRedisVersion } from '../test-utils';
import { transformArguments } from './ACL_GENPASS';
describe('ACL GENPASS', () => {
describeHandleMinimumRedisVersion([6]);
describe('transformArguments', () => {
it('simple', () => {
assert.deepEqual(
transformArguments(),
['ACL', 'GENPASS']
);
});
it('with bits', () => {
assert.deepEqual(
transformArguments(128),
['ACL', 'GENPASS', '128']
);
});
});
});

View File

@@ -0,0 +1,13 @@
import { transformReplyString } from './generic-transformers';
export function transformArguments(bits?: number): Array<string> {
const args = ['ACL', 'GENPASS'];
if (bits) {
args.push(bits.toString());
}
return args;
}
export const transformReply = transformReplyString;

View File

@@ -0,0 +1,27 @@
import { strict as assert } from 'assert';
import { describeHandleMinimumRedisVersion, itWithClient, TestRedisServers } from '../test-utils';
import { transformArguments } from './ACL_GETUSER';
describe('ACL GETUSER', () => {
describeHandleMinimumRedisVersion([6]);
it('transformArguments', () => {
assert.deepEqual(
transformArguments('username'),
['ACL', 'GETUSER', 'username']
);
});
itWithClient(TestRedisServers.OPEN, 'client.aclGetUser', async client => {
assert.deepEqual(
await client.aclGetUser('default'),
{
flags: ['on', 'allkeys', 'allchannels', 'allcommands', 'nopass'],
passwords: [],
commands: '+@all',
keys: ['*'],
channels: ['*']
}
);
});
});

View File

@@ -0,0 +1,34 @@
export function transformArguments(username: string): Array<string> {
return ['ACL', 'GETUSER', username];
}
type AclGetUserRawReply = [
_: string,
flags: Array<string>,
_: string,
passwords: Array<string>,
_: string,
commands: string,
_: string,
keys: Array<string>,
_: string,
channels: Array<string>
];
interface AclUser {
flags: Array<string>;
passwords: Array<string>;
commands: string;
keys: Array<string>;
channels: Array<string>
}
export function transformReply(reply: AclGetUserRawReply): AclUser {
return {
flags: reply[1],
passwords: reply[3],
commands: reply[5],
keys: reply[7],
channels: reply[9]
};
}

View File

@@ -0,0 +1,14 @@
import { strict as assert } from 'assert';
import { describeHandleMinimumRedisVersion } from '../test-utils';
import { transformArguments } from './ACL_LIST';
describe('ACL LIST', () => {
describeHandleMinimumRedisVersion([6]);
it('transformArguments', () => {
assert.deepEqual(
transformArguments(),
['ACL', 'LIST']
);
});
});

7
lib/commands/ACL_LIST.ts Normal file
View File

@@ -0,0 +1,7 @@
import { transformReplyStringArray } from './generic-transformers';
export function transformArguments(): Array<string> {
return ['ACL', 'LIST'];
}
export const transformReply = transformReplyStringArray;

View File

@@ -0,0 +1,14 @@
import { strict as assert } from 'assert';
import { describeHandleMinimumRedisVersion } from '../test-utils';
import { transformArguments } from './ACL_SAVE';
describe('ACL SAVE', () => {
describeHandleMinimumRedisVersion([6]);
it('transformArguments', () => {
assert.deepEqual(
transformArguments(),
['ACL', 'SAVE']
);
});
});

7
lib/commands/ACL_LOAD.ts Normal file
View File

@@ -0,0 +1,7 @@
import { transformReplyString } from './generic-transformers';
export function transformArguments(): Array<string> {
return ['ACL', 'LOAD'];
}
export const transformReply = transformReplyString;

View File

@@ -0,0 +1,53 @@
import { strict as assert } from 'assert';
import { describeHandleMinimumRedisVersion } from '../test-utils';
import { transformArguments, transformReply } from './ACL_LOG';
describe('ACL LOG', () => {
describeHandleMinimumRedisVersion([6]);
describe('transformArguments', () => {
it('simple', () => {
assert.deepEqual(
transformArguments(),
['ACL', 'LOG']
);
});
it('with count', () => {
assert.deepEqual(
transformArguments(10),
['ACL', 'LOG', '10']
);
});
});
it('transformReply', () => {
assert.deepEqual(
transformReply([[
'count',
1,
'reason',
'auth',
'context',
'toplevel',
'object',
'AUTH',
'username',
'someuser',
'age-seconds',
'4.096',
'client-info',
'id=6 addr=127.0.0.1:63026 fd=8 name= age=9 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=48 qbuf-free=32720 obl=0 oll=0 omem=0 events=r cmd=auth user=default'
]]),
[{
count: 1,
reason: 'auth',
context: 'toplevel',
object: 'AUTH',
username: 'someuser',
ageSeconds: 4.096,
clientInfo: 'id=6 addr=127.0.0.1:63026 fd=8 name= age=9 idle=0 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=48 qbuf-free=32720 obl=0 oll=0 omem=0 events=r cmd=auth user=default'
}]
);
});
});

48
lib/commands/ACL_LOG.ts Normal file
View File

@@ -0,0 +1,48 @@
export function transformArguments(count?: number): Array<string> {
const args = ['ACL', 'LOG'];
if (count) {
args.push(count.toString());
}
return args;
}
type AclLogRawReply = [
_: string,
count: number,
_: string,
reason: string,
_: string,
context: string,
_: string,
object: string,
_: string,
username: string,
_: string,
ageSeconds: string,
_: string,
clientInfo: string
];
interface AclLog {
count: number;
reason: string;
context: string;
object: string;
username: string;
ageSeconds: number;
clientInfo: string;
}
export function transformReply(reply: Array<AclLogRawReply>): Array<AclLog> {
return reply.map(log => ({
count: log[1],
reason: log[3],
context: log[5],
object: log[7],
username: log[9],
ageSeconds: Number(log[11]),
clientInfo: log[13]
}));
}

View File

@@ -0,0 +1,14 @@
import { strict as assert } from 'assert';
import { describeHandleMinimumRedisVersion } from '../test-utils';
import { transformArguments } from './ACL_LOG_RESET';
describe('ACL LOG RESET', () => {
describeHandleMinimumRedisVersion([6]);
it('transformArguments', () => {
assert.deepEqual(
transformArguments(),
['ACL', 'LOG', 'RESET']
);
});
});

View File

@@ -0,0 +1,7 @@
import { transformReplyString } from './generic-transformers';
export function transformArguments(): Array<string> {
return ['ACL', 'LOG', 'RESET'];
}
export const transformReply = transformReplyString;

View File

@@ -0,0 +1,14 @@
import { strict as assert } from 'assert';
import { describeHandleMinimumRedisVersion } from '../test-utils';
import { transformArguments } from './ACL_LOAD';
describe('ACL LOAD', () => {
describeHandleMinimumRedisVersion([6]);
it('transformArguments', () => {
assert.deepEqual(
transformArguments(),
['ACL', 'LOAD']
);
});
});

7
lib/commands/ACL_SAVE.ts Normal file
View File

@@ -0,0 +1,7 @@
import { transformReplyString } from './generic-transformers';
export function transformArguments(): Array<string> {
return ['ACL', 'SAVE'];
}
export const transformReply = transformReplyString;

View File

@@ -0,0 +1,23 @@
import { strict as assert } from 'assert';
import { describeHandleMinimumRedisVersion } from '../test-utils';
import { transformArguments } from './ACL_SETUSER';
describe('ACL SETUSER', () => {
describeHandleMinimumRedisVersion([6]);
describe('transformArguments', () => {
it('string', () => {
assert.deepEqual(
transformArguments('username', 'allkeys'),
['ACL', 'SETUSER', 'username', 'allkeys']
);
});
it('array', () => {
assert.deepEqual(
transformArguments('username', ['allkeys', 'allchannels']),
['ACL', 'SETUSER', 'username', 'allkeys', 'allchannels']
);
});
});
});

View File

@@ -0,0 +1,7 @@
import { pushVerdictArguments, transformReplyString } from './generic-transformers';
export function transformArguments(username: string, rule: string | Array<string>): Array<string> {
return pushVerdictArguments(['ACL', 'SETUSER', username], rule);
}
export const transformReply = transformReplyString;

View File

@@ -0,0 +1,14 @@
import { strict as assert } from 'assert';
import { describeHandleMinimumRedisVersion } from '../test-utils';
import { transformArguments } from './ACL_USERS';
describe('ACL USERS', () => {
describeHandleMinimumRedisVersion([6]);
it('transformArguments', () => {
assert.deepEqual(
transformArguments(),
['ACL', 'USERS']
);
});
});

View File

@@ -0,0 +1,7 @@
import { transformReplyStringArray } from './generic-transformers';
export function transformArguments(): Array<string> {
return ['ACL', 'USERS'];
}
export const transformReply = transformReplyStringArray;

View File

@@ -0,0 +1,14 @@
import { strict as assert } from 'assert';
import { describeHandleMinimumRedisVersion } from '../test-utils';
import { transformArguments } from './ACL_WHOAMI';
describe('ACL WHOAMI', () => {
describeHandleMinimumRedisVersion([6]);
it('transformArguments', () => {
assert.deepEqual(
transformArguments(),
['ACL', 'WHOAMI']
);
});
});

View File

@@ -0,0 +1,7 @@
import { transformReplyString } from './generic-transformers';
export function transformArguments(): Array<string> {
return ['ACL', 'WHOAMI'];
}
export const transformReply = transformReplyString;

View File

@@ -0,0 +1,11 @@
import { strict as assert } from 'assert';
import { transformArguments } from './APPEND';
describe('AUTH', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments('key', 'value'),
['APPEND', 'key', 'value']
);
});
});

9
lib/commands/APPEND.ts Normal file
View File

@@ -0,0 +1,9 @@
import { transformReplyString } from './generic-transformers';
export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string, value: string): Array<string> {
return ['APPEND', key, value];
}
export const transformReply = transformReplyString;

View File

@@ -0,0 +1,11 @@
import { strict as assert } from 'assert';
import { transformArguments } from './ASKING';
describe('ASKING', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments(),
['ASKING']
);
});
});

7
lib/commands/ASKING.ts Normal file
View File

@@ -0,0 +1,7 @@
import { transformReplyString } from './generic-transformers';
export function transformArguments(): Array<string> {
return ['ASKING'];
}
export const transformReply = transformReplyString;

25
lib/commands/AUTH.spec.ts Normal file
View File

@@ -0,0 +1,25 @@
import { strict as assert } from 'assert';
import { transformArguments } from './AUTH';
describe('AUTH', () => {
describe('transformArguments', () => {
it('password only', () => {
assert.deepEqual(
transformArguments({
password: 'password'
}),
['AUTH', 'password']
);
});
it('username & password', () => {
assert.deepEqual(
transformArguments({
username: 'username',
password: 'password'
}),
['AUTH', 'username', 'password']
);
});
});
});

16
lib/commands/AUTH.ts Normal file
View File

@@ -0,0 +1,16 @@
import { transformReplyString } from './generic-transformers';
export interface AuthOptions {
username?: string;
password: string;
}
export function transformArguments({username, password}: AuthOptions): Array<string> {
if (!username) {
return ['AUTH', password];
}
return ['AUTH', username, password];
}
export const transformReply = transformReplyString;

View File

@@ -0,0 +1,11 @@
import { strict as assert } from 'assert';
import { transformArguments } from './BGREWRITEAOF';
describe('BGREWRITEAOF', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments(),
['BGREWRITEAOF']
);
});
});

View File

@@ -0,0 +1,7 @@
import { transformReplyString } from './generic-transformers';
export function transformArguments(): Array<string> {
return ['BGREWRITEAOF'];
}
export const transformReply = transformReplyString;

View File

@@ -0,0 +1,23 @@
import { strict as assert } from 'assert';
import { describe } from 'mocha';
import { transformArguments } from './BGSAVE';
describe('BGSAVE', () => {
describe('transformArguments', () => {
it('simple', () => {
assert.deepEqual(
transformArguments(),
['BGSAVE']
);
});
it('with SCHEDULE', () => {
assert.deepEqual(
transformArguments({
SCHEDULE: true
}),
['BGSAVE', 'SCHEDULE']
);
});
});
});

17
lib/commands/BGSAVE.ts Normal file
View File

@@ -0,0 +1,17 @@
import { transformReplyString } from './generic-transformers';
interface BgSaveOptions {
SCHEDULE?: true;
}
export function transformArguments(options?: BgSaveOptions): Array<string> {
const args = ['BGSAVE'];
if (options?.SCHEDULE) {
args.push('SCHEDULE');
}
return args;
}
export const transformReply = transformReplyString;

View File

@@ -0,0 +1,31 @@
import { strict as assert } from 'assert';
import { TestRedisServers, itWithClient } from '../test-utils';
import { transformArguments } from './BITCOUNT';
describe('BITCOUNT', () => {
describe('transformArguments', () => {
it('simple', () => {
assert.deepEqual(
transformArguments('key'),
['BITCOUNT', 'key']
);
});
it('with range', () => {
assert.deepEqual(
transformArguments('key', {
start: 0,
end: 1
}),
['BITCOUNT', 'key', '0', '1']
);
});
});
itWithClient(TestRedisServers.OPEN, 'client.bitCount', async client => {
assert.equal(
await client.bitCount('key'),
0
);
});
});

25
lib/commands/BITCOUNT.ts Normal file
View File

@@ -0,0 +1,25 @@
import { transformReplyNumber } from './generic-transformers';
export const FIRST_KEY_INDEX = 1;
export const IS_READ_ONLY = true;
interface BitCountRange {
start: number;
end: number;
}
export function transformArguments(key: string, range?: BitCountRange): Array<string> {
const args = ['BITCOUNT', key];
if (range) {
args.push(
range.start.toString(),
range.end.toString()
);
}
return args;
}
export const transformReply = transformReplyNumber;

View File

@@ -0,0 +1,42 @@
import { strict as assert } from 'assert';
import { TestRedisServers, itWithClient } from '../test-utils';
import { transformArguments } from './BITFIELD';
describe('BITFIELD', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments('key', [{
operation: 'OVERFLOW',
behavior: 'WRAP'
}, {
operation: 'GET',
type: 'i8',
offset: 0
}, {
operation: 'OVERFLOW',
behavior: 'SAT'
}, {
operation: 'SET',
type: 'i16',
offset: 1,
value: 0
}, {
operation: 'OVERFLOW',
behavior: 'FAIL'
}, {
operation: 'INCRBY',
type: 'i32',
offset: 2,
increment: 1
}]),
['BITFIELD', 'key', 'OVERFLOW', 'WRAP', 'GET', 'i8', '0', 'OVERFLOW', 'SAT', 'SET', 'i16', '1', '0', 'OVERFLOW', 'FAIL', 'INCRBY', 'i32', '2', '1']
);
});
itWithClient(TestRedisServers.OPEN, 'client.bitField', async client => {
assert.deepEqual(
await client.bitField('key', []),
[]
);
});
});

84
lib/commands/BITFIELD.ts Normal file
View File

@@ -0,0 +1,84 @@
import { transformReplyNumberNullArray } from './generic-transformers';
export const FIRST_KEY_INDEX = 1;
export const IS_READ_ONLY = true;
type BitFieldType = string; // TODO 'i[1-64]' | 'u[1-63]'
interface BitFieldOperation<S extends string> {
operation: S;
}
interface BitFieldGetOperation extends BitFieldOperation<'GET'> {
type: BitFieldType;
offset: number | string;
}
interface BitFieldSetOperation extends BitFieldOperation<'SET'> {
type: BitFieldType;
offset: number | string;
value: number;
}
interface BitFieldIncrByOperation extends BitFieldOperation<'INCRBY'> {
type: BitFieldType;
offset: number | string;
increment: number;
}
interface BitFieldOverflowOperation extends BitFieldOperation<'OVERFLOW'> {
behavior: string;
}
type BitFieldOperations = Array<
BitFieldGetOperation |
BitFieldSetOperation |
BitFieldIncrByOperation |
BitFieldOverflowOperation
>;
export function transformArguments(key: string, operations: BitFieldOperations): Array<string> {
const args = ['BITFIELD', key];
for (const options of operations) {
switch (options.operation) {
case 'GET':
args.push(
'GET',
options.type,
options.offset.toString()
);
break;
case 'SET':
args.push(
'SET',
options.type,
options.offset.toString(),
options.value.toString()
);
break;
case 'INCRBY':
args.push(
'INCRBY',
options.type,
options.offset.toString(),
options.increment.toString()
)
break;
case 'OVERFLOW':
args.push(
'OVERFLOW',
options.behavior
);
break;
}
}
return args;
}
export const transformReply = transformReplyNumberNullArray;

View File

@@ -0,0 +1,35 @@
import { strict as assert } from 'assert';
import { TestRedisServers, itWithClient, TestRedisClusters, itWithCluster } from '../test-utils';
import { transformArguments } from './BITOP';
describe('BITOP', () => {
describe('transformArguments', () => {
it('single key', () => {
assert.deepEqual(
transformArguments('AND', 'destKey', 'key'),
['BITOP', 'AND', 'destKey', 'key']
);
});
it('multiple keys', () => {
assert.deepEqual(
transformArguments('AND', 'destKey', ['1', '2']),
['BITOP', 'AND', 'destKey', '1', '2']
);
});
});
itWithClient(TestRedisServers.OPEN, 'client.bitOp', async client => {
assert.equal(
await client.bitOp('AND', 'destKey', 'key'),
0
);
});
itWithCluster(TestRedisClusters.OPEN, 'cluster.bitOp', async cluster => {
assert.equal(
await cluster.bitOp('AND', '{tag}destKey', '{tag}key'),
0
);
});
});

11
lib/commands/BITOP.ts Normal file
View File

@@ -0,0 +1,11 @@
import { pushVerdictArguments, transformReplyNumber } from './generic-transformers';
export const FIRST_KEY_INDEX = 2;
type BitOperations = 'AND' | 'OR' | 'XOR' | 'NOT';
export function transformArguments(operation: BitOperations, destKey: string, key: string | Array<string>): Array<string> {
return pushVerdictArguments(['BITOP', operation, destKey], key);
}
export const transformReply = transformReplyNumber;

View File

@@ -0,0 +1,42 @@
import { strict as assert } from 'assert';
import { TestRedisServers, itWithClient, TestRedisClusters, itWithCluster } from '../test-utils';
import { transformArguments } from './BITPOS';
describe('BITPOS', () => {
describe('transformArguments', () => {
it('simple', () => {
assert.deepEqual(
transformArguments('key', 1),
['BITPOS', 'key', '1']
);
});
it('with start', () => {
assert.deepEqual(
transformArguments('key', 1, 1),
['BITPOS', 'key', '1', '1']
);
});
it('with start, end', () => {
assert.deepEqual(
transformArguments('key', 1, 1, -1),
['BITPOS', 'key', '1', '1', '-1']
);
});
});
itWithClient(TestRedisServers.OPEN, 'client.bitPos', async client => {
assert.equal(
await client.bitPos('key', 1, 1),
-1
);
});
itWithCluster(TestRedisClusters.OPEN, 'cluster.bitPos', async cluster => {
assert.equal(
await cluster.bitPos('key', 1, 1),
-1
);
});
});

21
lib/commands/BITPOS.ts Normal file
View File

@@ -0,0 +1,21 @@
import { BitValue, transformReplyNumber } from './generic-transformers';
export const FIRST_KEY_INDEX = 1;
export const IS_READ_ONLY = true;
export function transformArguments(key: string, bit: BitValue, start?: number, end?: number): Array<string> {
const args = ['BITPOS', key, bit.toString()];
if (typeof start === 'number') {
args.push(start.toString());
}
if (typeof end === 'number') {
args.push(end.toString());
}
return args;
}
export const transformReply = transformReplyNumber;

View File

@@ -0,0 +1,43 @@
import { strict as assert } from 'assert';
import { TestRedisServers, itWithClient, itWithCluster, TestRedisClusters, describeHandleMinimumRedisVersion } from '../test-utils';
import { transformArguments } from './BLMOVE';
import { commandOptions } from '../../index';
describe('BLMOVE', () => {
describeHandleMinimumRedisVersion([6, 2]);
it('transformArguments', () => {
assert.deepEqual(
transformArguments('source', 'destination', 'LEFT', 'RIGHT', 0),
['BLMOVE', 'source', 'destination', 'LEFT', 'RIGHT', '0']
);
});
itWithClient(TestRedisServers.OPEN, 'client.blMove', async client => {
const [blMoveReply] = await Promise.all([
client.blMove(commandOptions({
isolated: true
}), 'source', 'destination', 'LEFT', 'RIGHT', 0),
client.lPush('source', 'element')
]);
assert.equal(
blMoveReply,
'element'
);
});
itWithCluster(TestRedisClusters.OPEN, 'cluster.blMove', async cluster => {
const [blMoveReply] = await Promise.all([
cluster.blMove(commandOptions({
isolated: true
}), '{tag}source', '{tag}destination', 'LEFT', 'RIGHT', 0),
cluster.lPush('{tag}source', 'element')
]);
assert.equal(
blMoveReply,
'element'
);
});
});

23
lib/commands/BLMOVE.ts Normal file
View File

@@ -0,0 +1,23 @@
import { transformReplyStringNull } from './generic-transformers';
import { LMoveSide } from './LMOVE';
export const FIRST_KEY_INDEX = 1;
export function transformArguments(
source: string,
destination: string,
sourceDirection: LMoveSide,
destinationDirection: LMoveSide,
timeout: number
): Array<string> {
return [
'BLMOVE',
source,
destination,
sourceDirection,
destinationDirection,
timeout.toString()
];
}
export const transformReply = transformReplyStringNull;

View File

@@ -0,0 +1,79 @@
import { strict as assert } from 'assert';
import { TestRedisServers, itWithClient, itWithCluster, TestRedisClusters } from '../test-utils';
import { transformArguments, transformReply } from './BLPOP';
import { commandOptions } from '../../index';
describe('BLPOP', () => {
describe('transformArguments', () => {
it('single', () => {
assert.deepEqual(
transformArguments('key', 0),
['BLPOP', 'key', '0']
);
});
it('multiple', () => {
assert.deepEqual(
transformArguments(['key1', 'key2'], 0),
['BLPOP', 'key1', 'key2', '0']
);
});
});
describe('transformReply', () => {
it('null', () => {
assert.equal(
transformReply(null),
null
);
});
it('member', () => {
assert.deepEqual(
transformReply(['key', 'element']),
{
key: 'key',
element: 'element'
}
);
});
});
itWithClient(TestRedisServers.OPEN, 'client.blPop', async client => {
const [ blPopReply ] = await Promise.all([
client.blPop(
commandOptions({ isolated: true }),
'key',
1
),
client.lPush('key', 'element'),
]);
assert.deepEqual(
blPopReply,
{
key: 'key',
element: 'element'
}
);
});
itWithCluster(TestRedisClusters.OPEN, 'cluster.blPop', async cluster => {
const [ blPopReply ] = await Promise.all([
cluster.blPop(
commandOptions({ isolated: true }),
'key',
1
),
cluster.lPush('key', 'element'),
]);
assert.deepEqual(
blPopReply,
{
key: 'key',
element: 'element'
}
);
});
});

25
lib/commands/BLPOP.ts Normal file
View File

@@ -0,0 +1,25 @@
import { pushVerdictArguments } from './generic-transformers';
export const FIRST_KEY_INDEX = 1;
export function transformArguments(keys: string | Array<string>, timeout: number): Array<string> {
const args = pushVerdictArguments(['BLPOP'], keys);
args.push(timeout.toString());
return args;
}
type BLPOPReply = null | {
key: string;
element: string;
};
export function transformReply(reply: null | [string, string]): BLPOPReply {
if (reply === null) return null;
return {
key: reply[0],
element: reply[1]
};
}

View File

@@ -0,0 +1,79 @@
import { strict as assert } from 'assert';
import { TestRedisServers, itWithClient, itWithCluster, TestRedisClusters } from '../test-utils';
import { transformArguments, transformReply } from './BRPOP';
import { commandOptions } from '../../index';
describe('BRPOP', () => {
describe('transformArguments', () => {
it('single', () => {
assert.deepEqual(
transformArguments('key', 0),
['BRPOP', 'key', '0']
);
});
it('multiple', () => {
assert.deepEqual(
transformArguments(['key1', 'key2'], 0),
['BRPOP', 'key1', 'key2', '0']
);
});
});
describe('transformReply', () => {
it('null', () => {
assert.equal(
transformReply(null),
null
);
});
it('member', () => {
assert.deepEqual(
transformReply(['key', 'element']),
{
key: 'key',
element: 'element'
}
);
});
});
itWithClient(TestRedisServers.OPEN, 'client.brPop', async client => {
const [ brPopReply ] = await Promise.all([
client.brPop(
commandOptions({ isolated: true }),
'key',
1
),
client.lPush('key', 'element'),
]);
assert.deepEqual(
brPopReply,
{
key: 'key',
element: 'element'
}
);
});
itWithCluster(TestRedisClusters.OPEN, 'cluster.brPop', async cluster => {
const [ brPopReply ] = await Promise.all([
cluster.brPop(
commandOptions({ isolated: true }),
'key',
1
),
cluster.lPush('key', 'element'),
]);
assert.deepEqual(
brPopReply,
{
key: 'key',
element: 'element'
}
);
});
});

25
lib/commands/BRPOP.ts Normal file
View File

@@ -0,0 +1,25 @@
import { pushVerdictArguments } from './generic-transformers';
export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string | Array<string>, timeout: number): Array<string> {
const args = pushVerdictArguments(['BRPOP'], key);
args.push(timeout.toString());
return args;
}
type BRPOPReply = null | {
key: string;
element: string;
};
export function transformReply(reply: null | [string, string]): BRPOPReply {
if (reply === null) return null;
return {
key: reply[0],
element: reply[1]
};
}

View File

@@ -0,0 +1,47 @@
import { strict as assert } from 'assert';
import { TestRedisServers, itWithClient, itWithCluster, TestRedisClusters } from '../test-utils';
import { transformArguments } from './BRPOPLPUSH';
import { commandOptions } from '../../index';
describe('BRPOPLPUSH', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments('source', 'destination', 0),
['BRPOPLPUSH', 'source', 'destination', '0']
);
});
itWithClient(TestRedisServers.OPEN, 'client.brPopLPush', async client => {
const [ popReply ] = await Promise.all([
client.brPopLPush(
commandOptions({ isolated: true }),
'source',
'destination',
0
),
client.lPush('source', 'element')
]);
assert.equal(
popReply,
'element'
);
});
itWithCluster(TestRedisClusters.OPEN, 'cluster.brPopLPush', async cluster => {
const [ popReply ] = await Promise.all([
cluster.brPopLPush(
commandOptions({ isolated: true }),
'{tag}source',
'{tag}destination',
0
),
cluster.lPush('{tag}source', 'element')
]);
assert.equal(
popReply,
'element'
);
});
});

View File

@@ -0,0 +1,9 @@
import { transformReplyNumberNull } from './generic-transformers';
export const FIRST_KEY_INDEX = 1;
export function transformArguments(source: string, destination: string, timeout: number): Array<string> {
return ['BRPOPLPUSH', source, destination, timeout.toString()];
}
export const transformReply = transformReplyNumberNull;

View File

@@ -0,0 +1,66 @@
import { strict as assert } from 'assert';
import { TestRedisServers, itWithClient } from '../test-utils';
import { transformArguments, transformReply } from './BZPOPMAX';
import { commandOptions } from '../../index';
import { describe } from 'mocha';
describe('BZPOPMAX', () => {
describe('transformArguments', () => {
it('single', () => {
assert.deepEqual(
transformArguments('key', 0),
['BZPOPMAX', 'key', '0']
);
});
it('multiple', () => {
assert.deepEqual(
transformArguments(['1', '2'], 0),
['BZPOPMAX', '1', '2', '0']
);
});
});
describe('transformReply', () => {
it('null', () => {
assert.equal(
transformReply(null),
null
);
});
it('member', () => {
assert.deepEqual(
transformReply(['key', 'value', '1']),
{
key: 'key',
value: 'value',
score: 1
}
);
});
});
itWithClient(TestRedisServers.OPEN, 'client.bzPopMax', async client => {
const [ bzPopMaxReply ] = await Promise.all([
client.bzPopMax(
commandOptions({ isolated: true }),
'key',
0
),
client.zAdd('key', [{
value: '1',
score: 1
}])
]);
assert.deepEqual(
bzPopMaxReply,
{
key: 'key',
value: '1',
score: 1
}
);
});
});

27
lib/commands/BZPOPMAX.ts Normal file
View File

@@ -0,0 +1,27 @@
import { pushVerdictArguments, transformReplyNumberInfinity, ZMember } from './generic-transformers';
export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string | Array<string>, timeout: number): Array<string> {
const args = pushVerdictArguments(['BZPOPMAX'], key);
args.push(timeout.toString());
return args;
}
interface ZMemberWithKey extends ZMember {
key: string;
}
type BZPopMaxReply = ZMemberWithKey | null;
export function transformReply(reply: [key: string, value: string, score: string] | null): BZPopMaxReply | null {
if (!reply) return null;
return {
key: reply[0],
value: reply[1],
score: transformReplyNumberInfinity(reply[2])
};
}

View File

@@ -0,0 +1,65 @@
import { strict as assert } from 'assert';
import { TestRedisServers, itWithClient } from '../test-utils';
import { transformArguments, transformReply } from './BZPOPMIN';
import { commandOptions } from '../../index';
describe('BZPOPMIN', () => {
describe('transformArguments', () => {
it('single', () => {
assert.deepEqual(
transformArguments('key', 0),
['BZPOPMIN', 'key', '0']
);
});
it('multiple', () => {
assert.deepEqual(
transformArguments(['1', '2'], 0),
['BZPOPMIN', '1', '2', '0']
);
});
});
describe('transformReply', () => {
it('null', () => {
assert.equal(
transformReply(null),
null
);
});
it('member', () => {
assert.deepEqual(
transformReply(['key', 'value', '1']),
{
key: 'key',
value: 'value',
score: 1
}
);
});
});
itWithClient(TestRedisServers.OPEN, 'client.bzPopMin', async client => {
const [ bzPopMinReply ] = await Promise.all([
client.bzPopMin(
commandOptions({ isolated: true }),
'key',
0
),
client.zAdd('key', [{
value: '1',
score: 1
}])
]);
assert.deepEqual(
bzPopMinReply,
{
key: 'key',
value: '1',
score: 1
}
);
});
});

27
lib/commands/BZPOPMIN.ts Normal file
View File

@@ -0,0 +1,27 @@
import { pushVerdictArguments, transformReplyNumberInfinity, ZMember } from './generic-transformers';
export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string | Array<string>, timeout: number): Array<string> {
const args = pushVerdictArguments(['BZPOPMIN'], key);
args.push(timeout.toString());
return args;
}
interface ZMemberWithKey extends ZMember {
key: string;
}
type BZPopMinReply = ZMemberWithKey | null;
export function transformReply(reply: [key: string, value: string, score: string] | null): BZPopMinReply | null {
if (!reply) return null;
return {
key: reply[0],
value: reply[1],
score: transformReplyNumberInfinity(reply[2])
};
}

View File

@@ -0,0 +1,19 @@
import { strict as assert } from 'assert';
import { TestRedisServers, itWithClient } from '../test-utils';
import { transformArguments } from './CLIENT_ID';
describe('CLIENT ID', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments(),
['CLIENT', 'ID']
);
});
itWithClient(TestRedisServers.OPEN, 'client.clientId', async client => {
assert.equal(
typeof (await client.clientId()),
'number'
);
});
});

View File

@@ -0,0 +1,9 @@
import { transformReplyNumber } from './generic-transformers';
export const IS_READ_ONLY = true;
export function transformArguments(): Array<string> {
return ['CLIENT', 'ID'];
}
export const transformReply = transformReplyNumber;

View File

@@ -0,0 +1,42 @@
import { strict as assert } from 'assert';
import { transformArguments, transformReply } from './CLIENT_INFO';
describe('CLIENT INFO', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments(),
['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
}
);
});
});

View File

@@ -0,0 +1,85 @@
export function transformArguments(): Array<string> {
return ['CLIENT', 'INFO'];
}
interface ClientInfoReply {
id: number;
addr: string;
laddr: string;
fd: number;
name: string;
age: number;
idle: number;
flags: string;
db: number;
sub: number;
psub: number;
multi: number;
qbuf: number;
qbufFree: number;
argvMem: number;
obl: number;
oll: number;
omem: number;
totMem: number;
events: string;
cmd: string;
user: string;
redir: number;
}
const REGEX = /=([^\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)];
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)
};
}

View File

@@ -0,0 +1,20 @@
import { strict as assert } from 'assert';
import { transformArguments } 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']
);
});
});
});

View File

@@ -0,0 +1,15 @@
import { transformReplyString } from './generic-transformers';
export function transformArguments(slots: number | Array<number>): Array<string> {
const args = ['CLUSTER', 'ADDSLOTS'];
if (typeof slots === 'number') {
args.push(slots.toString());
} else {
args.push(...slots.map(String));
}
return args;
}
export const transformReply = transformReplyString;

View File

@@ -0,0 +1,11 @@
import { strict as assert } from 'assert';
import { transformArguments } from './CLUSTER_FLUSHSLOTS';
describe('CLUSTER FLUSHSLOTS', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments(),
['CLUSTER', 'FLUSHSLOTS']
);
});
});

View File

@@ -0,0 +1,7 @@
import { transformReplyString } from './generic-transformers';
export function transformArguments(): Array<string> {
return ['CLUSTER', 'FLUSHSLOTS'];
}
export const transformReply = transformReplyString;

View File

@@ -0,0 +1,11 @@
import { strict as assert } from 'assert';
import { transformArguments } from './CLUSTER_GETKEYSINSLOT';
describe('CLUSTER GETKEYSINSLOT', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments(0, 10),
['CLUSTER', 'GETKEYSINSLOT', '0', '10']
);
});
});

View File

@@ -0,0 +1,7 @@
import { transformReplyString } from './generic-transformers';
export function transformArguments(slot: number, count: number): Array<string> {
return ['CLUSTER', 'GETKEYSINSLOT', slot.toString(), count.toString()];
}
export const transformReply = transformReplyString;

View File

@@ -0,0 +1,64 @@
import { strict as assert } from 'assert';
import { itWithCluster, TestRedisClusters } from '../test-utils';
import { transformArguments, transformReply } from './CLUSTER_INFO';
describe('CLUSTER INFO', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments(),
['CLUSTER', 'INFO']
);
});
it('transformReply', () => {
assert.deepEqual(
transformReply([
'cluster_state:ok',
'cluster_slots_assigned:16384',
'cluster_slots_ok:16384',
'cluster_slots_pfail:0',
'cluster_slots_fail:0',
'cluster_known_nodes:6',
'cluster_size:3',
'cluster_current_epoch:6',
'cluster_my_epoch:2',
'cluster_stats_messages_sent:1483972',
'cluster_stats_messages_received:1483968'
].join('\r\n')),
{
state: 'ok',
slots: {
assigned: 16384,
ok: 16384,
pfail: 0,
fail: 0
},
knownNodes: 6,
size: 3,
currentEpoch: 6,
myEpoch: 2,
stats: {
messagesSent: 1483972,
messagesReceived: 1483968
}
}
);
});
itWithCluster(TestRedisClusters.OPEN, 'cluster.clusterInfo', async cluster => {
const info = await cluster.clusterInfo();
assert.equal(info.state, 'ok');
assert.deepEqual(info.slots, {
assigned: 16384,
ok: 16384,
pfail: 0,
fail: 0
});
assert.equal(info.knownNodes, 3);
assert.equal(info.size, 3);
assert.equal(typeof info.currentEpoch, 'number');
assert.equal(typeof info.myEpoch, 'number');
assert.equal(typeof info.stats.messagesReceived, 'number');
assert.equal(typeof info.stats.messagesSent, 'number');
});
});

View File

@@ -0,0 +1,47 @@
export function transformArguments(): Array<string> {
return ['CLUSTER', 'INFO'];
}
interface ClusterInfoReply {
state: string;
slots: {
assigned: number;
ok: number;
pfail: number;
fail: number;
};
knownNodes: number;
size: number;
currentEpoch: number;
myEpoch: number;
stats: {
messagesSent: number;
messagesReceived: number;
};
}
export function transformReply(reply: string): ClusterInfoReply {
const lines = reply.split('\r\n');
return {
state: extractLineValue(lines[0]),
slots: {
assigned: Number(extractLineValue(lines[1])),
ok: Number(extractLineValue(lines[2])),
pfail: Number(extractLineValue(lines[3])),
fail: Number(extractLineValue(lines[4]))
},
knownNodes: Number(extractLineValue(lines[5])),
size: Number(extractLineValue(lines[6])),
currentEpoch: Number(extractLineValue(lines[7])),
myEpoch: Number(extractLineValue(lines[8])),
stats: {
messagesSent: Number(extractLineValue(lines[9])),
messagesReceived: Number(extractLineValue(lines[10]))
}
};
}
export function extractLineValue(line: string): string {
return line.substring(line.indexOf(':') + 1);
}

View File

@@ -0,0 +1,11 @@
import { strict as assert } from 'assert';
import { transformArguments } from './CLUSTER_MEET';
describe('CLUSTER MEET', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments('127.0.0.1', 6379),
['CLUSTER', 'MEET', '127.0.0.1', '6379']
);
});
});

View File

@@ -0,0 +1,7 @@
import { transformReplyString } from './generic-transformers';
export function transformArguments(ip: string, port: number): Array<string> {
return ['CLUSTER', 'MEET', ip, port.toString()];
}
export const transformReply = transformReplyString;

View File

@@ -0,0 +1,116 @@
import { strict as assert } from 'assert';
import { itWithCluster, TestRedisClusters } from '../test-utils';
import { RedisClusterNodeLinkStates, transformArguments, transformReply } from './CLUSTER_NODES';
describe('CLUSTER NODES', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments(),
['CLUSTER', 'NODES']
);
});
describe('transformReply', () => {
it('simple', () => {
assert.deepEqual(
transformReply([
'master 127.0.0.1:30001@31001 myself,master - 0 0 1 connected 0-16384',
'slave 127.0.0.1:30002@31002 slave master 0 0 1 connected',
''
].join('\n')),
[{
id: 'master',
url: '127.0.0.1:30001@31001',
host: '127.0.0.1',
port: 30001,
cport: 31001,
flags: ['myself', 'master'],
pingSent: 0,
pongRecv: 0,
configEpoch: 1,
linkState: RedisClusterNodeLinkStates.CONNECTED,
slots: [{
from: 0,
to: 16384
}],
replicas: [{
id: 'slave',
url: '127.0.0.1:30002@31002',
host: '127.0.0.1',
port: 30002,
cport: 31002,
flags: ['slave'],
pingSent: 0,
pongRecv: 0,
configEpoch: 1,
linkState: RedisClusterNodeLinkStates.CONNECTED
}]
}]
);
});
it.skip('with importing slots', () => {
assert.deepEqual(
transformReply(
'id 127.0.0.1:30001@31001 master - 0 0 0 connected 0-<-16384\n'
),
[{
id: 'id',
url: '127.0.0.1:30001@31001',
host: '127.0.0.1',
port: 30001,
cport: 31001,
flags: ['master'],
pingSent: 0,
pongRecv: 0,
configEpoch: 0,
linkState: RedisClusterNodeLinkStates.CONNECTED,
slots: [], // TODO
replicas: []
}]
);
});
it.skip('with migrating slots', () => {
assert.deepEqual(
transformReply(
'id 127.0.0.1:30001@31001 master - 0 0 0 connected 0->-16384\n'
),
[{
id: 'id',
url: '127.0.0.1:30001@31001',
host: '127.0.0.1',
port: 30001,
cport: 31001,
flags: ['master'],
pingSent: 0,
pongRecv: 0,
configEpoch: 0,
linkState: RedisClusterNodeLinkStates.CONNECTED,
slots: [], // TODO
replicas: []
}]
);
});
});
itWithCluster(TestRedisClusters.OPEN, 'cluster.clusterNodes', async cluster => {
for (const node of (await cluster.clusterNodes())) {
assert.equal(typeof node.id, 'string');
assert.equal(typeof node.url, 'string');
assert.equal(typeof node.host, 'string');
assert.equal(typeof node.port, 'number');
assert.equal(typeof node.cport, 'number');
assert.ok(Array.isArray(node.flags));
assert.equal(typeof node.pingSent, 'number');
assert.equal(typeof node.pongRecv, 'number');
assert.equal(typeof node.configEpoch, 'number');
assert.equal(typeof node.linkState, 'string');
for (const slot of node.slots) {
assert.equal(typeof slot.from, 'number');
assert.equal(typeof slot.to, 'number');
}
}
});
});

View File

@@ -0,0 +1,96 @@
export function transformArguments(): Array<string> {
return ['CLUSTER', 'NODES'];
}
export enum RedisClusterNodeLinkStates {
CONNECTED = 'connected',
DISCONNECTED = 'disconnected'
}
interface RedisClusterNodeTransformedUrl {
host: string;
port: number;
cport: number;
}
export interface RedisClusterReplicaNode extends RedisClusterNodeTransformedUrl {
id: string;
url: string;
flags: Array<string>;
pingSent: number;
pongRecv: number;
configEpoch: number;
linkState: RedisClusterNodeLinkStates;
}
export interface RedisClusterMasterNode extends RedisClusterReplicaNode {
slots: Array<{
from: number;
to: number;
}>;
replicas: Array<RedisClusterReplicaNode>;
}
export function transformReply(reply: string): Array<RedisClusterMasterNode> {
const lines = reply.split('\n');
lines.pop(); // last line is empty
const mastersMap = new Map<string, RedisClusterMasterNode>(),
replicasMap = new Map<string, Array<RedisClusterReplicaNode>>();
for (const line of lines) {
const [id, url, flags, masterId, pingSent, pongRecv, configEpoch, linkState, ...slots] = line.split(' '),
node = {
id,
url,
...transformNodeUrl(url),
flags: flags.split(','),
pingSent: Number(pingSent),
pongRecv: Number(pongRecv),
configEpoch: Number(configEpoch),
linkState: (linkState as RedisClusterNodeLinkStates)
};
if (masterId === '-') {
let replicas = replicasMap.get(id);
if (!replicas) {
replicas = [];
replicasMap.set(id, replicas);
}
mastersMap.set(id, {
...node,
slots: slots.map(slot => {
// TODO: importing & exporting (https://redis.io/commands/cluster-nodes#special-slot-entries)
const [fromString, toString] = slot.split('-', 2),
from = Number(fromString);
return {
from,
to: toString ? Number(toString) : from
};
}),
replicas
});
} else {
const replicas = replicasMap.get(masterId);
if (!replicas) {
replicasMap.set(masterId, [node]);
} else {
replicas.push(node);
}
}
}
return [...mastersMap.values()];
}
function transformNodeUrl(url: string): RedisClusterNodeTransformedUrl {
const indexOfColon = url.indexOf(':'),
indexOfAt = url.indexOf('@', indexOfColon);
return {
host: url.substring(0, indexOfColon),
port: Number(url.substring(indexOfColon + 1, indexOfAt)),
cport: Number(url.substring(indexOfAt + 1))
};
}

View File

@@ -0,0 +1,27 @@
import { strict as assert } from 'assert';
import { transformArguments } from './CLUSTER_RESET';
describe('CLUSTER RESET', () => {
describe('transformArguments', () => {
it('simple', () => {
assert.deepEqual(
transformArguments(),
['CLUSTER', 'RESET']
);
});
it('HARD', () => {
assert.deepEqual(
transformArguments('HARD'),
['CLUSTER', 'RESET', 'HARD']
);
});
it('SOFT', () => {
assert.deepEqual(
transformArguments('SOFT'),
['CLUSTER', 'RESET', 'SOFT']
);
});
});
});

View File

@@ -0,0 +1,15 @@
import { transformReplyString } from './generic-transformers';
export type ClusterResetModes = 'HARD' | 'SOFT';
export function transformArguments(mode?: ClusterResetModes): Array<string> {
const args = ['CLUSTER', 'RESET'];
if (mode) {
args.push(mode);
}
return args;
}
export const transformReply = transformReplyString;

View File

@@ -0,0 +1,20 @@
import { strict as assert } from 'assert';
import { ClusterSlotStates, transformArguments } 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']
);
});
});
});

View File

@@ -0,0 +1,20 @@
import { transformReplyString } from './generic-transformers';
export enum ClusterSlotStates {
IMPORTING = 'IMPORTING',
MIGRATING = 'MIGRATING',
STABLE = 'STABLE',
NODE = 'NODE'
}
export function transformArguments(slot: number, state: ClusterSlotStates, nodeId?: string): Array<string> {
const args = ['CLUSTER', 'SETSLOT', slot.toString(), state];
if (nodeId) {
args.push(nodeId);
}
return args;
}
export const transformReply = transformReplyString;

View File

@@ -0,0 +1,11 @@
import { strict as assert } from 'assert';
import { transformArguments } from './CONFIG_GET';
describe('CONFIG GET', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments('*'),
['CONFIG', 'GET', '*']
);
});
});

View File

@@ -0,0 +1,7 @@
import { transformReplyTuples } from './generic-transformers';
export function transformArguments(parameter: string): Array<string> {
return ['CONFIG', 'GET', parameter];
}
export const transformReply = transformReplyTuples;

View File

@@ -0,0 +1,11 @@
import { strict as assert } from 'assert';
import { transformArguments } from './CONFIG_RESETSTAT';
describe('CONFIG RESETSTAT', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments(),
['CONFIG', 'RESETSTAT']
);
});
});

View File

@@ -0,0 +1,7 @@
import { transformReplyString } from './generic-transformers';
export function transformArguments(): Array<string> {
return ['CONFIG', 'RESETSTAT'];
}
export const transformReply = transformReplyString;

View File

@@ -0,0 +1,11 @@
import { strict as assert } from 'assert';
import { transformArguments } from './CONFIG_REWRITE';
describe('CONFIG REWRITE', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments(),
['CONFIG', 'REWRITE']
);
});
});

View File

@@ -0,0 +1,7 @@
import { transformReplyString } from './generic-transformers';
export function transformArguments(): Array<string> {
return ['CONFIG', 'REWRITE'];
}
export const transformReply = transformReplyString;

View File

@@ -0,0 +1,11 @@
import { strict as assert } from 'assert';
import { transformArguments } from './CONFIG_SET';
describe('CONFIG SET', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments('parameter', 'value'),
['CONFIG', 'SET', 'parameter', 'value']
);
});
});

View File

@@ -0,0 +1,7 @@
import { transformReplyString } from './generic-transformers';
export function transformArguments(parameter: string, value: string): Array<string> {
return ['CONFIG', 'SET', parameter, value];
}
export const transformReply = transformReplyString;

67
lib/commands/COPY.spec.ts Normal file
View File

@@ -0,0 +1,67 @@
import { strict as assert } from 'assert';
import { TestRedisServers, itWithClient, describeHandleMinimumRedisVersion } from '../test-utils';
import { transformArguments, transformReply } from './COPY';
describe('COPY', () => {
describeHandleMinimumRedisVersion([6, 2]);
describe('transformArguments', () => {
it('simple', () => {
assert.deepEqual(
transformArguments('source', 'destination'),
['COPY', 'source', 'destination']
);
});
it('with destination DB flag', () => {
assert.deepEqual(
transformArguments('source', 'destination', {
destinationDb: 1
}),
['COPY', 'source', 'destination', 'DB', '1']
);
});
it('with replace flag', () => {
assert.deepEqual(
transformArguments('source', 'destination', {
replace: true
}),
['COPY', 'source', 'destination', 'REPLACE']
);
});
it('with both flags', () => {
assert.deepEqual(
transformArguments('source', 'destination', {
destinationDb: 1,
replace: true
}),
['COPY', 'source', 'destination', 'DB', '1', 'REPLACE']
);
});
});
describe('transformReply', () => {
it('0', () => {
assert.equal(
transformReply(0),
false
);
});
it('1', () => {
assert.equal(
transformReply(1),
true
);
});
});
itWithClient(TestRedisServers.OPEN, 'client.copy', async client => {
assert.equal(
await client.copy('source', 'destination'),
false
);
});
});

24
lib/commands/COPY.ts Normal file
View File

@@ -0,0 +1,24 @@
import { transformReplyBoolean } from './generic-transformers';
interface CopyCommandOptions {
destinationDb?: number;
replace?: boolean;
}
export const FIRST_KEY_INDEX = 1;
export function transformArguments(source: string, destination: string, options?: CopyCommandOptions): Array<string> {
const args = ['COPY', source, destination];
if (options?.destinationDb) {
args.push('DB', options.destinationDb.toString());
}
if (options?.replace) {
args.push('REPLACE');
}
return args;
}
export const transformReply = transformReplyBoolean;

View File

@@ -0,0 +1,26 @@
import { strict as assert } from 'assert';
import { TestRedisServers, itWithClient, TestRedisClusters, itWithCluster } from '../test-utils';
import { transformArguments } from './DBSIZE';
describe('DBSIZE', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments(),
['DBSIZE']
);
});
itWithClient(TestRedisServers.OPEN, 'client.dbSize', async client => {
assert.equal(
await client.dbSize(),
0
);
});
itWithCluster(TestRedisClusters.OPEN, 'cluster.dbSize', async cluster => {
assert.equal(
await cluster.dbSize(),
0
);
});
});

9
lib/commands/DBSIZE.ts Normal file
View File

@@ -0,0 +1,9 @@
import { transformReplyNumber } from './generic-transformers';
export const IS_READ_ONLY = true;
export function transformArguments(): Array<string> {
return ['DBSIZE'];
}
export const transformReply = transformReplyNumber;

19
lib/commands/DECR.spec.ts Normal file
View File

@@ -0,0 +1,19 @@
import { strict as assert } from 'assert';
import { TestRedisServers, itWithClient } from '../test-utils';
import { transformArguments } from './DECR';
describe('DECR', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments('key'),
['DECR', 'key']
);
});
itWithClient(TestRedisServers.OPEN, 'client.decr', async client => {
assert.equal(
await client.decr('key'),
-1
);
});
});

9
lib/commands/DECR.ts Normal file
View File

@@ -0,0 +1,9 @@
import { transformReplyNumber } from './generic-transformers';
export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string): Array<string> {
return ['DECR', key];
}
export const transformReply = transformReplyNumber;

View File

@@ -0,0 +1,19 @@
import { strict as assert } from 'assert';
import { TestRedisServers, itWithClient } from '../test-utils';
import { transformArguments } from './DECRBY';
describe('DECRBY', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments('key', 2),
['DECRBY', 'key', '2']
);
});
itWithClient(TestRedisServers.OPEN, 'client.decrBy', async client => {
assert.equal(
await client.decrBy('key', 2),
-2
);
});
});

9
lib/commands/DECRBY.ts Normal file
View File

@@ -0,0 +1,9 @@
import { transformReplyNumber } from './generic-transformers';
export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string, decrement: number): Array<string> {
return ['DECRBY', key, decrement.toString()];
}
export const transformReply = transformReplyNumber;

28
lib/commands/DEL.spec.ts Normal file
View File

@@ -0,0 +1,28 @@
import { strict as assert } from 'assert';
import { TestRedisServers, itWithClient } from '../test-utils';
import { transformArguments } from './DEL';
describe('DEL', () => {
describe('transformArguments', () => {
it('string', () => {
assert.deepEqual(
transformArguments('key'),
['DEL', 'key']
);
});
it('array', () => {
assert.deepEqual(
transformArguments(['key1', 'key2']),
['DEL', 'key1', 'key2']
);
});
});
itWithClient(TestRedisServers.OPEN, 'client.del', async client => {
assert.equal(
await client.del('key'),
0
);
});
});

7
lib/commands/DEL.ts Normal file
View File

@@ -0,0 +1,7 @@
import { pushVerdictArguments, transformReplyNumber } from './generic-transformers';
export function transformArguments(keys: string | Array<string>): Array<string> {
return pushVerdictArguments(['DEL'], keys);
}
export const transformReply = transformReplyNumber;

View File

@@ -0,0 +1,11 @@
import { strict as assert } from 'assert';
import { transformArguments } from './DISCARD';
describe('DISCARD', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments(),
['DISCARD']
);
});
});

7
lib/commands/DISCARD.ts Normal file
View File

@@ -0,0 +1,7 @@
import { transformReplyString } from './generic-transformers';
export function transformArguments(): Array<string> {
return ['DISCARD'];
}
export const transformReply = transformReplyString;

11
lib/commands/DUMP.spec.ts Normal file
View File

@@ -0,0 +1,11 @@
import { strict as assert } from 'assert';
import { TestRedisServers, itWithClient } from '../test-utils';
describe('DUMP', () => {
itWithClient(TestRedisServers.OPEN, 'client.dump', async client => {
assert.equal(
await client.dump('key'),
null
);
});
});

7
lib/commands/DUMP.ts Normal file
View File

@@ -0,0 +1,7 @@
import { transformReplyString } from './generic-transformers';
export function transformArguments(key: string): Array<string> {
return ['DUMP', key];
}
export const transformReply = transformReplyString;

26
lib/commands/ECHO.spec.ts Normal file
View File

@@ -0,0 +1,26 @@
import { strict as assert } from 'assert';
import { TestRedisServers, itWithClient, TestRedisClusters, itWithCluster } from '../test-utils';
import { transformArguments } from './ECHO';
describe('ECHO', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments('message'),
['ECHO', 'message']
);
});
itWithClient(TestRedisServers.OPEN, 'client.echo', async client => {
assert.equal(
await client.echo('message'),
'message'
);
});
itWithCluster(TestRedisClusters.OPEN, 'cluster.echo', async cluster => {
assert.equal(
await cluster.echo('message'),
'message'
);
});
});

9
lib/commands/ECHO.ts Normal file
View File

@@ -0,0 +1,9 @@
import { transformReplyString } from './generic-transformers';
export const IS_READ_ONLY = true;
export function transformArguments(message: string): Array<string> {
return ['ECHO', message];
}
export const transformReply = transformReplyString;

29
lib/commands/EVAL.spec.ts Normal file
View File

@@ -0,0 +1,29 @@
import { strict as assert } from 'assert';
import { TestRedisServers, itWithClient, TestRedisClusters, itWithCluster } from '../test-utils';
import { transformArguments } from './EVAL';
describe('EVAL', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments('return KEYS[1] + ARGV[1]', {
keys: ['key'],
arguments: ['argument']
}),
['EVAL', 'return KEYS[1] + ARGV[1]', '1', 'key', 'argument']
);
});
itWithClient(TestRedisServers.OPEN, 'client.eval', async client => {
assert.equal(
await client.eval('return 1'),
1
);
});
itWithCluster(TestRedisClusters.OPEN, 'cluster.eval', async cluster => {
assert.equal(
await cluster.eval('return 1'),
1
);
});
});

9
lib/commands/EVAL.ts Normal file
View File

@@ -0,0 +1,9 @@
import { EvalOptions, pushEvalArguments } from './generic-transformers';
export function transformArguments(script: string, options?: EvalOptions): Array<string> {
return pushEvalArguments(['EVAL', script], options);
}
export function transformReply(reply: unknown): unknown {
return reply;
}

Some files were not shown because too many files have changed in this diff Show More