@uploadista/event-broadcaster-ioredis 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +5 -0
- package/LICENSE +21 -0
- package/README.md +637 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/io-redis-event-broadcaster.d.ts +29 -0
- package/dist/io-redis-event-broadcaster.d.ts.map +1 -0
- package/dist/io-redis-event-broadcaster.js +42 -0
- package/dist/redis-event-broadcaster.d.ts +29 -0
- package/dist/redis-event-broadcaster.d.ts.map +1 -0
- package/dist/redis-event-broadcaster.js +42 -0
- package/package.json +30 -0
- package/src/index.ts +1 -0
- package/src/io-redis-event-broadcaster.ts +75 -0
- package/tsconfig.json +14 -0
- package/tsconfig.tsbuildinfo +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 uploadista
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
# @uploadista/event-broadcaster-ioredis
|
|
2
|
+
|
|
3
|
+
IORedis-backed event broadcaster for Uploadista. Provides clustered event broadcasting with automatic failover and replica scaling.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The IORedis event broadcaster uses the IORedis library's advanced features for distributed event broadcasting. Perfect for:
|
|
8
|
+
|
|
9
|
+
- **Redis Clustering**: Built-in cluster support with auto-discovery
|
|
10
|
+
- **Sentinel HA**: Automatic failover with Redis Sentinel
|
|
11
|
+
- **Replica Scaling**: Distribute read load across replicas
|
|
12
|
+
- **Large Deployments**: 50+ server instances with efficient broadcasting
|
|
13
|
+
- **Production High-Availability**: Enterprise-grade resilience
|
|
14
|
+
|
|
15
|
+
Events published to a channel reach all subscribers across all servers, with automatic failover if nodes go down.
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @uploadista/event-broadcaster-ioredis ioredis
|
|
21
|
+
# or
|
|
22
|
+
pnpm add @uploadista/event-broadcaster-ioredis ioredis
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Prerequisites
|
|
26
|
+
|
|
27
|
+
- Node.js 18+
|
|
28
|
+
- Redis 5.0+ or Redis Cluster
|
|
29
|
+
- Optional: Redis Sentinel for automatic failover
|
|
30
|
+
- Two IORedis connections (one for pub, one for sub)
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { ioRedisEventBroadcaster } from "@uploadista/event-broadcaster-ioredis";
|
|
36
|
+
import Redis from "ioredis";
|
|
37
|
+
import { Effect } from "effect";
|
|
38
|
+
|
|
39
|
+
// Create IORedis clients (one for pub, one for sub)
|
|
40
|
+
const redis = new Redis({
|
|
41
|
+
host: "localhost",
|
|
42
|
+
port: 6379,
|
|
43
|
+
});
|
|
44
|
+
const subscriberRedis = new Redis({
|
|
45
|
+
host: "localhost",
|
|
46
|
+
port: 6379,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const program = Effect.gen(function* () {
|
|
50
|
+
// Event broadcaster is automatically available
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
Effect.runSync(
|
|
54
|
+
program.pipe(
|
|
55
|
+
Effect.provide(
|
|
56
|
+
ioRedisEventBroadcaster({
|
|
57
|
+
redis,
|
|
58
|
+
subscriberRedis,
|
|
59
|
+
})
|
|
60
|
+
),
|
|
61
|
+
// ... other layers
|
|
62
|
+
)
|
|
63
|
+
);
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Features
|
|
67
|
+
|
|
68
|
+
- ✅ **Redis Clustering**: Automatic cluster discovery and failover
|
|
69
|
+
- ✅ **Sentinel Support**: Monitored automatic master failover
|
|
70
|
+
- ✅ **Replica Reads**: Distribute read load across replicas
|
|
71
|
+
- ✅ **Cluster Scaling**: Add nodes dynamically without reconfiguration
|
|
72
|
+
- ✅ **Connection Pooling**: Efficient resource management
|
|
73
|
+
- ✅ **Advanced Retry Logic**: Robust connection recovery
|
|
74
|
+
|
|
75
|
+
## API Reference
|
|
76
|
+
|
|
77
|
+
### Main Exports
|
|
78
|
+
|
|
79
|
+
#### `ioRedisEventBroadcaster(config: IoRedisEventBroadcasterConfig): Layer<EventBroadcasterService>`
|
|
80
|
+
|
|
81
|
+
Creates an Effect layer providing the `EventBroadcasterService` backed by IORedis.
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { ioRedisEventBroadcaster } from "@uploadista/event-broadcaster-ioredis";
|
|
85
|
+
import Redis from "ioredis";
|
|
86
|
+
|
|
87
|
+
const redis = new Redis({ host: "localhost" });
|
|
88
|
+
const subscriberRedis = new Redis({ host: "localhost" });
|
|
89
|
+
|
|
90
|
+
const layer = ioRedisEventBroadcaster({
|
|
91
|
+
redis,
|
|
92
|
+
subscriberRedis,
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Configuration**:
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
interface IoRedisEventBroadcasterConfig {
|
|
100
|
+
redis: Redis; // Connection for publishing
|
|
101
|
+
subscriberRedis: Redis; // Connection for subscribing
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
#### `createIoRedisEventBroadcaster(config: IoRedisEventBroadcasterConfig): EventBroadcaster`
|
|
106
|
+
|
|
107
|
+
Factory function to create a broadcaster instance.
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
import { createIoRedisEventBroadcaster } from "@uploadista/event-broadcaster-ioredis";
|
|
111
|
+
|
|
112
|
+
const broadcaster = createIoRedisEventBroadcaster({
|
|
113
|
+
redis,
|
|
114
|
+
subscriberRedis,
|
|
115
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Available Operations
|
|
119
|
+
|
|
120
|
+
The IORedis broadcaster implements the `EventBroadcaster` interface:
|
|
121
|
+
|
|
122
|
+
#### `publish(channel: string, message: string): Effect<void>`
|
|
123
|
+
|
|
124
|
+
Broadcast a message to all subscribers (across cluster).
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
const program = Effect.gen(function* () {
|
|
128
|
+
yield* broadcaster.publish("uploads:complete", JSON.stringify({
|
|
129
|
+
uploadId: "abc123",
|
|
130
|
+
duration: 45000,
|
|
131
|
+
}));
|
|
132
|
+
});
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
#### `subscribe(channel: string, handler: (message: string) => void): Effect<void>`
|
|
136
|
+
|
|
137
|
+
Subscribe to a channel (with cluster awareness).
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
const program = Effect.gen(function* () {
|
|
141
|
+
yield* broadcaster.subscribe("uploads:complete", (message: string) => {
|
|
142
|
+
const event = JSON.parse(message);
|
|
143
|
+
console.log(`Upload complete: ${event.uploadId}`);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
#### `unsubscribe(channel: string): Effect<void>`
|
|
149
|
+
|
|
150
|
+
Unsubscribe from a channel.
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
const program = Effect.gen(function* () {
|
|
154
|
+
yield* broadcaster.unsubscribe("uploads:complete");
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Configuration
|
|
159
|
+
|
|
160
|
+
### Single Instance
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
import { ioRedisEventBroadcaster } from "@uploadista/event-broadcaster-ioredis";
|
|
164
|
+
import Redis from "ioredis";
|
|
165
|
+
|
|
166
|
+
const redis = new Redis({
|
|
167
|
+
host: "localhost",
|
|
168
|
+
port: 6379,
|
|
169
|
+
password: process.env.REDIS_PASSWORD,
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const subscriberRedis = new Redis({
|
|
173
|
+
host: "localhost",
|
|
174
|
+
port: 6379,
|
|
175
|
+
password: process.env.REDIS_PASSWORD,
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
const layer = ioRedisEventBroadcaster({
|
|
179
|
+
redis,
|
|
180
|
+
subscriberRedis,
|
|
181
|
+
});
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Redis Cluster
|
|
185
|
+
|
|
186
|
+
With automatic discovery:
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import { ioRedisEventBroadcaster } from "@uploadista/event-broadcaster-ioredis";
|
|
190
|
+
import Redis from "ioredis";
|
|
191
|
+
|
|
192
|
+
const redis = new Redis.Cluster(
|
|
193
|
+
[
|
|
194
|
+
{ host: "cluster-node-1", port: 6379 },
|
|
195
|
+
{ host: "cluster-node-2", port: 6379 },
|
|
196
|
+
{ host: "cluster-node-3", port: 6379 },
|
|
197
|
+
],
|
|
198
|
+
{
|
|
199
|
+
redisOptions: {
|
|
200
|
+
password: process.env.REDIS_PASSWORD,
|
|
201
|
+
},
|
|
202
|
+
}
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
const subscriberRedis = new Redis.Cluster(
|
|
206
|
+
[
|
|
207
|
+
{ host: "cluster-node-1", port: 6379 },
|
|
208
|
+
{ host: "cluster-node-2", port: 6379 },
|
|
209
|
+
{ host: "cluster-node-3", port: 6379 },
|
|
210
|
+
],
|
|
211
|
+
{
|
|
212
|
+
redisOptions: {
|
|
213
|
+
password: process.env.REDIS_PASSWORD,
|
|
214
|
+
},
|
|
215
|
+
}
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
const layer = ioRedisEventBroadcaster({
|
|
219
|
+
redis: redis as any,
|
|
220
|
+
subscriberRedis: subscriberRedis as any,
|
|
221
|
+
});
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Redis Sentinel
|
|
225
|
+
|
|
226
|
+
For automatic failover:
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
import { ioRedisEventBroadcaster } from "@uploadista/event-broadcaster-ioredis";
|
|
230
|
+
import Redis from "ioredis";
|
|
231
|
+
|
|
232
|
+
const redis = new Redis({
|
|
233
|
+
sentinels: [
|
|
234
|
+
{ host: "sentinel-1", port: 26379 },
|
|
235
|
+
{ host: "sentinel-2", port: 26379 },
|
|
236
|
+
{ host: "sentinel-3", port: 26379 },
|
|
237
|
+
],
|
|
238
|
+
name: "mymaster",
|
|
239
|
+
sentinelPassword: process.env.SENTINEL_PASSWORD,
|
|
240
|
+
password: process.env.REDIS_PASSWORD,
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const subscriberRedis = new Redis({
|
|
244
|
+
sentinels: [
|
|
245
|
+
{ host: "sentinel-1", port: 26379 },
|
|
246
|
+
{ host: "sentinel-2", port: 26379 },
|
|
247
|
+
{ host: "sentinel-3", port: 26379 },
|
|
248
|
+
],
|
|
249
|
+
name: "mymaster",
|
|
250
|
+
sentinelPassword: process.env.SENTINEL_PASSWORD,
|
|
251
|
+
password: process.env.REDIS_PASSWORD,
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
const layer = ioRedisEventBroadcaster({
|
|
255
|
+
redis,
|
|
256
|
+
subscriberRedis,
|
|
257
|
+
});
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Environment-Based Configuration
|
|
261
|
+
|
|
262
|
+
```typescript
|
|
263
|
+
import { ioRedisEventBroadcaster } from "@uploadista/event-broadcaster-ioredis";
|
|
264
|
+
import Redis from "ioredis";
|
|
265
|
+
|
|
266
|
+
const createBroadcaster = () => {
|
|
267
|
+
let redis: Redis;
|
|
268
|
+
let subscriberRedis: Redis;
|
|
269
|
+
|
|
270
|
+
if (process.env.REDIS_CLUSTER_NODES) {
|
|
271
|
+
// Cluster mode
|
|
272
|
+
const nodes = process.env.REDIS_CLUSTER_NODES.split(",").map(
|
|
273
|
+
(addr) => {
|
|
274
|
+
const [host, port] = addr.split(":");
|
|
275
|
+
return { host, port: parseInt(port) };
|
|
276
|
+
}
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
redis = new Redis.Cluster(nodes, {
|
|
280
|
+
redisOptions: {
|
|
281
|
+
password: process.env.REDIS_PASSWORD,
|
|
282
|
+
},
|
|
283
|
+
});
|
|
284
|
+
subscriberRedis = new Redis.Cluster(nodes, {
|
|
285
|
+
redisOptions: {
|
|
286
|
+
password: process.env.REDIS_PASSWORD,
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
} else {
|
|
290
|
+
// Single instance
|
|
291
|
+
redis = new Redis({
|
|
292
|
+
url: process.env.REDIS_URL || "redis://localhost:6379",
|
|
293
|
+
});
|
|
294
|
+
subscriberRedis = new Redis({
|
|
295
|
+
url: process.env.REDIS_URL || "redis://localhost:6379",
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return ioRedisEventBroadcaster({
|
|
300
|
+
redis: redis as any,
|
|
301
|
+
subscriberRedis: subscriberRedis as any,
|
|
302
|
+
});
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const layer = createBroadcaster();
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Examples
|
|
309
|
+
|
|
310
|
+
### Example 1: Clustered Upload Service
|
|
311
|
+
|
|
312
|
+
Large deployment across many servers:
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
import { ioRedisEventBroadcaster } from "@uploadista/event-broadcaster-ioredis";
|
|
316
|
+
import { uploadServer } from "@uploadista/server";
|
|
317
|
+
import Redis from "ioredis";
|
|
318
|
+
import { Effect } from "effect";
|
|
319
|
+
|
|
320
|
+
// 5-node cluster
|
|
321
|
+
const redis = new Redis.Cluster(
|
|
322
|
+
[
|
|
323
|
+
{ host: "redis-1.cluster", port: 6379 },
|
|
324
|
+
{ host: "redis-2.cluster", port: 6379 },
|
|
325
|
+
{ host: "redis-3.cluster", port: 6379 },
|
|
326
|
+
{ host: "redis-4.cluster", port: 6379 },
|
|
327
|
+
{ host: "redis-5.cluster", port: 6379 },
|
|
328
|
+
],
|
|
329
|
+
{
|
|
330
|
+
redisOptions: {
|
|
331
|
+
password: process.env.REDIS_PASSWORD,
|
|
332
|
+
},
|
|
333
|
+
}
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
const subscriberRedis = new Redis.Cluster(
|
|
337
|
+
// Same cluster nodes
|
|
338
|
+
{
|
|
339
|
+
redisOptions: {
|
|
340
|
+
password: process.env.REDIS_PASSWORD,
|
|
341
|
+
},
|
|
342
|
+
}
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
const program = Effect.gen(function* () {
|
|
346
|
+
const server = yield* uploadServer;
|
|
347
|
+
|
|
348
|
+
// Subscribe for uploads completing
|
|
349
|
+
yield* broadcaster.subscribe("uploads:complete", (message: string) => {
|
|
350
|
+
const { uploadId, size } = JSON.parse(message);
|
|
351
|
+
console.log(`Upload complete: ${uploadId} (${size} bytes)`);
|
|
352
|
+
|
|
353
|
+
// Trigger flow processing
|
|
354
|
+
// Each server independently processes
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
// Server 1 completes upload
|
|
358
|
+
yield* broadcaster.publish("uploads:complete", JSON.stringify({
|
|
359
|
+
uploadId: "upl_server1_123",
|
|
360
|
+
size: 1048576,
|
|
361
|
+
completedBy: "server-1",
|
|
362
|
+
}));
|
|
363
|
+
// ALL servers (including server 1) receive notification
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
Effect.runSync(
|
|
367
|
+
program.pipe(
|
|
368
|
+
Effect.provide(
|
|
369
|
+
ioRedisEventBroadcaster({
|
|
370
|
+
redis: redis as any,
|
|
371
|
+
subscriberRedis: subscriberRedis as any,
|
|
372
|
+
})
|
|
373
|
+
)
|
|
374
|
+
)
|
|
375
|
+
);
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
### Example 2: Sentinel-Based Failover
|
|
379
|
+
|
|
380
|
+
Automatic failover if primary goes down:
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
import { ioRedisEventBroadcaster } from "@uploadista/event-broadcaster-ioredis";
|
|
384
|
+
import Redis from "ioredis";
|
|
385
|
+
import { Effect } from "effect";
|
|
386
|
+
|
|
387
|
+
const redis = new Redis({
|
|
388
|
+
sentinels: [
|
|
389
|
+
{ host: process.env.SENTINEL_1, port: 26379 },
|
|
390
|
+
{ host: process.env.SENTINEL_2, port: 26379 },
|
|
391
|
+
{ host: process.env.SENTINEL_3, port: 26379 },
|
|
392
|
+
],
|
|
393
|
+
name: "uploadista-primary",
|
|
394
|
+
sentinelPassword: process.env.SENTINEL_PASSWORD,
|
|
395
|
+
password: process.env.REDIS_PASSWORD,
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
const subscriberRedis = new Redis({
|
|
399
|
+
sentinels: [
|
|
400
|
+
{ host: process.env.SENTINEL_1, port: 26379 },
|
|
401
|
+
{ host: process.env.SENTINEL_2, port: 26379 },
|
|
402
|
+
{ host: process.env.SENTINEL_3, port: 26379 },
|
|
403
|
+
],
|
|
404
|
+
name: "uploadista-primary",
|
|
405
|
+
sentinelPassword: process.env.SENTINEL_PASSWORD,
|
|
406
|
+
password: process.env.REDIS_PASSWORD,
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
const broadcaster = ioRedisEventBroadcaster({
|
|
410
|
+
redis,
|
|
411
|
+
subscriberRedis,
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// If Redis primary fails:
|
|
415
|
+
// 1. Sentinel detects failure
|
|
416
|
+
// 2. Promotes replica automatically
|
|
417
|
+
// 3. IORedis reconnects to new primary
|
|
418
|
+
// 4. Events continue flowing (transparent failover)
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### Example 3: Load Balancing with Replica Reads
|
|
422
|
+
|
|
423
|
+
```typescript
|
|
424
|
+
import { ioRedisEventBroadcaster } from "@uploadista/event-broadcaster-ioredis";
|
|
425
|
+
import Redis from "ioredis";
|
|
426
|
+
|
|
427
|
+
// Write to master, read from replicas
|
|
428
|
+
const redis = new Redis.Cluster(
|
|
429
|
+
[
|
|
430
|
+
{ host: "master", port: 6379 },
|
|
431
|
+
{ host: "replica-1", port: 6379 },
|
|
432
|
+
{ host: "replica-2", port: 6379 },
|
|
433
|
+
],
|
|
434
|
+
{
|
|
435
|
+
scaleReads: "slave", // Read from replicas
|
|
436
|
+
}
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
const subscriberRedis = new Redis.Cluster(
|
|
440
|
+
[
|
|
441
|
+
{ host: "master", port: 6379 },
|
|
442
|
+
{ host: "replica-1", port: 6379 },
|
|
443
|
+
{ host: "replica-2", port: 6379 },
|
|
444
|
+
],
|
|
445
|
+
{
|
|
446
|
+
scaleReads: "slave",
|
|
447
|
+
}
|
|
448
|
+
);
|
|
449
|
+
|
|
450
|
+
// Publishes always go to master
|
|
451
|
+
// Subscriptions distributed across replicas
|
|
452
|
+
const broadcaster = ioRedisEventBroadcaster({
|
|
453
|
+
redis: redis as any,
|
|
454
|
+
subscriberRedis: subscriberRedis as any,
|
|
455
|
+
});
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
## Performance Characteristics
|
|
459
|
+
|
|
460
|
+
| Operation | Latency | Throughput | Cluster Aware |
|
|
461
|
+
|-----------|---------|-----------|--------------|
|
|
462
|
+
| publish() | 1-2ms | 100k+ events/sec | ✅ |
|
|
463
|
+
| subscribe() | 2-5ms | N/A | ✅ |
|
|
464
|
+
| unsubscribe() | 1-2ms | N/A | ✅ |
|
|
465
|
+
| Failover | 1-5s | Auto-recovery | ✅ |
|
|
466
|
+
|
|
467
|
+
## Architecture Patterns
|
|
468
|
+
|
|
469
|
+
### Cluster with Replicas
|
|
470
|
+
|
|
471
|
+
```
|
|
472
|
+
App Servers ──→ Pub Redis Master ──→ Replicate to ──→ Replica 1
|
|
473
|
+
Replica 2
|
|
474
|
+
Replica 3
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### Sentinel Monitored
|
|
478
|
+
|
|
479
|
+
```
|
|
480
|
+
Sentinel 1 ┐
|
|
481
|
+
Sentinel 2 ├──→ Monitor ──→ Primary Redis ──→ Replica 1
|
|
482
|
+
Sentinel 3 ┘ Replica 2
|
|
483
|
+
↓
|
|
484
|
+
On failure: Promote Replica 1
|
|
485
|
+
Apps reconnect automatically
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
### Multi-Region Cluster
|
|
489
|
+
|
|
490
|
+
```
|
|
491
|
+
Region 1: Cluster Nodes ─┐
|
|
492
|
+
Region 2: Cluster Nodes ├──→ Single logical cluster
|
|
493
|
+
Region 3: Cluster Nodes ─┘ (apps in any region)
|
|
494
|
+
```
|
|
495
|
+
|
|
496
|
+
## Best Practices
|
|
497
|
+
|
|
498
|
+
### 1. Use Cluster for Scale
|
|
499
|
+
|
|
500
|
+
```typescript
|
|
501
|
+
// Development: Single instance
|
|
502
|
+
const redis = new Redis();
|
|
503
|
+
|
|
504
|
+
// Production: Cluster
|
|
505
|
+
const redis = new Redis.Cluster(nodes);
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
### 2. Configure Retry Strategy
|
|
509
|
+
|
|
510
|
+
```typescript
|
|
511
|
+
const redis = new Redis({
|
|
512
|
+
maxRetriesPerRequest: 3,
|
|
513
|
+
retryStrategy: (times: number) => {
|
|
514
|
+
const delay = Math.min(times * 50, 2000);
|
|
515
|
+
return delay;
|
|
516
|
+
},
|
|
517
|
+
});
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
### 3. Monitor Cluster Health
|
|
521
|
+
|
|
522
|
+
```bash
|
|
523
|
+
# Check cluster nodes
|
|
524
|
+
redis-cli -c CLUSTER NODES
|
|
525
|
+
|
|
526
|
+
# Check sentinel status
|
|
527
|
+
redis-cli -p 26379 SENTINEL MASTERS
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
## Deployment
|
|
531
|
+
|
|
532
|
+
### Docker Compose with Cluster
|
|
533
|
+
|
|
534
|
+
```yaml
|
|
535
|
+
version: "3"
|
|
536
|
+
services:
|
|
537
|
+
app:
|
|
538
|
+
environment:
|
|
539
|
+
REDIS_CLUSTER_NODES: redis1:6379,redis2:6379,redis3:6379
|
|
540
|
+
|
|
541
|
+
redis1:
|
|
542
|
+
image: redis:7-alpine
|
|
543
|
+
command: redis-server --cluster-enabled yes --cluster-config-file nodes.conf
|
|
544
|
+
|
|
545
|
+
redis2:
|
|
546
|
+
image: redis:7-alpine
|
|
547
|
+
command: redis-server --cluster-enabled yes --cluster-config-file nodes.conf
|
|
548
|
+
|
|
549
|
+
redis3:
|
|
550
|
+
image: redis:7-alpine
|
|
551
|
+
command: redis-server --cluster-enabled yes --cluster-config-file nodes.conf
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
### Kubernetes Deployment
|
|
555
|
+
|
|
556
|
+
```yaml
|
|
557
|
+
apiVersion: v1
|
|
558
|
+
kind: ConfigMap
|
|
559
|
+
metadata:
|
|
560
|
+
name: redis-config
|
|
561
|
+
data:
|
|
562
|
+
redis.conf: |
|
|
563
|
+
cluster-enabled yes
|
|
564
|
+
cluster-config-file nodes.conf
|
|
565
|
+
|
|
566
|
+
---
|
|
567
|
+
apiVersion: apps/v1
|
|
568
|
+
kind: StatefulSet
|
|
569
|
+
metadata:
|
|
570
|
+
name: redis-cluster
|
|
571
|
+
spec:
|
|
572
|
+
serviceName: redis
|
|
573
|
+
replicas: 6
|
|
574
|
+
template:
|
|
575
|
+
spec:
|
|
576
|
+
containers:
|
|
577
|
+
- name: redis
|
|
578
|
+
image: redis:7-alpine
|
|
579
|
+
ports:
|
|
580
|
+
- containerPort: 6379
|
|
581
|
+
- containerPort: 16379
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
## Related Packages
|
|
585
|
+
|
|
586
|
+
- [@uploadista/core](../../core) - Core types
|
|
587
|
+
- [@uploadista/event-broadcaster-redis](../redis) - Standard Redis broadcaster
|
|
588
|
+
- [@uploadista/event-broadcaster-memory](../memory) - Single-process broadcaster
|
|
589
|
+
- [@uploadista/kv-store-ioredis](../../kv-stores/ioredis) - IORedis KV store
|
|
590
|
+
- [@uploadista/server](../../servers/server) - Upload server
|
|
591
|
+
|
|
592
|
+
## Troubleshooting
|
|
593
|
+
|
|
594
|
+
### "Cluster connection failed"
|
|
595
|
+
|
|
596
|
+
Verify cluster nodes are reachable:
|
|
597
|
+
|
|
598
|
+
```bash
|
|
599
|
+
redis-cli -c
|
|
600
|
+
> CLUSTER INFO
|
|
601
|
+
|
|
602
|
+
# Should show: cluster_state:ok
|
|
603
|
+
```
|
|
604
|
+
|
|
605
|
+
### "Sentinel can't find master"
|
|
606
|
+
|
|
607
|
+
Check Sentinel configuration:
|
|
608
|
+
|
|
609
|
+
```bash
|
|
610
|
+
redis-cli -p 26379
|
|
611
|
+
> SENTINEL MASTERS
|
|
612
|
+
|
|
613
|
+
# Should list your master instance
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
### High Latency in Cluster
|
|
617
|
+
|
|
618
|
+
Check cluster topology:
|
|
619
|
+
|
|
620
|
+
```bash
|
|
621
|
+
redis-cli -c
|
|
622
|
+
> CLUSTER SLOTS
|
|
623
|
+
|
|
624
|
+
# Verify even slot distribution
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
## License
|
|
628
|
+
|
|
629
|
+
See [LICENSE](../../../LICENSE) in the main repository.
|
|
630
|
+
|
|
631
|
+
## See Also
|
|
632
|
+
|
|
633
|
+
- [EVENT_SYSTEM.md](./EVENT_SYSTEM.md) - Architecture guide
|
|
634
|
+
- [Redis Broadcaster](../redis/README.md) - Simpler Redis option
|
|
635
|
+
- [ioredis Documentation](https://github.com/luin/ioredis) - Official ioredis docs
|
|
636
|
+
- [Redis Cluster Tutorial](https://redis.io/topics/cluster-tutorial) - Redis clustering
|
|
637
|
+
- [Redis Sentinel Guide](https://redis.io/topics/sentinel) - HA with Sentinel
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,8BAA8B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./io-redis-event-broadcaster";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { EventBroadcaster } from "@uploadista/core/types";
|
|
2
|
+
import { EventBroadcasterService } from "@uploadista/core/types";
|
|
3
|
+
import { Layer } from "effect";
|
|
4
|
+
import type { Redis } from "ioredis";
|
|
5
|
+
/**
|
|
6
|
+
* Configuration for Redis event broadcaster
|
|
7
|
+
*/
|
|
8
|
+
export interface IoRedisEventBroadcasterConfig {
|
|
9
|
+
/**
|
|
10
|
+
* Redis client for publishing messages
|
|
11
|
+
*/
|
|
12
|
+
redis: Redis;
|
|
13
|
+
/**
|
|
14
|
+
* Separate Redis client for subscribing to messages
|
|
15
|
+
* (Redis requires a dedicated connection for pub/sub)
|
|
16
|
+
*/
|
|
17
|
+
subscriberRedis: Redis;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Redis-based event broadcaster for distributed deployments.
|
|
21
|
+
* Uses Redis Pub/Sub to broadcast events across multiple instances.
|
|
22
|
+
* Requires two separate Redis connections (one for pub, one for sub).
|
|
23
|
+
*/
|
|
24
|
+
export declare function createIoRedisEventBroadcaster(config: IoRedisEventBroadcasterConfig): EventBroadcaster;
|
|
25
|
+
/**
|
|
26
|
+
* Layer factory for Redis event broadcaster
|
|
27
|
+
*/
|
|
28
|
+
export declare const ioRedisEventBroadcaster: (config: IoRedisEventBroadcasterConfig) => Layer.Layer<EventBroadcasterService, never, never>;
|
|
29
|
+
//# sourceMappingURL=io-redis-event-broadcaster.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"io-redis-event-broadcaster.d.ts","sourceRoot":"","sources":["../src/io-redis-event-broadcaster.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAU,KAAK,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC;;GAEG;AACH,MAAM,WAAW,6BAA6B;IAC5C;;OAEG;IACH,KAAK,EAAE,KAAK,CAAC;IACb;;;OAGG;IACH,eAAe,EAAE,KAAK,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,6BAA6B,GACpC,gBAAgB,CAsClB;AAED;;GAEG;AACH,eAAO,MAAM,uBAAuB,GAClC,QAAQ,6BAA6B,uDAEwC,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { UploadistaError } from "@uploadista/core/errors";
|
|
2
|
+
import { EventBroadcasterService } from "@uploadista/core/types";
|
|
3
|
+
import { Effect, Layer } from "effect";
|
|
4
|
+
/**
|
|
5
|
+
* Redis-based event broadcaster for distributed deployments.
|
|
6
|
+
* Uses Redis Pub/Sub to broadcast events across multiple instances.
|
|
7
|
+
* Requires two separate Redis connections (one for pub, one for sub).
|
|
8
|
+
*/
|
|
9
|
+
export function createIoRedisEventBroadcaster(config) {
|
|
10
|
+
const { redis, subscriberRedis } = config;
|
|
11
|
+
return {
|
|
12
|
+
publish: (channel, message) => Effect.tryPromise({
|
|
13
|
+
try: () => redis.publish(channel, message),
|
|
14
|
+
catch: (cause) => UploadistaError.fromCode("UNKNOWN_ERROR", {
|
|
15
|
+
cause,
|
|
16
|
+
}),
|
|
17
|
+
}).pipe(Effect.asVoid),
|
|
18
|
+
subscribe: (channel, handler) => Effect.try({
|
|
19
|
+
try: () => {
|
|
20
|
+
subscriberRedis.subscribe(channel);
|
|
21
|
+
subscriberRedis.on("message", (ch, msg) => {
|
|
22
|
+
if (ch === channel) {
|
|
23
|
+
handler(msg);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
},
|
|
27
|
+
catch: (cause) => UploadistaError.fromCode("UNKNOWN_ERROR", {
|
|
28
|
+
cause,
|
|
29
|
+
}),
|
|
30
|
+
}),
|
|
31
|
+
unsubscribe: (channel) => Effect.tryPromise({
|
|
32
|
+
try: () => subscriberRedis.unsubscribe(channel),
|
|
33
|
+
catch: (cause) => UploadistaError.fromCode("UNKNOWN_ERROR", {
|
|
34
|
+
cause,
|
|
35
|
+
}),
|
|
36
|
+
}).pipe(Effect.asVoid),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Layer factory for Redis event broadcaster
|
|
41
|
+
*/
|
|
42
|
+
export const ioRedisEventBroadcaster = (config) => Layer.succeed(EventBroadcasterService, createIoRedisEventBroadcaster(config));
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { EventBroadcaster } from "@uploadista/core/types";
|
|
2
|
+
import { EventBroadcasterService } from "@uploadista/core/types";
|
|
3
|
+
import { Layer } from "effect";
|
|
4
|
+
import type { Redis } from "ioredis";
|
|
5
|
+
/**
|
|
6
|
+
* Configuration for Redis event broadcaster
|
|
7
|
+
*/
|
|
8
|
+
export interface RedisEventBroadcasterConfig {
|
|
9
|
+
/**
|
|
10
|
+
* Redis client for publishing messages
|
|
11
|
+
*/
|
|
12
|
+
redis: Redis;
|
|
13
|
+
/**
|
|
14
|
+
* Separate Redis client for subscribing to messages
|
|
15
|
+
* (Redis requires a dedicated connection for pub/sub)
|
|
16
|
+
*/
|
|
17
|
+
subscriberRedis: Redis;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Redis-based event broadcaster for distributed deployments.
|
|
21
|
+
* Uses Redis Pub/Sub to broadcast events across multiple instances.
|
|
22
|
+
* Requires two separate Redis connections (one for pub, one for sub).
|
|
23
|
+
*/
|
|
24
|
+
export declare function createRedisEventBroadcaster(config: RedisEventBroadcasterConfig): EventBroadcaster;
|
|
25
|
+
/**
|
|
26
|
+
* Layer factory for Redis event broadcaster
|
|
27
|
+
*/
|
|
28
|
+
export declare const redisEventBroadcaster: (config: RedisEventBroadcasterConfig) => Layer.Layer<EventBroadcasterService, never, never>;
|
|
29
|
+
//# sourceMappingURL=redis-event-broadcaster.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redis-event-broadcaster.d.ts","sourceRoot":"","sources":["../src/redis-event-broadcaster.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAU,KAAK,EAAE,MAAM,QAAQ,CAAC;AACvC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAErC;;GAEG;AACH,MAAM,WAAW,2BAA2B;IAC1C;;OAEG;IACH,KAAK,EAAE,KAAK,CAAC;IACb;;;OAGG;IACH,eAAe,EAAE,KAAK,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,MAAM,EAAE,2BAA2B,GAClC,gBAAgB,CAsClB;AAED;;GAEG;AACH,eAAO,MAAM,qBAAqB,GAChC,QAAQ,2BAA2B,uDAKlC,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { UploadistaError } from "@uploadista/core/errors";
|
|
2
|
+
import { EventBroadcasterService } from "@uploadista/core/types";
|
|
3
|
+
import { Effect, Layer } from "effect";
|
|
4
|
+
/**
|
|
5
|
+
* Redis-based event broadcaster for distributed deployments.
|
|
6
|
+
* Uses Redis Pub/Sub to broadcast events across multiple instances.
|
|
7
|
+
* Requires two separate Redis connections (one for pub, one for sub).
|
|
8
|
+
*/
|
|
9
|
+
export function createRedisEventBroadcaster(config) {
|
|
10
|
+
const { redis, subscriberRedis } = config;
|
|
11
|
+
return {
|
|
12
|
+
publish: (channel, message) => Effect.tryPromise({
|
|
13
|
+
try: () => redis.publish(channel, message),
|
|
14
|
+
catch: (cause) => UploadistaError.fromCode("UNKNOWN_ERROR", {
|
|
15
|
+
cause,
|
|
16
|
+
}),
|
|
17
|
+
}).pipe(Effect.asVoid),
|
|
18
|
+
subscribe: (channel, handler) => Effect.try({
|
|
19
|
+
try: () => {
|
|
20
|
+
subscriberRedis.subscribe(channel);
|
|
21
|
+
subscriberRedis.on("message", (ch, msg) => {
|
|
22
|
+
if (ch === channel) {
|
|
23
|
+
handler(msg);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
},
|
|
27
|
+
catch: (cause) => UploadistaError.fromCode("UNKNOWN_ERROR", {
|
|
28
|
+
cause,
|
|
29
|
+
}),
|
|
30
|
+
}),
|
|
31
|
+
unsubscribe: (channel) => Effect.tryPromise({
|
|
32
|
+
try: () => subscriberRedis.unsubscribe(channel),
|
|
33
|
+
catch: (cause) => UploadistaError.fromCode("UNKNOWN_ERROR", {
|
|
34
|
+
cause,
|
|
35
|
+
}),
|
|
36
|
+
}).pipe(Effect.asVoid),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Layer factory for Redis event broadcaster
|
|
41
|
+
*/
|
|
42
|
+
export const redisEventBroadcaster = (config) => Layer.succeed(EventBroadcasterService, createRedisEventBroadcaster(config));
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@uploadista/event-broadcaster-ioredis",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.0.3",
|
|
5
|
+
"description": "Redis event broadcaster for Uploadista",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"author": "Uploadista",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"default": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"effect": "3.18.4",
|
|
17
|
+
"ioredis": "5.8.1",
|
|
18
|
+
"@uploadista/core": "0.0.3"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/node": "24.8.1",
|
|
22
|
+
"@uploadista/typescript-config": "0.0.3"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsc -b",
|
|
26
|
+
"format": "biome format --write ./src",
|
|
27
|
+
"lint": "biome lint --write ./src",
|
|
28
|
+
"check": "biome check --write ./src"
|
|
29
|
+
}
|
|
30
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./io-redis-event-broadcaster";
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { UploadistaError } from "@uploadista/core/errors";
|
|
2
|
+
import type { EventBroadcaster } from "@uploadista/core/types";
|
|
3
|
+
import { EventBroadcasterService } from "@uploadista/core/types";
|
|
4
|
+
import { Effect, Layer } from "effect";
|
|
5
|
+
import type { Redis } from "ioredis";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Configuration for Redis event broadcaster
|
|
9
|
+
*/
|
|
10
|
+
export interface IoRedisEventBroadcasterConfig {
|
|
11
|
+
/**
|
|
12
|
+
* Redis client for publishing messages
|
|
13
|
+
*/
|
|
14
|
+
redis: Redis;
|
|
15
|
+
/**
|
|
16
|
+
* Separate Redis client for subscribing to messages
|
|
17
|
+
* (Redis requires a dedicated connection for pub/sub)
|
|
18
|
+
*/
|
|
19
|
+
subscriberRedis: Redis;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Redis-based event broadcaster for distributed deployments.
|
|
24
|
+
* Uses Redis Pub/Sub to broadcast events across multiple instances.
|
|
25
|
+
* Requires two separate Redis connections (one for pub, one for sub).
|
|
26
|
+
*/
|
|
27
|
+
export function createIoRedisEventBroadcaster(
|
|
28
|
+
config: IoRedisEventBroadcasterConfig,
|
|
29
|
+
): EventBroadcaster {
|
|
30
|
+
const { redis, subscriberRedis } = config;
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
publish: (channel: string, message: string) =>
|
|
34
|
+
Effect.tryPromise({
|
|
35
|
+
try: () => redis.publish(channel, message),
|
|
36
|
+
catch: (cause) =>
|
|
37
|
+
UploadistaError.fromCode("UNKNOWN_ERROR", {
|
|
38
|
+
cause,
|
|
39
|
+
}),
|
|
40
|
+
}).pipe(Effect.asVoid),
|
|
41
|
+
|
|
42
|
+
subscribe: (channel: string, handler: (message: string) => void) =>
|
|
43
|
+
Effect.try({
|
|
44
|
+
try: () => {
|
|
45
|
+
subscriberRedis.subscribe(channel);
|
|
46
|
+
subscriberRedis.on("message", (ch: string, msg: string) => {
|
|
47
|
+
if (ch === channel) {
|
|
48
|
+
handler(msg);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
},
|
|
52
|
+
catch: (cause) =>
|
|
53
|
+
UploadistaError.fromCode("UNKNOWN_ERROR", {
|
|
54
|
+
cause,
|
|
55
|
+
}),
|
|
56
|
+
}),
|
|
57
|
+
|
|
58
|
+
unsubscribe: (channel: string) =>
|
|
59
|
+
Effect.tryPromise({
|
|
60
|
+
try: () => subscriberRedis.unsubscribe(channel),
|
|
61
|
+
catch: (cause) =>
|
|
62
|
+
UploadistaError.fromCode("UNKNOWN_ERROR", {
|
|
63
|
+
cause,
|
|
64
|
+
}),
|
|
65
|
+
}).pipe(Effect.asVoid),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Layer factory for Redis event broadcaster
|
|
71
|
+
*/
|
|
72
|
+
export const ioRedisEventBroadcaster = (
|
|
73
|
+
config: IoRedisEventBroadcasterConfig,
|
|
74
|
+
) =>
|
|
75
|
+
Layer.succeed(EventBroadcasterService, createIoRedisEventBroadcaster(config));
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "@uploadista/typescript-config/server.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"baseUrl": "./",
|
|
5
|
+
"paths": {
|
|
6
|
+
"@/*": ["./src/*"]
|
|
7
|
+
},
|
|
8
|
+
"outDir": "./dist",
|
|
9
|
+
"rootDir": "./src",
|
|
10
|
+
"typeRoots": ["../../../../node_modules/@types"],
|
|
11
|
+
"lib": ["ES2022"]
|
|
12
|
+
},
|
|
13
|
+
"include": ["src"]
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["./src/index.ts","./src/io-redis-event-broadcaster.ts"],"version":"5.9.3"}
|