@mastra/upstash 0.12.1 → 0.12.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +7 -7
- package/CHANGELOG.md +53 -0
- package/dist/_tsup-dts-rollup.d.cts +342 -40
- package/dist/_tsup-dts-rollup.d.ts +342 -40
- package/dist/index.cjs +1133 -612
- package/dist/index.js +1134 -613
- package/docker-compose.yaml +1 -1
- package/package.json +5 -5
- package/src/storage/domains/legacy-evals/index.ts +279 -0
- package/src/storage/domains/memory/index.ts +902 -0
- package/src/storage/domains/operations/index.ts +168 -0
- package/src/storage/domains/scores/index.ts +216 -0
- package/src/storage/domains/traces/index.ts +172 -0
- package/src/storage/domains/utils.ts +57 -0
- package/src/storage/domains/workflows/index.ts +243 -0
- package/src/storage/index.test.ts +13 -0
- package/src/storage/index.ts +143 -1416
- package/src/storage/upstash.test.ts +0 -1461
package/docker-compose.yaml
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/upstash",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.2",
|
|
4
4
|
"description": "Upstash provider for Mastra - includes both vector and db storage capabilities",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -27,13 +27,13 @@
|
|
|
27
27
|
"@microsoft/api-extractor": "^7.52.8",
|
|
28
28
|
"@types/node": "^20.19.0",
|
|
29
29
|
"dotenv": "^17.0.0",
|
|
30
|
-
"eslint": "^9.
|
|
30
|
+
"eslint": "^9.30.1",
|
|
31
31
|
"tsup": "^8.5.0",
|
|
32
32
|
"typescript": "^5.8.3",
|
|
33
33
|
"vitest": "^3.2.4",
|
|
34
|
-
"@internal/
|
|
35
|
-
"@
|
|
36
|
-
"@
|
|
34
|
+
"@internal/storage-test-utils": "0.0.17",
|
|
35
|
+
"@internal/lint": "0.0.21",
|
|
36
|
+
"@mastra/core": "0.11.0"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
39
|
"@mastra/core": ">=0.10.7-0 <0.11.0-0"
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { ErrorCategory, ErrorDomain, MastraError } from '@mastra/core/error';
|
|
2
|
+
import type { MetricResult, TestInfo } from '@mastra/core/eval';
|
|
3
|
+
import type { EvalRow, PaginationArgs, PaginationInfo } from '@mastra/core/storage';
|
|
4
|
+
import { LegacyEvalsStorage, TABLE_EVALS } from '@mastra/core/storage';
|
|
5
|
+
import type { Redis } from '@upstash/redis';
|
|
6
|
+
import type { StoreOperationsUpstash } from '../operations';
|
|
7
|
+
|
|
8
|
+
function transformEvalRecord(record: Record<string, any>): EvalRow {
|
|
9
|
+
// Parse JSON strings if needed
|
|
10
|
+
let result = record.result;
|
|
11
|
+
if (typeof result === 'string') {
|
|
12
|
+
try {
|
|
13
|
+
result = JSON.parse(result);
|
|
14
|
+
} catch {
|
|
15
|
+
console.warn('Failed to parse result JSON:');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
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:');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
agentName: record.agent_name,
|
|
30
|
+
input: record.input,
|
|
31
|
+
output: record.output,
|
|
32
|
+
result: result as MetricResult,
|
|
33
|
+
metricName: record.metric_name,
|
|
34
|
+
instructions: record.instructions,
|
|
35
|
+
testInfo: testInfo as TestInfo | undefined,
|
|
36
|
+
globalRunId: record.global_run_id,
|
|
37
|
+
runId: record.run_id,
|
|
38
|
+
createdAt:
|
|
39
|
+
typeof record.created_at === 'string'
|
|
40
|
+
? record.created_at
|
|
41
|
+
: record.created_at instanceof Date
|
|
42
|
+
? record.created_at.toISOString()
|
|
43
|
+
: new Date().toISOString(),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export class StoreLegacyEvalsUpstash extends LegacyEvalsStorage {
|
|
48
|
+
private client: Redis;
|
|
49
|
+
private operations: StoreOperationsUpstash;
|
|
50
|
+
constructor({ client, operations }: { client: Redis; operations: StoreOperationsUpstash }) {
|
|
51
|
+
super();
|
|
52
|
+
this.client = client;
|
|
53
|
+
this.operations = operations;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* @deprecated Use getEvals instead
|
|
58
|
+
*/
|
|
59
|
+
async getEvalsByAgentName(agentName: string, type?: 'test' | 'live'): Promise<EvalRow[]> {
|
|
60
|
+
try {
|
|
61
|
+
const pattern = `${TABLE_EVALS}:*`;
|
|
62
|
+
const keys = await this.operations.scanKeys(pattern);
|
|
63
|
+
|
|
64
|
+
// Check if we have any keys before using pipeline
|
|
65
|
+
if (keys.length === 0) {
|
|
66
|
+
return [];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Use pipeline for batch fetching to improve performance
|
|
70
|
+
const pipeline = this.client.pipeline();
|
|
71
|
+
keys.forEach(key => pipeline.get(key));
|
|
72
|
+
const results = await pipeline.exec();
|
|
73
|
+
|
|
74
|
+
// Filter by agent name and remove nulls
|
|
75
|
+
const nonNullRecords = results.filter(
|
|
76
|
+
(record): record is Record<string, any> =>
|
|
77
|
+
record !== null && typeof record === 'object' && 'agent_name' in record && record.agent_name === agentName,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
let filteredEvals = nonNullRecords;
|
|
81
|
+
|
|
82
|
+
if (type === 'test') {
|
|
83
|
+
filteredEvals = filteredEvals.filter(record => {
|
|
84
|
+
if (!record.test_info) return false;
|
|
85
|
+
|
|
86
|
+
// Handle test_info as a JSON string
|
|
87
|
+
try {
|
|
88
|
+
if (typeof record.test_info === 'string') {
|
|
89
|
+
const parsedTestInfo = JSON.parse(record.test_info);
|
|
90
|
+
return parsedTestInfo && typeof parsedTestInfo === 'object' && 'testPath' in parsedTestInfo;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Handle test_info as an object
|
|
94
|
+
return typeof record.test_info === 'object' && 'testPath' in record.test_info;
|
|
95
|
+
} catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
} else if (type === 'live') {
|
|
100
|
+
filteredEvals = filteredEvals.filter(record => {
|
|
101
|
+
if (!record.test_info) return true;
|
|
102
|
+
|
|
103
|
+
// Handle test_info as a JSON string
|
|
104
|
+
try {
|
|
105
|
+
if (typeof record.test_info === 'string') {
|
|
106
|
+
const parsedTestInfo = JSON.parse(record.test_info);
|
|
107
|
+
return !(parsedTestInfo && typeof parsedTestInfo === 'object' && 'testPath' in parsedTestInfo);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Handle test_info as an object
|
|
111
|
+
return !(typeof record.test_info === 'object' && 'testPath' in record.test_info);
|
|
112
|
+
} catch {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Transform to EvalRow format
|
|
119
|
+
return filteredEvals.map(record => transformEvalRecord(record));
|
|
120
|
+
} catch (error) {
|
|
121
|
+
const mastraError = new MastraError(
|
|
122
|
+
{
|
|
123
|
+
id: 'STORAGE_UPSTASH_STORAGE_GET_EVALS_BY_AGENT_NAME_FAILED',
|
|
124
|
+
domain: ErrorDomain.STORAGE,
|
|
125
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
126
|
+
details: { agentName },
|
|
127
|
+
},
|
|
128
|
+
error,
|
|
129
|
+
);
|
|
130
|
+
this.logger?.trackException(mastraError);
|
|
131
|
+
this.logger.error(mastraError.toString());
|
|
132
|
+
return [];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get all evaluations with pagination and total count
|
|
138
|
+
* @param options Pagination and filtering options
|
|
139
|
+
* @returns Object with evals array and total count
|
|
140
|
+
*/
|
|
141
|
+
async getEvals(
|
|
142
|
+
options?: {
|
|
143
|
+
agentName?: string;
|
|
144
|
+
type?: 'test' | 'live';
|
|
145
|
+
} & PaginationArgs,
|
|
146
|
+
): Promise<PaginationInfo & { evals: EvalRow[] }> {
|
|
147
|
+
try {
|
|
148
|
+
// Default pagination parameters
|
|
149
|
+
const { agentName, type, page = 0, perPage = 100, dateRange } = options || {};
|
|
150
|
+
const fromDate = dateRange?.start;
|
|
151
|
+
const toDate = dateRange?.end;
|
|
152
|
+
|
|
153
|
+
// Get all keys that match the evals table pattern using cursor-based scanning
|
|
154
|
+
const pattern = `${TABLE_EVALS}:*`;
|
|
155
|
+
const keys = await this.operations.scanKeys(pattern);
|
|
156
|
+
|
|
157
|
+
// Check if we have any keys before using pipeline
|
|
158
|
+
if (keys.length === 0) {
|
|
159
|
+
return {
|
|
160
|
+
evals: [],
|
|
161
|
+
total: 0,
|
|
162
|
+
page,
|
|
163
|
+
perPage,
|
|
164
|
+
hasMore: false,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Use pipeline for batch fetching to improve performance
|
|
169
|
+
const pipeline = this.client.pipeline();
|
|
170
|
+
keys.forEach(key => pipeline.get(key));
|
|
171
|
+
const results = await pipeline.exec();
|
|
172
|
+
|
|
173
|
+
// Process results and apply filters
|
|
174
|
+
let filteredEvals = results
|
|
175
|
+
.map((result: any) => result as Record<string, any> | null)
|
|
176
|
+
.filter((record): record is Record<string, any> => record !== null && typeof record === 'object');
|
|
177
|
+
|
|
178
|
+
// Apply agent name filter if provided
|
|
179
|
+
if (agentName) {
|
|
180
|
+
filteredEvals = filteredEvals.filter(record => record.agent_name === agentName);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Apply type filter if provided
|
|
184
|
+
if (type === 'test') {
|
|
185
|
+
filteredEvals = filteredEvals.filter(record => {
|
|
186
|
+
if (!record.test_info) return false;
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
if (typeof record.test_info === 'string') {
|
|
190
|
+
const parsedTestInfo = JSON.parse(record.test_info);
|
|
191
|
+
return parsedTestInfo && typeof parsedTestInfo === 'object' && 'testPath' in parsedTestInfo;
|
|
192
|
+
}
|
|
193
|
+
return typeof record.test_info === 'object' && 'testPath' in record.test_info;
|
|
194
|
+
} catch {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
} else if (type === 'live') {
|
|
199
|
+
filteredEvals = filteredEvals.filter(record => {
|
|
200
|
+
if (!record.test_info) return true;
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
if (typeof record.test_info === 'string') {
|
|
204
|
+
const parsedTestInfo = JSON.parse(record.test_info);
|
|
205
|
+
return !(parsedTestInfo && typeof parsedTestInfo === 'object' && 'testPath' in parsedTestInfo);
|
|
206
|
+
}
|
|
207
|
+
return !(typeof record.test_info === 'object' && 'testPath' in record.test_info);
|
|
208
|
+
} catch {
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Apply date filters if provided
|
|
215
|
+
if (fromDate) {
|
|
216
|
+
filteredEvals = filteredEvals.filter(record => {
|
|
217
|
+
const createdAt = new Date(record.created_at || record.createdAt || 0);
|
|
218
|
+
return createdAt.getTime() >= fromDate.getTime();
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (toDate) {
|
|
223
|
+
filteredEvals = filteredEvals.filter(record => {
|
|
224
|
+
const createdAt = new Date(record.created_at || record.createdAt || 0);
|
|
225
|
+
return createdAt.getTime() <= toDate.getTime();
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Sort by creation date (newest first)
|
|
230
|
+
filteredEvals.sort((a, b) => {
|
|
231
|
+
const dateA = new Date(a.created_at || a.createdAt || 0).getTime();
|
|
232
|
+
const dateB = new Date(b.created_at || b.createdAt || 0).getTime();
|
|
233
|
+
return dateB - dateA;
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const total = filteredEvals.length;
|
|
237
|
+
|
|
238
|
+
// Apply pagination
|
|
239
|
+
const start = page * perPage;
|
|
240
|
+
const end = start + perPage;
|
|
241
|
+
const paginatedEvals = filteredEvals.slice(start, end);
|
|
242
|
+
const hasMore = end < total;
|
|
243
|
+
|
|
244
|
+
// Transform to EvalRow format
|
|
245
|
+
const evals = paginatedEvals.map(record => transformEvalRecord(record));
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
evals,
|
|
249
|
+
total,
|
|
250
|
+
page,
|
|
251
|
+
perPage,
|
|
252
|
+
hasMore,
|
|
253
|
+
};
|
|
254
|
+
} catch (error) {
|
|
255
|
+
const { page = 0, perPage = 100 } = options || {};
|
|
256
|
+
const mastraError = new MastraError(
|
|
257
|
+
{
|
|
258
|
+
id: 'STORAGE_UPSTASH_STORAGE_GET_EVALS_FAILED',
|
|
259
|
+
domain: ErrorDomain.STORAGE,
|
|
260
|
+
category: ErrorCategory.THIRD_PARTY,
|
|
261
|
+
details: {
|
|
262
|
+
page,
|
|
263
|
+
perPage,
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
error,
|
|
267
|
+
);
|
|
268
|
+
this.logger.error(mastraError.toString());
|
|
269
|
+
this.logger?.trackException(mastraError);
|
|
270
|
+
return {
|
|
271
|
+
evals: [],
|
|
272
|
+
total: 0,
|
|
273
|
+
page,
|
|
274
|
+
perPage,
|
|
275
|
+
hasMore: false,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|