Skip to content

Commit bf8fb65

Browse files
committed
Merge branch 'v4.0' of github.com:redis/node-redis
2 parents 4ff9a05 + eed4797 commit bf8fb65

File tree

11 files changed

+486
-190
lines changed

11 files changed

+486
-190
lines changed

.github/README.md

Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
# Node-Redis
2+
3+
[![Tests](https://img.shields.io/github/workflow/status/redis/node-redis/Tests/master.svg?label=tests)](https://codecov.io/gh/redis/node-redis)
4+
[![Coverage](https://codecov.io/gh/redis/node-redis/branch/master/graph/badge.svg?token=xcfqHhJC37)](https://codecov.io/gh/redis/node-redis)
5+
[![License](https://img.shields.io/github/license/redis/node-redis.svg)](https://codecov.io/gh/redis/node-redis)
6+
[![Chat](https://img.shields.io/discord/697882427875393627.svg)](https://discord.gg/XMMVgxUm)
7+
8+
## Installation
9+
10+
```bash
11+
npm install redis@next
12+
```
13+
14+
> :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).
15+
16+
## Usage
17+
18+
### Basic Example
19+
20+
```typescript
21+
import { createClient } from 'redis';
22+
23+
(async () => {
24+
const client = createClient();
25+
26+
client.on('error', (err) => console.log('Redis Client Error', err));
27+
28+
await client.connect();
29+
30+
await client.set('key', 'value');
31+
const value = await client.get('key');
32+
})();
33+
```
34+
35+
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]`:
36+
37+
```typescript
38+
createClient({
39+
url: 'redis://alice:[email protected]:6380'
40+
});
41+
```
42+
43+
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).
44+
45+
### Redis Commands
46+
47+
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.):
48+
49+
```typescript
50+
// raw Redis commands
51+
await client.HSET('key', 'field', 'value');
52+
await client.HGETALL('key');
53+
54+
// friendly JavaScript commands
55+
await client.hSet('key', 'field', 'value');
56+
await client.hGetAll('key');
57+
```
58+
59+
Modifiers to commands are specified using a JavaScript object:
60+
61+
```typescript
62+
await client.set('key', 'value', {
63+
EX: 10,
64+
NX: true
65+
});
66+
```
67+
68+
Replies will be transformed into useful data structures:
69+
70+
```typescript
71+
await client.hGetAll('key'); // { field1: 'value1', field2: 'value2' }
72+
await client.hVals('key'); // ['value1', 'value2']
73+
```
74+
75+
### Unsupported Redis Commands
76+
77+
If you want to run commands and/or use arguments that Node Redis doesn't know about (yet!) use `.sendCommand()`:
78+
79+
```typescript
80+
await client.sendCommand(['SET', 'key', 'value', 'NX']); // 'OK'
81+
82+
await client.sendCommand(['HGETALL', 'key']); // ['key1', 'field1', 'key2', 'field2']
83+
```
84+
85+
### Transactions (Multi/Exec)
86+
87+
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:
88+
89+
```typescript
90+
await client.set('another-key', 'another-value');
91+
92+
const [setKeyReply, otherKeyValue] = await client
93+
.multi()
94+
.set('key', 'value')
95+
.get('another-key')
96+
.exec(); // ['OK', 'another-value']
97+
```
98+
99+
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.
100+
101+
To dig deeper into transactions, check out the [Isolated Execution Guide](../docs/isolated-execution.md).
102+
103+
### Blocking Commands
104+
105+
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.
106+
107+
This pattern works especially well for blocking commands—such as `BLPOP` and `BLMOVE`:
108+
109+
```typescript
110+
import { commandOptions } from 'redis';
111+
112+
const blPopPromise = client.blPop(commandOptions({ isolated: true }), 'key', 0);
113+
114+
await client.lPush('key', ['1', '2']);
115+
116+
await blPopPromise; // '2'
117+
```
118+
119+
To learn more about isolated execution, check out the [guide](../docs/isolated-execution.md).
120+
121+
### Pub/Sub
122+
123+
Subscribing to a channel requires a dedicated stand-alone connection. You can easily get one by `.duplicate()`ing an existing Redis connection.
124+
125+
```typescript
126+
const subscriber = client.duplicate();
127+
128+
await subscriber.connect();
129+
```
130+
131+
Once you have one, simply subscribe and unsubscribe as needed:
132+
133+
```typescript
134+
await subscriber.subscribe('channel', (message) => {
135+
console.log(message); // 'message'
136+
});
137+
138+
await subscriber.pSubscribe('channe*', (message, channel) => {
139+
console.log(message, channel); // 'message', 'channel'
140+
});
141+
142+
await subscriber.unsubscribe('channel');
143+
144+
await subscriber.pUnsubscribe('channe*');
145+
```
146+
147+
Publish a message on a channel:
148+
149+
```typescript
150+
await publisher.publish('channel', 'message');
151+
```
152+
153+
### Scan Iterator
154+
155+
[`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):
156+
157+
```typescript
158+
for await (const key of client.scanIterator()) {
159+
// use the key!
160+
await client.get(key);
161+
}
162+
```
163+
164+
This works with `HSCAN`, `SSCAN`, and `ZSCAN` too:
165+
166+
```typescript
167+
for await (const { field, value } of client.hScanIterator('hash')) {}
168+
for await (const member of client.sScanIterator('set')) {}
169+
for await (const { score, member } of client.zScanIterator('sorted-set')) {}
170+
```
171+
172+
You can override the default options by providing a configuration object:
173+
174+
```typescript
175+
client.scanIterator({
176+
TYPE: 'string', // `SCAN` only
177+
MATCH: 'patter*',
178+
COUNT: 100
179+
});
180+
```
181+
182+
### Lua Scripts
183+
184+
Define new functions using [Lua scripts](https://redis.io/commands/eval) which execute on the Redis server:
185+
186+
```typescript
187+
import { createClient, defineScript } from 'redis';
188+
189+
(async () => {
190+
const client = createClient({
191+
scripts: {
192+
add: defineScript({
193+
NUMBER_OF_KEYS: 1,
194+
SCRIPT:
195+
'local val = redis.pcall("GET", KEYS[1]);' +
196+
'return val + ARGV[1];',
197+
transformArguments(key: string, toAdd: number): Array<string> {
198+
return [key, toAdd.toString()];
199+
},
200+
transformReply(reply: number): number {
201+
return reply;
202+
}
203+
})
204+
}
205+
});
206+
207+
await client.connect();
208+
209+
await client.set('key', '1');
210+
await client.add('key', 2); // 3
211+
})();
212+
```
213+
214+
### Disconnecting
215+
216+
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.
217+
218+
#### `.QUIT()`/`.quit()`
219+
220+
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.
221+
222+
```typescript
223+
const [ping, get, quit] = await Promise.all([
224+
client.ping(),
225+
client.get('key'),
226+
client.quit()
227+
]); // ['PONG', null, 'OK']
228+
229+
try {
230+
await client.get('key');
231+
} catch (err) {
232+
// ClosedClient Error
233+
}
234+
```
235+
236+
#### `.disconnect()`
237+
238+
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.
239+
240+
```typescript
241+
await client.disconnect();
242+
```
243+
244+
### Auto-Pipelining
245+
246+
Node Redis will automatically pipeline requests that are made during the same "tick".
247+
248+
```typescript
249+
client.set('Tm9kZSBSZWRpcw==', 'users:1');
250+
client.sAdd('users:1:tokens', 'Tm9kZSBSZWRpcw==');
251+
```
252+
253+
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()`.
254+
255+
```typescript
256+
await Promise.all([
257+
client.set('Tm9kZSBSZWRpcw==', 'users:1'),
258+
client.sAdd('users:1:tokens', 'Tm9kZSBSZWRpcw==')
259+
]);
260+
```
261+
262+
### Clustering
263+
264+
Check out the [Clustering Guide](../docs/clustering.md) when using Node Redis to connect to a Redis Cluster.
265+
266+
## Supported Redis versions
267+
268+
Node Redis is supported with the following versions of Redis:
269+
270+
| Version | Supported |
271+
|---------|--------------------|
272+
| 6.2.z | :heavy_check_mark: |
273+
| 6.0.z | :heavy_check_mark: |
274+
| 5.y.z | :heavy_check_mark: |
275+
| < 5.0 | :x: |
276+
277+
> Node Redis should work with older versions of Redis, but it is not fully tested and we cannot offer support.
278+
279+
## Packages
280+
281+
| Name | Description |
282+
|-------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
283+
| [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) |
284+
| [@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) |
285+
| [@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 |
286+
| [@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 |
287+
288+
## Contributing
289+
290+
If you'd like to contribute, check out the [contributing guide](CONTRIBUTING.md).
291+
292+
Thank you to all the people who already contributed to Node Redis!
293+
294+
[![Contributors](https://contrib.rocks/image?repo=redis/node-redis)](https://github.com/redis/node-redis/graphs/contributors)
295+
296+
## License
297+
298+
This repository is licensed under the "MIT" license. See [LICENSE](LICENSE).

examples/search+json.js

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Use Redis Search and Redis JSON
2+
3+
import { createClient, SchemaFieldTypes, AggregateGroupByReducers, AggregateSteps } from 'redis';
4+
5+
async function searchPlusJson() {
6+
const client = createClient();
7+
8+
await client.connect();
9+
10+
// Create an index
11+
await client.ft.create('users', {
12+
'$.name': {
13+
type: SchemaFieldTypes.TEXT,
14+
SORTABLE: 'UNF'
15+
},
16+
'$.age': SchemaFieldTypes.NUMERIC,
17+
'$.coins': SchemaFieldTypes.NUMERIC
18+
}, {
19+
ON: 'JSON'
20+
});
21+
22+
// Add some users
23+
await Promise.all([
24+
client.json.set('users:1', '$', {
25+
name: 'Alice',
26+
age: 32,
27+
coins: 100
28+
}),
29+
client.json.set('users:2', '$', {
30+
name: 'Bob',
31+
age: 23,
32+
coins: 15
33+
})
34+
]);
35+
36+
// Search all users under 30
37+
// TODO: why "$.age:[-inf, 30]" does not work?
38+
console.log(
39+
await client.ft.search('users', '*')
40+
);
41+
// {
42+
// total: 1,
43+
// documents: [...]
44+
// }
45+
46+
// Some aggrigrations
47+
console.log(
48+
await client.ft.aggregate('users', '*', {
49+
STEPS: [{
50+
type: AggregateSteps.GROUPBY,
51+
REDUCE: [{
52+
type: AggregateGroupByReducers.AVG,
53+
property: '$.age',
54+
AS: 'avarageAge'
55+
}, {
56+
type: AggregateGroupByReducers.SUM,
57+
property: '$.coins',
58+
AS: 'totalCoins'
59+
}]
60+
}]
61+
})
62+
);
63+
// {
64+
// total: 2,
65+
// results: [{
66+
// avarageAvg: '27.5',
67+
// totalCoins: '115'
68+
// }]
69+
// }
70+
71+
await client.quit();
72+
}
73+
74+
searchPlusJson();

0 commit comments

Comments
 (0)