1
0
mirror of https://github.com/redis/node-redis.git synced 2025-12-25 00:40:59 +03:00
Co-authored-by: Guy Royse <guy@guyroyse.com>
This commit is contained in:
Leibale
2023-06-14 15:01:52 -04:00
parent 826296fbff
commit c188a5c781
7 changed files with 124 additions and 109 deletions

View File

@@ -10,7 +10,7 @@ If don't want to queue commands in memory until a new socket is established, set
## How are commands batched?
Commands are pipelined using [`queueMicrotask`](https://nodejs.org/api/globals.html#globals_queuemicrotask_callback).
Commands are pipelined using [`setImmediate`](https://nodejs.org/api/globals.html#setimmediatecallback-args).
If `socket.write()` returns `false`—meaning that ["all or part of the data was queued in user memory"](https://nodejs.org/api/net.html#net_socket_write_data_encoding_callback:~:text=all%20or%20part%20of%20the%20data%20was%20queued%20in%20user%20memory)—the commands will stack in memory until the [`drain`](https://nodejs.org/api/net.html#net_event_drain) event is fired.

View File

@@ -1,8 +1,10 @@
# RESP
# Mapping RESP types
## Type Mapping
RESP, which stands for **R**edis **SE**rialization **P**rotocol, is the protocol used by Redis to communicate with clients. This document shows how RESP types can be mapped to JavaScript types. You can learn more about RESP itself in the [offical documentation](https://redis.io/docs/reference/protocol-spec/).
## RESP2 -> JS
By default, each type is mapped to the first option in the lists below. To change this, configure a [`typeMapping`](.).
## RESP2
- Integer (`:`) => `number`
- Simple String (`+`) => `string | Buffer`
@@ -10,7 +12,9 @@
- Simple Error (`-`) => `ErrorReply`
- Array (`*`) => `Array`
## RESP3 -> JS
> NOTE: the first type is the default type
## RESP3
- Null (`_`) => `null`
- Boolean (`#`) => `boolean`
@@ -29,11 +33,11 @@
> NOTE: the first type is the default type
## Map keys and Set members
### Map keys and Set members
When decoding Map to `Map | object` or Set to `Set`, keys/members (respectively) of type "Simple String" or "Blob String" will be decoded as `string`s (ignoring type mapping) to allow lookup by type. If you need them as `Buffer`s, make sure to decode `Map`s/`Set`s as `Array`s.
When decoding a Map to `Map | object` or a Set to `Set`, keys and members of type "Simple String" or "Blob String" will be decoded as `string`s which enables lookups by value, ignoring type mapping. If you want them as `Buffer`s, decode them as `Array`s instead.
## Not Implemented
### Not Implemented
These parts of RESP3 are not yet implemented in Redis itself (at the time of writing), so are not yet implemented in the Node-Redis client either:

View File

@@ -1,3 +1,24 @@
### 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.isolated().blPop(
'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).
# Isolated Execution
Sometimes you want to run your commands on an exclusive connection. There are a few reasons to do this:

View File

@@ -2,12 +2,11 @@
> :warning: The command options API in v5 has breaking changes from the previous version. For more details, refer to the [v4-to-v5 guide](./v4-to-v5.md#command-options).
TODO
Command Options are used to create "proxy clients" that change the behavior of executed commands. See the sections below for details.
## Type Mapping
Some RESP types can be mapped to more than one JavaScript type. For example, "Blob String" can be mapped to `string` or `Buffer`.
You can override the default type mapping using the `withTypeMapping` function:
Some [RESP types](./RESP.md) can be mapped to more than one JavaScript type. For example, "Blob String" can be mapped to `string` or `Buffer`. You can override the default type mapping using the `withTypeMapping` function:
```javascript
await client.get('key'); // `string | null`
@@ -23,48 +22,47 @@ See [RESP](./RESP.md) for a full list of types.
## Abort Signal
Commands can be aborted using the [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) API:
The client [batches commands](./FAQ.md#how-are-commands-batched) before sending them to Redis. Commands that haven't been written to the socket yet can be aborted using the [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) API:
```javascript
const controller = new AbortController();
controller.abort();
const controller = new AbortController(),
client = client.withAbortSignal(controller.signal);
try {
await client.withAbortSignal(controller.signal).get('key');
const promise = client.get('key');
controller.abort();
await promise;
} catch (err) {
// AbortError
}
```
> NOTE: Commands that are already written to the socket cannot be aborted.
## ASAP
Commands that are executed in the "asap" mode are added to the top of the queue. This is useful to ensure that commands are executed before other commands that are already in the queue.
Commands that are executed in the "asap" mode are added to the beginning of the "to sent" queue.
```javascript
const asapClient = client.asap();
client.on('connect', () => {
asapClient.clientSetName('my-name')
.catch(err => console.error('CLIENT SETNAME error', err));
});
await asapClient.ping();
```
## `withCommandOptions`
The `withCommandOptions` overrides all of the command options, without reusing any existing ones:
You can set all of the above command options in a single call with the `withCommandOptions` function:
```javascript
const bufferClient = client.withTypeMapping({
[TYPES.BLOB_STRING]: Buffer
client.withCommandOptions({
typeMapping: ...,
abortSignal: ...,
asap: ...
});
```
If any of the above options are omitted, the default value will be used. For example, the following client would **not** be in ASAP mode:
```javascript
client.asap().withCommandOptions({
typeMapping: ...,
abortSignal: ...
});
await bufferClient.get('key'); // `Buffer | null`
// reset all command options
const defaultClient = client.withCommandOptions({});
await defaultClient.get('key'); // `string | null`
```

View File

@@ -61,16 +61,15 @@ for more information, see the [Scan Iterators guide](./scan-iterators.md).
## Legacy Mode
TODO
In the previous version, you could access "legacy" mode by creating a client and passing in `{ legacyMode: true }`. Now, you can create one off of an existing client by calling the `.legacy()` function. This allows easier access to both APIs and enables better TypeScript support.
```javascript
const client = createClient(),
legacyClient = client.legacy();
// use `client` for the new API
// use `client` for the current API
const client = createClient();
await client.set('key', 'value');
// use `legacyClient` for the "legacy" API
const legacyClient = client.legacy();
legacyClient.set('key', 'value', (err, reply) => {
// ...
});
@@ -79,14 +78,48 @@ legacyClient.set('key', 'value', (err, reply) => {
## Isolation Pool
TODO
The `isolationPool` has been moved to it's on class `ClientPool`. You can create pool from a client using `client.createPool()`.
## Cluster MULTI
```javascript
await client.sendCommand(['GET', 'key']);
const pool = client.createPool({
min: 0,
max: Infinity
});
await pool.blPop('key');
await pool.sendCommand(['GET', 'key']);
await pool.use(client => client.blPop());
Cluster MULTI supports readonly/replicas
`cluster.multi.addCommand` now requires `isReadonly` as the second argument, to match `cluster.sendCommand`
await cluster.sendCommand('key', true, ['GET', 'key']);
const clusterPool = cluster.createPool({
min: 0,
max: Infinity
});
await clusterPool.blPop('key');
await clusterPool.sendCommand('key', true, ['GET', 'key']);
await clusterPool.use(client => client.blPop());
```
TODO
## Cluster `MULTI`
In v4, `cluster.multi()` did not support executing commands on replicas, even if they were readonly.
```javascript
// this might execute on a replica, depending on configuration
await cluster.sendCommand('key', true, ['GET', 'key']);
// this always executes on a master
await cluster.multi()
.addCommand('key', ['GET', 'key'])
.exec();
```
To support executing commands on replicas, `cluster.multi().addCommand` now requires `isReadonly` as the second argument, which matches the signature of `cluster.sendCommand`:
```javascript
await cluster.multi()
.addCommand('key', true, ['GET', 'key'])
.exec();
```
## Commands

View File

@@ -1,23 +1,22 @@
# RESP3 Support
[RESP3](./RESP3.md)
TODO
```javascript
const client = createClient({
RESP: 3
});
```
client.on('error', err => console.error(err));
```javascript
// by default
await client.hGetAll('key'); // Record<string, string>
await client.connect();
client.hGetAll('key'); // Record<string, string>
client.withTypeMapping({
await client.withTypeMapping({
[TYPES.MAP]: Map
}).hGetAll('key'); // Map<string, string>
client.withTypeMapping({
await client.withTypeMapping({
[TYPES.MAP]: Map,
[TYPES.BLOB_STRING]: Buffer
}).hGetAll('key'); // Map<string, Buffer>

View File

@@ -42,9 +42,11 @@ await client.connect();
await client.set('key', 'value');
const value = await client.get('key');
client.destroy();
await client.close();
```
> :warning: You **MUST** listen to `error` events. If a client doesn't have at least one `error` listener registered and an `error` occurs, that error will be thrown and the Node.js process will exit. See the [`EventEmitter` docs](https://nodejs.org/api/events.html#events_error_events) for more details.
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]`:
```javascript
@@ -83,21 +85,14 @@ await client.set('key', 'value', {
});
```
Replies will be transformed into useful data structures:
Replies will be mapped to useful data structures:
```typescript
await client.hGetAll('key'); // { field1: 'value1', field2: 'value2' }
await client.hVals('key'); // ['value1', 'value2']
```
`Buffer`s are supported as well:
```typescript
await client.hSet('key', 'field', Buffer.from('value')); // 'OK'
await client.withTypeMapping({
[TYPES.BLOB_STRING]: Buffer
}).hGetAll('key'); // { field: <Buffer 76 61 6c 75 65> }
```
> NOTE: you can change the default type mapping. See the [Type Mapping](../../docs/command-options.md#type-mapping) documentation for more information.
### Unsupported Redis Commands
@@ -109,37 +104,12 @@ await client.sendCommand(['SET', 'key', 'value', 'NX']); // 'OK'
await client.sendCommand(['HGETALL', 'key']); // ['key1', 'field1', 'key2', 'field2']
```
### Links
- [Multi](../../docs/multi.md).
- [Pub/Sub](../../docs/pub-sub.md).
- [Scan Iterators](../../docs/scan-iterators.md).
- [Programmability](../../docs/programmability.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.isolated().blPop(
'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).
### Disconnecting
There are two functions that disconnect a client from the Redis server. In most scenarios you should use `.close()` to ensure that pending commands are sent to Redis before closing a connection.
> :warning: The `.quit()` and `.disconnect()` methods have been deprecated in v5. For more details, refer to the [v4-to-v5 guide](../../docs/v4-to-v5.md#quit-vs-disconnect).
#### `.close()`
```typescript
@@ -180,26 +150,6 @@ await Promise.all([
]);
```
### Aborting Commands
If you want to abort a command, you can use the `AbortController` API:
```typescript
const controller = new AbortController();
client.withAbortSignal(contoller.signal).get('key').catch(err => {
// AbortError
});
controller.abort();
```
> :watning: commands can only be aborted before they are sent to Redis. Once a command is sent (written on the socket), it cannot be aborted.
### Clustering
Check out the [Clustering Guide](../../docs/clustering.md) when using Node Redis to connect to a Redis Cluster.
### Events
The Node Redis client class is an Nodejs EventEmitter and it emits an event each time the network status changes:
@@ -242,3 +192,13 @@ Thank you to all the people who already contributed to Node Redis!
## License
This repository is licensed under the "MIT" license. See [LICENSE](../../LICENSE).
### Links
- [Multi](../../docs/multi.md).
- [Pub/Sub](../../docs/pub-sub.md).
- [Scan Iterators](../../docs/scan-iterators.md).
- [Programmability](../../docs/programmability.md).
- [Command Options](../../docs/command-options.md).
- [Blocking Commands](../../docs/blocking-commands.md).
- [Clustering](../../docs/clustering.md).