Skip to content

Commit 1d51c69

Browse files
authored
Merge pull request #368 from mike182uk/redis-cluster-support
2 parents 7c78ce1 + 2f1a39f commit 1d51c69

File tree

4 files changed

+79
-13
lines changed

4 files changed

+79
-13
lines changed

CHANGES.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ To be released.
6464
hanging on slow or unresponsive servers.
6565
[[#258] by Hyunchae Kim]
6666

67+
[#353]: https://github.com/fedify-dev/fedify/issues/353
68+
[#365]: https://github.com/fedify-dev/fedify/pull/365
69+
6770
### @fedify/next
6871

6972
- Created [Next.js] integration as the *@fedify/next* package.
@@ -72,6 +75,13 @@ To be released.
7275
[Next.js]: https://nextjs.org/
7376
[#313]: https://github.com/fedify-dev/fedify/issues/313
7477

78+
### @fedify/redis
79+
80+
- Added support for Redis Cluster to the *@fedify/redis* package.
81+
[[#368] by Michael Barrett]
82+
83+
[#368]: https://github.com/fedify-dev/fedify/pull/368
84+
7585

7686
Version 1.8.5
7787
-------------

packages/redis/README.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,27 @@ implementations for Redis:
1616
~~~~ typescript
1717
import { createFederation } from "@fedify/fedify";
1818
import { RedisKvStore, RedisMessageQueue } from "@fedify/redis";
19-
import { Redis } from "ioredis";
19+
import { Redis, Cluster } from "ioredis";
2020

21+
// Using a standalone Redis instance:
2122
const federation = createFederation({
2223
kv: new RedisKvStore(new Redis()),
2324
queue: new RedisMessageQueue(() => new Redis()),
2425
});
26+
27+
// Using a Redis Cluster:
28+
const federation = createFederation({
29+
kv: new RedisKvStore(new Cluster([
30+
{ host: "127.0.0.1", port: 7000 },
31+
{ host: "127.0.0.1", port: 7001 },
32+
{ host: "127.0.0.1", port: 7002 },
33+
])),
34+
queue: new RedisMessageQueue(() => new Cluster([
35+
{ host: "127.0.0.1", port: 7000 },
36+
{ host: "127.0.0.1", port: 7001 },
37+
{ host: "127.0.0.1", port: 7002 },
38+
])),
39+
});
2540
~~~~
2641

2742
[JSR]: https://jsr.io/@fedify/redis

packages/redis/src/kv.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { KvKey, KvStore, KvStoreSetOptions } from "@fedify/fedify";
2-
import type { Redis, RedisKey } from "ioredis";
2+
import type { Cluster, Redis, RedisKey } from "ioredis";
33
import { Buffer } from "node:buffer";
44
import { type Codec, JsonCodec } from "./codec.ts";
55

@@ -27,26 +27,38 @@ export interface RedisKvStoreOptions {
2727
* ```ts ignore
2828
* import { createFederation } from "@fedify/fedify";
2929
* import { RedisKvStore } from "@fedify/redis";
30-
* import { Redis } from "ioredis";
30+
* import { Redis, Cluster } from "ioredis";
3131
*
32+
* // Using a standalone Redis instance:
3233
* const federation = createFederation({
3334
* // ...
3435
* kv: new RedisKvStore(new Redis()),
3536
* });
37+
*
38+
* // Using a Redis Cluster:
39+
* const cluster = new Cluster([
40+
* { host: "127.0.0.1", port: 7000 },
41+
* { host: "127.0.0.1", port: 7001 },
42+
* { host: "127.0.0.1", port: 7002 },
43+
* ]);
44+
* const federation = createFederation({
45+
* // ...
46+
* kv: new RedisKvStore(cluster),
47+
* });
3648
* ```
3749
*/
3850
export class RedisKvStore implements KvStore {
39-
#redis: Redis;
51+
#redis: Redis | Cluster;
4052
#keyPrefix: RedisKey;
4153
#codec: Codec;
4254
#textEncoder = new TextEncoder();
4355

4456
/**
4557
* Creates a new Redis key–value store.
46-
* @param redis The Redis client to use.
58+
* @param redis The Redis client (standalone or cluster) to use.
4759
* @param options The options for the key–value store.
4860
*/
49-
constructor(redis: Redis, options: RedisKvStoreOptions = {}) {
61+
constructor(redis: Redis | Cluster, options: RedisKvStoreOptions = {}) {
5062
this.#redis = redis;
5163
this.#keyPrefix = options.keyPrefix ?? "fedify::";
5264
this.#codec = options.codec ?? new JsonCodec();

packages/redis/src/mq.ts

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type {
55
MessageQueueListenOptions,
66
} from "@fedify/fedify";
77
import { getLogger } from "@logtape/logtape";
8-
import type { Redis, RedisKey } from "ioredis";
8+
import type { Cluster, Redis, RedisKey } from "ioredis";
99
import { type Codec, JsonCodec } from "./codec.ts";
1010

1111
const logger = getLogger(["fedify", "redis", "mq"]);
@@ -63,17 +63,28 @@ export interface RedisMessageQueueOptions {
6363
* ```ts ignore
6464
* import { createFederation } from "@fedify/fedify";
6565
* import { RedisMessageQueue } from "@fedify/redis";
66-
* import { Redis } from "ioredis";
66+
* import { Redis, Cluster } from "ioredis";
6767
*
68+
* // Using a standalone Redis instance:
6869
* const federation = createFederation({
6970
* // ...
7071
* queue: new RedisMessageQueue(() => new Redis()),
7172
* });
73+
*
74+
* // Using a Redis Cluster:
75+
* const federation = createFederation({
76+
* // ...
77+
* queue: new RedisMessageQueue(() => new Cluster([
78+
* { host: "127.0.0.1", port: 7000 },
79+
* { host: "127.0.0.1", port: 7001 },
80+
* { host: "127.0.0.1", port: 7002 },
81+
* ])),
82+
* });
7283
* ```
7384
*/
7485
export class RedisMessageQueue implements MessageQueue, Disposable {
75-
#redis: Redis;
76-
#subRedis: Redis;
86+
#redis: Redis | Cluster;
87+
#subRedis: Redis | Cluster;
7788
#workerId: string;
7889
#channelKey: RedisKey;
7990
#queueKey: RedisKey;
@@ -87,7 +98,10 @@ export class RedisMessageQueue implements MessageQueue, Disposable {
8798
* @param redis The Redis client factory.
8899
* @param options The options for the message queue.
89100
*/
90-
constructor(redis: () => Redis, options: RedisMessageQueueOptions = {}) {
101+
constructor(
102+
redis: () => Redis | Cluster,
103+
options: RedisMessageQueueOptions = {},
104+
) {
91105
this.#redis = redis();
92106
this.#subRedis = redis();
93107
this.#workerId = options.workerId ?? crypto.randomUUID();
@@ -196,9 +210,24 @@ export class RedisMessageQueue implements MessageQueue, Disposable {
196210
}
197211
};
198212
const promise = this.#subRedis.subscribe(this.#channelKey, () => {
199-
this.#subRedis.on("message", poll);
213+
/**
214+
* Cast to Redis for event methods. Both Redis and Cluster extend EventEmitter
215+
* and get the same methods via applyMixin at runtime, but their TypeScript
216+
* interfaces are incompatible:
217+
* - Redis declares specific overloads: on(event: "message", cb: (channel, message) => void)
218+
* - Cluster only has generic: on(event: string | symbol, listener: Function)
219+
*
220+
* This makes the union type Redis | Cluster incompatible for these method calls.
221+
* The cast is safe because both classes use applyMixin(Class, EventEmitter) which
222+
* copies all EventEmitter prototype methods, giving them identical pub/sub functionality.
223+
*
224+
* @see https://github.com/redis/ioredis/blob/main/lib/Redis.ts#L863 (has specific overloads)
225+
* @see https://github.com/redis/ioredis/blob/main/lib/cluster/index.ts#L1110 (empty interface)
226+
*/
227+
const subRedis = this.#subRedis as Redis;
228+
subRedis.on("message", poll);
200229
signal?.addEventListener("abort", () => {
201-
this.#subRedis.off("message", poll);
230+
subRedis.off("message", poll);
202231
});
203232
});
204233
signal?.addEventListener(

0 commit comments

Comments
 (0)