@mulingai-npm/redis 3.0.0 → 3.0.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.
package/README.md ADDED
@@ -0,0 +1,7 @@
1
+ # Redis CLI
2
+
3
+ docker exec -it local-redis redis-cli
4
+
5
+ ## choose DB
6
+
7
+ 127.0.0.1:6379> SELECT 1
@@ -0,0 +1,20 @@
1
+ import { MulingstreamChunkManager, MulingstreamChunkData } from '../managers/mulingstream-chunk-manager';
2
+ export interface ChunkLogOptions {
3
+ chunkId?: boolean;
4
+ chunkNumber?: boolean;
5
+ roomId?: boolean;
6
+ language?: boolean;
7
+ processLength?: boolean;
8
+ finalTranscription?: boolean;
9
+ sttStatus?: boolean;
10
+ stt?: boolean;
11
+ translation?: boolean;
12
+ tts?: boolean;
13
+ retryMs?: number;
14
+ label?: string;
15
+ }
16
+ export declare class MulingstreamChunkLogger extends MulingstreamChunkManager {
17
+ logChunk(roomId: string, chunkNumber: number, opts?: ChunkLogOptions): Promise<void>;
18
+ logChunks(roomId: string, opts?: ChunkLogOptions): Promise<Record<string, unknown>[]>;
19
+ mapChunk(chunk: MulingstreamChunkData, cfg?: ChunkLogOptions): Record<string, unknown>;
20
+ }
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MulingstreamChunkLogger = void 0;
4
+ const mulingstream_chunk_manager_1 = require("../managers/mulingstream-chunk-manager");
5
+ const defaults = {
6
+ chunkId: false,
7
+ chunkNumber: true,
8
+ roomId: false,
9
+ processLength: false,
10
+ language: false,
11
+ finalTranscription: true,
12
+ sttStatus: true,
13
+ stt: false,
14
+ translation: false,
15
+ tts: true,
16
+ retryMs: undefined,
17
+ label: ''
18
+ };
19
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
20
+ class MulingstreamChunkLogger extends mulingstream_chunk_manager_1.MulingstreamChunkManager {
21
+ /* ------------ one chunk ------------------------------------ */
22
+ /* ------------ one chunk ------------------------------------ */
23
+ async logChunk(roomId, chunkNumber, opts = {}) {
24
+ const cfg = { ...defaults, ...opts };
25
+ const prefix = cfg.label ? `[${cfg.label}] ` : '';
26
+ let chunk = await this.getMulingstreamChunkById(roomId, chunkNumber);
27
+ if (!chunk) {
28
+ await sleep(cfg.retryMs);
29
+ chunk = await this.getMulingstreamChunkById(roomId, chunkNumber);
30
+ }
31
+ if (!chunk) {
32
+ console.log(`${prefix}[logger] room ${roomId} chunk ${chunkNumber} not found`);
33
+ return;
34
+ }
35
+ if (cfg.label)
36
+ console.log(prefix.trim()); // single-line label
37
+ console.dir(this.mapChunk(chunk, cfg), { depth: null, colors: true });
38
+ }
39
+ /* ------------ all chunks in room ---------------------------- */
40
+ async logChunks(roomId, opts = {}) {
41
+ const cfg = { ...defaults, ...opts };
42
+ const prefix = cfg.label ? `[${cfg.label}] ` : '';
43
+ let chunks = await this.getRoomById(roomId);
44
+ if (!(chunks === null || chunks === void 0 ? void 0 : chunks.length)) {
45
+ await sleep(cfg.retryMs);
46
+ chunks = await this.getRoomById(roomId);
47
+ }
48
+ if (!(chunks === null || chunks === void 0 ? void 0 : chunks.length)) {
49
+ console.log(`${prefix}[logger] room ${roomId} is empty / not found`);
50
+ return [];
51
+ }
52
+ const view = chunks.map((c) => this.mapChunk(c, cfg));
53
+ if (cfg.label)
54
+ console.log(prefix.trim());
55
+ console.dir(view, { depth: null, colors: true });
56
+ return view;
57
+ }
58
+ /* ------------ filter helper -------------------------------- */
59
+ mapChunk(chunk, cfg = {}) {
60
+ const o = { ...defaults, ...cfg };
61
+ const out = {};
62
+ if (o.chunkId)
63
+ out.chunkId = chunk.chunkId;
64
+ if (o.roomId)
65
+ out.roomId = chunk.roomId;
66
+ if (o.chunkNumber)
67
+ out.chunkNumber = chunk.chunkNumber;
68
+ if (o.processLength)
69
+ out.processLength = Date.now() - chunk.createdAt + 'ms';
70
+ if (o.language)
71
+ out.language = chunk.language;
72
+ if (o.finalTranscription)
73
+ out.finalTranscription = chunk.finalTranscription;
74
+ if (o.sttStatus)
75
+ out.sttStatus = chunk.sttStatus;
76
+ if (o.stt)
77
+ out.stt = chunk.stt;
78
+ if (o.translation)
79
+ out.translation = chunk.translation;
80
+ if (o.tts)
81
+ out.tts = chunk.tts;
82
+ return out;
83
+ }
84
+ }
85
+ exports.MulingstreamChunkLogger = MulingstreamChunkLogger;
@@ -2,6 +2,7 @@ import { RedisClient } from '../redis-client';
2
2
  export type StepStatus = 'INIT' | 'DISCARDED' | 'READY' | 'USED';
3
3
  export type SttProvider = 'azure' | 'whisper' | 'google' | 'aws';
4
4
  export type MulingstreamChunkData = {
5
+ chunkId: string;
5
6
  roomId: string;
6
7
  chunkNumber: number;
7
8
  language: string;
@@ -44,25 +45,17 @@ export type MulingstreamChunkData = {
44
45
  export declare class MulingstreamChunkManager {
45
46
  private redisClient;
46
47
  constructor(redisClient: RedisClient);
48
+ private roomZsetKey;
49
+ private chunkHashKey;
50
+ private generateChunkId;
51
+ private serialize;
52
+ private deserialize;
53
+ private hashToChunk;
47
54
  private getTimeout;
48
- /**
49
- * Initializes a room in Redis as an empty JSON array.
50
- * If the key [roomId] already exists, we do NOT overwrite it.
51
- * Returns true if room was created, false if room already existed.
52
- */
53
55
  initRoom(roomId: string): Promise<boolean>;
54
- /**
55
- * Returns all rooms. This is naive if you store many other keys in Redis,
56
- * because we search keys for pattern "[*]".
57
- * Adjust as needed if you have a separate naming prefix.
58
- */
59
56
  getRooms(): Promise<string[]>;
60
- /**
61
- * Returns the entire array of chunks for the given roomId,
62
- * or null if the room doesn't exist in Redis.
63
- */
64
57
  getMulingstreamChunksByRoom(roomId: string): Promise<MulingstreamChunkData[] | null>;
65
- getRoomById(roomId: string): Promise<MulingstreamChunkData[] | null>;
58
+ getRoomById(roomId: string): Promise<MulingstreamChunkData[]>;
66
59
  addMulingstreamChunk(params: {
67
60
  roomId: string;
68
61
  chunkNumber: number;
@@ -77,53 +70,40 @@ export declare class MulingstreamChunkManager {
77
70
  targetLanguages: string[];
78
71
  shortCodeTargetLanguages: string[];
79
72
  }): Promise<void>;
80
- /**
81
- * Given roomId and chunkNumber, return the single chunk from the array
82
- * or null if not found.
83
- */
84
- getMulingstreamChunkById(roomId: string, chunkNumber: number): Promise<MulingstreamChunkData | null>;
85
- /**
86
- * Update STT fields for a given chunk.
87
- * If transcription or sttStatus is null, skip that field.
88
- */
89
- updateStt(roomId: string, chunkNumber: number, sttProvider: SttProvider, options: {
73
+ private getChunkId;
74
+ getMulingstreamChunkById(roomId: string, n: number): Promise<MulingstreamChunkData>;
75
+ private withChunk;
76
+ updateStt(roomId: string, n: number, provider: SttProvider, opt: {
90
77
  transcription?: string;
91
78
  sttStatus?: StepStatus;
92
79
  }): Promise<boolean>;
93
- updateSttObject(roomId: string, chunkNumber: number, newStt: Record<string, {
80
+ updateSttObject(roomId: string, n: number, newStt: Record<string, {
94
81
  transcription: string;
95
82
  status: StepStatus;
96
83
  }>): Promise<boolean>;
97
- discardStt(roomId: string, chunkNumber: number): Promise<boolean>;
98
- updateFinalTranscription(roomId: string, chunkNumber: number, options: {
84
+ discardStt(roomId: string, n: number): Promise<boolean>;
85
+ updateFinalTranscription(roomId: string, n: number, opt: {
99
86
  transcription?: string;
100
87
  sttStatus?: StepStatus;
101
- }): Promise<MulingstreamChunkData | null>;
102
- /**
103
- * Discards all post-STT steps for a given chunk:
104
- * sets all translation[].status & tts[].status to "DISCARDED".
105
- */
106
- discardPostStt(roomId: string, chunkNumber: number): Promise<boolean>;
107
- /**
108
- * Discards a specific language in both translation and tts for a chunk.
109
- */
110
- discardLanguage(roomId: string, chunkNumber: number, language: string): Promise<boolean>;
111
- discardLanguages(roomId: string, chunkNumber: number, options: {
88
+ }): Promise<MulingstreamChunkData>;
89
+ discardPostStt(roomId: string, n: number): Promise<boolean>;
90
+ discardLanguage(roomId: string, n: number, lang: string): Promise<boolean>;
91
+ discardLanguages(roomId: string, n: number, opt: {
112
92
  translation?: string[];
113
93
  tts?: string[];
114
- }): Promise<MulingstreamChunkData | null>;
115
- updateTranslation(roomId: string, chunkNumber: number, language: string, options: {
94
+ }): Promise<MulingstreamChunkData>;
95
+ updateTranslation(roomId: string, n: number, lang: string, opt: {
116
96
  translation?: string;
117
97
  status?: StepStatus;
118
- }): Promise<MulingstreamChunkData | null>;
119
- updateTranslationInBulk(roomId: string, chunkNumber: number, translations: Record<string, string>, status?: StepStatus): Promise<MulingstreamChunkData | null>;
120
- updateTts(roomId: string, chunkNumber: number, language: string, options: {
98
+ }): Promise<MulingstreamChunkData>;
99
+ updateTranslationInBulk(roomId: string, n: number, dict: Record<string, string>, status?: StepStatus): Promise<MulingstreamChunkData>;
100
+ updateTts(roomId: string, n: number, lang: string, opt: {
121
101
  ttsAudioPath?: string;
122
102
  status?: StepStatus;
123
103
  isEmitted?: boolean;
124
104
  totalCheck?: number;
125
- }): Promise<MulingstreamChunkData | null>;
126
- increaseTotalCheck(roomId: string, chunkNumber: number, language: string): Promise<MulingstreamChunkData | null>;
127
- areTranslationsProcessed(roomId: string, chunkNumber: number): Promise<boolean>;
128
- getAllReadyTts(roomId: string, language: string): Promise<MulingstreamChunkData[]>;
105
+ }): Promise<MulingstreamChunkData>;
106
+ increaseTotalCheck(roomId: string, n: number, lang: string): Promise<MulingstreamChunkData>;
107
+ areTranslationsProcessed(roomId: string, n: number): Promise<boolean>;
108
+ getAllReadyTts(roomId: string, lang: string): Promise<MulingstreamChunkData[]>;
129
109
  }
@@ -1,452 +1,346 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MulingstreamChunkManager = void 0;
4
- const EXPIRATION = 12 * 60 * 60; // 12 hours in seconds
5
- const ROOM_ARRAY_LENGTH = 50; // keep only the last 5 elements
4
+ const uuid_1 = require("uuid");
5
+ /* -------------------------------------------------------------------------- */
6
+ /* Constants */
7
+ /* -------------------------------------------------------------------------- */
8
+ const EXPIRATION = 12 * 60 * 60; // 12 h
9
+ const ROOM_ARRAY_LENGTH = 50; // keep last 50 chunks
10
+ /* -------------------------------------------------------------------------- */
11
+ /* Main class */
12
+ /* -------------------------------------------------------------------------- */
6
13
  class MulingstreamChunkManager {
7
14
  constructor(redisClient) {
8
15
  this.redisClient = redisClient;
9
16
  }
10
- getTimeout(audioStart, audioEnd) {
11
- const audioChunkLength = audioEnd - audioStart;
12
- if (audioChunkLength < 1000) {
13
- return 1500;
14
- }
15
- else if (audioChunkLength >= 1000 && audioChunkLength < 3000) {
16
- return 3000;
17
- }
18
- else if (audioChunkLength >= 3000 && audioChunkLength < 5000) {
19
- return 7000;
20
- }
21
- else if (audioChunkLength >= 5000 && audioChunkLength < 8000) {
22
- return 10000;
23
- }
24
- else if (audioChunkLength >= 8000 && audioChunkLength < 12000) {
25
- return 15000;
26
- }
27
- else if (audioChunkLength >= 12000 && audioChunkLength < 20000) {
28
- return 20000;
29
- }
30
- else {
31
- return 30000;
32
- }
17
+ /* ----------------------------- key helpers ----------------------------- */
18
+ roomZsetKey(roomId) {
19
+ return `room:${roomId}:chunks`;
20
+ }
21
+ chunkHashKey(chunkId) {
22
+ return `chunk:${chunkId}`;
23
+ }
24
+ generateChunkId(roomId, chunkNumber) {
25
+ return `[${roomId}]-[${chunkNumber}]-[${(0, uuid_1.v4)()}]`;
26
+ }
27
+ /* --------------------------- (de)serialization -------------------------- */
28
+ serialize(value) {
29
+ return JSON.stringify(value);
30
+ }
31
+ deserialize(v) {
32
+ return v ? JSON.parse(v) : undefined;
33
+ }
34
+ hashToChunk(h) {
35
+ return {
36
+ chunkId: h.chunkId,
37
+ roomId: h.roomId,
38
+ chunkNumber: parseInt(h.chunkNumber, 10),
39
+ language: h.language,
40
+ sttProviders: this.deserialize(h.sttProviders),
41
+ targetLanguages: this.deserialize(h.targetLanguages),
42
+ shortCodeTargetLanguages: this.deserialize(h.shortCodeTargetLanguages),
43
+ finalTranscription: h.finalTranscription,
44
+ sttStatus: h.sttStatus,
45
+ createdAt: parseInt(h.createdAt, 10),
46
+ audioChunk: this.deserialize(h.audioChunk),
47
+ stt: this.deserialize(h.stt),
48
+ translation: this.deserialize(h.translation),
49
+ tts: this.deserialize(h.tts)
50
+ };
51
+ }
52
+ /* ------------------------------ timeouts ------------------------------- */
53
+ getTimeout(start, end) {
54
+ // const len = end - start;
55
+ // if (len < 1_000) return 1_500;
56
+ // if (len < 3_000) return 3_000;
57
+ // if (len < 5_000) return 7_000;
58
+ // if (len < 8_000) return 10_000;
59
+ // if (len < 12_000) return 15_000;
60
+ // if (len < 20_000) return 20_000;
61
+ return 25000;
33
62
  }
34
- /**
35
- * Initializes a room in Redis as an empty JSON array.
36
- * If the key [roomId] already exists, we do NOT overwrite it.
37
- * Returns true if room was created, false if room already existed.
38
- */
63
+ /* ---------------------------------------------------------------------- */
64
+ /* Public API (same) */
65
+ /* ---------------------------------------------------------------------- */
66
+ /* Room helpers ---------------------------------------------------------- */
39
67
  async initRoom(roomId) {
40
- // Check if the key already exists (JSON.GET)
41
- const isRoomExisting = await this.redisClient.jsonGet(`[${roomId}]`, '.');
42
- if (isRoomExisting !== null) {
68
+ const exists = await this.redisClient.exists(this.roomZsetKey(roomId));
69
+ if (exists)
43
70
  return false;
44
- }
45
- // Create an empty array at the root
46
- await this.redisClient.jsonSet(`[${roomId}]`, '.', []);
47
- await this.redisClient.expire(`[${roomId}]`, EXPIRATION);
71
+ await this.redisClient.expire(this.roomZsetKey(roomId), EXPIRATION);
48
72
  return true;
49
73
  }
50
- /**
51
- * Returns all rooms. This is naive if you store many other keys in Redis,
52
- * because we search keys for pattern "[*]".
53
- * Adjust as needed if you have a separate naming prefix.
54
- */
55
74
  async getRooms() {
56
- // e.g., if we store everything as [foo], [bar], etc., we do:
57
- const rooms = await this.redisClient.keys('\\[*\\]');
58
- // This returns the raw keys, e.g. ['[myRoom]', '[anotherRoom]']
59
- // We might want to strip off the brackets:
60
- return rooms.map((k) => k.replace(/^\[|\]$/g, ''));
75
+ const keys = await this.redisClient.keys('room:*:chunks');
76
+ return keys.map((k) => k.replace(/^room:(.*):chunks$/, '$1'));
61
77
  }
62
- /**
63
- * Returns the entire array of chunks for the given roomId,
64
- * or null if the room doesn't exist in Redis.
65
- */
66
78
  async getMulingstreamChunksByRoom(roomId) {
67
- // JSON.GET [roomId] .
68
- const chunks = await this.redisClient.jsonGet(`[${roomId}]`, '.');
69
- if (chunks === null) {
79
+ const ids = await this.redisClient.zrange(this.roomZsetKey(roomId), 0, -1);
80
+ if (!ids.length)
70
81
  return null;
71
- }
72
- return chunks;
82
+ const pipe = this.redisClient.pipeline();
83
+ ids.forEach((cid) => pipe.hgetall(this.chunkHashKey(cid)));
84
+ const res = await pipe.exec();
85
+ return res.map(([, d]) => this.hashToChunk(d));
73
86
  }
74
- async getRoomById(roomId) {
87
+ getRoomById(roomId) {
75
88
  return this.getMulingstreamChunksByRoom(roomId);
76
89
  }
90
+ /* ------------------------- chunk insertion ---------------------------- */
77
91
  async addMulingstreamChunk(params) {
78
- var _a;
79
92
  const { roomId, chunkNumber, language, start, end, duration, isFirst, isLast, theme, sttProviders, targetLanguages, shortCodeTargetLanguages } = params;
80
- /* ---------- build chunk skeleton ---------- */
81
- const audioChunk = {
82
- start,
83
- end,
84
- duration,
85
- isFirst,
86
- isLast,
87
- theme,
88
- processingStart: Date.now()
89
- };
90
- const stt = {};
91
- for (const p of sttProviders) {
92
- stt[p] = { transcription: '', status: 'INIT' };
93
+ /* ---------- reset room on first chunk ---------- */
94
+ if (chunkNumber === 1) {
95
+ const old = await this.redisClient.zrange(this.roomZsetKey(roomId), 0, -1);
96
+ if (old.length) {
97
+ const p = this.redisClient.pipeline();
98
+ old.forEach((cid) => p.unlink(this.chunkHashKey(cid)));
99
+ p.del(this.roomZsetKey(roomId));
100
+ await p.exec();
101
+ }
102
+ }
103
+ else {
104
+ // remove older duplicate with same chunkNumber
105
+ const dup = await this.redisClient.zrangebyscore(this.roomZsetKey(roomId), chunkNumber, chunkNumber);
106
+ if (dup.length) {
107
+ const p = this.redisClient.pipeline();
108
+ dup.forEach((cid) => p.unlink(this.chunkHashKey(cid)));
109
+ p.zrem(this.roomZsetKey(roomId), ...dup);
110
+ await p.exec();
111
+ }
93
112
  }
113
+ /* ---------- build new chunk ---------- */
114
+ const chunkId = this.generateChunkId(roomId, chunkNumber);
115
+ const audioChunk = { start, end, duration, isFirst, isLast, theme, processingStart: Date.now() };
116
+ const stt = {};
117
+ sttProviders.forEach((p) => (stt[p] = { transcription: '', status: 'INIT' }));
94
118
  const translation = {};
95
119
  const tts = {};
96
- for (const lang of shortCodeTargetLanguages) {
97
- translation[lang] = { translation: '', status: 'INIT' };
98
- tts[lang] = { ttsAudioPath: '', status: 'INIT', isEmitted: false, totalCheck: 0 };
99
- }
100
- const newChunk = {
120
+ shortCodeTargetLanguages.forEach((l) => {
121
+ translation[l] = { translation: '', status: 'INIT' };
122
+ tts[l] = { ttsAudioPath: '', status: 'INIT', isEmitted: false, totalCheck: 0 };
123
+ });
124
+ const hash = {
125
+ chunkId,
101
126
  roomId,
102
- chunkNumber,
127
+ chunkNumber: String(chunkNumber),
103
128
  language,
104
- sttProviders,
105
- targetLanguages,
106
- shortCodeTargetLanguages,
129
+ sttProviders: this.serialize(sttProviders),
130
+ targetLanguages: this.serialize(targetLanguages),
131
+ shortCodeTargetLanguages: this.serialize(shortCodeTargetLanguages),
107
132
  finalTranscription: '',
108
133
  sttStatus: 'INIT',
109
- createdAt: Date.now(),
110
- audioChunk,
111
- stt,
112
- translation,
113
- tts
134
+ createdAt: String(Date.now()),
135
+ audioChunk: this.serialize(audioChunk),
136
+ stt: this.serialize(stt),
137
+ translation: this.serialize(translation),
138
+ tts: this.serialize(tts)
114
139
  };
115
- /* ---------- pull existing room array ---------- */
116
- let chunks = (_a = (await this.redisClient.jsonGet(`[${roomId}]`, '.'))) !== null && _a !== void 0 ? _a : [];
117
- /* ---------- reset the room if this is the first chunk ---------- */
118
- if (chunkNumber === 1) {
119
- chunks = []; // start fresh
140
+ /* ---------- write + trim (pipeline) ---------- */
141
+ const pipe = this.redisClient.pipeline();
142
+ pipe.hset(this.chunkHashKey(chunkId), hash);
143
+ pipe.expire(this.chunkHashKey(chunkId), EXPIRATION);
144
+ pipe.zadd(this.roomZsetKey(roomId), chunkNumber, chunkId);
145
+ pipe.expire(this.roomZsetKey(roomId), EXPIRATION);
146
+ pipe.zrange(this.roomZsetKey(roomId), 0, -ROOM_ARRAY_LENGTH - 1, 'WITHSCORES');
147
+ const execResults = await pipe.exec();
148
+ const oldPairs = execResults[4][1]; // [id1, score1, id2, score2, ...]
149
+ if (Array.isArray(oldPairs) && oldPairs.length) {
150
+ const oldIds = [];
151
+ for (let i = 0; i < oldPairs.length; i += 2)
152
+ oldIds.push(oldPairs[i]);
153
+ const trim = this.redisClient.pipeline();
154
+ trim.zrem(this.roomZsetKey(roomId), ...oldIds);
155
+ oldIds.forEach((cid) => trim.unlink(this.chunkHashKey(cid)));
156
+ await trim.exec();
120
157
  }
121
- /* ---------- insert / replace ---------- */
122
- const idx = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
123
- if (idx !== -1) {
124
- chunks[idx] = newChunk;
125
- }
126
- else {
127
- chunks.push(newChunk);
128
- if (chunks.length > ROOM_ARRAY_LENGTH)
129
- chunks.shift();
130
- }
131
- /* ---------- persist ---------- */
132
- await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
133
- await this.redisClient.expire(`[${roomId}]`, EXPIRATION);
134
158
  }
135
- /**
136
- * Given roomId and chunkNumber, return the single chunk from the array
137
- * or null if not found.
138
- */
139
- async getMulingstreamChunkById(roomId, chunkNumber) {
140
- // Retrieve the entire array
141
- const chunks = await this.getMulingstreamChunksByRoom(roomId);
142
- if (!chunks)
159
+ /* --------------------------- helper lookup ---------------------------- */
160
+ async getChunkId(roomId, n) {
161
+ const ids = await this.redisClient.zrangebyscore(this.roomZsetKey(roomId), n, n);
162
+ return ids.length ? ids[0] : null;
163
+ }
164
+ async getMulingstreamChunkById(roomId, n) {
165
+ const cid = await this.getChunkId(roomId, n);
166
+ if (!cid)
143
167
  return null;
144
- // Find by chunkNumber
145
- const chunk = chunks.find((c) => c.chunkNumber === chunkNumber);
146
- return chunk || null;
168
+ const raw = await this.redisClient.hgetall(this.chunkHashKey(cid));
169
+ return raw.chunkId ? this.hashToChunk(raw) : null;
147
170
  }
148
- /**
149
- * Update STT fields for a given chunk.
150
- * If transcription or sttStatus is null, skip that field.
151
- */
152
- async updateStt(roomId, chunkNumber, sttProvider, options) {
153
- const chunks = await this.getMulingstreamChunksByRoom(roomId);
154
- if (!chunks)
155
- return false;
156
- // Find the chunk
157
- const chunkIndex = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
158
- if (chunkIndex === -1)
159
- return false;
160
- const chunk = chunks[chunkIndex];
161
- // If this stt provider doesn't exist, ignore
162
- if (!chunk.stt[sttProvider])
163
- return false;
164
- // Update
165
- if (options.transcription != null) {
166
- chunk.stt[sttProvider].transcription = options.transcription;
167
- }
168
- if (options.sttStatus != null) {
169
- chunk.stt[sttProvider].status = options.sttStatus;
170
- }
171
- // Write back
172
- chunks[chunkIndex] = chunk;
173
- await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
174
- return true;
171
+ /* ------------------------------ Updaters ------------------------------ */
172
+ /* helper to fetch-mutate-save one chunk */
173
+ async withChunk(roomId, n, fn) {
174
+ const cid = await this.getChunkId(roomId, n);
175
+ if (!cid)
176
+ return null;
177
+ const key = this.chunkHashKey(cid);
178
+ const raw = await this.redisClient.hgetall(key);
179
+ if (!raw.chunkId)
180
+ return null;
181
+ const chunk = this.hashToChunk(raw);
182
+ const out = await fn(chunk);
183
+ const p = this.redisClient.pipeline();
184
+ p.hset(key, {
185
+ finalTranscription: chunk.finalTranscription,
186
+ sttStatus: chunk.sttStatus,
187
+ stt: this.serialize(chunk.stt),
188
+ translation: this.serialize(chunk.translation),
189
+ tts: this.serialize(chunk.tts)
190
+ });
191
+ p.expire(key, EXPIRATION);
192
+ await p.exec();
193
+ return out;
175
194
  }
176
- async updateSttObject(roomId, chunkNumber, newStt) {
177
- // Fetch all chunks in the room
178
- const chunks = await this.getMulingstreamChunksByRoom(roomId);
179
- if (!chunks) {
180
- return false;
181
- }
182
- // Locate the target chunk
183
- const idx = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
184
- if (idx === -1) {
185
- return false;
186
- }
187
- // Replace the stt object
188
- chunks[idx].stt = newStt;
189
- // Persist back to Redis
190
- await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
191
- return true;
195
+ /* ---- specific update methods (same signatures as before) ---- */
196
+ async updateStt(roomId, n, provider, opt) {
197
+ return ((await this.withChunk(roomId, n, (c) => {
198
+ if (!c.stt[provider])
199
+ return false;
200
+ if (opt.transcription !== undefined)
201
+ c.stt[provider].transcription = opt.transcription;
202
+ if (opt.sttStatus !== undefined)
203
+ c.stt[provider].status = opt.sttStatus;
204
+ return true;
205
+ })) === true);
192
206
  }
193
- async discardStt(roomId, chunkNumber) {
194
- // Fetch all chunks in this room
195
- const chunks = await this.getMulingstreamChunksByRoom(roomId);
196
- if (!chunks) {
197
- return false;
198
- }
199
- // Locate the desired chunk
200
- const idx = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
201
- if (idx === -1) {
202
- return false;
203
- }
204
- const chunk = chunks[idx];
205
- // Reset every STT provider entry
206
- for (const provider of Object.keys(chunk.stt)) {
207
- chunk.stt[provider].transcription = '';
208
- chunk.stt[provider].status = 'DISCARDED';
209
- }
210
- // Reset aggregate STT fields
211
- chunk.finalTranscription = '';
212
- chunk.sttStatus = 'DISCARDED';
213
- // Persist back to Redis
214
- chunks[idx] = chunk;
215
- await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
216
- return true;
207
+ async updateSttObject(roomId, n, newStt) {
208
+ return ((await this.withChunk(roomId, n, (c) => {
209
+ c.stt = newStt;
210
+ return true;
211
+ })) === true);
217
212
  }
218
- async updateFinalTranscription(roomId, chunkNumber, options) {
219
- // 1) fetch room’s chunks
220
- const chunks = await this.getMulingstreamChunksByRoom(roomId);
221
- if (!chunks)
222
- return null;
223
- // 2) locate the chunk
224
- const idx = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
225
- if (idx === -1)
226
- return null;
227
- // 3) apply updates only when the key is present
228
- if (options.transcription !== undefined) {
229
- chunks[idx].finalTranscription = options.transcription;
230
- }
231
- if (options.sttStatus !== undefined) {
232
- chunks[idx].sttStatus = options.sttStatus;
233
- }
234
- // 4) persist and return
235
- await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
236
- return chunks[idx];
213
+ async discardStt(roomId, n) {
214
+ return ((await this.withChunk(roomId, n, (c) => {
215
+ Object.keys(c.stt).forEach((p) => {
216
+ c.stt[p].transcription = '';
217
+ c.stt[p].status = 'DISCARDED';
218
+ });
219
+ c.finalTranscription = '';
220
+ c.sttStatus = 'DISCARDED';
221
+ return true;
222
+ })) === true);
237
223
  }
238
- /**
239
- * Discards all post-STT steps for a given chunk:
240
- * sets all translation[].status & tts[].status to "DISCARDED".
241
- */
242
- async discardPostStt(roomId, chunkNumber) {
243
- const chunks = await this.getMulingstreamChunksByRoom(roomId);
244
- if (!chunks)
245
- return false;
246
- // Find chunk
247
- const chunkIndex = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
248
- if (chunkIndex === -1)
249
- return false;
250
- // Discard translation & TTS statuses
251
- const chunk = chunks[chunkIndex];
252
- for (const lang of Object.keys(chunk.translation)) {
253
- chunk.translation[lang].status = 'DISCARDED';
254
- }
255
- for (const lang of Object.keys(chunk.tts)) {
256
- chunk.tts[lang].status = 'DISCARDED';
257
- }
258
- chunks[chunkIndex] = chunk;
259
- await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
260
- return true;
224
+ async updateFinalTranscription(roomId, n, opt) {
225
+ return this.withChunk(roomId, n, (c) => {
226
+ if (opt.transcription !== undefined)
227
+ c.finalTranscription = opt.transcription;
228
+ if (opt.sttStatus !== undefined)
229
+ c.sttStatus = opt.sttStatus;
230
+ return c;
231
+ });
261
232
  }
262
- /**
263
- * Discards a specific language in both translation and tts for a chunk.
264
- */
265
- async discardLanguage(roomId, chunkNumber, language) {
266
- const chunks = await this.getMulingstreamChunksByRoom(roomId);
267
- if (!chunks)
268
- return false;
269
- const chunkIndex = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
270
- if (chunkIndex === -1)
271
- return false;
272
- const chunk = chunks[chunkIndex];
273
- if (chunk.translation[language]) {
274
- chunk.translation[language].status = 'DISCARDED';
275
- }
276
- if (chunk.tts[language]) {
277
- chunk.tts[language].status = 'DISCARDED';
278
- }
279
- chunks[chunkIndex] = chunk;
280
- await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
281
- return true;
233
+ async discardPostStt(roomId, n) {
234
+ return ((await this.withChunk(roomId, n, (c) => {
235
+ Object.values(c.translation).forEach((t) => (t.status = 'DISCARDED'));
236
+ Object.values(c.tts).forEach((t) => (t.status = 'DISCARDED'));
237
+ return true;
238
+ })) === true);
282
239
  }
283
- async discardLanguages(roomId, chunkNumber, options) {
284
- // 1) fetch room’s chunks
285
- const chunks = await this.getMulingstreamChunksByRoom(roomId);
286
- if (!chunks)
287
- return null;
288
- // 2) locate the chunk
289
- const idx = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
290
- if (idx === -1)
291
- return null;
292
- const chunk = chunks[idx];
293
- // 3) discard translation languages still in INIT
294
- if (options.translation) {
295
- for (const lang of options.translation) {
296
- const entry = chunk.translation[lang];
297
- if (entry && entry.status === 'INIT') {
298
- entry.status = 'DISCARDED';
299
- }
300
- }
301
- }
302
- // 4) discard TTS languages still in INIT
303
- if (options.tts) {
304
- for (const lang of options.tts) {
305
- const entry = chunk.tts[lang];
306
- if (entry && entry.status === 'INIT') {
307
- entry.status = 'DISCARDED';
308
- }
309
- }
310
- }
311
- // 5) persist and return
312
- chunks[idx] = chunk;
313
- await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
314
- return chunk;
240
+ async discardLanguage(roomId, n, lang) {
241
+ return ((await this.withChunk(roomId, n, (c) => {
242
+ if (c.translation[lang])
243
+ c.translation[lang].status = 'DISCARDED';
244
+ if (c.tts[lang])
245
+ c.tts[lang].status = 'DISCARDED';
246
+ return true;
247
+ })) === true);
315
248
  }
316
- async updateTranslation(roomId, chunkNumber, language, options) {
317
- // 1) fetch the room’s chunks
318
- const chunks = await this.getMulingstreamChunksByRoom(roomId);
319
- if (!chunks)
320
- return null;
321
- // 2) locate the chunk
322
- const idx = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
323
- if (idx === -1)
324
- return null;
325
- const chunk = chunks[idx];
326
- // 3) ensure the requested language exists
327
- if (!chunk.translation[language])
328
- return null;
329
- // 4) apply updates only for provided keys
330
- if (options.translation !== undefined) {
331
- chunk.translation[language].translation = options.translation;
332
- }
333
- if (options.status !== undefined) {
334
- chunk.translation[language].status = options.status;
335
- }
336
- // 5) persist and return
337
- chunks[idx] = chunk;
338
- await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
339
- return chunk;
249
+ async discardLanguages(roomId, n, opt) {
250
+ return this.withChunk(roomId, n, (c) => {
251
+ var _a, _b;
252
+ (_a = opt.translation) === null || _a === void 0 ? void 0 : _a.forEach((l) => {
253
+ const e = c.translation[l];
254
+ if (e && e.status === 'INIT')
255
+ e.status = 'DISCARDED';
256
+ });
257
+ (_b = opt.tts) === null || _b === void 0 ? void 0 : _b.forEach((l) => {
258
+ const e = c.tts[l];
259
+ if (e && e.status === 'INIT')
260
+ e.status = 'DISCARDED';
261
+ });
262
+ return c;
263
+ });
340
264
  }
341
- async updateTranslationInBulk(roomId, chunkNumber, translations, status = 'READY') {
342
- // 1) fetch the room array
343
- const chunks = await this.getMulingstreamChunksByRoom(roomId);
344
- if (!chunks) {
345
- return null;
346
- }
347
- // 2) locate the target chunk
348
- const chunkIdx = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
349
- if (chunkIdx === -1) {
350
- return null;
351
- }
352
- const chunk = chunks[chunkIdx];
353
- // 3) apply updates only for the languages provided
354
- for (const [lang, translationText] of Object.entries(translations)) {
355
- if (!chunk.translation[lang]) {
356
- continue;
357
- }
358
- chunk.translation[lang].translation = translationText;
359
- chunk.translation[lang].status = status;
360
- }
361
- // 4) write the full room array back to Redis
362
- chunks[chunkIdx] = chunk;
363
- await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
364
- return chunk;
265
+ async updateTranslation(roomId, n, lang, opt) {
266
+ return this.withChunk(roomId, n, (c) => {
267
+ const e = c.translation[lang];
268
+ if (!e)
269
+ return null;
270
+ if (opt.translation !== undefined)
271
+ e.translation = opt.translation;
272
+ if (opt.status !== undefined)
273
+ e.status = opt.status;
274
+ return c;
275
+ });
365
276
  }
366
- async updateTts(roomId, chunkNumber, language, options) {
367
- // 1) fetch the room’s chunks
368
- const chunks = await this.getMulingstreamChunksByRoom(roomId);
369
- if (!chunks)
370
- return null;
371
- // 2) locate the chunk
372
- const idx = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
373
- if (idx === -1)
374
- return null;
375
- const chunk = chunks[idx];
376
- // 3) ensure the language exists in the TTS map
377
- if (!chunk.tts[language])
378
- return null;
379
- // 4) apply only the provided keys
380
- if (options.ttsAudioPath !== undefined) {
381
- chunk.tts[language].ttsAudioPath = options.ttsAudioPath;
382
- }
383
- if (options.status !== undefined) {
384
- chunk.tts[language].status = options.status;
385
- }
386
- if (options.isEmitted !== undefined) {
387
- chunk.tts[language].isEmitted = options.isEmitted;
388
- }
389
- if (options.totalCheck !== undefined) {
390
- chunk.tts[language].totalCheck = options.totalCheck;
391
- }
392
- // 5) persist and return the updated chunk
393
- chunks[idx] = chunk;
394
- await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
395
- return chunk;
277
+ async updateTranslationInBulk(roomId, n, dict, status = 'READY') {
278
+ return this.withChunk(roomId, n, (c) => {
279
+ Object.entries(dict).forEach(([l, txt]) => {
280
+ if (!c.translation[l])
281
+ return;
282
+ c.translation[l].translation = txt;
283
+ c.translation[l].status = status;
284
+ });
285
+ return c;
286
+ });
396
287
  }
397
- async increaseTotalCheck(roomId, chunkNumber, language) {
398
- const chunks = await this.getMulingstreamChunksByRoom(roomId);
399
- if (!chunks) {
400
- return null;
401
- }
402
- const idx = chunks.findIndex((c) => c.chunkNumber === chunkNumber);
403
- if (idx === -1) {
404
- return null;
405
- }
406
- const chunk = chunks[idx];
407
- if (!chunk.tts[language]) {
408
- return null;
409
- }
410
- chunk.tts[language].totalCheck += 1;
411
- chunks[idx] = chunk;
412
- await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
413
- return chunk;
288
+ async updateTts(roomId, n, lang, opt) {
289
+ return this.withChunk(roomId, n, (c) => {
290
+ const e = c.tts[lang];
291
+ if (!e)
292
+ return null;
293
+ if (opt.ttsAudioPath !== undefined)
294
+ e.ttsAudioPath = opt.ttsAudioPath;
295
+ if (opt.status !== undefined)
296
+ e.status = opt.status;
297
+ if (opt.isEmitted !== undefined)
298
+ e.isEmitted = opt.isEmitted;
299
+ if (opt.totalCheck !== undefined)
300
+ e.totalCheck = opt.totalCheck;
301
+ return c;
302
+ });
414
303
  }
415
- async areTranslationsProcessed(roomId, chunkNumber) {
416
- const chunk = await this.getMulingstreamChunkById(roomId, chunkNumber);
417
- if (!chunk) {
418
- return false;
419
- }
420
- return Object.values(chunk.translation).every((t) => t.status !== 'INIT');
304
+ async increaseTotalCheck(roomId, n, lang) {
305
+ return this.withChunk(roomId, n, (c) => {
306
+ if (!c.tts[lang])
307
+ return null;
308
+ c.tts[lang].totalCheck += 1;
309
+ return c;
310
+ });
421
311
  }
422
- async getAllReadyTts(roomId, language) {
423
- const chunks = await this.getMulingstreamChunksByRoom(roomId);
424
- if (!(chunks === null || chunks === void 0 ? void 0 : chunks.length))
312
+ async areTranslationsProcessed(roomId, n) {
313
+ const c = await this.getMulingstreamChunkById(roomId, n);
314
+ return !!c && Object.values(c.translation).every((t) => t.status !== 'INIT');
315
+ }
316
+ /* -------------------------- READY-TTS sequence ------------------------- */
317
+ async getAllReadyTts(roomId, lang) {
318
+ var _a;
319
+ const chunks = (_a = (await this.getMulingstreamChunksByRoom(roomId))) !== null && _a !== void 0 ? _a : [];
320
+ if (!chunks.length)
425
321
  return [];
426
- // chunks are received in consecutive order already, no need for sorting for now (maybe in the future we have more speaker in a room, so we may need sorting)
427
- // chunks.sort((a, b) => a.chunkNumber - b.chunkNumber);
428
- /* ---- isolate the tail where the language exists ---- */
429
- let lastLangIdx = -1;
430
- for (let i = chunks.length - 1; i >= 0; i--) {
431
- if (chunks[i].tts[language]) {
432
- lastLangIdx = i;
322
+ chunks.sort((a, b) => a.chunkNumber - b.chunkNumber);
323
+ /* isolate window where lang exists */
324
+ let lastIdx = -1;
325
+ for (let i = chunks.length - 1; i >= 0; i--)
326
+ if (chunks[i].tts[lang]) {
327
+ lastIdx = i;
433
328
  break;
434
329
  }
435
- }
436
- if (lastLangIdx === -1)
330
+ if (lastIdx === -1)
437
331
  return [];
438
- let firstLangIdx = lastLangIdx;
439
- for (let i = lastLangIdx - 1; i >= 0; i--) {
440
- if (!chunks[i].tts[language])
332
+ let firstIdx = lastIdx;
333
+ for (let i = lastIdx - 1; i >= 0; i--) {
334
+ if (!chunks[i].tts[lang])
441
335
  break;
442
- firstLangIdx = i;
336
+ firstIdx = i;
443
337
  }
444
- const window = chunks.slice(firstLangIdx, lastLangIdx + 1);
445
- /* ---- find last USED / DISCARDED in that window ---- */
338
+ const window = chunks.slice(firstIdx, lastIdx + 1);
339
+ /* find last USED/DISCARDED */
446
340
  let lastDone = -1;
447
341
  for (let i = window.length - 1; i >= 0; i--) {
448
- const st = window[i].tts[language].status;
449
- if (st === 'USED' || st === 'DISCARDED') {
342
+ const s = window[i].tts[lang].status;
343
+ if (s === 'USED' || s === 'DISCARDED') {
450
344
  lastDone = i;
451
345
  break;
452
346
  }
@@ -455,30 +349,28 @@ class MulingstreamChunkManager {
455
349
  let blocked = false;
456
350
  const now = Date.now();
457
351
  for (let i = lastDone + 1; i < window.length; i++) {
458
- const entry = window[i].tts[language];
459
- const audioChunk = window[i].audioChunk;
352
+ const entry = window[i].tts[lang];
353
+ const audio = window[i].audioChunk;
460
354
  if (entry.status === 'INIT') {
461
- const timeout = this.getTimeout(audioChunk.start, audioChunk.end);
462
- console.log('AUDIO LENGTH: ', audioChunk.end - audioChunk.start, ' TIMEOUT BASED ON AUDIO LENGTH: ', timeout, ' SINCE CREATED UNTIL NOW: ', now - window[i].createdAt);
463
- if (now - window[i].createdAt > timeout) {
355
+ if (now - window[i].createdAt > this.getTimeout(audio.start, audio.end))
464
356
  entry.status = 'DISCARDED';
465
- }
466
- else {
357
+ else
467
358
  blocked = true;
468
- }
469
359
  continue;
470
360
  }
471
361
  if (entry.status === 'READY') {
472
362
  if (blocked)
473
- break; // earlier INIT still pending
363
+ break;
474
364
  ready.push(window[i]);
475
365
  continue;
476
366
  }
477
- break; // unexpected USED / DISCARDED
367
+ break; // got USED/DISCARDED unexpectedly
478
368
  }
479
- // persist any DISCARDED changes we made
480
- if (ready.length === 0 || blocked) {
481
- await this.redisClient.jsonSet(`[${roomId}]`, '.', chunks);
369
+ /* persist DISCARDED changes (if any) */
370
+ if (!ready.length || blocked) {
371
+ const p = this.redisClient.pipeline();
372
+ window.forEach((c) => p.hset(this.chunkHashKey(c.chunkId), { tts: this.serialize(c.tts) }));
373
+ await p.exec();
482
374
  }
483
375
  return ready;
484
376
  }
@@ -1,29 +1,44 @@
1
+ import IORedis from 'ioredis';
1
2
  export interface RedisConfig {
2
3
  host: string;
3
4
  port: number;
4
5
  password?: string;
5
6
  db: number;
6
7
  }
8
+ /**
9
+ * Thin wrapper around ioredis that exposes exactly the helpers used by the two
10
+ * managers. All helpers are 1‑to‑1 pass‑through so there is no behavioural
11
+ * change – this merely centralises typing and keeps the rest of the codebase
12
+ * clean.
13
+ */
7
14
  export declare class RedisClient {
8
15
  private client;
9
16
  constructor(config: RedisConfig);
10
17
  set(key: string, value: string): Promise<void>;
11
18
  get(key: string): Promise<string | null>;
12
19
  del(key: string): Promise<number>;
20
+ exists(key: string): Promise<number>;
21
+ expire(key: string, seconds: number): Promise<number>;
13
22
  sadd(key: string, ...values: string[]): Promise<number>;
14
23
  srem(key: string, ...values: string[]): Promise<number>;
15
24
  smembers(key: string): Promise<string[]>;
16
- keys(pattern: string): Promise<string[]>;
17
25
  hset(key: string, data: Record<string, string>): Promise<number>;
18
26
  hgetall(key: string): Promise<Record<string, string>>;
19
- flushAll(): Promise<string>;
20
- flushDb(): Promise<string>;
21
- expire(key: string, seconds: number): Promise<number>;
27
+ zadd(key: string, score: number, member: string): Promise<number>;
28
+ zrange(key: string, start: number, stop: number): Promise<string[]>;
29
+ zremrangebyrank(key: string, start: number, stop: number): Promise<number>;
30
+ zrangebyscore(key: string, min: number | string, max: number | string): Promise<string[]>;
31
+ zrem(key: string, ...members: string[]): Promise<number>;
32
+ scan(cursor: number, pattern: string, count?: number): Promise<[string, string[]]>;
33
+ keys(pattern: string): Promise<string[]>;
34
+ pipeline(): ReturnType<IORedis['pipeline']>;
35
+ unlink(...keys: string[]): Promise<number>;
22
36
  jsonSet<T>(key: string, path: string, value: T): Promise<string>;
23
37
  jsonGet<T>(key: string, path: string): Promise<T | null>;
24
38
  jsonDel(key: string, path: string): Promise<number>;
25
39
  jsonArrAppend<T>(key: string, path: string, ...values: T[]): Promise<number>;
26
40
  jsonArrLen(key: string, path: string): Promise<number | null>;
27
41
  jsonArrPop(key: string, path: string, index?: number): Promise<any>;
28
- xadd(key: string, id: string, ...fieldValues: (string | number)[]): Promise<string>;
42
+ flushAll(): Promise<string>;
43
+ flushDb(): Promise<string>;
29
44
  }
@@ -5,6 +5,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.RedisClient = void 0;
7
7
  const ioredis_1 = __importDefault(require("ioredis"));
8
+ /**
9
+ * Thin wrapper around ioredis that exposes exactly the helpers used by the two
10
+ * managers. All helpers are 1‑to‑1 pass‑through so there is no behavioural
11
+ * change – this merely centralises typing and keeps the rest of the codebase
12
+ * clean.
13
+ */
8
14
  class RedisClient {
9
15
  constructor(config) {
10
16
  this.client = new ioredis_1.default({
@@ -17,7 +23,9 @@ class RedisClient {
17
23
  console.error('Redis error:', err);
18
24
  });
19
25
  }
20
- // existing set/get
26
+ /* ----------------------------------------------------------------------- */
27
+ /* Primitive string helpers */
28
+ /* ----------------------------------------------------------------------- */
21
29
  async set(key, value) {
22
30
  await this.client.set(key, value);
23
31
  }
@@ -27,75 +35,109 @@ class RedisClient {
27
35
  async del(key) {
28
36
  return this.client.del(key);
29
37
  }
30
- // Insert one or more values into a set
38
+ async exists(key) {
39
+ return this.client.exists(key);
40
+ }
41
+ async expire(key, seconds) {
42
+ return this.client.expire(key, seconds);
43
+ }
44
+ /* ----------------------------------------------------------------------- */
45
+ /* Set helpers */
46
+ /* ----------------------------------------------------------------------- */
31
47
  async sadd(key, ...values) {
32
48
  return this.client.sadd(key, values);
33
49
  }
34
50
  async srem(key, ...values) {
35
51
  return this.client.srem(key, values);
36
52
  }
37
- // Get all set members
38
53
  async smembers(key) {
39
54
  return this.client.smembers(key);
40
55
  }
41
- // Search for all keys matching a pattern
42
- // NOTE: If you have lots of keys in Redis, you should look into using SCAN instead of KEYS
43
- async keys(pattern) {
44
- return this.client.keys(pattern);
45
- }
46
- // add these methods for hash, sets, keys, etc.
56
+ /* ----------------------------------------------------------------------- */
57
+ /* Hash helpers */
58
+ /* ----------------------------------------------------------------------- */
47
59
  async hset(key, data) {
48
60
  return this.client.hset(key, data);
49
61
  }
50
62
  async hgetall(key) {
51
63
  return this.client.hgetall(key);
52
64
  }
53
- // flush methods
54
- async flushAll() {
55
- console.log('Flushing All Redis Data!');
56
- return this.client.flushall();
65
+ /* ----------------------------------------------------------------------- */
66
+ /* Sorted‑set helpers */
67
+ /* ----------------------------------------------------------------------- */
68
+ async zadd(key, score, member) {
69
+ return this.client.zadd(key, score, member);
57
70
  }
58
- async flushDb() {
59
- console.log('Flushing Redis Data from DB!');
60
- return this.client.flushdb();
71
+ async zrange(key, start, stop) {
72
+ return this.client.zrange(key, start, stop);
61
73
  }
62
- async expire(key, seconds) {
63
- return this.client.expire(key, seconds);
74
+ async zremrangebyrank(key, start, stop) {
75
+ return this.client.zremrangebyrank(key, start, stop);
64
76
  }
77
+ async zrangebyscore(key, min, max) {
78
+ return this.client.zrangebyscore(key, min, max);
79
+ }
80
+ async zrem(key, ...members) {
81
+ return this.client.zrem(key, members);
82
+ }
83
+ /* ----------------------------------------------------------------------- */
84
+ /* Scan (pattern search) */
85
+ /* ----------------------------------------------------------------------- */
86
+ async scan(cursor, pattern, count = 1000) {
87
+ return this.client.scan(cursor, 'MATCH', pattern, 'COUNT', count);
88
+ }
89
+ async keys(pattern) {
90
+ return this.client.keys(pattern);
91
+ }
92
+ /* ----------------------------------------------------------------------- */
93
+ /* Pipeline */
94
+ /* ----------------------------------------------------------------------- */
95
+ pipeline() {
96
+ return this.client.pipeline();
97
+ }
98
+ /* ----------------------------------------------------------------------- */
99
+ /* Misc */
100
+ /* ----------------------------------------------------------------------- */
101
+ async unlink(...keys) {
102
+ // unlink exists on Redis ≥4 and is supported by ioredis but not typed in
103
+ // older versions – we cast to any.
104
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
105
+ // @ts-ignore – runtime command exists even if typings lag behind.
106
+ return this.client.unlink(...keys);
107
+ }
108
+ /* JSON commands (RedisJSON module) -------------------------------------- */
65
109
  async jsonSet(key, path, value) {
66
110
  const result = await this.client.call('JSON.SET', key, path, JSON.stringify(value));
67
111
  return result;
68
112
  }
69
113
  async jsonGet(key, path) {
70
- const result = (await this.client.call('JSON.GET', key, path));
71
- if (!result) {
72
- return null;
73
- }
74
- return JSON.parse(result);
114
+ const raw = (await this.client.call('JSON.GET', key, path));
115
+ return raw ? JSON.parse(raw) : null;
75
116
  }
76
117
  async jsonDel(key, path) {
77
- const result = await this.client.call('JSON.DEL', key, path);
78
- return Number(result);
118
+ return Number(await this.client.call('JSON.DEL', key, path));
79
119
  }
80
120
  async jsonArrAppend(key, path, ...values) {
81
- const stringifiedValues = values.map((v) => JSON.stringify(v));
82
- const result = await this.client.call('JSON.ARRAPPEND', key, path, ...stringifiedValues);
83
- return Number(result);
121
+ const stringified = values.map((v) => JSON.stringify(v));
122
+ return Number(await this.client.call('JSON.ARRAPPEND', key, path, ...stringified));
84
123
  }
85
124
  async jsonArrLen(key, path) {
86
- const result = await this.client.call('JSON.ARRLEN', key, path);
87
- return result === null ? null : Number(result);
125
+ const len = await this.client.call('JSON.ARRLEN', key, path);
126
+ return len === null ? null : Number(len);
88
127
  }
89
128
  async jsonArrPop(key, path, index) {
90
- if (index !== undefined) {
91
- return this.client.call('JSON.ARRPOP', key, path, index);
92
- }
93
- return this.client.call('JSON.ARRPOP', key, path);
94
- }
95
- async xadd(key, id, ...fieldValues) {
96
- // IORedis already supports XADD natively
97
- // key id ('*' for autogenerated) field1 value1 field2 value2 ...
98
- return this.client.xadd(key, id, ...fieldValues);
129
+ return index !== undefined ? this.client.call('JSON.ARRPOP', key, path, index) : this.client.call('JSON.ARRPOP', key, path);
130
+ }
131
+ /* ----------------------------------------------------------------------- */
132
+ /* Dangerous! flush helpers */
133
+ /* ----------------------------------------------------------------------- */
134
+ async flushAll() {
135
+ console.warn('Flushing all Redis data!');
136
+ return this.client.flushall();
137
+ }
138
+ async flushDb() {
139
+ console.warn('Flushing current Redis DB!');
140
+ return this.client.flushdb();
99
141
  }
100
142
  }
101
143
  exports.RedisClient = RedisClient;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mulingai-npm/redis",
3
- "version": "3.0.0",
3
+ "version": "3.0.2",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "repository": {