1
0
mirror of https://github.com/redis/node-redis.git synced 2025-08-04 15:02:09 +03:00

v4.0.0-rc.4 (#1723)

* update workflows & README

* add .deepsource.toml

* fix client.quit, add error events on cluster, fix some "deepsource.io" warnings

* Release 4.0.0-rc.1

* add cluster.duplicate, add some tests

* fix #1650 - add support for Buffer in some commands, add GET_BUFFER command

* fix GET and GET_BUFFER return type

* update FAQ

* Update invalid code example in README.md (#1654)

* Update invalid code example in README.md

* Update README.md

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

* fix #1652

* ref #1653 - better types

* better types

* fix 54124793ad

* Update GEOSEARCHSTORE.spec.ts

* fix #1660 - add support for client.HSET('key', 'field', 'value')

* upgrade dependencies, update README

* fix #1659 - add support for db-number in client options url

* fix README, remove unused import, downgrade typedoc & typedoc-plugin-markdown

* update client-configurations.md

* fix README

* add CLUSTER_SLOTS, add some tests

* fix "createClient with url" test with redis 5

* remove unused imports

* Release 4.0.0-rc.2

* add missing semicolon

* replace empty "transformReply" functions with typescript "declare"

* fix EVAL & EVALSHA, add some tests, npm update

* fix #1665 - add ZRANGEBYLEX, ZRANGEBYSCORE, ZRANGEBYSCORE_WITHSCORES

* new issue templates

* add all COMMAND commands

* run COMMAND & COMMAND INFO tests only on redis >6

* Create SECURITY.md

* fix #1671 - add support for all client configurations in cluster

* ref #1671 - add support for defaults

* remove some commands from cluster, npm update, clean code,

* lock benny version

* fix #1674 - remove `isolationPoolOptions` when creating isolated connection

* increase test coverage

* update .npmignore

* Release 4.0.0-rc.3

* fix README

* remove whitespace from LICENSE

* use "export { x as y }" instead of import & const

* move from "NodeRedis" to "Redis"

* fix #1676

* update comments

* Auth before select database (#1679)

* Auth before select database

* fix #1681

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

* Adds connect-as-acl-user example. (#1684)

* Adds connect-as-acl-user example.

* Adds blank line at end.

* Set to private.

* Adds examples folder to npmignore.

* Adds Apple .DS_Store file to .gitignore (#1685)

* Adds Apple .DS_Store file.

* Add .DS_Store to .npmignore too

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

* move examples

* clean some tests

* clean code

* Adds examples table of contents and contribution guidelines. (#1686)

* Updated examples to use named functions. (#1687)

* Updated examples to user named functions.

* Update README.md

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

* update docs, add 6.0.x to the tests matrix, add eslint, npm update, fix some commands, fix some types

Co-authored-by: Simon Prickett <simon@crudworks.org>

* fix tests with redis 6.0.x

* fix ACL GETUSER test

* fix client.quit and client.disconnect

* fix ACL GETUSER

* Adds TypeScript note and corrects a typo.

* Fixes a bug in the Scan Iterator section. (#1694)

* Made examples use local version.

* Add `lua-multi-incr.js` example (#1692)

Also fix syntax error in the lua example in the README

Closes #1689.

* Add(examples): Create an example for blPop & lPush (#1696)

* Add(examples): Create an example for blPop & lPush

Signed-off-by: Aditya Rastogi <adit.rastogi2014@gmail.com>

* Update(examples): fix case, add timeout, update readme

Signed-off-by: Aditya Rastogi <adit.rastogi2014@gmail.com>

Closes #1693.

* Add command-with-modifiers.js example (#1695)

* Adds TypeScript note and corrects a typo.

* Adds command-with-modifiers example. (redis#1688)

* Adds command-with-modifiers example. (redis#1688)

* Adds command-with-modifiers example. (redis#1688)

* Removed callbacks.

Co-authored-by: Simon Prickett <simon@redislabs.com>

Closes #1688.

* Issue # 1697 FIX - creates an example script that shows how to use the SSCAN iterator (#1699)

* #1697 fix for set scan example

* adds the js file

* adds comment

* Minor layout and comment adjustment.

Co-authored-by: srawat2 <shashank19aug>
Co-authored-by: Simon Prickett <simon@redislabs.com>

Closes #1697.

* fix #1706 - HSET return type should be number

* use dockers for tests, fix some bugs

* increase dockers timeout to 30s

* release drafter (#1683)

* release drafter

* fixing contributors

* use dockers for tests, use npm workspaces, add rejson & redisearch modules, fix some bugs

* fix #1712 - fix LINDEX return type

* uncomment TIME tests

* use codecov

* fix tests.yml

* uncomment "should handle live resharding" test

* fix #1714 - update README(s)

* add package-lock.json

* update CONTRIBUTING.md

* update examples

* uncomment some tests

* fix test-utils

* move "all-in-one" to root folder

* fix tests workflow

* fix bug in cluster slots, enhance live resharding test

* fix live resharding test

* fix #1707 - handle number arguments in legacy mode

* Add rejectedUnauthorized and other TLS options (#1708)

* Update socket.ts

* fix #1716 - decode username and password from url

* fix some Z (sorted list) commands, increase commands test coverage

* remove empty lines

* fix 'Scenario' typo (#1720)

* update readmes, add createCluster to the `redis` package

* add .release-it.json files, update some md files

* run tests on pull requests too

* Support esModuleInterop set to false. (#1717)

* Support esModuleInterop set to false.

When testing the upcoming 4.x release, we got a bunch of typescript
errors emitted from this project.

We quickly realized this is because the library uses the esModuleInterop
flag. This makes some imports _slightly_ easier to write, but it comes
at a cost: it forces any application or library using this library to
*also* have esModuleInterop on.

The `esModuleInterop` flag is a bit of a holdover from an earlier time,
and I would not recommend using it in libraries. The main issue is that
if it's set to true, you are forcing any users of the library to also
have `esModuleInterop`, where if you keep have it set to `false` (the
default), you leave the decision to the user.

This change should have no rammifications to users with
`esModuleInterop` on, but it will enable support for those that have it
off.

This is especially good for library authors such as myself, because I
would also like to keep this flag off to not force *my* users into this
feature.

* All tests now pass!

* Move @types/redis-parser into client sub-package

and removed a comma

* npm update, remove html from readme

* add tests and licence badges

* update changelog.md

* update .npmignore and .release-it.json

* update .release-it.json

* Release client@1.0.0-rc.0

* revert d32f1edf8a

* fix .npmignore

* replace @redis with @node-redis

* Release client@1.0.0-rc.0

* update json & search version

* Release json@1.0.0-rc.0

* Release search@1.0.0-rc.0

* update dependencies

* Release redis@4.0.0-rc.4

Co-authored-by: Richard Samuelsson <noobtoothfairy@gmail.com>
Co-authored-by: mustard <mhqnwt@gmail.com>
Co-authored-by: Simon Prickett <simon@redislabs.com>
Co-authored-by: Simon Prickett <simon@crudworks.org>
Co-authored-by: Suze Shardlow <SuzeShardlow@users.noreply.github.com>
Co-authored-by: Joshua T <buildingsomethingfun@gmail.com>
Co-authored-by: Aditya Rastogi <adit.rastogi2014@gmail.com>
Co-authored-by: Rohan Kumar <rohan.kr20@gmail.com>
Co-authored-by: Kalki <shashank.kviit@gmail.com>
Co-authored-by: Chayim <chayim@users.noreply.github.com>
Co-authored-by: Da-Jin Chu <dajinchu@gmail.com>
Co-authored-by: Henrique Corrêa <75134774+HeCorr@users.noreply.github.com>
Co-authored-by: Evert Pot <me@evertpot.com>
This commit is contained in:
Leibale Eidelman
2021-11-16 02:48:20 -05:00
committed by GitHub
parent 199285aa71
commit eed479778f
705 changed files with 9959 additions and 4349 deletions

View File

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

View File

@@ -0,0 +1,11 @@
export function transformArguments(categoryName?: string): Array<string> {
const args = ['ACL', 'CAT'];
if (categoryName) {
args.push(categoryName);
}
return args;
}
export declare function transformReply(): Array<string>;

View File

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

View File

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

View File

@@ -0,0 +1,23 @@
import { strict as assert } from 'assert';
import testUtils from '../test-utils';
import { transformArguments } from './ACL_GENPASS';
describe('ACL GENPASS', () => {
testUtils.isVersionGreaterThanHook([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,11 @@
export function transformArguments(bits?: number): Array<string> {
const args = ['ACL', 'GENPASS'];
if (bits) {
args.push(bits.toString());
}
return args;
}
export declare function transformReply(): string;

View File

@@ -0,0 +1,32 @@
import { strict as assert } from 'assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './ACL_GETUSER';
describe('ACL GETUSER', () => {
testUtils.isVersionGreaterThanHook([6]);
it('transformArguments', () => {
assert.deepEqual(
transformArguments('username'),
['ACL', 'GETUSER', 'username']
);
});
testUtils.testWithClient('client.aclGetUser', async client => {
assert.deepEqual(
await client.aclGetUser('default'),
{
passwords: [],
commands: '+@all',
keys: ['*'],
...(testUtils.isVersionGreaterThan([6, 2]) ? {
flags: ['on', 'allkeys', 'allchannels', 'allcommands', 'nopass'],
channels: ['*']
} : {
flags: ['on', 'allkeys', 'allcommands', 'nopass'],
channels: undefined
})
}
);
}, GLOBAL.SERVERS.OPEN);
});

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 testUtils from '../test-utils';
import { transformArguments } from './ACL_LIST';
describe('ACL LIST', () => {
testUtils.isVersionGreaterThanHook([6]);
it('transformArguments', () => {
assert.deepEqual(
transformArguments(),
['ACL', 'LIST']
);
});
});

View File

@@ -0,0 +1,5 @@
export function transformArguments(): Array<string> {
return ['ACL', 'LIST'];
}
export declare function transformReply(): Array<string>;

View File

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

View File

@@ -0,0 +1,5 @@
export function transformArguments(): Array<string> {
return ['ACL', 'LOAD'];
}
export declare function transformReply(): string;

View File

@@ -0,0 +1,53 @@
import { strict as assert } from 'assert';
import testUtils from '../test-utils';
import { transformArguments, transformReply } from './ACL_LOG';
describe('ACL LOG', () => {
testUtils.isVersionGreaterThanHook([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'
}]
);
});
});

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 testUtils from '../test-utils';
import { transformArguments } from './ACL_LOG_RESET';
describe('ACL LOG RESET', () => {
testUtils.isVersionGreaterThanHook([6]);
it('transformArguments', () => {
assert.deepEqual(
transformArguments(),
['ACL', 'LOG', 'RESET']
);
});
});

View File

@@ -0,0 +1,5 @@
export function transformArguments(): Array<string> {
return ['ACL', 'LOG', 'RESET'];
}
export declare function transformReply(): string;

View File

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

View File

@@ -0,0 +1,5 @@
export function transformArguments(): Array<string> {
return ['ACL', 'SAVE'];
}
export declare function transformReply(): string;

View File

@@ -0,0 +1,23 @@
import { strict as assert } from 'assert';
import testUtils from '../test-utils';
import { transformArguments } from './ACL_SETUSER';
describe('ACL SETUSER', () => {
testUtils.isVersionGreaterThanHook([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,8 @@
import { RedisCommandArguments } from '.';
import { pushVerdictArguments } from './generic-transformers';
export function transformArguments(username: string, rule: string | Array<string>): RedisCommandArguments {
return pushVerdictArguments(['ACL', 'SETUSER', username], rule);
}
export declare function transformReply(): string;

View File

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

View File

@@ -0,0 +1,5 @@
export function transformArguments(): Array<string> {
return ['ACL', 'USERS'];
}
export declare function transformReply(): Array<string>;

View File

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

View File

@@ -0,0 +1,5 @@
export function transformArguments(): Array<string> {
return ['ACL', 'WHOAMI'];
}
export declare function transformReply(): string;

View File

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

View File

@@ -0,0 +1,7 @@
export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string, value: string): Array<string> {
return ['APPEND', key, value];
}
export declare function transformReply(): string;

View File

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

View File

@@ -0,0 +1,5 @@
export function transformArguments(): Array<string> {
return ['ASKING'];
}
export declare function transformReply(): string;

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']
);
});
});
});

View File

@@ -0,0 +1,14 @@
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 declare function transformReply(): string;

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,5 @@
export function transformArguments(): Array<string> {
return ['BGREWRITEAOF'];
}
export declare function transformReply(): string;

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']
);
});
});
});

View File

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

View File

@@ -0,0 +1,31 @@
import { strict as assert } from 'assert';
import testUtils, { GLOBAL } 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']
);
});
});
testUtils.testWithClient('client.bitCount', async client => {
assert.equal(
await client.bitCount('key'),
0
);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -0,0 +1,23 @@
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 declare function transformReply(): number;

View File

@@ -0,0 +1,42 @@
import { strict as assert } from 'assert';
import testUtils, { GLOBAL } 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']
);
});
testUtils.testWithClient('client.bitField', async client => {
assert.deepEqual(
await client.bitField('key', []),
[]
);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -0,0 +1,82 @@
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 declare function transformReply(): Array<number | null>;

View File

@@ -0,0 +1,35 @@
import { strict as assert } from 'assert';
import testUtils, { GLOBAL } 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']
);
});
});
testUtils.testWithClient('client.bitOp', async client => {
assert.equal(
await client.bitOp('AND', 'destKey', 'key'),
0
);
}, GLOBAL.SERVERS.OPEN);
testUtils.testWithCluster('cluster.bitOp', async cluster => {
assert.equal(
await cluster.bitOp('AND', '{tag}destKey', '{tag}key'),
0
);
}, GLOBAL.CLUSTERS.OPEN);
});

View File

@@ -0,0 +1,12 @@
import { RedisCommandArguments } from '.';
import { pushVerdictArguments } 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>): RedisCommandArguments {
return pushVerdictArguments(['BITOP', operation, destKey], key);
}
export declare function transformReply(): number;

View File

@@ -0,0 +1,42 @@
import { strict as assert } from 'assert';
import testUtils, { GLOBAL } 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']
);
});
});
testUtils.testWithClient('client.bitPos', async client => {
assert.equal(
await client.bitPos('key', 1, 1),
-1
);
}, GLOBAL.SERVERS.OPEN);
testUtils.testWithCluster('cluster.bitPos', async cluster => {
assert.equal(
await cluster.bitPos('key', 1, 1),
-1
);
}, GLOBAL.CLUSTERS.OPEN);
});

View File

@@ -0,0 +1,21 @@
import { BitValue } 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 declare function transformReply(): number;

View File

@@ -0,0 +1,43 @@
import { strict as assert } from 'assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './BLMOVE';
import { commandOptions } from '../../index';
describe('BLMOVE', () => {
testUtils.isVersionGreaterThanHook([6, 2]);
it('transformArguments', () => {
assert.deepEqual(
transformArguments('source', 'destination', 'LEFT', 'RIGHT', 0),
['BLMOVE', 'source', 'destination', 'LEFT', 'RIGHT', '0']
);
});
testUtils.testWithClient('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'
);
}, GLOBAL.SERVERS.OPEN);
testUtils.testWithCluster('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'
);
}, GLOBAL.CLUSTERS.OPEN);
});

View File

@@ -0,0 +1,22 @@
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 declare function transformReply(): string | null;

View File

@@ -0,0 +1,79 @@
import { strict as assert } from 'assert';
import testUtils, { GLOBAL } 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'
}
);
});
});
testUtils.testWithClient('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'
}
);
}, GLOBAL.SERVERS.OPEN);
testUtils.testWithCluster('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'
}
);
}, GLOBAL.CLUSTERS.OPEN);
});

View File

@@ -0,0 +1,26 @@
import { RedisCommandArguments } from '.';
import { pushVerdictArguments } from './generic-transformers';
export const FIRST_KEY_INDEX = 1;
export function transformArguments(keys: string | Buffer | Array<string | Buffer>, timeout: number): RedisCommandArguments {
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 testUtils, { GLOBAL } 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'
}
);
});
});
testUtils.testWithClient('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'
}
);
}, GLOBAL.SERVERS.OPEN);
testUtils.testWithCluster('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'
}
);
}, GLOBAL.CLUSTERS.OPEN);
});

View File

@@ -0,0 +1,26 @@
import { RedisCommandArguments } from '.';
import { pushVerdictArguments } from './generic-transformers';
export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string | Array<string>, timeout: number): RedisCommandArguments {
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 testUtils, { GLOBAL } 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']
);
});
testUtils.testWithClient('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'
);
}, GLOBAL.SERVERS.OPEN);
testUtils.testWithCluster('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'
);
}, GLOBAL.CLUSTERS.OPEN);
});

View File

@@ -0,0 +1,7 @@
export const FIRST_KEY_INDEX = 1;
export function transformArguments(source: string, destination: string, timeout: number): Array<string> {
return ['BRPOPLPUSH', source, destination, timeout.toString()];
}
export declare function transformReply(): number | null;

View File

@@ -0,0 +1,65 @@
import { strict as assert } from 'assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments, transformReply } from './BZPOPMAX';
import { commandOptions } from '../../index';
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
}
);
});
});
testUtils.testWithClient('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
}
);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -0,0 +1,28 @@
import { RedisCommandArguments } from '.';
import { pushVerdictArguments, transformReplyNumberInfinity, ZMember } from './generic-transformers';
export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string | Array<string>, timeout: number): RedisCommandArguments {
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 testUtils, { GLOBAL } 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
}
);
});
});
testUtils.testWithClient('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
}
);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -0,0 +1,28 @@
import { RedisCommandArguments } from '.';
import { pushVerdictArguments, transformReplyNumberInfinity, ZMember } from './generic-transformers';
export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string | Array<string>, timeout: number): RedisCommandArguments {
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 testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './CLIENT_ID';
describe('CLIENT ID', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments(),
['CLIENT', 'ID']
);
});
testUtils.testWithClient('client.clientId', async client => {
assert.equal(
typeof (await client.clientId()),
'number'
);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -0,0 +1,7 @@
export const IS_READ_ONLY = true;
export function transformArguments(): Array<string> {
return ['CLIENT', 'ID'];
}
export declare function transformReply(): number;

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,13 @@
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 declare function transformReply(): string;

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,5 @@
export function transformArguments(): Array<string> {
return ['CLUSTER', 'FLUSHSLOTS'];
}
export declare function transformReply(): string;

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,5 @@
export function transformArguments(slot: number, count: number): Array<string> {
return ['CLUSTER', 'GETKEYSINSLOT', slot.toString(), count.toString()];
}
export declare function transformReply(): string;

View File

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

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,5 @@
export function transformArguments(ip: string, port: number): Array<string> {
return ['CLUSTER', 'MEET', ip, port.toString()];
}
export declare function transformReply(): string;

View File

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

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,13 @@
export type ClusterResetModes = 'HARD' | 'SOFT';
export function transformArguments(mode?: ClusterResetModes): Array<string> {
const args = ['CLUSTER', 'RESET'];
if (mode) {
args.push(mode);
}
return args;
}
export declare function transformReply(): string;

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,18 @@
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 declare function transformReply(): string;

View File

@@ -0,0 +1,76 @@
import { strict as assert } from 'assert';
import { transformArguments, transformReply } from './CLUSTER_SLOTS';
describe('CLUSTER SLOTS', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments(),
['CLUSTER', 'SLOTS']
);
});
it('transformReply', () => {
assert.deepEqual(
transformReply([
[
0,
5460,
['127.0.0.1', 30001, '09dbe9720cda62f7865eabc5fd8857c5d2678366'],
['127.0.0.1', 30004, '821d8ca00d7ccf931ed3ffc7e3db0599d2271abf']
],
[
5461,
10922,
['127.0.0.1', 30002, 'c9d93d9f2c0c524ff34cc11838c2003d8c29e013'],
['127.0.0.1', 30005, 'faadb3eb99009de4ab72ad6b6ed87634c7ee410f']
],
[
10923,
16383,
['127.0.0.1', 30003, '044ec91f325b7595e76dbcb18cc688b6a5b434a1'],
['127.0.0.1', 30006, '58e6e48d41228013e5d9c1c37c5060693925e97e']
]
]),
[{
from: 0,
to: 5460,
master: {
ip: '127.0.0.1',
port: 30001,
id: '09dbe9720cda62f7865eabc5fd8857c5d2678366'
},
replicas: [{
ip: '127.0.0.1',
port: 30004,
id: '821d8ca00d7ccf931ed3ffc7e3db0599d2271abf'
}]
}, {
from: 5461,
to: 10922,
master: {
ip: '127.0.0.1',
port: 30002,
id: 'c9d93d9f2c0c524ff34cc11838c2003d8c29e013'
},
replicas: [{
ip: '127.0.0.1',
port: 30005,
id: 'faadb3eb99009de4ab72ad6b6ed87634c7ee410f'
}]
}, {
from: 10923,
to: 16383,
master: {
ip: '127.0.0.1',
port: 30003,
id: '044ec91f325b7595e76dbcb18cc688b6a5b434a1'
},
replicas: [{
ip: '127.0.0.1',
port: 30006,
id: '58e6e48d41228013e5d9c1c37c5060693925e97e'
}]
}]
);
});
});

View File

@@ -0,0 +1,41 @@
import { RedisCommandArguments } from '.';
export function transformArguments(): RedisCommandArguments {
return ['CLUSTER', 'SLOTS'];
}
type ClusterSlotsRawNode = [ip: string, port: number, id: string];
type ClusterSlotsRawReply = Array<[from: number, to: number, master: ClusterSlotsRawNode, ...replicas: Array<ClusterSlotsRawNode>]>;
type ClusterSlotsNode = {
ip: string;
port: number;
id: string;
};
export type ClusterSlotsReply = Array<{
from: number;
to: number;
master: ClusterSlotsNode;
replicas: Array<ClusterSlotsNode>;
}>;
export function transformReply(reply: ClusterSlotsRawReply): ClusterSlotsReply {
return reply.map(([from, to, master, ...replicas]) => {
return {
from,
to,
master: transformNode(master),
replicas: replicas.map(transformNode)
};
});
}
function transformNode([ip, port, id]: ClusterSlotsRawNode): ClusterSlotsNode {
return {
ip,
port,
id
};
}

View File

@@ -0,0 +1,17 @@
import { strict as assert } from 'assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './COMMAND';
import { assertPingCommand } from './COMMAND_INFO.spec';
describe('COMMAND', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments(),
['COMMAND']
);
});
testUtils.testWithClient('client.command', async client => {
assertPingCommand((await client.command()).find(command => command.name === 'ping'));
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -0,0 +1,12 @@
import { RedisCommandArguments } from '.';
import { CommandRawReply, CommandReply, transformCommandReply } from './generic-transformers';
export const IS_READ_ONLY = true;
export function transformArguments(): RedisCommandArguments {
return ['COMMAND'];
}
export function transformReply(reply: Array<CommandRawReply>): Array<CommandReply> {
return reply.map(transformCommandReply);
}

View File

@@ -0,0 +1,19 @@
import { strict as assert } from 'assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './COMMAND_COUNT';
describe('COMMAND COUNT', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments(),
['COMMAND', 'COUNT']
);
});
testUtils.testWithClient('client.commandCount', async client => {
assert.equal(
typeof await client.commandCount(),
'number'
);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -0,0 +1,9 @@
import { RedisCommandArguments } from '.';
export const IS_READ_ONLY = true;
export function transformArguments(): RedisCommandArguments {
return ['COMMAND', 'COUNT'];
}
export declare function transformReply(): number;

View File

@@ -0,0 +1,19 @@
import { strict as assert } from 'assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './COMMAND_GETKEYS';
describe('COMMAND GETKEYS', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments(['GET', 'key']),
['COMMAND', 'GETKEYS', 'GET', 'key']
);
});
testUtils.testWithClient('client.commandGetKeys', async client => {
assert.deepEqual(
await client.commandGetKeys(['GET', 'key']),
['key']
);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -0,0 +1,9 @@
import { RedisCommandArguments } from '.';
export const IS_READ_ONLY = true;
export function transformArguments(args: Array<string>): RedisCommandArguments {
return ['COMMAND', 'GETKEYS', ...args];
}
export declare function transformReply(): Array<string>;

View File

@@ -0,0 +1,45 @@
import { strict as assert } from 'assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './COMMAND_INFO';
import { CommandCategories, CommandFlags, CommandReply } from './generic-transformers';
export function assertPingCommand(commandInfo: CommandReply | null | undefined): void {
assert.deepEqual(
commandInfo,
{
name: 'ping',
arity: -1,
flags: new Set([CommandFlags.STALE, CommandFlags.FAST]),
firstKeyIndex: 0,
lastKeyIndex: 0,
step: 0,
categories: new Set(
testUtils.isVersionGreaterThan([6]) ?
[CommandCategories.FAST, CommandCategories.CONNECTION] :
[]
)
}
);
}
describe('COMMAND INFO', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments(['PING']),
['COMMAND', 'INFO', 'PING']
);
});
describe('client.commandInfo', () => {
testUtils.testWithClient('PING', async client => {
assertPingCommand((await client.commandInfo(['PING']))[0]);
}, GLOBAL.SERVERS.OPEN);
testUtils.testWithClient('DOSE_NOT_EXISTS', async client => {
assert.deepEqual(
await client.commandInfo(['DOSE_NOT_EXISTS']),
[null]
);
}, GLOBAL.SERVERS.OPEN);
});
});

View File

@@ -0,0 +1,12 @@
import { RedisCommandArguments } from '.';
import { CommandRawReply, CommandReply, transformCommandReply } from './generic-transformers';
export const IS_READ_ONLY = true;
export function transformArguments(commands: Array<string>): RedisCommandArguments {
return ['COMMAND', 'INFO', ...commands];
}
export function transformReply(reply: Array<CommandRawReply | null>): Array<CommandReply | null> {
return reply.map(command => command ? transformCommandReply(command) : null);
}

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,5 @@
export function transformArguments(parameter: string): Array<string> {
return ['CONFIG', 'GET', parameter];
}
export { transformReplyTuples as transformReply } from './generic-transformers';

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,5 @@
export function transformArguments(): Array<string> {
return ['CONFIG', 'RESETSTAT'];
}
export declare function transformReply(): string;

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,5 @@
export function transformArguments(): Array<string> {
return ['CONFIG', 'REWRITE'];
}
export declare function transformReply(): string;

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,5 @@
export function transformArguments(parameter: string, value: string): Array<string> {
return ['CONFIG', 'SET', parameter, value];
}
export declare function transformReply(): string;

View File

@@ -0,0 +1,67 @@
import { strict as assert } from 'assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments, transformReply } from './COPY';
describe('COPY', () => {
testUtils.isVersionGreaterThanHook([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
);
});
});
testUtils.testWithClient('client.copy', async client => {
assert.equal(
await client.copy('source', 'destination'),
false
);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -0,0 +1,22 @@
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 { transformReplyBoolean as transformReply } from './generic-transformers';

View File

@@ -0,0 +1,19 @@
import { strict as assert } from 'assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './DBSIZE';
describe('DBSIZE', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments(),
['DBSIZE']
);
});
testUtils.testWithClient('client.dbSize', async client => {
assert.equal(
await client.dbSize(),
0
);
}, GLOBAL.SERVERS.OPEN);
});

View File

@@ -0,0 +1,7 @@
export const IS_READ_ONLY = true;
export function transformArguments(): Array<string> {
return ['DBSIZE'];
}
export declare function transformReply(): number;

View File

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

View File

@@ -0,0 +1,7 @@
export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string): Array<string> {
return ['DECR', key];
}
export declare function transformReply(): number;

View File

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

View File

@@ -0,0 +1,7 @@
export const FIRST_KEY_INDEX = 1;
export function transformArguments(key: string, decrement: number): Array<string> {
return ['DECRBY', key, decrement.toString()];
}
export declare function transformReply(): number;

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