Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/v5' into restructure-commands
Browse files Browse the repository at this point in the history
  • Loading branch information
sjpotter committed Feb 21, 2024
2 parents d9d3cdd + 3e16791 commit 1b262ba
Show file tree
Hide file tree
Showing 8 changed files with 71 additions and 37 deletions.
16 changes: 15 additions & 1 deletion README.md
Expand Up @@ -11,6 +11,20 @@

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-server via docker (or any other method you prefer):
Expand All @@ -34,7 +48,7 @@ npm install redis
| [`redis`](./packages/redis) | The client with all the ["redis-stack"](https://github.com/redis-stack/redis-stack) modules |
| [`@redis/client`](./packages/client) | The base clients (i.e `RedisClient`, `RedisCluster`, etc.) |
| [`@redis/bloom`](./packages/bloom) | [Redis Bloom](https://redis.io/docs/data-types/probabilistic/) commands |
| [`redis/graph`](./packages/graph) | [Redis Graph](https://redis.io/docs/data-types/probabilistic/) commands |
| [`@redis/graph`](./packages/graph) | [Redis Graph](https://redis.io/docs/data-types/probabilistic/) commands |
| [`@redis/json`](./packages/json) | [Redis JSON](https://redis.io/docs/data-types/json/) commands |
| [`@redis/search`](./packages/search) | [RediSearch](https://redis.io/docs/interact/search-and-query/) commands |
| [`@redis/time-series`](./packages/time-series) | [Redis Time-Series](https://redis.io/docs/data-types/timeseries/) commands |
Expand Down
12 changes: 8 additions & 4 deletions docs/pub-sub.md
@@ -1,18 +1,20 @@
# Pub/Sub

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

## Pub/Sub with `RedisClient`

Pub/Sub requires a dedicated stand-alone client. You can easily get one by `.duplicate()`ing an existing `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 a `RedisCluster`, this is handled automatically for you.
> When working with either `RedisCluster` or `RedisSentinel`, this is handled automatically for you.
### `sharded-channel-moved` event

Expand All @@ -29,6 +31,8 @@ The event listener signature is as follows:
)
```

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

```javascript
Expand All @@ -39,7 +43,7 @@ await client.pSubscribe('channe*', listener);
await client.sSubscribe('channel', listener);
```

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

Expand Down
5 changes: 3 additions & 2 deletions packages/client/lib/client/index.ts
Expand Up @@ -21,7 +21,8 @@ export interface RedisClientOptions<
F extends RedisFunctions = RedisFunctions,
S extends RedisScripts = RedisScripts,
RESP extends RespVersions = RespVersions,
TYPE_MAPPING extends TypeMapping = TypeMapping
TYPE_MAPPING extends TypeMapping = TypeMapping,
SocketOptions extends RedisSocketOptions = RedisSocketOptions
> extends CommanderConfig<M, F, S, RESP> {
/**
* `redis[s]://[[username][:password]@][host][:port][/db-number]`
Expand All @@ -31,7 +32,7 @@ export interface RedisClientOptions<
/**
* Socket connection properties
*/
socket?: RedisSocketOptions;
socket?: SocketOptions;
/**
* ACL username ([see ACL guide](https://redis.io/topics/acl))
*/
Expand Down
43 changes: 25 additions & 18 deletions packages/client/lib/client/socket.ts
Expand Up @@ -9,25 +9,9 @@ type NetOptions = {
tls?: false;
};

type TcpOptions = NetOptions & Omit<
net.TcpNetConnectOpts,
'timeout' | 'onread' | 'readable' | 'writable' | 'port'
> & {
port?: number;
};

type IpcOptions = NetOptions & Omit<
net.IpcNetConnectOpts,
'timeout' | 'onread' | 'readable' | 'writable'
>;

type TlsOptions = {
tls: true;
} & tls.ConnectionOptions;

type ReconnectStrategyFunction = (retries: number, cause: Error) => false | Error | number;

export type RedisSocketOptions = {
type RedisSocketOptionsCommon = {
/**
* Connection timeout (in milliseconds)
*/
Expand All @@ -39,7 +23,30 @@ export type RedisSocketOptions = {
* 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.
*/
reconnectStrategy?: false | number | ReconnectStrategyFunction;
} & (TcpOptions | IpcOptions | TlsOptions);
}

type RedisTcpOptions = RedisSocketOptionsCommon & NetOptions & Omit<
net.TcpNetConnectOpts,
'timeout' | 'onread' | 'readable' | 'writable' | 'port'
> & {
port?: number;
};

type RedisTlsOptions = RedisSocketOptionsCommon & tls.ConnectionOptions & {
tls: true;
host: string;
}

type RedisIpcOptions = RedisSocketOptionsCommon & Omit<
net.IpcNetConnectOpts,
'timeout' | 'onread' | 'readable' | 'writable'
> & {
tls: false;
}

export type RedisTcpSocketOptions = RedisTcpOptions | RedisTlsOptions;

export type RedisSocketOptions = RedisTcpSocketOptions | RedisIpcOptions;

export type RedisSocketInitiator = () => void | Promise<unknown>;

Expand Down
3 changes: 2 additions & 1 deletion packages/client/lib/cluster/index.ts
Expand Up @@ -8,6 +8,7 @@ import RedisClusterSlots, { NodeAddressMap, ShardNode } from './cluster-slots';
import RedisClusterMultiCommand, { RedisClusterMultiCommandType } from './multi-command';
import { PubSubListener } from '../client/pub-sub';
import { ErrorReply } from '../errors';
import { RedisTcpSocketOptions } from '../client/socket';

interface ClusterCommander<
M extends RedisModules,
Expand All @@ -21,7 +22,7 @@ interface ClusterCommander<
}

export type RedisClusterClientOptions = Omit<
RedisClientOptions,
RedisClientOptions<RedisModules, RedisFunctions, RedisScripts, RespVersions, TypeMapping, RedisTcpSocketOptions>,
keyof ClusterCommander<RedisModules, RedisFunctions, RedisScripts, RespVersions, TypeMapping/*, CommandPolicies*/>
>;

Expand Down
16 changes: 10 additions & 6 deletions packages/client/lib/sentinel/index.ts
Expand Up @@ -14,6 +14,8 @@ import { setTimeout } from 'node:timers/promises';
import RedisSentinelModule from './module'
import { RedisVariadicArgument } from '../commands/generic-transformers';
import { WaitQueue } from './wait-queue';
import { TcpNetConnectOpts } from 'node:net';
import { RedisTcpSocketOptions } from '../client/socket';

interface ClientInfo {
id: number;
Expand Down Expand Up @@ -578,8 +580,8 @@ class RedisSentinelInternal<
}

readonly #name: string;
readonly #nodeClientOptions: RedisClientOptions<M, F, S, RESP, TYPE_MAPPING>;
readonly #sentinelClientOptions: RedisClientOptions<typeof RedisSentinelModule, F, S, RESP, TYPE_MAPPING>;
readonly #nodeClientOptions: RedisClientOptions<M, F, S, RESP, TYPE_MAPPING, RedisTcpSocketOptions>;
readonly #sentinelClientOptions: RedisClientOptions<typeof RedisSentinelModule, F, S, RESP, TYPE_MAPPING, RedisTcpSocketOptions>;
readonly #scanInterval: number;
readonly #passthroughClientErrorEvents: boolean;

Expand Down Expand Up @@ -624,12 +626,12 @@ class RedisSentinelInternal<
this.#scanInterval = options.scanInterval ?? 0;
this.#passthroughClientErrorEvents = options.passthroughClientErrorEvents ?? false;

this.#nodeClientOptions = options.nodeClientOptions ? Object.assign({} as RedisClientOptions<M, F, S, RESP, TYPE_MAPPING>, options.nodeClientOptions) : {};
this.#nodeClientOptions = options.nodeClientOptions ? Object.assign({} as RedisClientOptions<M, F, S, RESP, TYPE_MAPPING, RedisTcpSocketOptions>, options.nodeClientOptions) : {};
if (this.#nodeClientOptions.url !== undefined) {
throw new Error("invalid nodeClientOptions for Sentinel");
}

this.#sentinelClientOptions = options.sentinelClientOptions ? Object.assign({} as RedisClientOptions<typeof RedisSentinelModule, F, S, RESP, TYPE_MAPPING>, options.sentinelClientOptions) : {};
this.#sentinelClientOptions = options.sentinelClientOptions ? Object.assign({} as RedisClientOptions<typeof RedisSentinelModule, F, S, RESP, TYPE_MAPPING, RedisTcpSocketOptions>, options.sentinelClientOptions) : {};
this.#sentinelClientOptions.modules = RedisSentinelModule;

if (this.#sentinelClientOptions.url !== undefined) {
Expand Down Expand Up @@ -754,7 +756,8 @@ class RedisSentinelInternal<
await this.#reset();
continue;
}
this.#trace("attemping to send command to " + client.options?.socket?.host + ":" + client.options?.socket?.port)
const sockOpts = client.options?.socket as TcpNetConnectOpts | undefined;
this.#trace("attemping to send command to " + sockOpts?.host + ":" + sockOpts?.port)

try {
/*
Expand Down Expand Up @@ -1198,7 +1201,8 @@ class RedisSentinelInternal<

if (replicaCloseSet.has(str) || !replica.isOpen) {
if (replica.isOpen) {
this.#trace(`destroying replica client to ${replica.options?.socket?.host}:${replica.options?.socket?.port}`);
const sockOpts = replica.options?.socket as TcpNetConnectOpts | undefined;
this.#trace(`destroying replica client to ${sockOpts?.host}:${sockOpts?.port}`);
replica.destroy()
}
if (!removedSet.has(str)) {
Expand Down
5 changes: 3 additions & 2 deletions packages/client/lib/sentinel/types.ts
Expand Up @@ -3,6 +3,7 @@ import { CommandOptions } from '../client/commands-queue';
import { CommandSignature, CommanderConfig, RedisFunctions, RedisModules, RedisScripts, RespVersions, TypeMapping } from '../RESP/types';
import COMMANDS from '../commands';
import RedisSentinel, { RedisSentinelClient } from '.';
import { RedisTcpSocketOptions } from '../client/socket';

export interface RedisNode {
host: string;
Expand Down Expand Up @@ -31,11 +32,11 @@ export interface RedisSentinelOptions<
/**
* The configuration values for every node in the cluster. Use this for example when specifying an ACL user to connect with
*/
nodeClientOptions?: RedisClientOptions<M, F, S, RESP, TYPE_MAPPING>;
nodeClientOptions?: RedisClientOptions<M, F, S, RESP, TYPE_MAPPING, RedisTcpSocketOptions>;
/**
* The configuration values for every sentinel in the cluster. Use this for example when specifying an ACL user to connect with
*/
sentinelClientOptions?: RedisClientOptions<M, F, S, RESP, TYPE_MAPPING>;
sentinelClientOptions?: RedisClientOptions<M, F, S, RESP, TYPE_MAPPING, RedisTcpSocketOptions>;
/**
* The number of clients connected to the master node
*/
Expand Down
8 changes: 5 additions & 3 deletions packages/client/lib/sentinel/utils.ts
@@ -1,5 +1,5 @@
import { Command, RedisFunction, RedisScript, RespVersions } from '../RESP/types';
import { RedisSocketOptions } from '../client/socket';
import { RedisSocketOptions, RedisTcpSocketOptions } from '../client/socket';
import { functionArgumentsPrefix, getTransformReply, scriptArgumentsPrefix } from '../commander';
import { NamespaceProxySentinel, NamespaceProxySentinelClient, NodeInfo, ProxySentinel, ProxySentinelClient, RedisNode } from './types';

Expand Down Expand Up @@ -27,9 +27,11 @@ export function createNodeList(nodes: Array<NodeInfo>) {
}

export function clientSocketToNode(socket: RedisSocketOptions): RedisNode {
const s = socket as RedisTcpSocketOptions;

return {
host: socket.host!,
port: socket.port!
host: s.host!,
port: s.port!
}
}

Expand Down

0 comments on commit 1b262ba

Please sign in to comment.