@mastra/redis 1.0.1 → 1.0.2-alpha.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.
package/dist/index.cjs ADDED
@@ -0,0 +1,1804 @@
1
+ 'use strict';
2
+
3
+ var agent = require('@mastra/core/agent');
4
+ var error = require('@mastra/core/error');
5
+ var storage = require('@mastra/core/storage');
6
+ var crypto2 = require('crypto');
7
+ var evals = require('@mastra/core/evals');
8
+ var redis = require('redis');
9
+
10
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
+
12
+ var crypto2__default = /*#__PURE__*/_interopDefault(crypto2);
13
+
14
+ // src/storage/domains/memory/index.ts
15
+ function getKey(tableName, keys) {
16
+ const keyParts = Object.entries(keys).filter(([_, value]) => value !== void 0).map(([key, value]) => {
17
+ if (value && typeof value === "object") {
18
+ return `${key}:${JSON.stringify(value)}`;
19
+ }
20
+ return `${key}:${value}`;
21
+ });
22
+ return `${tableName}:${keyParts.join(":")}`;
23
+ }
24
+ function processRecord(tableName, record) {
25
+ let key;
26
+ if (tableName === storage.TABLE_MESSAGES) {
27
+ key = getKey(tableName, { threadId: record.threadId, id: record.id });
28
+ } else if (tableName === storage.TABLE_WORKFLOW_SNAPSHOT) {
29
+ key = getKey(tableName, {
30
+ namespace: record.namespace || "workflows",
31
+ workflow_name: record.workflow_name,
32
+ run_id: record.run_id,
33
+ ...record.resourceId ? { resourceId: record.resourceId } : {}
34
+ });
35
+ } else {
36
+ key = getKey(tableName, { id: record.id });
37
+ }
38
+ const processedRecord = {
39
+ ...record,
40
+ createdAt: storage.serializeDate(record.createdAt),
41
+ updatedAt: storage.serializeDate(record.updatedAt)
42
+ };
43
+ return { key, processedRecord };
44
+ }
45
+
46
+ // src/storage/db/index.ts
47
+ var RedisDB = class {
48
+ client;
49
+ constructor({ client }) {
50
+ this.client = client;
51
+ }
52
+ getClient() {
53
+ return this.client;
54
+ }
55
+ async insert({ tableName, record }) {
56
+ const { key, processedRecord } = processRecord(tableName, record);
57
+ try {
58
+ await this.client.set(key, JSON.stringify(processedRecord));
59
+ } catch (error$1) {
60
+ throw new error.MastraError(
61
+ {
62
+ id: storage.createStorageErrorId("REDIS", "INSERT", "FAILED"),
63
+ domain: error.ErrorDomain.STORAGE,
64
+ category: error.ErrorCategory.THIRD_PARTY,
65
+ details: {
66
+ tableName
67
+ }
68
+ },
69
+ error$1
70
+ );
71
+ }
72
+ }
73
+ async get({ tableName, keys }) {
74
+ const key = getKey(tableName, keys);
75
+ try {
76
+ const data = await this.client.get(key);
77
+ if (!data) {
78
+ return null;
79
+ }
80
+ return JSON.parse(data);
81
+ } catch (error$1) {
82
+ throw new error.MastraError(
83
+ {
84
+ id: storage.createStorageErrorId("REDIS", "LOAD", "FAILED"),
85
+ domain: error.ErrorDomain.STORAGE,
86
+ category: error.ErrorCategory.THIRD_PARTY,
87
+ details: {
88
+ tableName
89
+ }
90
+ },
91
+ error$1
92
+ );
93
+ }
94
+ }
95
+ async scanAndDelete(pattern, batchSize = 1e4) {
96
+ let cursor = "0";
97
+ let totalDeleted = 0;
98
+ do {
99
+ const result = await this.client.scan(cursor, { MATCH: pattern, COUNT: batchSize });
100
+ if (result.keys.length > 0) {
101
+ await this.client.del(result.keys);
102
+ totalDeleted += result.keys.length;
103
+ }
104
+ cursor = result.cursor;
105
+ } while (cursor !== "0");
106
+ return totalDeleted;
107
+ }
108
+ async scanKeys(pattern, batchSize = 1e4) {
109
+ let cursor = "0";
110
+ const keys = [];
111
+ do {
112
+ const result = await this.client.scan(cursor, { MATCH: pattern, COUNT: batchSize });
113
+ keys.push(...result.keys);
114
+ cursor = result.cursor;
115
+ } while (cursor !== "0");
116
+ return keys;
117
+ }
118
+ async deleteData({ tableName }) {
119
+ const pattern = `${tableName}:*`;
120
+ try {
121
+ await this.scanAndDelete(pattern);
122
+ } catch (error$1) {
123
+ throw new error.MastraError(
124
+ {
125
+ id: storage.createStorageErrorId("REDIS", "CLEAR_TABLE", "FAILED"),
126
+ domain: error.ErrorDomain.STORAGE,
127
+ category: error.ErrorCategory.THIRD_PARTY,
128
+ details: {
129
+ tableName
130
+ }
131
+ },
132
+ error$1
133
+ );
134
+ }
135
+ }
136
+ };
137
+
138
+ // src/storage/domains/memory/index.ts
139
+ var StoreMemoryRedis = class extends storage.MemoryStorage {
140
+ client;
141
+ db;
142
+ constructor(config) {
143
+ super();
144
+ this.client = config.client;
145
+ this.db = new RedisDB({ client: config.client });
146
+ }
147
+ async dangerouslyClearAll() {
148
+ await this.db.deleteData({ tableName: storage.TABLE_THREADS });
149
+ await this.db.deleteData({ tableName: storage.TABLE_MESSAGES });
150
+ await this.db.deleteData({ tableName: storage.TABLE_RESOURCES });
151
+ await this.db.scanAndDelete("msg-idx:*");
152
+ await this.db.scanAndDelete("thread:*:messages");
153
+ }
154
+ async getThreadById({ threadId }) {
155
+ try {
156
+ const thread = await this.db.get({
157
+ tableName: storage.TABLE_THREADS,
158
+ keys: { id: threadId }
159
+ });
160
+ if (!thread) {
161
+ return null;
162
+ }
163
+ return {
164
+ ...thread,
165
+ createdAt: storage.ensureDate(thread.createdAt),
166
+ updatedAt: storage.ensureDate(thread.updatedAt),
167
+ metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata
168
+ };
169
+ } catch (error$1) {
170
+ throw new error.MastraError(
171
+ {
172
+ id: storage.createStorageErrorId("REDIS", "GET_THREAD_BY_ID", "FAILED"),
173
+ domain: error.ErrorDomain.STORAGE,
174
+ category: error.ErrorCategory.THIRD_PARTY,
175
+ details: {
176
+ threadId
177
+ }
178
+ },
179
+ error$1
180
+ );
181
+ }
182
+ }
183
+ async listThreadsByResourceId(args) {
184
+ return this.listThreads(args);
185
+ }
186
+ async listThreads(args) {
187
+ const { page = 0, perPage: perPageInput, orderBy, filter } = args;
188
+ const { field, direction } = this.parseOrderBy(orderBy);
189
+ try {
190
+ this.validatePaginationInput(page, perPageInput ?? 100);
191
+ } catch (error$1) {
192
+ throw new error.MastraError(
193
+ {
194
+ id: storage.createStorageErrorId("REDIS", "LIST_THREADS", "INVALID_PAGE"),
195
+ domain: error.ErrorDomain.STORAGE,
196
+ category: error.ErrorCategory.USER,
197
+ details: { page, ...perPageInput !== void 0 && { perPage: perPageInput } }
198
+ },
199
+ error$1 instanceof Error ? error$1 : new Error("Invalid pagination parameters")
200
+ );
201
+ }
202
+ const perPage = storage.normalizePerPage(perPageInput, 100);
203
+ try {
204
+ this.validateMetadataKeys(filter?.metadata);
205
+ } catch (error$1) {
206
+ throw new error.MastraError(
207
+ {
208
+ id: storage.createStorageErrorId("REDIS", "LIST_THREADS", "INVALID_METADATA_KEY"),
209
+ domain: error.ErrorDomain.STORAGE,
210
+ category: error.ErrorCategory.USER,
211
+ details: { metadataKeys: filter?.metadata ? Object.keys(filter.metadata).join(", ") : "" }
212
+ },
213
+ error$1 instanceof Error ? error$1 : new Error("Invalid metadata key")
214
+ );
215
+ }
216
+ const { offset, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage);
217
+ try {
218
+ let allThreads = [];
219
+ const pattern = `${storage.TABLE_THREADS}:*`;
220
+ const keys = await this.db.scanKeys(pattern);
221
+ if (keys.length === 0) {
222
+ return {
223
+ threads: [],
224
+ total: 0,
225
+ page,
226
+ perPage: perPageForResponse,
227
+ hasMore: false
228
+ };
229
+ }
230
+ const results = await this.client.mGet(keys);
231
+ for (let i = 0; i < results.length; i++) {
232
+ const data = results[i];
233
+ if (!data) {
234
+ continue;
235
+ }
236
+ const thread = JSON.parse(data);
237
+ if (filter?.resourceId && thread.resourceId !== filter.resourceId) {
238
+ continue;
239
+ }
240
+ if (filter?.metadata && Object.keys(filter.metadata).length > 0) {
241
+ const threadMetadata = typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata;
242
+ const matches = Object.entries(filter.metadata).every(
243
+ ([key, value]) => storage.jsonValueEquals(threadMetadata?.[key], value)
244
+ );
245
+ if (!matches) {
246
+ continue;
247
+ }
248
+ }
249
+ allThreads.push({
250
+ ...thread,
251
+ createdAt: storage.ensureDate(thread.createdAt),
252
+ updatedAt: storage.ensureDate(thread.updatedAt),
253
+ metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata
254
+ });
255
+ }
256
+ const sortedThreads = this.sortThreads(allThreads, field, direction);
257
+ const total = sortedThreads.length;
258
+ const end = perPageInput === false ? total : offset + perPage;
259
+ const paginatedThreads = sortedThreads.slice(offset, end);
260
+ const hasMore = perPageInput === false ? false : end < total;
261
+ return {
262
+ threads: paginatedThreads,
263
+ total,
264
+ page,
265
+ perPage: perPageForResponse,
266
+ hasMore
267
+ };
268
+ } catch (error$1) {
269
+ const mastraError = new error.MastraError(
270
+ {
271
+ id: storage.createStorageErrorId("REDIS", "LIST_THREADS", "FAILED"),
272
+ domain: error.ErrorDomain.STORAGE,
273
+ category: error.ErrorCategory.THIRD_PARTY,
274
+ details: {
275
+ ...filter?.resourceId && { resourceId: filter.resourceId },
276
+ hasMetadataFilter: !!filter?.metadata,
277
+ page,
278
+ perPage
279
+ }
280
+ },
281
+ error$1
282
+ );
283
+ this.logger.trackException(mastraError);
284
+ this.logger.error(mastraError.toString());
285
+ return {
286
+ threads: [],
287
+ total: 0,
288
+ page,
289
+ perPage: perPageForResponse,
290
+ hasMore: false
291
+ };
292
+ }
293
+ }
294
+ async saveThread({ thread }) {
295
+ try {
296
+ await this.db.insert({
297
+ tableName: storage.TABLE_THREADS,
298
+ record: thread
299
+ });
300
+ return thread;
301
+ } catch (error$1) {
302
+ const mastraError = new error.MastraError(
303
+ {
304
+ id: storage.createStorageErrorId("REDIS", "SAVE_THREAD", "FAILED"),
305
+ domain: error.ErrorDomain.STORAGE,
306
+ category: error.ErrorCategory.THIRD_PARTY,
307
+ details: {
308
+ threadId: thread.id
309
+ }
310
+ },
311
+ error$1
312
+ );
313
+ this.logger.trackException(mastraError);
314
+ this.logger.error(mastraError.toString());
315
+ throw mastraError;
316
+ }
317
+ }
318
+ async updateThread({
319
+ id,
320
+ title,
321
+ metadata
322
+ }) {
323
+ const thread = await this.getThreadById({ threadId: id });
324
+ if (!thread) {
325
+ throw new error.MastraError({
326
+ id: storage.createStorageErrorId("REDIS", "UPDATE_THREAD", "FAILED"),
327
+ domain: error.ErrorDomain.STORAGE,
328
+ category: error.ErrorCategory.USER,
329
+ text: `Thread ${id} not found`,
330
+ details: {
331
+ threadId: id
332
+ }
333
+ });
334
+ }
335
+ const updatedThread = {
336
+ ...thread,
337
+ title,
338
+ metadata: {
339
+ ...thread.metadata,
340
+ ...metadata
341
+ },
342
+ updatedAt: /* @__PURE__ */ new Date()
343
+ };
344
+ try {
345
+ await this.saveThread({ thread: updatedThread });
346
+ return updatedThread;
347
+ } catch (error$1) {
348
+ throw new error.MastraError(
349
+ {
350
+ id: storage.createStorageErrorId("REDIS", "UPDATE_THREAD", "FAILED"),
351
+ domain: error.ErrorDomain.STORAGE,
352
+ category: error.ErrorCategory.THIRD_PARTY,
353
+ details: {
354
+ threadId: id
355
+ }
356
+ },
357
+ error$1
358
+ );
359
+ }
360
+ }
361
+ async deleteThread({ threadId }) {
362
+ const threadKey = getKey(storage.TABLE_THREADS, { id: threadId });
363
+ const threadMessagesKey = getThreadMessagesKey(threadId);
364
+ try {
365
+ const messageIds = await this.client.zRange(threadMessagesKey, 0, -1);
366
+ const multi = this.client.multi();
367
+ multi.del(threadKey);
368
+ multi.del(threadMessagesKey);
369
+ for (const messageId of messageIds) {
370
+ const messageKey = getMessageKey(threadId, messageId);
371
+ multi.del(messageKey);
372
+ multi.del(getMessageIndexKey(messageId));
373
+ }
374
+ await multi.exec();
375
+ await this.db.scanAndDelete(getMessageKey(threadId, "*"));
376
+ } catch (error$1) {
377
+ throw new error.MastraError(
378
+ {
379
+ id: storage.createStorageErrorId("REDIS", "DELETE_THREAD", "FAILED"),
380
+ domain: error.ErrorDomain.STORAGE,
381
+ category: error.ErrorCategory.THIRD_PARTY,
382
+ details: {
383
+ threadId
384
+ }
385
+ },
386
+ error$1
387
+ );
388
+ }
389
+ }
390
+ async saveMessages(args) {
391
+ const { messages } = args;
392
+ if (messages.length === 0) {
393
+ return { messages: [] };
394
+ }
395
+ const threadId = messages[0]?.threadId;
396
+ try {
397
+ if (!threadId) {
398
+ throw new Error("Thread ID is required");
399
+ }
400
+ const thread = await this.getThreadById({ threadId });
401
+ if (!thread) {
402
+ throw new Error(`Thread ${threadId} not found`);
403
+ }
404
+ } catch (error$1) {
405
+ throw new error.MastraError(
406
+ {
407
+ id: storage.createStorageErrorId("REDIS", "SAVE_MESSAGES", "INVALID_ARGS"),
408
+ domain: error.ErrorDomain.STORAGE,
409
+ category: error.ErrorCategory.USER
410
+ },
411
+ error$1
412
+ );
413
+ }
414
+ const messagesWithIndex = messages.map((message, index) => {
415
+ if (!message.threadId) {
416
+ throw new Error(
417
+ `Expected to find a threadId for message, but couldn't find one. An unexpected error has occurred.`
418
+ );
419
+ }
420
+ if (!message.resourceId) {
421
+ throw new Error(
422
+ `Expected to find a resourceId for message, but couldn't find one. An unexpected error has occurred.`
423
+ );
424
+ }
425
+ return {
426
+ ...message,
427
+ _index: index
428
+ };
429
+ });
430
+ const threadKey = getKey(storage.TABLE_THREADS, { id: threadId });
431
+ const existingThreadData = await this.client.get(threadKey);
432
+ const existingThread = existingThreadData ? JSON.parse(existingThreadData) : null;
433
+ try {
434
+ const batchSize = 1e3;
435
+ const existingThreadIds = await this.client.mGet(
436
+ messagesWithIndex.map((message) => getMessageIndexKey(message.id))
437
+ );
438
+ for (let i = 0; i < messagesWithIndex.length; i += batchSize) {
439
+ const batch = messagesWithIndex.slice(i, i + batchSize);
440
+ const batchExistingThreadIds = existingThreadIds.slice(i, i + batch.length);
441
+ const multi = this.client.multi();
442
+ for (const [batchIndex, message] of batch.entries()) {
443
+ const key = getMessageKey(message.threadId, message.id);
444
+ const score = getMessageScore(message);
445
+ const existingThreadId = batchExistingThreadIds[batchIndex];
446
+ if (existingThreadId && existingThreadId !== message.threadId) {
447
+ const existingMessageKey = getMessageKey(existingThreadId, message.id);
448
+ multi.del(existingMessageKey);
449
+ multi.zRem(getThreadMessagesKey(existingThreadId), message.id);
450
+ }
451
+ multi.set(key, JSON.stringify(message));
452
+ multi.set(getMessageIndexKey(message.id), message.threadId);
453
+ multi.zAdd(getThreadMessagesKey(message.threadId), { score, value: message.id });
454
+ }
455
+ if (i === 0 && existingThread) {
456
+ const updatedThread = {
457
+ ...existingThread,
458
+ updatedAt: /* @__PURE__ */ new Date()
459
+ };
460
+ multi.set(threadKey, JSON.stringify(processRecord(storage.TABLE_THREADS, updatedThread).processedRecord));
461
+ }
462
+ await multi.exec();
463
+ }
464
+ const list = new agent.MessageList().add(messages, "memory");
465
+ return { messages: list.get.all.db() };
466
+ } catch (error$1) {
467
+ throw new error.MastraError(
468
+ {
469
+ id: storage.createStorageErrorId("REDIS", "SAVE_MESSAGES", "FAILED"),
470
+ domain: error.ErrorDomain.STORAGE,
471
+ category: error.ErrorCategory.THIRD_PARTY,
472
+ details: {
473
+ threadId
474
+ }
475
+ },
476
+ error$1
477
+ );
478
+ }
479
+ }
480
+ async getThreadIdForMessage(messageId) {
481
+ const indexedThreadId = await this.client.get(getMessageIndexKey(messageId));
482
+ if (indexedThreadId) {
483
+ return indexedThreadId;
484
+ }
485
+ const keys = await this.db.scanKeys(getMessageKey("*", messageId));
486
+ if (keys.length === 0) {
487
+ return null;
488
+ }
489
+ const messageData = await this.client.get(keys[0]);
490
+ if (!messageData) {
491
+ return null;
492
+ }
493
+ const message = JSON.parse(messageData);
494
+ if (message.threadId) {
495
+ await this.client.set(getMessageIndexKey(messageId), message.threadId);
496
+ }
497
+ return message.threadId || null;
498
+ }
499
+ async getIncludedMessages(include) {
500
+ if (!include?.length) {
501
+ return [];
502
+ }
503
+ const messageIds = /* @__PURE__ */ new Set();
504
+ const messageIdToThreadIds = {};
505
+ for (const item of include) {
506
+ const itemThreadId = await this.getThreadIdForMessage(item.id);
507
+ if (!itemThreadId) {
508
+ continue;
509
+ }
510
+ messageIds.add(item.id);
511
+ messageIdToThreadIds[item.id] = itemThreadId;
512
+ const itemThreadMessagesKey = getThreadMessagesKey(itemThreadId);
513
+ const rank = await this.client.zRank(itemThreadMessagesKey, item.id);
514
+ if (rank === null) {
515
+ continue;
516
+ }
517
+ if (item.withPreviousMessages) {
518
+ const start = Math.max(0, rank - item.withPreviousMessages);
519
+ const prevIds = rank === 0 ? [] : await this.client.zRange(itemThreadMessagesKey, start, rank - 1);
520
+ prevIds.forEach((id) => {
521
+ messageIds.add(id);
522
+ messageIdToThreadIds[id] = itemThreadId;
523
+ });
524
+ }
525
+ if (item.withNextMessages) {
526
+ const nextIds = await this.client.zRange(itemThreadMessagesKey, rank + 1, rank + item.withNextMessages);
527
+ nextIds.forEach((id) => {
528
+ messageIds.add(id);
529
+ messageIdToThreadIds[id] = itemThreadId;
530
+ });
531
+ }
532
+ }
533
+ if (messageIds.size === 0) {
534
+ return [];
535
+ }
536
+ const keysToFetch = Array.from(messageIds).map((id) => getMessageKey(messageIdToThreadIds[id], id));
537
+ const results = await this.client.mGet(keysToFetch);
538
+ return results.filter((data) => data !== null).map((data) => JSON.parse(data));
539
+ }
540
+ parseStoredMessage(storedMessage) {
541
+ const defaultMessageContent = { format: 2, parts: [{ type: "text", text: "" }] };
542
+ const { _index, ...rest } = storedMessage;
543
+ return {
544
+ ...rest,
545
+ createdAt: new Date(rest.createdAt),
546
+ content: rest.content || defaultMessageContent
547
+ };
548
+ }
549
+ async listMessagesById({ messageIds }) {
550
+ if (messageIds.length === 0) {
551
+ return { messages: [] };
552
+ }
553
+ try {
554
+ const rawMessages = [];
555
+ const indexKeys = messageIds.map((id) => getMessageIndexKey(id));
556
+ const indexResults = await this.client.mGet(indexKeys);
557
+ const indexedIds = [];
558
+ const unindexedIds = [];
559
+ messageIds.forEach((id, i) => {
560
+ const threadId = indexResults[i];
561
+ if (threadId) {
562
+ indexedIds.push({ messageId: id, threadId });
563
+ return;
564
+ }
565
+ unindexedIds.push(id);
566
+ });
567
+ if (indexedIds.length > 0) {
568
+ const messageKeys = indexedIds.map(({ messageId, threadId }) => getMessageKey(threadId, messageId));
569
+ const messageResults = await this.client.mGet(messageKeys);
570
+ for (const data of messageResults) {
571
+ if (data) {
572
+ rawMessages.push(JSON.parse(data));
573
+ }
574
+ }
575
+ }
576
+ if (unindexedIds.length > 0) {
577
+ const threadKeys = await this.db.scanKeys("thread:*:messages");
578
+ const result = await Promise.all(
579
+ threadKeys.map(async (threadKey) => {
580
+ const threadId = threadKey.split(":")[1];
581
+ if (!threadId) {
582
+ throw new Error(`Failed to parse thread ID from thread key "${threadKey}"`);
583
+ }
584
+ const msgKeys = unindexedIds.map((id) => getMessageKey(threadId, id));
585
+ return this.client.mGet(msgKeys);
586
+ })
587
+ );
588
+ const foundMessages = result.flat(1).filter((data) => !!data).map((data) => JSON.parse(data));
589
+ rawMessages.push(...foundMessages);
590
+ if (foundMessages.length > 0) {
591
+ const multi = this.client.multi();
592
+ foundMessages.forEach((msg) => {
593
+ if (msg.threadId) {
594
+ multi.set(getMessageIndexKey(msg.id), msg.threadId);
595
+ }
596
+ });
597
+ await multi.exec();
598
+ }
599
+ }
600
+ const list = new agent.MessageList().add(rawMessages.map(this.parseStoredMessage), "memory");
601
+ return { messages: list.get.all.db() };
602
+ } catch (error$1) {
603
+ throw new error.MastraError(
604
+ {
605
+ id: storage.createStorageErrorId("REDIS", "LIST_MESSAGES_BY_ID", "FAILED"),
606
+ domain: error.ErrorDomain.STORAGE,
607
+ category: error.ErrorCategory.THIRD_PARTY,
608
+ details: {
609
+ messageIds: JSON.stringify(messageIds)
610
+ }
611
+ },
612
+ error$1
613
+ );
614
+ }
615
+ }
616
+ async listMessages(args) {
617
+ const { threadId, resourceId, include, filter, perPage: perPageInput, page = 0, orderBy } = args;
618
+ const threadIds = Array.isArray(threadId) ? threadId : [threadId];
619
+ const threadIdsSet = new Set(threadIds);
620
+ if (threadIds.length === 0 || threadIds.some((id) => !id.trim())) {
621
+ throw new error.MastraError(
622
+ {
623
+ id: storage.createStorageErrorId("REDIS", "LIST_MESSAGES", "INVALID_THREAD_ID"),
624
+ domain: error.ErrorDomain.STORAGE,
625
+ category: error.ErrorCategory.USER,
626
+ details: { threadId: Array.isArray(threadId) ? threadId.join(",") : threadId }
627
+ },
628
+ new Error("threadId must be a non-empty string or array of non-empty strings")
629
+ );
630
+ }
631
+ const perPage = storage.normalizePerPage(perPageInput, 40);
632
+ const { offset, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage);
633
+ try {
634
+ if (page < 0) {
635
+ throw new error.MastraError(
636
+ {
637
+ id: storage.createStorageErrorId("REDIS", "LIST_MESSAGES", "INVALID_PAGE"),
638
+ domain: error.ErrorDomain.STORAGE,
639
+ category: error.ErrorCategory.USER,
640
+ details: { page }
641
+ },
642
+ new Error("page must be >= 0")
643
+ );
644
+ }
645
+ const { field, direction } = this.parseOrderBy(orderBy, "ASC");
646
+ const getFieldValue = (msg) => {
647
+ if (field === "createdAt") {
648
+ return new Date(msg.createdAt).getTime();
649
+ }
650
+ const value = msg[field];
651
+ if (typeof value === "number") {
652
+ return value;
653
+ }
654
+ if (value instanceof Date) {
655
+ return value.getTime();
656
+ }
657
+ return 0;
658
+ };
659
+ if (perPage === 0 && (!include || include.length === 0)) {
660
+ return {
661
+ messages: [],
662
+ total: 0,
663
+ page,
664
+ perPage: perPageForResponse,
665
+ hasMore: false
666
+ };
667
+ }
668
+ let includedMessages = [];
669
+ if (include && include.length > 0) {
670
+ const included = await this.getIncludedMessages(include);
671
+ includedMessages = included.map(this.parseStoredMessage);
672
+ }
673
+ if (perPage === 0 && include && include.length > 0) {
674
+ const list2 = new agent.MessageList().add(includedMessages, "memory");
675
+ const messages = list2.get.all.db().sort((a, b) => {
676
+ const aValue = getFieldValue(a);
677
+ const bValue = getFieldValue(b);
678
+ return direction === "ASC" ? aValue - bValue : bValue - aValue;
679
+ });
680
+ return {
681
+ messages,
682
+ total: 0,
683
+ page,
684
+ perPage: perPageForResponse,
685
+ hasMore: false
686
+ };
687
+ }
688
+ const allMessageIdsWithThreads = [];
689
+ for (const tid of threadIds) {
690
+ const threadMessagesKey = getThreadMessagesKey(tid);
691
+ const msgIds = await this.client.zRange(threadMessagesKey, 0, -1);
692
+ for (const mid of msgIds) {
693
+ allMessageIdsWithThreads.push({ threadId: tid, messageId: mid });
694
+ }
695
+ }
696
+ if (allMessageIdsWithThreads.length === 0) {
697
+ return {
698
+ messages: [],
699
+ total: 0,
700
+ page,
701
+ perPage: perPageForResponse,
702
+ hasMore: false
703
+ };
704
+ }
705
+ const messageKeys = allMessageIdsWithThreads.map(({ threadId: tid, messageId }) => getMessageKey(tid, messageId));
706
+ const results = await this.client.mGet(messageKeys);
707
+ let messagesData = results.filter((data) => data !== null).map((data) => JSON.parse(data)).map(this.parseStoredMessage);
708
+ if (resourceId) {
709
+ messagesData = messagesData.filter((msg) => msg.resourceId === resourceId);
710
+ }
711
+ messagesData = storage.filterByDateRange(
712
+ messagesData,
713
+ (msg) => new Date(msg.createdAt),
714
+ filter?.dateRange
715
+ );
716
+ messagesData.sort((a, b) => {
717
+ const aValue = getFieldValue(a);
718
+ const bValue = getFieldValue(b);
719
+ return direction === "ASC" ? aValue - bValue : bValue - aValue;
720
+ });
721
+ const total = messagesData.length;
722
+ const start = offset;
723
+ const end = perPageInput === false ? total : start + perPage;
724
+ const paginatedMessages = messagesData.slice(start, end);
725
+ const messageIdsSet = /* @__PURE__ */ new Set();
726
+ const allMessages = [];
727
+ for (const msg of paginatedMessages) {
728
+ if (messageIdsSet.has(msg.id)) {
729
+ continue;
730
+ }
731
+ allMessages.push(msg);
732
+ messageIdsSet.add(msg.id);
733
+ }
734
+ for (const msg of includedMessages) {
735
+ if (messageIdsSet.has(msg.id)) {
736
+ continue;
737
+ }
738
+ allMessages.push(msg);
739
+ messageIdsSet.add(msg.id);
740
+ }
741
+ const list = new agent.MessageList().add(allMessages, "memory");
742
+ let finalMessages = list.get.all.db();
743
+ finalMessages = finalMessages.sort((a, b) => {
744
+ const aValue = getFieldValue(a);
745
+ const bValue = getFieldValue(b);
746
+ return direction === "ASC" ? aValue - bValue : bValue - aValue;
747
+ });
748
+ const returnedThreadMessageIds = new Set(
749
+ finalMessages.filter((m) => {
750
+ return m.threadId && threadIdsSet.has(m.threadId);
751
+ }).map((m) => m.id)
752
+ );
753
+ const allThreadMessagesReturned = returnedThreadMessageIds.size >= total;
754
+ const hasMore = perPageInput !== false && !allThreadMessagesReturned && end < total;
755
+ return {
756
+ messages: finalMessages,
757
+ total,
758
+ page,
759
+ perPage: perPageForResponse,
760
+ hasMore
761
+ };
762
+ } catch (error$1) {
763
+ const mastraError = new error.MastraError(
764
+ {
765
+ id: storage.createStorageErrorId("REDIS", "LIST_MESSAGES", "FAILED"),
766
+ domain: error.ErrorDomain.STORAGE,
767
+ category: error.ErrorCategory.THIRD_PARTY,
768
+ details: {
769
+ threadId: Array.isArray(threadId) ? threadId.join(",") : threadId,
770
+ resourceId: resourceId ?? ""
771
+ }
772
+ },
773
+ error$1
774
+ );
775
+ this.logger.error(mastraError.toString());
776
+ this.logger.trackException(mastraError);
777
+ return {
778
+ messages: [],
779
+ total: 0,
780
+ page,
781
+ perPage: perPageForResponse,
782
+ hasMore: false
783
+ };
784
+ }
785
+ }
786
+ async getResourceById({ resourceId }) {
787
+ try {
788
+ const key = `${storage.TABLE_RESOURCES}:${resourceId}`;
789
+ const data = await this.client.get(key);
790
+ if (!data) {
791
+ return null;
792
+ }
793
+ const resource = JSON.parse(data);
794
+ return {
795
+ ...resource,
796
+ createdAt: new Date(resource.createdAt),
797
+ updatedAt: new Date(resource.updatedAt),
798
+ workingMemory: typeof resource.workingMemory === "object" ? JSON.stringify(resource.workingMemory) : resource.workingMemory,
799
+ metadata: typeof resource.metadata === "string" ? JSON.parse(resource.metadata) : resource.metadata
800
+ };
801
+ } catch (error) {
802
+ this.logger.error("Error getting resource by ID:", error);
803
+ throw error;
804
+ }
805
+ }
806
+ async saveResource({ resource }) {
807
+ try {
808
+ const key = `${storage.TABLE_RESOURCES}:${resource.id}`;
809
+ const serializedResource = {
810
+ ...resource,
811
+ metadata: JSON.stringify(resource.metadata),
812
+ createdAt: resource.createdAt.toISOString(),
813
+ updatedAt: resource.updatedAt.toISOString()
814
+ };
815
+ await this.client.set(key, JSON.stringify(serializedResource));
816
+ return resource;
817
+ } catch (error) {
818
+ this.logger.error("Error saving resource:", error);
819
+ throw error;
820
+ }
821
+ }
822
+ async updateResource({
823
+ resourceId,
824
+ workingMemory,
825
+ metadata
826
+ }) {
827
+ try {
828
+ const existingResource = await this.getResourceById({ resourceId });
829
+ if (!existingResource) {
830
+ const newResource = {
831
+ id: resourceId,
832
+ workingMemory,
833
+ metadata: metadata || {},
834
+ createdAt: /* @__PURE__ */ new Date(),
835
+ updatedAt: /* @__PURE__ */ new Date()
836
+ };
837
+ return this.saveResource({ resource: newResource });
838
+ }
839
+ const updatedResource = {
840
+ ...existingResource,
841
+ workingMemory: workingMemory !== void 0 ? workingMemory : existingResource.workingMemory,
842
+ metadata: {
843
+ ...existingResource.metadata,
844
+ ...metadata
845
+ },
846
+ updatedAt: /* @__PURE__ */ new Date()
847
+ };
848
+ await this.saveResource({ resource: updatedResource });
849
+ return updatedResource;
850
+ } catch (error) {
851
+ this.logger.error("Error updating resource:", error);
852
+ throw error;
853
+ }
854
+ }
855
+ async updateMessages(args) {
856
+ const { messages } = args;
857
+ if (messages.length === 0) {
858
+ return [];
859
+ }
860
+ try {
861
+ const messageIds = messages.map((m) => m.id);
862
+ const existingMessages = [];
863
+ const messageIdToKey = {};
864
+ for (const messageId of messageIds) {
865
+ const pattern = getMessageKey("*", messageId);
866
+ const keys = await this.db.scanKeys(pattern);
867
+ for (const key of keys) {
868
+ const data = await this.client.get(key);
869
+ if (!data) {
870
+ continue;
871
+ }
872
+ const message = JSON.parse(data);
873
+ if (message && message.id === messageId) {
874
+ existingMessages.push(message);
875
+ messageIdToKey[messageId] = key;
876
+ break;
877
+ }
878
+ }
879
+ }
880
+ if (existingMessages.length === 0) {
881
+ return [];
882
+ }
883
+ const threadIdsToUpdate = /* @__PURE__ */ new Set();
884
+ const multi = this.client.multi();
885
+ for (const existingMessage of existingMessages) {
886
+ const updatePayload = messages.find((m) => m.id === existingMessage.id);
887
+ if (!updatePayload) {
888
+ continue;
889
+ }
890
+ const { id, ...fieldsToUpdate } = updatePayload;
891
+ if (Object.keys(fieldsToUpdate).length === 0) {
892
+ continue;
893
+ }
894
+ threadIdsToUpdate.add(existingMessage.threadId);
895
+ if (updatePayload.threadId && updatePayload.threadId !== existingMessage.threadId) {
896
+ threadIdsToUpdate.add(updatePayload.threadId);
897
+ }
898
+ const updatedMessage = { ...existingMessage };
899
+ if (fieldsToUpdate.content) {
900
+ const existingContent = existingMessage.content;
901
+ const newContent = {
902
+ ...existingContent,
903
+ ...fieldsToUpdate.content,
904
+ ...existingContent?.metadata && fieldsToUpdate.content.metadata ? {
905
+ metadata: {
906
+ ...existingContent.metadata,
907
+ ...fieldsToUpdate.content.metadata
908
+ }
909
+ } : {}
910
+ };
911
+ updatedMessage.content = newContent;
912
+ }
913
+ for (const key2 in fieldsToUpdate) {
914
+ if (Object.prototype.hasOwnProperty.call(fieldsToUpdate, key2) && key2 !== "content") {
915
+ updatedMessage[key2] = fieldsToUpdate[key2];
916
+ }
917
+ }
918
+ const key = messageIdToKey[id];
919
+ if (!key) {
920
+ continue;
921
+ }
922
+ if (updatePayload.threadId && updatePayload.threadId !== existingMessage.threadId) {
923
+ multi.zRem(getThreadMessagesKey(existingMessage.threadId), id);
924
+ multi.del(key);
925
+ const newKey = getMessageKey(updatePayload.threadId, id);
926
+ multi.set(newKey, JSON.stringify(updatedMessage));
927
+ multi.set(getMessageIndexKey(id), updatePayload.threadId);
928
+ const score = getMessageScore(updatedMessage);
929
+ multi.zAdd(getThreadMessagesKey(updatePayload.threadId), { score, value: id });
930
+ messageIdToKey[id] = newKey;
931
+ continue;
932
+ }
933
+ multi.set(key, JSON.stringify(updatedMessage));
934
+ }
935
+ const now = /* @__PURE__ */ new Date();
936
+ for (const threadId of threadIdsToUpdate) {
937
+ if (threadId) {
938
+ const threadKey = getKey(storage.TABLE_THREADS, { id: threadId });
939
+ const existingThreadData = await this.client.get(threadKey);
940
+ if (existingThreadData) {
941
+ const existingThread = JSON.parse(existingThreadData);
942
+ const updatedThread = {
943
+ ...existingThread,
944
+ updatedAt: now
945
+ };
946
+ multi.set(threadKey, JSON.stringify(processRecord(storage.TABLE_THREADS, updatedThread).processedRecord));
947
+ }
948
+ }
949
+ }
950
+ await multi.exec();
951
+ const updatedMessages = [];
952
+ for (const messageId of messageIds) {
953
+ const key = messageIdToKey[messageId];
954
+ if (key) {
955
+ const data = await this.client.get(key);
956
+ if (data) {
957
+ updatedMessages.push(JSON.parse(data));
958
+ }
959
+ }
960
+ }
961
+ return updatedMessages;
962
+ } catch (error$1) {
963
+ throw new error.MastraError(
964
+ {
965
+ id: storage.createStorageErrorId("REDIS", "UPDATE_MESSAGES", "FAILED"),
966
+ domain: error.ErrorDomain.STORAGE,
967
+ category: error.ErrorCategory.THIRD_PARTY,
968
+ details: {
969
+ messageIds: messages.map((m) => m.id).join(",")
970
+ }
971
+ },
972
+ error$1
973
+ );
974
+ }
975
+ }
976
+ async deleteMessages(messageIds) {
977
+ if (!messageIds || messageIds.length === 0) {
978
+ return;
979
+ }
980
+ try {
981
+ const threadIds = /* @__PURE__ */ new Set();
982
+ const messageKeys = [];
983
+ const foundMessageIds = [];
984
+ const messageIdToThreadId = /* @__PURE__ */ new Map();
985
+ const indexKeys = messageIds.map((id) => getMessageIndexKey(id));
986
+ const indexResults = await this.client.mGet(indexKeys);
987
+ const indexedMessages = [];
988
+ const unindexedMessageIds = [];
989
+ messageIds.forEach((id, i) => {
990
+ const threadId = indexResults[i];
991
+ if (threadId) {
992
+ indexedMessages.push({ messageId: id, threadId });
993
+ return;
994
+ }
995
+ unindexedMessageIds.push(id);
996
+ });
997
+ for (const { messageId, threadId } of indexedMessages) {
998
+ messageKeys.push(getMessageKey(threadId, messageId));
999
+ foundMessageIds.push(messageId);
1000
+ messageIdToThreadId.set(messageId, threadId);
1001
+ threadIds.add(threadId);
1002
+ }
1003
+ for (const messageId of unindexedMessageIds) {
1004
+ const pattern = getMessageKey("*", messageId);
1005
+ const keys = await this.db.scanKeys(pattern);
1006
+ for (const key of keys) {
1007
+ const data = await this.client.get(key);
1008
+ if (!data) {
1009
+ continue;
1010
+ }
1011
+ const message = JSON.parse(data);
1012
+ if (message && message.id === messageId) {
1013
+ messageKeys.push(key);
1014
+ foundMessageIds.push(messageId);
1015
+ if (message.threadId) {
1016
+ messageIdToThreadId.set(messageId, message.threadId);
1017
+ threadIds.add(message.threadId);
1018
+ }
1019
+ break;
1020
+ }
1021
+ }
1022
+ }
1023
+ if (messageKeys.length === 0) {
1024
+ return;
1025
+ }
1026
+ const multi = this.client.multi();
1027
+ for (const key of messageKeys) {
1028
+ multi.del(key);
1029
+ }
1030
+ for (const messageId of foundMessageIds) {
1031
+ multi.del(getMessageIndexKey(messageId));
1032
+ }
1033
+ if (threadIds.size > 0) {
1034
+ for (const threadId of threadIds) {
1035
+ for (const [msgId, msgThreadId] of messageIdToThreadId) {
1036
+ if (msgThreadId === threadId) {
1037
+ multi.zRem(getThreadMessagesKey(threadId), msgId);
1038
+ }
1039
+ }
1040
+ const threadKey = getKey(storage.TABLE_THREADS, { id: threadId });
1041
+ const threadData = await this.client.get(threadKey);
1042
+ if (!threadData) {
1043
+ continue;
1044
+ }
1045
+ const thread = JSON.parse(threadData);
1046
+ const updatedThread = { ...thread, updatedAt: /* @__PURE__ */ new Date() };
1047
+ multi.set(threadKey, JSON.stringify(processRecord(storage.TABLE_THREADS, updatedThread).processedRecord));
1048
+ }
1049
+ }
1050
+ await multi.exec();
1051
+ } catch (error$1) {
1052
+ throw new error.MastraError(
1053
+ {
1054
+ id: storage.createStorageErrorId("REDIS", "DELETE_MESSAGES", "FAILED"),
1055
+ domain: error.ErrorDomain.STORAGE,
1056
+ category: error.ErrorCategory.THIRD_PARTY,
1057
+ details: { messageIds: messageIds.join(", ") }
1058
+ },
1059
+ error$1
1060
+ );
1061
+ }
1062
+ }
1063
+ sortThreads(threads, field, direction) {
1064
+ return threads.sort((a, b) => {
1065
+ const aValue = new Date(a[field]).getTime();
1066
+ const bValue = new Date(b[field]).getTime();
1067
+ return direction === "ASC" ? aValue - bValue : bValue - aValue;
1068
+ });
1069
+ }
1070
+ async cloneThread(args) {
1071
+ const { sourceThreadId, newThreadId: providedThreadId, resourceId, title, metadata, options } = args;
1072
+ const sourceThread = await this.getThreadById({ threadId: sourceThreadId });
1073
+ if (!sourceThread) {
1074
+ throw new error.MastraError({
1075
+ id: storage.createStorageErrorId("REDIS", "CLONE_THREAD", "SOURCE_NOT_FOUND"),
1076
+ domain: error.ErrorDomain.STORAGE,
1077
+ category: error.ErrorCategory.USER,
1078
+ text: `Source thread with id ${sourceThreadId} not found`,
1079
+ details: { sourceThreadId }
1080
+ });
1081
+ }
1082
+ const newThreadId = providedThreadId || crypto.randomUUID();
1083
+ const existingThread = await this.getThreadById({ threadId: newThreadId });
1084
+ if (existingThread) {
1085
+ throw new error.MastraError({
1086
+ id: storage.createStorageErrorId("REDIS", "CLONE_THREAD", "THREAD_EXISTS"),
1087
+ domain: error.ErrorDomain.STORAGE,
1088
+ category: error.ErrorCategory.USER,
1089
+ text: `Thread with id ${newThreadId} already exists`,
1090
+ details: { newThreadId }
1091
+ });
1092
+ }
1093
+ try {
1094
+ const threadMessagesKey = getThreadMessagesKey(sourceThreadId);
1095
+ const msgIds = await this.client.zRange(threadMessagesKey, 0, -1);
1096
+ const messageKeys = msgIds.map((mid) => getMessageKey(sourceThreadId, mid));
1097
+ let sourceMessages = [];
1098
+ if (messageKeys.length > 0) {
1099
+ const results = await this.client.mGet(messageKeys);
1100
+ sourceMessages = results.filter((data) => data !== null).map((data) => {
1101
+ const msg = JSON.parse(data);
1102
+ return { ...msg, createdAt: new Date(msg.createdAt) };
1103
+ });
1104
+ }
1105
+ if (options?.messageFilter?.startDate || options?.messageFilter?.endDate) {
1106
+ sourceMessages = storage.filterByDateRange(sourceMessages, (msg) => new Date(msg.createdAt), {
1107
+ start: options.messageFilter?.startDate,
1108
+ end: options.messageFilter?.endDate
1109
+ });
1110
+ }
1111
+ if (options?.messageFilter?.messageIds && options.messageFilter.messageIds.length > 0) {
1112
+ const messageIdSet = new Set(options.messageFilter.messageIds);
1113
+ sourceMessages = sourceMessages.filter((msg) => messageIdSet.has(msg.id));
1114
+ }
1115
+ sourceMessages.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
1116
+ if (options?.messageLimit && options.messageLimit > 0 && sourceMessages.length > options.messageLimit) {
1117
+ sourceMessages = sourceMessages.slice(-options.messageLimit);
1118
+ }
1119
+ const now = /* @__PURE__ */ new Date();
1120
+ const lastMessageId = sourceMessages.length > 0 ? sourceMessages[sourceMessages.length - 1].id : void 0;
1121
+ const cloneMetadata = {
1122
+ sourceThreadId,
1123
+ clonedAt: now,
1124
+ ...lastMessageId && { lastMessageId }
1125
+ };
1126
+ const newThread = {
1127
+ id: newThreadId,
1128
+ resourceId: resourceId || sourceThread.resourceId,
1129
+ title: title || (sourceThread.title ? `Clone of ${sourceThread.title}` : void 0),
1130
+ metadata: { ...metadata, clone: cloneMetadata },
1131
+ createdAt: now,
1132
+ updatedAt: now
1133
+ };
1134
+ const multi = this.client.multi();
1135
+ const threadKey = getKey(storage.TABLE_THREADS, { id: newThreadId });
1136
+ multi.set(threadKey, JSON.stringify(processRecord(storage.TABLE_THREADS, newThread).processedRecord));
1137
+ const clonedMessages = [];
1138
+ const targetResourceId = resourceId || sourceThread.resourceId;
1139
+ const newThreadMessagesKey = getThreadMessagesKey(newThreadId);
1140
+ for (let i = 0; i < sourceMessages.length; i++) {
1141
+ const sourceMsg = sourceMessages[i];
1142
+ const newMessageId = crypto.randomUUID();
1143
+ const { _index, ...restMsg } = sourceMsg;
1144
+ const newMessage = {
1145
+ ...restMsg,
1146
+ id: newMessageId,
1147
+ threadId: newThreadId,
1148
+ resourceId: targetResourceId
1149
+ };
1150
+ const messageKey = getMessageKey(newThreadId, newMessageId);
1151
+ multi.set(messageKey, JSON.stringify(newMessage));
1152
+ multi.set(getMessageIndexKey(newMessageId), newThreadId);
1153
+ const score = getMessageScore({ createdAt: newMessage.createdAt, _index: i });
1154
+ multi.zAdd(newThreadMessagesKey, { score, value: newMessageId });
1155
+ clonedMessages.push(newMessage);
1156
+ }
1157
+ await multi.exec();
1158
+ return {
1159
+ thread: newThread,
1160
+ clonedMessages
1161
+ };
1162
+ } catch (error$1) {
1163
+ if (error$1 instanceof error.MastraError) {
1164
+ throw error$1;
1165
+ }
1166
+ throw new error.MastraError(
1167
+ {
1168
+ id: storage.createStorageErrorId("REDIS", "CLONE_THREAD", "FAILED"),
1169
+ domain: error.ErrorDomain.STORAGE,
1170
+ category: error.ErrorCategory.THIRD_PARTY,
1171
+ details: { sourceThreadId, newThreadId }
1172
+ },
1173
+ error$1
1174
+ );
1175
+ }
1176
+ }
1177
+ };
1178
+ function getThreadMessagesKey(threadId) {
1179
+ return `thread:${threadId}:messages`;
1180
+ }
1181
+ function getMessageKey(threadId, messageId) {
1182
+ return getKey(storage.TABLE_MESSAGES, { threadId, id: messageId });
1183
+ }
1184
+ function getMessageIndexKey(messageId) {
1185
+ return `msg-idx:${messageId}`;
1186
+ }
1187
+ function getMessageScore(message) {
1188
+ const createdAtScore = new Date(message.createdAt).getTime();
1189
+ const index = typeof message._index === "number" ? message._index : 0;
1190
+ return createdAtScore * 1e3 + index;
1191
+ }
1192
+ var ScoresRedis = class extends storage.ScoresStorage {
1193
+ client;
1194
+ db;
1195
+ constructor(config) {
1196
+ super();
1197
+ this.client = config.client;
1198
+ this.db = new RedisDB({ client: config.client });
1199
+ }
1200
+ async dangerouslyClearAll() {
1201
+ await this.db.deleteData({ tableName: storage.TABLE_SCORERS });
1202
+ }
1203
+ async getScoreById({ id }) {
1204
+ try {
1205
+ const data = await this.db.get({
1206
+ tableName: storage.TABLE_SCORERS,
1207
+ keys: { id }
1208
+ });
1209
+ if (!data) {
1210
+ return null;
1211
+ }
1212
+ return storage.transformScoreRow(data);
1213
+ } catch (error$1) {
1214
+ throw new error.MastraError(
1215
+ {
1216
+ id: storage.createStorageErrorId("REDIS", "GET_SCORE_BY_ID", "FAILED"),
1217
+ domain: error.ErrorDomain.STORAGE,
1218
+ category: error.ErrorCategory.THIRD_PARTY,
1219
+ details: {
1220
+ ...id && { id }
1221
+ }
1222
+ },
1223
+ error$1
1224
+ );
1225
+ }
1226
+ }
1227
+ async listScoresByScorerId({
1228
+ scorerId,
1229
+ entityId,
1230
+ entityType,
1231
+ source,
1232
+ pagination = { page: 0, perPage: 20 }
1233
+ }) {
1234
+ return this.fetchAndFilterScores(pagination, (row) => {
1235
+ if (row.scorerId !== scorerId) {
1236
+ return false;
1237
+ }
1238
+ if (entityId && row.entityId !== entityId) {
1239
+ return false;
1240
+ }
1241
+ if (entityType && row.entityType !== entityType) {
1242
+ return false;
1243
+ }
1244
+ if (source && row.source !== source) {
1245
+ return false;
1246
+ }
1247
+ return true;
1248
+ });
1249
+ }
1250
+ async saveScore(score) {
1251
+ let validatedScore;
1252
+ try {
1253
+ validatedScore = evals.saveScorePayloadSchema.parse(score);
1254
+ } catch (error$1) {
1255
+ throw new error.MastraError(
1256
+ {
1257
+ id: storage.createStorageErrorId("REDIS", "SAVE_SCORE", "VALIDATION_FAILED"),
1258
+ domain: error.ErrorDomain.STORAGE,
1259
+ category: error.ErrorCategory.USER,
1260
+ details: {
1261
+ scorer: typeof score.scorer?.id === "string" ? score.scorer.id : String(score.scorer?.id ?? "unknown"),
1262
+ entityId: score.entityId ?? "unknown",
1263
+ entityType: score.entityType ?? "unknown",
1264
+ traceId: score.traceId ?? "",
1265
+ spanId: score.spanId ?? ""
1266
+ }
1267
+ },
1268
+ error$1
1269
+ );
1270
+ }
1271
+ const now = /* @__PURE__ */ new Date();
1272
+ const id = crypto2__default.default.randomUUID();
1273
+ const scoreWithId = {
1274
+ ...validatedScore,
1275
+ id,
1276
+ createdAt: now,
1277
+ updatedAt: now
1278
+ };
1279
+ const { key, processedRecord } = processRecord(storage.TABLE_SCORERS, scoreWithId);
1280
+ try {
1281
+ await this.client.set(key, JSON.stringify(processedRecord));
1282
+ return { score: { ...validatedScore, id, createdAt: now, updatedAt: now } };
1283
+ } catch (error$1) {
1284
+ throw new error.MastraError(
1285
+ {
1286
+ id: storage.createStorageErrorId("REDIS", "SAVE_SCORE", "FAILED"),
1287
+ domain: error.ErrorDomain.STORAGE,
1288
+ category: error.ErrorCategory.THIRD_PARTY,
1289
+ details: { id }
1290
+ },
1291
+ error$1
1292
+ );
1293
+ }
1294
+ }
1295
+ async listScoresByRunId({
1296
+ runId,
1297
+ pagination = { page: 0, perPage: 20 }
1298
+ }) {
1299
+ return this.fetchAndFilterScores(pagination, (row) => row.runId === runId);
1300
+ }
1301
+ async listScoresByEntityId({
1302
+ entityId,
1303
+ entityType,
1304
+ pagination = { page: 0, perPage: 20 }
1305
+ }) {
1306
+ return this.fetchAndFilterScores(pagination, (row) => {
1307
+ if (row.entityId !== entityId) {
1308
+ return false;
1309
+ }
1310
+ if (entityType && row.entityType !== entityType) {
1311
+ return false;
1312
+ }
1313
+ return true;
1314
+ });
1315
+ }
1316
+ async listScoresBySpan({
1317
+ traceId,
1318
+ spanId,
1319
+ pagination = { page: 0, perPage: 20 }
1320
+ }) {
1321
+ return this.fetchAndFilterScores(pagination, (row) => row.traceId === traceId && row.spanId === spanId);
1322
+ }
1323
+ async fetchAndFilterScores(pagination, filterFn) {
1324
+ const { page, perPage: perPageInput } = pagination;
1325
+ const keys = await this.db.scanKeys(`${storage.TABLE_SCORERS}:*`);
1326
+ if (keys.length === 0) {
1327
+ return {
1328
+ scores: [],
1329
+ pagination: { total: 0, page, perPage: perPageInput, hasMore: false }
1330
+ };
1331
+ }
1332
+ const results = await this.client.mGet(keys);
1333
+ const filtered = results.map((data) => {
1334
+ if (!data) {
1335
+ return null;
1336
+ }
1337
+ try {
1338
+ return JSON.parse(data);
1339
+ } catch {
1340
+ return null;
1341
+ }
1342
+ }).filter((row) => !!row && typeof row === "object" && filterFn(row));
1343
+ const total = filtered.length;
1344
+ const perPage = storage.normalizePerPage(perPageInput, 100);
1345
+ const { offset: start, perPage: perPageForResponse } = storage.calculatePagination(page, perPageInput, perPage);
1346
+ const end = perPageInput === false ? total : start + perPage;
1347
+ const scores = filtered.slice(start, end).map((row) => storage.transformScoreRow(row));
1348
+ return {
1349
+ scores,
1350
+ pagination: {
1351
+ total,
1352
+ page,
1353
+ perPage: perPageForResponse,
1354
+ hasMore: end < total
1355
+ }
1356
+ };
1357
+ }
1358
+ };
1359
+ function parseWorkflowRun(row) {
1360
+ let parsedSnapshot = row.snapshot;
1361
+ if (typeof parsedSnapshot === "string") {
1362
+ try {
1363
+ parsedSnapshot = JSON.parse(row.snapshot);
1364
+ } catch (e) {
1365
+ console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
1366
+ }
1367
+ }
1368
+ return {
1369
+ workflowName: row.workflow_name,
1370
+ runId: row.run_id,
1371
+ snapshot: parsedSnapshot,
1372
+ createdAt: storage.ensureDate(row.createdAt),
1373
+ updatedAt: storage.ensureDate(row.updatedAt),
1374
+ resourceId: row.resourceId
1375
+ };
1376
+ }
1377
+ var WorkflowsRedis = class extends storage.WorkflowsStorage {
1378
+ client;
1379
+ db;
1380
+ constructor(config) {
1381
+ super();
1382
+ this.client = config.client;
1383
+ this.db = new RedisDB({ client: config.client });
1384
+ }
1385
+ supportsConcurrentUpdates() {
1386
+ return false;
1387
+ }
1388
+ async dangerouslyClearAll() {
1389
+ await this.db.deleteData({ tableName: storage.TABLE_WORKFLOW_SNAPSHOT });
1390
+ }
1391
+ async updateWorkflowResults({
1392
+ workflowName,
1393
+ runId,
1394
+ stepId,
1395
+ result,
1396
+ requestContext
1397
+ }) {
1398
+ try {
1399
+ const existingRecord = await this.db.get({
1400
+ tableName: storage.TABLE_WORKFLOW_SNAPSHOT,
1401
+ keys: {
1402
+ namespace: "workflows",
1403
+ workflow_name: workflowName,
1404
+ run_id: runId
1405
+ }
1406
+ });
1407
+ const existingSnapshot = existingRecord?.snapshot;
1408
+ let snapshot = existingSnapshot;
1409
+ if (!snapshot) {
1410
+ snapshot = {
1411
+ context: {},
1412
+ activePaths: [],
1413
+ timestamp: Date.now(),
1414
+ suspendedPaths: {},
1415
+ activeStepsPath: {},
1416
+ resumeLabels: {},
1417
+ serializedStepGraph: [],
1418
+ status: "pending",
1419
+ value: {},
1420
+ waitingPaths: {},
1421
+ runId,
1422
+ requestContext: {}
1423
+ };
1424
+ }
1425
+ snapshot.context[stepId] = result;
1426
+ snapshot.requestContext = { ...snapshot.requestContext, ...requestContext };
1427
+ await this.persistWorkflowSnapshot({
1428
+ namespace: "workflows",
1429
+ workflowName,
1430
+ runId,
1431
+ snapshot,
1432
+ createdAt: existingRecord?.createdAt ? storage.ensureDate(existingRecord.createdAt) : void 0
1433
+ });
1434
+ return snapshot.context;
1435
+ } catch (error$1) {
1436
+ if (error$1 instanceof error.MastraError) {
1437
+ throw error$1;
1438
+ }
1439
+ throw new error.MastraError(
1440
+ {
1441
+ id: storage.createStorageErrorId("REDIS", "UPDATE_WORKFLOW_RESULTS", "FAILED"),
1442
+ domain: error.ErrorDomain.STORAGE,
1443
+ category: error.ErrorCategory.THIRD_PARTY,
1444
+ details: { workflowName, runId, stepId }
1445
+ },
1446
+ error$1
1447
+ );
1448
+ }
1449
+ }
1450
+ async updateWorkflowState({
1451
+ workflowName,
1452
+ runId,
1453
+ opts
1454
+ }) {
1455
+ try {
1456
+ const existingRecord = await this.db.get({
1457
+ tableName: storage.TABLE_WORKFLOW_SNAPSHOT,
1458
+ keys: {
1459
+ namespace: "workflows",
1460
+ workflow_name: workflowName,
1461
+ run_id: runId
1462
+ }
1463
+ });
1464
+ const existingSnapshot = existingRecord?.snapshot;
1465
+ if (!existingSnapshot || !existingSnapshot.context) {
1466
+ return void 0;
1467
+ }
1468
+ const updatedSnapshot = { ...existingSnapshot, ...opts };
1469
+ await this.persistWorkflowSnapshot({
1470
+ namespace: "workflows",
1471
+ workflowName,
1472
+ runId,
1473
+ snapshot: updatedSnapshot,
1474
+ createdAt: existingRecord?.createdAt ? storage.ensureDate(existingRecord.createdAt) : void 0
1475
+ });
1476
+ return updatedSnapshot;
1477
+ } catch (error$1) {
1478
+ if (error$1 instanceof error.MastraError) {
1479
+ throw error$1;
1480
+ }
1481
+ throw new error.MastraError(
1482
+ {
1483
+ id: storage.createStorageErrorId("REDIS", "UPDATE_WORKFLOW_STATE", "FAILED"),
1484
+ domain: error.ErrorDomain.STORAGE,
1485
+ category: error.ErrorCategory.THIRD_PARTY,
1486
+ details: { workflowName, runId }
1487
+ },
1488
+ error$1
1489
+ );
1490
+ }
1491
+ }
1492
+ async persistWorkflowSnapshot(params) {
1493
+ const { namespace = "workflows", workflowName, runId, resourceId, snapshot, createdAt, updatedAt } = params;
1494
+ try {
1495
+ let finalCreatedAt = createdAt;
1496
+ if (!finalCreatedAt) {
1497
+ const existing = await this.db.get({
1498
+ tableName: storage.TABLE_WORKFLOW_SNAPSHOT,
1499
+ keys: {
1500
+ namespace,
1501
+ workflow_name: workflowName,
1502
+ run_id: runId
1503
+ }
1504
+ });
1505
+ finalCreatedAt = existing?.createdAt ? storage.ensureDate(existing.createdAt) : /* @__PURE__ */ new Date();
1506
+ }
1507
+ await this.db.insert({
1508
+ tableName: storage.TABLE_WORKFLOW_SNAPSHOT,
1509
+ record: {
1510
+ namespace,
1511
+ workflow_name: workflowName,
1512
+ run_id: runId,
1513
+ resourceId,
1514
+ snapshot,
1515
+ createdAt: finalCreatedAt,
1516
+ updatedAt: updatedAt ?? /* @__PURE__ */ new Date()
1517
+ }
1518
+ });
1519
+ } catch (error$1) {
1520
+ throw new error.MastraError(
1521
+ {
1522
+ id: storage.createStorageErrorId("REDIS", "PERSIST_WORKFLOW_SNAPSHOT", "FAILED"),
1523
+ domain: error.ErrorDomain.STORAGE,
1524
+ category: error.ErrorCategory.THIRD_PARTY,
1525
+ details: {
1526
+ namespace,
1527
+ workflowName,
1528
+ runId
1529
+ }
1530
+ },
1531
+ error$1
1532
+ );
1533
+ }
1534
+ }
1535
+ async loadWorkflowSnapshot(params) {
1536
+ const { namespace = "workflows", workflowName, runId } = params;
1537
+ const key = getKey(storage.TABLE_WORKFLOW_SNAPSHOT, {
1538
+ namespace,
1539
+ workflow_name: workflowName,
1540
+ run_id: runId
1541
+ });
1542
+ try {
1543
+ const data = await this.client.get(key);
1544
+ if (!data) {
1545
+ return null;
1546
+ }
1547
+ const parsed = JSON.parse(data);
1548
+ return parsed.snapshot;
1549
+ } catch (error$1) {
1550
+ throw new error.MastraError(
1551
+ {
1552
+ id: storage.createStorageErrorId("REDIS", "LOAD_WORKFLOW_SNAPSHOT", "FAILED"),
1553
+ domain: error.ErrorDomain.STORAGE,
1554
+ category: error.ErrorCategory.THIRD_PARTY,
1555
+ details: {
1556
+ namespace,
1557
+ workflowName,
1558
+ runId
1559
+ }
1560
+ },
1561
+ error$1
1562
+ );
1563
+ }
1564
+ }
1565
+ async getWorkflowRunById({
1566
+ runId,
1567
+ workflowName
1568
+ }) {
1569
+ try {
1570
+ const key = getKey(storage.TABLE_WORKFLOW_SNAPSHOT, { namespace: "workflows", workflow_name: workflowName, run_id: runId }) + "*";
1571
+ const keys = await this.db.scanKeys(key);
1572
+ if (keys.length === 0) {
1573
+ return null;
1574
+ }
1575
+ const results = await this.client.mGet(keys);
1576
+ const workflows = results.filter((data2) => data2 !== null).map(
1577
+ (data2) => JSON.parse(data2)
1578
+ );
1579
+ const data = workflows.find((workflow) => {
1580
+ if (!workflow) {
1581
+ return false;
1582
+ }
1583
+ const runIdMatch = workflow.run_id === runId;
1584
+ if (workflowName) {
1585
+ return runIdMatch && workflow.workflow_name === workflowName;
1586
+ }
1587
+ return runIdMatch;
1588
+ });
1589
+ if (!data) {
1590
+ return null;
1591
+ }
1592
+ return parseWorkflowRun(data);
1593
+ } catch (error$1) {
1594
+ throw new error.MastraError(
1595
+ {
1596
+ id: storage.createStorageErrorId("REDIS", "GET_WORKFLOW_RUN_BY_ID", "FAILED"),
1597
+ domain: error.ErrorDomain.STORAGE,
1598
+ category: error.ErrorCategory.THIRD_PARTY,
1599
+ details: {
1600
+ namespace: "workflows",
1601
+ runId,
1602
+ workflowName: workflowName || ""
1603
+ }
1604
+ },
1605
+ error$1
1606
+ );
1607
+ }
1608
+ }
1609
+ async deleteWorkflowRunById({ runId, workflowName }) {
1610
+ const key = getKey(storage.TABLE_WORKFLOW_SNAPSHOT, { namespace: "workflows", workflow_name: workflowName, run_id: runId });
1611
+ try {
1612
+ await this.client.del(key);
1613
+ } catch (error$1) {
1614
+ throw new error.MastraError(
1615
+ {
1616
+ id: storage.createStorageErrorId("REDIS", "DELETE_WORKFLOW_RUN_BY_ID", "FAILED"),
1617
+ domain: error.ErrorDomain.STORAGE,
1618
+ category: error.ErrorCategory.THIRD_PARTY,
1619
+ details: {
1620
+ namespace: "workflows",
1621
+ runId,
1622
+ workflowName
1623
+ }
1624
+ },
1625
+ error$1
1626
+ );
1627
+ }
1628
+ }
1629
+ async listWorkflowRuns({
1630
+ workflowName,
1631
+ fromDate,
1632
+ toDate,
1633
+ perPage,
1634
+ page,
1635
+ resourceId,
1636
+ status
1637
+ } = {}) {
1638
+ try {
1639
+ if (page !== void 0 && page < 0) {
1640
+ throw new error.MastraError(
1641
+ {
1642
+ id: storage.createStorageErrorId("REDIS", "LIST_WORKFLOW_RUNS", "INVALID_PAGE"),
1643
+ domain: error.ErrorDomain.STORAGE,
1644
+ category: error.ErrorCategory.USER,
1645
+ details: { page }
1646
+ },
1647
+ new Error("page must be >= 0")
1648
+ );
1649
+ }
1650
+ const normalizedFrom = fromDate ? storage.ensureDate(fromDate) : void 0;
1651
+ const normalizedTo = toDate ? storage.ensureDate(toDate) : void 0;
1652
+ let pattern = getKey(storage.TABLE_WORKFLOW_SNAPSHOT, { namespace: "workflows" }) + ":*";
1653
+ if (workflowName && resourceId) {
1654
+ pattern = getKey(storage.TABLE_WORKFLOW_SNAPSHOT, {
1655
+ namespace: "workflows",
1656
+ workflow_name: workflowName,
1657
+ run_id: "*",
1658
+ resourceId
1659
+ });
1660
+ } else if (workflowName) {
1661
+ pattern = getKey(storage.TABLE_WORKFLOW_SNAPSHOT, { namespace: "workflows", workflow_name: workflowName }) + ":*";
1662
+ } else if (resourceId) {
1663
+ pattern = getKey(storage.TABLE_WORKFLOW_SNAPSHOT, {
1664
+ namespace: "workflows",
1665
+ workflow_name: "*",
1666
+ run_id: "*",
1667
+ resourceId
1668
+ });
1669
+ }
1670
+ const keys = await this.db.scanKeys(pattern);
1671
+ if (keys.length === 0) {
1672
+ return { runs: [], total: 0 };
1673
+ }
1674
+ const results = await this.client.mGet(keys);
1675
+ let runs = results.filter((data) => data !== null).map((data) => JSON.parse(data)).filter(
1676
+ (record) => record !== null && record !== void 0 && typeof record === "object" && "workflow_name" in record
1677
+ ).filter((record) => !workflowName || record.workflow_name === workflowName).map((w) => parseWorkflowRun(w)).filter((w) => {
1678
+ if (normalizedFrom && w.createdAt < normalizedFrom) {
1679
+ return false;
1680
+ }
1681
+ if (normalizedTo && w.createdAt > normalizedTo) {
1682
+ return false;
1683
+ }
1684
+ if (status) {
1685
+ let snapshot = w.snapshot;
1686
+ if (typeof snapshot === "string") {
1687
+ try {
1688
+ snapshot = JSON.parse(snapshot);
1689
+ } catch (e) {
1690
+ console.warn(`Failed to parse snapshot for workflow ${w.workflowName}: ${e}`);
1691
+ return false;
1692
+ }
1693
+ }
1694
+ return snapshot.status === status;
1695
+ }
1696
+ return true;
1697
+ }).sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
1698
+ const total = runs.length;
1699
+ if (typeof perPage === "number" && typeof page === "number") {
1700
+ const normalizedPerPage = storage.normalizePerPage(perPage, Number.MAX_SAFE_INTEGER);
1701
+ const offset = page * normalizedPerPage;
1702
+ runs = runs.slice(offset, offset + normalizedPerPage);
1703
+ }
1704
+ return { runs, total };
1705
+ } catch (error$1) {
1706
+ if (error$1 instanceof error.MastraError) {
1707
+ throw error$1;
1708
+ }
1709
+ throw new error.MastraError(
1710
+ {
1711
+ id: storage.createStorageErrorId("REDIS", "LIST_WORKFLOW_RUNS", "FAILED"),
1712
+ domain: error.ErrorDomain.STORAGE,
1713
+ category: error.ErrorCategory.THIRD_PARTY,
1714
+ details: {
1715
+ namespace: "workflows",
1716
+ workflowName: workflowName || "",
1717
+ resourceId: resourceId || ""
1718
+ }
1719
+ },
1720
+ error$1
1721
+ );
1722
+ }
1723
+ }
1724
+ };
1725
+
1726
+ // src/storage/utils.ts
1727
+ function isClientConfig(config) {
1728
+ return "client" in config;
1729
+ }
1730
+ function isConnectionStringConfig(config) {
1731
+ return "connectionString" in config;
1732
+ }
1733
+
1734
+ // src/storage/store.ts
1735
+ var RedisStore = class extends storage.MastraStorage {
1736
+ client;
1737
+ shouldManageConnection;
1738
+ stores;
1739
+ constructor(config) {
1740
+ super({ id: config.id, name: "Redis", disableInit: config.disableInit });
1741
+ const { client, shouldManageConnection } = this.createClient(config);
1742
+ this.client = client;
1743
+ this.shouldManageConnection = shouldManageConnection;
1744
+ this.stores = {
1745
+ scores: new ScoresRedis({ client: this.client }),
1746
+ workflows: new WorkflowsRedis({ client: this.client }),
1747
+ memory: new StoreMemoryRedis({ client: this.client })
1748
+ };
1749
+ }
1750
+ async init() {
1751
+ if (this.shouldManageConnection && !this.client.isOpen) {
1752
+ await this.client.connect();
1753
+ }
1754
+ await super.init();
1755
+ }
1756
+ getClient() {
1757
+ return this.client;
1758
+ }
1759
+ async close() {
1760
+ if (this.shouldManageConnection && this.client.isOpen) {
1761
+ await this.client.quit();
1762
+ }
1763
+ }
1764
+ createClient(config) {
1765
+ if (isClientConfig(config)) {
1766
+ return { client: config.client, shouldManageConnection: false };
1767
+ }
1768
+ if (isConnectionStringConfig(config)) {
1769
+ if (!config.connectionString?.trim()) {
1770
+ throw new Error("RedisStore: connectionString is required and cannot be empty.");
1771
+ }
1772
+ return {
1773
+ client: redis.createClient({ url: config.connectionString }),
1774
+ shouldManageConnection: true
1775
+ };
1776
+ }
1777
+ if (!config.host?.trim()) {
1778
+ throw new Error("RedisStore: host is required and cannot be empty.");
1779
+ }
1780
+ const url = this.createClientUrl({
1781
+ ...config,
1782
+ db: config.db ?? 0,
1783
+ port: config.port ?? 6379
1784
+ });
1785
+ return {
1786
+ client: redis.createClient({ url }),
1787
+ shouldManageConnection: true
1788
+ };
1789
+ }
1790
+ createClientUrl(config) {
1791
+ const encodedPassword = config.password ? encodeURIComponent(config.password) : null;
1792
+ if (config.password) {
1793
+ return `redis://:${encodedPassword}@${config.host}:${config.port || 6379}/${config.db || 0}`;
1794
+ }
1795
+ return `redis://${config.host}:${config.port || 6379}/${config.db || 0}`;
1796
+ }
1797
+ };
1798
+
1799
+ exports.RedisStore = RedisStore;
1800
+ exports.ScoresRedis = ScoresRedis;
1801
+ exports.StoreMemoryRedis = StoreMemoryRedis;
1802
+ exports.WorkflowsRedis = WorkflowsRedis;
1803
+ //# sourceMappingURL=index.cjs.map
1804
+ //# sourceMappingURL=index.cjs.map