1
0
mirror of https://github.com/redis/node-redis.git synced 2025-08-09 00:22:08 +03:00
* 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

* fix #1724 - fix LINDEX signature

* add positive test for LINDEX

* fix #1718 - add support for buffers in pubsub

* Fixed a few typos.

* fix ARRPOP

* fix #1726

* enhance cluster reshard handling

* Adds RediSearch demo.

* Adds intro sentence.

* Made top level comment more descriptive.

* Adds RedisJSON example.

* Renamed JSON search example.

* Some refactoring.

* Fixed search example for JSON.

* Minor wording updates.

* Added missing pet name.

* Adds JSON package overview.

* Fixed typo.

* Search package README initial version.

* remove echo from docker entrypoint.sh

* npm update

* update docs

* fix merge

* fix merge

* Release client@1.0.0

* npm update

* Release search@1.0.0

* update sub modules

* Release redis@4.0.0

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-24 21:59:48 -05:00
committed by GitHub
parent eed479778f
commit 3790c6eb4c
34 changed files with 1435 additions and 835 deletions

298
.github/README.md vendored
View File

@@ -1,298 +0,0 @@
# Node-Redis
[![Tests](https://img.shields.io/github/workflow/status/redis/node-redis/Tests/master.svg?label=tests)](https://codecov.io/gh/redis/node-redis)
[![Coverage](https://codecov.io/gh/redis/node-redis/branch/master/graph/badge.svg?token=xcfqHhJC37)](https://codecov.io/gh/redis/node-redis)
[![License](https://img.shields.io/github/license/redis/node-redis.svg)](https://codecov.io/gh/redis/node-redis)
[![Chat](https://img.shields.io/discord/697882427875393627.svg)](https://discord.gg/XMMVgxUm)
## Installation
```bash
npm install redis@next
```
> :warning: The new interface is clean and cool, but if you have an existing code base, you'll want to read the [migration guide](../docs/v3-to-v4.md).
## Usage
### Basic Example
```typescript
import { createClient } from 'redis';
(async () => {
const client = createClient();
client.on('error', (err) => console.log('Redis Client Error', err));
await client.connect();
await client.set('key', 'value');
const value = await client.get('key');
})();
```
The above code connects to localhost on port 6379. To connect to a different host or port, use a connection string in the format `redis[s]://[[username][:password]@][host][:port][/db-number]`:
```typescript
createClient({
url: 'redis://alice:foobared@awesome.redis.server:6380'
});
```
You can also use discrete parameters, UNIX sockets, and even TLS to connect. Details can be found in the [client configuration guide](../docs/client-configuration.md).
### Redis Commands
There is built-in support for all of the [out-of-the-box Redis commands](https://redis.io/commands). They are exposed using the raw Redis command names (`HSET`, `HGETALL`, etc.) and a friendlier camel-cased version (`hSet`, `hGetAll`, etc.):
```typescript
// raw Redis commands
await client.HSET('key', 'field', 'value');
await client.HGETALL('key');
// friendly JavaScript commands
await client.hSet('key', 'field', 'value');
await client.hGetAll('key');
```
Modifiers to commands are specified using a JavaScript object:
```typescript
await client.set('key', 'value', {
EX: 10,
NX: true
});
```
Replies will be transformed into useful data structures:
```typescript
await client.hGetAll('key'); // { field1: 'value1', field2: 'value2' }
await client.hVals('key'); // ['value1', 'value2']
```
### Unsupported Redis Commands
If you want to run commands and/or use arguments that Node Redis doesn't know about (yet!) use `.sendCommand()`:
```typescript
await client.sendCommand(['SET', 'key', 'value', 'NX']); // 'OK'
await client.sendCommand(['HGETALL', 'key']); // ['key1', 'field1', 'key2', 'field2']
```
### Transactions (Multi/Exec)
Start a [transaction](https://redis.io/topics/transactions) by calling `.multi()`, then chaining your commands. When you're done, call `.exec()` and you'll get an array back with your results:
```typescript
await client.set('another-key', 'another-value');
const [setKeyReply, otherKeyValue] = await client
.multi()
.set('key', 'value')
.get('another-key')
.exec(); // ['OK', 'another-value']
```
You can also [watch](https://redis.io/topics/transactions#optimistic-locking-using-check-and-set) keys by calling `.watch()`. Your transaction will abort if any of the watched keys change.
To dig deeper into transactions, check out the [Isolated Execution Guide](../docs/isolated-execution.md).
### Blocking Commands
Any command can be run on a new connection by specifying the `isolated` option. The newly created connection is closed when the command's `Promise` is fulfilled.
This pattern works especially well for blocking commands—such as `BLPOP` and `BLMOVE`:
```typescript
import { commandOptions } from 'redis';
const blPopPromise = client.blPop(commandOptions({ isolated: true }), 'key', 0);
await client.lPush('key', ['1', '2']);
await blPopPromise; // '2'
```
To learn more about isolated execution, check out the [guide](../docs/isolated-execution.md).
### Pub/Sub
Subscribing to a channel requires a dedicated stand-alone connection. You can easily get one by `.duplicate()`ing an existing Redis connection.
```typescript
const subscriber = client.duplicate();
await subscriber.connect();
```
Once you have one, simply subscribe and unsubscribe as needed:
```typescript
await subscriber.subscribe('channel', (message) => {
console.log(message); // 'message'
});
await subscriber.pSubscribe('channe*', (message, channel) => {
console.log(message, channel); // 'message', 'channel'
});
await subscriber.unsubscribe('channel');
await subscriber.pUnsubscribe('channe*');
```
Publish a message on a channel:
```typescript
await publisher.publish('channel', 'message');
```
### Scan Iterator
[`SCAN`](https://redis.io/commands/scan) results can be looped over using [async iterators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/asyncIterator):
```typescript
for await (const key of client.scanIterator()) {
// use the key!
await client.get(key);
}
```
This works with `HSCAN`, `SSCAN`, and `ZSCAN` too:
```typescript
for await (const { field, value } of client.hScanIterator('hash')) {}
for await (const member of client.sScanIterator('set')) {}
for await (const { score, member } of client.zScanIterator('sorted-set')) {}
```
You can override the default options by providing a configuration object:
```typescript
client.scanIterator({
TYPE: 'string', // `SCAN` only
MATCH: 'patter*',
COUNT: 100
});
```
### Lua Scripts
Define new functions using [Lua scripts](https://redis.io/commands/eval) which execute on the Redis server:
```typescript
import { createClient, defineScript } from 'redis';
(async () => {
const client = createClient({
scripts: {
add: defineScript({
NUMBER_OF_KEYS: 1,
SCRIPT:
'local val = redis.pcall("GET", KEYS[1]);' +
'return val + ARGV[1];',
transformArguments(key: string, toAdd: number): Array<string> {
return [key, toAdd.toString()];
},
transformReply(reply: number): number {
return reply;
}
})
}
});
await client.connect();
await client.set('key', '1');
await client.add('key', 2); // 3
})();
```
### Disconnecting
There are two functions that disconnect a client from the Redis server. In most scenarios you should use `.quit()` to ensure that pending commands are sent to Redis before closing a connection.
#### `.QUIT()`/`.quit()`
Gracefully close a client's connection to Redis, by sending the [`QUIT`](https://redis.io/commands/quit) command to the server. Before quitting, the client executes any remaining commands in its queue, and will receive replies from Redis for each of them.
```typescript
const [ping, get, quit] = await Promise.all([
client.ping(),
client.get('key'),
client.quit()
]); // ['PONG', null, 'OK']
try {
await client.get('key');
} catch (err) {
// ClosedClient Error
}
```
#### `.disconnect()`
Forcibly close a client's connection to Redis immediately. Calling `disconnect` will not send further pending commands to the Redis server, or wait for or parse outstanding responses.
```typescript
await client.disconnect();
```
### Auto-Pipelining
Node Redis will automatically pipeline requests that are made during the same "tick".
```typescript
client.set('Tm9kZSBSZWRpcw==', 'users:1');
client.sAdd('users:1:tokens', 'Tm9kZSBSZWRpcw==');
```
Of course, if you don't do something with your Promises you're certain to get [unhandled Promise exceptions](https://nodejs.org/api/process.html#process_event_unhandledrejection). To take advantage of auto-pipelining and handle your Promises, use `Promise.all()`.
```typescript
await Promise.all([
client.set('Tm9kZSBSZWRpcw==', 'users:1'),
client.sAdd('users:1:tokens', 'Tm9kZSBSZWRpcw==')
]);
```
### Clustering
Check out the [Clustering Guide](../docs/clustering.md) when using Node Redis to connect to a Redis Cluster.
## Supported Redis versions
Node Redis is supported with the following versions of Redis:
| Version | Supported |
|---------|--------------------|
| 6.2.z | :heavy_check_mark: |
| 6.0.z | :heavy_check_mark: |
| 5.y.z | :heavy_check_mark: |
| < 5.0 | :x: |
> Node Redis should work with older versions of Redis, but it is not fully tested and we cannot offer support.
## Packages
| Name | Description |
|-------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [redis](../) | [![Downloads](https://img.shields.io/npm/dm/redis.svg)](https://www.npmjs.com/package/redis/v/next) [![Version](https://img.shields.io/npm/v/redis/next.svg)](https://www.npmjs.com/package/redis/v/next) |
| [@node-redis/client](../packages/client) | [![Downloads](https://img.shields.io/npm/dm/@node-redis/client.svg)](https://www.npmjs.com/package/@node-redis/client/v/next) [![Version](https://img.shields.io/npm/v/@node-redis/client/next.svg)](https://www.npmjs.com/package/@node-redis/client/v/next) |
| [@node-redis/json](../packages/json) | [![Downloads](https://img.shields.io/npm/dm/@node-redis/json.svg)](https://www.npmjs.com/package/@node-redis/json/v/next) [![Version](https://img.shields.io/npm/v/@node-redis/json/next.svg)](https://www.npmjs.com/package/@node-redis/json/v/next) [Redis JSON](https://oss.redis.com/redisjson/) commands |
| [@node-redis/search](../packages/search) | [![Downloads](https://img.shields.io/npm/dm/@node-redis/search.svg)](https://www.npmjs.com/package/@node-redis/search/v/next) [![Version](https://img.shields.io/npm/v/@node-redis/search/next.svg)](https://www.npmjs.com/package/@node-redis/search/v/next) [Redis Search](https://oss.redis.com/redisearch/) commands |
## Contributing
If you'd like to contribute, check out the [contributing guide](CONTRIBUTING.md).
Thank you to all the people who already contributed to Node Redis!
[![Contributors](https://contrib.rocks/image?repo=redis/node-redis)](https://github.com/redis/node-redis/graphs/contributors)
## License
This repository is licensed under the "MIT" license. See [LICENSE](LICENSE).

314
README.md
View File

@@ -1,2 +1,312 @@
# redis
The sources and docs for this package are in the main [node-redis](https://github.com/redis/node-redis) repo.
# Node-Redis
[![Tests](https://img.shields.io/github/workflow/status/redis/node-redis/Tests/master.svg?label=tests)](https://codecov.io/gh/redis/node-redis)
[![Coverage](https://codecov.io/gh/redis/node-redis/branch/master/graph/badge.svg?token=xcfqHhJC37)](https://codecov.io/gh/redis/node-redis)
[![License](https://img.shields.io/github/license/redis/node-redis.svg)](https://codecov.io/gh/redis/node-redis)
[![Chat](https://img.shields.io/discord/697882427875393627.svg)](https://discord.gg/XMMVgxUm)
node-redis is a modern, high performance [Redis](https://redis.io) client for Node.js with built-in support for Redis 6.2 commands and modules including [RediSearch](https://redisearch.io) and [RedisJSON](https://redisjson.io).
## Installation
```bash
npm install redis
```
> :warning: The new interface is clean and cool, but if you have an existing codebase, you'll want to read the [migration guide](./docs/v3-to-v4.md).
## Usage
### Basic Example
```typescript
import { createClient } from 'redis';
(async () => {
const client = createClient();
client.on('error', (err) => console.log('Redis Client Error', err));
await client.connect();
await client.set('key', 'value');
const value = await client.get('key');
})();
```
The above code connects to localhost on port 6379. To connect to a different host or port, use a connection string in the format `redis[s]://[[username][:password]@][host][:port][/db-number]`:
```typescript
createClient({
url: 'redis://alice:foobared@awesome.redis.server:6380'
});
```
You can also use discrete parameters, UNIX sockets, and even TLS to connect. Details can be found in the [client configuration guide](./docs/client-configuration.md).
### Redis Commands
There is built-in support for all of the [out-of-the-box Redis commands](https://redis.io/commands). They are exposed using the raw Redis command names (`HSET`, `HGETALL`, etc.) and a friendlier camel-cased version (`hSet`, `hGetAll`, etc.):
```typescript
// raw Redis commands
await client.HSET('key', 'field', 'value');
await client.HGETALL('key');
// friendly JavaScript commands
await client.hSet('key', 'field', 'value');
await client.hGetAll('key');
```
Modifiers to commands are specified using a JavaScript object:
```typescript
await client.set('key', 'value', {
EX: 10,
NX: true
});
```
Replies will be transformed into useful data structures:
```typescript
await client.hGetAll('key'); // { field1: 'value1', field2: 'value2' }
await client.hVals('key'); // ['value1', 'value2']
```
### Unsupported Redis Commands
If you want to run commands and/or use arguments that Node Redis doesn't know about (yet!) use `.sendCommand()`:
```typescript
await client.sendCommand(['SET', 'key', 'value', 'NX']); // 'OK'
await client.sendCommand(['HGETALL', 'key']); // ['key1', 'field1', 'key2', 'field2']
```
### Transactions (Multi/Exec)
Start a [transaction](https://redis.io/topics/transactions) by calling `.multi()`, then chaining your commands. When you're done, call `.exec()` and you'll get an array back with your results:
```typescript
await client.set('another-key', 'another-value');
const [setKeyReply, otherKeyValue] = await client
.multi()
.set('key', 'value')
.get('another-key')
.exec(); // ['OK', 'another-value']
```
You can also [watch](https://redis.io/topics/transactions#optimistic-locking-using-check-and-set) keys by calling `.watch()`. Your transaction will abort if any of the watched keys change.
To dig deeper into transactions, check out the [Isolated Execution Guide](./docs/isolated-execution.md).
### Blocking Commands
Any command can be run on a new connection by specifying the `isolated` option. The newly created connection is closed when the command's `Promise` is fulfilled.
This pattern works especially well for blocking commands—such as `BLPOP` and `BLMOVE`:
```typescript
import { commandOptions } from 'redis';
const blPopPromise = client.blPop(commandOptions({ isolated: true }), 'key', 0);
await client.lPush('key', ['1', '2']);
await blPopPromise; // '2'
```
To learn more about isolated execution, check out the [guide](./docs/isolated-execution.md).
### Pub/Sub
Subscribing to a channel requires a dedicated stand-alone connection. You can easily get one by `.duplicate()`ing an existing Redis connection.
```typescript
const subscriber = client.duplicate();
await subscriber.connect();
```
Once you have one, simply subscribe and unsubscribe as needed:
```typescript
await subscriber.subscribe('channel', (message) => {
console.log(message); // 'message'
});
await subscriber.pSubscribe('channe*', (message, channel) => {
console.log(message, channel); // 'message', 'channel'
});
await subscriber.unsubscribe('channel');
await subscriber.pUnsubscribe('channe*');
```
Publish a message on a channel:
```typescript
await publisher.publish('channel', 'message');
```
There is support for buffers as well:
```typescript
await subscriber.subscribe('channel', (message) => {
console.log(message); // <Buffer 6d 65 73 73 61 67 65>
}, true);
await subscriber.pSubscribe('channe*', (message, channel) => {
console.log(message, channel); // <Buffer 6d 65 73 73 61 67 65>, <Buffer 63 68 61 6e 6e 65 6c>
}, true);
```
### Scan Iterator
[`SCAN`](https://redis.io/commands/scan) results can be looped over using [async iterators](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/asyncIterator):
```typescript
for await (const key of client.scanIterator()) {
// use the key!
await client.get(key);
}
```
This works with `HSCAN`, `SSCAN`, and `ZSCAN` too:
```typescript
for await (const { field, value } of client.hScanIterator('hash')) {}
for await (const member of client.sScanIterator('set')) {}
for await (const { score, member } of client.zScanIterator('sorted-set')) {}
```
You can override the default options by providing a configuration object:
```typescript
client.scanIterator({
TYPE: 'string', // `SCAN` only
MATCH: 'patter*',
COUNT: 100
});
```
### Lua Scripts
Define new functions using [Lua scripts](https://redis.io/commands/eval) which execute on the Redis server:
```typescript
import { createClient, defineScript } from 'redis';
(async () => {
const client = createClient({
scripts: {
add: defineScript({
NUMBER_OF_KEYS: 1,
SCRIPT:
'local val = redis.pcall("GET", KEYS[1]);' +
'return val + ARGV[1];',
transformArguments(key: string, toAdd: number): Array<string> {
return [key, toAdd.toString()];
},
transformReply(reply: number): number {
return reply;
}
})
}
});
await client.connect();
await client.set('key', '1');
await client.add('key', 2); // 3
})();
```
### Disconnecting
There are two functions that disconnect a client from the Redis server. In most scenarios you should use `.quit()` to ensure that pending commands are sent to Redis before closing a connection.
#### `.QUIT()`/`.quit()`
Gracefully close a client's connection to Redis, by sending the [`QUIT`](https://redis.io/commands/quit) command to the server. Before quitting, the client executes any remaining commands in its queue, and will receive replies from Redis for each of them.
```typescript
const [ping, get, quit] = await Promise.all([
client.ping(),
client.get('key'),
client.quit()
]); // ['PONG', null, 'OK']
try {
await client.get('key');
} catch (err) {
// ClosedClient Error
}
```
#### `.disconnect()`
Forcibly close a client's connection to Redis immediately. Calling `disconnect` will not send further pending commands to the Redis server, or wait for or parse outstanding responses.
```typescript
await client.disconnect();
```
### Auto-Pipelining
Node Redis will automatically pipeline requests that are made during the same "tick".
```typescript
client.set('Tm9kZSBSZWRpcw==', 'users:1');
client.sAdd('users:1:tokens', 'Tm9kZSBSZWRpcw==');
```
Of course, if you don't do something with your Promises you're certain to get [unhandled Promise exceptions](https://nodejs.org/api/process.html#process_event_unhandledrejection). To take advantage of auto-pipelining and handle your Promises, use `Promise.all()`.
```typescript
await Promise.all([
client.set('Tm9kZSBSZWRpcw==', 'users:1'),
client.sAdd('users:1:tokens', 'Tm9kZSBSZWRpcw==')
]);
```
### Clustering
Check out the [Clustering Guide](./docs/clustering.md) when using Node Redis to connect to a Redis Cluster.
## Supported Redis versions
Node Redis is supported with the following versions of Redis:
| Version | Supported |
|---------|--------------------|
| 6.2.z | :heavy_check_mark: |
| 6.0.z | :heavy_check_mark: |
| 5.y.z | :heavy_check_mark: |
| < 5.0 | :x: |
> Node Redis should work with older versions of Redis, but it is not fully tested and we cannot offer support.
## Packages
| Name | Description |
|-----------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [redis](./) | [![Downloads](https://img.shields.io/npm/dm/redis.svg)](https://www.npmjs.com/package/redis/v/next) [![Version](https://img.shields.io/npm/v/redis/next.svg)](https://www.npmjs.com/package/redis/v/next) |
| [@node-redis/client](./packages/client) | [![Downloads](https://img.shields.io/npm/dm/@node-redis/client.svg)](https://www.npmjs.com/package/@node-redis/client/v/next) [![Version](https://img.shields.io/npm/v/@node-redis/client/next.svg)](https://www.npmjs.com/package/@node-redis/client/v/next) |
| [@node-redis/json](./packages/json) | [![Downloads](https://img.shields.io/npm/dm/@node-redis/json.svg)](https://www.npmjs.com/package/@node-redis/json/v/next) [![Version](https://img.shields.io/npm/v/@node-redis/json/next.svg)](https://www.npmjs.com/package/@node-redis/json/v/next) [Redis JSON](https://oss.redis.com/redisjson/) commands |
| [@node-redis/search](./packages/search) | [![Downloads](https://img.shields.io/npm/dm/@node-redis/search.svg)](https://www.npmjs.com/package/@node-redis/search/v/next) [![Version](https://img.shields.io/npm/v/@node-redis/search/next.svg)](https://www.npmjs.com/package/@node-redis/search/v/next) [Redis Search](https://oss.redis.com/redisearch/) commands |
## Contributing
If you'd like to contribute, check out the [contributing guide](CONTRIBUTING.md).
Thank you to all the people who already contributed to Node Redis!
[![Contributors](https://contrib.rocks/image?repo=redis/node-redis)](https://github.com/redis/node-redis/graphs/contributors)
## License
This repository is licensed under the "MIT" license. See [LICENSE](LICENSE).

View File

@@ -15,11 +15,11 @@
| username | | ACL username ([see ACL guide](https://redis.io/topics/acl)) |
| password | | ACL password or the old "--requirepass" password |
| database | | Database number to connect to (see [`SELECT`](https://redis.io/commands/select) command) |
| modules | | Object defining which [Redis Modules](../.github/README.md#packages) to include |
| modules | | Object defining which [Redis Modules](../README.md#packages) to include |
| scripts | | Object defining Lua Scripts to use with this client (see [Lua Scripts](../README.md#lua-scripts)) |
| commandsQueueMaxLength | | Maximum length of the client's internal command queue |
| readonly | `false` | Connect in [`READONLY`](https://redis.io/commands/readonly) mode |
| legacyMode | `false` | Maintain some backwards compatibility (see the [Migration Guide](v3-to-v4.md)) |
| legacyMode | `false` | Maintain some backwards compatibility (see the [Migration Guide](./v3-to-v4.md)) |
| isolationPoolOptions | | See the [Isolated Execution Guide](./isolated-execution.md) |
## Reconnect Strategy

View File

@@ -38,7 +38,7 @@ import { createCluster } from 'redis';
| defaults | | The default configuration values for every client in the cluster. Use this for example when specifying an ACL user to connect with |
| useReplicas | `false` | When `true`, distribute load by executing readonly commands (such as `GET`, `GEOSEARCH`, etc.) across all cluster nodes. When `false`, only use master nodes |
| maxCommandRedirections | `16` | The maximum number of times a command will be redirected due to `MOVED` or `ASK` errors |
| modules | | Object defining which [Redis Modules](../../README.md#modules) to include |
| modules | | Object defining which [Redis Modules](../README.md#modules) to include |
| scripts | | Object defining Lua Scripts to use with this client (see [Lua Scripts](../README.md#lua-scripts)) |
## Command Routing

View File

@@ -4,7 +4,7 @@ Version 4 of Node Redis is a major refactor. While we have tried to maintain bac
## Breaking Changes
See the [Change Log](../CHANGELOG.md).
See the [Change Log](../packages/client/CHANGELOG.md).
## Promises

View File

@@ -2,14 +2,16 @@
This folder contains example scripts showing how to use Node Redis in different scenarios.
| File Name | Description |
|-----------------------------|------------------------------------------------------------------------------------|
| `blocking-list-pop.js` | Block until an element is pushed to a list |
| `command-with-modifiers.js` | Define a script that allows to run a command with several modifiers |
| `connect-as-acl-user.js` | Connect to Redis 6 using an ACL user |
| `lua-multi-incr.js` | Define a custom lua script that allows you to perform INCRBY on multiple keys |
| `search+json.js` | Use [Redis Search](https://redisearch.io/) and [Redis JSON](https://redisjson.io/) |
| `set-scan.js` | An example script that shows how to use the SSCAN iterator functionality |
| File Name | Description |
|-----------------------------|----------------------------------------------------------------------------------------------------------------|
| `blocking-list-pop.js` | Block until an element is pushed to a list |
| `command-with-modifiers.js` | Define a script that allows to run a command with several modifiers |
| `connect-as-acl-user.js` | Connect to Redis 6 using an ACL user |
| `lua-multi-incr.js` | Define a custom lua script that allows you to perform INCRBY on multiple keys |
| `managing-json.js` | Store, retrieve and manipulate JSON data atomically with [RedisJSON](https://redisjson.io/) |
| `search-hashes.js` | Uses [RediSearch](https://redisearch.io) to index and search data in hashes |
| `search-json.js` | Uses [RediSearch](https://redisearch.io/) and [RedisJSON](https://redisjson.io/) to index and search JSON data |
| `set-scan.js` | An example script that shows how to use the SSCAN iterator functionality |
## Contributing

81
examples/managing-json.js Normal file
View File

@@ -0,0 +1,81 @@
// Store, retrieve and manipulate JSON data atomically with RedisJSON.
import { createClient } from 'redis';
async function managingJSON() {
const client = createClient();
await client.connect();
await client.del('noderedis:jsondata');
// Store a JSON object...
await client.json.set('noderedis:jsondata', '$', {
name: 'Roberta McDonald',
pets: [
{
name: 'Fluffy',
species: 'dog',
age: 5,
isMammal: true
},
{
name: 'Rex',
species: 'dog',
age: 3,
isMammal: true
},
{
name: 'Goldie',
species: 'fish',
age: 2,
isMammal: false
}
],
address: {
number: 99,
street: 'Main Street',
city: 'Springfield',
state: 'OH',
country: 'USA'
}
});
// Retrieve the name and age of the second pet in the pets array.
let results = await client.json.get('noderedis:jsondata', {
path: [
'.pets[1].name',
'.pets[1].age'
]
});
// { '.pets[1].name': 'Rex', '.pets[1].age': 3 }
console.log(results);
// Goldie had a birthday, increment the age...
await client.json.numIncrBy('noderedis:jsondata', '.pets[2].age', 1);
results = await client.json.get('noderedis:jsondata', {
path: '.pets[2].age'
});
// Goldie is 3 years old now.
console.log(`Goldie is ${JSON.stringify(results)} years old now.`);
// Add a new pet...
await client.json.arrAppend('noderedis:jsondata', '.pets', {
name: 'Robin',
species: 'bird',
isMammal: false,
age: 1
});
// How many pets do we have now?
const numPets = await client.json.arrLen('noderedis:jsondata', '.pets');
// We now have 4 pets.
console.log(`We now have ${numPets} pets.`);
await client.quit();
}
managingJSON();

View File

@@ -1,74 +0,0 @@
// Use Redis Search and Redis JSON
import { createClient, SchemaFieldTypes, AggregateGroupByReducers, AggregateSteps } from 'redis';
async function searchPlusJson() {
const client = createClient();
await client.connect();
// Create an index
await client.ft.create('users', {
'$.name': {
type: SchemaFieldTypes.TEXT,
SORTABLE: 'UNF'
},
'$.age': SchemaFieldTypes.NUMERIC,
'$.coins': SchemaFieldTypes.NUMERIC
}, {
ON: 'JSON'
});
// Add some users
await Promise.all([
client.json.set('users:1', '$', {
name: 'Alice',
age: 32,
coins: 100
}),
client.json.set('users:2', '$', {
name: 'Bob',
age: 23,
coins: 15
})
]);
// Search all users under 30
// TODO: why "$.age:[-inf, 30]" does not work?
console.log(
await client.ft.search('users', '*')
);
// {
// total: 1,
// documents: [...]
// }
// Some aggrigrations
console.log(
await client.ft.aggregate('users', '*', {
STEPS: [{
type: AggregateSteps.GROUPBY,
REDUCE: [{
type: AggregateGroupByReducers.AVG,
property: '$.age',
AS: 'avarageAge'
}, {
type: AggregateGroupByReducers.SUM,
property: '$.coins',
AS: 'totalCoins'
}]
}]
})
);
// {
// total: 2,
// results: [{
// avarageAvg: '27.5',
// totalCoins: '115'
// }]
// }
await client.quit();
}
searchPlusJson();

82
examples/search-hashes.js Normal file
View File

@@ -0,0 +1,82 @@
// This example demonstrates how to use RediSearch to index and query data
// stored in Redis hashes.
import { createClient, SchemaFieldTypes } from 'redis';
async function searchHashes() {
const client = createClient();
await client.connect();
// Create an index...
try {
// Documentation: https://oss.redis.com/redisearch/Commands/#ftcreate
await client.ft.create('idx:animals', {
name: {
type: SchemaFieldTypes.TEXT,
sortable: true
},
species: SchemaFieldTypes.TAG,
age: SchemaFieldTypes.NUMERIC
}, {
ON: 'HASH',
PREFIX: 'noderedis:animals'
});
} catch (e) {
if (e.message === 'Index already exists') {
console.log('Index exists already, skipped creation.');
} else {
// Something went wrong, perhaps RediSearch isn't installed...
console.error(e);
process.exit(1);
}
}
// Add some sample data...
await Promise.all([
client.hSet('noderedis:animals:1', {name: 'Fluffy', species: 'cat', age: 3}),
client.hSet('noderedis:animals:2', {name: 'Ginger', species: 'cat', age: 4}),
client.hSet('noderedis:animals:3', {name: 'Rover', species: 'dog', age: 9}),
client.hSet('noderedis:animals:4', {name: 'Fido', species: 'dog', age: 7})
]);
// Perform a search query, find all the dogs...
// Documentation: https://oss.redis.com/redisearch/Commands/#ftsearch
// Query synatax: https://oss.redis.com/redisearch/Query_Syntax/
const results = await client.ft.search('idx:animals', '@species:{dog}');
// results:
// {
// total: 2,
// documents: [
// {
// id: 'noderedis:animals:4',
// value: {
// name: 'Fido',
// species: 'dog',
// age: '7'
// }
// },
// {
// id: 'noderedis:animals:3',
// value: {
// name: 'Rover',
// species: 'dog',
// age: '9'
// }
// }
// ]
// }
console.log(`Results found: ${results.total}.`);
for (const doc of results.documents) {
// noderedis:animals:4: Fido
// noderedis:animals:3: Rover
console.log(`${doc.id}: ${doc.value.name}`);
}
await client.quit();
}
searchHashes();

93
examples/search-json.js Normal file
View File

@@ -0,0 +1,93 @@
// This example demonstrates how to use RediSearch and RedisJSON together.
import { createClient, SchemaFieldTypes, AggregateGroupByReducers, AggregateSteps } from 'redis';
async function searchJSON() {
const client = createClient();
await client.connect();
// Create an index.
try {
await client.ft.create('idx:users', {
'$.name': {
type: SchemaFieldTypes.TEXT,
SORTABLE: 'UNF'
},
'$.age': {
type: SchemaFieldTypes.NUMERIC,
AS: 'age'
},
'$.coins': {
type: SchemaFieldTypes.NUMERIC,
AS: 'coins'
}
}, {
ON: 'JSON',
PREFIX: 'noderedis:users'
});
} catch (e) {
if (e.message === 'Index already exists') {
console.log('Index exists already, skipped creation.');
} else {
// Something went wrong, perhaps RediSearch isn't installed...
console.error(e);
process.exit(1);
}
}
// Add some users.
await Promise.all([
client.json.set('noderedis:users:1', '$', {
name: 'Alice',
age: 32,
coins: 100
}),
client.json.set('noderedis:users:2', '$', {
name: 'Bob',
age: 23,
coins: 15
})
]);
// Search all users under 30
console.log('Users under 30 years old:');
console.log(
// https://oss.redis.com/redisearch/Commands/#ftsearch
await client.ft.search('idx:users', '@age:[0 30]')
);
// {
// total: 1,
// documents: [ { id: 'noderedis:users:2', value: [Object] } ]
// }
// Some aggregrations, what's the average age and total number of coins...
// https://oss.redis.com/redisearch/Commands/#ftaggregate
console.log(
await client.ft.aggregate('idx:users', '*', {
STEPS: [{
type: AggregateSteps.GROUPBY,
REDUCE: [{
type: AggregateGroupByReducers.AVG,
property: 'age',
AS: 'averageAge'
}, {
type: AggregateGroupByReducers.SUM,
property: 'coins',
AS: 'totalCoins'
}]
}]
})
);
// {
// total: 2,
// results: [{
// averageAge: '27.5',
// totalCoins: '115'
// }]
// }
await client.quit();
}
searchJSON();

519
package-lock.json generated
View File

@@ -1,25 +1,25 @@
{
"name": "redis",
"version": "4.0.0-rc.4",
"version": "4.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "redis",
"version": "4.0.0-rc.4",
"version": "4.0.0",
"license": "MIT",
"workspaces": [
"./packages/*"
],
"dependencies": {
"@node-redis/client": "^1.0.0-rc",
"@node-redis/json": "^1.0.0-rc",
"@node-redis/search": "^1.0.0-rc"
"@node-redis/client": "^1.0.0",
"@node-redis/json": "^1.0.0",
"@node-redis/search": "^1.0.0"
},
"devDependencies": {
"@tsconfig/node12": "^1.0.9",
"release-it": "^14.11.7",
"typescript": "^4.4.4"
"release-it": "^14.11.8",
"typescript": "^4.5.2"
}
},
"node_modules/@babel/code-frame": {
@@ -35,9 +35,9 @@
}
},
"node_modules/@babel/compat-data": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.0.tgz",
"integrity": "sha512-DGjt2QZse5SGd9nfOSqO4WLJ8NN/oHkijbXbPrxuoJO3oIPJL3TciZs9FX+cOHNiY9E9l0opL8g7BmLe3T+9ew==",
"version": "7.16.4",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.4.tgz",
"integrity": "sha512-1o/jo7D+kC9ZjHX5v+EHrdjl3PhxMrLSOTGsOdHJ+KL8HCaEK6ehrVL2RS6oHDZp+L7xLirLrPmQtEng769J/Q==",
"dev": true,
"engines": {
"node": ">=6.9.0"
@@ -364,9 +364,9 @@
}
},
"node_modules/@babel/parser": {
"version": "7.16.3",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.3.tgz",
"integrity": "sha512-dcNwU1O4sx57ClvLBVFbEgx0UZWfd0JQX5X6fxFRCLHelFBGXFfSz6Y0FAq2PEwUqlqLkdVjVr4VASEOuUnLJw==",
"version": "7.16.4",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.4.tgz",
"integrity": "sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng==",
"dev": true,
"bin": {
"parser": "bin/babel-parser.js"
@@ -669,6 +669,10 @@
"resolved": "packages/test-utils",
"link": true
},
"node_modules/@node-redis/time-series": {
"resolved": "packages/time-series",
"link": true
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -816,15 +820,15 @@
}
},
"node_modules/@octokit/rest": {
"version": "18.10.0",
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.10.0.tgz",
"integrity": "sha512-esHR5OKy38bccL/sajHqZudZCvmv4yjovMJzyXlphaUo7xykmtOdILGJ3aAm0mFHmMLmPFmDMJXf39cAjNJsrw==",
"version": "18.12.0",
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.12.0.tgz",
"integrity": "sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q==",
"dev": true,
"dependencies": {
"@octokit/core": "^3.5.1",
"@octokit/plugin-paginate-rest": "^2.16.0",
"@octokit/plugin-paginate-rest": "^2.16.8",
"@octokit/plugin-request-log": "^1.0.4",
"@octokit/plugin-rest-endpoint-methods": "^5.9.0"
"@octokit/plugin-rest-endpoint-methods": "^5.12.0"
}
},
"node_modules/@octokit/types": {
@@ -959,9 +963,9 @@
"dev": true
},
"node_modules/@types/node": {
"version": "16.11.7",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.7.tgz",
"integrity": "sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw==",
"version": "16.11.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.10.tgz",
"integrity": "sha512-3aRnHa1KlOEEhJ6+CvyHKK5vE9BcLGjtUpwvqYLRvYNQKMfabu3BwfJaA/SLW8dxe28LsNDjtHwePTuzn3gmOA==",
"dev": true
},
"node_modules/@types/parse-json": {
@@ -1011,9 +1015,9 @@
"dev": true
},
"node_modules/@types/yargs": {
"version": "17.0.5",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.5.tgz",
"integrity": "sha512-4HNq144yhaVjJs+ON6A07NEoi9Hh0Rhl/jI9Nt/l/YRjt+T6St/QK3meFARWZ8IgkzoD1LC0PdTdJenlQQi2WQ==",
"version": "17.0.7",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.7.tgz",
"integrity": "sha512-OvLKmpKdea1aWtqHv9bxVVcMoT6syAeK+198dfETIFkAevYRGwqh4H+KFxfjUETZuUuE5sQCAFwdOdoHUdo8eg==",
"dev": true,
"dependencies": {
"@types/yargs-parser": "*"
@@ -1189,9 +1193,9 @@
"dev": true
},
"node_modules/acorn": {
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz",
"integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==",
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.6.0.tgz",
"integrity": "sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
@@ -1630,9 +1634,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001280",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001280.tgz",
"integrity": "sha512-kFXwYvHe5rix25uwueBxC569o53J6TpnGu0BEEn+6Lhl2vsnAumRFWEBhDft1fwyo6m1r4i+RqA4+163FpeFcA==",
"version": "1.0.30001282",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001282.tgz",
"integrity": "sha512-YhF/hG6nqBEllymSIjLtR2iWDDnChvhnVJqp+vloyt2tEHFG1yBR+ac2B/rOw0qOK0m0lEXU2dv4E/sMk5P9Kg==",
"dev": true,
"funding": {
"type": "opencollective",
@@ -1683,9 +1687,9 @@
}
},
"node_modules/ci-info": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.2.0.tgz",
"integrity": "sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A==",
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.0.tgz",
"integrity": "sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw==",
"dev": true
},
"node_modules/clean-stack": {
@@ -2085,9 +2089,9 @@
"dev": true
},
"node_modules/electron-to-chromium": {
"version": "1.3.897",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.897.tgz",
"integrity": "sha512-nRNZhAZ7hVCe75jrCUG7xLOqHMwloJMj6GEXEzY4OMahRGgwerAo+ls/qbqUwFH+E20eaSncKkQ4W8KP5SOiAg==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.0.tgz",
"integrity": "sha512-+oXCt6SaIu8EmFTPx8wNGSB0tHQ5biDscnlf6Uxuz17e9CjzMRtGk9B8705aMPnj0iWr3iC74WuIkngCsLElmA==",
"dev": true
},
"node_modules/emoji-regex": {
@@ -2160,9 +2164,9 @@
}
},
"node_modules/eslint": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.2.0.tgz",
"integrity": "sha512-erw7XmM+CLxTOickrimJ1SiF55jiNlVSp2qqm0NuBWPtHYQCegD5ZMaW0c3i5ytPqL+SSLaCxdvQXFPLJn+ABw==",
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.3.0.tgz",
"integrity": "sha512-aIay56Ph6RxOTC7xyr59Kt3ewX185SaGnAr8eWukoPLeriCrvGjvAubxuvaXOfsxhtwV5g0uBOsyhAom4qJdww==",
"dev": true,
"dependencies": {
"@eslint/eslintrc": "^1.0.4",
@@ -2174,10 +2178,10 @@
"doctrine": "^3.0.0",
"enquirer": "^2.3.5",
"escape-string-regexp": "^4.0.0",
"eslint-scope": "^6.0.0",
"eslint-scope": "^7.1.0",
"eslint-utils": "^3.0.0",
"eslint-visitor-keys": "^3.0.0",
"espree": "^9.0.0",
"eslint-visitor-keys": "^3.1.0",
"espree": "^9.1.0",
"esquery": "^1.4.0",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
@@ -2276,9 +2280,9 @@
}
},
"node_modules/eslint/node_modules/eslint-scope": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-6.0.0.tgz",
"integrity": "sha512-uRDL9MWmQCkaFus8RF5K9/L/2fn+80yoW3jkD53l4shjCh26fCtvJGasxjUqP5OT87SYTxCVA3BwTUzuELx9kA==",
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.0.tgz",
"integrity": "sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg==",
"dev": true,
"dependencies": {
"esrecurse": "^4.3.0",
@@ -2331,14 +2335,14 @@
}
},
"node_modules/espree": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.0.0.tgz",
"integrity": "sha512-r5EQJcYZ2oaGbeR0jR0fFVijGOcwai07/690YRXLINuhmVeRY4UKSAsQPe/0BNuDgwP7Ophoc1PRsr2E3tkbdQ==",
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.1.0.tgz",
"integrity": "sha512-ZgYLvCS1wxOczBYGcQT9DDWgicXwJ4dbocr9uYN+/eresBAUuBu+O4WzB21ufQ/JqQT8gyp7hJ3z8SHii32mTQ==",
"dev": true,
"dependencies": {
"acorn": "^8.5.0",
"acorn": "^8.6.0",
"acorn-jsx": "^5.3.1",
"eslint-visitor-keys": "^3.0.0"
"eslint-visitor-keys": "^3.1.0"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -2860,9 +2864,9 @@
}
},
"node_modules/got": {
"version": "11.8.2",
"resolved": "https://registry.npmjs.org/got/-/got-11.8.2.tgz",
"integrity": "sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==",
"version": "11.8.3",
"resolved": "https://registry.npmjs.org/got/-/got-11.8.3.tgz",
"integrity": "sha512-7gtQ5KiPh1RtGS9/Jbv1ofDpBFuq42gyfEib+ejaRBJuj/3tQFeR5+gw57e4ipaU8c/rCjvX6fkQz2lyDlGAOg==",
"dev": true,
"dependencies": {
"@sindresorhus/is": "^4.0.0",
@@ -2870,7 +2874,7 @@
"@types/cacheable-request": "^6.0.1",
"@types/responselike": "^1.0.0",
"cacheable-lookup": "^5.0.3",
"cacheable-request": "^7.0.1",
"cacheable-request": "^7.0.2",
"decompress-response": "^6.0.0",
"http2-wrapper": "^1.0.0-beta.5.2",
"lowercase-keys": "^2.0.0",
@@ -3182,9 +3186,9 @@
}
},
"node_modules/inquirer": {
"version": "8.1.5",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.1.5.tgz",
"integrity": "sha512-G6/9xUqmt/r+UvufSyrPpt84NYwhKZ9jLsgMbQzlx804XErNupor8WQdBnBRrXmBfTPpuwf1sV+ss2ovjgdXIg==",
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.0.tgz",
"integrity": "sha512-0crLweprevJ02tTuA6ThpoAERAGyVILC4sS74uib58Xf/zSr1/ZWtmm7D5CI+bSQEaA04f0K7idaHpQbSWgiVQ==",
"dev": true,
"dependencies": {
"ansi-escapes": "^4.2.1",
@@ -3234,12 +3238,12 @@
}
},
"node_modules/is-ci": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.0.tgz",
"integrity": "sha512-kDXyttuLeslKAHYL/K28F2YkM3x5jvFPEw3yXbRptXydjD9rpLEz+C5K5iutY9ZiUu6AP41JdvRQwF4Iqs4ZCQ==",
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz",
"integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==",
"dev": true,
"dependencies": {
"ci-info": "^3.1.1"
"ci-info": "^3.2.0"
},
"bin": {
"is-ci": "bin.js"
@@ -3701,9 +3705,9 @@
}
},
"node_modules/lines-and-columns": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz",
"integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=",
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"dev": true
},
"node_modules/locate-path": {
@@ -3871,21 +3875,21 @@
}
},
"node_modules/mime-db": {
"version": "1.49.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz",
"integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==",
"version": "1.51.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz",
"integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==",
"dev": true,
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.32",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz",
"integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==",
"version": "2.1.34",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz",
"integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==",
"dev": true,
"dependencies": {
"mime-db": "1.49.0"
"mime-db": "1.51.0"
},
"engines": {
"node": ">= 0.6"
@@ -5227,13 +5231,13 @@
}
},
"node_modules/release-it": {
"version": "14.11.7",
"resolved": "https://registry.npmjs.org/release-it/-/release-it-14.11.7.tgz",
"integrity": "sha512-m4p9+x6AEQPczc96Jyg6dGFeovpJVgRCtA1lxeIgTmQVt9dutYPkkjZeJngZgUJ17/Lb1bx6ZzW2qsKmopKnbQ==",
"version": "14.11.8",
"resolved": "https://registry.npmjs.org/release-it/-/release-it-14.11.8.tgz",
"integrity": "sha512-951DJ0kwjwU7CwGU3BCvRBgLxuJsOPRrZkqx0AsugJdSyPpUdwY9nlU0RAoSKqgh+VTerzecXLIIwgsGIpNxlA==",
"dev": true,
"dependencies": {
"@iarna/toml": "2.2.5",
"@octokit/rest": "18.10.0",
"@octokit/rest": "18.12.0",
"async-retry": "1.3.3",
"chalk": "4.1.2",
"cosmiconfig": "7.0.1",
@@ -5243,12 +5247,12 @@
"form-data": "4.0.0",
"git-url-parse": "11.6.0",
"globby": "11.0.4",
"got": "11.8.2",
"got": "11.8.3",
"import-cwd": "3.0.0",
"inquirer": "8.1.5",
"is-ci": "3.0.0",
"inquirer": "8.2.0",
"is-ci": "3.0.1",
"lodash": "4.17.21",
"mime-types": "2.1.32",
"mime-types": "2.1.34",
"new-github-release-url": "1.0.0",
"open": "7.4.2",
"ora": "5.4.1",
@@ -5562,9 +5566,9 @@
}
},
"node_modules/signal-exit": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.5.tgz",
"integrity": "sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==",
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz",
"integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==",
"dev": true
},
"node_modules/sinon": {
@@ -5613,9 +5617,9 @@
}
},
"node_modules/source-map-support": {
"version": "0.5.20",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz",
"integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==",
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
"dev": true,
"dependencies": {
"buffer-from": "^1.0.0",
@@ -5940,9 +5944,9 @@
}
},
"node_modules/typedoc": {
"version": "0.22.9",
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.9.tgz",
"integrity": "sha512-84PjudoXVcap6bwdZFbYIUWlgdz/iLV09ZHwrCzhtHWXaDQG6mlosJ8te6DSThuRkRvQjp46HO+qY/P7Gpm78g==",
"version": "0.22.10",
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.10.tgz",
"integrity": "sha512-hQYZ4WtoMZ61wDC6w10kxA42+jclWngdmztNZsDvIz7BMJg7F2xnT+uYsUa7OluyKossdFj9E9Ye4QOZKTy8SA==",
"dev": true,
"dependencies": {
"glob": "^7.2.0",
@@ -5958,7 +5962,7 @@
"node": ">= 12.10.0"
},
"peerDependencies": {
"typescript": "4.0.x || 4.1.x || 4.2.x || 4.3.x || 4.4.x"
"typescript": "4.0.x || 4.1.x || 4.2.x || 4.3.x || 4.4.x || 4.5.x"
}
},
"node_modules/typedoc-github-wiki-theme": {
@@ -5972,9 +5976,9 @@
}
},
"node_modules/typedoc-plugin-markdown": {
"version": "3.11.6",
"resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.11.6.tgz",
"integrity": "sha512-CV1BuxL7HR/EE1ctnPXOWzf4/Exl0FzkwtFVYaKTVWTnD/dkFLgABOfWuOL4lPmzLUOsAL85pmq+/PB6cdRppw==",
"version": "3.11.7",
"resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.11.7.tgz",
"integrity": "sha512-Wm3HP5gcBOGOOTeDA8GLgw+BY+GAI31RP9Lyog21BvTaSeWUcdXls5TG1MK+XDatS2/0dup9gFO+emoyoQJm9Q==",
"dev": true,
"dependencies": {
"handlebars": "^4.7.7"
@@ -5984,9 +5988,9 @@
}
},
"node_modules/typescript": {
"version": "4.4.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz",
"integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==",
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz",
"integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
@@ -6412,7 +6416,7 @@
},
"packages/client": {
"name": "@node-redis/client",
"version": "1.0.0-rc",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"cluster-key-slot": "1.1.0",
@@ -6423,22 +6427,22 @@
"devDependencies": {
"@istanbuljs/nyc-config-typescript": "^1.0.1",
"@node-redis/test-utils": "*",
"@types/node": "^16.11.7",
"@types/node": "^16.11.10",
"@types/redis-parser": "^3.0.0",
"@types/sinon": "^10.0.6",
"@types/yallist": "^4.0.1",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"eslint": "^8.2.0",
"eslint": "^8.3.0",
"nyc": "^15.1.0",
"release-it": "^14.11.7",
"release-it": "^14.11.8",
"sinon": "^12.0.1",
"source-map-support": "^0.5.20",
"source-map-support": "^0.5.21",
"ts-node": "^10.4.0",
"typedoc": "^0.22.9",
"typedoc": "^0.22.10",
"typedoc-github-wiki-theme": "^0.6.0",
"typedoc-plugin-markdown": "^3.11.6",
"typescript": "^4.4.4"
"typedoc-plugin-markdown": "^3.11.7",
"typescript": "^4.5.2"
},
"engines": {
"node": ">=12"
@@ -6451,19 +6455,56 @@
"devDependencies": {
"@istanbuljs/nyc-config-typescript": "^1.0.1",
"@node-redis/test-utils": "*",
"@types/node": "^16.11.7",
"@types/node": "^16.11.10",
"nyc": "^15.1.0",
"release-it": "^14.11.7",
"source-map-support": "^0.5.20",
"release-it": "^14.11.8",
"source-map-support": "^0.5.21",
"ts-node": "^10.4.0",
"typescript": "^4.4.4"
"typescript": "^4.5.2"
},
"peerDependencies": {
"@node-redis/client": "^1.0.0-rc"
"@node-redis/client": "^1.0.0"
}
},
"packages/search": {
"name": "@node-redis/search",
"version": "1.0.0",
"license": "MIT",
"devDependencies": {
"@istanbuljs/nyc-config-typescript": "^1.0.1",
"@node-redis/test-utils": "*",
"@types/node": "^16.11.10",
"nyc": "^15.1.0",
"release-it": "^14.11.8",
"source-map-support": "^0.5.21",
"ts-node": "^10.4.0",
"typescript": "^4.5.2"
},
"peerDependencies": {
"@node-redis/client": "^1.0.0"
}
},
"packages/test-utils": {
"name": "@node-redis/test-utils",
"devDependencies": {
"@istanbuljs/nyc-config-typescript": "^1.0.1",
"@types/mocha": "^9.0.0",
"@types/node": "^16.11.10",
"@types/yargs": "^17.0.7",
"mocha": "^9.1.3",
"nyc": "^15.1.0",
"release-it": "^14.11.8",
"source-map-support": "^0.5.21",
"ts-node": "^10.4.0",
"typescript": "^4.5.2",
"yargs": "^17.2.1"
},
"peerDependencies": {
"@node-redis/client": "^1.0.0"
}
},
"packages/time-series": {
"name": "@node-redis/time-series",
"version": "1.0.0-rc.0",
"license": "MIT",
"devDependencies": {
@@ -6477,26 +6518,7 @@
"typescript": "^4.4.4"
},
"peerDependencies": {
"@node-redis/client": "^1.0.0-rc"
}
},
"packages/test-utils": {
"name": "@node-redis/test-utils",
"devDependencies": {
"@istanbuljs/nyc-config-typescript": "^1.0.1",
"@types/mocha": "^9.0.0",
"@types/node": "^16.11.7",
"@types/yargs": "^17.0.5",
"mocha": "^9.1.3",
"nyc": "^15.1.0",
"release-it": "^14.11.7",
"source-map-support": "^0.5.20",
"ts-node": "^10.4.0",
"typescript": "^4.4.4",
"yargs": "^17.2.1"
},
"peerDependencies": {
"@node-redis/client": "^1.0.0-rc"
"@node-redis/client": "^1.0.0"
}
}
},
@@ -6511,9 +6533,9 @@
}
},
"@babel/compat-data": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.0.tgz",
"integrity": "sha512-DGjt2QZse5SGd9nfOSqO4WLJ8NN/oHkijbXbPrxuoJO3oIPJL3TciZs9FX+cOHNiY9E9l0opL8g7BmLe3T+9ew==",
"version": "7.16.4",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.4.tgz",
"integrity": "sha512-1o/jo7D+kC9ZjHX5v+EHrdjl3PhxMrLSOTGsOdHJ+KL8HCaEK6ehrVL2RS6oHDZp+L7xLirLrPmQtEng769J/Q==",
"dev": true
},
"@babel/core": {
@@ -6767,9 +6789,9 @@
}
},
"@babel/parser": {
"version": "7.16.3",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.3.tgz",
"integrity": "sha512-dcNwU1O4sx57ClvLBVFbEgx0UZWfd0JQX5X6fxFRCLHelFBGXFfSz6Y0FAq2PEwUqlqLkdVjVr4VASEOuUnLJw==",
"version": "7.16.4",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.4.tgz",
"integrity": "sha512-6V0qdPUaiVHH3RtZeLIsc+6pDhbYzHR8ogA8w+f+Wc77DuXto19g2QUwveINoS34Uw+W8/hQDGJCx+i4n7xcng==",
"dev": true
},
"@babel/template": {
@@ -6990,25 +7012,25 @@
"requires": {
"@istanbuljs/nyc-config-typescript": "^1.0.1",
"@node-redis/test-utils": "*",
"@types/node": "^16.11.7",
"@types/node": "^16.11.10",
"@types/redis-parser": "^3.0.0",
"@types/sinon": "^10.0.6",
"@types/yallist": "^4.0.1",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"cluster-key-slot": "1.1.0",
"eslint": "^8.2.0",
"eslint": "^8.3.0",
"generic-pool": "3.8.2",
"nyc": "^15.1.0",
"redis-parser": "3.0.0",
"release-it": "^14.11.7",
"release-it": "^14.11.8",
"sinon": "^12.0.1",
"source-map-support": "^0.5.20",
"source-map-support": "^0.5.21",
"ts-node": "^10.4.0",
"typedoc": "^0.22.9",
"typedoc": "^0.22.10",
"typedoc-github-wiki-theme": "^0.6.0",
"typedoc-plugin-markdown": "^3.11.6",
"typescript": "^4.4.4",
"typedoc-plugin-markdown": "^3.11.7",
"typescript": "^4.5.2",
"yallist": "4.0.0"
}
},
@@ -7017,16 +7039,45 @@
"requires": {
"@istanbuljs/nyc-config-typescript": "^1.0.1",
"@node-redis/test-utils": "*",
"@types/node": "^16.11.7",
"@types/node": "^16.11.10",
"nyc": "^15.1.0",
"release-it": "^14.11.7",
"source-map-support": "^0.5.20",
"release-it": "^14.11.8",
"source-map-support": "^0.5.21",
"ts-node": "^10.4.0",
"typescript": "^4.4.4"
"typescript": "^4.5.2"
}
},
"@node-redis/search": {
"version": "file:packages/search",
"requires": {
"@istanbuljs/nyc-config-typescript": "^1.0.1",
"@node-redis/test-utils": "*",
"@types/node": "^16.11.10",
"nyc": "^15.1.0",
"release-it": "^14.11.8",
"source-map-support": "^0.5.21",
"ts-node": "^10.4.0",
"typescript": "^4.5.2"
}
},
"@node-redis/test-utils": {
"version": "file:packages/test-utils",
"requires": {
"@istanbuljs/nyc-config-typescript": "^1.0.1",
"@types/mocha": "^9.0.0",
"@types/node": "^16.11.10",
"@types/yargs": "^17.0.7",
"mocha": "^9.1.3",
"nyc": "^15.1.0",
"release-it": "^14.11.8",
"source-map-support": "^0.5.21",
"ts-node": "^10.4.0",
"typescript": "^4.5.2",
"yargs": "^17.2.1"
}
},
"@node-redis/time-series": {
"version": "file:packages/time-series",
"requires": {
"@istanbuljs/nyc-config-typescript": "^1.0.1",
"@node-redis/test-utils": "*",
@@ -7038,22 +7089,6 @@
"typescript": "^4.4.4"
}
},
"@node-redis/test-utils": {
"version": "file:packages/test-utils",
"requires": {
"@istanbuljs/nyc-config-typescript": "^1.0.1",
"@types/mocha": "^9.0.0",
"@types/node": "^16.11.7",
"@types/yargs": "^17.0.5",
"mocha": "^9.1.3",
"nyc": "^15.1.0",
"release-it": "^14.11.7",
"source-map-support": "^0.5.20",
"ts-node": "^10.4.0",
"typescript": "^4.4.4",
"yargs": "^17.2.1"
}
},
"@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -7184,15 +7219,15 @@
}
},
"@octokit/rest": {
"version": "18.10.0",
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.10.0.tgz",
"integrity": "sha512-esHR5OKy38bccL/sajHqZudZCvmv4yjovMJzyXlphaUo7xykmtOdILGJ3aAm0mFHmMLmPFmDMJXf39cAjNJsrw==",
"version": "18.12.0",
"resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.12.0.tgz",
"integrity": "sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q==",
"dev": true,
"requires": {
"@octokit/core": "^3.5.1",
"@octokit/plugin-paginate-rest": "^2.16.0",
"@octokit/plugin-paginate-rest": "^2.16.8",
"@octokit/plugin-request-log": "^1.0.4",
"@octokit/plugin-rest-endpoint-methods": "^5.9.0"
"@octokit/plugin-rest-endpoint-methods": "^5.12.0"
}
},
"@octokit/types": {
@@ -7318,9 +7353,9 @@
"dev": true
},
"@types/node": {
"version": "16.11.7",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.7.tgz",
"integrity": "sha512-QB5D2sqfSjCmTuWcBWyJ+/44bcjO7VbjSbOE0ucoVbAsSNQc4Lt6QkgkVXkTDwkL4z/beecZNDvVX15D4P8Jbw==",
"version": "16.11.10",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.10.tgz",
"integrity": "sha512-3aRnHa1KlOEEhJ6+CvyHKK5vE9BcLGjtUpwvqYLRvYNQKMfabu3BwfJaA/SLW8dxe28LsNDjtHwePTuzn3gmOA==",
"dev": true
},
"@types/parse-json": {
@@ -7370,9 +7405,9 @@
"dev": true
},
"@types/yargs": {
"version": "17.0.5",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.5.tgz",
"integrity": "sha512-4HNq144yhaVjJs+ON6A07NEoi9Hh0Rhl/jI9Nt/l/YRjt+T6St/QK3meFARWZ8IgkzoD1LC0PdTdJenlQQi2WQ==",
"version": "17.0.7",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.7.tgz",
"integrity": "sha512-OvLKmpKdea1aWtqHv9bxVVcMoT6syAeK+198dfETIFkAevYRGwqh4H+KFxfjUETZuUuE5sQCAFwdOdoHUdo8eg==",
"dev": true,
"requires": {
"@types/yargs-parser": "*"
@@ -7474,9 +7509,9 @@
"dev": true
},
"acorn": {
"version": "8.5.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz",
"integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==",
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.6.0.tgz",
"integrity": "sha512-U1riIR+lBSNi3IbxtaHOIKdH8sLFv3NYfNv8sg7ZsNhcfl4HF2++BfqqrNAxoCLQW1iiylOj76ecnaUxz+z9yw==",
"dev": true
},
"acorn-jsx": {
@@ -7791,9 +7826,9 @@
"dev": true
},
"caniuse-lite": {
"version": "1.0.30001280",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001280.tgz",
"integrity": "sha512-kFXwYvHe5rix25uwueBxC569o53J6TpnGu0BEEn+6Lhl2vsnAumRFWEBhDft1fwyo6m1r4i+RqA4+163FpeFcA==",
"version": "1.0.30001282",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001282.tgz",
"integrity": "sha512-YhF/hG6nqBEllymSIjLtR2iWDDnChvhnVJqp+vloyt2tEHFG1yBR+ac2B/rOw0qOK0m0lEXU2dv4E/sMk5P9Kg==",
"dev": true
},
"chalk": {
@@ -7829,9 +7864,9 @@
}
},
"ci-info": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.2.0.tgz",
"integrity": "sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A==",
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.0.tgz",
"integrity": "sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw==",
"dev": true
},
"clean-stack": {
@@ -8137,9 +8172,9 @@
"dev": true
},
"electron-to-chromium": {
"version": "1.3.897",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.897.tgz",
"integrity": "sha512-nRNZhAZ7hVCe75jrCUG7xLOqHMwloJMj6GEXEzY4OMahRGgwerAo+ls/qbqUwFH+E20eaSncKkQ4W8KP5SOiAg==",
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.0.tgz",
"integrity": "sha512-+oXCt6SaIu8EmFTPx8wNGSB0tHQ5biDscnlf6Uxuz17e9CjzMRtGk9B8705aMPnj0iWr3iC74WuIkngCsLElmA==",
"dev": true
},
"emoji-regex": {
@@ -8200,9 +8235,9 @@
"dev": true
},
"eslint": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.2.0.tgz",
"integrity": "sha512-erw7XmM+CLxTOickrimJ1SiF55jiNlVSp2qqm0NuBWPtHYQCegD5ZMaW0c3i5ytPqL+SSLaCxdvQXFPLJn+ABw==",
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.3.0.tgz",
"integrity": "sha512-aIay56Ph6RxOTC7xyr59Kt3ewX185SaGnAr8eWukoPLeriCrvGjvAubxuvaXOfsxhtwV5g0uBOsyhAom4qJdww==",
"dev": true,
"requires": {
"@eslint/eslintrc": "^1.0.4",
@@ -8214,10 +8249,10 @@
"doctrine": "^3.0.0",
"enquirer": "^2.3.5",
"escape-string-regexp": "^4.0.0",
"eslint-scope": "^6.0.0",
"eslint-scope": "^7.1.0",
"eslint-utils": "^3.0.0",
"eslint-visitor-keys": "^3.0.0",
"espree": "^9.0.0",
"eslint-visitor-keys": "^3.1.0",
"espree": "^9.1.0",
"esquery": "^1.4.0",
"esutils": "^2.0.2",
"fast-deep-equal": "^3.1.3",
@@ -8252,9 +8287,9 @@
"dev": true
},
"eslint-scope": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-6.0.0.tgz",
"integrity": "sha512-uRDL9MWmQCkaFus8RF5K9/L/2fn+80yoW3jkD53l4shjCh26fCtvJGasxjUqP5OT87SYTxCVA3BwTUzuELx9kA==",
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.0.tgz",
"integrity": "sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg==",
"dev": true,
"requires": {
"esrecurse": "^4.3.0",
@@ -8324,14 +8359,14 @@
"dev": true
},
"espree": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.0.0.tgz",
"integrity": "sha512-r5EQJcYZ2oaGbeR0jR0fFVijGOcwai07/690YRXLINuhmVeRY4UKSAsQPe/0BNuDgwP7Ophoc1PRsr2E3tkbdQ==",
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.1.0.tgz",
"integrity": "sha512-ZgYLvCS1wxOczBYGcQT9DDWgicXwJ4dbocr9uYN+/eresBAUuBu+O4WzB21ufQ/JqQT8gyp7hJ3z8SHii32mTQ==",
"dev": true,
"requires": {
"acorn": "^8.5.0",
"acorn": "^8.6.0",
"acorn-jsx": "^5.3.1",
"eslint-visitor-keys": "^3.0.0"
"eslint-visitor-keys": "^3.1.0"
}
},
"esprima": {
@@ -8705,9 +8740,9 @@
}
},
"got": {
"version": "11.8.2",
"resolved": "https://registry.npmjs.org/got/-/got-11.8.2.tgz",
"integrity": "sha512-D0QywKgIe30ODs+fm8wMZiAcZjypcCodPNuMz5H9Mny7RJ+IjJ10BdmGW7OM7fHXP+O7r6ZwapQ/YQmMSvB0UQ==",
"version": "11.8.3",
"resolved": "https://registry.npmjs.org/got/-/got-11.8.3.tgz",
"integrity": "sha512-7gtQ5KiPh1RtGS9/Jbv1ofDpBFuq42gyfEib+ejaRBJuj/3tQFeR5+gw57e4ipaU8c/rCjvX6fkQz2lyDlGAOg==",
"dev": true,
"requires": {
"@sindresorhus/is": "^4.0.0",
@@ -8715,7 +8750,7 @@
"@types/cacheable-request": "^6.0.1",
"@types/responselike": "^1.0.0",
"cacheable-lookup": "^5.0.3",
"cacheable-request": "^7.0.1",
"cacheable-request": "^7.0.2",
"decompress-response": "^6.0.0",
"http2-wrapper": "^1.0.0-beta.5.2",
"lowercase-keys": "^2.0.0",
@@ -8933,9 +8968,9 @@
"dev": true
},
"inquirer": {
"version": "8.1.5",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.1.5.tgz",
"integrity": "sha512-G6/9xUqmt/r+UvufSyrPpt84NYwhKZ9jLsgMbQzlx804XErNupor8WQdBnBRrXmBfTPpuwf1sV+ss2ovjgdXIg==",
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.0.tgz",
"integrity": "sha512-0crLweprevJ02tTuA6ThpoAERAGyVILC4sS74uib58Xf/zSr1/ZWtmm7D5CI+bSQEaA04f0K7idaHpQbSWgiVQ==",
"dev": true,
"requires": {
"ansi-escapes": "^4.2.1",
@@ -8976,12 +9011,12 @@
}
},
"is-ci": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.0.tgz",
"integrity": "sha512-kDXyttuLeslKAHYL/K28F2YkM3x5jvFPEw3yXbRptXydjD9rpLEz+C5K5iutY9ZiUu6AP41JdvRQwF4Iqs4ZCQ==",
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz",
"integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==",
"dev": true,
"requires": {
"ci-info": "^3.1.1"
"ci-info": "^3.2.0"
}
},
"is-core-module": {
@@ -9325,9 +9360,9 @@
}
},
"lines-and-columns": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz",
"integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=",
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"dev": true
},
"locate-path": {
@@ -9452,18 +9487,18 @@
}
},
"mime-db": {
"version": "1.49.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.49.0.tgz",
"integrity": "sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==",
"version": "1.51.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz",
"integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==",
"dev": true
},
"mime-types": {
"version": "2.1.32",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.32.tgz",
"integrity": "sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==",
"version": "2.1.34",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz",
"integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==",
"dev": true,
"requires": {
"mime-db": "1.49.0"
"mime-db": "1.51.0"
}
},
"mimic-fn": {
@@ -10482,13 +10517,13 @@
}
},
"release-it": {
"version": "14.11.7",
"resolved": "https://registry.npmjs.org/release-it/-/release-it-14.11.7.tgz",
"integrity": "sha512-m4p9+x6AEQPczc96Jyg6dGFeovpJVgRCtA1lxeIgTmQVt9dutYPkkjZeJngZgUJ17/Lb1bx6ZzW2qsKmopKnbQ==",
"version": "14.11.8",
"resolved": "https://registry.npmjs.org/release-it/-/release-it-14.11.8.tgz",
"integrity": "sha512-951DJ0kwjwU7CwGU3BCvRBgLxuJsOPRrZkqx0AsugJdSyPpUdwY9nlU0RAoSKqgh+VTerzecXLIIwgsGIpNxlA==",
"dev": true,
"requires": {
"@iarna/toml": "2.2.5",
"@octokit/rest": "18.10.0",
"@octokit/rest": "18.12.0",
"async-retry": "1.3.3",
"chalk": "4.1.2",
"cosmiconfig": "7.0.1",
@@ -10498,12 +10533,12 @@
"form-data": "4.0.0",
"git-url-parse": "11.6.0",
"globby": "11.0.4",
"got": "11.8.2",
"got": "11.8.3",
"import-cwd": "3.0.0",
"inquirer": "8.1.5",
"is-ci": "3.0.0",
"inquirer": "8.2.0",
"is-ci": "3.0.1",
"lodash": "4.17.21",
"mime-types": "2.1.32",
"mime-types": "2.1.34",
"new-github-release-url": "1.0.0",
"open": "7.4.2",
"ora": "5.4.1",
@@ -10727,9 +10762,9 @@
}
},
"signal-exit": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.5.tgz",
"integrity": "sha512-KWcOiKeQj6ZyXx7zq4YxSMgHRlod4czeBQZrPb8OKcohcqAXShm7E20kEMle9WBt26hFcAf0qLOcp5zmY7kOqQ==",
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz",
"integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==",
"dev": true
},
"sinon": {
@@ -10770,9 +10805,9 @@
"dev": true
},
"source-map-support": {
"version": "0.5.20",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.20.tgz",
"integrity": "sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw==",
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
"dev": true,
"requires": {
"buffer-from": "^1.0.0",
@@ -11016,9 +11051,9 @@
}
},
"typedoc": {
"version": "0.22.9",
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.9.tgz",
"integrity": "sha512-84PjudoXVcap6bwdZFbYIUWlgdz/iLV09ZHwrCzhtHWXaDQG6mlosJ8te6DSThuRkRvQjp46HO+qY/P7Gpm78g==",
"version": "0.22.10",
"resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.10.tgz",
"integrity": "sha512-hQYZ4WtoMZ61wDC6w10kxA42+jclWngdmztNZsDvIz7BMJg7F2xnT+uYsUa7OluyKossdFj9E9Ye4QOZKTy8SA==",
"dev": true,
"requires": {
"glob": "^7.2.0",
@@ -11036,18 +11071,18 @@
"requires": {}
},
"typedoc-plugin-markdown": {
"version": "3.11.6",
"resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.11.6.tgz",
"integrity": "sha512-CV1BuxL7HR/EE1ctnPXOWzf4/Exl0FzkwtFVYaKTVWTnD/dkFLgABOfWuOL4lPmzLUOsAL85pmq+/PB6cdRppw==",
"version": "3.11.7",
"resolved": "https://registry.npmjs.org/typedoc-plugin-markdown/-/typedoc-plugin-markdown-3.11.7.tgz",
"integrity": "sha512-Wm3HP5gcBOGOOTeDA8GLgw+BY+GAI31RP9Lyog21BvTaSeWUcdXls5TG1MK+XDatS2/0dup9gFO+emoyoQJm9Q==",
"dev": true,
"requires": {
"handlebars": "^4.7.7"
}
},
"typescript": {
"version": "4.4.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.4.tgz",
"integrity": "sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA==",
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz",
"integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==",
"dev": true
},
"uglify-js": {

View File

@@ -1,6 +1,6 @@
{
"name": "redis",
"version": "4.0.0-rc.4",
"version": "4.0.0",
"license": "MIT",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -17,14 +17,14 @@
"build-all": "npm run build:client && npm run build:test-utils && npm run build:modules && npm run build"
},
"dependencies": {
"@node-redis/client": "^1.0.0-rc.0",
"@node-redis/json": "^1.0.0-rc.0",
"@node-redis/search": "^1.0.0-rc.0"
"@node-redis/client": "^1.0.0",
"@node-redis/json": "^1.0.0",
"@node-redis/search": "^1.0.0"
},
"devDependencies": {
"@tsconfig/node12": "^1.0.9",
"release-it": "^14.11.7",
"typescript": "^4.4.4"
"release-it": "^14.11.8",
"typescript": "^4.5.2"
},
"repository": {
"type": "git",

View File

@@ -1,6 +1,6 @@
# Changelog
## v4.0.0
## v4.0.0 - 24 Nov, 2021
This version is a major change and refactor, adding modern JavaScript capabilities and multiple breaking changes. See the [migration guide](../../docs/v3-to-v4.md) for tips on how to upgrade.
@@ -17,10 +17,10 @@ This version is a major change and refactor, adding modern JavaScript capabiliti
- Added support for Promises
- Added built-in TypeScript declaration files enabling code completion
- Added support for [clustering](../../.github/README.md#cluster)
- Added idiomatic arguments and responses to [Redis commands](../../.github/README.md#redis-commands)
- Added full support for [Lua Scripts](../../.github/README.md#lua-scripts)
- Added support for [SCAN iterators](../../.github/README.md#scan-iterator)
- Added support for [clustering](../../README.md#cluster)
- Added idiomatic arguments and responses to [Redis commands](../../README.md#redis-commands)
- Added full support for [Lua Scripts](../../README.md#lua-scripts)
- Added support for [SCAN iterators](../../README.md#scan-iterator)
- Added the ability to extend Node Redis with Redis Module commands
## v3.1.2

View File

@@ -1,2 +1,2 @@
# @node-redis/client
The sources and docs for this package are in the main [node-redis](https://github.com/redis/node-redis) repo.
The source code and documentation for this package are in the main [node-redis](https://github.com/redis/node-redis) repo.

View File

@@ -1,18 +1,15 @@
import * as LinkedList from 'yallist';
import { AbortError } from '../errors';
import { RedisCommandArguments, RedisCommandRawReply } from '../commands';
// We need to use 'require', because it's not possible with Typescript to import
// classes that are exported as 'module.exports = class`, without esModuleInterop
// set to true.
const RedisParser = require('redis-parser');
export interface QueueCommandOptions {
asap?: boolean;
chainId?: symbol;
signal?: AbortSignal;
}
interface CommandWaitingToBeSent extends CommandWaitingForReply {
args: RedisCommandArguments;
chainId?: symbol;
@@ -21,27 +18,44 @@ interface CommandWaitingToBeSent extends CommandWaitingForReply {
listener(): void;
};
}
interface CommandWaitingForReply {
resolve(reply?: unknown): void;
reject(err: Error): void;
channelsCounter?: number;
bufferMode?: boolean;
}
export enum PubSubSubscribeCommands {
SUBSCRIBE = 'SUBSCRIBE',
PSUBSCRIBE = 'PSUBSCRIBE'
}
export enum PubSubUnsubscribeCommands {
UNSUBSCRIBE = 'UNSUBSCRIBE',
PUNSUBSCRIBE = 'PUNSUBSCRIBE'
}
export type PubSubListener = (message: string, channel: string) => unknown;
type PubSubArgumentTypes = Buffer | string;
export type PubSubListenersMap = Map<string, Set<PubSubListener>>;
export type PubSubListener<
BUFFER_MODE extends boolean = false,
T = BUFFER_MODE extends true ? Buffer : string
> = (message: T, channel: T) => unknown;
interface PubSubListeners {
buffers: Set<PubSubListener<true>>;
strings: Set<PubSubListener<false>>;
}
type PubSubListenersMap = Map<string, PubSubListeners>;
interface PubSubState {
subscribing: number;
subscribed: number;
unsubscribing: number;
listeners: {
channels: PubSubListenersMap;
patterns: PubSubListenersMap;
};
}
export default class RedisCommandsQueue {
static #flushQueue<T extends CommandWaitingForReply>(queue: LinkedList<T>, err: Error): void {
@@ -50,53 +64,64 @@ export default class RedisCommandsQueue {
}
}
static #emitPubSubMessage(listeners: Set<PubSubListener>, message: string, channel: string): void {
for (const listener of listeners) {
static #emitPubSubMessage(listenersMap: PubSubListenersMap, message: Buffer, channel: Buffer, pattern?: Buffer): void {
const keyString = (pattern || channel).toString(),
listeners = listenersMap.get(keyString)!;
for (const listener of listeners.buffers) {
listener(message, channel);
}
if (!listeners.strings.size) return;
const messageString = message.toString(),
channelString = pattern ? channel.toString() : keyString;
for (const listener of listeners.strings) {
listener(messageString, channelString);
}
}
readonly #maxLength: number | null | undefined;
readonly #waitingToBeSent = new LinkedList<CommandWaitingToBeSent>();
readonly #waitingForReply = new LinkedList<CommandWaitingForReply>();
readonly #pubSubState = {
subscribing: 0,
subscribed: 0,
unsubscribing: 0
};
#pubSubState: PubSubState | undefined;
readonly #pubSubListeners = {
channels: <PubSubListenersMap>new Map(),
patterns: <PubSubListenersMap>new Map()
static readonly #PUB_SUB_MESSAGES = {
message: Buffer.from('message'),
pMessage: Buffer.from('pmessage'),
subscribe: Buffer.from('subscribe'),
pSubscribe: Buffer.from('psubscribe'),
unsubscribe: Buffer.from('unsunscribe'),
pUnsubscribe: Buffer.from('punsubscribe')
};
readonly #parser = new RedisParser({
returnReply: (reply: unknown) => {
if ((this.#pubSubState.subscribing || this.#pubSubState.subscribed) && Array.isArray(reply)) {
switch (reply[0]) {
case 'message':
return RedisCommandsQueue.#emitPubSubMessage(
this.#pubSubListeners.channels.get(reply[1])!,
reply[2],
reply[1]
);
case 'pmessage':
return RedisCommandsQueue.#emitPubSubMessage(
this.#pubSubListeners.patterns.get(reply[1])!,
reply[3],
reply[2]
);
case 'subscribe':
case 'psubscribe':
if (--this.#waitingForReply.head!.value.channelsCounter! === 0) {
this.#shiftWaitingForReply().resolve();
}
return;
if (this.#pubSubState && Array.isArray(reply)) {
if (RedisCommandsQueue.#PUB_SUB_MESSAGES.message.equals(reply[0])) {
return RedisCommandsQueue.#emitPubSubMessage(
this.#pubSubState.listeners.channels,
reply[2],
reply[1]
);
} else if (RedisCommandsQueue.#PUB_SUB_MESSAGES.pMessage.equals(reply[0])) {
return RedisCommandsQueue.#emitPubSubMessage(
this.#pubSubState.listeners.patterns,
reply[3],
reply[2],
reply[1]
);
} else if (
RedisCommandsQueue.#PUB_SUB_MESSAGES.subscribe.equals(reply[0]) ||
RedisCommandsQueue.#PUB_SUB_MESSAGES.pSubscribe.equals(reply[0]) ||
RedisCommandsQueue.#PUB_SUB_MESSAGES.unsubscribe.equals(reply[0]) ||
RedisCommandsQueue.#PUB_SUB_MESSAGES.pUnsubscribe.equals(reply[0])
) {
if (--this.#waitingForReply.head!.value.channelsCounter! === 0) {
this.#shiftWaitingForReply().resolve();
}
return;
}
}
@@ -104,29 +129,26 @@ export default class RedisCommandsQueue {
},
returnError: (err: Error) => this.#shiftWaitingForReply().reject(err)
});
#chainInExecution: symbol | undefined;
constructor(maxLength: number | null | undefined) {
this.#maxLength = maxLength;
}
addCommand<T = RedisCommandRawReply>(args: RedisCommandArguments, options?: QueueCommandOptions, bufferMode?: boolean): Promise<T> {
if (this.#pubSubState.subscribing || this.#pubSubState.subscribed) {
if (this.#pubSubState) {
return Promise.reject(new Error('Cannot send commands in PubSub mode'));
} else if (this.#maxLength && this.#waitingToBeSent.length + this.#waitingForReply.length >= this.#maxLength) {
return Promise.reject(new Error('The queue is full'));
} else if (options?.signal?.aborted) {
return Promise.reject(new AbortError());
}
return new Promise((resolve, reject) => {
const node = new LinkedList.Node<CommandWaitingToBeSent>({
args,
chainId: options?.chainId,
bufferMode,
resolve,
reject,
reject
});
if (options?.signal) {
@@ -134,7 +156,6 @@ export default class RedisCommandsQueue {
this.#waitingToBeSent.removeNode(node);
node.value.reject(new AbortError());
};
node.value.abort = {
signal: options.signal,
listener
@@ -144,7 +165,6 @@ export default class RedisCommandsQueue {
once: true
});
}
if (options?.asap) {
this.#waitingToBeSent.unshiftNode(node);
} else {
@@ -153,28 +173,63 @@ export default class RedisCommandsQueue {
});
}
subscribe(command: PubSubSubscribeCommands, channels: string | Array<string>, listener: PubSubListener): Promise<void> {
const channelsToSubscribe: Array<string> = [],
listeners = command === PubSubSubscribeCommands.SUBSCRIBE ? this.#pubSubListeners.channels : this.#pubSubListeners.patterns;
#initiatePubSubState(): PubSubState {
return this.#pubSubState ??= {
subscribed: 0,
subscribing: 0,
unsubscribing: 0,
listeners: {
channels: new Map(),
patterns: new Map()
}
};
}
subscribe<T extends boolean>(
command: PubSubSubscribeCommands,
channels: PubSubArgumentTypes | Array<PubSubArgumentTypes>,
listener: PubSubListener<T>,
bufferMode?: T
): Promise<void> {
const pubSubState = this.#initiatePubSubState(),
channelsToSubscribe: Array<PubSubArgumentTypes> = [],
listenersMap = command === PubSubSubscribeCommands.SUBSCRIBE ? pubSubState.listeners.channels : pubSubState.listeners.patterns;
for (const channel of (Array.isArray(channels) ? channels : [channels])) {
if (listeners.has(channel)) {
listeners.get(channel)!.add(listener);
continue;
const channelString = typeof channel === 'string' ? channel : channel.toString();
let listeners = listenersMap.get(channelString);
if (!listeners) {
listeners = {
buffers: new Set(),
strings: new Set()
};
listenersMap.set(channelString, listeners);
channelsToSubscribe.push(channel);
}
listeners.set(channel, new Set([listener]));
channelsToSubscribe.push(channel);
// https://github.com/microsoft/TypeScript/issues/23132
(bufferMode ? listeners.buffers : listeners.strings).add(listener as any);
}
if (!channelsToSubscribe.length) {
return Promise.resolve();
}
return this.#pushPubSubCommand(command, channelsToSubscribe);
}
unsubscribe(command: PubSubUnsubscribeCommands, channels?: string | Array<string>, listener?: PubSubListener): Promise<void> {
const listeners = command === PubSubUnsubscribeCommands.UNSUBSCRIBE ? this.#pubSubListeners.channels : this.#pubSubListeners.patterns;
unsubscribe<T extends boolean>(
command: PubSubUnsubscribeCommands,
channels?: string | Array<string>,
listener?: PubSubListener<T>,
bufferMode?: T
): Promise<void> {
if (!this.#pubSubState) {
return Promise.resolve();
}
const listeners = command === PubSubUnsubscribeCommands.UNSUBSCRIBE ?
this.#pubSubState.listeners.channels :
this.#pubSubState.listeners.patterns;
if (!channels) {
const size = listeners.size;
listeners.clear();
@@ -183,13 +238,16 @@ export default class RedisCommandsQueue {
const channelsToUnsubscribe = [];
for (const channel of (Array.isArray(channels) ? channels : [channels])) {
const set = listeners.get(channel);
if (!set) continue;
const sets = listeners.get(channel);
if (!sets) continue;
let shouldUnsubscribe = !listener;
let shouldUnsubscribe;
if (listener) {
set.delete(listener);
shouldUnsubscribe = set.size === 0;
// https://github.com/microsoft/TypeScript/issues/23132
(bufferMode ? sets.buffers : sets.strings).delete(listener as any);
shouldUnsubscribe = !sets.buffers.size && !sets.strings.size;
} else {
shouldUnsubscribe = true;
}
if (shouldUnsubscribe) {
@@ -197,19 +255,18 @@ export default class RedisCommandsQueue {
listeners.delete(channel);
}
}
if (!channelsToUnsubscribe.length) {
return Promise.resolve();
}
return this.#pushPubSubCommand(command, channelsToUnsubscribe);
}
#pushPubSubCommand(command: PubSubSubscribeCommands | PubSubUnsubscribeCommands, channels: number | Array<string>): Promise<void> {
#pushPubSubCommand(command: PubSubSubscribeCommands | PubSubUnsubscribeCommands, channels: number | Array<PubSubArgumentTypes>): Promise<void> {
return new Promise((resolve, reject) => {
const isSubscribe = command === PubSubSubscribeCommands.SUBSCRIBE || command === PubSubSubscribeCommands.PSUBSCRIBE,
const pubSubState = this.#initiatePubSubState(),
isSubscribe = command === PubSubSubscribeCommands.SUBSCRIBE || command === PubSubSubscribeCommands.PSUBSCRIBE,
inProgressKey = isSubscribe ? 'subscribing' : 'unsubscribing',
commandArgs: Array<string> = [command];
commandArgs: Array<PubSubArgumentTypes> = [command];
let channelsCounter: number;
if (typeof channels === 'number') { // unsubscribe only
@@ -219,18 +276,26 @@ export default class RedisCommandsQueue {
channelsCounter = channels.length;
}
this.#pubSubState[inProgressKey] += channelsCounter;
pubSubState[inProgressKey] += channelsCounter;
this.#waitingToBeSent.push({
args: commandArgs,
channelsCounter,
bufferMode: true,
resolve: () => {
this.#pubSubState[inProgressKey] -= channelsCounter;
this.#pubSubState.subscribed += channelsCounter * (isSubscribe ? 1 : -1);
pubSubState[inProgressKey] -= channelsCounter;
if (isSubscribe) {
pubSubState.subscribed += channelsCounter;
} else {
pubSubState.subscribed -= channelsCounter;
if (!pubSubState.subscribed && !pubSubState.subscribing && !pubSubState.subscribed) {
this.#pubSubState = undefined;
}
}
resolve();
},
reject: () => {
this.#pubSubState[inProgressKey] -= channelsCounter;
pubSubState[inProgressKey] -= channelsCounter * (isSubscribe ? 1 : -1);
reject();
}
});
@@ -238,22 +303,19 @@ export default class RedisCommandsQueue {
}
resubscribe(): Promise<any> | undefined {
if (!this.#pubSubState.subscribed && !this.#pubSubState.subscribing) {
if (!this.#pubSubState) {
return;
}
this.#pubSubState.subscribed = this.#pubSubState.subscribing = 0;
// TODO: acl error on one channel/pattern will reject the whole command
return Promise.all([
this.#pushPubSubCommand(PubSubSubscribeCommands.SUBSCRIBE, [...this.#pubSubListeners.channels.keys()]),
this.#pushPubSubCommand(PubSubSubscribeCommands.PSUBSCRIBE, [...this.#pubSubListeners.patterns.keys()])
this.#pushPubSubCommand(PubSubSubscribeCommands.SUBSCRIBE, [...this.#pubSubState.listeners.channels.keys()]),
this.#pushPubSubCommand(PubSubSubscribeCommands.PSUBSCRIBE, [...this.#pubSubState.listeners.patterns.keys()])
]);
}
getCommandToSend(): RedisCommandArguments | undefined {
const toSend = this.#waitingToBeSent.shift();
if (toSend) {
this.#waitingForReply.push({
resolve: toSend.resolve,
@@ -262,14 +324,15 @@ export default class RedisCommandsQueue {
bufferMode: toSend.bufferMode
});
}
this.#chainInExecution = toSend?.chainId;
return toSend?.args;
}
parseResponse(data: Buffer): void {
this.#parser.setReturnBuffers(!!this.#waitingForReply.head?.value.bufferMode);
this.#parser.setReturnBuffers(
!!this.#waitingForReply.head?.value.bufferMode ||
!!this.#pubSubState?.subscribed
);
this.#parser.execute(data);
}
@@ -277,24 +340,18 @@ export default class RedisCommandsQueue {
if (!this.#waitingForReply.length) {
throw new Error('Got an unexpected reply from Redis');
}
return this.#waitingForReply.shift()!;
}
flushWaitingForReply(err: Error): void {
RedisCommandsQueue.#flushQueue(this.#waitingForReply, err);
if (!this.#chainInExecution) {
return;
}
while (this.#waitingToBeSent.head?.value.chainId === this.#chainInExecution) {
this.#waitingToBeSent.shift();
}
this.#chainInExecution = undefined;
}
flushAll(err: Error): void {
RedisCommandsQueue.#flushQueue(this.#waitingForReply, err);
RedisCommandsQueue.#flushQueue(this.#waitingToBeSent, err);

View File

@@ -561,63 +561,66 @@ describe('Client', () => {
}, GLOBAL.SERVERS.OPEN);
testUtils.testWithClient('PubSub', async publisher => {
function assertStringListener(message: string, channel: string) {
assert.ok(typeof message === 'string');
assert.ok(typeof channel === 'string');
}
function assertBufferListener(message: Buffer, channel: Buffer) {
assert.ok(Buffer.isBuffer(message));
assert.ok(Buffer.isBuffer(channel));
}
const subscriber = publisher.duplicate();
await subscriber.connect();
try {
const channelListener1 = spy(),
channelListener2 = spy(),
patternListener = spy();
const channelListener1 = spy(assertBufferListener),
channelListener2 = spy(assertStringListener),
patternListener = spy(assertStringListener);
await Promise.all([
subscriber.subscribe('channel', channelListener1),
subscriber.subscribe('channel', channelListener1, true),
subscriber.subscribe('channel', channelListener2),
subscriber.pSubscribe('channel*', patternListener)
]);
await Promise.all([
waitTillBeenCalled(channelListener1),
waitTillBeenCalled(channelListener2),
waitTillBeenCalled(patternListener),
publisher.publish('channel', 'message')
publisher.publish(Buffer.from('channel'), Buffer.from('message'))
]);
assert.ok(channelListener1.calledOnceWithExactly('message', 'channel'));
assert.ok(channelListener1.calledOnceWithExactly(Buffer.from('message'), Buffer.from('channel')));
assert.ok(channelListener2.calledOnceWithExactly('message', 'channel'));
assert.ok(patternListener.calledOnceWithExactly('message', 'channel'));
await subscriber.unsubscribe('channel', channelListener1);
await subscriber.unsubscribe('channel', channelListener1, true);
await Promise.all([
waitTillBeenCalled(channelListener2),
waitTillBeenCalled(patternListener),
publisher.publish('channel', 'message')
]);
assert.ok(channelListener1.calledOnce);
assert.ok(channelListener2.calledTwice);
assert.ok(channelListener2.secondCall.calledWithExactly('message', 'channel'));
assert.ok(patternListener.calledTwice);
assert.ok(patternListener.secondCall.calledWithExactly('message', 'channel'));
await subscriber.unsubscribe('channel');
await Promise.all([
waitTillBeenCalled(patternListener),
publisher.publish('channel', 'message')
]);
assert.ok(channelListener1.calledOnce);
assert.ok(channelListener2.calledTwice);
assert.ok(patternListener.calledThrice);
assert.ok(patternListener.thirdCall.calledWithExactly('message', 'channel'));
await subscriber.pUnsubscribe();
await publisher.publish('channel', 'message');
assert.ok(channelListener1.calledOnce);
assert.ok(channelListener2.calledTwice);
assert.ok(patternListener.calledThrice);
// should be able to send commands when unsubsribed from all channels (see #1652)
await assert.doesNotReject(subscriber.ping());
} finally {

View File

@@ -388,42 +388,93 @@ export default class RedisClient<M extends RedisModules, S extends RedisScripts>
select = this.SELECT;
SUBSCRIBE(channels: string | Array<string>, listener: PubSubListener): Promise<void> {
return this.#subscribe(PubSubSubscribeCommands.SUBSCRIBE, channels, listener);
#subscribe<T extends boolean>(
command: PubSubSubscribeCommands,
channels: string | Array<string>,
listener: PubSubListener<T>,
bufferMode?: T
): Promise<void> {
const promise = this.#queue.subscribe(
command,
channels,
listener,
bufferMode
);
this.#tick();
return promise;
}
SUBSCRIBE<T extends boolean = false>(
channels: string | Array<string>,
listener: PubSubListener<T>,
bufferMode?: T
): Promise<void> {
return this.#subscribe(
PubSubSubscribeCommands.SUBSCRIBE,
channels,
listener,
bufferMode
);
}
subscribe = this.SUBSCRIBE;
PSUBSCRIBE(patterns: string | Array<string>, listener: PubSubListener): Promise<void> {
return this.#subscribe(PubSubSubscribeCommands.PSUBSCRIBE, patterns, listener);
PSUBSCRIBE<T extends boolean = false>(
patterns: string | Array<string>,
listener: PubSubListener<T>,
bufferMode?: T
): Promise<void> {
return this.#subscribe(
PubSubSubscribeCommands.PSUBSCRIBE,
patterns,
listener,
bufferMode
);
}
pSubscribe = this.PSUBSCRIBE;
#subscribe(command: PubSubSubscribeCommands, channels: string | Array<string>, listener: PubSubListener): Promise<void> {
const promise = this.#queue.subscribe(command, channels, listener);
#unsubscribe<T extends boolean>(
command: PubSubUnsubscribeCommands,
channels?: string | Array<string>,
listener?: PubSubListener<T>,
bufferMode?: T
): Promise<void> {
const promise = this.#queue.unsubscribe(command, channels, listener, bufferMode);
this.#tick();
return promise;
}
UNSUBSCRIBE(channels?: string | Array<string>, listener?: PubSubListener): Promise<void> {
return this.#unsubscribe(PubSubUnsubscribeCommands.UNSUBSCRIBE, channels, listener);
UNSUBSCRIBE<T extends boolean = false>(
channels?: string | Array<string>,
listener?: PubSubListener<T>,
bufferMode?: T
): Promise<void> {
return this.#unsubscribe(
PubSubUnsubscribeCommands.UNSUBSCRIBE,
channels,
listener,
bufferMode
);
}
unsubscribe = this.UNSUBSCRIBE;
PUNSUBSCRIBE(patterns?: string | Array<string>, listener?: PubSubListener): Promise<void> {
return this.#unsubscribe(PubSubUnsubscribeCommands.PUNSUBSCRIBE, patterns, listener);
PUNSUBSCRIBE<T extends boolean = false>(
patterns?: string | Array<string>,
listener?: PubSubListener<T>,
bufferMode?: T
): Promise<void> {
return this.#unsubscribe(
PubSubUnsubscribeCommands.PUNSUBSCRIBE,
patterns,
listener,
bufferMode
);
}
pUnsubscribe = this.PUNSUBSCRIBE;
#unsubscribe(command: PubSubUnsubscribeCommands, channels?: string | Array<string>, listener?: PubSubListener): Promise<void> {
const promise = this.#queue.unsubscribe(command, channels, listener);
this.#tick();
return promise;
}
QUIT(): Promise<void> {
return this.#socket.quit(() => {
const quitPromise = this.#queue.addCommand(['QUIT']);

View File

@@ -42,20 +42,8 @@ export default class RedisClusterSlots<M extends RedisModules, S extends RedisSc
throw new Error('None of the root nodes is available');
}
async discover(startWith: RedisClientType<M, S>): Promise<void> {
if (await this.#discoverNodes(startWith.options)) return;
for (const { client } of this.#nodeByUrl.values()) {
if (client === startWith) continue;
if (await this.#discoverNodes(client.options)) return;
}
throw new Error('None of the cluster nodes is available');
}
async #discoverNodes(clientOptions?: RedisClusterClientOptions): Promise<boolean> {
const client = new this.#Client(clientOptions);
const client = this.#initiateClient(clientOptions);
await client.connect();
@@ -72,6 +60,29 @@ export default class RedisClusterSlots<M extends RedisModules, S extends RedisSc
}
}
#runningRediscoverPromise?: Promise<void>;
async rediscover(startWith: RedisClientType<M, S>): Promise<void> {
if (!this.#runningRediscoverPromise) {
this.#runningRediscoverPromise = this.#rediscover(startWith)
.finally(() => this.#runningRediscoverPromise = undefined);
}
return this.#runningRediscoverPromise;
}
async #rediscover(startWith: RedisClientType<M, S>): Promise<void> {
if (await this.#discoverNodes(startWith.options)) return;
for (const { client } of this.#nodeByUrl.values()) {
if (client === startWith) continue;
if (await this.#discoverNodes(client.options)) return;
}
throw new Error('None of the cluster nodes is available');
}
async #reset(masters: Array<RedisClusterMasterNode>): Promise<void> {
// Override this.#slots and add not existing clients to this.#nodeByUrl
const promises: Array<Promise<void>> = [],
@@ -103,18 +114,23 @@ export default class RedisClusterSlots<M extends RedisModules, S extends RedisSc
await Promise.all(promises);
}
#clientOptionsDefaults(options: RedisClusterClientOptions): RedisClusterClientOptions {
#clientOptionsDefaults(options?: RedisClusterClientOptions): RedisClusterClientOptions | undefined {
if (!this.#options.defaults) return options;
const merged = Object.assign({}, this.#options.defaults, options);
if (options.socket && this.#options.defaults.socket) {
if (options?.socket && this.#options.defaults.socket) {
Object.assign({}, this.#options.defaults.socket, options.socket);
}
return merged;
}
#initiateClient(options?: RedisClusterClientOptions): RedisClientType<M, S> {
return new this.#Client(this.#clientOptionsDefaults(options))
.on('error', this.#onError);
}
#initiateClientForNode(nodeData: RedisClusterMasterNode | RedisClusterReplicaNode, readonly: boolean, clientsInUse: Set<string>, promises: Array<Promise<void>>): ClusterNode<M, S> {
const url = `${nodeData.host}:${nodeData.port}`;
clientsInUse.add(url);
@@ -123,15 +139,13 @@ export default class RedisClusterSlots<M extends RedisModules, S extends RedisSc
if (!node) {
node = {
id: nodeData.id,
client: new this.#Client(
this.#clientOptionsDefaults({
socket: {
host: nodeData.host,
port: nodeData.port
},
readonly
})
)
client: this.#initiateClient({
socket: {
host: nodeData.host,
port: nodeData.port
},
readonly
})
};
promises.push(node.client.connect());
this.#nodeByUrl.set(url, node);

View File

@@ -157,7 +157,7 @@ export default class RedisCluster<M extends RedisModules = Record<string, never>
const url = err.message.substring(err.message.lastIndexOf(' ') + 1);
let node = this.#slots.getNodeByUrl(url);
if (!node) {
await this.#slots.discover(client);
await this.#slots.rediscover(client);
node = this.#slots.getNodeByUrl(url);
if (!node) {
@@ -168,7 +168,7 @@ export default class RedisCluster<M extends RedisModules = Record<string, never>
await node.client.asking();
return node.client;
} else if (err.message.startsWith('MOVED')) {
await this.#slots.discover(client);
await this.#slots.rediscover(client);
return true;
}

View File

@@ -48,6 +48,31 @@ describe('CLUSTER NODES', () => {
);
});
it('should support urls without cport', () => {
assert.deepEqual(
transformReply(
'id 127.0.0.1:30001 master - 0 0 0 connected 0-16384\n'
),
[{
id: 'id',
url: '127.0.0.1:30001',
host: '127.0.0.1',
port: 30001,
cport: null,
flags: ['master'],
pingSent: 0,
pongRecv: 0,
configEpoch: 0,
linkState: RedisClusterNodeLinkStates.CONNECTED,
slots: [{
from: 0,
to: 16384
}],
replicas: []
}]
);
});
it.skip('with importing slots', () => {
assert.deepEqual(
transformReply(

View File

@@ -10,7 +10,7 @@ export enum RedisClusterNodeLinkStates {
interface RedisClusterNodeTransformedUrl {
host: string;
port: number;
cport: number;
cport: number | null;
}
export interface RedisClusterReplicaNode extends RedisClusterNodeTransformedUrl {
@@ -86,7 +86,16 @@ export function transformReply(reply: string): Array<RedisClusterMasterNode> {
function transformNodeUrl(url: string): RedisClusterNodeTransformedUrl {
const indexOfColon = url.indexOf(':'),
indexOfAt = url.indexOf('@', indexOfColon);
indexOfAt = url.indexOf('@', indexOfColon),
host = url.substring(0, indexOfColon);
if (indexOfAt === -1) {
return {
host,
port: Number(url.substring(indexOfColon + 1)),
cport: null
};
}
return {
host: url.substring(0, indexOfColon),

View File

@@ -1,26 +1,36 @@
import { strict as assert } from 'assert';
import testUtils, { GLOBAL } from '../test-utils';
import { transformArguments } from './LINDEX';
describe('LINDEX', () => {
it('transformArguments', () => {
assert.deepEqual(
transformArguments('key', 'element'),
['LINDEX', 'key', 'element']
transformArguments('key', 0),
['LINDEX', 'key', '0']
);
});
testUtils.testWithClient('client.lIndex', async client => {
assert.equal(
await client.lIndex('key', 'element'),
null
);
}, GLOBAL.SERVERS.OPEN);
describe('client.lIndex', () => {
testUtils.testWithClient('null', async client => {
assert.equal(
await client.lIndex('key', 0),
null
);
}, GLOBAL.SERVERS.OPEN);
testUtils.testWithClient('with value', async client => {
const [, lIndexReply] = await Promise.all([
client.lPush('key', 'element'),
client.lIndex('key', 0)
]);
assert.equal(lIndexReply, 'element');
}, GLOBAL.SERVERS.OPEN);
});
testUtils.testWithCluster('cluster.lIndex', async cluster => {
assert.equal(
await cluster.lIndex('key', 'element'),
await cluster.lIndex('key', 0),
null
);
}, GLOBAL.CLUSTERS.OPEN);
});
});

View File

@@ -1,9 +1,8 @@
export const FIRST_KEY_INDEX = 1;
export const IS_READ_ONLY = true;
export function transformArguments(key: string, element: string): Array<string> {
return ['LINDEX', key, element];
export function transformArguments(key: string, index: number): Array<string> {
return ['LINDEX', key, index.toString()];
}
export declare function transformReply(): string | null;
export declare function transformReply(): string | null;

View File

@@ -1,4 +1,6 @@
export function transformArguments(channel: string, message: string): Array<string> {
import { RedisCommandArguments } from '.';
export function transformArguments(channel: string | Buffer, message: string | Buffer): RedisCommandArguments {
return ['PUBLISH', channel, message];
}

View File

@@ -1,6 +1,6 @@
{
"name": "@node-redis/client",
"version": "1.0.0-rc.0",
"version": "1.0.0",
"license": "MIT",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -19,22 +19,22 @@
"devDependencies": {
"@istanbuljs/nyc-config-typescript": "^1.0.1",
"@node-redis/test-utils": "*",
"@types/node": "^16.11.7",
"@types/node": "^16.11.10",
"@types/redis-parser": "^3.0.0",
"@types/sinon": "^10.0.6",
"@types/yallist": "^4.0.1",
"@typescript-eslint/eslint-plugin": "^5.4.0",
"@typescript-eslint/parser": "^5.4.0",
"eslint": "^8.2.0",
"eslint": "^8.3.0",
"nyc": "^15.1.0",
"release-it": "^14.11.7",
"release-it": "^14.11.8",
"sinon": "^12.0.1",
"source-map-support": "^0.5.20",
"source-map-support": "^0.5.21",
"ts-node": "^10.4.0",
"typedoc": "^0.22.9",
"typedoc": "^0.22.10",
"typedoc-github-wiki-theme": "^0.6.0",
"typedoc-plugin-markdown": "^3.11.6",
"typescript": "^4.4.4"
"typedoc-plugin-markdown": "^3.11.7",
"typescript": "^4.5.2"
},
"engines": {
"node": ">=12"

View File

@@ -1,2 +1,80 @@
# @node-redis/json
The sources and docs for this package are in the main [node-redis](https://github.com/redis/node-redis) repo.
This package provides support for the [RedisJSON](https://redisjson.io) module, which adds JSON as a native data type to Redis. It extends the [Node Redis client](https://github.com/redis/node-redis) to include functions for each of the RedisJSON commands.
To use these extra commands, your Redis server must have the RedisJSON module installed.
## Usage
For a complete example, see [`managing-json.js`](https://github.com/redis/node-redis/blob/master/examples/managing-json.js) in the Node Redis examples folder.
### Storing JSON Documents in Redis
The [`JSON.SET`](https://oss.redis.com/redisjson/commands/#jsonset) command stores a JSON value at a given JSON Path in a Redis key.
Here, we'll store a JSON document in the root of the Redis key "`mydoc`":
```javascript
import { createClient } from 'redis';
...
await client.json.set('noderedis:jsondata', '$', {
name: 'Roberta McDonald',
pets: [
{
name: 'Rex',
species: 'dog',
age: 3,
isMammal: true
},
{
name: 'Goldie',
species: 'fish',
age: 2,
isMammal: false
}
]
});
```
For more information about RedisJSON's path syntax, [check out the documentation](https://oss.redis.com/redisjson/path/).
### Retrieving JSON Documents from Redis
With RedisJSON, we can retrieve all or part(s) of a JSON document using the [`JSON.GET`]() command and one or more JSON Paths. Let's get the name and age of one of the pets:
```javascript
const results = await client.json.get('noderedis:jsondata', {
path: [
'.pets[1].name',
'.pets[1].age'
]
});
```
`results` will contain the following:
```javascript
{ '.pets[1].name': 'Goldie', '.pets[1].age': 2 }
```
### Performing Atomic Updates on JSON Documents Stored in Redis
RedisJSON includes commands that can atomically update values in a JSON document, in place in Redis without having to first retrieve the entire document.
Using the [`JSON.NUMINCRBY`](https://oss.redis.com/redisjson/commands/#jsonnumincrby) command, we can update the age of one of the pets like this:
```javascript
await client.json.numIncrBy('noderedis:jsondata', '.pets[1].age', 1);
```
And we can add a new object to the pets array with the [`JSON.ARRAPPEND`](https://oss.redis.com/redisjson/commands/#jsonarrappend) command:
```javascript
await client.json.arrAppend('noderedis:jsondata', '.pets', {
name: 'Robin',
species: 'bird',
age: 1,
isMammal: false
});
```

View File

@@ -14,4 +14,4 @@ export function transformArguments(key: string, path?: string, index?: number):
return args;
}
export { transformRedisJsonNullArrayReply as transformReply } from '.';
export { transformRedisJsonNullArrayNullReply as transformReply } from '.';

View File

@@ -84,8 +84,9 @@ export function transformRedisJsonNullReply(json: string | null): RedisJSON | nu
return transformRedisJsonReply(json);
}
export function transformRedisJsonNullArrayNullReply(jsons: Array<string | null> | null): Array<RedisJSON | null> | null {
if (jsons === null) return null;
export function transformRedisJsonNullArrayReply(jsons: Array<string | null>): Array<RedisJSON | null> {
return jsons.map(transformRedisJsonNullReply);
}

View File

@@ -9,16 +9,16 @@
"build": "tsc"
},
"peerDependencies": {
"@node-redis/client": "^1.0.0-rc"
"@node-redis/client": "^1.0.0"
},
"devDependencies": {
"@istanbuljs/nyc-config-typescript": "^1.0.1",
"@node-redis/test-utils": "*",
"@types/node": "^16.11.7",
"@types/node": "^16.11.10",
"nyc": "^15.1.0",
"release-it": "^14.11.7",
"source-map-support": "^0.5.20",
"release-it": "^14.11.8",
"source-map-support": "^0.5.21",
"ts-node": "^10.4.0",
"typescript": "^4.4.4"
"typescript": "^4.5.2"
}
}

View File

@@ -1,2 +1,120 @@
# @node-redis/search
The sources and docs for this package are in the main [node-redis](https://github.com/redis/node-redis) repo.
This package provides support for the [RediSearch](https://redisearch.io) module, which adds indexing and querying support for data stored in Redis Hashes or as JSON documents with the RedisJSON module. It extends the [Node Redis client](https://github.com/redis/node-redis) to include functions for each of the RediSearch commands.
To use these extra commands, your Redis server must have the RediSearch module installed. To index and query JSON documents, you'll also need to add the RedisJSON module.
## Usage
For complete examples, see [`search-hashes.js`](https://github.com/redis/node-redis/blob/master/examples/search-hashes.js) and [`search-json.js`](https://github.com/redis/node-redis/blob/master/examples/search-json.js) in the Node Redis examples folder.
### Indexing and Querying Data in Redis Hashes
#### Creating an Index
Before we can perform any searches, we need to tell RediSearch how to index our data, and which Redis keys to find that data in. The [FT.CREATE](https://oss.redis.com/redisearch/Commands/#ftcreate) command creates a RediSearch index. Here's how to use it to create an index we'll call `idx:animals` where we want to index hashes containing `name`, `species` and `age` fields, and whose key names in Redis begin with the prefix `noderedis:animals`:
```javascript
await client.ft.create('idx:animals', {
name: {
type: SchemaFieldTypes.TEXT,
sortable: true
},
species: SchemaFieldTypes.TAG,
age: SchemaFieldTypes.NUMERIC
}, {
ON: 'HASH',
PREFIX: 'noderedis:animals'
}
);
```
See the [`FT.CREATE` documentation](https://oss.redis.com/redisearch/Commands/#ftcreate) for information about the different field types and additional options.
#### Querying the Index
Once we've created an index, and added some data to Redis hashes whose keys begin with the prefix `noderedis:animals`, we can start writing some search queries. RediSearch supports a rich query syntax for full-text search, faceted search, aggregation and more. Check out the [`FT.SEARCH` documentation](https://oss.redis.com/redisearch/Commands/#ftsearch) and the [query syntax reference](https://oss.redis.com/redisearch/Query_Syntax/) for more information.
Let's write a query to find all the animals where the `species` field has the value `dog`:
```javascript
const results = await client.ft.search('idx:animals', '@species:{dog}');
```
`results` looks like this:
```javascript
{
total: 2,
documents: [
{
id: 'noderedis:animals:4',
value: {
name: 'Fido',
species: 'dog',
age: '7'
}
},
{
id: 'noderedis:animals:3',
value: {
name: 'Rover',
species: 'dog',
age: '9'
}
}
]
}
```
### Indexing and Querying Data with RedisJSON
RediSearch can also index and query JSON documents stored in Redis using the RedisJSON module. The approach is similar to that for indexing and searching data in hashes, but we can now use JSON Path like syntax and the data no longer has to be flat name/value pairs - it can contain nested objects and arrays.
#### Creating an Index
As before, we create an index with the `FT.CREATE` command, this time specifying we want to index JSON documents that look like this:
```javascript
{
name: 'Alice',
age: 32,
coins: 100
}
```
Each document represents a user in some system, and users have name, age and coins properties.
One way we might choose to index these documents is as follows:
```javascript
await client.ft.create('idx:users', {
'$.name': {
type: SchemaFieldTypes.TEXT,
SORTABLE: 'UNF'
},
'$.age': {
type: SchemaFieldTypes.NUMERIC,
AS: 'age'
},
'$.coins': {
type: SchemaFieldTypes.NUMERIC,
AS: 'coins'
}
}, {
ON: 'JSON',
PREFIX: 'noderedis:users'
});
```
Note that we're using JSON Path to specify where the fields to index are in our JSON documents, and the `AS` clause to define a name/alias for each field. We'll use these when writing queries.
#### Querying the Index
Now we have an index and some data stored as JSON documents in Redis (see the [JSON package documentation](https://github.com/redis/node-redis/tree/master/packages/json) for examples of how to store JSON), we can write some queries...
We'll use the [RediSearch query language](https://oss.redis.com/redisearch/Query_Syntax/) and [`FT.SEARCH`](https://oss.redis.com/redisearch/Commands/#ftsearch) command. Here's a query to find users under the age of 30:
```javascript
await client.ft.search('idx:users', '@age:[0 30]');
```

View File

@@ -1,6 +1,6 @@
{
"name": "@node-redis/search",
"version": "1.0.0-rc.0",
"version": "1.0.0",
"license": "MIT",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
@@ -9,16 +9,16 @@
"build": "tsc"
},
"peerDependencies": {
"@node-redis/client": "^1.0.0-rc"
"@node-redis/client": "^1.0.0"
},
"devDependencies": {
"@istanbuljs/nyc-config-typescript": "^1.0.1",
"@node-redis/test-utils": "*",
"@types/node": "^16.11.7",
"@types/node": "^16.11.10",
"nyc": "^15.1.0",
"release-it": "^14.11.7",
"source-map-support": "^0.5.20",
"release-it": "^14.11.8",
"source-map-support": "^0.5.21",
"ts-node": "^10.4.0",
"typescript": "^4.4.4"
"typescript": "^4.5.2"
}
}

View File

@@ -1,7 +1,3 @@
#!/bin/bash
echo testststealkshdfklhasdf
echo $REDIS_ARGUMENTS
redis-server $REDIS_ARGUMENTS

View File

@@ -8,19 +8,19 @@
"test": "echo \"TODO\""
},
"peerDependencies": {
"@node-redis/client": "^1.0.0-rc"
"@node-redis/client": "^1.0.0"
},
"devDependencies": {
"@istanbuljs/nyc-config-typescript": "^1.0.1",
"@types/mocha": "^9.0.0",
"@types/node": "^16.11.7",
"@types/yargs": "^17.0.5",
"@types/node": "^16.11.10",
"@types/yargs": "^17.0.7",
"mocha": "^9.1.3",
"nyc": "^15.1.0",
"release-it": "^14.11.7",
"source-map-support": "^0.5.20",
"release-it": "^14.11.8",
"source-map-support": "^0.5.21",
"ts-node": "^10.4.0",
"typescript": "^4.4.4",
"typescript": "^4.5.2",
"yargs": "^17.2.1"
}
}

View File

@@ -0,0 +1,6 @@
.nyc_output/
coverage/
lib/
.nycrc.json
.release-it.json
tsconfig.json