@mulingai-npm/redis 2.4.0 → 2.5.2

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.
@@ -9,20 +9,29 @@ export type MulingstreamSpeakerData = {
9
9
  prompts: string;
10
10
  recordingsDuration: number;
11
11
  targetLanguages: string[];
12
+ timestamp: string;
12
13
  };
13
14
  export declare class MulingstreamSpeakerManager {
14
15
  private redisClient;
15
16
  constructor(redisClient: RedisClient);
16
- private parseHashData;
17
+ private parseHash;
17
18
  private serialize;
18
- private buildKey;
19
19
  private buildId;
20
- addSpeaker(payload: Omit<MulingstreamSpeakerData, 'speakerId'>): Promise<string>;
21
- removeSpeaker(roomId: string, socketId: string): Promise<boolean>;
22
- getSpeakersByRoom(roomId: string): Promise<MulingstreamSpeakerData[]>;
23
- getSpeakerBySocketId(roomId: string, socketId: string): Promise<MulingstreamSpeakerData | null>;
24
- getSpeakerByUserId(roomId: string, userId: string): Promise<MulingstreamSpeakerData | null>;
25
- updateSourceLanguage(roomId: string, socketId: string, newLang: string): Promise<boolean>;
26
- updateTargetLanguages(roomId: string, socketId: string, languages: string[]): Promise<boolean>;
27
- increaseRecordingDuration(roomId: string, socketId: string, delta: number): Promise<number>;
20
+ private buildKey;
21
+ addSpeaker(payload: Omit<MulingstreamSpeakerData, 'speakerId' | 'timestamp'>): Promise<string>;
22
+ removeSpeakerBySocketId(socketId: string): Promise<boolean>;
23
+ removeSpeakersByUserId(userId: string): Promise<number>;
24
+ removeSpeakersByRoomId(roomId: string): Promise<number>;
25
+ private removeSpeakerById;
26
+ getSpeakerBySpeakerId(speakerId: string): Promise<MulingstreamSpeakerData | null>;
27
+ getSpeakerBySocketId(socketId: string): Promise<MulingstreamSpeakerData | null>;
28
+ getSpeakersByRoomId(roomId: string): Promise<MulingstreamSpeakerData[]>;
29
+ getSpeakersByUserId(userId: string): Promise<MulingstreamSpeakerData[]>;
30
+ getSpeakerByUserRoom(roomId: string, userId: string): Promise<MulingstreamSpeakerData | null>;
31
+ getSpeakersLength(roomId: string): Promise<number>;
32
+ isRoomEmpty(roomId: string): Promise<boolean>;
33
+ updateSourceLanguage(socketId: string, newLang: string): Promise<boolean>;
34
+ updateTargetLanguages(socketId: string, languages: string[]): Promise<boolean>;
35
+ increaseRecordingDuration(socketId: string, delta: number): Promise<number>;
36
+ private cleanIndexes;
28
37
  }
@@ -6,7 +6,7 @@ class MulingstreamSpeakerManager {
6
6
  constructor(redisClient) {
7
7
  this.redisClient = redisClient;
8
8
  }
9
- parseHashData(hash) {
9
+ parseHash(hash) {
10
10
  return {
11
11
  speakerId: hash.speakerId,
12
12
  userId: hash.userId,
@@ -16,7 +16,8 @@ class MulingstreamSpeakerManager {
16
16
  theme: hash.theme,
17
17
  prompts: hash.prompts,
18
18
  recordingsDuration: parseInt(hash.recordingsDuration, 10) || 0,
19
- targetLanguages: JSON.parse(hash.targetLanguages || '[]')
19
+ targetLanguages: JSON.parse(hash.targetLanguages || '[]'),
20
+ timestamp: hash.timestamp
20
21
  };
21
22
  }
22
23
  serialize(data) {
@@ -29,79 +30,196 @@ class MulingstreamSpeakerManager {
29
30
  theme: data.theme,
30
31
  prompts: data.prompts,
31
32
  recordingsDuration: data.recordingsDuration.toString(),
32
- targetLanguages: JSON.stringify(data.targetLanguages)
33
+ targetLanguages: JSON.stringify(data.targetLanguages),
34
+ timestamp: data.timestamp
33
35
  };
34
36
  }
35
- buildKey(speakerId) {
36
- return `speaker:${speakerId}`;
37
- }
38
37
  buildId(roomId, userId, socketId) {
39
38
  return `[${roomId}]-[${userId}]-[${socketId}]`;
40
39
  }
40
+ buildKey(speakerId) {
41
+ return `speaker:${speakerId}`;
42
+ }
41
43
  async addSpeaker(payload) {
42
44
  const speakerId = this.buildId(payload.roomId, payload.userId, payload.socketId);
43
- await this.redisClient.hset(this.buildKey(speakerId), this.serialize({ ...payload, speakerId }));
44
- await this.redisClient.expire(this.buildKey(speakerId), EXPIRATION);
45
+ const key = this.buildKey(speakerId);
46
+ const speakerData = {
47
+ ...payload,
48
+ speakerId,
49
+ timestamp: new Date(Date.now()).toISOString()
50
+ };
51
+ await this.redisClient.hset(key, this.serialize(speakerData));
52
+ await this.redisClient.expire(key, EXPIRATION);
53
+ // index sets / mapping
54
+ await this.redisClient.sadd(`room:${payload.roomId}:speakers`, speakerId);
55
+ await this.redisClient.sadd(`user:${payload.userId}:speakers`, speakerId);
56
+ await this.redisClient.set(`socket:${payload.socketId}:speaker`, speakerId);
57
+ await this.redisClient.expire(`socket:${payload.socketId}:speaker`, EXPIRATION);
45
58
  return speakerId;
46
59
  }
47
- async removeSpeaker(roomId, socketId) {
48
- const speaker = await this.getSpeakerBySocketId(roomId, socketId);
49
- if (!speaker) {
60
+ async removeSpeakerBySocketId(socketId) {
61
+ const speakerId = await this.redisClient.get(`socket:${socketId}:speaker`);
62
+ if (speakerId === null) {
50
63
  return false;
51
64
  }
52
- await this.redisClient.del(this.buildKey(speaker.speakerId));
65
+ const removed = await this.removeSpeakerById(speakerId);
66
+ return removed;
67
+ }
68
+ async removeSpeakersByUserId(userId) {
69
+ const ids = await this.redisClient.smembers(`user:${userId}:speakers`);
70
+ if (ids.length === 0) {
71
+ return 0;
72
+ }
73
+ let deletedCount = 0;
74
+ for (const id of ids) {
75
+ if (await this.removeSpeakerById(id)) {
76
+ deletedCount += 1;
77
+ }
78
+ }
79
+ return deletedCount;
80
+ }
81
+ async removeSpeakersByRoomId(roomId) {
82
+ const ids = await this.redisClient.smembers(`room:${roomId}:speakers`);
83
+ if (ids.length === 0) {
84
+ return 0;
85
+ }
86
+ let deletedCount = 0;
87
+ for (const id of ids) {
88
+ if (await this.removeSpeakerById(id)) {
89
+ deletedCount += 1;
90
+ }
91
+ }
92
+ return deletedCount;
93
+ }
94
+ async removeSpeakerById(speakerId) {
95
+ const key = this.buildKey(speakerId);
96
+ const data = await this.redisClient.hgetall(key);
97
+ if (data === null || Object.keys(data).length === 0) {
98
+ // already gone; clean indexes anyway
99
+ await this.cleanIndexes(speakerId);
100
+ return false;
101
+ }
102
+ await this.redisClient.del(key);
103
+ await this.cleanIndexes(speakerId);
53
104
  return true;
54
105
  }
55
- async getSpeakersByRoom(roomId) {
56
- const keys = await this.redisClient.keys(`speaker:[${roomId}]-*`);
57
- if (!keys.length) {
58
- return [];
106
+ async getSpeakerBySpeakerId(speakerId) {
107
+ const key = this.buildKey(speakerId);
108
+ const hash = await this.redisClient.hgetall(key);
109
+ if (hash === null || Object.keys(hash).length === 0) {
110
+ // the hash has expired or was never there – remove any stray index refs
111
+ await this.cleanIndexes(speakerId);
112
+ return null;
113
+ }
114
+ return this.parseHash(hash);
115
+ }
116
+ async getSpeakerBySocketId(socketId) {
117
+ const speakerId = await this.redisClient.get(`socket:${socketId}:speaker`);
118
+ if (speakerId === null) {
119
+ return null;
120
+ }
121
+ const hash = await this.redisClient.hgetall(this.buildKey(speakerId));
122
+ if (hash !== null && Object.keys(hash).length > 0) {
123
+ return this.parseHash(hash);
124
+ }
125
+ // hash expired: tidy indexes
126
+ await this.cleanIndexes(speakerId);
127
+ return null;
128
+ }
129
+ async getSpeakersByRoomId(roomId) {
130
+ const ids = await this.redisClient.smembers(`room:${roomId}:speakers`);
131
+ const result = [];
132
+ for (const id of ids) {
133
+ const hash = await this.redisClient.hgetall(this.buildKey(id));
134
+ if (hash !== null && Object.keys(hash).length > 0) {
135
+ result.push(this.parseHash(hash));
136
+ }
137
+ else {
138
+ await this.cleanIndexes(id);
139
+ }
140
+ }
141
+ return result;
142
+ }
143
+ async getSpeakersByUserId(userId) {
144
+ const ids = await this.redisClient.smembers(`user:${userId}:speakers`);
145
+ const result = [];
146
+ for (const id of ids) {
147
+ const hash = await this.redisClient.hgetall(this.buildKey(id));
148
+ if (hash !== null && Object.keys(hash).length > 0) {
149
+ result.push(this.parseHash(hash));
150
+ }
151
+ else {
152
+ await this.cleanIndexes(id);
153
+ }
59
154
  }
60
- const out = [];
61
- for (const k of keys) {
62
- const hash = await this.redisClient.hgetall(k);
63
- if (hash && Object.keys(hash).length)
64
- out.push(this.parseHashData(hash));
155
+ return result;
156
+ }
157
+ async getSpeakerByUserRoom(roomId, userId) {
158
+ const ids = await this.redisClient.smembers(`room:${roomId}:speakers`);
159
+ for (const id of ids) {
160
+ if (id.includes(`]-[${userId}]-[`)) {
161
+ const hash = await this.redisClient.hgetall(this.buildKey(id));
162
+ if (hash !== null && Object.keys(hash).length > 0) {
163
+ return this.parseHash(hash);
164
+ }
165
+ await this.cleanIndexes(id);
166
+ return null;
167
+ }
65
168
  }
66
- return out;
169
+ return null;
67
170
  }
68
- async getSpeakerBySocketId(roomId, socketId) {
69
- var _a;
70
- const speakers = await this.getSpeakersByRoom(roomId);
71
- return (_a = speakers.find((s) => s.socketId === socketId)) !== null && _a !== void 0 ? _a : null;
171
+ async getSpeakersLength(roomId) {
172
+ const speakers = await this.getSpeakersByRoomId(roomId);
173
+ return speakers.length;
72
174
  }
73
- async getSpeakerByUserId(roomId, userId) {
74
- var _a;
75
- const speakers = await this.getSpeakersByRoom(roomId);
76
- return (_a = speakers.find((s) => s.userId === userId)) !== null && _a !== void 0 ? _a : null;
175
+ async isRoomEmpty(roomId) {
176
+ const totalSpeakers = await this.getSpeakersLength(roomId);
177
+ return totalSpeakers === 0;
77
178
  }
78
- async updateSourceLanguage(roomId, socketId, newLang) {
79
- const speaker = await this.getSpeakerBySocketId(roomId, socketId);
80
- if (!speaker)
179
+ async updateSourceLanguage(socketId, newLang) {
180
+ const speaker = await this.getSpeakerBySocketId(socketId);
181
+ if (speaker === null) {
81
182
  return false;
183
+ }
82
184
  await this.redisClient.hset(this.buildKey(speaker.speakerId), {
83
185
  sourceLanguage: newLang
84
186
  });
85
187
  return true;
86
188
  }
87
- async updateTargetLanguages(roomId, socketId, languages) {
88
- const speaker = await this.getSpeakerBySocketId(roomId, socketId);
89
- if (!speaker)
189
+ async updateTargetLanguages(socketId, languages) {
190
+ const speaker = await this.getSpeakerBySocketId(socketId);
191
+ if (speaker === null) {
90
192
  return false;
193
+ }
91
194
  await this.redisClient.hset(this.buildKey(speaker.speakerId), {
92
195
  targetLanguages: JSON.stringify(languages)
93
196
  });
94
197
  return true;
95
198
  }
96
- async increaseRecordingDuration(roomId, socketId, delta) {
97
- const speaker = await this.getSpeakerBySocketId(roomId, socketId);
98
- if (!speaker)
199
+ async increaseRecordingDuration(socketId, delta) {
200
+ const speaker = await this.getSpeakerBySocketId(socketId);
201
+ if (speaker === null) {
99
202
  return -1;
203
+ }
100
204
  speaker.recordingsDuration += delta;
101
205
  await this.redisClient.hset(this.buildKey(speaker.speakerId), {
102
206
  recordingsDuration: speaker.recordingsDuration.toString()
103
207
  });
104
208
  return speaker.recordingsDuration;
105
209
  }
210
+ async cleanIndexes(speakerId) {
211
+ const parts = speakerId
212
+ .substring(1, speakerId.length - 1) // remove outer [...]
213
+ .split(']-['); // ['roomId','userId','socketId']
214
+ if (parts.length !== 3) {
215
+ return;
216
+ }
217
+ const roomId = parts[0];
218
+ const userId = parts[1];
219
+ const socketId = parts[2];
220
+ await this.redisClient.srem(`room:${roomId}:speakers`, speakerId);
221
+ await this.redisClient.srem(`user:${userId}:speakers`, speakerId);
222
+ await this.redisClient.del(`socket:${socketId}:speaker`);
223
+ }
106
224
  }
107
225
  exports.MulingstreamSpeakerManager = MulingstreamSpeakerManager;
@@ -52,10 +52,11 @@ class RedisClient {
52
52
  }
53
53
  // flush methods
54
54
  async flushAll() {
55
- console.log('Removing all data!');
55
+ console.log('Flushing All Redis Data!');
56
56
  return this.client.flushall();
57
57
  }
58
58
  async flushDb() {
59
+ console.log('Flushing Redis Data from DB!');
59
60
  return this.client.flushdb();
60
61
  }
61
62
  async expire(key, seconds) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mulingai-npm/redis",
3
- "version": "2.4.0",
3
+ "version": "2.5.2",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "repository": {