@mastra/upstash 0.2.2-alpha.6 → 0.2.2-alpha.8
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 +15 -0
- package/dist/_tsup-dts-rollup.d.cts +2 -1
- package/dist/_tsup-dts-rollup.d.ts +2 -1
- package/dist/index.cjs +81 -7
- package/dist/index.js +82 -8
- package/package.json +2 -2
- package/src/storage/index.ts +115 -14
- package/src/storage/upstash.test.ts +116 -2
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
|
|
2
|
-
> @mastra/upstash@0.2.2-alpha.
|
|
2
|
+
> @mastra/upstash@0.2.2-alpha.8 build /home/runner/work/mastra/mastra/stores/upstash
|
|
3
3
|
> tsup src/index.ts --format esm,cjs --experimental-dts --clean --treeshake=smallest --splitting
|
|
4
4
|
|
|
5
5
|
[34mCLI[39m Building entry: src/index.ts
|
|
6
6
|
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
7
7
|
[34mCLI[39m tsup v8.4.0
|
|
8
8
|
[34mTSC[39m Build start
|
|
9
|
-
[32mTSC[39m ⚡️ Build success in
|
|
9
|
+
[32mTSC[39m ⚡️ Build success in 8689ms
|
|
10
10
|
[34mDTS[39m Build start
|
|
11
11
|
[34mCLI[39m Target: es2022
|
|
12
12
|
Analysis will use the bundled TypeScript version 5.8.2
|
|
13
13
|
[36mWriting package typings: /home/runner/work/mastra/mastra/stores/upstash/dist/_tsup-dts-rollup.d.ts[39m
|
|
14
14
|
Analysis will use the bundled TypeScript version 5.8.2
|
|
15
15
|
[36mWriting package typings: /home/runner/work/mastra/mastra/stores/upstash/dist/_tsup-dts-rollup.d.cts[39m
|
|
16
|
-
[32mDTS[39m ⚡️ Build success in
|
|
16
|
+
[32mDTS[39m ⚡️ Build success in 10613ms
|
|
17
17
|
[34mCLI[39m Cleaning output folder
|
|
18
18
|
[34mESM[39m Build start
|
|
19
19
|
[34mCJS[39m Build start
|
|
20
|
-
[32mESM[39m [1mdist/index.js [22m[
|
|
21
|
-
[32mESM[39m ⚡️ Build success in
|
|
22
|
-
[32mCJS[39m [1mdist/index.cjs [22m[
|
|
23
|
-
[32mCJS[39m ⚡️ Build success in
|
|
20
|
+
[32mESM[39m [1mdist/index.js [22m[32m24.02 KB[39m
|
|
21
|
+
[32mESM[39m ⚡️ Build success in 1171ms
|
|
22
|
+
[32mCJS[39m [1mdist/index.cjs [22m[32m24.11 KB[39m
|
|
23
|
+
[32mCJS[39m ⚡️ Build success in 1172ms
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @mastra/upstash
|
|
2
2
|
|
|
3
|
+
## 0.2.2-alpha.8
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- b7c1319: Upstash getTraces method
|
|
8
|
+
- Updated dependencies [8df4a77]
|
|
9
|
+
- @mastra/core@0.8.0-alpha.8
|
|
10
|
+
|
|
11
|
+
## 0.2.2-alpha.7
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- Updated dependencies [febc8a6]
|
|
16
|
+
- @mastra/core@0.8.0-alpha.7
|
|
17
|
+
|
|
3
18
|
## 0.2.2-alpha.6
|
|
4
19
|
|
|
5
20
|
### Patch Changes
|
|
@@ -44,7 +44,7 @@ declare class UpstashStore extends MastraStorage {
|
|
|
44
44
|
}): Promise<void>;
|
|
45
45
|
getEvalsByAgentName(agentName: string, type?: 'test' | 'live'): Promise<EvalRow[]>;
|
|
46
46
|
private transformEvalRecord;
|
|
47
|
-
getTraces(
|
|
47
|
+
getTraces({ name, scope, page, perPage, attributes, filters, }?: {
|
|
48
48
|
name?: string;
|
|
49
49
|
scope?: string;
|
|
50
50
|
page: number;
|
|
@@ -52,6 +52,7 @@ declare class UpstashStore extends MastraStorage {
|
|
|
52
52
|
attributes?: Record<string, string>;
|
|
53
53
|
filters?: Record<string, any>;
|
|
54
54
|
}): Promise<any[]>;
|
|
55
|
+
private parseJSON;
|
|
55
56
|
private redis;
|
|
56
57
|
constructor(config: UpstashConfig);
|
|
57
58
|
private getKey;
|
|
@@ -44,7 +44,7 @@ declare class UpstashStore extends MastraStorage {
|
|
|
44
44
|
}): Promise<void>;
|
|
45
45
|
getEvalsByAgentName(agentName: string, type?: 'test' | 'live'): Promise<EvalRow[]>;
|
|
46
46
|
private transformEvalRecord;
|
|
47
|
-
getTraces(
|
|
47
|
+
getTraces({ name, scope, page, perPage, attributes, filters, }?: {
|
|
48
48
|
name?: string;
|
|
49
49
|
scope?: string;
|
|
50
50
|
page: number;
|
|
@@ -52,6 +52,7 @@ declare class UpstashStore extends MastraStorage {
|
|
|
52
52
|
attributes?: Record<string, string>;
|
|
53
53
|
filters?: Record<string, any>;
|
|
54
54
|
}): Promise<any[]>;
|
|
55
|
+
private parseJSON;
|
|
55
56
|
private redis;
|
|
56
57
|
constructor(config: UpstashConfig);
|
|
57
58
|
private getKey;
|
package/dist/index.cjs
CHANGED
|
@@ -34,7 +34,7 @@ var UpstashStore = class extends storage.MastraStorage {
|
|
|
34
34
|
return parsedTestInfo && typeof parsedTestInfo === "object" && "testPath" in parsedTestInfo;
|
|
35
35
|
}
|
|
36
36
|
return typeof record.test_info === "object" && "testPath" in record.test_info;
|
|
37
|
-
} catch
|
|
37
|
+
} catch {
|
|
38
38
|
return false;
|
|
39
39
|
}
|
|
40
40
|
});
|
|
@@ -47,7 +47,7 @@ var UpstashStore = class extends storage.MastraStorage {
|
|
|
47
47
|
return !(parsedTestInfo && typeof parsedTestInfo === "object" && "testPath" in parsedTestInfo);
|
|
48
48
|
}
|
|
49
49
|
return !(typeof record.test_info === "object" && "testPath" in record.test_info);
|
|
50
|
-
} catch
|
|
50
|
+
} catch {
|
|
51
51
|
return true;
|
|
52
52
|
}
|
|
53
53
|
});
|
|
@@ -63,7 +63,7 @@ var UpstashStore = class extends storage.MastraStorage {
|
|
|
63
63
|
if (typeof result === "string") {
|
|
64
64
|
try {
|
|
65
65
|
result = JSON.parse(result);
|
|
66
|
-
} catch
|
|
66
|
+
} catch {
|
|
67
67
|
console.warn("Failed to parse result JSON:");
|
|
68
68
|
}
|
|
69
69
|
}
|
|
@@ -71,7 +71,7 @@ var UpstashStore = class extends storage.MastraStorage {
|
|
|
71
71
|
if (typeof testInfo === "string") {
|
|
72
72
|
try {
|
|
73
73
|
testInfo = JSON.parse(testInfo);
|
|
74
|
-
} catch
|
|
74
|
+
} catch {
|
|
75
75
|
console.warn("Failed to parse test_info JSON:");
|
|
76
76
|
}
|
|
77
77
|
}
|
|
@@ -88,8 +88,82 @@ var UpstashStore = class extends storage.MastraStorage {
|
|
|
88
88
|
createdAt: typeof record.created_at === "string" ? record.created_at : record.created_at instanceof Date ? record.created_at.toISOString() : (/* @__PURE__ */ new Date()).toISOString()
|
|
89
89
|
};
|
|
90
90
|
}
|
|
91
|
-
getTraces(
|
|
92
|
-
|
|
91
|
+
async getTraces({
|
|
92
|
+
name,
|
|
93
|
+
scope,
|
|
94
|
+
page = 0,
|
|
95
|
+
perPage = 100,
|
|
96
|
+
attributes,
|
|
97
|
+
filters
|
|
98
|
+
} = {
|
|
99
|
+
page: 0,
|
|
100
|
+
perPage: 100
|
|
101
|
+
}) {
|
|
102
|
+
try {
|
|
103
|
+
const pattern = `${storage.TABLE_TRACES}:*`;
|
|
104
|
+
const keys = await this.redis.keys(pattern);
|
|
105
|
+
const traceRecords = await Promise.all(
|
|
106
|
+
keys.map(async (key) => {
|
|
107
|
+
const data = await this.redis.get(key);
|
|
108
|
+
return data;
|
|
109
|
+
})
|
|
110
|
+
);
|
|
111
|
+
let filteredTraces = traceRecords.filter(
|
|
112
|
+
(record) => record !== null && typeof record === "object"
|
|
113
|
+
);
|
|
114
|
+
if (name) {
|
|
115
|
+
filteredTraces = filteredTraces.filter((record) => record.name?.toLowerCase().startsWith(name.toLowerCase()));
|
|
116
|
+
}
|
|
117
|
+
if (scope) {
|
|
118
|
+
filteredTraces = filteredTraces.filter((record) => record.scope === scope);
|
|
119
|
+
}
|
|
120
|
+
if (attributes) {
|
|
121
|
+
filteredTraces = filteredTraces.filter((record) => {
|
|
122
|
+
const recordAttributes = record.attributes;
|
|
123
|
+
if (!recordAttributes) return false;
|
|
124
|
+
const parsedAttributes = typeof recordAttributes === "string" ? JSON.parse(recordAttributes) : recordAttributes;
|
|
125
|
+
return Object.entries(attributes).every(([key, value]) => parsedAttributes[key] === value);
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
if (filters) {
|
|
129
|
+
filteredTraces = filteredTraces.filter(
|
|
130
|
+
(record) => Object.entries(filters).every(([key, value]) => record[key] === value)
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
filteredTraces.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
134
|
+
const start = page * perPage;
|
|
135
|
+
const end = start + perPage;
|
|
136
|
+
const paginatedTraces = filteredTraces.slice(start, end);
|
|
137
|
+
return paginatedTraces.map((record) => ({
|
|
138
|
+
id: record.id,
|
|
139
|
+
parentSpanId: record.parentSpanId,
|
|
140
|
+
traceId: record.traceId,
|
|
141
|
+
name: record.name,
|
|
142
|
+
scope: record.scope,
|
|
143
|
+
kind: record.kind,
|
|
144
|
+
status: this.parseJSON(record.status),
|
|
145
|
+
events: this.parseJSON(record.events),
|
|
146
|
+
links: this.parseJSON(record.links),
|
|
147
|
+
attributes: this.parseJSON(record.attributes),
|
|
148
|
+
startTime: record.startTime,
|
|
149
|
+
endTime: record.endTime,
|
|
150
|
+
other: this.parseJSON(record.other),
|
|
151
|
+
createdAt: this.ensureDate(record.createdAt)
|
|
152
|
+
}));
|
|
153
|
+
} catch (error) {
|
|
154
|
+
console.error("Failed to get traces:", error);
|
|
155
|
+
return [];
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
parseJSON(value) {
|
|
159
|
+
if (typeof value === "string") {
|
|
160
|
+
try {
|
|
161
|
+
return JSON.parse(value);
|
|
162
|
+
} catch {
|
|
163
|
+
return value;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return value;
|
|
93
167
|
}
|
|
94
168
|
redis;
|
|
95
169
|
constructor(config) {
|
|
@@ -319,7 +393,7 @@ var UpstashStore = class extends storage.MastraStorage {
|
|
|
319
393
|
if (typeof parsedSnapshot === "string") {
|
|
320
394
|
try {
|
|
321
395
|
parsedSnapshot = JSON.parse(w.snapshot);
|
|
322
|
-
} catch
|
|
396
|
+
} catch {
|
|
323
397
|
console.warn(`Failed to parse snapshot for workflow ${w.workflow_name}:`);
|
|
324
398
|
}
|
|
325
399
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MastraStorage, TABLE_EVALS, TABLE_MESSAGES, TABLE_WORKFLOW_SNAPSHOT, TABLE_THREADS } from '@mastra/core/storage';
|
|
1
|
+
import { MastraStorage, TABLE_EVALS, TABLE_TRACES, TABLE_MESSAGES, TABLE_WORKFLOW_SNAPSHOT, 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';
|
|
@@ -32,7 +32,7 @@ var UpstashStore = class extends MastraStorage {
|
|
|
32
32
|
return parsedTestInfo && typeof parsedTestInfo === "object" && "testPath" in parsedTestInfo;
|
|
33
33
|
}
|
|
34
34
|
return typeof record.test_info === "object" && "testPath" in record.test_info;
|
|
35
|
-
} catch
|
|
35
|
+
} catch {
|
|
36
36
|
return false;
|
|
37
37
|
}
|
|
38
38
|
});
|
|
@@ -45,7 +45,7 @@ var UpstashStore = class extends MastraStorage {
|
|
|
45
45
|
return !(parsedTestInfo && typeof parsedTestInfo === "object" && "testPath" in parsedTestInfo);
|
|
46
46
|
}
|
|
47
47
|
return !(typeof record.test_info === "object" && "testPath" in record.test_info);
|
|
48
|
-
} catch
|
|
48
|
+
} catch {
|
|
49
49
|
return true;
|
|
50
50
|
}
|
|
51
51
|
});
|
|
@@ -61,7 +61,7 @@ var UpstashStore = class extends MastraStorage {
|
|
|
61
61
|
if (typeof result === "string") {
|
|
62
62
|
try {
|
|
63
63
|
result = JSON.parse(result);
|
|
64
|
-
} catch
|
|
64
|
+
} catch {
|
|
65
65
|
console.warn("Failed to parse result JSON:");
|
|
66
66
|
}
|
|
67
67
|
}
|
|
@@ -69,7 +69,7 @@ var UpstashStore = class extends MastraStorage {
|
|
|
69
69
|
if (typeof testInfo === "string") {
|
|
70
70
|
try {
|
|
71
71
|
testInfo = JSON.parse(testInfo);
|
|
72
|
-
} catch
|
|
72
|
+
} catch {
|
|
73
73
|
console.warn("Failed to parse test_info JSON:");
|
|
74
74
|
}
|
|
75
75
|
}
|
|
@@ -86,8 +86,82 @@ var UpstashStore = class extends MastraStorage {
|
|
|
86
86
|
createdAt: typeof record.created_at === "string" ? record.created_at : record.created_at instanceof Date ? record.created_at.toISOString() : (/* @__PURE__ */ new Date()).toISOString()
|
|
87
87
|
};
|
|
88
88
|
}
|
|
89
|
-
getTraces(
|
|
90
|
-
|
|
89
|
+
async getTraces({
|
|
90
|
+
name,
|
|
91
|
+
scope,
|
|
92
|
+
page = 0,
|
|
93
|
+
perPage = 100,
|
|
94
|
+
attributes,
|
|
95
|
+
filters
|
|
96
|
+
} = {
|
|
97
|
+
page: 0,
|
|
98
|
+
perPage: 100
|
|
99
|
+
}) {
|
|
100
|
+
try {
|
|
101
|
+
const pattern = `${TABLE_TRACES}:*`;
|
|
102
|
+
const keys = await this.redis.keys(pattern);
|
|
103
|
+
const traceRecords = await Promise.all(
|
|
104
|
+
keys.map(async (key) => {
|
|
105
|
+
const data = await this.redis.get(key);
|
|
106
|
+
return data;
|
|
107
|
+
})
|
|
108
|
+
);
|
|
109
|
+
let filteredTraces = traceRecords.filter(
|
|
110
|
+
(record) => record !== null && typeof record === "object"
|
|
111
|
+
);
|
|
112
|
+
if (name) {
|
|
113
|
+
filteredTraces = filteredTraces.filter((record) => record.name?.toLowerCase().startsWith(name.toLowerCase()));
|
|
114
|
+
}
|
|
115
|
+
if (scope) {
|
|
116
|
+
filteredTraces = filteredTraces.filter((record) => record.scope === scope);
|
|
117
|
+
}
|
|
118
|
+
if (attributes) {
|
|
119
|
+
filteredTraces = filteredTraces.filter((record) => {
|
|
120
|
+
const recordAttributes = record.attributes;
|
|
121
|
+
if (!recordAttributes) return false;
|
|
122
|
+
const parsedAttributes = typeof recordAttributes === "string" ? JSON.parse(recordAttributes) : recordAttributes;
|
|
123
|
+
return Object.entries(attributes).every(([key, value]) => parsedAttributes[key] === value);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
if (filters) {
|
|
127
|
+
filteredTraces = filteredTraces.filter(
|
|
128
|
+
(record) => Object.entries(filters).every(([key, value]) => record[key] === value)
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
filteredTraces.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
132
|
+
const start = page * perPage;
|
|
133
|
+
const end = start + perPage;
|
|
134
|
+
const paginatedTraces = filteredTraces.slice(start, end);
|
|
135
|
+
return paginatedTraces.map((record) => ({
|
|
136
|
+
id: record.id,
|
|
137
|
+
parentSpanId: record.parentSpanId,
|
|
138
|
+
traceId: record.traceId,
|
|
139
|
+
name: record.name,
|
|
140
|
+
scope: record.scope,
|
|
141
|
+
kind: record.kind,
|
|
142
|
+
status: this.parseJSON(record.status),
|
|
143
|
+
events: this.parseJSON(record.events),
|
|
144
|
+
links: this.parseJSON(record.links),
|
|
145
|
+
attributes: this.parseJSON(record.attributes),
|
|
146
|
+
startTime: record.startTime,
|
|
147
|
+
endTime: record.endTime,
|
|
148
|
+
other: this.parseJSON(record.other),
|
|
149
|
+
createdAt: this.ensureDate(record.createdAt)
|
|
150
|
+
}));
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.error("Failed to get traces:", error);
|
|
153
|
+
return [];
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
parseJSON(value) {
|
|
157
|
+
if (typeof value === "string") {
|
|
158
|
+
try {
|
|
159
|
+
return JSON.parse(value);
|
|
160
|
+
} catch {
|
|
161
|
+
return value;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return value;
|
|
91
165
|
}
|
|
92
166
|
redis;
|
|
93
167
|
constructor(config) {
|
|
@@ -317,7 +391,7 @@ var UpstashStore = class extends MastraStorage {
|
|
|
317
391
|
if (typeof parsedSnapshot === "string") {
|
|
318
392
|
try {
|
|
319
393
|
parsedSnapshot = JSON.parse(w.snapshot);
|
|
320
|
-
} catch
|
|
394
|
+
} catch {
|
|
321
395
|
console.warn(`Failed to parse snapshot for workflow ${w.workflow_name}:`);
|
|
322
396
|
}
|
|
323
397
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mastra/upstash",
|
|
3
|
-
"version": "0.2.2-alpha.
|
|
3
|
+
"version": "0.2.2-alpha.8",
|
|
4
4
|
"description": "Upstash provider for Mastra - includes both vector and db storage capabilities",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@upstash/redis": "^1.34.5",
|
|
23
23
|
"@upstash/vector": "^1.2.1",
|
|
24
|
-
"@mastra/core": "^0.8.0-alpha.
|
|
24
|
+
"@mastra/core": "^0.8.0-alpha.8"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@microsoft/api-extractor": "^7.52.1",
|
package/src/storage/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
TABLE_THREADS,
|
|
7
7
|
TABLE_WORKFLOW_SNAPSHOT,
|
|
8
8
|
TABLE_EVALS,
|
|
9
|
+
TABLE_TRACES,
|
|
9
10
|
} from '@mastra/core/storage';
|
|
10
11
|
import type { TABLE_NAMES, StorageColumn, StorageGetMessagesArg, EvalRow } from '@mastra/core/storage';
|
|
11
12
|
import type { WorkflowRunState } from '@mastra/core/workflows';
|
|
@@ -57,7 +58,7 @@ export class UpstashStore extends MastraStorage {
|
|
|
57
58
|
|
|
58
59
|
// Handle test_info as an object
|
|
59
60
|
return typeof record.test_info === 'object' && 'testPath' in record.test_info;
|
|
60
|
-
} catch
|
|
61
|
+
} catch {
|
|
61
62
|
return false;
|
|
62
63
|
}
|
|
63
64
|
});
|
|
@@ -74,7 +75,7 @@ export class UpstashStore extends MastraStorage {
|
|
|
74
75
|
|
|
75
76
|
// Handle test_info as an object
|
|
76
77
|
return !(typeof record.test_info === 'object' && 'testPath' in record.test_info);
|
|
77
|
-
} catch
|
|
78
|
+
} catch {
|
|
78
79
|
return true;
|
|
79
80
|
}
|
|
80
81
|
});
|
|
@@ -94,7 +95,7 @@ export class UpstashStore extends MastraStorage {
|
|
|
94
95
|
if (typeof result === 'string') {
|
|
95
96
|
try {
|
|
96
97
|
result = JSON.parse(result);
|
|
97
|
-
} catch
|
|
98
|
+
} catch {
|
|
98
99
|
console.warn('Failed to parse result JSON:');
|
|
99
100
|
}
|
|
100
101
|
}
|
|
@@ -103,7 +104,7 @@ export class UpstashStore extends MastraStorage {
|
|
|
103
104
|
if (typeof testInfo === 'string') {
|
|
104
105
|
try {
|
|
105
106
|
testInfo = JSON.parse(testInfo);
|
|
106
|
-
} catch
|
|
107
|
+
} catch {
|
|
107
108
|
console.warn('Failed to parse test_info JSON:');
|
|
108
109
|
}
|
|
109
110
|
}
|
|
@@ -127,15 +128,115 @@ export class UpstashStore extends MastraStorage {
|
|
|
127
128
|
};
|
|
128
129
|
}
|
|
129
130
|
|
|
130
|
-
getTraces(
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
131
|
+
async getTraces(
|
|
132
|
+
{
|
|
133
|
+
name,
|
|
134
|
+
scope,
|
|
135
|
+
page = 0,
|
|
136
|
+
perPage = 100,
|
|
137
|
+
attributes,
|
|
138
|
+
filters,
|
|
139
|
+
}: {
|
|
140
|
+
name?: string;
|
|
141
|
+
scope?: string;
|
|
142
|
+
page: number;
|
|
143
|
+
perPage: number;
|
|
144
|
+
attributes?: Record<string, string>;
|
|
145
|
+
filters?: Record<string, any>;
|
|
146
|
+
} = {
|
|
147
|
+
page: 0,
|
|
148
|
+
perPage: 100,
|
|
149
|
+
},
|
|
150
|
+
): Promise<any[]> {
|
|
151
|
+
try {
|
|
152
|
+
// Get all keys that match the traces table pattern
|
|
153
|
+
const pattern = `${TABLE_TRACES}:*`;
|
|
154
|
+
const keys = await this.redis.keys(pattern);
|
|
155
|
+
|
|
156
|
+
// Fetch all trace records
|
|
157
|
+
const traceRecords = await Promise.all(
|
|
158
|
+
keys.map(async key => {
|
|
159
|
+
const data = await this.redis.get<Record<string, any>>(key);
|
|
160
|
+
return data;
|
|
161
|
+
}),
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
// Filter out nulls and apply filters
|
|
165
|
+
let filteredTraces = traceRecords.filter(
|
|
166
|
+
(record): record is Record<string, any> => record !== null && typeof record === 'object',
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
// Apply name filter if provided
|
|
170
|
+
if (name) {
|
|
171
|
+
filteredTraces = filteredTraces.filter(record => record.name?.toLowerCase().startsWith(name.toLowerCase()));
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Apply scope filter if provided
|
|
175
|
+
if (scope) {
|
|
176
|
+
filteredTraces = filteredTraces.filter(record => record.scope === scope);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Apply attributes filter if provided
|
|
180
|
+
if (attributes) {
|
|
181
|
+
filteredTraces = filteredTraces.filter(record => {
|
|
182
|
+
const recordAttributes = record.attributes;
|
|
183
|
+
if (!recordAttributes) return false;
|
|
184
|
+
|
|
185
|
+
// Parse attributes if stored as string
|
|
186
|
+
const parsedAttributes =
|
|
187
|
+
typeof recordAttributes === 'string' ? JSON.parse(recordAttributes) : recordAttributes;
|
|
188
|
+
|
|
189
|
+
return Object.entries(attributes).every(([key, value]) => parsedAttributes[key] === value);
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Apply custom filters if provided
|
|
194
|
+
if (filters) {
|
|
195
|
+
filteredTraces = filteredTraces.filter(record =>
|
|
196
|
+
Object.entries(filters).every(([key, value]) => record[key] === value),
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Sort traces by creation date (newest first)
|
|
201
|
+
filteredTraces.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
202
|
+
|
|
203
|
+
// Apply pagination
|
|
204
|
+
const start = page * perPage;
|
|
205
|
+
const end = start + perPage;
|
|
206
|
+
const paginatedTraces = filteredTraces.slice(start, end);
|
|
207
|
+
|
|
208
|
+
// Transform and return the traces
|
|
209
|
+
return paginatedTraces.map(record => ({
|
|
210
|
+
id: record.id,
|
|
211
|
+
parentSpanId: record.parentSpanId,
|
|
212
|
+
traceId: record.traceId,
|
|
213
|
+
name: record.name,
|
|
214
|
+
scope: record.scope,
|
|
215
|
+
kind: record.kind,
|
|
216
|
+
status: this.parseJSON(record.status),
|
|
217
|
+
events: this.parseJSON(record.events),
|
|
218
|
+
links: this.parseJSON(record.links),
|
|
219
|
+
attributes: this.parseJSON(record.attributes),
|
|
220
|
+
startTime: record.startTime,
|
|
221
|
+
endTime: record.endTime,
|
|
222
|
+
other: this.parseJSON(record.other),
|
|
223
|
+
createdAt: this.ensureDate(record.createdAt),
|
|
224
|
+
}));
|
|
225
|
+
} catch (error) {
|
|
226
|
+
console.error('Failed to get traces:', error);
|
|
227
|
+
return [];
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private parseJSON(value: any): any {
|
|
232
|
+
if (typeof value === 'string') {
|
|
233
|
+
try {
|
|
234
|
+
return JSON.parse(value);
|
|
235
|
+
} catch {
|
|
236
|
+
return value;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return value;
|
|
139
240
|
}
|
|
140
241
|
|
|
141
242
|
private redis: Redis;
|
|
@@ -485,7 +586,7 @@ export class UpstashStore extends MastraStorage {
|
|
|
485
586
|
if (typeof parsedSnapshot === 'string') {
|
|
486
587
|
try {
|
|
487
588
|
parsedSnapshot = JSON.parse(w!.snapshot as string) as WorkflowRunState;
|
|
488
|
-
} catch
|
|
589
|
+
} catch {
|
|
489
590
|
// If parsing fails, return the raw snapshot string
|
|
490
591
|
console.warn(`Failed to parse snapshot for workflow ${w!.workflow_name}:`);
|
|
491
592
|
}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { randomUUID } from 'crypto';
|
|
2
|
-
import type { MetricResult, TestInfo } from '@mastra/core/eval';
|
|
3
2
|
import type { MessageType } from '@mastra/core/memory';
|
|
4
3
|
import type { TABLE_NAMES } from '@mastra/core/storage';
|
|
5
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
TABLE_MESSAGES,
|
|
6
|
+
TABLE_THREADS,
|
|
7
|
+
TABLE_WORKFLOW_SNAPSHOT,
|
|
8
|
+
TABLE_EVALS,
|
|
9
|
+
TABLE_TRACES,
|
|
10
|
+
} from '@mastra/core/storage';
|
|
6
11
|
import type { WorkflowRunState } from '@mastra/core/workflows';
|
|
7
12
|
import { describe, it, expect, beforeAll, beforeEach, afterAll, vi } from 'vitest';
|
|
8
13
|
|
|
@@ -55,6 +60,23 @@ const createSampleWorkflowSnapshot = (status: string, createdAt?: Date) => {
|
|
|
55
60
|
return { snapshot, runId, stepId };
|
|
56
61
|
};
|
|
57
62
|
|
|
63
|
+
const createSampleTrace = (name: string, scope?: string, attributes?: Record<string, string>) => ({
|
|
64
|
+
id: `trace-${randomUUID()}`,
|
|
65
|
+
parentSpanId: `span-${randomUUID()}`,
|
|
66
|
+
traceId: `trace-${randomUUID()}`,
|
|
67
|
+
name,
|
|
68
|
+
scope,
|
|
69
|
+
kind: 'internal',
|
|
70
|
+
status: JSON.stringify({ code: 'success' }),
|
|
71
|
+
events: JSON.stringify([{ name: 'start', timestamp: Date.now() }]),
|
|
72
|
+
links: JSON.stringify([]),
|
|
73
|
+
attributes: attributes ? JSON.stringify(attributes) : undefined,
|
|
74
|
+
startTime: new Date().toISOString(),
|
|
75
|
+
endTime: new Date().toISOString(),
|
|
76
|
+
other: JSON.stringify({ custom: 'data' }),
|
|
77
|
+
createdAt: new Date().toISOString(),
|
|
78
|
+
});
|
|
79
|
+
|
|
58
80
|
const createSampleEval = (agentName: string, isTest = false) => {
|
|
59
81
|
const testInfo = isTest ? { testPath: 'test/path.ts', testName: 'Test Name' } : undefined;
|
|
60
82
|
|
|
@@ -98,6 +120,7 @@ describe('UpstashStore', () => {
|
|
|
98
120
|
await store.clearTable({ tableName: TABLE_MESSAGES });
|
|
99
121
|
await store.clearTable({ tableName: TABLE_WORKFLOW_SNAPSHOT });
|
|
100
122
|
await store.clearTable({ tableName: TABLE_EVALS });
|
|
123
|
+
await store.clearTable({ tableName: TABLE_TRACES });
|
|
101
124
|
});
|
|
102
125
|
|
|
103
126
|
describe('Table Operations', () => {
|
|
@@ -321,6 +344,97 @@ describe('UpstashStore', () => {
|
|
|
321
344
|
});
|
|
322
345
|
});
|
|
323
346
|
|
|
347
|
+
describe('Trace Operations', () => {
|
|
348
|
+
beforeEach(async () => {
|
|
349
|
+
await store.clearTable({ tableName: TABLE_TRACES });
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it('should retrieve traces with filtering and pagination', async () => {
|
|
353
|
+
// Insert sample traces
|
|
354
|
+
const trace1 = createSampleTrace('test-trace-1', 'scope1', { env: 'prod' });
|
|
355
|
+
const trace2 = createSampleTrace('test-trace-2', 'scope1', { env: 'dev' });
|
|
356
|
+
const trace3 = createSampleTrace('other-trace', 'scope2', { env: 'prod' });
|
|
357
|
+
|
|
358
|
+
await store.insert({ tableName: TABLE_TRACES, record: trace1 });
|
|
359
|
+
await store.insert({ tableName: TABLE_TRACES, record: trace2 });
|
|
360
|
+
await store.insert({ tableName: TABLE_TRACES, record: trace3 });
|
|
361
|
+
|
|
362
|
+
// Test name filter
|
|
363
|
+
const testTraces = await store.getTraces({ name: 'test-trace', page: 0, perPage: 10 });
|
|
364
|
+
expect(testTraces).toHaveLength(2);
|
|
365
|
+
expect(testTraces.map(t => t.name)).toContain('test-trace-1');
|
|
366
|
+
expect(testTraces.map(t => t.name)).toContain('test-trace-2');
|
|
367
|
+
|
|
368
|
+
// Test scope filter
|
|
369
|
+
const scope1Traces = await store.getTraces({ scope: 'scope1', page: 0, perPage: 10 });
|
|
370
|
+
expect(scope1Traces).toHaveLength(2);
|
|
371
|
+
expect(scope1Traces.every(t => t.scope === 'scope1')).toBe(true);
|
|
372
|
+
|
|
373
|
+
// Test attributes filter
|
|
374
|
+
const prodTraces = await store.getTraces({
|
|
375
|
+
attributes: { env: 'prod' },
|
|
376
|
+
page: 0,
|
|
377
|
+
perPage: 10,
|
|
378
|
+
});
|
|
379
|
+
expect(prodTraces).toHaveLength(2);
|
|
380
|
+
expect(prodTraces.every(t => t.attributes.env === 'prod')).toBe(true);
|
|
381
|
+
|
|
382
|
+
// Test pagination
|
|
383
|
+
const pagedTraces = await store.getTraces({ page: 0, perPage: 2 });
|
|
384
|
+
expect(pagedTraces).toHaveLength(2);
|
|
385
|
+
|
|
386
|
+
// Test combined filters
|
|
387
|
+
const combinedTraces = await store.getTraces({
|
|
388
|
+
scope: 'scope1',
|
|
389
|
+
attributes: { env: 'prod' },
|
|
390
|
+
page: 0,
|
|
391
|
+
perPage: 10,
|
|
392
|
+
});
|
|
393
|
+
expect(combinedTraces).toHaveLength(1);
|
|
394
|
+
expect(combinedTraces[0].name).toBe('test-trace-1');
|
|
395
|
+
|
|
396
|
+
// Verify trace object structure
|
|
397
|
+
const trace = combinedTraces[0];
|
|
398
|
+
expect(trace).toHaveProperty('id');
|
|
399
|
+
expect(trace).toHaveProperty('parentSpanId');
|
|
400
|
+
expect(trace).toHaveProperty('traceId');
|
|
401
|
+
expect(trace).toHaveProperty('name');
|
|
402
|
+
expect(trace).toHaveProperty('scope');
|
|
403
|
+
expect(trace).toHaveProperty('kind');
|
|
404
|
+
expect(trace).toHaveProperty('status');
|
|
405
|
+
expect(trace).toHaveProperty('events');
|
|
406
|
+
expect(trace).toHaveProperty('links');
|
|
407
|
+
expect(trace).toHaveProperty('attributes');
|
|
408
|
+
expect(trace).toHaveProperty('startTime');
|
|
409
|
+
expect(trace).toHaveProperty('endTime');
|
|
410
|
+
expect(trace).toHaveProperty('other');
|
|
411
|
+
expect(trace).toHaveProperty('createdAt');
|
|
412
|
+
|
|
413
|
+
// Verify JSON fields are parsed
|
|
414
|
+
expect(typeof trace.status).toBe('object');
|
|
415
|
+
expect(typeof trace.events).toBe('object');
|
|
416
|
+
expect(typeof trace.links).toBe('object');
|
|
417
|
+
expect(typeof trace.attributes).toBe('object');
|
|
418
|
+
expect(typeof trace.other).toBe('object');
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it('should handle empty results', async () => {
|
|
422
|
+
const traces = await store.getTraces({ page: 0, perPage: 10 });
|
|
423
|
+
expect(traces).toHaveLength(0);
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
it('should handle invalid JSON in fields', async () => {
|
|
427
|
+
const trace = createSampleTrace('test-trace');
|
|
428
|
+
trace.status = 'invalid-json{'; // Intentionally invalid JSON
|
|
429
|
+
|
|
430
|
+
await store.insert({ tableName: TABLE_TRACES, record: trace });
|
|
431
|
+
const traces = await store.getTraces({ page: 0, perPage: 10 });
|
|
432
|
+
|
|
433
|
+
expect(traces).toHaveLength(1);
|
|
434
|
+
expect(traces[0].status).toBe('invalid-json{'); // Should return raw string when JSON parsing fails
|
|
435
|
+
});
|
|
436
|
+
});
|
|
437
|
+
|
|
324
438
|
describe('Workflow Operations', () => {
|
|
325
439
|
const testNamespace = 'test';
|
|
326
440
|
const testWorkflow = 'test-workflow';
|