@mastra/upstash 0.0.0-working-memory-per-user-20250620163010 → 0.0.0-zod-v4-compat-part-2-20250820135355

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.
Files changed (57) hide show
  1. package/CHANGELOG.md +268 -6
  2. package/LICENSE.md +12 -4
  3. package/README.md +98 -0
  4. package/dist/index.cjs +1629 -626
  5. package/dist/index.cjs.map +1 -0
  6. package/dist/index.d.ts +4 -4
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +1645 -642
  9. package/dist/index.js.map +1 -0
  10. package/dist/storage/domains/legacy-evals/index.d.ts +28 -0
  11. package/dist/storage/domains/legacy-evals/index.d.ts.map +1 -0
  12. package/dist/storage/domains/memory/index.d.ts +86 -0
  13. package/dist/storage/domains/memory/index.d.ts.map +1 -0
  14. package/dist/storage/domains/operations/index.d.ts +40 -0
  15. package/dist/storage/domains/operations/index.d.ts.map +1 -0
  16. package/dist/storage/domains/scores/index.d.ts +65 -0
  17. package/dist/storage/domains/scores/index.d.ts.map +1 -0
  18. package/dist/storage/domains/traces/index.d.ts +28 -0
  19. package/dist/storage/domains/traces/index.d.ts.map +1 -0
  20. package/dist/storage/domains/utils.d.ts +12 -0
  21. package/dist/storage/domains/utils.d.ts.map +1 -0
  22. package/dist/storage/domains/workflows/index.d.ts +36 -0
  23. package/dist/storage/domains/workflows/index.d.ts.map +1 -0
  24. package/dist/storage/index.d.ts +208 -0
  25. package/dist/storage/index.d.ts.map +1 -0
  26. package/dist/vector/filter.d.ts +21 -0
  27. package/dist/vector/filter.d.ts.map +1 -0
  28. package/dist/vector/index.d.ts +79 -0
  29. package/dist/vector/index.d.ts.map +1 -0
  30. package/dist/vector/prompt.d.ts +6 -0
  31. package/dist/vector/prompt.d.ts.map +1 -0
  32. package/dist/vector/types.d.ts +23 -0
  33. package/dist/vector/types.d.ts.map +1 -0
  34. package/docker-compose.yaml +1 -1
  35. package/package.json +12 -12
  36. package/src/storage/domains/legacy-evals/index.ts +279 -0
  37. package/src/storage/domains/memory/index.ts +972 -0
  38. package/src/storage/domains/operations/index.ts +168 -0
  39. package/src/storage/domains/scores/index.ts +216 -0
  40. package/src/storage/domains/traces/index.ts +172 -0
  41. package/src/storage/domains/utils.ts +57 -0
  42. package/src/storage/domains/workflows/index.ts +243 -0
  43. package/src/storage/index.test.ts +13 -0
  44. package/src/storage/index.ts +149 -1078
  45. package/src/vector/filter.test.ts +7 -6
  46. package/src/vector/filter.ts +10 -4
  47. package/src/vector/hybrid.test.ts +1455 -0
  48. package/src/vector/index.test.ts +4 -4
  49. package/src/vector/index.ts +155 -69
  50. package/src/vector/types.ts +26 -0
  51. package/tsconfig.build.json +9 -0
  52. package/tsconfig.json +1 -1
  53. package/tsup.config.ts +22 -0
  54. package/dist/_tsup-dts-rollup.d.cts +0 -318
  55. package/dist/_tsup-dts-rollup.d.ts +0 -318
  56. package/dist/index.d.cts +0 -4
  57. package/src/storage/upstash.test.ts +0 -1386
package/dist/index.js CHANGED
@@ -1,156 +1,49 @@
1
- import { MessageList } from '@mastra/core/agent';
2
- import { MastraStorage, TABLE_MESSAGES, TABLE_WORKFLOW_SNAPSHOT, TABLE_EVALS, TABLE_TRACES, TABLE_THREADS, TABLE_RESOURCES } from '@mastra/core/storage';
1
+ import { MastraStorage, StoreOperations, TracesStorage, TABLE_TRACES, ScoresStorage, TABLE_SCORERS, WorkflowsStorage, TABLE_WORKFLOW_SNAPSHOT, MemoryStorage, TABLE_THREADS, resolveMessageLimit, TABLE_RESOURCES, LegacyEvalsStorage, TABLE_EVALS, TABLE_MESSAGES, serializeDate } from '@mastra/core/storage';
3
2
  import { Redis } from '@upstash/redis';
3
+ import { MastraError, ErrorCategory, ErrorDomain } from '@mastra/core/error';
4
+ import { MessageList } from '@mastra/core/agent';
4
5
  import { MastraVector } from '@mastra/core/vector';
5
6
  import { Index } from '@upstash/vector';
6
7
  import { BaseFilterTranslator } from '@mastra/core/vector/filter';
7
8
 
8
9
  // src/storage/index.ts
9
- var UpstashStore = class extends MastraStorage {
10
- redis;
11
- constructor(config) {
12
- super({ name: "Upstash" });
13
- this.redis = new Redis({
14
- url: config.url,
15
- token: config.token
16
- });
17
- }
18
- get supports() {
19
- return {
20
- selectByIncludeResourceScope: true,
21
- resourceWorkingMemory: true
22
- };
23
- }
24
- transformEvalRecord(record) {
25
- let result = record.result;
26
- if (typeof result === "string") {
27
- try {
28
- result = JSON.parse(result);
29
- } catch {
30
- console.warn("Failed to parse result JSON:");
31
- }
32
- }
33
- let testInfo = record.test_info;
34
- if (typeof testInfo === "string") {
35
- try {
36
- testInfo = JSON.parse(testInfo);
37
- } catch {
38
- console.warn("Failed to parse test_info JSON:");
39
- }
40
- }
41
- return {
42
- agentName: record.agent_name,
43
- input: record.input,
44
- output: record.output,
45
- result,
46
- metricName: record.metric_name,
47
- instructions: record.instructions,
48
- testInfo,
49
- globalRunId: record.global_run_id,
50
- runId: record.run_id,
51
- createdAt: typeof record.created_at === "string" ? record.created_at : record.created_at instanceof Date ? record.created_at.toISOString() : (/* @__PURE__ */ new Date()).toISOString()
52
- };
53
- }
54
- parseJSON(value) {
55
- if (typeof value === "string") {
56
- try {
57
- return JSON.parse(value);
58
- } catch {
59
- return value;
60
- }
10
+ function transformEvalRecord(record) {
11
+ let result = record.result;
12
+ if (typeof result === "string") {
13
+ try {
14
+ result = JSON.parse(result);
15
+ } catch {
16
+ console.warn("Failed to parse result JSON:");
61
17
  }
62
- return value;
63
- }
64
- getKey(tableName, keys) {
65
- const keyParts = Object.entries(keys).filter(([_, value]) => value !== void 0).map(([key, value]) => `${key}:${value}`);
66
- return `${tableName}:${keyParts.join(":")}`;
67
- }
68
- /**
69
- * Scans for keys matching the given pattern using SCAN and returns them as an array.
70
- * @param pattern Redis key pattern, e.g. "table:*"
71
- * @param batchSize Number of keys to scan per batch (default: 1000)
72
- */
73
- async scanKeys(pattern, batchSize = 1e4) {
74
- let cursor = "0";
75
- let keys = [];
76
- do {
77
- const [nextCursor, batch] = await this.redis.scan(cursor, {
78
- match: pattern,
79
- count: batchSize
80
- });
81
- keys.push(...batch);
82
- cursor = nextCursor;
83
- } while (cursor !== "0");
84
- return keys;
85
- }
86
- /**
87
- * Deletes all keys matching the given pattern using SCAN and DEL in batches.
88
- * @param pattern Redis key pattern, e.g. "table:*"
89
- * @param batchSize Number of keys to delete per batch (default: 1000)
90
- */
91
- async scanAndDelete(pattern, batchSize = 1e4) {
92
- let cursor = "0";
93
- let totalDeleted = 0;
94
- do {
95
- const [nextCursor, keys] = await this.redis.scan(cursor, {
96
- match: pattern,
97
- count: batchSize
98
- });
99
- if (keys.length > 0) {
100
- await this.redis.del(...keys);
101
- totalDeleted += keys.length;
102
- }
103
- cursor = nextCursor;
104
- } while (cursor !== "0");
105
- return totalDeleted;
106
- }
107
- getMessageKey(threadId, messageId) {
108
- const key = this.getKey(TABLE_MESSAGES, { threadId, id: messageId });
109
- return key;
110
18
  }
111
- getThreadMessagesKey(threadId) {
112
- return `thread:${threadId}:messages`;
113
- }
114
- parseWorkflowRun(row) {
115
- let parsedSnapshot = row.snapshot;
116
- if (typeof parsedSnapshot === "string") {
117
- try {
118
- parsedSnapshot = JSON.parse(row.snapshot);
119
- } catch (e) {
120
- console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
121
- }
19
+ let testInfo = record.test_info;
20
+ if (typeof testInfo === "string") {
21
+ try {
22
+ testInfo = JSON.parse(testInfo);
23
+ } catch {
24
+ console.warn("Failed to parse test_info JSON:");
122
25
  }
123
- return {
124
- workflowName: row.workflow_name,
125
- runId: row.run_id,
126
- snapshot: parsedSnapshot,
127
- createdAt: this.ensureDate(row.createdAt),
128
- updatedAt: this.ensureDate(row.updatedAt),
129
- resourceId: row.resourceId
130
- };
131
26
  }
132
- processRecord(tableName, record) {
133
- let key;
134
- if (tableName === TABLE_MESSAGES) {
135
- key = this.getKey(tableName, { threadId: record.threadId, id: record.id });
136
- } else if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
137
- key = this.getKey(tableName, {
138
- namespace: record.namespace || "workflows",
139
- workflow_name: record.workflow_name,
140
- run_id: record.run_id,
141
- ...record.resourceId ? { resourceId: record.resourceId } : {}
142
- });
143
- } else if (tableName === TABLE_EVALS) {
144
- key = this.getKey(tableName, { id: record.run_id });
145
- } else {
146
- key = this.getKey(tableName, { id: record.id });
147
- }
148
- const processedRecord = {
149
- ...record,
150
- createdAt: this.serializeDate(record.createdAt),
151
- updatedAt: this.serializeDate(record.updatedAt)
152
- };
153
- return { key, processedRecord };
27
+ return {
28
+ agentName: record.agent_name,
29
+ input: record.input,
30
+ output: record.output,
31
+ result,
32
+ metricName: record.metric_name,
33
+ instructions: record.instructions,
34
+ testInfo,
35
+ globalRunId: record.global_run_id,
36
+ runId: record.run_id,
37
+ createdAt: typeof record.created_at === "string" ? record.created_at : record.created_at instanceof Date ? record.created_at.toISOString() : (/* @__PURE__ */ new Date()).toISOString()
38
+ };
39
+ }
40
+ var StoreLegacyEvalsUpstash = class extends LegacyEvalsStorage {
41
+ client;
42
+ operations;
43
+ constructor({ client, operations }) {
44
+ super();
45
+ this.client = client;
46
+ this.operations = operations;
154
47
  }
155
48
  /**
156
49
  * @deprecated Use getEvals instead
@@ -158,11 +51,11 @@ var UpstashStore = class extends MastraStorage {
158
51
  async getEvalsByAgentName(agentName, type) {
159
52
  try {
160
53
  const pattern = `${TABLE_EVALS}:*`;
161
- const keys = await this.scanKeys(pattern);
54
+ const keys = await this.operations.scanKeys(pattern);
162
55
  if (keys.length === 0) {
163
56
  return [];
164
57
  }
165
- const pipeline = this.redis.pipeline();
58
+ const pipeline = this.client.pipeline();
166
59
  keys.forEach((key) => pipeline.get(key));
167
60
  const results = await pipeline.exec();
168
61
  const nonNullRecords = results.filter(
@@ -196,170 +89,220 @@ var UpstashStore = class extends MastraStorage {
196
89
  }
197
90
  });
198
91
  }
199
- return filteredEvals.map((record) => this.transformEvalRecord(record));
92
+ return filteredEvals.map((record) => transformEvalRecord(record));
200
93
  } catch (error) {
201
- console.error("Failed to get evals for the specified agent:", error);
94
+ const mastraError = new MastraError(
95
+ {
96
+ id: "STORAGE_UPSTASH_STORAGE_GET_EVALS_BY_AGENT_NAME_FAILED",
97
+ domain: ErrorDomain.STORAGE,
98
+ category: ErrorCategory.THIRD_PARTY,
99
+ details: { agentName }
100
+ },
101
+ error
102
+ );
103
+ this.logger?.trackException(mastraError);
104
+ this.logger.error(mastraError.toString());
202
105
  return [];
203
106
  }
204
107
  }
205
108
  /**
206
- * @deprecated use getTracesPaginated instead
109
+ * Get all evaluations with pagination and total count
110
+ * @param options Pagination and filtering options
111
+ * @returns Object with evals array and total count
207
112
  */
208
- async getTraces(args) {
209
- if (args.fromDate || args.toDate) {
210
- args.dateRange = {
211
- start: args.fromDate,
212
- end: args.toDate
213
- };
214
- }
215
- const { traces } = await this.getTracesPaginated(args);
216
- return traces;
217
- }
218
- async getTracesPaginated(args) {
219
- const { name, scope, page = 0, perPage = 100, attributes, filters, dateRange } = args;
220
- const fromDate = dateRange?.start;
221
- const toDate = dateRange?.end;
113
+ async getEvals(options) {
222
114
  try {
223
- const pattern = `${TABLE_TRACES}:*`;
224
- const keys = await this.scanKeys(pattern);
115
+ const { agentName, type, page = 0, perPage = 100, dateRange } = options || {};
116
+ const fromDate = dateRange?.start;
117
+ const toDate = dateRange?.end;
118
+ const pattern = `${TABLE_EVALS}:*`;
119
+ const keys = await this.operations.scanKeys(pattern);
225
120
  if (keys.length === 0) {
226
121
  return {
227
- traces: [],
122
+ evals: [],
228
123
  total: 0,
229
124
  page,
230
- perPage: perPage || 100,
125
+ perPage,
231
126
  hasMore: false
232
127
  };
233
128
  }
234
- const pipeline = this.redis.pipeline();
129
+ const pipeline = this.client.pipeline();
235
130
  keys.forEach((key) => pipeline.get(key));
236
131
  const results = await pipeline.exec();
237
- let filteredTraces = results.filter(
238
- (record) => record !== null && typeof record === "object"
239
- );
240
- if (name) {
241
- filteredTraces = filteredTraces.filter((record) => record.name?.toLowerCase().startsWith(name.toLowerCase()));
242
- }
243
- if (scope) {
244
- filteredTraces = filteredTraces.filter((record) => record.scope === scope);
132
+ let filteredEvals = results.map((result) => result).filter((record) => record !== null && typeof record === "object");
133
+ if (agentName) {
134
+ filteredEvals = filteredEvals.filter((record) => record.agent_name === agentName);
245
135
  }
246
- if (attributes) {
247
- filteredTraces = filteredTraces.filter((record) => {
248
- const recordAttributes = record.attributes;
249
- if (!recordAttributes) return false;
250
- const parsedAttributes = typeof recordAttributes === "string" ? JSON.parse(recordAttributes) : recordAttributes;
251
- return Object.entries(attributes).every(([key, value]) => parsedAttributes[key] === value);
136
+ if (type === "test") {
137
+ filteredEvals = filteredEvals.filter((record) => {
138
+ if (!record.test_info) return false;
139
+ try {
140
+ if (typeof record.test_info === "string") {
141
+ const parsedTestInfo = JSON.parse(record.test_info);
142
+ return parsedTestInfo && typeof parsedTestInfo === "object" && "testPath" in parsedTestInfo;
143
+ }
144
+ return typeof record.test_info === "object" && "testPath" in record.test_info;
145
+ } catch {
146
+ return false;
147
+ }
148
+ });
149
+ } else if (type === "live") {
150
+ filteredEvals = filteredEvals.filter((record) => {
151
+ if (!record.test_info) return true;
152
+ try {
153
+ if (typeof record.test_info === "string") {
154
+ const parsedTestInfo = JSON.parse(record.test_info);
155
+ return !(parsedTestInfo && typeof parsedTestInfo === "object" && "testPath" in parsedTestInfo);
156
+ }
157
+ return !(typeof record.test_info === "object" && "testPath" in record.test_info);
158
+ } catch {
159
+ return true;
160
+ }
252
161
  });
253
- }
254
- if (filters) {
255
- filteredTraces = filteredTraces.filter(
256
- (record) => Object.entries(filters).every(([key, value]) => record[key] === value)
257
- );
258
162
  }
259
163
  if (fromDate) {
260
- filteredTraces = filteredTraces.filter(
261
- (record) => new Date(record.createdAt).getTime() >= new Date(fromDate).getTime()
262
- );
164
+ filteredEvals = filteredEvals.filter((record) => {
165
+ const createdAt = new Date(record.created_at || record.createdAt || 0);
166
+ return createdAt.getTime() >= fromDate.getTime();
167
+ });
263
168
  }
264
169
  if (toDate) {
265
- filteredTraces = filteredTraces.filter(
266
- (record) => new Date(record.createdAt).getTime() <= new Date(toDate).getTime()
267
- );
170
+ filteredEvals = filteredEvals.filter((record) => {
171
+ const createdAt = new Date(record.created_at || record.createdAt || 0);
172
+ return createdAt.getTime() <= toDate.getTime();
173
+ });
268
174
  }
269
- filteredTraces.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
270
- const transformedTraces = filteredTraces.map((record) => ({
271
- id: record.id,
272
- parentSpanId: record.parentSpanId,
273
- traceId: record.traceId,
274
- name: record.name,
275
- scope: record.scope,
276
- kind: record.kind,
277
- status: this.parseJSON(record.status),
278
- events: this.parseJSON(record.events),
279
- links: this.parseJSON(record.links),
280
- attributes: this.parseJSON(record.attributes),
281
- startTime: record.startTime,
282
- endTime: record.endTime,
283
- other: this.parseJSON(record.other),
284
- createdAt: this.ensureDate(record.createdAt)
285
- }));
286
- const total = transformedTraces.length;
287
- const resolvedPerPage = perPage || 100;
288
- const start = page * resolvedPerPage;
289
- const end = start + resolvedPerPage;
290
- const paginatedTraces = transformedTraces.slice(start, end);
175
+ filteredEvals.sort((a, b) => {
176
+ const dateA = new Date(a.created_at || a.createdAt || 0).getTime();
177
+ const dateB = new Date(b.created_at || b.createdAt || 0).getTime();
178
+ return dateB - dateA;
179
+ });
180
+ const total = filteredEvals.length;
181
+ const start = page * perPage;
182
+ const end = start + perPage;
183
+ const paginatedEvals = filteredEvals.slice(start, end);
291
184
  const hasMore = end < total;
185
+ const evals = paginatedEvals.map((record) => transformEvalRecord(record));
292
186
  return {
293
- traces: paginatedTraces,
187
+ evals,
294
188
  total,
295
189
  page,
296
- perPage: resolvedPerPage,
190
+ perPage,
297
191
  hasMore
298
192
  };
299
193
  } catch (error) {
300
- console.error("Failed to get traces:", error);
194
+ const { page = 0, perPage = 100 } = options || {};
195
+ const mastraError = new MastraError(
196
+ {
197
+ id: "STORAGE_UPSTASH_STORAGE_GET_EVALS_FAILED",
198
+ domain: ErrorDomain.STORAGE,
199
+ category: ErrorCategory.THIRD_PARTY,
200
+ details: {
201
+ page,
202
+ perPage
203
+ }
204
+ },
205
+ error
206
+ );
207
+ this.logger.error(mastraError.toString());
208
+ this.logger?.trackException(mastraError);
301
209
  return {
302
- traces: [],
210
+ evals: [],
303
211
  total: 0,
304
212
  page,
305
- perPage: perPage || 100,
213
+ perPage,
306
214
  hasMore: false
307
215
  };
308
216
  }
309
217
  }
310
- async createTable({
311
- tableName,
312
- schema
313
- }) {
314
- await this.redis.set(`schema:${tableName}`, schema);
315
- }
316
- /**
317
- * No-op: This backend is schemaless and does not require schema changes.
318
- * @param tableName Name of the table
319
- * @param schema Schema of the table
320
- * @param ifNotExists Array of column names to add if they don't exist
321
- */
322
- async alterTable(_args) {
323
- }
324
- async clearTable({ tableName }) {
325
- const pattern = `${tableName}:*`;
326
- await this.scanAndDelete(pattern);
327
- }
328
- async insert({ tableName, record }) {
329
- const { key, processedRecord } = this.processRecord(tableName, record);
330
- await this.redis.set(key, processedRecord);
331
- }
332
- async batchInsert(input) {
333
- const { tableName, records } = input;
334
- if (!records.length) return;
335
- const batchSize = 1e3;
336
- for (let i = 0; i < records.length; i += batchSize) {
337
- const batch = records.slice(i, i + batchSize);
338
- const pipeline = this.redis.pipeline();
339
- for (const record of batch) {
340
- const { key, processedRecord } = this.processRecord(tableName, record);
341
- pipeline.set(key, processedRecord);
342
- }
343
- await pipeline.exec();
218
+ };
219
+ function ensureDate(value) {
220
+ if (!value) return null;
221
+ if (value instanceof Date) return value;
222
+ if (typeof value === "string") return new Date(value);
223
+ if (typeof value === "number") return new Date(value);
224
+ return null;
225
+ }
226
+ function parseJSON(value) {
227
+ if (typeof value === "string") {
228
+ try {
229
+ return JSON.parse(value);
230
+ } catch {
231
+ return value;
344
232
  }
345
233
  }
346
- async load({ tableName, keys }) {
347
- const key = this.getKey(tableName, keys);
348
- const data = await this.redis.get(key);
349
- return data || null;
234
+ return value;
235
+ }
236
+ function getKey(tableName, keys) {
237
+ const keyParts = Object.entries(keys).filter(([_, value]) => value !== void 0).map(([key, value]) => `${key}:${value}`);
238
+ return `${tableName}:${keyParts.join(":")}`;
239
+ }
240
+ function processRecord(tableName, record) {
241
+ let key;
242
+ if (tableName === TABLE_MESSAGES) {
243
+ key = getKey(tableName, { threadId: record.threadId, id: record.id });
244
+ } else if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
245
+ key = getKey(tableName, {
246
+ namespace: record.namespace || "workflows",
247
+ workflow_name: record.workflow_name,
248
+ run_id: record.run_id,
249
+ ...record.resourceId ? { resourceId: record.resourceId } : {}
250
+ });
251
+ } else if (tableName === TABLE_EVALS) {
252
+ key = getKey(tableName, { id: record.run_id });
253
+ } else {
254
+ key = getKey(tableName, { id: record.id });
255
+ }
256
+ const processedRecord = {
257
+ ...record,
258
+ createdAt: serializeDate(record.createdAt),
259
+ updatedAt: serializeDate(record.updatedAt)
260
+ };
261
+ return { key, processedRecord };
262
+ }
263
+
264
+ // src/storage/domains/memory/index.ts
265
+ function getThreadMessagesKey(threadId) {
266
+ return `thread:${threadId}:messages`;
267
+ }
268
+ function getMessageKey(threadId, messageId) {
269
+ const key = getKey(TABLE_MESSAGES, { threadId, id: messageId });
270
+ return key;
271
+ }
272
+ var StoreMemoryUpstash = class extends MemoryStorage {
273
+ client;
274
+ operations;
275
+ constructor({ client, operations }) {
276
+ super();
277
+ this.client = client;
278
+ this.operations = operations;
350
279
  }
351
280
  async getThreadById({ threadId }) {
352
- const thread = await this.load({
353
- tableName: TABLE_THREADS,
354
- keys: { id: threadId }
355
- });
356
- if (!thread) return null;
357
- return {
358
- ...thread,
359
- createdAt: this.ensureDate(thread.createdAt),
360
- updatedAt: this.ensureDate(thread.updatedAt),
361
- metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata
362
- };
281
+ try {
282
+ const thread = await this.operations.load({
283
+ tableName: TABLE_THREADS,
284
+ keys: { id: threadId }
285
+ });
286
+ if (!thread) return null;
287
+ return {
288
+ ...thread,
289
+ createdAt: ensureDate(thread.createdAt),
290
+ updatedAt: ensureDate(thread.updatedAt),
291
+ metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata
292
+ };
293
+ } catch (error) {
294
+ throw new MastraError(
295
+ {
296
+ id: "STORAGE_UPSTASH_STORAGE_GET_THREAD_BY_ID_FAILED",
297
+ domain: ErrorDomain.STORAGE,
298
+ category: ErrorCategory.THIRD_PARTY,
299
+ details: {
300
+ threadId
301
+ }
302
+ },
303
+ error
304
+ );
305
+ }
363
306
  }
364
307
  /**
365
308
  * @deprecated use getThreadsByResourceIdPaginated instead
@@ -367,12 +310,12 @@ var UpstashStore = class extends MastraStorage {
367
310
  async getThreadsByResourceId({ resourceId }) {
368
311
  try {
369
312
  const pattern = `${TABLE_THREADS}:*`;
370
- const keys = await this.scanKeys(pattern);
313
+ const keys = await this.operations.scanKeys(pattern);
371
314
  if (keys.length === 0) {
372
315
  return [];
373
316
  }
374
317
  const allThreads = [];
375
- const pipeline = this.redis.pipeline();
318
+ const pipeline = this.client.pipeline();
376
319
  keys.forEach((key) => pipeline.get(key));
377
320
  const results = await pipeline.exec();
378
321
  for (let i = 0; i < results.length; i++) {
@@ -380,8 +323,8 @@ var UpstashStore = class extends MastraStorage {
380
323
  if (thread && thread.resourceId === resourceId) {
381
324
  allThreads.push({
382
325
  ...thread,
383
- createdAt: this.ensureDate(thread.createdAt),
384
- updatedAt: this.ensureDate(thread.updatedAt),
326
+ createdAt: ensureDate(thread.createdAt),
327
+ updatedAt: ensureDate(thread.updatedAt),
385
328
  metadata: typeof thread.metadata === "string" ? JSON.parse(thread.metadata) : thread.metadata
386
329
  });
387
330
  }
@@ -389,7 +332,19 @@ var UpstashStore = class extends MastraStorage {
389
332
  allThreads.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
390
333
  return allThreads;
391
334
  } catch (error) {
392
- console.error("Error in getThreadsByResourceId:", error);
335
+ const mastraError = new MastraError(
336
+ {
337
+ id: "STORAGE_UPSTASH_STORAGE_GET_THREADS_BY_RESOURCE_ID_FAILED",
338
+ domain: ErrorDomain.STORAGE,
339
+ category: ErrorCategory.THIRD_PARTY,
340
+ details: {
341
+ resourceId
342
+ }
343
+ },
344
+ error
345
+ );
346
+ this.logger?.trackException(mastraError);
347
+ this.logger.error(mastraError.toString());
393
348
  return [];
394
349
  }
395
350
  }
@@ -410,7 +365,21 @@ var UpstashStore = class extends MastraStorage {
410
365
  hasMore
411
366
  };
412
367
  } catch (error) {
413
- console.error("Error in getThreadsByResourceIdPaginated:", error);
368
+ const mastraError = new MastraError(
369
+ {
370
+ id: "STORAGE_UPSTASH_STORAGE_GET_THREADS_BY_RESOURCE_ID_PAGINATED_FAILED",
371
+ domain: ErrorDomain.STORAGE,
372
+ category: ErrorCategory.THIRD_PARTY,
373
+ details: {
374
+ resourceId,
375
+ page,
376
+ perPage
377
+ }
378
+ },
379
+ error
380
+ );
381
+ this.logger?.trackException(mastraError);
382
+ this.logger.error(mastraError.toString());
414
383
  return {
415
384
  threads: [],
416
385
  total: 0,
@@ -421,20 +390,45 @@ var UpstashStore = class extends MastraStorage {
421
390
  }
422
391
  }
423
392
  async saveThread({ thread }) {
424
- await this.insert({
425
- tableName: TABLE_THREADS,
426
- record: thread
427
- });
428
- return thread;
429
- }
430
- async updateThread({
431
- id,
393
+ try {
394
+ await this.operations.insert({
395
+ tableName: TABLE_THREADS,
396
+ record: thread
397
+ });
398
+ return thread;
399
+ } catch (error) {
400
+ const mastraError = new MastraError(
401
+ {
402
+ id: "STORAGE_UPSTASH_STORAGE_SAVE_THREAD_FAILED",
403
+ domain: ErrorDomain.STORAGE,
404
+ category: ErrorCategory.THIRD_PARTY,
405
+ details: {
406
+ threadId: thread.id
407
+ }
408
+ },
409
+ error
410
+ );
411
+ this.logger?.trackException(mastraError);
412
+ this.logger.error(mastraError.toString());
413
+ throw mastraError;
414
+ }
415
+ }
416
+ async updateThread({
417
+ id,
432
418
  title,
433
419
  metadata
434
420
  }) {
435
421
  const thread = await this.getThreadById({ threadId: id });
436
422
  if (!thread) {
437
- throw new Error(`Thread ${id} not found`);
423
+ throw new MastraError({
424
+ id: "STORAGE_UPSTASH_STORAGE_UPDATE_THREAD_FAILED",
425
+ domain: ErrorDomain.STORAGE,
426
+ category: ErrorCategory.USER,
427
+ text: `Thread ${id} not found`,
428
+ details: {
429
+ threadId: id
430
+ }
431
+ });
438
432
  }
439
433
  const updatedThread = {
440
434
  ...thread,
@@ -444,67 +438,149 @@ var UpstashStore = class extends MastraStorage {
444
438
  ...metadata
445
439
  }
446
440
  };
447
- await this.saveThread({ thread: updatedThread });
448
- return updatedThread;
441
+ try {
442
+ await this.saveThread({ thread: updatedThread });
443
+ return updatedThread;
444
+ } catch (error) {
445
+ throw new MastraError(
446
+ {
447
+ id: "STORAGE_UPSTASH_STORAGE_UPDATE_THREAD_FAILED",
448
+ domain: ErrorDomain.STORAGE,
449
+ category: ErrorCategory.THIRD_PARTY,
450
+ details: {
451
+ threadId: id
452
+ }
453
+ },
454
+ error
455
+ );
456
+ }
449
457
  }
450
458
  async deleteThread({ threadId }) {
451
- const threadKey = this.getKey(TABLE_THREADS, { id: threadId });
452
- const threadMessagesKey = this.getThreadMessagesKey(threadId);
453
- const messageIds = await this.redis.zrange(threadMessagesKey, 0, -1);
454
- const pipeline = this.redis.pipeline();
455
- pipeline.del(threadKey);
456
- pipeline.del(threadMessagesKey);
457
- for (let i = 0; i < messageIds.length; i++) {
458
- const messageId = messageIds[i];
459
- const messageKey = this.getMessageKey(threadId, messageId);
460
- pipeline.del(messageKey);
461
- }
462
- await pipeline.exec();
463
- await this.scanAndDelete(this.getMessageKey(threadId, "*"));
459
+ const threadKey = getKey(TABLE_THREADS, { id: threadId });
460
+ const threadMessagesKey = getThreadMessagesKey(threadId);
461
+ try {
462
+ const messageIds = await this.client.zrange(threadMessagesKey, 0, -1);
463
+ const pipeline = this.client.pipeline();
464
+ pipeline.del(threadKey);
465
+ pipeline.del(threadMessagesKey);
466
+ for (let i = 0; i < messageIds.length; i++) {
467
+ const messageId = messageIds[i];
468
+ const messageKey = getMessageKey(threadId, messageId);
469
+ pipeline.del(messageKey);
470
+ }
471
+ await pipeline.exec();
472
+ await this.operations.scanAndDelete(getMessageKey(threadId, "*"));
473
+ } catch (error) {
474
+ throw new MastraError(
475
+ {
476
+ id: "STORAGE_UPSTASH_STORAGE_DELETE_THREAD_FAILED",
477
+ domain: ErrorDomain.STORAGE,
478
+ category: ErrorCategory.THIRD_PARTY,
479
+ details: {
480
+ threadId
481
+ }
482
+ },
483
+ error
484
+ );
485
+ }
464
486
  }
465
487
  async saveMessages(args) {
466
488
  const { messages, format = "v1" } = args;
467
489
  if (messages.length === 0) return [];
468
490
  const threadId = messages[0]?.threadId;
469
- if (!threadId) {
470
- throw new Error("Thread ID is required");
471
- }
472
- const thread = await this.getThreadById({ threadId });
473
- if (!thread) {
474
- throw new Error(`Thread ${threadId} not found`);
491
+ try {
492
+ if (!threadId) {
493
+ throw new Error("Thread ID is required");
494
+ }
495
+ const thread = await this.getThreadById({ threadId });
496
+ if (!thread) {
497
+ throw new Error(`Thread ${threadId} not found`);
498
+ }
499
+ } catch (error) {
500
+ throw new MastraError(
501
+ {
502
+ id: "STORAGE_UPSTASH_STORAGE_SAVE_MESSAGES_INVALID_ARGS",
503
+ domain: ErrorDomain.STORAGE,
504
+ category: ErrorCategory.USER
505
+ },
506
+ error
507
+ );
475
508
  }
476
- const messagesWithIndex = messages.map((message, index) => ({
477
- ...message,
478
- _index: index
479
- }));
480
- const threadKey = this.getKey(TABLE_THREADS, { id: threadId });
481
- const existingThread = await this.redis.get(threadKey);
482
- const batchSize = 1e3;
483
- for (let i = 0; i < messagesWithIndex.length; i += batchSize) {
484
- const batch = messagesWithIndex.slice(i, i + batchSize);
485
- const pipeline = this.redis.pipeline();
486
- for (const message of batch) {
487
- const key = this.getMessageKey(message.threadId, message.id);
488
- const createdAtScore = new Date(message.createdAt).getTime();
489
- const score = message._index !== void 0 ? message._index : createdAtScore;
490
- pipeline.set(key, message);
491
- pipeline.zadd(this.getThreadMessagesKey(message.threadId), {
492
- score,
493
- member: message.id
494
- });
509
+ const messagesWithIndex = messages.map((message, index) => {
510
+ if (!message.threadId) {
511
+ throw new Error(
512
+ `Expected to find a threadId for message, but couldn't find one. An unexpected error has occurred.`
513
+ );
495
514
  }
496
- if (i === 0 && existingThread) {
497
- const updatedThread = {
498
- ...existingThread,
499
- updatedAt: /* @__PURE__ */ new Date()
500
- };
501
- pipeline.set(threadKey, this.processRecord(TABLE_THREADS, updatedThread).processedRecord);
515
+ if (!message.resourceId) {
516
+ throw new Error(
517
+ `Expected to find a resourceId for message, but couldn't find one. An unexpected error has occurred.`
518
+ );
502
519
  }
503
- await pipeline.exec();
520
+ return {
521
+ ...message,
522
+ _index: index
523
+ };
524
+ });
525
+ const threadKey = getKey(TABLE_THREADS, { id: threadId });
526
+ const existingThread = await this.client.get(threadKey);
527
+ try {
528
+ const batchSize = 1e3;
529
+ for (let i = 0; i < messagesWithIndex.length; i += batchSize) {
530
+ const batch = messagesWithIndex.slice(i, i + batchSize);
531
+ const pipeline = this.client.pipeline();
532
+ for (const message of batch) {
533
+ const key = getMessageKey(message.threadId, message.id);
534
+ const createdAtScore = new Date(message.createdAt).getTime();
535
+ const score = message._index !== void 0 ? message._index : createdAtScore;
536
+ const existingKeyPattern = getMessageKey("*", message.id);
537
+ const keys = await this.operations.scanKeys(existingKeyPattern);
538
+ if (keys.length > 0) {
539
+ const pipeline2 = this.client.pipeline();
540
+ keys.forEach((key2) => pipeline2.get(key2));
541
+ const results = await pipeline2.exec();
542
+ const existingMessages = results.filter(
543
+ (msg) => msg !== null
544
+ );
545
+ for (const existingMessage of existingMessages) {
546
+ const existingMessageKey = getMessageKey(existingMessage.threadId, existingMessage.id);
547
+ if (existingMessage && existingMessage.threadId !== message.threadId) {
548
+ pipeline.del(existingMessageKey);
549
+ pipeline.zrem(getThreadMessagesKey(existingMessage.threadId), existingMessage.id);
550
+ }
551
+ }
552
+ }
553
+ pipeline.set(key, message);
554
+ pipeline.zadd(getThreadMessagesKey(message.threadId), {
555
+ score,
556
+ member: message.id
557
+ });
558
+ }
559
+ if (i === 0 && existingThread) {
560
+ const updatedThread = {
561
+ ...existingThread,
562
+ updatedAt: /* @__PURE__ */ new Date()
563
+ };
564
+ pipeline.set(threadKey, processRecord(TABLE_THREADS, updatedThread).processedRecord);
565
+ }
566
+ await pipeline.exec();
567
+ }
568
+ const list = new MessageList().add(messages, "memory");
569
+ if (format === `v2`) return list.get.all.v2();
570
+ return list.get.all.v1();
571
+ } catch (error) {
572
+ throw new MastraError(
573
+ {
574
+ id: "STORAGE_UPSTASH_STORAGE_SAVE_MESSAGES_FAILED",
575
+ domain: ErrorDomain.STORAGE,
576
+ category: ErrorCategory.THIRD_PARTY,
577
+ details: {
578
+ threadId
579
+ }
580
+ },
581
+ error
582
+ );
504
583
  }
505
- const list = new MessageList().add(messages, "memory");
506
- if (format === `v2`) return list.get.all.v2();
507
- return list.get.all.v1();
508
584
  }
509
585
  async _getIncludedMessages(threadId, selectBy) {
510
586
  const messageIds = /* @__PURE__ */ new Set();
@@ -514,29 +590,29 @@ var UpstashStore = class extends MastraStorage {
514
590
  messageIds.add(item.id);
515
591
  const itemThreadId = item.threadId || threadId;
516
592
  messageIdToThreadIds[item.id] = itemThreadId;
517
- const itemThreadMessagesKey = this.getThreadMessagesKey(itemThreadId);
518
- const rank = await this.redis.zrank(itemThreadMessagesKey, item.id);
593
+ const itemThreadMessagesKey = getThreadMessagesKey(itemThreadId);
594
+ const rank = await this.client.zrank(itemThreadMessagesKey, item.id);
519
595
  if (rank === null) continue;
520
596
  if (item.withPreviousMessages) {
521
597
  const start = Math.max(0, rank - item.withPreviousMessages);
522
- const prevIds = rank === 0 ? [] : await this.redis.zrange(itemThreadMessagesKey, start, rank - 1);
598
+ const prevIds = rank === 0 ? [] : await this.client.zrange(itemThreadMessagesKey, start, rank - 1);
523
599
  prevIds.forEach((id) => {
524
600
  messageIds.add(id);
525
601
  messageIdToThreadIds[id] = itemThreadId;
526
602
  });
527
603
  }
528
604
  if (item.withNextMessages) {
529
- const nextIds = await this.redis.zrange(itemThreadMessagesKey, rank + 1, rank + item.withNextMessages);
605
+ const nextIds = await this.client.zrange(itemThreadMessagesKey, rank + 1, rank + item.withNextMessages);
530
606
  nextIds.forEach((id) => {
531
607
  messageIds.add(id);
532
608
  messageIdToThreadIds[id] = itemThreadId;
533
609
  });
534
610
  }
535
611
  }
536
- const pipeline = this.redis.pipeline();
612
+ const pipeline = this.client.pipeline();
537
613
  Array.from(messageIds).forEach((id) => {
538
614
  const tId = messageIdToThreadIds[id] || threadId;
539
- pipeline.get(this.getMessageKey(tId, id));
615
+ pipeline.get(getMessageKey(tId, id));
540
616
  });
541
617
  const results = await pipeline.exec();
542
618
  return results.filter((result) => result !== null);
@@ -548,69 +624,91 @@ var UpstashStore = class extends MastraStorage {
548
624
  selectBy,
549
625
  format
550
626
  }) {
551
- const threadMessagesKey = this.getThreadMessagesKey(threadId);
552
- const allMessageIds = await this.redis.zrange(threadMessagesKey, 0, -1);
553
- const limit = this.resolveMessageLimit({ last: selectBy?.last, defaultLimit: Number.MAX_SAFE_INTEGER });
554
- const messageIds = /* @__PURE__ */ new Set();
555
- const messageIdToThreadIds = {};
556
- if (limit === 0 && !selectBy?.include) {
557
- return [];
558
- }
559
- if (limit === Number.MAX_SAFE_INTEGER) {
560
- const allIds = await this.redis.zrange(threadMessagesKey, 0, -1);
561
- allIds.forEach((id) => {
562
- messageIds.add(id);
563
- messageIdToThreadIds[id] = threadId;
627
+ const threadMessagesKey = getThreadMessagesKey(threadId);
628
+ try {
629
+ const allMessageIds = await this.client.zrange(threadMessagesKey, 0, -1);
630
+ const limit = resolveMessageLimit({ last: selectBy?.last, defaultLimit: Number.MAX_SAFE_INTEGER });
631
+ const messageIds = /* @__PURE__ */ new Set();
632
+ const messageIdToThreadIds = {};
633
+ if (limit === 0 && !selectBy?.include) {
634
+ return [];
635
+ }
636
+ if (limit === Number.MAX_SAFE_INTEGER) {
637
+ const allIds = await this.client.zrange(threadMessagesKey, 0, -1);
638
+ allIds.forEach((id) => {
639
+ messageIds.add(id);
640
+ messageIdToThreadIds[id] = threadId;
641
+ });
642
+ } else if (limit > 0) {
643
+ const latestIds = await this.client.zrange(threadMessagesKey, -limit, -1);
644
+ latestIds.forEach((id) => {
645
+ messageIds.add(id);
646
+ messageIdToThreadIds[id] = threadId;
647
+ });
648
+ }
649
+ const includedMessages = await this._getIncludedMessages(threadId, selectBy);
650
+ const messages = [
651
+ ...includedMessages,
652
+ ...(await Promise.all(
653
+ Array.from(messageIds).map(async (id) => {
654
+ const tId = messageIdToThreadIds[id] || threadId;
655
+ const byThreadId = await this.client.get(getMessageKey(tId, id));
656
+ if (byThreadId) return byThreadId;
657
+ return null;
658
+ })
659
+ )).filter((msg) => msg !== null)
660
+ ];
661
+ messages.sort((a, b) => allMessageIds.indexOf(a.id) - allMessageIds.indexOf(b.id));
662
+ const seen = /* @__PURE__ */ new Set();
663
+ const dedupedMessages = messages.filter((row) => {
664
+ if (seen.has(row.id)) return false;
665
+ seen.add(row.id);
666
+ return true;
564
667
  });
565
- } else if (limit > 0) {
566
- const latestIds = await this.redis.zrange(threadMessagesKey, -limit, -1);
567
- latestIds.forEach((id) => {
568
- messageIds.add(id);
569
- messageIdToThreadIds[id] = threadId;
668
+ const prepared = dedupedMessages.filter((message) => message !== null && message !== void 0).map((message) => {
669
+ const { _index, ...messageWithoutIndex } = message;
670
+ return messageWithoutIndex;
570
671
  });
571
- }
572
- const includedMessages = await this._getIncludedMessages(threadId, selectBy);
573
- const messages = [
574
- ...includedMessages,
575
- ...(await Promise.all(
576
- Array.from(messageIds).map(async (id) => {
577
- const tId = messageIdToThreadIds[id] || threadId;
578
- const byThreadId = await this.redis.get(this.getMessageKey(tId, id));
579
- if (byThreadId) return byThreadId;
580
- return null;
581
- })
582
- )).filter((msg) => msg !== null)
583
- ];
584
- messages.sort((a, b) => allMessageIds.indexOf(a.id) - allMessageIds.indexOf(b.id));
585
- const seen = /* @__PURE__ */ new Set();
586
- const dedupedMessages = messages.filter((row) => {
587
- if (seen.has(row.id)) return false;
588
- seen.add(row.id);
589
- return true;
590
- });
591
- const prepared = dedupedMessages.filter((message) => message !== null && message !== void 0).map((message) => {
592
- const { _index, ...messageWithoutIndex } = message;
593
- return messageWithoutIndex;
594
- });
595
- if (format === "v2") {
672
+ if (format === "v2") {
673
+ return prepared.map((msg) => ({
674
+ ...msg,
675
+ createdAt: new Date(msg.createdAt),
676
+ content: msg.content || { format: 2, parts: [{ type: "text", text: "" }] }
677
+ }));
678
+ }
596
679
  return prepared.map((msg) => ({
597
680
  ...msg,
598
- content: msg.content || { format: 2, parts: [{ type: "text", text: "" }] }
681
+ createdAt: new Date(msg.createdAt)
599
682
  }));
683
+ } catch (error) {
684
+ throw new MastraError(
685
+ {
686
+ id: "STORAGE_UPSTASH_STORAGE_GET_MESSAGES_FAILED",
687
+ domain: ErrorDomain.STORAGE,
688
+ category: ErrorCategory.THIRD_PARTY,
689
+ details: {
690
+ threadId
691
+ }
692
+ },
693
+ error
694
+ );
600
695
  }
601
- return prepared;
602
696
  }
603
697
  async getMessagesPaginated(args) {
604
698
  const { threadId, selectBy, format } = args;
605
699
  const { page = 0, perPage = 40, dateRange } = selectBy?.pagination || {};
606
700
  const fromDate = dateRange?.start;
607
701
  const toDate = dateRange?.end;
608
- const threadMessagesKey = this.getThreadMessagesKey(threadId);
702
+ const threadMessagesKey = getThreadMessagesKey(threadId);
609
703
  const messages = [];
610
- const includedMessages = await this._getIncludedMessages(threadId, selectBy);
611
- messages.push(...includedMessages);
612
704
  try {
613
- const allMessageIds = await this.redis.zrange(threadMessagesKey, 0, -1);
705
+ const includedMessages = await this._getIncludedMessages(threadId, selectBy);
706
+ messages.push(...includedMessages);
707
+ const allMessageIds = await this.client.zrange(
708
+ threadMessagesKey,
709
+ args?.selectBy?.last ? -args.selectBy.last : 0,
710
+ -1
711
+ );
614
712
  if (allMessageIds.length === 0) {
615
713
  return {
616
714
  messages: [],
@@ -620,8 +718,8 @@ var UpstashStore = class extends MastraStorage {
620
718
  hasMore: false
621
719
  };
622
720
  }
623
- const pipeline = this.redis.pipeline();
624
- allMessageIds.forEach((id) => pipeline.get(this.getMessageKey(threadId, id)));
721
+ const pipeline = this.client.pipeline();
722
+ allMessageIds.forEach((id) => pipeline.get(getMessageKey(threadId, id)));
625
723
  const results = await pipeline.exec();
626
724
  let messagesData = results.filter((msg) => msg !== null);
627
725
  if (fromDate) {
@@ -647,7 +745,19 @@ var UpstashStore = class extends MastraStorage {
647
745
  hasMore
648
746
  };
649
747
  } catch (error) {
650
- console.error("Failed to get paginated messages:", error);
748
+ const mastraError = new MastraError(
749
+ {
750
+ id: "STORAGE_UPSTASH_STORAGE_GET_MESSAGES_PAGINATED_FAILED",
751
+ domain: ErrorDomain.STORAGE,
752
+ category: ErrorCategory.THIRD_PARTY,
753
+ details: {
754
+ threadId
755
+ }
756
+ },
757
+ error
758
+ );
759
+ this.logger.error(mastraError.toString());
760
+ this.logger?.trackException(mastraError);
651
761
  return {
652
762
  messages: [],
653
763
  total: 0,
@@ -657,161 +767,849 @@ var UpstashStore = class extends MastraStorage {
657
767
  };
658
768
  }
659
769
  }
660
- async persistWorkflowSnapshot(params) {
661
- const { namespace = "workflows", workflowName, runId, snapshot } = params;
662
- await this.insert({
663
- tableName: TABLE_WORKFLOW_SNAPSHOT,
664
- record: {
665
- namespace,
666
- workflow_name: workflowName,
667
- run_id: runId,
668
- snapshot,
669
- createdAt: /* @__PURE__ */ new Date(),
670
- updatedAt: /* @__PURE__ */ new Date()
770
+ async getResourceById({ resourceId }) {
771
+ try {
772
+ const key = `${TABLE_RESOURCES}:${resourceId}`;
773
+ const data = await this.client.get(key);
774
+ if (!data) {
775
+ return null;
671
776
  }
672
- });
777
+ return {
778
+ ...data,
779
+ createdAt: new Date(data.createdAt),
780
+ updatedAt: new Date(data.updatedAt),
781
+ // Ensure workingMemory is always returned as a string, regardless of automatic parsing
782
+ workingMemory: typeof data.workingMemory === "object" ? JSON.stringify(data.workingMemory) : data.workingMemory,
783
+ metadata: typeof data.metadata === "string" ? JSON.parse(data.metadata) : data.metadata
784
+ };
785
+ } catch (error) {
786
+ this.logger.error("Error getting resource by ID:", error);
787
+ throw error;
788
+ }
673
789
  }
674
- async loadWorkflowSnapshot(params) {
675
- const { namespace = "workflows", workflowName, runId } = params;
676
- const key = this.getKey(TABLE_WORKFLOW_SNAPSHOT, {
677
- namespace,
678
- workflow_name: workflowName,
679
- run_id: runId
680
- });
681
- const data = await this.redis.get(key);
682
- if (!data) return null;
683
- return data.snapshot;
790
+ async saveResource({ resource }) {
791
+ try {
792
+ const key = `${TABLE_RESOURCES}:${resource.id}`;
793
+ const serializedResource = {
794
+ ...resource,
795
+ metadata: JSON.stringify(resource.metadata),
796
+ createdAt: resource.createdAt.toISOString(),
797
+ updatedAt: resource.updatedAt.toISOString()
798
+ };
799
+ await this.client.set(key, serializedResource);
800
+ return resource;
801
+ } catch (error) {
802
+ this.logger.error("Error saving resource:", error);
803
+ throw error;
804
+ }
684
805
  }
685
- /**
686
- * Get all evaluations with pagination and total count
687
- * @param options Pagination and filtering options
688
- * @returns Object with evals array and total count
689
- */
690
- async getEvals(options) {
806
+ async updateResource({
807
+ resourceId,
808
+ workingMemory,
809
+ metadata
810
+ }) {
691
811
  try {
692
- const { agentName, type, page = 0, perPage = 100, dateRange } = options || {};
693
- const fromDate = dateRange?.start;
694
- const toDate = dateRange?.end;
695
- const pattern = `${TABLE_EVALS}:*`;
696
- const keys = await this.scanKeys(pattern);
697
- if (keys.length === 0) {
698
- return {
699
- evals: [],
700
- total: 0,
701
- page,
702
- perPage,
703
- hasMore: false
812
+ const existingResource = await this.getResourceById({ resourceId });
813
+ if (!existingResource) {
814
+ const newResource = {
815
+ id: resourceId,
816
+ workingMemory,
817
+ metadata: metadata || {},
818
+ createdAt: /* @__PURE__ */ new Date(),
819
+ updatedAt: /* @__PURE__ */ new Date()
704
820
  };
821
+ return this.saveResource({ resource: newResource });
705
822
  }
706
- const pipeline = this.redis.pipeline();
707
- keys.forEach((key) => pipeline.get(key));
708
- const results = await pipeline.exec();
709
- let filteredEvals = results.map((result) => result).filter((record) => record !== null && typeof record === "object");
710
- if (agentName) {
711
- filteredEvals = filteredEvals.filter((record) => record.agent_name === agentName);
823
+ const updatedResource = {
824
+ ...existingResource,
825
+ workingMemory: workingMemory !== void 0 ? workingMemory : existingResource.workingMemory,
826
+ metadata: {
827
+ ...existingResource.metadata,
828
+ ...metadata
829
+ },
830
+ updatedAt: /* @__PURE__ */ new Date()
831
+ };
832
+ await this.saveResource({ resource: updatedResource });
833
+ return updatedResource;
834
+ } catch (error) {
835
+ this.logger.error("Error updating resource:", error);
836
+ throw error;
837
+ }
838
+ }
839
+ async updateMessages(args) {
840
+ const { messages } = args;
841
+ if (messages.length === 0) {
842
+ return [];
843
+ }
844
+ try {
845
+ const messageIds = messages.map((m) => m.id);
846
+ const existingMessages = [];
847
+ const messageIdToKey = {};
848
+ for (const messageId of messageIds) {
849
+ const pattern = getMessageKey("*", messageId);
850
+ const keys = await this.operations.scanKeys(pattern);
851
+ for (const key of keys) {
852
+ const message = await this.client.get(key);
853
+ if (message && message.id === messageId) {
854
+ existingMessages.push(message);
855
+ messageIdToKey[messageId] = key;
856
+ break;
857
+ }
858
+ }
712
859
  }
713
- if (type === "test") {
714
- filteredEvals = filteredEvals.filter((record) => {
715
- if (!record.test_info) return false;
716
- try {
717
- if (typeof record.test_info === "string") {
718
- const parsedTestInfo = JSON.parse(record.test_info);
719
- return parsedTestInfo && typeof parsedTestInfo === "object" && "testPath" in parsedTestInfo;
720
- }
721
- return typeof record.test_info === "object" && "testPath" in record.test_info;
722
- } catch {
723
- return false;
860
+ if (existingMessages.length === 0) {
861
+ return [];
862
+ }
863
+ const threadIdsToUpdate = /* @__PURE__ */ new Set();
864
+ const pipeline = this.client.pipeline();
865
+ for (const existingMessage of existingMessages) {
866
+ const updatePayload = messages.find((m) => m.id === existingMessage.id);
867
+ if (!updatePayload) continue;
868
+ const { id, ...fieldsToUpdate } = updatePayload;
869
+ if (Object.keys(fieldsToUpdate).length === 0) continue;
870
+ threadIdsToUpdate.add(existingMessage.threadId);
871
+ if (updatePayload.threadId && updatePayload.threadId !== existingMessage.threadId) {
872
+ threadIdsToUpdate.add(updatePayload.threadId);
873
+ }
874
+ const updatedMessage = { ...existingMessage };
875
+ if (fieldsToUpdate.content) {
876
+ const existingContent = existingMessage.content;
877
+ const newContent = {
878
+ ...existingContent,
879
+ ...fieldsToUpdate.content,
880
+ // Deep merge metadata if it exists on both
881
+ ...existingContent?.metadata && fieldsToUpdate.content.metadata ? {
882
+ metadata: {
883
+ ...existingContent.metadata,
884
+ ...fieldsToUpdate.content.metadata
885
+ }
886
+ } : {}
887
+ };
888
+ updatedMessage.content = newContent;
889
+ }
890
+ for (const key2 in fieldsToUpdate) {
891
+ if (Object.prototype.hasOwnProperty.call(fieldsToUpdate, key2) && key2 !== "content") {
892
+ updatedMessage[key2] = fieldsToUpdate[key2];
724
893
  }
725
- });
726
- } else if (type === "live") {
727
- filteredEvals = filteredEvals.filter((record) => {
728
- if (!record.test_info) return true;
729
- try {
730
- if (typeof record.test_info === "string") {
731
- const parsedTestInfo = JSON.parse(record.test_info);
732
- return !(parsedTestInfo && typeof parsedTestInfo === "object" && "testPath" in parsedTestInfo);
894
+ }
895
+ const key = messageIdToKey[id];
896
+ if (key) {
897
+ if (updatePayload.threadId && updatePayload.threadId !== existingMessage.threadId) {
898
+ const oldThreadMessagesKey = getThreadMessagesKey(existingMessage.threadId);
899
+ pipeline.zrem(oldThreadMessagesKey, id);
900
+ pipeline.del(key);
901
+ const newKey = getMessageKey(updatePayload.threadId, id);
902
+ pipeline.set(newKey, updatedMessage);
903
+ const newThreadMessagesKey = getThreadMessagesKey(updatePayload.threadId);
904
+ const score = updatedMessage._index !== void 0 ? updatedMessage._index : new Date(updatedMessage.createdAt).getTime();
905
+ pipeline.zadd(newThreadMessagesKey, { score, member: id });
906
+ } else {
907
+ pipeline.set(key, updatedMessage);
908
+ }
909
+ }
910
+ }
911
+ const now = /* @__PURE__ */ new Date();
912
+ for (const threadId of threadIdsToUpdate) {
913
+ if (threadId) {
914
+ const threadKey = getKey(TABLE_THREADS, { id: threadId });
915
+ const existingThread = await this.client.get(threadKey);
916
+ if (existingThread) {
917
+ const updatedThread = {
918
+ ...existingThread,
919
+ updatedAt: now
920
+ };
921
+ pipeline.set(threadKey, processRecord(TABLE_THREADS, updatedThread).processedRecord);
922
+ }
923
+ }
924
+ }
925
+ await pipeline.exec();
926
+ const updatedMessages = [];
927
+ for (const messageId of messageIds) {
928
+ const key = messageIdToKey[messageId];
929
+ if (key) {
930
+ const updatedMessage = await this.client.get(key);
931
+ if (updatedMessage) {
932
+ const v2e = updatedMessage;
933
+ updatedMessages.push(v2e);
934
+ }
935
+ }
936
+ }
937
+ return updatedMessages;
938
+ } catch (error) {
939
+ throw new MastraError(
940
+ {
941
+ id: "STORAGE_UPSTASH_STORAGE_UPDATE_MESSAGES_FAILED",
942
+ domain: ErrorDomain.STORAGE,
943
+ category: ErrorCategory.THIRD_PARTY,
944
+ details: {
945
+ messageIds: messages.map((m) => m.id).join(",")
946
+ }
947
+ },
948
+ error
949
+ );
950
+ }
951
+ }
952
+ async deleteMessages(messageIds) {
953
+ if (!messageIds || messageIds.length === 0) {
954
+ return;
955
+ }
956
+ try {
957
+ const threadIds = /* @__PURE__ */ new Set();
958
+ const messageKeys = [];
959
+ for (const messageId of messageIds) {
960
+ const pattern = getMessageKey("*", messageId);
961
+ const keys = await this.operations.scanKeys(pattern);
962
+ for (const key of keys) {
963
+ const message = await this.client.get(key);
964
+ if (message && message.id === messageId) {
965
+ messageKeys.push(key);
966
+ if (message.threadId) {
967
+ threadIds.add(message.threadId);
733
968
  }
734
- return !(typeof record.test_info === "object" && "testPath" in record.test_info);
735
- } catch {
736
- return true;
969
+ break;
737
970
  }
738
- });
971
+ }
739
972
  }
740
- if (fromDate) {
741
- filteredEvals = filteredEvals.filter((record) => {
742
- const createdAt = new Date(record.created_at || record.createdAt || 0);
743
- return createdAt.getTime() >= fromDate.getTime();
744
- });
973
+ if (messageKeys.length === 0) {
974
+ return;
745
975
  }
746
- if (toDate) {
747
- filteredEvals = filteredEvals.filter((record) => {
748
- const createdAt = new Date(record.created_at || record.createdAt || 0);
749
- return createdAt.getTime() <= toDate.getTime();
750
- });
976
+ const pipeline = this.client.pipeline();
977
+ for (const key of messageKeys) {
978
+ pipeline.del(key);
751
979
  }
752
- filteredEvals.sort((a, b) => {
753
- const dateA = new Date(a.created_at || a.createdAt || 0).getTime();
754
- const dateB = new Date(b.created_at || b.createdAt || 0).getTime();
755
- return dateB - dateA;
756
- });
757
- const total = filteredEvals.length;
758
- const start = page * perPage;
759
- const end = start + perPage;
760
- const paginatedEvals = filteredEvals.slice(start, end);
761
- const hasMore = end < total;
762
- const evals = paginatedEvals.map((record) => this.transformEvalRecord(record));
763
- return {
764
- evals,
765
- total,
766
- page,
767
- perPage,
768
- hasMore
769
- };
980
+ if (threadIds.size > 0) {
981
+ for (const threadId of threadIds) {
982
+ const threadKey = getKey(TABLE_THREADS, { id: threadId });
983
+ const thread = await this.client.get(threadKey);
984
+ if (thread) {
985
+ const updatedThread = {
986
+ ...thread,
987
+ updatedAt: /* @__PURE__ */ new Date()
988
+ };
989
+ pipeline.set(threadKey, processRecord(TABLE_THREADS, updatedThread).processedRecord);
990
+ }
991
+ }
992
+ }
993
+ await pipeline.exec();
994
+ } catch (error) {
995
+ throw new MastraError(
996
+ {
997
+ id: "STORAGE_UPSTASH_DELETE_MESSAGES_FAILED",
998
+ domain: ErrorDomain.STORAGE,
999
+ category: ErrorCategory.THIRD_PARTY,
1000
+ details: { messageIds: messageIds.join(", ") }
1001
+ },
1002
+ error
1003
+ );
1004
+ }
1005
+ }
1006
+ };
1007
+ var StoreOperationsUpstash = class extends StoreOperations {
1008
+ client;
1009
+ constructor({ client }) {
1010
+ super();
1011
+ this.client = client;
1012
+ }
1013
+ async createTable({
1014
+ tableName: _tableName,
1015
+ schema: _schema
1016
+ }) {
1017
+ }
1018
+ async alterTable({
1019
+ tableName: _tableName,
1020
+ schema: _schema,
1021
+ ifNotExists: _ifNotExists
1022
+ }) {
1023
+ }
1024
+ async clearTable({ tableName }) {
1025
+ const pattern = `${tableName}:*`;
1026
+ try {
1027
+ await this.scanAndDelete(pattern);
1028
+ } catch (error) {
1029
+ throw new MastraError(
1030
+ {
1031
+ id: "STORAGE_UPSTASH_STORAGE_CLEAR_TABLE_FAILED",
1032
+ domain: ErrorDomain.STORAGE,
1033
+ category: ErrorCategory.THIRD_PARTY,
1034
+ details: {
1035
+ tableName
1036
+ }
1037
+ },
1038
+ error
1039
+ );
1040
+ }
1041
+ }
1042
+ async dropTable({ tableName }) {
1043
+ return this.clearTable({ tableName });
1044
+ }
1045
+ async insert({ tableName, record }) {
1046
+ const { key, processedRecord } = processRecord(tableName, record);
1047
+ try {
1048
+ await this.client.set(key, processedRecord);
1049
+ } catch (error) {
1050
+ throw new MastraError(
1051
+ {
1052
+ id: "STORAGE_UPSTASH_STORAGE_INSERT_FAILED",
1053
+ domain: ErrorDomain.STORAGE,
1054
+ category: ErrorCategory.THIRD_PARTY,
1055
+ details: {
1056
+ tableName
1057
+ }
1058
+ },
1059
+ error
1060
+ );
1061
+ }
1062
+ }
1063
+ async batchInsert(input) {
1064
+ const { tableName, records } = input;
1065
+ if (!records.length) return;
1066
+ const batchSize = 1e3;
1067
+ try {
1068
+ for (let i = 0; i < records.length; i += batchSize) {
1069
+ const batch = records.slice(i, i + batchSize);
1070
+ const pipeline = this.client.pipeline();
1071
+ for (const record of batch) {
1072
+ const { key, processedRecord } = processRecord(tableName, record);
1073
+ pipeline.set(key, processedRecord);
1074
+ }
1075
+ await pipeline.exec();
1076
+ }
1077
+ } catch (error) {
1078
+ throw new MastraError(
1079
+ {
1080
+ id: "STORAGE_UPSTASH_STORAGE_BATCH_INSERT_FAILED",
1081
+ domain: ErrorDomain.STORAGE,
1082
+ category: ErrorCategory.THIRD_PARTY,
1083
+ details: {
1084
+ tableName
1085
+ }
1086
+ },
1087
+ error
1088
+ );
1089
+ }
1090
+ }
1091
+ async load({ tableName, keys }) {
1092
+ const key = getKey(tableName, keys);
1093
+ try {
1094
+ const data = await this.client.get(key);
1095
+ return data || null;
1096
+ } catch (error) {
1097
+ throw new MastraError(
1098
+ {
1099
+ id: "STORAGE_UPSTASH_STORAGE_LOAD_FAILED",
1100
+ domain: ErrorDomain.STORAGE,
1101
+ category: ErrorCategory.THIRD_PARTY,
1102
+ details: {
1103
+ tableName
1104
+ }
1105
+ },
1106
+ error
1107
+ );
1108
+ }
1109
+ }
1110
+ async hasColumn(_tableName, _column) {
1111
+ return true;
1112
+ }
1113
+ async scanKeys(pattern, batchSize = 1e4) {
1114
+ let cursor = "0";
1115
+ let keys = [];
1116
+ do {
1117
+ const [nextCursor, batch] = await this.client.scan(cursor, {
1118
+ match: pattern,
1119
+ count: batchSize
1120
+ });
1121
+ keys.push(...batch);
1122
+ cursor = nextCursor;
1123
+ } while (cursor !== "0");
1124
+ return keys;
1125
+ }
1126
+ async scanAndDelete(pattern, batchSize = 1e4) {
1127
+ let cursor = "0";
1128
+ let totalDeleted = 0;
1129
+ do {
1130
+ const [nextCursor, keys] = await this.client.scan(cursor, {
1131
+ match: pattern,
1132
+ count: batchSize
1133
+ });
1134
+ if (keys.length > 0) {
1135
+ await this.client.del(...keys);
1136
+ totalDeleted += keys.length;
1137
+ }
1138
+ cursor = nextCursor;
1139
+ } while (cursor !== "0");
1140
+ return totalDeleted;
1141
+ }
1142
+ };
1143
+ function transformScoreRow(row) {
1144
+ const parseField = (v) => {
1145
+ if (typeof v === "string") {
1146
+ try {
1147
+ return JSON.parse(v);
1148
+ } catch {
1149
+ return v;
1150
+ }
1151
+ }
1152
+ return v;
1153
+ };
1154
+ return {
1155
+ ...row,
1156
+ scorer: parseField(row.scorer),
1157
+ extractStepResult: parseField(row.extractStepResult),
1158
+ analyzeStepResult: parseField(row.analyzeStepResult),
1159
+ metadata: parseField(row.metadata),
1160
+ input: parseField(row.input),
1161
+ output: parseField(row.output),
1162
+ additionalContext: parseField(row.additionalContext),
1163
+ runtimeContext: parseField(row.runtimeContext),
1164
+ entity: parseField(row.entity),
1165
+ createdAt: row.createdAt,
1166
+ updatedAt: row.updatedAt
1167
+ };
1168
+ }
1169
+ var ScoresUpstash = class extends ScoresStorage {
1170
+ client;
1171
+ operations;
1172
+ constructor({ client, operations }) {
1173
+ super();
1174
+ this.client = client;
1175
+ this.operations = operations;
1176
+ }
1177
+ async getScoreById({ id }) {
1178
+ try {
1179
+ const data = await this.operations.load({
1180
+ tableName: TABLE_SCORERS,
1181
+ keys: { id }
1182
+ });
1183
+ if (!data) return null;
1184
+ return transformScoreRow(data);
1185
+ } catch (error) {
1186
+ throw new MastraError(
1187
+ {
1188
+ id: "STORAGE_UPSTASH_STORAGE_GET_SCORE_BY_ID_FAILED",
1189
+ domain: ErrorDomain.STORAGE,
1190
+ category: ErrorCategory.THIRD_PARTY,
1191
+ details: { id }
1192
+ },
1193
+ error
1194
+ );
1195
+ }
1196
+ }
1197
+ async getScoresByScorerId({
1198
+ scorerId,
1199
+ pagination = { page: 0, perPage: 20 }
1200
+ }) {
1201
+ const pattern = `${TABLE_SCORERS}:*`;
1202
+ const keys = await this.operations.scanKeys(pattern);
1203
+ if (keys.length === 0) {
1204
+ return {
1205
+ scores: [],
1206
+ pagination: { total: 0, page: pagination.page, perPage: pagination.perPage, hasMore: false }
1207
+ };
1208
+ }
1209
+ const pipeline = this.client.pipeline();
1210
+ keys.forEach((key) => pipeline.get(key));
1211
+ const results = await pipeline.exec();
1212
+ const filtered = results.map((row) => row).filter((row) => !!row && typeof row === "object" && row.scorerId === scorerId);
1213
+ const total = filtered.length;
1214
+ const { page, perPage } = pagination;
1215
+ const start = page * perPage;
1216
+ const end = start + perPage;
1217
+ const paged = filtered.slice(start, end);
1218
+ const scores = paged.map((row) => transformScoreRow(row));
1219
+ return {
1220
+ scores,
1221
+ pagination: {
1222
+ total,
1223
+ page,
1224
+ perPage,
1225
+ hasMore: end < total
1226
+ }
1227
+ };
1228
+ }
1229
+ async saveScore(score) {
1230
+ const { key, processedRecord } = processRecord(TABLE_SCORERS, score);
1231
+ try {
1232
+ await this.client.set(key, processedRecord);
1233
+ return { score };
1234
+ } catch (error) {
1235
+ throw new MastraError(
1236
+ {
1237
+ id: "STORAGE_UPSTASH_STORAGE_SAVE_SCORE_FAILED",
1238
+ domain: ErrorDomain.STORAGE,
1239
+ category: ErrorCategory.THIRD_PARTY,
1240
+ details: { id: score.id }
1241
+ },
1242
+ error
1243
+ );
1244
+ }
1245
+ }
1246
+ async getScoresByRunId({
1247
+ runId,
1248
+ pagination = { page: 0, perPage: 20 }
1249
+ }) {
1250
+ const pattern = `${TABLE_SCORERS}:*`;
1251
+ const keys = await this.operations.scanKeys(pattern);
1252
+ if (keys.length === 0) {
1253
+ return {
1254
+ scores: [],
1255
+ pagination: { total: 0, page: pagination.page, perPage: pagination.perPage, hasMore: false }
1256
+ };
1257
+ }
1258
+ const pipeline = this.client.pipeline();
1259
+ keys.forEach((key) => pipeline.get(key));
1260
+ const results = await pipeline.exec();
1261
+ const filtered = results.map((row) => row).filter((row) => !!row && typeof row === "object" && row.runId === runId);
1262
+ const total = filtered.length;
1263
+ const { page, perPage } = pagination;
1264
+ const start = page * perPage;
1265
+ const end = start + perPage;
1266
+ const paged = filtered.slice(start, end);
1267
+ const scores = paged.map((row) => transformScoreRow(row));
1268
+ return {
1269
+ scores,
1270
+ pagination: {
1271
+ total,
1272
+ page,
1273
+ perPage,
1274
+ hasMore: end < total
1275
+ }
1276
+ };
1277
+ }
1278
+ async getScoresByEntityId({
1279
+ entityId,
1280
+ entityType,
1281
+ pagination = { page: 0, perPage: 20 }
1282
+ }) {
1283
+ const pattern = `${TABLE_SCORERS}:*`;
1284
+ const keys = await this.operations.scanKeys(pattern);
1285
+ if (keys.length === 0) {
1286
+ return {
1287
+ scores: [],
1288
+ pagination: { total: 0, page: pagination.page, perPage: pagination.perPage, hasMore: false }
1289
+ };
1290
+ }
1291
+ const pipeline = this.client.pipeline();
1292
+ keys.forEach((key) => pipeline.get(key));
1293
+ const results = await pipeline.exec();
1294
+ const filtered = results.map((row) => row).filter((row) => {
1295
+ if (!row || typeof row !== "object") return false;
1296
+ if (row.entityId !== entityId) return false;
1297
+ if (entityType && row.entityType !== entityType) return false;
1298
+ return true;
1299
+ });
1300
+ const total = filtered.length;
1301
+ const { page, perPage } = pagination;
1302
+ const start = page * perPage;
1303
+ const end = start + perPage;
1304
+ const paged = filtered.slice(start, end);
1305
+ const scores = paged.map((row) => transformScoreRow(row));
1306
+ return {
1307
+ scores,
1308
+ pagination: {
1309
+ total,
1310
+ page,
1311
+ perPage,
1312
+ hasMore: end < total
1313
+ }
1314
+ };
1315
+ }
1316
+ };
1317
+ var TracesUpstash = class extends TracesStorage {
1318
+ client;
1319
+ operations;
1320
+ constructor({ client, operations }) {
1321
+ super();
1322
+ this.client = client;
1323
+ this.operations = operations;
1324
+ }
1325
+ /**
1326
+ * @deprecated use getTracesPaginated instead
1327
+ */
1328
+ async getTraces(args) {
1329
+ if (args.fromDate || args.toDate) {
1330
+ args.dateRange = {
1331
+ start: args.fromDate,
1332
+ end: args.toDate
1333
+ };
1334
+ }
1335
+ try {
1336
+ const { traces } = await this.getTracesPaginated(args);
1337
+ return traces;
1338
+ } catch (error) {
1339
+ throw new MastraError(
1340
+ {
1341
+ id: "STORAGE_UPSTASH_STORAGE_GET_TRACES_FAILED",
1342
+ domain: ErrorDomain.STORAGE,
1343
+ category: ErrorCategory.THIRD_PARTY
1344
+ },
1345
+ error
1346
+ );
1347
+ }
1348
+ }
1349
+ async getTracesPaginated(args) {
1350
+ const { name, scope, page = 0, perPage = 100, attributes, filters, dateRange } = args;
1351
+ const fromDate = dateRange?.start;
1352
+ const toDate = dateRange?.end;
1353
+ try {
1354
+ const pattern = `${TABLE_TRACES}:*`;
1355
+ const keys = await this.operations.scanKeys(pattern);
1356
+ if (keys.length === 0) {
1357
+ return {
1358
+ traces: [],
1359
+ total: 0,
1360
+ page,
1361
+ perPage: perPage || 100,
1362
+ hasMore: false
1363
+ };
1364
+ }
1365
+ const pipeline = this.client.pipeline();
1366
+ keys.forEach((key) => pipeline.get(key));
1367
+ const results = await pipeline.exec();
1368
+ let filteredTraces = results.filter(
1369
+ (record) => record !== null && typeof record === "object"
1370
+ );
1371
+ if (name) {
1372
+ filteredTraces = filteredTraces.filter((record) => record.name?.toLowerCase().startsWith(name.toLowerCase()));
1373
+ }
1374
+ if (scope) {
1375
+ filteredTraces = filteredTraces.filter((record) => record.scope === scope);
1376
+ }
1377
+ if (attributes) {
1378
+ filteredTraces = filteredTraces.filter((record) => {
1379
+ const recordAttributes = record.attributes;
1380
+ if (!recordAttributes) return false;
1381
+ const parsedAttributes = typeof recordAttributes === "string" ? JSON.parse(recordAttributes) : recordAttributes;
1382
+ return Object.entries(attributes).every(([key, value]) => parsedAttributes[key] === value);
1383
+ });
1384
+ }
1385
+ if (filters) {
1386
+ filteredTraces = filteredTraces.filter(
1387
+ (record) => Object.entries(filters).every(([key, value]) => record[key] === value)
1388
+ );
1389
+ }
1390
+ if (fromDate) {
1391
+ filteredTraces = filteredTraces.filter(
1392
+ (record) => new Date(record.createdAt).getTime() >= new Date(fromDate).getTime()
1393
+ );
1394
+ }
1395
+ if (toDate) {
1396
+ filteredTraces = filteredTraces.filter(
1397
+ (record) => new Date(record.createdAt).getTime() <= new Date(toDate).getTime()
1398
+ );
1399
+ }
1400
+ filteredTraces.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
1401
+ const transformedTraces = filteredTraces.map((record) => ({
1402
+ id: record.id,
1403
+ parentSpanId: record.parentSpanId,
1404
+ traceId: record.traceId,
1405
+ name: record.name,
1406
+ scope: record.scope,
1407
+ kind: record.kind,
1408
+ status: parseJSON(record.status),
1409
+ events: parseJSON(record.events),
1410
+ links: parseJSON(record.links),
1411
+ attributes: parseJSON(record.attributes),
1412
+ startTime: record.startTime,
1413
+ endTime: record.endTime,
1414
+ other: parseJSON(record.other),
1415
+ createdAt: ensureDate(record.createdAt)
1416
+ }));
1417
+ const total = transformedTraces.length;
1418
+ const resolvedPerPage = perPage || 100;
1419
+ const start = page * resolvedPerPage;
1420
+ const end = start + resolvedPerPage;
1421
+ const paginatedTraces = transformedTraces.slice(start, end);
1422
+ const hasMore = end < total;
1423
+ return {
1424
+ traces: paginatedTraces,
1425
+ total,
1426
+ page,
1427
+ perPage: resolvedPerPage,
1428
+ hasMore
1429
+ };
1430
+ } catch (error) {
1431
+ const mastraError = new MastraError(
1432
+ {
1433
+ id: "STORAGE_UPSTASH_STORAGE_GET_TRACES_PAGINATED_FAILED",
1434
+ domain: ErrorDomain.STORAGE,
1435
+ category: ErrorCategory.THIRD_PARTY,
1436
+ details: {
1437
+ name: args.name || "",
1438
+ scope: args.scope || ""
1439
+ }
1440
+ },
1441
+ error
1442
+ );
1443
+ this.logger?.trackException(mastraError);
1444
+ this.logger.error(mastraError.toString());
1445
+ return {
1446
+ traces: [],
1447
+ total: 0,
1448
+ page,
1449
+ perPage: perPage || 100,
1450
+ hasMore: false
1451
+ };
1452
+ }
1453
+ }
1454
+ async batchTraceInsert(args) {
1455
+ return this.operations.batchInsert({
1456
+ tableName: TABLE_TRACES,
1457
+ records: args.records
1458
+ });
1459
+ }
1460
+ };
1461
+ function parseWorkflowRun(row) {
1462
+ let parsedSnapshot = row.snapshot;
1463
+ if (typeof parsedSnapshot === "string") {
1464
+ try {
1465
+ parsedSnapshot = JSON.parse(row.snapshot);
1466
+ } catch (e) {
1467
+ console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
1468
+ }
1469
+ }
1470
+ return {
1471
+ workflowName: row.workflow_name,
1472
+ runId: row.run_id,
1473
+ snapshot: parsedSnapshot,
1474
+ createdAt: ensureDate(row.createdAt),
1475
+ updatedAt: ensureDate(row.updatedAt),
1476
+ resourceId: row.resourceId
1477
+ };
1478
+ }
1479
+ var WorkflowsUpstash = class extends WorkflowsStorage {
1480
+ client;
1481
+ operations;
1482
+ constructor({ client, operations }) {
1483
+ super();
1484
+ this.client = client;
1485
+ this.operations = operations;
1486
+ }
1487
+ async persistWorkflowSnapshot(params) {
1488
+ const { namespace = "workflows", workflowName, runId, snapshot } = params;
1489
+ try {
1490
+ await this.operations.insert({
1491
+ tableName: TABLE_WORKFLOW_SNAPSHOT,
1492
+ record: {
1493
+ namespace,
1494
+ workflow_name: workflowName,
1495
+ run_id: runId,
1496
+ snapshot,
1497
+ createdAt: /* @__PURE__ */ new Date(),
1498
+ updatedAt: /* @__PURE__ */ new Date()
1499
+ }
1500
+ });
1501
+ } catch (error) {
1502
+ throw new MastraError(
1503
+ {
1504
+ id: "STORAGE_UPSTASH_STORAGE_PERSIST_WORKFLOW_SNAPSHOT_FAILED",
1505
+ domain: ErrorDomain.STORAGE,
1506
+ category: ErrorCategory.THIRD_PARTY,
1507
+ details: {
1508
+ namespace,
1509
+ workflowName,
1510
+ runId
1511
+ }
1512
+ },
1513
+ error
1514
+ );
1515
+ }
1516
+ }
1517
+ async loadWorkflowSnapshot(params) {
1518
+ const { namespace = "workflows", workflowName, runId } = params;
1519
+ const key = getKey(TABLE_WORKFLOW_SNAPSHOT, {
1520
+ namespace,
1521
+ workflow_name: workflowName,
1522
+ run_id: runId
1523
+ });
1524
+ try {
1525
+ const data = await this.client.get(key);
1526
+ if (!data) return null;
1527
+ return data.snapshot;
1528
+ } catch (error) {
1529
+ throw new MastraError(
1530
+ {
1531
+ id: "STORAGE_UPSTASH_STORAGE_LOAD_WORKFLOW_SNAPSHOT_FAILED",
1532
+ domain: ErrorDomain.STORAGE,
1533
+ category: ErrorCategory.THIRD_PARTY,
1534
+ details: {
1535
+ namespace,
1536
+ workflowName,
1537
+ runId
1538
+ }
1539
+ },
1540
+ error
1541
+ );
1542
+ }
1543
+ }
1544
+ async getWorkflowRunById({
1545
+ runId,
1546
+ workflowName
1547
+ }) {
1548
+ try {
1549
+ const key = getKey(TABLE_WORKFLOW_SNAPSHOT, { namespace: "workflows", workflow_name: workflowName, run_id: runId }) + "*";
1550
+ const keys = await this.operations.scanKeys(key);
1551
+ const workflows = await Promise.all(
1552
+ keys.map(async (key2) => {
1553
+ const data2 = await this.client.get(key2);
1554
+ return data2;
1555
+ })
1556
+ );
1557
+ const data = workflows.find((w) => w?.run_id === runId && w?.workflow_name === workflowName);
1558
+ if (!data) return null;
1559
+ return parseWorkflowRun(data);
770
1560
  } catch (error) {
771
- const { page = 0, perPage = 100 } = options || {};
772
- console.error("Failed to get evals:", error);
773
- return {
774
- evals: [],
775
- total: 0,
776
- page,
777
- perPage,
778
- hasMore: false
779
- };
1561
+ throw new MastraError(
1562
+ {
1563
+ id: "STORAGE_UPSTASH_STORAGE_GET_WORKFLOW_RUN_BY_ID_FAILED",
1564
+ domain: ErrorDomain.STORAGE,
1565
+ category: ErrorCategory.THIRD_PARTY,
1566
+ details: {
1567
+ namespace: "workflows",
1568
+ runId,
1569
+ workflowName: workflowName || ""
1570
+ }
1571
+ },
1572
+ error
1573
+ );
780
1574
  }
781
1575
  }
782
1576
  async getWorkflowRuns({
783
- namespace,
784
1577
  workflowName,
785
1578
  fromDate,
786
1579
  toDate,
787
1580
  limit,
788
1581
  offset,
789
1582
  resourceId
790
- } = { namespace: "workflows" }) {
1583
+ }) {
791
1584
  try {
792
- let pattern = this.getKey(TABLE_WORKFLOW_SNAPSHOT, { namespace }) + ":*";
1585
+ let pattern = getKey(TABLE_WORKFLOW_SNAPSHOT, { namespace: "workflows" }) + ":*";
793
1586
  if (workflowName && resourceId) {
794
- pattern = this.getKey(TABLE_WORKFLOW_SNAPSHOT, {
795
- namespace,
1587
+ pattern = getKey(TABLE_WORKFLOW_SNAPSHOT, {
1588
+ namespace: "workflows",
796
1589
  workflow_name: workflowName,
797
1590
  run_id: "*",
798
1591
  resourceId
799
1592
  });
800
1593
  } else if (workflowName) {
801
- pattern = this.getKey(TABLE_WORKFLOW_SNAPSHOT, { namespace, workflow_name: workflowName }) + ":*";
1594
+ pattern = getKey(TABLE_WORKFLOW_SNAPSHOT, { namespace: "workflows", workflow_name: workflowName }) + ":*";
802
1595
  } else if (resourceId) {
803
- pattern = this.getKey(TABLE_WORKFLOW_SNAPSHOT, { namespace, workflow_name: "*", run_id: "*", resourceId });
1596
+ pattern = getKey(TABLE_WORKFLOW_SNAPSHOT, {
1597
+ namespace: "workflows",
1598
+ workflow_name: "*",
1599
+ run_id: "*",
1600
+ resourceId
1601
+ });
804
1602
  }
805
- const keys = await this.scanKeys(pattern);
1603
+ const keys = await this.operations.scanKeys(pattern);
806
1604
  if (keys.length === 0) {
807
1605
  return { runs: [], total: 0 };
808
1606
  }
809
- const pipeline = this.redis.pipeline();
1607
+ const pipeline = this.client.pipeline();
810
1608
  keys.forEach((key) => pipeline.get(key));
811
1609
  const results = await pipeline.exec();
812
1610
  let runs = results.map((result) => result).filter(
813
1611
  (record) => record !== null && record !== void 0 && typeof record === "object" && "workflow_name" in record
814
- ).filter((record) => !workflowName || record.workflow_name === workflowName).map((w) => this.parseWorkflowRun(w)).filter((w) => {
1612
+ ).filter((record) => !workflowName || record.workflow_name === workflowName).map((w) => parseWorkflowRun(w)).filter((w) => {
815
1613
  if (fromDate && w.createdAt < fromDate) return false;
816
1614
  if (toDate && w.createdAt > toDate) return false;
817
1615
  return true;
@@ -822,106 +1620,222 @@ var UpstashStore = class extends MastraStorage {
822
1620
  }
823
1621
  return { runs, total };
824
1622
  } catch (error) {
825
- console.error("Error getting workflow runs:", error);
826
- throw error;
1623
+ throw new MastraError(
1624
+ {
1625
+ id: "STORAGE_UPSTASH_STORAGE_GET_WORKFLOW_RUNS_FAILED",
1626
+ domain: ErrorDomain.STORAGE,
1627
+ category: ErrorCategory.THIRD_PARTY,
1628
+ details: {
1629
+ namespace: "workflows",
1630
+ workflowName: workflowName || "",
1631
+ resourceId: resourceId || ""
1632
+ }
1633
+ },
1634
+ error
1635
+ );
827
1636
  }
828
1637
  }
1638
+ };
1639
+
1640
+ // src/storage/index.ts
1641
+ var UpstashStore = class extends MastraStorage {
1642
+ redis;
1643
+ stores;
1644
+ constructor(config) {
1645
+ super({ name: "Upstash" });
1646
+ this.redis = new Redis({
1647
+ url: config.url,
1648
+ token: config.token
1649
+ });
1650
+ const operations = new StoreOperationsUpstash({ client: this.redis });
1651
+ const traces = new TracesUpstash({ client: this.redis, operations });
1652
+ const scores = new ScoresUpstash({ client: this.redis, operations });
1653
+ const workflows = new WorkflowsUpstash({ client: this.redis, operations });
1654
+ const memory = new StoreMemoryUpstash({ client: this.redis, operations });
1655
+ const legacyEvals = new StoreLegacyEvalsUpstash({ client: this.redis, operations });
1656
+ this.stores = {
1657
+ operations,
1658
+ traces,
1659
+ scores,
1660
+ workflows,
1661
+ memory,
1662
+ legacyEvals
1663
+ };
1664
+ }
1665
+ get supports() {
1666
+ return {
1667
+ selectByIncludeResourceScope: true,
1668
+ resourceWorkingMemory: true,
1669
+ hasColumn: false,
1670
+ createTable: false,
1671
+ deleteMessages: true
1672
+ };
1673
+ }
1674
+ /**
1675
+ * @deprecated Use getEvals instead
1676
+ */
1677
+ async getEvalsByAgentName(agentName, type) {
1678
+ return this.stores.legacyEvals.getEvalsByAgentName(agentName, type);
1679
+ }
1680
+ /**
1681
+ * Get all evaluations with pagination and total count
1682
+ * @param options Pagination and filtering options
1683
+ * @returns Object with evals array and total count
1684
+ */
1685
+ async getEvals(options) {
1686
+ return this.stores.legacyEvals.getEvals(options);
1687
+ }
1688
+ /**
1689
+ * @deprecated use getTracesPaginated instead
1690
+ */
1691
+ async getTraces(args) {
1692
+ return this.stores.traces.getTraces(args);
1693
+ }
1694
+ async getTracesPaginated(args) {
1695
+ return this.stores.traces.getTracesPaginated(args);
1696
+ }
1697
+ async batchTraceInsert(args) {
1698
+ return this.stores.traces.batchTraceInsert(args);
1699
+ }
1700
+ async createTable({
1701
+ tableName,
1702
+ schema
1703
+ }) {
1704
+ return this.stores.operations.createTable({ tableName, schema });
1705
+ }
1706
+ /**
1707
+ * No-op: This backend is schemaless and does not require schema changes.
1708
+ * @param tableName Name of the table
1709
+ * @param schema Schema of the table
1710
+ * @param ifNotExists Array of column names to add if they don't exist
1711
+ */
1712
+ async alterTable(args) {
1713
+ return this.stores.operations.alterTable(args);
1714
+ }
1715
+ async clearTable({ tableName }) {
1716
+ return this.stores.operations.clearTable({ tableName });
1717
+ }
1718
+ async dropTable({ tableName }) {
1719
+ return this.stores.operations.dropTable({ tableName });
1720
+ }
1721
+ async insert({ tableName, record }) {
1722
+ return this.stores.operations.insert({ tableName, record });
1723
+ }
1724
+ async batchInsert(input) {
1725
+ return this.stores.operations.batchInsert(input);
1726
+ }
1727
+ async load({ tableName, keys }) {
1728
+ return this.stores.operations.load({ tableName, keys });
1729
+ }
1730
+ async getThreadById({ threadId }) {
1731
+ return this.stores.memory.getThreadById({ threadId });
1732
+ }
1733
+ /**
1734
+ * @deprecated use getThreadsByResourceIdPaginated instead
1735
+ */
1736
+ async getThreadsByResourceId({ resourceId }) {
1737
+ return this.stores.memory.getThreadsByResourceId({ resourceId });
1738
+ }
1739
+ async getThreadsByResourceIdPaginated(args) {
1740
+ return this.stores.memory.getThreadsByResourceIdPaginated(args);
1741
+ }
1742
+ async saveThread({ thread }) {
1743
+ return this.stores.memory.saveThread({ thread });
1744
+ }
1745
+ async updateThread({
1746
+ id,
1747
+ title,
1748
+ metadata
1749
+ }) {
1750
+ return this.stores.memory.updateThread({ id, title, metadata });
1751
+ }
1752
+ async deleteThread({ threadId }) {
1753
+ return this.stores.memory.deleteThread({ threadId });
1754
+ }
1755
+ async saveMessages(args) {
1756
+ return this.stores.memory.saveMessages(args);
1757
+ }
1758
+ async getMessages({
1759
+ threadId,
1760
+ selectBy,
1761
+ format
1762
+ }) {
1763
+ return this.stores.memory.getMessages({ threadId, selectBy, format });
1764
+ }
1765
+ async getMessagesPaginated(args) {
1766
+ return this.stores.memory.getMessagesPaginated(args);
1767
+ }
1768
+ async persistWorkflowSnapshot(params) {
1769
+ return this.stores.workflows.persistWorkflowSnapshot(params);
1770
+ }
1771
+ async loadWorkflowSnapshot(params) {
1772
+ return this.stores.workflows.loadWorkflowSnapshot(params);
1773
+ }
1774
+ async getWorkflowRuns({
1775
+ workflowName,
1776
+ fromDate,
1777
+ toDate,
1778
+ limit,
1779
+ offset,
1780
+ resourceId
1781
+ } = {}) {
1782
+ return this.stores.workflows.getWorkflowRuns({ workflowName, fromDate, toDate, limit, offset, resourceId });
1783
+ }
829
1784
  async getWorkflowRunById({
830
- namespace = "workflows",
831
1785
  runId,
832
1786
  workflowName
833
1787
  }) {
834
- try {
835
- const key = this.getKey(TABLE_WORKFLOW_SNAPSHOT, { namespace, workflow_name: workflowName, run_id: runId }) + "*";
836
- const keys = await this.scanKeys(key);
837
- const workflows = await Promise.all(
838
- keys.map(async (key2) => {
839
- const data2 = await this.redis.get(key2);
840
- return data2;
841
- })
842
- );
843
- const data = workflows.find((w) => w?.run_id === runId && w?.workflow_name === workflowName);
844
- if (!data) return null;
845
- return this.parseWorkflowRun(data);
846
- } catch (error) {
847
- console.error("Error getting workflow run by ID:", error);
848
- throw error;
849
- }
1788
+ return this.stores.workflows.getWorkflowRunById({ runId, workflowName });
850
1789
  }
851
1790
  async close() {
852
1791
  }
853
- async updateMessages(_args) {
854
- this.logger.error("updateMessages is not yet implemented in UpstashStore");
855
- throw new Error("Method not implemented");
1792
+ async updateMessages(args) {
1793
+ return this.stores.memory.updateMessages(args);
1794
+ }
1795
+ async deleteMessages(messageIds) {
1796
+ return this.stores.memory.deleteMessages(messageIds);
856
1797
  }
857
1798
  async getResourceById({ resourceId }) {
858
- try {
859
- const key = `${TABLE_RESOURCES}:${resourceId}`;
860
- const data = await this.redis.get(key);
861
- if (!data) {
862
- return null;
863
- }
864
- return {
865
- ...data,
866
- createdAt: new Date(data.createdAt),
867
- updatedAt: new Date(data.updatedAt),
868
- // Ensure workingMemory is always returned as a string, regardless of automatic parsing
869
- workingMemory: typeof data.workingMemory === "object" ? JSON.stringify(data.workingMemory) : data.workingMemory,
870
- metadata: typeof data.metadata === "string" ? JSON.parse(data.metadata) : data.metadata
871
- };
872
- } catch (error) {
873
- this.logger.error("Error getting resource by ID:", error);
874
- throw error;
875
- }
1799
+ return this.stores.memory.getResourceById({ resourceId });
876
1800
  }
877
1801
  async saveResource({ resource }) {
878
- try {
879
- const key = `${TABLE_RESOURCES}:${resource.id}`;
880
- const serializedResource = {
881
- ...resource,
882
- metadata: JSON.stringify(resource.metadata),
883
- createdAt: resource.createdAt.toISOString(),
884
- updatedAt: resource.updatedAt.toISOString()
885
- };
886
- await this.redis.set(key, serializedResource);
887
- return resource;
888
- } catch (error) {
889
- this.logger.error("Error saving resource:", error);
890
- throw error;
891
- }
1802
+ return this.stores.memory.saveResource({ resource });
892
1803
  }
893
1804
  async updateResource({
894
1805
  resourceId,
895
1806
  workingMemory,
896
1807
  metadata
897
1808
  }) {
898
- try {
899
- const existingResource = await this.getResourceById({ resourceId });
900
- if (!existingResource) {
901
- const newResource = {
902
- id: resourceId,
903
- workingMemory,
904
- metadata: metadata || {},
905
- createdAt: /* @__PURE__ */ new Date(),
906
- updatedAt: /* @__PURE__ */ new Date()
907
- };
908
- return this.saveResource({ resource: newResource });
909
- }
910
- const updatedResource = {
911
- ...existingResource,
912
- workingMemory: workingMemory !== void 0 ? workingMemory : existingResource.workingMemory,
913
- metadata: {
914
- ...existingResource.metadata,
915
- ...metadata
916
- },
917
- updatedAt: /* @__PURE__ */ new Date()
918
- };
919
- await this.saveResource({ resource: updatedResource });
920
- return updatedResource;
921
- } catch (error) {
922
- this.logger.error("Error updating resource:", error);
923
- throw error;
924
- }
1809
+ return this.stores.memory.updateResource({ resourceId, workingMemory, metadata });
1810
+ }
1811
+ async getScoreById({ id: _id }) {
1812
+ return this.stores.scores.getScoreById({ id: _id });
1813
+ }
1814
+ async saveScore(score) {
1815
+ return this.stores.scores.saveScore(score);
1816
+ }
1817
+ async getScoresByRunId({
1818
+ runId,
1819
+ pagination
1820
+ }) {
1821
+ return this.stores.scores.getScoresByRunId({ runId, pagination });
1822
+ }
1823
+ async getScoresByEntityId({
1824
+ entityId,
1825
+ entityType,
1826
+ pagination
1827
+ }) {
1828
+ return this.stores.scores.getScoresByEntityId({
1829
+ entityId,
1830
+ entityType,
1831
+ pagination
1832
+ });
1833
+ }
1834
+ async getScoresByScorerId({
1835
+ scorerId,
1836
+ pagination
1837
+ }) {
1838
+ return this.stores.scores.getScoresByScorerId({ scorerId, pagination });
925
1839
  }
926
1840
  };
927
1841
  var UpstashFilterTranslator = class extends BaseFilterTranslator {
@@ -1137,21 +2051,40 @@ var UpstashVector = class extends MastraVector {
1137
2051
  * @param {UpsertVectorParams} params - The parameters for the upsert operation.
1138
2052
  * @returns {Promise<string[]>} A promise that resolves to the IDs of the upserted vectors.
1139
2053
  */
1140
- async upsert({ indexName: namespace, vectors, metadata, ids }) {
2054
+ async upsert({
2055
+ indexName: namespace,
2056
+ vectors,
2057
+ metadata,
2058
+ ids,
2059
+ sparseVectors
2060
+ }) {
1141
2061
  const generatedIds = ids || vectors.map(() => crypto.randomUUID());
1142
2062
  const points = vectors.map((vector, index) => ({
1143
2063
  id: generatedIds[index],
1144
2064
  vector,
2065
+ ...sparseVectors?.[index] && { sparseVector: sparseVectors[index] },
1145
2066
  metadata: metadata?.[index]
1146
2067
  }));
1147
- await this.client.upsert(points, {
1148
- namespace
1149
- });
1150
- return generatedIds;
2068
+ try {
2069
+ await this.client.upsert(points, {
2070
+ namespace
2071
+ });
2072
+ return generatedIds;
2073
+ } catch (error) {
2074
+ throw new MastraError(
2075
+ {
2076
+ id: "STORAGE_UPSTASH_VECTOR_UPSERT_FAILED",
2077
+ domain: ErrorDomain.STORAGE,
2078
+ category: ErrorCategory.THIRD_PARTY,
2079
+ details: { namespace, vectorCount: vectors.length }
2080
+ },
2081
+ error
2082
+ );
2083
+ }
1151
2084
  }
1152
2085
  /**
1153
2086
  * Transforms a Mastra vector filter into an Upstash-compatible filter string.
1154
- * @param {VectorFilter} [filter] - The filter to transform.
2087
+ * @param {UpstashVectorFilter} [filter] - The filter to transform.
1155
2088
  * @returns {string | undefined} The transformed filter string, or undefined if no filter is provided.
1156
2089
  */
1157
2090
  transformFilter(filter) {
@@ -1176,31 +2109,60 @@ var UpstashVector = class extends MastraVector {
1176
2109
  queryVector,
1177
2110
  topK = 10,
1178
2111
  filter,
1179
- includeVector = false
2112
+ includeVector = false,
2113
+ sparseVector,
2114
+ fusionAlgorithm,
2115
+ queryMode
1180
2116
  }) {
1181
- const ns = this.client.namespace(namespace);
1182
- const filterString = this.transformFilter(filter);
1183
- const results = await ns.query({
1184
- topK,
1185
- vector: queryVector,
1186
- includeVectors: includeVector,
1187
- includeMetadata: true,
1188
- ...filterString ? { filter: filterString } : {}
1189
- });
1190
- return (results || []).map((result) => ({
1191
- id: `${result.id}`,
1192
- score: result.score,
1193
- metadata: result.metadata,
1194
- ...includeVector && { vector: result.vector || [] }
1195
- }));
2117
+ try {
2118
+ const ns = this.client.namespace(namespace);
2119
+ const filterString = this.transformFilter(filter);
2120
+ const results = await ns.query({
2121
+ topK,
2122
+ vector: queryVector,
2123
+ ...sparseVector && { sparseVector },
2124
+ includeVectors: includeVector,
2125
+ includeMetadata: true,
2126
+ ...filterString ? { filter: filterString } : {},
2127
+ ...fusionAlgorithm && { fusionAlgorithm },
2128
+ ...queryMode && { queryMode }
2129
+ });
2130
+ return (results || []).map((result) => ({
2131
+ id: `${result.id}`,
2132
+ score: result.score,
2133
+ metadata: result.metadata,
2134
+ ...includeVector && { vector: result.vector || [] }
2135
+ }));
2136
+ } catch (error) {
2137
+ throw new MastraError(
2138
+ {
2139
+ id: "STORAGE_UPSTASH_VECTOR_QUERY_FAILED",
2140
+ domain: ErrorDomain.STORAGE,
2141
+ category: ErrorCategory.THIRD_PARTY,
2142
+ details: { namespace, topK }
2143
+ },
2144
+ error
2145
+ );
2146
+ }
1196
2147
  }
1197
2148
  /**
1198
2149
  * Lists all namespaces in the Upstash vector index, which correspond to indexes.
1199
2150
  * @returns {Promise<string[]>} A promise that resolves to a list of index names.
1200
2151
  */
1201
2152
  async listIndexes() {
1202
- const indexes = await this.client.listNamespaces();
1203
- return indexes.filter(Boolean);
2153
+ try {
2154
+ const indexes = await this.client.listNamespaces();
2155
+ return indexes.filter(Boolean);
2156
+ } catch (error) {
2157
+ throw new MastraError(
2158
+ {
2159
+ id: "STORAGE_UPSTASH_VECTOR_LIST_INDEXES_FAILED",
2160
+ domain: ErrorDomain.STORAGE,
2161
+ category: ErrorCategory.THIRD_PARTY
2162
+ },
2163
+ error
2164
+ );
2165
+ }
1204
2166
  }
1205
2167
  /**
1206
2168
  * Retrieves statistics about a vector index.
@@ -1209,12 +2171,24 @@ var UpstashVector = class extends MastraVector {
1209
2171
  * @returns A promise that resolves to the index statistics including dimension, count and metric
1210
2172
  */
1211
2173
  async describeIndex({ indexName: namespace }) {
1212
- const info = await this.client.info();
1213
- return {
1214
- dimension: info.dimension,
1215
- count: info.namespaces?.[namespace]?.vectorCount || 0,
1216
- metric: info?.similarityFunction?.toLowerCase()
1217
- };
2174
+ try {
2175
+ const info = await this.client.info();
2176
+ return {
2177
+ dimension: info.dimension,
2178
+ count: info.namespaces?.[namespace]?.vectorCount || 0,
2179
+ metric: info?.similarityFunction?.toLowerCase()
2180
+ };
2181
+ } catch (error) {
2182
+ throw new MastraError(
2183
+ {
2184
+ id: "STORAGE_UPSTASH_VECTOR_DESCRIBE_INDEX_FAILED",
2185
+ domain: ErrorDomain.STORAGE,
2186
+ category: ErrorCategory.THIRD_PARTY,
2187
+ details: { namespace }
2188
+ },
2189
+ error
2190
+ );
2191
+ }
1218
2192
  }
1219
2193
  /**
1220
2194
  * Deletes an index (namespace).
@@ -1225,7 +2199,15 @@ var UpstashVector = class extends MastraVector {
1225
2199
  try {
1226
2200
  await this.client.deleteNamespace(namespace);
1227
2201
  } catch (error) {
1228
- this.logger.error("Failed to delete namespace:", error);
2202
+ throw new MastraError(
2203
+ {
2204
+ id: "STORAGE_UPSTASH_VECTOR_DELETE_INDEX_FAILED",
2205
+ domain: ErrorDomain.STORAGE,
2206
+ category: ErrorCategory.THIRD_PARTY,
2207
+ details: { namespace }
2208
+ },
2209
+ error
2210
+ );
1229
2211
  }
1230
2212
  }
1231
2213
  /**
@@ -1239,30 +2221,40 @@ var UpstashVector = class extends MastraVector {
1239
2221
  * @throws Will throw an error if no updates are provided or if the update operation fails.
1240
2222
  */
1241
2223
  async updateVector({ indexName: namespace, id, update }) {
1242
- try {
1243
- if (!update.vector && !update.metadata) {
1244
- throw new Error("No update data provided");
1245
- }
1246
- if (!update.vector && update.metadata) {
1247
- throw new Error("Both vector and metadata must be provided for an update");
1248
- }
1249
- const updatePayload = { id };
1250
- if (update.vector) {
1251
- updatePayload.vector = update.vector;
1252
- }
1253
- if (update.metadata) {
1254
- updatePayload.metadata = update.metadata;
1255
- }
1256
- const points = {
1257
- id: updatePayload.id,
1258
- vector: updatePayload.vector,
1259
- metadata: updatePayload.metadata
1260
- };
1261
- await this.client.upsert(points, {
1262
- namespace
2224
+ if (!update.vector && !update.metadata && !update.sparseVector) {
2225
+ throw new MastraError({
2226
+ id: "STORAGE_UPSTASH_VECTOR_UPDATE_VECTOR_FAILED",
2227
+ domain: ErrorDomain.STORAGE,
2228
+ category: ErrorCategory.THIRD_PARTY,
2229
+ details: { namespace, id },
2230
+ text: "No update data provided"
1263
2231
  });
2232
+ }
2233
+ if (!update.vector && !update.sparseVector && update.metadata) {
2234
+ throw new MastraError({
2235
+ id: "STORAGE_UPSTASH_VECTOR_UPDATE_VECTOR_FAILED",
2236
+ domain: ErrorDomain.STORAGE,
2237
+ category: ErrorCategory.THIRD_PARTY,
2238
+ details: { namespace, id },
2239
+ text: "Both vector and metadata must be provided for an update"
2240
+ });
2241
+ }
2242
+ try {
2243
+ const points = { id };
2244
+ if (update.vector) points.vector = update.vector;
2245
+ if (update.metadata) points.metadata = update.metadata;
2246
+ if (update.sparseVector) points.sparseVector = update.sparseVector;
2247
+ await this.client.upsert(points, { namespace });
1264
2248
  } catch (error) {
1265
- throw new Error(`Failed to update vector by id: ${id} for index name: ${namespace}: ${error.message}`);
2249
+ throw new MastraError(
2250
+ {
2251
+ id: "STORAGE_UPSTASH_VECTOR_UPDATE_VECTOR_FAILED",
2252
+ domain: ErrorDomain.STORAGE,
2253
+ category: ErrorCategory.THIRD_PARTY,
2254
+ details: { namespace, id }
2255
+ },
2256
+ error
2257
+ );
1266
2258
  }
1267
2259
  }
1268
2260
  /**
@@ -1278,7 +2270,16 @@ var UpstashVector = class extends MastraVector {
1278
2270
  namespace
1279
2271
  });
1280
2272
  } catch (error) {
1281
- this.logger.error(`Failed to delete vector by id: ${id} for namespace: ${namespace}:`, error);
2273
+ const mastraError = new MastraError(
2274
+ {
2275
+ id: "STORAGE_UPSTASH_VECTOR_DELETE_VECTOR_FAILED",
2276
+ domain: ErrorDomain.STORAGE,
2277
+ category: ErrorCategory.THIRD_PARTY,
2278
+ details: { namespace, id }
2279
+ },
2280
+ error
2281
+ );
2282
+ this.logger?.error(mastraError.toString());
1282
2283
  }
1283
2284
  }
1284
2285
  };
@@ -1359,3 +2360,5 @@ Example Complex Query:
1359
2360
  }`;
1360
2361
 
1361
2362
  export { UPSTASH_PROMPT, UpstashStore, UpstashVector };
2363
+ //# sourceMappingURL=index.js.map
2364
+ //# sourceMappingURL=index.js.map