@pikku/redis 0.11.0

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.
@@ -0,0 +1,341 @@
1
+ import { PikkuWorkflowService, } from '@pikku/core/workflow';
2
+ import { Redis } from 'ioredis';
3
+ import { randomUUID } from 'crypto';
4
+ export class RedisWorkflowService extends PikkuWorkflowService {
5
+ redis;
6
+ keyPrefix;
7
+ ownsConnection;
8
+ lockTTL = 30000;
9
+ constructor(connectionOrConfig, keyPrefix = 'workflows') {
10
+ super();
11
+ this.keyPrefix = keyPrefix;
12
+ if (connectionOrConfig instanceof Redis) {
13
+ this.redis = connectionOrConfig;
14
+ this.ownsConnection = false;
15
+ }
16
+ else {
17
+ this.redis = new Redis(connectionOrConfig);
18
+ this.ownsConnection = true;
19
+ }
20
+ }
21
+ async init() {
22
+ await this.redis.ping();
23
+ }
24
+ runKey(runId) {
25
+ return `${this.keyPrefix}:run:${runId}`;
26
+ }
27
+ stepKey(runId, stepName) {
28
+ return `${this.keyPrefix}:step:${runId}:${stepName}`;
29
+ }
30
+ stepHistoryKey(stepId) {
31
+ return `${this.keyPrefix}:step-history:${stepId}`;
32
+ }
33
+ lockKey(runId) {
34
+ return `${this.keyPrefix}:lock:${runId}`;
35
+ }
36
+ async saveStepHistory(stepId, stepName, attemptCount, status, result, error, retries, retryDelay) {
37
+ const historyKey = this.stepHistoryKey(stepId);
38
+ const now = Date.now();
39
+ const entry = {
40
+ stepId,
41
+ stepName,
42
+ attemptCount,
43
+ status,
44
+ result: result !== undefined ? JSON.stringify(result) : undefined,
45
+ error: error !== undefined ? JSON.stringify(error) : undefined,
46
+ retries,
47
+ retryDelay,
48
+ createdAt: now,
49
+ };
50
+ switch (status) {
51
+ case 'running':
52
+ entry.runningAt = now;
53
+ break;
54
+ case 'scheduled':
55
+ entry.scheduledAt = now;
56
+ break;
57
+ case 'succeeded':
58
+ entry.succeededAt = now;
59
+ break;
60
+ case 'failed':
61
+ entry.failedAt = now;
62
+ break;
63
+ }
64
+ Object.keys(entry).forEach((key) => entry[key] === undefined && delete entry[key]);
65
+ await this.redis.zadd(historyKey, attemptCount, JSON.stringify(entry));
66
+ }
67
+ async updateCurrentHistoryRecord(stepId, stepName, attemptCount, status, result, error, retries, retryDelay) {
68
+ const historyKey = this.stepHistoryKey(stepId);
69
+ const historyEntries = await this.redis.zrangebyscore(historyKey, attemptCount, attemptCount);
70
+ if (historyEntries.length === 0) {
71
+ await this.saveStepHistory(stepId, stepName, attemptCount, status, result, error, retries, retryDelay);
72
+ return;
73
+ }
74
+ const existingEntry = JSON.parse(historyEntries[0]);
75
+ const now = Date.now();
76
+ const updatedEntry = {
77
+ ...existingEntry,
78
+ status,
79
+ result: result !== undefined ? JSON.stringify(result) : undefined,
80
+ error: error !== undefined ? JSON.stringify(error) : undefined,
81
+ };
82
+ switch (status) {
83
+ case 'running':
84
+ updatedEntry.runningAt = now;
85
+ break;
86
+ case 'scheduled':
87
+ updatedEntry.scheduledAt = now;
88
+ break;
89
+ case 'succeeded':
90
+ updatedEntry.succeededAt = now;
91
+ break;
92
+ case 'failed':
93
+ updatedEntry.failedAt = now;
94
+ break;
95
+ }
96
+ await this.redis.zremrangebyscore(historyKey, attemptCount, attemptCount);
97
+ await this.redis.zadd(historyKey, attemptCount, JSON.stringify(updatedEntry));
98
+ }
99
+ async createRun(workflowName, input) {
100
+ const id = randomUUID();
101
+ const now = Date.now();
102
+ const key = this.runKey(id);
103
+ await this.redis.hmset(key, 'id', id, 'workflow', workflowName, 'status', 'running', 'input', JSON.stringify(input), 'createdAt', now.toString(), 'updatedAt', now.toString());
104
+ return id;
105
+ }
106
+ async getRun(id) {
107
+ const key = this.runKey(id);
108
+ const data = await this.redis.hgetall(key);
109
+ if (!data.id) {
110
+ return null;
111
+ }
112
+ return {
113
+ id: data.id,
114
+ workflow: data.workflow,
115
+ status: data.status,
116
+ input: JSON.parse(data.input),
117
+ output: data.output ? JSON.parse(data.output) : undefined,
118
+ error: data.error ? JSON.parse(data.error) : undefined,
119
+ createdAt: new Date(Number(data.createdAt)),
120
+ updatedAt: new Date(Number(data.updatedAt)),
121
+ };
122
+ }
123
+ async updateRunStatus(id, status, output, error) {
124
+ const now = Date.now();
125
+ const key = this.runKey(id);
126
+ const fields = {
127
+ status,
128
+ updatedAt: now.toString(),
129
+ };
130
+ if (output !== undefined) {
131
+ fields.output = JSON.stringify(output);
132
+ }
133
+ if (error !== undefined) {
134
+ fields.error = JSON.stringify(error);
135
+ }
136
+ await this.redis.hmset(key, fields);
137
+ }
138
+ async insertStepState(runId, stepName, rpcName, data, stepOptions) {
139
+ const now = Date.now();
140
+ const stepId = `${runId}:${stepName}:${now}`;
141
+ const key = this.stepKey(runId, stepName);
142
+ const fields = {
143
+ stepId,
144
+ rpcName,
145
+ data: JSON.stringify(data),
146
+ status: 'pending',
147
+ attemptCount: '1',
148
+ createdAt: now.toString(),
149
+ updatedAt: now.toString(),
150
+ };
151
+ if (stepOptions?.retries !== undefined) {
152
+ fields.retries = stepOptions.retries.toString();
153
+ }
154
+ if (stepOptions?.retryDelay !== undefined) {
155
+ fields.retryDelay = stepOptions.retryDelay.toString();
156
+ }
157
+ await this.redis.hmset(key, fields);
158
+ await this.saveStepHistory(stepId, stepName, 1, 'pending', undefined, undefined, stepOptions?.retries, stepOptions?.retryDelay?.toString());
159
+ return {
160
+ stepId,
161
+ status: 'pending',
162
+ attemptCount: 1,
163
+ retries: stepOptions?.retries,
164
+ retryDelay: stepOptions?.retryDelay !== undefined
165
+ ? stepOptions.retryDelay.toString()
166
+ : undefined,
167
+ createdAt: new Date(now),
168
+ updatedAt: new Date(now),
169
+ };
170
+ }
171
+ async getStepState(runId, stepName) {
172
+ const key = this.stepKey(runId, stepName);
173
+ const data = await this.redis.hgetall(key);
174
+ if (!data.stepId) {
175
+ throw new Error(`Step not found: runId=${runId}, stepName=${stepName}. Use insertStepState to create it.`);
176
+ }
177
+ return {
178
+ stepId: data.stepId,
179
+ status: data.status,
180
+ result: data.result ? JSON.parse(data.result) : undefined,
181
+ error: data.error ? JSON.parse(data.error) : undefined,
182
+ attemptCount: Number(data.attemptCount || 1),
183
+ retries: data.retries ? Number(data.retries) : undefined,
184
+ retryDelay: data.retryDelay,
185
+ createdAt: new Date(Number(data.createdAt)),
186
+ updatedAt: new Date(Number(data.updatedAt)),
187
+ };
188
+ }
189
+ async getRunHistory(runId) {
190
+ const pattern = `${this.keyPrefix}:step:${runId}:*`;
191
+ const stepKeys = [];
192
+ let cursor = '0';
193
+ do {
194
+ const [newCursor, foundKeys] = await this.redis.scan(cursor, 'MATCH', pattern, 'COUNT', 100);
195
+ cursor = newCursor;
196
+ stepKeys.push(...foundKeys);
197
+ } while (cursor !== '0');
198
+ const allHistoryEntries = [];
199
+ for (const stepKey of stepKeys) {
200
+ const stepData = await this.redis.hgetall(stepKey);
201
+ if (!stepData.stepId)
202
+ continue;
203
+ const stepId = stepData.stepId;
204
+ const historyKey = this.stepHistoryKey(stepId);
205
+ const historyEntries = await this.redis.zrange(historyKey, 0, -1);
206
+ for (const entryStr of historyEntries) {
207
+ const entry = JSON.parse(entryStr);
208
+ allHistoryEntries.push({
209
+ stepId: entry.stepId,
210
+ stepName: entry.stepName,
211
+ status: entry.status,
212
+ result: entry.result ? JSON.parse(entry.result) : undefined,
213
+ error: entry.error ? JSON.parse(entry.error) : undefined,
214
+ attemptCount: entry.attemptCount,
215
+ retries: entry.retries,
216
+ retryDelay: entry.retryDelay,
217
+ createdAt: new Date(entry.createdAt),
218
+ updatedAt: new Date(entry.createdAt),
219
+ runningAt: entry.runningAt ? new Date(entry.runningAt) : undefined,
220
+ scheduledAt: entry.scheduledAt
221
+ ? new Date(entry.scheduledAt)
222
+ : undefined,
223
+ succeededAt: entry.succeededAt
224
+ ? new Date(entry.succeededAt)
225
+ : undefined,
226
+ failedAt: entry.failedAt ? new Date(entry.failedAt) : undefined,
227
+ });
228
+ }
229
+ }
230
+ return allHistoryEntries.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
231
+ }
232
+ async setStepRunning(stepId) {
233
+ const parts = stepId.split(':');
234
+ const runId = parts[0];
235
+ const stepName = parts.slice(1, -1).join(':');
236
+ const now = Date.now();
237
+ const key = this.stepKey(runId, stepName);
238
+ const data = await this.redis.hgetall(key);
239
+ const attemptCount = Number(data.attemptCount || 1);
240
+ const retries = data.retries ? Number(data.retries) : undefined;
241
+ const retryDelay = data.retryDelay;
242
+ await this.redis.hmset(key, 'status', 'running', 'updatedAt', now.toString());
243
+ await this.updateCurrentHistoryRecord(stepId, stepName, attemptCount, 'running', undefined, undefined, retries, retryDelay);
244
+ }
245
+ async setStepScheduled(stepId) {
246
+ const parts = stepId.split(':');
247
+ const runId = parts[0];
248
+ const stepName = parts.slice(1, -1).join(':');
249
+ const now = Date.now();
250
+ const key = this.stepKey(runId, stepName);
251
+ const data = await this.redis.hgetall(key);
252
+ const attemptCount = Number(data.attemptCount || 1);
253
+ const retries = data.retries ? Number(data.retries) : undefined;
254
+ const retryDelay = data.retryDelay;
255
+ await this.redis.hmset(key, 'status', 'scheduled', 'updatedAt', now.toString());
256
+ await this.updateCurrentHistoryRecord(stepId, stepName, attemptCount, 'scheduled', undefined, undefined, retries, retryDelay);
257
+ }
258
+ async setStepResult(stepId, result) {
259
+ const parts = stepId.split(':');
260
+ const runId = parts[0];
261
+ const stepName = parts.slice(1, -1).join(':');
262
+ const now = Date.now();
263
+ const key = this.stepKey(runId, stepName);
264
+ const data = await this.redis.hgetall(key);
265
+ const attemptCount = Number(data.attemptCount || 1);
266
+ const retries = data.retries ? Number(data.retries) : undefined;
267
+ const retryDelay = data.retryDelay;
268
+ await this.redis.hmset(key, 'status', 'succeeded', 'result', JSON.stringify(result), 'updatedAt', now.toString());
269
+ await this.redis.hdel(key, 'error');
270
+ await this.updateCurrentHistoryRecord(stepId, stepName, attemptCount, 'succeeded', result, undefined, retries, retryDelay);
271
+ }
272
+ async setStepError(stepId, error) {
273
+ const parts = stepId.split(':');
274
+ const runId = parts[0];
275
+ const stepName = parts.slice(1, -1).join(':');
276
+ const now = Date.now();
277
+ const key = this.stepKey(runId, stepName);
278
+ const data = await this.redis.hgetall(key);
279
+ const attemptCount = Number(data.attemptCount || 1);
280
+ const retries = data.retries ? Number(data.retries) : undefined;
281
+ const retryDelay = data.retryDelay;
282
+ const serializedError = {
283
+ message: error.message,
284
+ stack: error.stack,
285
+ code: error.code,
286
+ };
287
+ await this.redis.hmset(key, 'status', 'failed', 'error', JSON.stringify(serializedError), 'updatedAt', now.toString());
288
+ await this.redis.hdel(key, 'result');
289
+ await this.updateCurrentHistoryRecord(stepId, stepName, attemptCount, 'failed', undefined, serializedError, retries, retryDelay);
290
+ }
291
+ async withRunLock(id, fn) {
292
+ const lockKey = this.lockKey(id);
293
+ const lockValue = randomUUID();
294
+ const maxRetries = 10;
295
+ const retryDelay = 100;
296
+ for (let i = 0; i < maxRetries; i++) {
297
+ const acquired = await this.redis.set(lockKey, lockValue, 'PX', this.lockTTL, 'NX');
298
+ if (acquired === 'OK') {
299
+ try {
300
+ return await fn();
301
+ }
302
+ finally {
303
+ await this.redis.eval(`if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end`, 1, lockKey, lockValue);
304
+ }
305
+ }
306
+ await new Promise((resolve) => setTimeout(resolve, retryDelay));
307
+ }
308
+ throw new Error(`Failed to acquire lock for run ${id} after ${maxRetries} retries`);
309
+ }
310
+ async createRetryAttempt(stepId, status) {
311
+ const parts = stepId.split(':');
312
+ const runId = parts[0];
313
+ const stepName = parts.slice(1, -1).join(':');
314
+ const now = Date.now();
315
+ const key = this.stepKey(runId, stepName);
316
+ const data = await this.redis.hgetall(key);
317
+ const currentAttempt = Number(data.attemptCount || 1);
318
+ const newAttemptCount = currentAttempt + 1;
319
+ const retries = data.retries ? Number(data.retries) : undefined;
320
+ const retryDelay = data.retryDelay;
321
+ await this.redis.hmset(key, 'status', status, 'attemptCount', newAttemptCount.toString(), 'updatedAt', now.toString());
322
+ await this.saveStepHistory(stepId, stepName, newAttemptCount, 'pending', undefined, undefined, retries, retryDelay);
323
+ return {
324
+ stepId: data.stepId,
325
+ status: 'pending',
326
+ result: data.result ? JSON.parse(data.result) : undefined,
327
+ error: data.error ? JSON.parse(data.error) : undefined,
328
+ attemptCount: newAttemptCount,
329
+ retries: retries,
330
+ retryDelay: retryDelay,
331
+ createdAt: new Date(Number(data.createdAt)),
332
+ updatedAt: new Date(now),
333
+ };
334
+ }
335
+ async close() {
336
+ if (this.ownsConnection) {
337
+ await this.redis.quit();
338
+ }
339
+ }
340
+ }
341
+ //# sourceMappingURL=redis-workflow-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-workflow-service.js","sourceRoot":"","sources":["../../src/redis-workflow-service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ {"root":["../src/index.ts","../src/redis-channel-store.ts","../src/redis-eventhub-store.ts","../src/redis-workflow-service.ts"],"version":"5.9.3"}
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@pikku/redis",
3
+ "version": "0.11.0",
4
+ "author": "yasser.fadl@gmail.com",
5
+ "license": "MIT",
6
+ "module": "dist/src/index.js",
7
+ "main": "dist/src/index.js",
8
+ "type": "module",
9
+ "scripts": {
10
+ "tsc": "tsc",
11
+ "ncu": "npx npm-check-updates",
12
+ "build": "tsc -b",
13
+ "test": "bash run-tests.sh",
14
+ "test:watch": "bash run-tests.sh --watch",
15
+ "test:coverage": "bash run-tests.sh --coverage",
16
+ "release": "npm run build && npm test"
17
+ },
18
+ "peerDependencies": {
19
+ "@pikku/core": "^0.11.0"
20
+ },
21
+ "dependencies": {
22
+ "ioredis": "^5.4.1"
23
+ },
24
+ "devDependencies": {
25
+ "typescript": "^5.9"
26
+ }
27
+ }
package/run-tests.sh ADDED
@@ -0,0 +1,33 @@
1
+ #!/bin/bash
2
+
3
+ # Parse command line arguments
4
+ WATCH_MODE=false
5
+ COVERAGE_MODE=false
6
+
7
+ for arg in "$@"
8
+ do
9
+ case $arg in
10
+ --watch)
11
+ WATCH_MODE=true
12
+ shift
13
+ ;;
14
+ --coverage)
15
+ COVERAGE_MODE=true
16
+ shift
17
+ ;;
18
+ esac
19
+ done
20
+
21
+ # Build the command
22
+ CMD="node --import tsx --test src/**/*.test.ts"
23
+
24
+ if [ "$WATCH_MODE" = true ]; then
25
+ CMD="$CMD --watch"
26
+ fi
27
+
28
+ if [ "$COVERAGE_MODE" = true ]; then
29
+ CMD="$CMD --experimental-test-coverage"
30
+ fi
31
+
32
+ # Execute the command
33
+ eval $CMD
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { RedisChannelStore } from './redis-channel-store.js'
2
+ export { RedisEventHubStore } from './redis-eventhub-store.js'
3
+ export { RedisWorkflowService } from './redis-workflow-service.js'
@@ -0,0 +1,136 @@
1
+ import { CoreUserSession } from '@pikku/core'
2
+ import { Channel, ChannelStore } from '@pikku/core/channel'
3
+ import { Redis, type RedisOptions } from 'ioredis'
4
+
5
+ /**
6
+ * Redis-based implementation of ChannelStore
7
+ *
8
+ * Stores channel state in Redis with configurable key prefix.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * const redis = new Redis({ host: 'localhost', port: 6379 })
13
+ * const store = new RedisChannelStore(redis, 'myapp')
14
+ * ```
15
+ */
16
+ export class RedisChannelStore extends ChannelStore {
17
+ private redis: Redis
18
+ private keyPrefix: string
19
+ private ownsConnection: boolean
20
+
21
+ /**
22
+ * @param connectionOrConfig - ioredis Redis instance or RedisOptions config
23
+ * @param keyPrefix - Redis key prefix (default: 'pikku')
24
+ */
25
+ constructor(
26
+ connectionOrConfig: Redis | RedisOptions | string,
27
+ keyPrefix = 'pikku'
28
+ ) {
29
+ super()
30
+ this.keyPrefix = keyPrefix
31
+
32
+ // Check if it's a Redis instance or config options
33
+ if (connectionOrConfig instanceof Redis) {
34
+ this.redis = connectionOrConfig
35
+ this.ownsConnection = false
36
+ } else if (typeof connectionOrConfig === 'string') {
37
+ // It's a connection string
38
+ this.redis = new Redis(connectionOrConfig)
39
+ this.ownsConnection = true
40
+ } else {
41
+ // It's a config object
42
+ this.redis = new Redis(connectionOrConfig)
43
+ this.ownsConnection = true
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Initialize the store (no-op for Redis, always ready)
49
+ */
50
+ public async init(): Promise<void> {
51
+ // Redis doesn't require schema initialization
52
+ // Just verify connection works
53
+ await this.redis.ping()
54
+ }
55
+
56
+ private channelKey(channelId: string): string {
57
+ return `${this.keyPrefix}:channel:${channelId}`
58
+ }
59
+
60
+ public async addChannel({
61
+ channelId,
62
+ channelName,
63
+ openingData,
64
+ }: Channel): Promise<void> {
65
+ const key = this.channelKey(channelId)
66
+
67
+ await this.redis.hmset(
68
+ key,
69
+ 'channelId',
70
+ channelId,
71
+ 'channelName',
72
+ channelName,
73
+ 'openingData',
74
+ JSON.stringify(openingData || {}),
75
+ 'createdAt',
76
+ Date.now().toString()
77
+ )
78
+ }
79
+
80
+ public async removeChannels(channelIds: string[]): Promise<void> {
81
+ if (channelIds.length === 0) {
82
+ return
83
+ }
84
+
85
+ const pipeline = this.redis.pipeline()
86
+
87
+ for (const channelId of channelIds) {
88
+ const key = this.channelKey(channelId)
89
+ pipeline.del(key)
90
+ // Also remove from subscriptions
91
+ pipeline.del(`${this.keyPrefix}:subs:${channelId}`)
92
+ }
93
+
94
+ await pipeline.exec()
95
+ }
96
+
97
+ public async setUserSession(
98
+ channelId: string,
99
+ session: CoreUserSession | null
100
+ ): Promise<void> {
101
+ const key = this.channelKey(channelId)
102
+
103
+ if (session) {
104
+ await this.redis.hset(key, 'userSession', JSON.stringify(session))
105
+ } else {
106
+ await this.redis.hdel(key, 'userSession')
107
+ }
108
+ }
109
+
110
+ public async getChannelAndSession(
111
+ channelId: string
112
+ ): Promise<Channel & { session: CoreUserSession }> {
113
+ const key = this.channelKey(channelId)
114
+ const data = await this.redis.hgetall(key)
115
+
116
+ if (!data.channelId) {
117
+ throw new Error(`Channel not found: ${channelId}`)
118
+ }
119
+
120
+ return {
121
+ channelId: data.channelId!,
122
+ channelName: data.channelName!,
123
+ openingData: data.openingData ? JSON.parse(data.openingData) : {},
124
+ session: data.userSession ? JSON.parse(data.userSession) : {},
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Close the Redis connection if owned by this store
130
+ */
131
+ public async close(): Promise<void> {
132
+ if (this.ownsConnection) {
133
+ await this.redis.quit()
134
+ }
135
+ }
136
+ }
@@ -0,0 +1,104 @@
1
+ import { EventHubStore } from '@pikku/core/channel'
2
+ import { Redis, type RedisOptions } from 'ioredis'
3
+
4
+ /**
5
+ * Redis-based implementation of EventHubStore
6
+ *
7
+ * Manages pub/sub topic subscriptions for channels in Redis.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * const redis = new Redis({ host: 'localhost', port: 6379 })
12
+ * const store = new RedisEventHubStore(redis, 'myapp')
13
+ * await store.subscribe('user.123', channelId)
14
+ * ```
15
+ */
16
+ export class RedisEventHubStore implements EventHubStore {
17
+ private redis: Redis
18
+ private keyPrefix: string
19
+ private ownsConnection: boolean
20
+
21
+ /**
22
+ * @param connectionOrConfig - ioredis Redis instance or RedisOptions config
23
+ * @param keyPrefix - Redis key prefix (default: 'pikku')
24
+ */
25
+ constructor(
26
+ connectionOrConfig: Redis | RedisOptions | string,
27
+ keyPrefix = 'pikku'
28
+ ) {
29
+ this.keyPrefix = keyPrefix
30
+
31
+ // Check if it's a Redis instance or config options
32
+ if (connectionOrConfig instanceof Redis) {
33
+ this.redis = connectionOrConfig
34
+ this.ownsConnection = false
35
+ } else if (typeof connectionOrConfig === 'string') {
36
+ // It's a connection string
37
+ this.redis = new Redis(connectionOrConfig)
38
+ this.ownsConnection = true
39
+ } else {
40
+ // It's a config object
41
+ this.redis = new Redis(connectionOrConfig)
42
+ this.ownsConnection = true
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Initialize the store (no-op for Redis, always ready)
48
+ */
49
+ public async init(): Promise<void> {
50
+ // Redis doesn't require schema initialization
51
+ await this.redis.ping()
52
+ }
53
+
54
+ private topicKey(topic: string): string {
55
+ return `${this.keyPrefix}:topic:${topic}`
56
+ }
57
+
58
+ private channelSubsKey(channelId: string): string {
59
+ return `${this.keyPrefix}:subs:${channelId}`
60
+ }
61
+
62
+ public async getChannelIdsForTopic(topic: string): Promise<string[]> {
63
+ const key = this.topicKey(topic)
64
+ return await this.redis.smembers(key)
65
+ }
66
+
67
+ public async subscribe(topic: string, channelId: string): Promise<boolean> {
68
+ const topicKey = this.topicKey(topic)
69
+ const subsKey = this.channelSubsKey(channelId)
70
+
71
+ // Add channelId to topic set and topic to channel's subscription set
72
+ const pipeline = this.redis.pipeline()
73
+ pipeline.sadd(topicKey, channelId)
74
+ pipeline.sadd(subsKey, topic)
75
+
76
+ await pipeline.exec()
77
+
78
+ return true
79
+ }
80
+
81
+ public async unsubscribe(topic: string, channelId: string): Promise<boolean> {
82
+ const topicKey = this.topicKey(topic)
83
+ const subsKey = this.channelSubsKey(channelId)
84
+
85
+ // Remove channelId from topic set and topic from channel's subscription set
86
+ const pipeline = this.redis.pipeline()
87
+ pipeline.srem(topicKey, channelId)
88
+ pipeline.srem(subsKey, topic)
89
+
90
+ const results = await pipeline.exec()
91
+
92
+ // Check if the channel was actually in the topic set
93
+ return (results![0]![1] as number) > 0
94
+ }
95
+
96
+ /**
97
+ * Close the Redis connection if owned by this store
98
+ */
99
+ public async close(): Promise<void> {
100
+ if (this.ownsConnection) {
101
+ await this.redis.quit()
102
+ }
103
+ }
104
+ }