## 1. Project Overview & Quickstart (redis/node-redis)

## File: README.md

# Node-Redis

[](https://github.com/redis/node-redis/actions/workflows/tests.yml)
[](https://codecov.io/gh/redis/node-redis)
[](https://github.com/redis/node-redis/blob/master/LICENSE)

[](https://discord.gg/redis)
[](https://www.twitch.tv/redisinc)
[](https://www.youtube.com/redisinc)
[](https://twitter.com/redisinc)

node-redis is a modern, high performance [Redis](https://redis.io) client for Node.js.

## How do I Redis?

[Learn for free at Redis University](https://university.redis.com/)

[Build faster with the Redis Launchpad](https://launchpad.redis.com/)

[Try the Redis Cloud](https://redis.com/try-free/)

[Dive in developer tutorials](https://developer.redis.com/)

[Join the Redis community](https://redis.com/community/)

[Work at Redis](https://redis.com/company/careers/jobs/)

## Installation

Start a redis via docker:

```bash
docker run -p 6379:6379 -d redis:8.0-rc1
```

To install node-redis, simply:

```bash
npm install redis
```
> "redis" is the "whole in one" package that includes all the other packages. If you only need a subset of the commands,
> you can install the individual packages. See the list below.

## Packages

| Name                                           | Description                                                                                 |
| ---------------------------------------------- | ------------------------------------------------------------------------------------------- |
| [`redis`](https://github.com/redis/node-redis/tree/master/packages/redis)                    | The client with all the ["redis-stack"](https://github.com/redis-stack/redis-stack) modules |
| [`@redis/client`](https://github.com/redis/node-redis/tree/master/packages/client)           | The base clients (i.e `RedisClient`, `RedisCluster`, etc.)                                  |
| [`@redis/bloom`](https://github.com/redis/node-redis/tree/master/packages/bloom)             | [Redis Bloom](https://redis.io/docs/data-types/probabilistic/) commands                     |
| [`@redis/json`](https://github.com/redis/node-redis/tree/master/packages/json)               | [Redis JSON](https://redis.io/docs/data-types/json/) commands                               |
| [`@redis/search`](https://github.com/redis/node-redis/tree/master/packages/search)           | [RediSearch](https://redis.io/docs/interact/search-and-query/) commands                     |
| [`@redis/time-series`](https://github.com/redis/node-redis/tree/master/packages/time-series) | [Redis Time-Series](https://redis.io/docs/data-types/timeseries/) commands                  |
| [`@redis/entraid`](https://github.com/redis/node-redis/tree/master/packages/entraid)         | Secure token-based authentication for Redis clients using Microsoft Entra ID                |

> Looking for a high-level library to handle object mapping?
> See [redis-om-node](https://github.com/redis/redis-om-node)!


## Usage

### Basic Example

```typescript
import { createClient } from "redis";

const client = await createClient()
  .on("error", (err) => console.log("Redis Client Error", err))
  .connect();

await client.set("key", "value");
const value = await client.get("key");
console.log(value); // 'value'
client.destroy();
```

The same example in CommonJS (for example `node example.js` without `"type": "module"` in `package.json`):

```js
const { createClient } = require("redis");

async function main() {
  const client = await createClient()
    .on("error", (err) => console.log("Redis Client Error", err))
    .connect();

  await client.set("key", "value");
  const value = await client.get("key");
  console.log(value); // 'value'
  client.destroy();
}

main().catch(console.error);
```

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](https://github.com/redis/node-redis/blob/master/docs/client-configuration.md).

To check if the client is connected and ready to send commands, use `client.isReady` which returns a boolean.
`client.isOpen` is also available. This returns `true` when the client's underlying socket is open, and `false` when it
isn't (for example when the client is still connecting or reconnecting after a network error).

### 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']
```

`Buffer`s are supported as well:

```typescript
const client = createClient().withTypeMapping({
  [RESP_TYPES.BLOB_STRING]: Buffer
});

await client.hSet("key", "field", Buffer.from("value")); // 'OK'
await client.hGet("key", "field"); // { field:  }

```

For commands that return serialized binary payloads, such as `DUMP`, map blob strings to `Buffer` before using the result with commands like `RESTORE`:

```typescript
const binaryClient = createClient().withTypeMapping({
  [RESP_TYPES.BLOB_STRING]: Buffer
});

const dump = await binaryClient.dump("source");
await binaryClient.restore("destination", 0, dump);
```

### 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']
```

_Note: the [API is different when using a cluster](https://github.com/redis/node-redis/blob/master/docs/clustering.md#unsupported-redis-commands)._

### 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.


### Blocking Commands

In v4, `RedisClient` had the ability to create a pool of connections using an "Isolation Pool" on top of the "main"
connection. However, there was no way to use the pool without a "main" connection:

```javascript
const client = await createClient()
  .on("error", (err) => console.error(err))
  .connect();

await client.ping(client.commandOptions({ isolated: true }));
```

In v5 we've extracted this pool logic into its own class—`RedisClientPool`:

```javascript
const pool = await createClientPool()
  .on("error", (err) => console.error(err))
  .connect();

await pool.ping();
```


### Pub/Sub

See the [Pub/Sub overview](https://github.com/redis/node-redis/blob/master/docs/pub-sub.md).

### 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 keys of client.scanIterator()) {
  console.log(keys, await client.mGet(keys));
}
```

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, value } of client.zScanIterator("sorted-set")) {
}
```

You can override the default options by providing a configuration object:

```typescript
client.scanIterator({
  TYPE: "string", // `SCAN` only
  MATCH: "pattern*",
  COUNT: 100,
});
```

### Compare-and-Set/Delete (CAS/CAD)

> **Note**: CAS/CAD operations were introduced in Redis 8.4

```typescript
// Conditionally update only if the current value matches
await client.set("key", "new-value", { condition: "IFEQ", matchValue: "old-value" });

// Conditionally delete only if the current value matches
await client.delEx("key", { condition: "IFEQ", matchValue: "expected-value" });
```

#### Local Digest

> **Note**: This feature requires the optional `@node-rs/xxhash` peer dependency.

The `digest` helper computes an XXH3 64-bit hash locally, matching what Redis computes via the `DIGEST` command. This is useful for CAS/CAD operations with large values where comparing the full value would be inefficient.

```bash
npm install @node-rs/xxhash
```

```typescript
import { digest } from "redis";

const hash = await digest("my-value");

await client.set("key", "new-value", { condition: "IFDEQ", matchValue: hash });
```

### Disconnecting

The `QUIT` command has been deprecated in Redis 7.2 and should now also be considered deprecated in Node-Redis. Instead
of sending a `QUIT` command to the server, the client can simply close the network connection.

`client.QUIT/quit()` is replaced by `client.close()`. and, to avoid confusion, `client.disconnect()` has been renamed to
`client.destroy()`.

```typescript
client.destroy();
```
### Client Side Caching

Node Redis v5 adds support for [Client Side Caching](https://redis.io/docs/manual/client-side-caching/), which enables clients to cache query results locally. The Redis server will notify the client when cached results are no longer valid.

```typescript
// Enable client side caching with RESP3
const client = createClient({
  RESP: 3,
  clientSideCache: {
    ttl: 0,             // Time-to-live (0 = no expiration)
    maxEntries: 0,      // Maximum entries (0 = unlimited)
    evictPolicy: "LRU"  // Eviction policy: "LRU" or "FIFO"
  }
});
```

See the [V5 documentation](https://github.com/redis/node-redis/blob/master/docs/v5.md#client-side-caching) for more details and advanced usage.

### 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=="),
]);
```

### Sentinel

Look at the [sentinel section](docs/sentinel.md) to figure out how to use this library with sentinels.

### Programmability

See the [Programmability overview](https://github.com/redis/node-redis/blob/master/docs/programmability.md).

### Clustering

Check out the [Clustering Guide](https://github.com/redis/node-redis/blob/master/docs/clustering.md) when using Node Redis to connect to a Redis Cluster.

### OpenTelemetry

#### OpenTelemetry Metrics Instrumentation

```typescript
import { createClient, OpenTelemetry } from "redis";

OpenTelemetry.init({
  metrics: {
    enabled: true
  }
});

const client = createClient()

await client.connect();
// ... use the client as usual
```

**Important:** Initializing `OpenTelemetry` only enables node-redis metrics instrumentation and requires both `@opentelemetry/api` and an OpenTelemetry SDK configured in your application.

**Important:** Initialize `OpenTelemetry` before creating Redis clients.
For SDK/provider/exporter setup, verification, and advanced configuration, see:

- [OpenTelemetry Metrics docs](./docs/otel-metrics.md)
- [OpenTelemetry Metrics example](./examples/otel-metrics.js)

### Diagnostics Channel

Node Redis publishes telemetry through Node.js [`diagnostics_channel`](https://nodejs.org/api/diagnostics_channel.html), enabling APM tools and custom instrumentation to observe commands, connections, and internal events. See the [Diagnostics Channel guide](./docs/diagnostics-channel.md) for the full channel reference and usage examples.

### Events

The Node Redis client class is an Nodejs EventEmitter and it emits an event each time the network status changes:

| Name                    | When                                                                               | Listener arguments                                        |
| ----------------------- | ---------------------------------------------------------------------------------- | --------------------------------------------------------- |
| `connect`               | Initiating a connection to the server                                              | _No arguments_                                            |
| `ready`                 | Client is ready to use                                                             | _No arguments_                                            |
| `end`                   | Connection has been closed (via `.close()` or `.destroy()`)                        | _No arguments_                                            |
| `error`                 | An error has occurred—usually a network issue such as "Socket closed unexpectedly" | `(error: Error)`                                          |
| `reconnecting`          | Client is trying to reconnect to the server                                        | _No arguments_                                            |
| `sharded-channel-moved` | See [here](https://github.com/redis/node-redis/blob/master/docs/pub-sub.md#sharded-channel-moved-event)                          | See [here](https://github.com/redis/node-redis/blob/master/docs/pub-sub.md#sharded-channel-moved-event) |
| `invalidate`            | Client Tracking is on with `emitInvalidate` and a key is invalidated               | `(key: RedisItem \| null)`                                 |

> :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 client will not emit [any other events](https://github.com/redis/node-redis/blob/master/docs/v3-to-v4.md#all-the-removed-events) beyond those listed above.

## Supported Redis versions

Node Redis is supported with the following versions of Redis:

See [Supported Redis Versions](https://github.com/redis/node-redis/blob/master/SUPPORTED_REDIS_VERSIONS.md).

## Migration

- [From V3 to V4](https://github.com/redis/node-redis/blob/master/docs/v3-to-v4.md)
- [From V4 to V5](https://github.com/redis/node-redis/blob/master/docs/v4-to-v5.md)
- [V5](https://github.com/redis/node-redis/blob/master/docs/v5.md)

## Contributing

If you'd like to contribute, check out the [contributing guide](https://github.com/redis/node-redis/blob/master/CONTRIBUTING.md).

Thank you to all the people who already contributed to Node Redis!

[](https://github.com/redis/node-redis/graphs/contributors)

## License

This repository is licensed under the "MIT" license. See [LICENSE](https://github.com/redis/node-redis/blob/master/LICENSE).

---

## File: docs/client-configuration.md

# `createClient` configuration

| Property                     | Default                                  | Description                                                                                                                                                                                                                                         |
|------------------------------|------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| url                          |                                          | `redis[s]://[[username][:password]@][host][:port][/db-number]` (see [`redis`](https://www.iana.org/assignments/uri-schemes/prov/redis) and [`rediss`](https://www.iana.org/assignments/uri-schemes/prov/rediss) IANA registration for more details), or `unix://[[username][:password]@]/path/to/socket[?db=N]` for a UNIX domain socket |
| socket                       |                                          | Socket connection properties. Unlisted [`net.connect`](https://nodejs.org/api/net.html#socketconnectoptions-connectlistener) properties (and [`tls.connect`](https://nodejs.org/api/tls.html#tlsconnectoptions-callback)) are also supported        |
| socket.port                  | `6379`                                   | Redis server port                                                                                                                                                                                                                                   |
| socket.host                  | `'localhost'`                            | Redis server hostname                                                                                                                                                                                                                               |
| socket.servername            |                                          | Server name for the SNI (Server Name Indication) TLS extension. Set this property if the Redis server requires SNI during TLS handshake.                                                                                                                                                                                                                                |
| socket.family                | `0`                                      | IP Stack version (one of `4 \| 6 \| 0`)                                                                                                                                                                                                             |
| socket.path                  |                                          | Path to the UNIX Socket                                                                                                                                                                                                                             |
| socket.connectTimeout        | `5000`                                   | Connection timeout (in milliseconds)                                                                                                                                                                                                                |
| socket.socketTimeout           |                                          | The maximum duration (in milliseconds) that the socket can remain idle (i.e., with no data sent or received) before being automatically closed |
| socket.noDelay               | `true`                                   | Toggle [`Nagle's algorithm`](https://nodejs.org/api/net.html#net_socket_setnodelay_nodelay)                                                                                                                                                         |
| socket.keepAlive             | `true`                                   | Toggle [`keep-alive`](https://nodejs.org/api/net.html#socketsetkeepaliveenable-initialdelay) functionality                                                                                                                                          |
| socket.keepAliveInitialDelay | `30000`                                  | If set to a positive number, it sets the initial delay before the first keepalive probe is sent on an idle socket                                                                                                                                   |
| socket.tls                   |                                          | See explanation and examples [below](#TLS)                                                                                                                                                                                                          |
| socket.reconnectStrategy     | Exponential backoff with a maximum of 2000 ms; plus 0-200 ms random jitter.       | A function containing the [Reconnect Strategy](#reconnect-strategy) logic                                                                                                                                                                           |
| username                     |                                          | ACL username ([see ACL guide](https://redis.io/topics/acl))                                                                                                                                                                                         |
| password                     |                                          | ACL password or the old "--requirepass" password                                                                                                                                                                                                    |
| name                         |                                          | Client name ([see `CLIENT SETNAME`](https://redis.io/commands/client-setname))                                                                                                                                                                      |
| database                     |                                          | Redis database number (see [`SELECT`](https://redis.io/commands/select) command)                                                                                                                                                                    |
| keyPrefix                    |                                          | Prefix prepended to every key sent to Redis (ioredis-compatible). See [Key Prefixing](#key-prefixing).                                                                                                                                              |
| modules                      |                                          | Included [Redis Modules](../README.md#packages)                                                                                                                                                                                                     |
| scripts                      |                                          | Script definitions (see [Lua Scripts](../README.md#lua-scripts))                                                                                                                                                                                    |
| functions                    |                                          | Function definitions (see [Functions](../README.md#functions))                                                                                                                                                                                      |
| commandsQueueMaxLength       |                                          | Maximum length of the client's internal command queue                                                                                                                                                                                               |
| disableOfflineQueue          | `false`                                  | Disables offline queuing, see [FAQ](./FAQ.md#what-happens-when-the-network-goes-down)                                                                                                                                                               |
| 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))                                                                                                                                                                    |
| isolationPoolOptions         |                                          | An object that configures a pool of isolated connections, If you frequently need isolated connections, consider using [createClientPool](https://github.com/redis/node-redis/blob/master/docs/pool.md#creating-a-pool) instead                                                                                                     |
| pingInterval                 |                                          | Send `PING` command at interval (in ms). Useful with ["Azure Cache for Redis"](https://learn.microsoft.com/en-us/azure/azure-cache-for-redis/cache-best-practices-connection#idle-timeout)                                                          |
| disableClientInfo            | `false`                                  | Disables `CLIENT SETINFO LIB-NAME node-redis` and `CLIENT SETINFO LIB-VER X.X.X` commands                                                                                                                                                           |
| commandOptions.timeout       | `5000`                                   | Default per-command timeout in milliseconds. Set to `undefined` (or `0`) to disable. See [Command Options](./command-options.md).                                                                                                                   |

## Reconnect Strategy

When the socket closes unexpectedly (without calling `.quit()`/`.disconnect()`), the client uses `reconnectStrategy` to decide what to do. The following values are supported:
1. `false` -> do not reconnect, close the client and flush the command queue.
2. `number` -> wait for `X` milliseconds before reconnecting.
3. `(retries: number, cause: Error) => false | number | Error` -> `number` is the same as configuring a `number` directly, `Error` is the same as `false`, but with a custom error.

By default the strategy uses exponential backoff, but it can be overwritten like so:

```javascript
createClient({
  socket: {
    reconnectStrategy: (retries, cause) => {
        // By default, do not reconnect on socket timeout.
        if (cause instanceof SocketTimeoutError) {
          return false;
        }

        // Generate a random jitter between 0 – 200 ms:
        const jitter = Math.floor(Math.random() * 200);
        // Delay is an exponential back off, (times^2) * 50 ms, with a maximum value of 2000 ms:
        const delay = Math.min(Math.pow(2, retries) * 50, 2000);

        return delay + jitter;
    }
  }
});
```

## TLS

To enable TLS, set `socket.tls` to `true`. Below are some basic examples.

> For configuration options see [tls.connect](https://nodejs.org/api/tls.html#tlsconnectoptions-callback) and [tls.createSecureContext](https://nodejs.org/api/tls.html#tlscreatesecurecontextoptions), as those are the underlying functions used by this library.

### Create a SSL client

```javascript
createClient({
  socket: {
    tls: true,
    ca: '...',
    cert: '...'
  }
});
```

### Create a SSL client using a self-signed certificate

```javascript
createClient({
  socket: {
    tls: true,
    rejectUnauthorized: false,
    cert: '...'
  }
});
```
## Key Prefixing

The `keyPrefix` option prepends a prefix to **every key** sent to Redis. It is an ioredis-compatible
way to isolate keyspaces — for example to isolate tests in CI, or to separate the keys of different
parts of an application (web app, background workers, …) that share a single Redis instance.

```javascript
const client = createClient({ keyPrefix: 'app:' });
await client.connect();

await client.set('key', 'value'); // actually stores 'app:key'
await client.get('key');          // reads 'app:key' -> 'value'
```

The prefix is applied uniformly across the standard client, [cluster](./clustering.md),
[sentinel](./sentinel.md), [pool](./pool.md), and inside [transactions and pipelines](./transactions.md).
In cluster mode the slot is computed from the prefixed key, so routing remains correct.

### Semantics (matching ioredis)

- Only keys **sent** to Redis are prefixed. Keys **returned** by Redis are **not** un-prefixed — e.g.
  `KEYS *`, `SCAN`, and `RANDOMKEY` return keys that still include the prefix.
- Because returned keys keep the prefix, **`scanIterator` yields already-prefixed keys**. Feeding
  them straight back into a key-prefixing command double-prefixes them — e.g. with `keyPrefix: 'app:'`,
  a yielded `'app:foo'` passed to `client.mGet(...)` becomes `'app:app:foo'`. Strip the prefix first,
  or use a client without a `keyPrefix`. See [Scan Iterators](./scan-iterators.md).
- `SCAN`/`KEYS`/`HSCAN`/… `MATCH` patterns are **not** auto-prefixed. Include the prefix in the
  pattern yourself if required (e.g. `client.scan('0', { MATCH: 'app:user:*' })`).
- Pub/Sub channels are **not** prefixed (this includes sharded `SPUBLISH`/`SSUBSCRIBE`), since
  channels are a separate namespace from keys.
- The deprecated `parseArgs`/`transformArguments` helper does not apply `keyPrefix`.

`keyPrefix` may be a `string` or a `Buffer`.

## Connection Pooling

In most cases, a single Redis connection is sufficient, as the node-redis client efficiently handles commands using an underlying socket. Unlike traditional databases, Redis does not require connection pooling for optimal performance.

However, if your use case requires exclusive connections see [RedisClientPool](https://github.com/redis/node-redis/blob/master/docs/pool.md), which allows you to create and manage multiple dedicated connections.

---

## File: docs/clustering.md

# Clustering

## Basic Example

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:

```javascript
import { createCluster } from 'redis';

const cluster = await createCluster({
    rootNodes: [{
      url: 'redis://10.0.0.1:30001'
    }, {
      url: 'redis://10.0.0.2:30002'
    }]
  })
  .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

> See the [client configuration](./client-configuration.md) page for the `rootNodes` and `defaults` configuration schemas.

| Property               | Default | Description                                                                                                                                                                                                                                                                                                         |
|------------------------|---------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| rootNodes              |         | An array of root nodes that are part of the cluster, which will be used to get the cluster topology. Each element in the array is a client configuration object. There is no need to specify every node in the cluster: 3 should be enough to reliably connect and obtain the cluster configuration from the server |
| 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                                                                                                                                                        |
| minimizeConnections    | `false` | When `true`, `.connect()` will only discover the cluster topology, without actually connecting to all the nodes. Useful for short-term or Pub/Sub-only connections.                                                                                                                                                 |
| maxCommandRedirections | `16`    | The maximum number of times a command will be redirected due to `MOVED` or `ASK` errors                                                                                                                                                                                                                             |
| nodeAddressMap         |         | Defines the [node address mapping](#node-address-map)                                                                                                                                                                                                                                                               |
| modules                |         | Included [Redis Modules](../README.md#packages)                                                                                                                                                                                                                                                                     |
| scripts                |         | Script definitions (see [Lua Scripts](./programmability.md#lua-scripts))                                                                                                                                                                                                                                                    |
| functions              |         | Function definitions (see [Functions](./programmability.md#functions))                                                                                                                                                                                                                                                      |

## Usage

Most redis commands are the same as with individual clients.

### Unsupported Redis Commands

If you want to run commands and/or use arguments that Node Redis doesn't know about (yet!) use `.sendCommand()`.

When clustering, `sendCommand` takes 3 arguments to help with routing to the correct redis node:
* `firstKey`: the key that is being operated on, or `undefined` to route to a random node.
* `isReadOnly`: determines if the command needs to go to the master or may go to a replica.
*  `args`: the command and all arguments (including the key), as an array of strings.

```javascript
await cluster.sendCommand("key", false, ["SET", "key", "value", "NX"]); // 'OK'

await cluster.sendCommand("key", true, ["HGETALL", "key"]); // ['key1', 'field1', 'key2', 'field2']
```

## Root nodes vs. defaults

The configuration in `rootNodes` is only used for the connections that discover the cluster topology. It is **not** inherited by the connections the cluster then makes to the discovered nodes — those are created from the `defaults` option (plus the discovered host and port).

This means that any setting that should apply to every connection in the cluster — credentials, TLS, timeouts, etc. — must be specified via `defaults`, even if it is already present in a root node URL or configuration.

### Auth with password and username

Specifying the password in the URL or a root node will only affect the connection used for topology discovery. In case you want to set the password for all the connections being created from a cluster instance, use the `defaults` option.

```javascript
createCluster({
  rootNodes: [{
    url: 'redis://10.0.0.1:30001'
  }, {
    url: 'redis://10.0.0.2:30002'
  }],
  defaults: {
    username: 'username',
    password: 'password'
  }
});
```

### TLS

Likewise, TLS options specified in the URL (`rediss://`) or in a root node only affect the topology discovery connection. If your cluster requires TLS (e.g. AWS ElastiCache with in-transit encryption enabled), enable it via `defaults.socket`, otherwise the connections to the discovered nodes will be attempted in plaintext and can hang without an error:

```javascript
createCluster({
  rootNodes: [{
    url: 'rediss://external-host.io:30001'
  }],
  defaults: {
    socket: {
      tls: true
    }
  }
});
```

## Node Address Map

A mapping between the addresses in the cluster (see `CLUSTER SHARDS`) and the addresses the client should connect to.
Useful when the cluster is running on a different network to the client.

```javascript
const rootNodes = [{
  url: 'external-host-1.io:30001'
}, {
  url: 'external-host-2.io:30002'
}];

// Use either a static mapping:
createCluster({
  rootNodes,
  nodeAddressMap: {
    '10.0.0.1:30001': {
      host: 'external-host.io',
      port: 30001
    },
    '10.0.0.2:30002': {
      host: 'external-host.io',
      port: 30002
    }
  }
});

// or create the mapping dynamically, as a function:
createCluster({
  rootNodes,
  nodeAddressMap(address) {
    const indexOfDash = address.lastIndexOf('-'),
      indexOfDot = address.indexOf('.', indexOfDash),
      indexOfColons = address.indexOf(':', indexOfDot);
    
    return {
      host: `external-host-${address.substring(indexOfDash + 1, indexOfDot)}.io`,
      port: Number(address.substring(indexOfColons + 1))
    };
  }
});
```

> This is a common problem when using ElastiCache. See [Accessing ElastiCache from outside AWS](https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/accessing-elasticache.html) for more information on that.

### Events

The Node Redis Cluster class extends Node.js’s EventEmitter and emits the following events:

| Name                    | When                                                                               | Listener arguments                                        |
| ----------------------- | ---------------------------------------------------------------------------------- | --------------------------------------------------------- |
| `connect`               | The cluster has successfully connected and is ready to us                          | _No arguments_                                            |
| `disconnect`            | The cluster has disconnected                                                       | _No arguments_                                            |
| `error`                 | The cluster has errored                                                            | `(error: Error)`                                          |
| `node-ready`            | A cluster node is ready to establish a connection                                  | `(node: { host: string, port: number })`                  |
| `node-connect`          | A cluster node has connected                                                       | `(node: { host: string, port: number })`                  |
| `node-reconnecting`     | A cluster node is attempting to reconnect after an error                           | `(node: { host: string, port: number })`                  |
| `node-disconnect`       | A cluster node has disconnected                                                    | `(node: { host: string, port: number })`                  |
| `node-error`            | A cluster node has has errored (usually during TCP connection)                     | `(error: Error, node: { host: string, port: number })`    |

> :warning: You **MUST** listen to `error` events. If a cluster 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.

## Command Routing

### Commands that operate on Redis Keys

Commands such as `GET`, `SET`, etc. are routed by the first key specified. For example `MGET 1 2 3` will be routed by the key `1`.

### [Server Commands](https://redis.io/commands#server)

Admin commands such as `MEMORY STATS`, `FLUSHALL`, etc. are not attached to the cluster, and must be executed on a specific node via `.getSlotMaster()`.

### "Forwarded Commands"

Certain commands (e.g. `PUBLISH`) are forwarded to other cluster nodes by the Redis server. The client sends these commands to a random node in order to spread the load across the cluster.

### Transactions with `WATCH`

`WATCH` relies on connection-level state on a specific node, so it isn't exposed directly on the cluster client. Use `.getNodeClientForKey()` to get the node client responsible for a key's slot and run the optimistic-locking transaction on it:

```javascript
const key = 'key';
const nodeClient = await cluster.getNodeClientForKey(key);

await nodeClient.watch(key);
const value = await nodeClient.get(key);
const reply = await nodeClient
  .multi()
  .set(key, calculateNewValue(value)) // application logic
  .exec(); // `null` if `key` changed since `WATCH`, retry in that case
```

All keys touched in the transaction must hash to the same slot. Pass `true` as the second argument (`getNodeClientForKey(key, true)`) to allow a replica for read-only use.

---

## File: docs/command-options.md

# Command Options

> :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).

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](./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`

const proxyClient = client.withTypeMapping({
  [TYPES.BLOB_STRING]: Buffer
});

await proxyClient.get('key'); // `Buffer | null`
```

See [RESP](./RESP.md) for a full list of types.

### `DUMP`/`RESTORE` and Binary Data

[`DUMP`](https://redis.io/commands/dump/) returns serialized binary data. If blob strings are decoded as `string` (the default), the payload can be lossy and [`RESTORE`](https://redis.io/commands/restore/) may fail with `ERR DUMP payload version or checksum are wrong`.

Use `Buffer` mapping for blob strings:

```javascript
import { createClient, RESP_TYPES } from 'redis';

const client = createClient().withTypeMapping({
  [RESP_TYPES.BLOB_STRING]: Buffer
});

const dump = await client.dump('source');
await client.restore('destination', 0, dump);
```

You can also set it as a default:

```javascript
const client = createClient({
  commandOptions: {
    typeMapping: {
      [RESP_TYPES.BLOB_STRING]: Buffer
    }
  }
});
```

In v5, use `withTypeMapping` or `createClient({ commandOptions: { typeMapping } })` instead of the v4 `client.commandOptions({ returnBuffers: true })` call style.

## Abort Signal

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(),
  client = client.withAbortSignal(controller.signal);

try {
  const promise = client.get('key');
  controller.abort();
  await promise;
} catch (err) {
  // AbortError
}
```


## Timeout

This option is similar to the Abort Signal one, but provides an easier way to set timeout for commands. Again, this applies to commands that haven't been written to the socket yet.

Since v6, `commandOptions.timeout` defaults to `5000` (5 seconds). Set it to `undefined` (or `0`) to disable.

```javascript
const client = createClient({
  commandOptions: {
    timeout: 1000
  }
})
```

## ASAP

Commands that are executed in the "asap" mode are added to the beginning of the "to sent" queue.

```javascript
const asapClient = client.asap();
await asapClient.ping();
```

## `withCommandOptions`

You can set all of the above command options in a single call with the `withCommandOptions` function:

```javascript
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: ...
});
```

---

## File: docs/FAQ.md

# F.A.Q.

Nobody has *actually* asked these questions. But, we needed somewhere to put all the important bits and bobs that didn't fit anywhere else. So, here you go!

## What happens when the network goes down?

When a socket closes unexpectedly, all the commands that were already sent will reject as they might have been executed on the server. The rest will remain queued in memory until a new socket is established. If the client is closed—either by returning an error from [`reconnectStrategy`](./client-configuration.md#reconnect-strategy) or by manually calling `.disconnect()`—they will be rejected.

If don't want to queue commands in memory until a new socket is established, set the `disableOfflineQueue` option to `true` in the [client configuration](./client-configuration.md). This will result in those commands being rejected.

## How are commands batched?

Commands are pipelined using [`setImmediate`](https://nodejs.org/api/timers.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.

## `RedisClientType`

Redis has support for [modules](https://redis.io/modules) and running [Lua scripts](../README.md#lua-scripts) within the Redis context. To take advantage of typing within these scenarios, `RedisClient` and `RedisCluster` should be used with [typeof](https://www.typescriptlang.org/docs/handbook/2/typeof-types.html), rather than the base types `RedisClientType` and `RedisClusterType`.

```typescript
import { createClient } from '@redis/client';

export const client = createClient();

export type RedisClientType = typeof client;
```

---

## File: docs/pool.md

# `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 its 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
  }
}
```

---

## File: docs/programmability.md

# [Programmability](https://redis.io/docs/manual/programmability/)

Redis provides a programming interface allowing code execution on the redis server.

## [Functions](https://redis.io/docs/manual/programmability/functions-intro/)

The following example retrieves a key in redis, returning the value of the key, incremented by an integer. For example, if your key _foo_ has the value _17_ and we run `add('foo', 25)`, it returns the answer to Life, the Universe and Everything.

```lua
#!lua name=library

redis.register_function {
  function_name = 'add',
  callback = function(keys, args) return redis.call('GET', keys[1]) + args[1] end,
  flags = { 'no-writes' }
}
```

Here is the same example, but in a format that can be pasted into the `redis-cli`.

```
FUNCTION LOAD "#!lua name=library\nredis.register_function{function_name='add', callback=function(keys, args) return redis.call('GET', keys[1])+args[1] end, flags={'no-writes'}}"
```

Load the prior redis function on the _redis server_ before running the example below.

```typescript
import { CommandParser, createClient, RedisArgument } from '@redis/client';
import { NumberReply } from '@redis/client/dist/lib/RESP/types.js';

const client = createClient({
  functions: {
    library: {
      add: {
        NUMBER_OF_KEYS: 1,
        parseCommand(
          parser: CommandParser,
          key: RedisArgument,
          toAdd: RedisArgument
        ) {
          parser.pushKey(key)
          parser.push(toAdd)
        },
        transformReply: undefined as unknown as () => NumberReply
      }
    }
  }
});

await client.connect();
await client.set('key', '1');
await client.library.add('key', '2'); // 3
```

## [Lua Scripts](https://redis.io/docs/manual/programmability/eval-intro/)

The following is an end-to-end example of the prior concept.

```typescript
import { CommandParser, createClient, defineScript, RedisArgument } from '@redis/client';
import { NumberReply } from '@redis/client/dist/lib/RESP/types.js';

const client = createClient({
  scripts: {
    add: defineScript({
      SCRIPT: 'return redis.call("GET", KEYS[1]) + ARGV[1];',
      NUMBER_OF_KEYS: 1,
      FIRST_KEY_INDEX: 1,
      parseCommand(
        parser: CommandParser,
        key: RedisArgument,
        toAdd: RedisArgument
      ) {
        parser.pushKey(key)
        parser.push(toAdd)
      },
      transformReply: undefined as unknown as () => NumberReply
    })
  }
});

await client.connect();
await client.set('key', '1');
await client.add('key', '2'); // 3
```

---

## File: docs/pub-sub.md

# Pub/Sub

The Pub/Sub API is implemented by `RedisClient`, `RedisCluster`, and `RedisSentinel`.

## Pub/Sub with `RedisClient`

### RESP2

Using RESP2, Pub/Sub "takes over" the connection (a client with subscriptions will not execute commands), therefore it requires a dedicated connection. You can easily get one by `.duplicate()`ing an existing `RedisClient`:

```javascript
const subscriber = client.duplicate();
subscriber.on('error', err => console.error(err));
await subscriber.connect();
```

> When working with either `RedisCluster` or `RedisSentinel`, this is handled automatically for you.

### `sharded-channel-moved` event

`RedisClient` emits the `sharded-channel-moved` event when the ["cluster slot"](https://redis.io/docs/reference/cluster-spec/#key-distribution-model) of a subscribed [Sharded Pub/Sub](https://redis.io/docs/manual/pubsub/#sharded-pubsub) channel has been moved to another shard.

The event listener signature is as follows:
```typescript
(
  channel: string,
  listeners: {
    buffers: Set<Listener>;
    strings: Set<Listener>;
  }
)
```

> When working with `RedisCluster`, this is handled automatically for you.

## Subscribing

```javascript
const listener = (message, channel) => console.log(message, channel);
await client.subscribe('channel', listener);
await client.pSubscribe('channe*', listener);
// Use sSubscribe for sharded Pub/Sub:
await client.sSubscribe('channel', listener);
```

> ⚠️ Subscribing to the same channel more than once will create multiple listeners, each of which will be called when a message is received.

## Publishing

```javascript
await client.publish('channel', 'message');
// Use sPublish for sharded Pub/Sub:
await client.sPublish('channel', 'message');
```

## Unsubscribing

The code below unsubscribes all listeners from all channels.

```javascript
await client.unsubscribe();
await client.pUnsubscribe();
// Use sUnsubscribe for sharded Pub/Sub:
await client.sUnsubscribe();
```

To unsubscribe from specific channels:

```javascript
await client.unsubscribe('channel');
await client.unsubscribe(['1', '2']);
```

To unsubscribe a specific listener:

```javascript
await client.unsubscribe('channel', listener);
```

## Buffers

Publishing and subscribing using `Buffer`s is also supported:

```javascript
await subscriber.subscribe('channel', message => {
  console.log(message); // 
}, true); // true = subscribe in `Buffer` mode.

await subscriber.publish(Buffer.from('channel'), Buffer.from('message'));
```

> NOTE: Buffers and strings are supported both for the channel name and the message. You can mix and match these as desired.

---

## File: docs/RESP.md

# Mapping RESP types

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 [official documentation](https://redis.io/docs/reference/protocol-spec/).

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`
- Blob String (`$`) => `string | Buffer`
- Simple Error (`-`) => `ErrorReply`
- Array (`*`) => `Array`

> NOTE: the first type is the default type

## RESP3

- Null (`_`) => `null`
- Boolean (`#`) => `boolean`
- Number (`:`) => `number | string`
- Big Number (`(`) => `BigInt | string`
- Double (`,`) => `number | string`
- Simple String (`+`) => `string | Buffer`
- Blob String (`$`) => `string | Buffer`
- Verbatim String (`=`) => `string | Buffer | VerbatimString` 
- Simple Error (`-`) => `ErrorReply`
- Blob Error (`!`) => `ErrorReply`
- Array (`*`) => `Array`
- Set (`~`) => `Array | Set`
- Map (`%`) => `object | Map | Array`
- Push (`>`) => `Array` => PubSub push/`'push'` event

> NOTE: the first type is the default type

### Map keys and Set members

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

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:

- [Attribute type](https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md#attribute-type)
- [Streamed strings](https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md#streamed-strings)
- [Streamed aggregated data types](https://github.com/redis/redis-specifications/blob/master/protocol/RESP3.md#streamed-aggregated-data-types)

---

## File: docs/scan-iterators.md

# Scan Iterators

> :warning: The scan iterators API in v5 has breaking changes from the previous version. For more details, refer to the [v4-to-v5 guide](./v4-to-v5.md#scan-iterators).

[`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):

```javascript
for await (const keys of client.scanIterator()) {
  const values = await client.mGet(keys);
}
```

> :warning: **`keyPrefix` gotcha:** When the client is configured with a [`keyPrefix`](./client-configuration.md#key-prefixing), `scanIterator` yields the **already-prefixed** keys as stored on the server (replies are never un-prefixed). Passing them straight back into a key-prefixing command — like the `mGet(keys)` above — prefixes them a **second** time. For example, with `keyPrefix: 'app:'` a yielded `'app:foo'` becomes `'app:app:foo'`, so the `mGet` silently reads the wrong keys. Strip the prefix before reusing the keys, or read them with a separate client that has no `keyPrefix`. (This affects only `scanIterator`; `HSCAN`/`SSCAN`/`ZSCAN` yield fields/members, which are not keys.)

This works with `HSCAN`, `SSCAN`, and `ZSCAN` too:

```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')) {}
```

You can override the default options by providing a configuration object:

```javascript
client.scanIterator({
  cursor: '0', // optional, defaults to '0'
  TYPE: 'string', // `SCAN` only
  MATCH: 'patter*',
  COUNT: 100
});
```

## 2. Official Technical Reference & Guides (redis/docs)

# Redis Docs

> Note: if you are an AI agent, please see [AI_AGENT_DEVELOPER_GUIDE.md](AI_AGENT_DEVELOPER_GUIDE.md) for more information.

PRs are merged to the `main` branch of this repo. Merging to `main` automatically builds and publishes your changes to the live docs site (under `/latest`) — there is no separate manual publish step, so allow a little time for the build and CDN cache to refresh after your PR is merged.
Before your PR is merged, you can preview your changes on a per-branch staging URL; the docs team's bot posts the staging links as a comment on your PR.

## Site template files and folders

* **/archetypes**: A Markdown file needs to have some front matter. An archetype defines which front matter is used when using `hugo new content`. Right now, the only supported archetype is the default one. **Note:** We might want to add additional archetypes in the future because most of our pages contain additional meta data properties like `linkTitle`.
* **/content**: This folder contains the markdown files. We will have the subfolders like `/develop`, `/integrate`, and `/operate`
* **/assets**: CSS files, site-wide icons, and images.
* **/data**: Data files that are accessed by Hugo and rendered with the help of short codes or partials.
* **/layouts/partials**: HTML templates that are used across sites. Examples are TOCs, breadcrumbs, or headers.
* **/layouts/$type**: Each page type has at least the following templates to implement `single.html` and `list.html`. The `single` template is used to render a discrete page. The `list` template is used to render a collection of related pages (e.g., all sub-pages).
* **/layouts/home.html**: The home page of the site, that is, the page that is displayed when you open the root path.
* **/layouts/404.html**: The default 404 page.
* **/layouts/shortcodes** HTML template snippets that you want to leverage within Markdown files. Examples are tabbed code examples or notification blocks.
* **/public**: This is the folder that is generated by Hugo. This content needs to be published by site maintainers.
* **/static**: Any static files that need to be accessed by the site, e.g., CSS or JavaScript.
* **/package.json**: Node.js dependencies. Tailwind, for example, is installed via the Node package manager (`npm`).
* **/config.toml**: Hugo's site configuration, like the root path and menu items. Hugo can access configuration elements when rendering the site. So you can define custom configuration settings here.
* **/Makefile**: We use make to wrap some Hugo commands and to add additional build steps.
* **/tailwind.config.js**: This is the Tailwind CSS framwork's configuration file.
* **/postcss.config.js**: Needed to make Tailwind statically accessible to the site.

## Requirements

You will need the following tools to build the site locally:

- `python3`: Python >= 3.8.
- `node` and `npm`: Node.js >= 19.7.0, and the Node.js package manager >= 9.5.0.
- `hugo`: Hugo site generator, v0.143.0; the extended edition.
- `make`: To run the makefile.
- `git`: To manage the source code of this repo.

## Build script and data files

The site can be built using the command `make all`. Here is what's executed:

1. Clean the current folder by deleting the generated output of a previous build.
1. Install the necessary Node.js and Python dependencies.
1. Build the components that were fetched from external Github repositories (e.g., client examples). This fetches source code files and generates data files.
1. Execute the `hugo` command to generate the HTML pages. The build result can be found within the folder `/public`.

The command `make serve` performs the same steps, but serves the web pages on the local host for development purposes.

The `Makefile` contains details about the executed commands.

N.B. once you've built the site once, you can continuing working without rebuilding every time you make a change.
Use `hugo serve` to serve the docs locally.
The only time you really need to rebuild the site locally is when new code samples need to be pulled from the client sites.

## Build pipeline

The build pipeline that is defined within `.github/workflows/main.yml` builds the documentation and uploads it into a GCS bucket:

1. Install Hugo
1. Check the branch out
1. Perform the build as stated before by using `make all`
1. Install the Google Cloud CLI
1. Authenticate to the GCS bucket
1. Validate the branch name
1. Sync the branch with a GCS folder


## Hugo specifics

### Relative links

We are using the following syntax for Hugo relrefs:

```
[Link title]({{< relref "link relative to the site's base url" >}})
```

Here is an example:

```
[Data structure store]({{< relref "/develop/get-started/data-store" >}})
```

It's strongly advised to use `relref` because it provides the following advantages:

1. Links are checked at build time. Any broken references within the site are reported, stopping the build.
2. References are prefixed with the site's base URL, which means that they work in builds with a different base URL.


The following needs to be taken into account when using `relref`: The reference `/develop/get-started/data-store` and `/develop/get-started/data-store/` aren't the same. You must use the trailing slash if the referenced article is an `_index.md` file within a folder (e.g., `.../data-store/` for `.../data-store/_index.md`). Otherwise, you should not use the trailing slash (e.g., `.../get-started/data-store.md`).

### Images

The image shortcode doesn't need to be closed anymore. Here is an example;

```
{{< image filename="/images/rc/icon-database-update-status-pending.png" alt="Database update" >}}
```

The `filename` property value can be any file name path which is relative to the site's base path.

We added a new property `class` which allows you to override the CSS class of the image. Images have by default a `block` display in Tailwind. You can change this by setting the class property to `inline`. The following example shows two images that are in a single line:

```
{{< image filename="/images/rc/icon-database-update-status-pending.png#no-click" alt="Pending database status" class="inline" >}} &nbsp; {{< image filename="/images/rc/icon-database-status-active.png#no-click" alt="Active database status" class="inline" >}}
```

### Templating

Variables are scoped in the context of their block. Overriding a variable within an if block doesn't change the variable value as soon as you leave that block. This is a specific limitation of the Go templating language.

The following will give you the `/` outside of the condition block if the path was initially set to `/`:

```
{{ $path := $parsed.Path }}
{{ if (eq $path "/") }}
	{{ $path = "" }}
{{ end }}

{{- $path -}}
```

The solution is to use a scratchpad for storing the 'global' state:

```
{{ $path := $parsed.Path }}
{{ if (eq $path "/") }}
	{{ .Scratch.Set "path" ""}}
{{ else }}
    {{ .Scratch.Set "path" $path}}
{{ end }}

{{- .Scratch.Get "path" -}}
```

### Child pages

The redis.io template adds a list of links to child pages at the bottom of each section page. You can prevent this by adding the following front matter property:

```
hideListLinks: true
```


## Tailwind specifics

### Fonts

The fonts are located in the folder `static/fonts`. This folder contains typically file of the formats `ttf`, `woff`, or `woff2`. You can perform the following steps to add a font:

* Copy your font files to the fonts folder `static/fonts`.
* Ensure that the font is loaded by adding it to the partial `layouts/partials/fonts.html`. This partial defines the font using a style sheet and ensures that the font resources are pre-loaded.

    Here is an example that shows how to define the font face 'Geist Mono' with a regular font weight:

    ```
    @font-face {
        font-family: 'Geist Mono';
        src: url('{{ $basePath }}/fonts/GeistMono-Regular.woff2') format('woff2');
        font-weight: 400;
        font-style: normal;
        font-display: swap;
    }
    ```

* Make your font available via Tailwind by adding it to the `tailwind.config.js` file.

    ```
    module.exports = {
      content: ["content/**/*.md", "layouts/**/*.html", "./tailwindcss.whitelist.txt"],
      theme: {
        extend: {
          fontFamily: {
                                    sans: [ 'Space Grotesk', ...defaultTheme.fontFamily.sans ],
                                    mono: [ 'Space Mono', 'SF Mono', ...defaultTheme.fontFamily.mono ],
                                    monogeist: ['Geist Mono', ...defaultTheme.fontFamily.mono ],
          },
    	  ...
    ```

* Use a `font-*` CSS class within HTML or your style sheets, whereby the `*`  needs to be substituted with one of the Tailwind fonts that you defined in the Tailwind configuration file (`tailwind.config.js`).

    Here is an example how to use `font-monogeist` in the global CSS file `assets/css/index.css` to change the font of code blocks:

    ```
    .prose pre > code {
      @apply bg-none font-monogeist;
    }
    ```


## Troubleshooting

### NIL pointer error when rendering a section

You might sometimes see the following error message:

```
nil pointer evaluating page.Page.Params
```

This happened for us when Hugo executed the `bannerText` logic, which inspected all parent pages by checking if they have a banner text set. The problem was that one of the parent folders was missing an `_index.md` file, which meant the folder couldn't be identified as a page. The solution was to add an `_index.md` file to the folder that didn't have it yet.