From 7247777a6f639b4b6adb96d86f2a16d7d34fc804 Mon Sep 17 00:00:00 2001 From: Leibale Date: Thu, 9 Nov 2023 21:01:26 -0500 Subject: [PATCH] a little bit docs --- docs/blocking-commands.md | 88 ----------------------------- docs/clustering.md | 22 +++----- docs/pool.md | 74 ++++++++++++++++++++++++ docs/pub-sub.md | 2 +- docs/scan-iterators.md | 6 +- packages/client/lib/client/index.ts | 2 +- packages/client/lib/client/pool.ts | 2 +- packages/redis/README.md | 16 +++--- 8 files changed, 97 insertions(+), 115 deletions(-) delete mode 100644 docs/blocking-commands.md create mode 100644 docs/pool.md diff --git a/docs/blocking-commands.md b/docs/blocking-commands.md deleted file mode 100644 index 639ab2b78e..0000000000 --- a/docs/blocking-commands.md +++ /dev/null @@ -1,88 +0,0 @@ -### 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: - -- You're using [transactions]() and need to `WATCH` a key or keys for changes. -- You want to run a blocking command that will take over the connection, such as `BLPOP` or `BLMOVE`. -- You're using the `MONITOR` command which also takes over a connection. - -Below are several examples of how to use isolated execution. - -> NOTE: Behind the scenes we're using [`generic-pool`](https://www.npmjs.com/package/generic-pool) to provide a pool of connections that can be isolated. Go there to learn more. - -## The Simple Scenario - -This just isolates execution on a single connection. Do what you want with that connection: - -```typescript -await client.executeIsolated(async isolatedClient => { - await isolatedClient.set('key', 'value'); - await isolatedClient.get('key'); -}); -``` - -## Transactions - -Things get a little more complex with transactions. Here we are `.watch()`ing some keys. If the keys change during the transaction, a `WatchError` is thrown when `.exec()` is called: - -```typescript -try { - await client.executeIsolated(async isolatedClient => { - await isolatedClient.watch('key'); - - const multi = isolatedClient.multi() - .ping() - .get('key'); - - if (Math.random() > 0.5) { - await isolatedClient.watch('another-key'); - multi.set('another-key', await isolatedClient.get('another-key') / 2); - } - - return multi.exec(); - }); -} catch (err) { - if (err instanceof WatchError) { - // the transaction aborted - } -} - -``` - -## Blocking Commands - -For blocking commands, you can execute a tidy little one-liner: - -```typescript -await client.executeIsolated(isolatedClient => isolatedClient.blPop('key')); -``` - -Or, you can just run the command directly, and provide the `isolated` option: - -```typescript -await client.blPop( - commandOptions({ isolated: true }), - 'key' -); -``` diff --git a/docs/clustering.md b/docs/clustering.md index 7b8b66a9ad..38419b06cd 100644 --- a/docs/clustering.md +++ b/docs/clustering.md @@ -4,26 +4,22 @@ Connecting to a cluster is a bit different. Create the client by specifying some (or all) of the nodes in your cluster and then use it like a regular client instance: -```typescript +```javascript import { createCluster } from 'redis'; -const cluster = createCluster({ - rootNodes: [ - { +const cluster = await createCluster({ + rootNodes: [{ url: 'redis://10.0.0.1:30001' - }, - { + }, { url: 'redis://10.0.0.2:30002' - } - ] -}); - -cluster.on('error', (err) => console.log('Redis Cluster Error', err)); - -await cluster.connect(); + }] + }) + .on('error', err => console.log('Redis Cluster Error', err)) + .connect(); await cluster.set('key', 'value'); const value = await cluster.get('key'); +await cluster.close(); ``` ## `createCluster` configuration diff --git a/docs/pool.md b/docs/pool.md new file mode 100644 index 0000000000..7121e601d7 --- /dev/null +++ b/docs/pool.md @@ -0,0 +1,74 @@ +# `RedisClientPool` + +Sometimes you want to run your commands on an exclusive connection. There are a few reasons to do this: + +- You want to run a blocking command that will take over the connection, such as `BLPOP` or `BLMOVE`. +- You're using [transactions](https://redis.io/docs/interact/transactions/) and need to `WATCH` a key or keys for changes. +- Some more... + +For those use cases you'll need to create a connection pool. + +## Creating a pool + +You can create a pool using the `createClientPool` function: + +```javascript +import { createClientPool } from 'redis'; + +const pool = await createClientPool() + .on('error', err => console.error('Redis Client Pool Error', err)); +``` + +the function accepts two arguments, the client configuration (see [here](./client-configuration.md) for more details), and the pool configuration: + +| Property | Default | Description | +|----------------|---------|--------------------------------------------------------------------------------------------------------------------------------| +| minimum | 1 | The minimum clients the pool should hold to. The pool won't close clients if the pool size is less than the minimum. | +| maximum | 100 | The maximum clients the pool will have at once. The pool won't create any more resources and queue requests in memory. | +| acquireTimeout | 3000 | The maximum time (in ms) a task can wait in the queue. The pool will reject the task with `TimeoutError` in case of a timeout. | +| cleanupDelay | 3000 | The time to wait before cleaning up unused clients. | + +You can also create a pool from a client (reusing it's configuration): +```javascript +const pool = await client.createPool() + .on('error', err => console.error('Redis Client Pool Error', err)); +``` + +## The Simple Scenario + +All the client APIs are exposed on the pool instance directly, and will execute the commands using one of the available clients. + +```javascript +await pool.sendCommand(['PING']); // 'PONG' +await client.ping(); // 'PONG' +await client.withTypeMapping({ + [RESP_TYPES.SIMPLE_STRING]: Buffer +}).ping(); // Buffer +``` + +## Transactions + +Things get a little more complex with transactions. Here we are `.watch()`ing some keys. If the keys change during the transaction, a `WatchError` is thrown when `.exec()` is called: + +```javascript +try { + await pool.execute(async client => { + await client.watch('key'); + + const multi = client.multi() + .ping() + .get('key'); + + if (Math.random() > 0.5) { + await client.watch('another-key'); + multi.set('another-key', await client.get('another-key') / 2); + } + + return multi.exec(); + }); +} catch (err) { + if (err instanceof WatchError) { + // the transaction aborted + } +} +``` diff --git a/docs/pub-sub.md b/docs/pub-sub.md index b319925569..bd1d842e0e 100644 --- a/docs/pub-sub.md +++ b/docs/pub-sub.md @@ -6,7 +6,7 @@ The Pub/Sub API is implemented by `RedisClient` and `RedisCluster`. Pub/Sub requires a dedicated stand-alone client. You can easily get one by `.duplicate()`ing an existing `RedisClient`: -```typescript +```javascript const subscriber = client.duplicate(); subscriber.on('error', err => console.error(err)); await subscriber.connect(); diff --git a/docs/scan-iterators.md b/docs/scan-iterators.md index ead3a346a2..47c4d6c056 100644 --- a/docs/scan-iterators.md +++ b/docs/scan-iterators.md @@ -4,7 +4,7 @@ [`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 +```javascript for await (const keys of client.scanIterator()) { const values = await client.mGet(keys); } @@ -12,7 +12,7 @@ for await (const keys of client.scanIterator()) { This works with `HSCAN`, `SSCAN`, and `ZSCAN` too: -```typescript +```javascript for await (const entries of client.hScanIterator('hash')) {} for await (const members of client.sScanIterator('set')) {} for await (const membersWithScores of client.zScanIterator('sorted-set')) {} @@ -20,7 +20,7 @@ for await (const membersWithScores of client.zScanIterator('sorted-set')) {} You can override the default options by providing a configuration object: -```typescript +```javascript client.scanIterator({ cursor: '0', // optional, defaults to '0' TYPE: 'string', // `SCAN` only diff --git a/packages/client/lib/client/index.ts b/packages/client/lib/client/index.ts index 4979c905b2..72ce1f2ff7 100644 --- a/packages/client/lib/client/index.ts +++ b/packages/client/lib/client/index.ts @@ -517,7 +517,7 @@ export default class RedisClient< /** * Create {@link RedisClientPool `RedisClientPool`} using this client as a prototype */ - pool(options?: Partial) { + createPool(options?: Partial) { return RedisClientPool.create( this._options, options diff --git a/packages/client/lib/client/pool.ts b/packages/client/lib/client/pool.ts index 6ed008f886..357c89f45b 100644 --- a/packages/client/lib/client/pool.ts +++ b/packages/client/lib/client/pool.ts @@ -216,7 +216,7 @@ export class RedisClientPool< } /** - * You are probably looking for {@link RedisClient.pool `RedisClient.pool`}, + * You are probably looking for {@link RedisClient.createPool `RedisClient.createPool`}, * {@link RedisClientPool.fromClient `RedisClientPool.fromClient`}, * or {@link RedisClientPool.fromOptions `RedisClientPool.fromOptions`}... */ diff --git a/packages/redis/README.md b/packages/redis/README.md index ce8b618e9b..4a57dbd7b1 100644 --- a/packages/redis/README.md +++ b/packages/redis/README.md @@ -61,7 +61,7 @@ To check if the the client is connected and ready to send commands, use `client. 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 +```javascript // raw Redis commands await client.HSET('key', 'field', 'value'); await client.HGETALL('key'); @@ -73,7 +73,7 @@ await client.hGetAll('key'); Modifiers to commands are specified using a JavaScript object: -```typescript +```javascript await client.set('key', 'value', { expiration: { type: 'EX', @@ -85,7 +85,7 @@ await client.set('key', 'value', { Replies will be mapped to useful data structures: -```typescript +```javascript await client.hGetAll('key'); // { field1: 'value1', field2: 'value2' } await client.hVals('key'); // ['value1', 'value2'] ``` @@ -96,7 +96,7 @@ await client.hVals('key'); // ['value1', 'value2'] If you want to run commands and/or use arguments that Node Redis doesn't know about (yet!) use `.sendCommand()`: -```typescript +```javascript await client.sendCommand(['SET', 'key', 'value', 'NX']); // 'OK' await client.sendCommand(['HGETALL', 'key']); // ['key1', 'field1', 'key2', 'field2'] @@ -110,7 +110,7 @@ There are two functions that disconnect a client from the Redis server. In most #### `.close()` -```typescript +```javascript const [ping, get] = await Promise.all([ client.ping(), client.get('key'), @@ -134,14 +134,14 @@ Forcibly close a client's connection to Redis immediately. Calling `destroy` wil Node Redis will automatically pipeline requests that are made during the same "tick". -```typescript +```javascript 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 +```javascript await Promise.all([ client.set('Tm9kZSBSZWRpcw==', 'users:1'), client.sAdd('users:1:tokens', 'Tm9kZSBSZWRpcw==') @@ -172,7 +172,7 @@ The Node Redis client class is an Nodejs EventEmitter and it emits an event each - [Scan Iterators](../../docs/scan-iterators.md). - [Programmability](../../docs/programmability.md). - [Command Options](../../docs/command-options.md). -- [Blocking Commands](../../docs/blocking-commands.md). +- [Pool](../../docs/pool.md). - [Clustering](../../docs/clustering.md). ## Supported Redis versions