@mastra/upstash 0.10.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { MastraStorage, TABLE_EVALS, TABLE_TRACES, TABLE_MESSAGES, TABLE_WORKFLOW_SNAPSHOT, TABLE_THREADS } from '@mastra/core/storage';
1
+ import { MastraStorage, TABLE_MESSAGES, TABLE_WORKFLOW_SNAPSHOT, TABLE_EVALS, TABLE_TRACES, TABLE_THREADS } from '@mastra/core/storage';
2
2
  import { Redis } from '@upstash/redis';
3
3
  import { MastraVector } from '@mastra/core/vector';
4
4
  import { Index } from '@upstash/vector';
@@ -6,13 +6,157 @@ import { BaseFilterTranslator } from '@mastra/core/vector/filter';
6
6
 
7
7
  // src/storage/index.ts
8
8
  var UpstashStore = class extends MastraStorage {
9
- batchInsert(_input) {
10
- throw new Error("Method not implemented.");
9
+ redis;
10
+ constructor(config) {
11
+ super({ name: "Upstash" });
12
+ this.redis = new Redis({
13
+ url: config.url,
14
+ token: config.token
15
+ });
16
+ }
17
+ transformEvalRecord(record) {
18
+ let result = record.result;
19
+ if (typeof result === "string") {
20
+ try {
21
+ result = JSON.parse(result);
22
+ } catch {
23
+ console.warn("Failed to parse result JSON:");
24
+ }
25
+ }
26
+ let testInfo = record.test_info;
27
+ if (typeof testInfo === "string") {
28
+ try {
29
+ testInfo = JSON.parse(testInfo);
30
+ } catch {
31
+ console.warn("Failed to parse test_info JSON:");
32
+ }
33
+ }
34
+ return {
35
+ agentName: record.agent_name,
36
+ input: record.input,
37
+ output: record.output,
38
+ result,
39
+ metricName: record.metric_name,
40
+ instructions: record.instructions,
41
+ testInfo,
42
+ globalRunId: record.global_run_id,
43
+ runId: record.run_id,
44
+ createdAt: typeof record.created_at === "string" ? record.created_at : record.created_at instanceof Date ? record.created_at.toISOString() : (/* @__PURE__ */ new Date()).toISOString()
45
+ };
46
+ }
47
+ parseJSON(value) {
48
+ if (typeof value === "string") {
49
+ try {
50
+ return JSON.parse(value);
51
+ } catch {
52
+ return value;
53
+ }
54
+ }
55
+ return value;
56
+ }
57
+ getKey(tableName, keys) {
58
+ const keyParts = Object.entries(keys).filter(([_, value]) => value !== void 0).map(([key, value]) => `${key}:${value}`);
59
+ return `${tableName}:${keyParts.join(":")}`;
60
+ }
61
+ ensureDate(date) {
62
+ if (!date) return void 0;
63
+ return date instanceof Date ? date : new Date(date);
64
+ }
65
+ serializeDate(date) {
66
+ if (!date) return void 0;
67
+ const dateObj = this.ensureDate(date);
68
+ return dateObj?.toISOString();
69
+ }
70
+ /**
71
+ * Scans for keys matching the given pattern using SCAN and returns them as an array.
72
+ * @param pattern Redis key pattern, e.g. "table:*"
73
+ * @param batchSize Number of keys to scan per batch (default: 1000)
74
+ */
75
+ async scanKeys(pattern, batchSize = 1e4) {
76
+ let cursor = "0";
77
+ let keys = [];
78
+ do {
79
+ const [nextCursor, batch] = await this.redis.scan(cursor, {
80
+ match: pattern,
81
+ count: batchSize
82
+ });
83
+ keys.push(...batch);
84
+ cursor = nextCursor;
85
+ } while (cursor !== "0");
86
+ return keys;
87
+ }
88
+ /**
89
+ * Deletes all keys matching the given pattern using SCAN and DEL in batches.
90
+ * @param pattern Redis key pattern, e.g. "table:*"
91
+ * @param batchSize Number of keys to delete per batch (default: 1000)
92
+ */
93
+ async scanAndDelete(pattern, batchSize = 1e4) {
94
+ let cursor = "0";
95
+ let totalDeleted = 0;
96
+ do {
97
+ const [nextCursor, keys] = await this.redis.scan(cursor, {
98
+ match: pattern,
99
+ count: batchSize
100
+ });
101
+ if (keys.length > 0) {
102
+ await this.redis.del(...keys);
103
+ totalDeleted += keys.length;
104
+ }
105
+ cursor = nextCursor;
106
+ } while (cursor !== "0");
107
+ return totalDeleted;
108
+ }
109
+ getMessageKey(threadId, messageId) {
110
+ return this.getKey(TABLE_MESSAGES, { threadId, id: messageId });
111
+ }
112
+ getThreadMessagesKey(threadId) {
113
+ return `thread:${threadId}:messages`;
114
+ }
115
+ parseWorkflowRun(row) {
116
+ let parsedSnapshot = row.snapshot;
117
+ if (typeof parsedSnapshot === "string") {
118
+ try {
119
+ parsedSnapshot = JSON.parse(row.snapshot);
120
+ } catch (e) {
121
+ console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
122
+ }
123
+ }
124
+ return {
125
+ workflowName: row.workflow_name,
126
+ runId: row.run_id,
127
+ snapshot: parsedSnapshot,
128
+ createdAt: this.ensureDate(row.createdAt),
129
+ updatedAt: this.ensureDate(row.updatedAt),
130
+ resourceId: row.resourceId
131
+ };
132
+ }
133
+ processRecord(tableName, record) {
134
+ let key;
135
+ if (tableName === TABLE_MESSAGES) {
136
+ key = this.getKey(tableName, { threadId: record.threadId, id: record.id });
137
+ } else if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
138
+ key = this.getKey(tableName, {
139
+ namespace: record.namespace || "workflows",
140
+ workflow_name: record.workflow_name,
141
+ run_id: record.run_id,
142
+ ...record.resourceId ? { resourceId: record.resourceId } : {}
143
+ });
144
+ } else if (tableName === TABLE_EVALS) {
145
+ key = this.getKey(tableName, { id: record.run_id });
146
+ } else {
147
+ key = this.getKey(tableName, { id: record.id });
148
+ }
149
+ const processedRecord = {
150
+ ...record,
151
+ createdAt: this.serializeDate(record.createdAt),
152
+ updatedAt: this.serializeDate(record.updatedAt)
153
+ };
154
+ return { key, processedRecord };
11
155
  }
12
156
  async getEvalsByAgentName(agentName, type) {
13
157
  try {
14
158
  const pattern = `${TABLE_EVALS}:*`;
15
- const keys = await this.redis.keys(pattern);
159
+ const keys = await this.scanKeys(pattern);
16
160
  const evalRecords = await Promise.all(
17
161
  keys.map(async (key) => {
18
162
  const data = await this.redis.get(key);
@@ -56,36 +200,6 @@ var UpstashStore = class extends MastraStorage {
56
200
  return [];
57
201
  }
58
202
  }
59
- transformEvalRecord(record) {
60
- let result = record.result;
61
- if (typeof result === "string") {
62
- try {
63
- result = JSON.parse(result);
64
- } catch {
65
- console.warn("Failed to parse result JSON:");
66
- }
67
- }
68
- let testInfo = record.test_info;
69
- if (typeof testInfo === "string") {
70
- try {
71
- testInfo = JSON.parse(testInfo);
72
- } catch {
73
- console.warn("Failed to parse test_info JSON:");
74
- }
75
- }
76
- return {
77
- agentName: record.agent_name,
78
- input: record.input,
79
- output: record.output,
80
- result,
81
- metricName: record.metric_name,
82
- instructions: record.instructions,
83
- testInfo,
84
- globalRunId: record.global_run_id,
85
- runId: record.run_id,
86
- createdAt: typeof record.created_at === "string" ? record.created_at : record.created_at instanceof Date ? record.created_at.toISOString() : (/* @__PURE__ */ new Date()).toISOString()
87
- };
88
- }
89
203
  async getTraces({
90
204
  name,
91
205
  scope,
@@ -101,7 +215,7 @@ var UpstashStore = class extends MastraStorage {
101
215
  }) {
102
216
  try {
103
217
  const pattern = `${TABLE_TRACES}:*`;
104
- const keys = await this.redis.keys(pattern);
218
+ const keys = await this.scanKeys(pattern);
105
219
  const traceRecords = await Promise.all(
106
220
  keys.map(async (key) => {
107
221
  const data = await this.redis.get(key);
@@ -165,37 +279,6 @@ var UpstashStore = class extends MastraStorage {
165
279
  return [];
166
280
  }
167
281
  }
168
- parseJSON(value) {
169
- if (typeof value === "string") {
170
- try {
171
- return JSON.parse(value);
172
- } catch {
173
- return value;
174
- }
175
- }
176
- return value;
177
- }
178
- redis;
179
- constructor(config) {
180
- super({ name: "Upstash" });
181
- this.redis = new Redis({
182
- url: config.url,
183
- token: config.token
184
- });
185
- }
186
- getKey(tableName, keys) {
187
- const keyParts = Object.entries(keys).filter(([_, value]) => value !== void 0).map(([key, value]) => `${key}:${value}`);
188
- return `${tableName}:${keyParts.join(":")}`;
189
- }
190
- ensureDate(date) {
191
- if (!date) return void 0;
192
- return date instanceof Date ? date : new Date(date);
193
- }
194
- serializeDate(date) {
195
- if (!date) return void 0;
196
- const dateObj = this.ensureDate(date);
197
- return dateObj?.toISOString();
198
- }
199
282
  async createTable({
200
283
  tableName,
201
284
  schema
@@ -204,34 +287,26 @@ var UpstashStore = class extends MastraStorage {
204
287
  }
205
288
  async clearTable({ tableName }) {
206
289
  const pattern = `${tableName}:*`;
207
- const keys = await this.redis.keys(pattern);
208
- if (keys.length > 0) {
209
- await this.redis.del(...keys);
210
- }
290
+ await this.scanAndDelete(pattern);
211
291
  }
212
292
  async insert({ tableName, record }) {
213
- let key;
214
- if (tableName === TABLE_MESSAGES) {
215
- key = this.getKey(tableName, { threadId: record.threadId, id: record.id });
216
- } else if (tableName === TABLE_WORKFLOW_SNAPSHOT) {
217
- key = this.getKey(tableName, {
218
- namespace: record.namespace || "workflows",
219
- workflow_name: record.workflow_name,
220
- run_id: record.run_id,
221
- ...record.resourceId ? { resourceId: record.resourceId } : {}
222
- });
223
- } else if (tableName === TABLE_EVALS) {
224
- key = this.getKey(tableName, { id: record.run_id });
225
- } else {
226
- key = this.getKey(tableName, { id: record.id });
227
- }
228
- const processedRecord = {
229
- ...record,
230
- createdAt: this.serializeDate(record.createdAt),
231
- updatedAt: this.serializeDate(record.updatedAt)
232
- };
293
+ const { key, processedRecord } = this.processRecord(tableName, record);
233
294
  await this.redis.set(key, processedRecord);
234
295
  }
296
+ async batchInsert(input) {
297
+ const { tableName, records } = input;
298
+ if (!records.length) return;
299
+ const batchSize = 1e3;
300
+ for (let i = 0; i < records.length; i += batchSize) {
301
+ const batch = records.slice(i, i + batchSize);
302
+ const pipeline = this.redis.pipeline();
303
+ for (const record of batch) {
304
+ const { key, processedRecord } = this.processRecord(tableName, record);
305
+ pipeline.set(key, processedRecord);
306
+ }
307
+ await pipeline.exec();
308
+ }
309
+ }
235
310
  async load({ tableName, keys }) {
236
311
  const key = this.getKey(tableName, keys);
237
312
  const data = await this.redis.get(key);
@@ -252,7 +327,7 @@ var UpstashStore = class extends MastraStorage {
252
327
  }
253
328
  async getThreadsByResourceId({ resourceId }) {
254
329
  const pattern = `${TABLE_THREADS}:*`;
255
- const keys = await this.redis.keys(pattern);
330
+ const keys = await this.scanKeys(pattern);
256
331
  const threads = await Promise.all(
257
332
  keys.map(async (key) => {
258
333
  const data = await this.redis.get(key);
@@ -297,29 +372,27 @@ var UpstashStore = class extends MastraStorage {
297
372
  const key = this.getKey(TABLE_THREADS, { id: threadId });
298
373
  await this.redis.del(key);
299
374
  }
300
- getMessageKey(threadId, messageId) {
301
- return this.getKey(TABLE_MESSAGES, { threadId, id: messageId });
302
- }
303
- getThreadMessagesKey(threadId) {
304
- return `thread:${threadId}:messages`;
305
- }
306
375
  async saveMessages({ messages }) {
307
376
  if (messages.length === 0) return [];
308
- const pipeline = this.redis.pipeline();
309
377
  const messagesWithIndex = messages.map((message, index) => ({
310
378
  ...message,
311
379
  _index: index
312
380
  }));
313
- for (const message of messagesWithIndex) {
314
- const key = this.getMessageKey(message.threadId, message.id);
315
- const score = message._index !== void 0 ? message._index : new Date(message.createdAt).getTime();
316
- pipeline.set(key, message);
317
- pipeline.zadd(this.getThreadMessagesKey(message.threadId), {
318
- score,
319
- member: message.id
320
- });
381
+ const batchSize = 1e3;
382
+ for (let i = 0; i < messagesWithIndex.length; i += batchSize) {
383
+ const batch = messagesWithIndex.slice(i, i + batchSize);
384
+ const pipeline = this.redis.pipeline();
385
+ for (const message of batch) {
386
+ const key = this.getMessageKey(message.threadId, message.id);
387
+ const score = message._index !== void 0 ? message._index : new Date(message.createdAt).getTime();
388
+ pipeline.set(key, message);
389
+ pipeline.zadd(this.getThreadMessagesKey(message.threadId), {
390
+ score,
391
+ member: message.id
392
+ });
393
+ }
394
+ await pipeline.exec();
321
395
  }
322
- await pipeline.exec();
323
396
  return messages;
324
397
  }
325
398
  async getMessages({ threadId, selectBy }) {
@@ -383,24 +456,6 @@ var UpstashStore = class extends MastraStorage {
383
456
  if (!data) return null;
384
457
  return data.snapshot;
385
458
  }
386
- parseWorkflowRun(row) {
387
- let parsedSnapshot = row.snapshot;
388
- if (typeof parsedSnapshot === "string") {
389
- try {
390
- parsedSnapshot = JSON.parse(row.snapshot);
391
- } catch (e) {
392
- console.warn(`Failed to parse snapshot for workflow ${row.workflow_name}: ${e}`);
393
- }
394
- }
395
- return {
396
- workflowName: row.workflow_name,
397
- runId: row.run_id,
398
- snapshot: parsedSnapshot,
399
- createdAt: this.ensureDate(row.createdAt),
400
- updatedAt: this.ensureDate(row.updatedAt),
401
- resourceId: row.resourceId
402
- };
403
- }
404
459
  async getWorkflowRuns({
405
460
  namespace,
406
461
  workflowName,
@@ -424,7 +479,7 @@ var UpstashStore = class extends MastraStorage {
424
479
  } else if (resourceId) {
425
480
  pattern = this.getKey(TABLE_WORKFLOW_SNAPSHOT, { namespace, workflow_name: "*", run_id: "*", resourceId });
426
481
  }
427
- const keys = await this.redis.keys(pattern);
482
+ const keys = await this.scanKeys(pattern);
428
483
  const workflows = await Promise.all(
429
484
  keys.map(async (key) => {
430
485
  const data = await this.redis.get(key);
@@ -453,7 +508,7 @@ var UpstashStore = class extends MastraStorage {
453
508
  }) {
454
509
  try {
455
510
  const key = this.getKey(TABLE_WORKFLOW_SNAPSHOT, { namespace, workflow_name: workflowName, run_id: runId }) + "*";
456
- const keys = await this.redis.keys(key);
511
+ const keys = await this.scanKeys(key);
457
512
  const workflows = await Promise.all(
458
513
  keys.map(async (key2) => {
459
514
  const data2 = await this.redis.get(key2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mastra/upstash",
3
- "version": "0.10.0",
3
+ "version": "0.10.1",
4
4
  "description": "Upstash provider for Mastra - includes both vector and db storage capabilities",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -31,8 +31,8 @@
31
31
  "tsup": "^8.4.0",
32
32
  "typescript": "^5.8.2",
33
33
  "vitest": "^3.1.2",
34
- "@mastra/core": "0.10.0",
35
- "@internal/lint": "0.0.6"
34
+ "@internal/lint": "0.0.7",
35
+ "@mastra/core": "0.10.1"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "@mastra/core": "^0.10.0"