From 5ba5e093a87b2c4353f1338dd6b4956597d74368 Mon Sep 17 00:00:00 2001 From: Leibale Eidelman Date: Mon, 19 Feb 2024 13:10:38 -0800 Subject: [PATCH 1/5] Update pub-sub.md --- docs/pub-sub.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pub-sub.md b/docs/pub-sub.md index bd1d842e0e5..f295010a4a8 100644 --- a/docs/pub-sub.md +++ b/docs/pub-sub.md @@ -1,6 +1,6 @@ # 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` From cf5587ec4a44d349964610f8f047efc9e5928306 Mon Sep 17 00:00:00 2001 From: Leibale Eidelman Date: Mon, 19 Feb 2024 13:18:40 -0800 Subject: [PATCH 2/5] Update pub-sub.md --- docs/pub-sub.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/pub-sub.md b/docs/pub-sub.md index f295010a4a8..7bbb0733c18 100644 --- a/docs/pub-sub.md +++ b/docs/pub-sub.md @@ -4,7 +4,9 @@ The Pub/Sub API is implemented by `RedisClient`, `RedisCluster`, and `RedisSenti ## 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(); @@ -12,7 +14,7 @@ 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 @@ -29,6 +31,8 @@ The event listener signature is as follows: ) ``` +> When working with `RedisCluster`, this is handled automatically for you. + ## Subscribing ```javascript @@ -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 From 06ba71370643d2995aab27f1c557c76685893417 Mon Sep 17 00:00:00 2001 From: Leibale Eidelman Date: Mon, 19 Feb 2024 13:26:59 -0800 Subject: [PATCH 3/5] copy #2628 into v5 --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 783f6785bab..0706e1bb770 100644 --- a/README.md +++ b/README.md @@ -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): From 7e27f72f72eb79dbb7822babc3313c83e6c8bbfe Mon Sep 17 00:00:00 2001 From: Leibale Eidelman Date: Mon, 19 Feb 2024 13:28:47 -0800 Subject: [PATCH 4/5] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0706e1bb770..990204eb3df 100644 --- a/README.md +++ b/README.md @@ -48,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 | From 3e167912fbf55e20c9ffb8a8f09bb6778ce33b2c Mon Sep 17 00:00:00 2001 From: Shaya Potter Date: Wed, 21 Feb 2024 10:29:24 +0200 Subject: [PATCH 5/5] More fixes for socket issue (#2710) * more typing fixes * try to redo typing a bit and genericize to make better * use genericized cluster options for cluster as well --- packages/client/lib/client/index.ts | 5 ++-- packages/client/lib/client/socket.ts | 43 ++++++++++++++++----------- packages/client/lib/cluster/index.ts | 3 +- packages/client/lib/sentinel/index.ts | 16 ++++++---- packages/client/lib/sentinel/types.ts | 5 ++-- packages/client/lib/sentinel/utils.ts | 8 +++-- 6 files changed, 48 insertions(+), 32 deletions(-) diff --git a/packages/client/lib/client/index.ts b/packages/client/lib/client/index.ts index 3a14dc40e1a..3efa793eeb9 100644 --- a/packages/client/lib/client/index.ts +++ b/packages/client/lib/client/index.ts @@ -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 { /** * `redis[s]://[[username][:password]@][host][:port][/db-number]` @@ -31,7 +32,7 @@ export interface RedisClientOptions< /** * Socket connection properties */ - socket?: RedisSocketOptions; + socket?: SocketOptions; /** * ACL username ([see ACL guide](https://redis.io/topics/acl)) */ diff --git a/packages/client/lib/client/socket.ts b/packages/client/lib/client/socket.ts index 03dbcde1546..dcadad4c3dd 100644 --- a/packages/client/lib/client/socket.ts +++ b/packages/client/lib/client/socket.ts @@ -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) */ @@ -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; diff --git a/packages/client/lib/cluster/index.ts b/packages/client/lib/cluster/index.ts index e26582942d2..d6018fc270e 100644 --- a/packages/client/lib/cluster/index.ts +++ b/packages/client/lib/cluster/index.ts @@ -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, @@ -21,7 +22,7 @@ interface ClusterCommander< } export type RedisClusterClientOptions = Omit< - RedisClientOptions, + RedisClientOptions, keyof ClusterCommander >; diff --git a/packages/client/lib/sentinel/index.ts b/packages/client/lib/sentinel/index.ts index 210bf954ea9..57819133e0c 100644 --- a/packages/client/lib/sentinel/index.ts +++ b/packages/client/lib/sentinel/index.ts @@ -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; @@ -578,8 +580,8 @@ class RedisSentinelInternal< } readonly #name: string; - readonly #nodeClientOptions: RedisClientOptions; - readonly #sentinelClientOptions: RedisClientOptions; + readonly #nodeClientOptions: RedisClientOptions; + readonly #sentinelClientOptions: RedisClientOptions; readonly #scanInterval: number; readonly #passthroughClientErrorEvents: boolean; @@ -624,12 +626,12 @@ class RedisSentinelInternal< this.#scanInterval = options.scanInterval ?? 0; this.#passthroughClientErrorEvents = options.passthroughClientErrorEvents ?? false; - this.#nodeClientOptions = options.nodeClientOptions ? Object.assign({} as RedisClientOptions, options.nodeClientOptions) : {}; + this.#nodeClientOptions = options.nodeClientOptions ? Object.assign({} as RedisClientOptions, options.nodeClientOptions) : {}; if (this.#nodeClientOptions.url !== undefined) { throw new Error("invalid nodeClientOptions for Sentinel"); } - this.#sentinelClientOptions = options.sentinelClientOptions ? Object.assign({} as RedisClientOptions, options.sentinelClientOptions) : {}; + this.#sentinelClientOptions = options.sentinelClientOptions ? Object.assign({} as RedisClientOptions, options.sentinelClientOptions) : {}; this.#sentinelClientOptions.modules = RedisSentinelModule; if (this.#sentinelClientOptions.url !== undefined) { @@ -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 { /* @@ -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)) { diff --git a/packages/client/lib/sentinel/types.ts b/packages/client/lib/sentinel/types.ts index 571100766e3..1f868ec5177 100644 --- a/packages/client/lib/sentinel/types.ts +++ b/packages/client/lib/sentinel/types.ts @@ -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; @@ -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; + nodeClientOptions?: RedisClientOptions; /** * The configuration values for every sentinel in the cluster. Use this for example when specifying an ACL user to connect with */ - sentinelClientOptions?: RedisClientOptions; + sentinelClientOptions?: RedisClientOptions; /** * The number of clients connected to the master node */ diff --git a/packages/client/lib/sentinel/utils.ts b/packages/client/lib/sentinel/utils.ts index 44de98fe71a..4ae829183a2 100644 --- a/packages/client/lib/sentinel/utils.ts +++ b/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'; @@ -27,9 +27,11 @@ export function createNodeList(nodes: Array) { } export function clientSocketToNode(socket: RedisSocketOptions): RedisNode { + const s = socket as RedisTcpSocketOptions; + return { - host: socket.host!, - port: socket.port! + host: s.host!, + port: s.port! } }