@mulingai-npm/redis 3.17.2 → 3.18.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,115 @@
1
+ import { RedisClient } from '../redis-client';
2
+ /**
3
+ * Demo Room State
4
+ * Tracks the runtime state of an active demo room
5
+ */
6
+ export interface DemoRoomState {
7
+ roomId: string;
8
+ userId: string;
9
+ demoType: 'preTranscribed' | 'alwaysLive';
10
+ sourceLanguage: string;
11
+ targetLanguages: string[];
12
+ currentChunkIndex: number;
13
+ totalChunks: number;
14
+ loopCount: number;
15
+ startedAt: number;
16
+ demoStartAt: number | null;
17
+ demoDurationMinutes: number;
18
+ isActive: boolean;
19
+ isPaused: boolean;
20
+ lastChunkEmittedAt: number | null;
21
+ createdAt: number;
22
+ updatedAt: number;
23
+ }
24
+ /**
25
+ * Demo State Manager
26
+ * Manages Redis-backed state for active demo rooms
27
+ */
28
+ export declare class DemoStateManager {
29
+ private redisClient;
30
+ private readonly EXPIRATION;
31
+ private readonly KEY_PREFIX;
32
+ private readonly ACTIVE_DEMOS_SET;
33
+ constructor(redisClient: RedisClient);
34
+ /**
35
+ * Generate Redis key for demo room state
36
+ */
37
+ private getStateKey;
38
+ /**
39
+ * Serialize state to Redis format
40
+ */
41
+ private serializeState;
42
+ /**
43
+ * Deserialize state from Redis format
44
+ */
45
+ private deserializeState;
46
+ /**
47
+ * Initialize a new demo room state
48
+ */
49
+ createDemoState(params: {
50
+ roomId: string;
51
+ userId: string;
52
+ demoType: 'preTranscribed' | 'alwaysLive';
53
+ sourceLanguage: string;
54
+ targetLanguages: string[];
55
+ totalChunks: number;
56
+ demoStartAt: number | null;
57
+ demoDurationMinutes: number;
58
+ }): Promise<DemoRoomState>;
59
+ /**
60
+ * Get demo room state
61
+ */
62
+ getDemoState(roomId: string): Promise<DemoRoomState | null>;
63
+ /**
64
+ * Update demo state (incremental update)
65
+ */
66
+ updateDemoState(roomId: string, updates: Partial<Omit<DemoRoomState, 'roomId' | 'userId' | 'createdAt'>>): Promise<DemoRoomState | null>;
67
+ /**
68
+ * Advance to next chunk
69
+ * Handles looping when reaching end of transcriptions
70
+ */
71
+ advanceToNextChunk(roomId: string): Promise<DemoRoomState | null>;
72
+ /**
73
+ * Check if demo should still be running
74
+ * Returns true if demo should continue, false if it should stop
75
+ */
76
+ isDemoStillActive(state: DemoRoomState): boolean;
77
+ /**
78
+ * Stop a demo room
79
+ */
80
+ stopDemo(roomId: string): Promise<boolean>;
81
+ /**
82
+ * Pause a demo room
83
+ */
84
+ pauseDemo(roomId: string): Promise<boolean>;
85
+ /**
86
+ * Resume a paused demo room
87
+ */
88
+ resumeDemo(roomId: string): Promise<boolean>;
89
+ /**
90
+ * Get all active demo room IDs
91
+ */
92
+ getActiveDemoRooms(): Promise<string[]>;
93
+ /**
94
+ * Delete demo state completely
95
+ */
96
+ deleteDemoState(roomId: string): Promise<void>;
97
+ /**
98
+ * Get statistics about a demo
99
+ */
100
+ getDemoStats(roomId: string): Promise<{
101
+ totalChunksProcessed: number;
102
+ loopCount: number;
103
+ uptimeMinutes: number;
104
+ isActive: boolean;
105
+ } | null>;
106
+ /**
107
+ * Check if a demo room is currently active
108
+ */
109
+ isDemoActive(roomId: string): Promise<boolean>;
110
+ /**
111
+ * Cleanup stale demos (safety mechanism)
112
+ * Call this periodically to remove demos that should have ended
113
+ */
114
+ cleanupStaleDemos(): Promise<number>;
115
+ }
@@ -0,0 +1,282 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DemoStateManager = void 0;
4
+ /**
5
+ * Demo State Manager
6
+ * Manages Redis-backed state for active demo rooms
7
+ */
8
+ class DemoStateManager {
9
+ constructor(redisClient) {
10
+ this.redisClient = redisClient;
11
+ this.EXPIRATION = 24 * 60 * 60; // 24 hours
12
+ this.KEY_PREFIX = 'demo:room:';
13
+ this.ACTIVE_DEMOS_SET = 'demo:active';
14
+ }
15
+ /**
16
+ * Generate Redis key for demo room state
17
+ */
18
+ getStateKey(roomId) {
19
+ return `${this.KEY_PREFIX}${roomId}`;
20
+ }
21
+ /**
22
+ * Serialize state to Redis format
23
+ */
24
+ serializeState(state) {
25
+ return {
26
+ roomId: state.roomId,
27
+ userId: state.userId,
28
+ demoType: state.demoType,
29
+ sourceLanguage: state.sourceLanguage,
30
+ targetLanguages: JSON.stringify(state.targetLanguages),
31
+ currentChunkIndex: String(state.currentChunkIndex),
32
+ totalChunks: String(state.totalChunks),
33
+ loopCount: String(state.loopCount),
34
+ startedAt: String(state.startedAt),
35
+ demoStartAt: state.demoStartAt ? String(state.demoStartAt) : '',
36
+ demoDurationMinutes: String(state.demoDurationMinutes),
37
+ isActive: String(state.isActive),
38
+ isPaused: String(state.isPaused),
39
+ lastChunkEmittedAt: state.lastChunkEmittedAt ? String(state.lastChunkEmittedAt) : '',
40
+ createdAt: String(state.createdAt),
41
+ updatedAt: String(state.updatedAt),
42
+ };
43
+ }
44
+ /**
45
+ * Deserialize state from Redis format
46
+ */
47
+ deserializeState(data) {
48
+ return {
49
+ roomId: data.roomId,
50
+ userId: data.userId,
51
+ demoType: data.demoType,
52
+ sourceLanguage: data.sourceLanguage,
53
+ targetLanguages: JSON.parse(data.targetLanguages),
54
+ currentChunkIndex: parseInt(data.currentChunkIndex, 10),
55
+ totalChunks: parseInt(data.totalChunks, 10),
56
+ loopCount: parseInt(data.loopCount, 10),
57
+ startedAt: parseInt(data.startedAt, 10),
58
+ demoStartAt: data.demoStartAt ? parseInt(data.demoStartAt, 10) : null,
59
+ demoDurationMinutes: parseInt(data.demoDurationMinutes, 10),
60
+ isActive: data.isActive === 'true',
61
+ isPaused: data.isPaused === 'true',
62
+ lastChunkEmittedAt: data.lastChunkEmittedAt ? parseInt(data.lastChunkEmittedAt, 10) : null,
63
+ createdAt: parseInt(data.createdAt, 10),
64
+ updatedAt: parseInt(data.updatedAt, 10),
65
+ };
66
+ }
67
+ /**
68
+ * Initialize a new demo room state
69
+ */
70
+ async createDemoState(params) {
71
+ const now = Date.now();
72
+ const state = {
73
+ roomId: params.roomId,
74
+ userId: params.userId,
75
+ demoType: params.demoType,
76
+ sourceLanguage: params.sourceLanguage,
77
+ targetLanguages: params.targetLanguages,
78
+ currentChunkIndex: 0,
79
+ totalChunks: params.totalChunks,
80
+ loopCount: 0,
81
+ startedAt: now,
82
+ demoStartAt: params.demoStartAt,
83
+ demoDurationMinutes: params.demoDurationMinutes,
84
+ isActive: true,
85
+ isPaused: false,
86
+ lastChunkEmittedAt: null,
87
+ createdAt: now,
88
+ updatedAt: now,
89
+ };
90
+ const key = this.getStateKey(params.roomId);
91
+ const serialized = this.serializeState(state);
92
+ // Store state and add to active demos set
93
+ const pipeline = this.redisClient.pipeline();
94
+ pipeline.hset(key, serialized);
95
+ pipeline.expire(key, this.EXPIRATION);
96
+ pipeline.sadd(this.ACTIVE_DEMOS_SET, params.roomId);
97
+ await pipeline.exec();
98
+ return state;
99
+ }
100
+ /**
101
+ * Get demo room state
102
+ */
103
+ async getDemoState(roomId) {
104
+ const key = this.getStateKey(roomId);
105
+ const data = await this.redisClient.hgetall(key);
106
+ if (!data || Object.keys(data).length === 0) {
107
+ return null;
108
+ }
109
+ return this.deserializeState(data);
110
+ }
111
+ /**
112
+ * Update demo state (incremental update)
113
+ */
114
+ async updateDemoState(roomId, updates) {
115
+ const current = await this.getDemoState(roomId);
116
+ if (!current) {
117
+ return null;
118
+ }
119
+ const updated = {
120
+ ...current,
121
+ ...updates,
122
+ updatedAt: Date.now(),
123
+ };
124
+ const key = this.getStateKey(roomId);
125
+ const serialized = this.serializeState(updated);
126
+ const pipeline = this.redisClient.pipeline();
127
+ pipeline.hset(key, serialized);
128
+ pipeline.expire(key, this.EXPIRATION);
129
+ await pipeline.exec();
130
+ return updated;
131
+ }
132
+ /**
133
+ * Advance to next chunk
134
+ * Handles looping when reaching end of transcriptions
135
+ */
136
+ async advanceToNextChunk(roomId) {
137
+ const state = await this.getDemoState(roomId);
138
+ if (!state) {
139
+ return null;
140
+ }
141
+ const nextIndex = state.currentChunkIndex + 1;
142
+ let newLoopCount = state.loopCount;
143
+ // Check if we've reached the end
144
+ if (nextIndex >= state.totalChunks) {
145
+ // Loop back to beginning
146
+ return await this.updateDemoState(roomId, {
147
+ currentChunkIndex: 0,
148
+ loopCount: state.loopCount + 1,
149
+ lastChunkEmittedAt: Date.now(),
150
+ });
151
+ }
152
+ // Normal advancement
153
+ return await this.updateDemoState(roomId, {
154
+ currentChunkIndex: nextIndex,
155
+ lastChunkEmittedAt: Date.now(),
156
+ });
157
+ }
158
+ /**
159
+ * Check if demo should still be running
160
+ * Returns true if demo should continue, false if it should stop
161
+ */
162
+ isDemoStillActive(state) {
163
+ // Always-live demos run forever
164
+ if (state.demoType === 'alwaysLive') {
165
+ return true;
166
+ }
167
+ // Check if scheduled demo duration has expired
168
+ if (state.demoDurationMinutes === 0) {
169
+ return true; // Duration 0 = infinite
170
+ }
171
+ const now = Date.now();
172
+ const elapsedMs = now - state.startedAt;
173
+ const durationMs = state.demoDurationMinutes * 60 * 1000;
174
+ return elapsedMs < durationMs;
175
+ }
176
+ /**
177
+ * Stop a demo room
178
+ */
179
+ async stopDemo(roomId) {
180
+ const state = await this.getDemoState(roomId);
181
+ if (!state) {
182
+ return false;
183
+ }
184
+ await this.updateDemoState(roomId, {
185
+ isActive: false,
186
+ isPaused: false,
187
+ });
188
+ // Remove from active demos set
189
+ await this.redisClient.srem(this.ACTIVE_DEMOS_SET, roomId);
190
+ return true;
191
+ }
192
+ /**
193
+ * Pause a demo room
194
+ */
195
+ async pauseDemo(roomId) {
196
+ const state = await this.getDemoState(roomId);
197
+ if (!state) {
198
+ return false;
199
+ }
200
+ await this.updateDemoState(roomId, {
201
+ isPaused: true,
202
+ });
203
+ return true;
204
+ }
205
+ /**
206
+ * Resume a paused demo room
207
+ */
208
+ async resumeDemo(roomId) {
209
+ const state = await this.getDemoState(roomId);
210
+ if (!state) {
211
+ return false;
212
+ }
213
+ await this.updateDemoState(roomId, {
214
+ isPaused: false,
215
+ });
216
+ return true;
217
+ }
218
+ /**
219
+ * Get all active demo room IDs
220
+ */
221
+ async getActiveDemoRooms() {
222
+ return await this.redisClient.smembers(this.ACTIVE_DEMOS_SET);
223
+ }
224
+ /**
225
+ * Delete demo state completely
226
+ */
227
+ async deleteDemoState(roomId) {
228
+ const key = this.getStateKey(roomId);
229
+ const pipeline = this.redisClient.pipeline();
230
+ pipeline.del(key);
231
+ pipeline.srem(this.ACTIVE_DEMOS_SET, roomId);
232
+ await pipeline.exec();
233
+ }
234
+ /**
235
+ * Get statistics about a demo
236
+ */
237
+ async getDemoStats(roomId) {
238
+ const state = await this.getDemoState(roomId);
239
+ if (!state) {
240
+ return null;
241
+ }
242
+ const now = Date.now();
243
+ const uptimeMs = now - state.startedAt;
244
+ return {
245
+ totalChunksProcessed: state.loopCount * state.totalChunks + state.currentChunkIndex,
246
+ loopCount: state.loopCount,
247
+ uptimeMinutes: Math.floor(uptimeMs / 1000 / 60),
248
+ isActive: state.isActive,
249
+ };
250
+ }
251
+ /**
252
+ * Check if a demo room is currently active
253
+ */
254
+ async isDemoActive(roomId) {
255
+ const state = await this.getDemoState(roomId);
256
+ return state !== null && state.isActive;
257
+ }
258
+ /**
259
+ * Cleanup stale demos (safety mechanism)
260
+ * Call this periodically to remove demos that should have ended
261
+ */
262
+ async cleanupStaleDemos() {
263
+ const activeDemoIds = await this.getActiveDemoRooms();
264
+ let cleaned = 0;
265
+ for (const roomId of activeDemoIds) {
266
+ const state = await this.getDemoState(roomId);
267
+ if (!state) {
268
+ // State missing but in active set - cleanup
269
+ await this.redisClient.srem(this.ACTIVE_DEMOS_SET, roomId);
270
+ cleaned++;
271
+ continue;
272
+ }
273
+ // Check if demo should still be running
274
+ if (!this.isDemoStillActive(state)) {
275
+ await this.stopDemo(roomId);
276
+ cleaned++;
277
+ }
278
+ }
279
+ return cleaned;
280
+ }
281
+ }
282
+ exports.DemoStateManager = DemoStateManager;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mulingai-npm/redis",
3
- "version": "3.17.2",
3
+ "version": "3.18.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "repository": {