@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.
@@ -1,23 +1,23 @@
1
1
 
2
- > @mastra/upstash@0.2.2-alpha.6 build /home/runner/work/mastra/mastra/stores/upstash
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
  CLI Building entry: src/index.ts
6
6
  CLI Using tsconfig: tsconfig.json
7
7
  CLI tsup v8.4.0
8
8
  TSC Build start
9
- TSC ⚡️ Build success in 9094ms
9
+ TSC ⚡️ Build success in 8689ms
10
10
  DTS Build start
11
11
  CLI Target: es2022
12
12
  Analysis will use the bundled TypeScript version 5.8.2
13
13
  Writing package typings: /home/runner/work/mastra/mastra/stores/upstash/dist/_tsup-dts-rollup.d.ts
14
14
  Analysis will use the bundled TypeScript version 5.8.2
15
15
  Writing package typings: /home/runner/work/mastra/mastra/stores/upstash/dist/_tsup-dts-rollup.d.cts
16
- DTS ⚡️ Build success in 10401ms
16
+ DTS ⚡️ Build success in 10613ms
17
17
  CLI Cleaning output folder
18
18
  ESM Build start
19
19
  CJS Build start
20
- ESM dist/index.js 21.62 KB
21
- ESM ⚡️ Build success in 704ms
22
- CJS dist/index.cjs 21.72 KB
23
- CJS ⚡️ Build success in 703ms
20
+ ESM dist/index.js 24.02 KB
21
+ ESM ⚡️ Build success in 1171ms
22
+ CJS dist/index.cjs 24.11 KB
23
+ CJS ⚡️ 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(_input: {
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(_input: {
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 (_e) {
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 (_e) {
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 (_e) {
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 (_e) {
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(_input) {
92
- throw new Error("Method not implemented.");
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 (_e) {
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 (_e) {
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 (_e) {
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 (_e) {
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 (_e) {
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(_input) {
90
- throw new Error("Method not implemented.");
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 (_e) {
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.6",
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.6"
24
+ "@mastra/core": "^0.8.0-alpha.8"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@microsoft/api-extractor": "^7.52.1",
@@ -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 (_e) {
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 (_e) {
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 (_e) {
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 (_e) {
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(_input: {
131
- name?: string;
132
- scope?: string;
133
- page: number;
134
- perPage: number;
135
- attributes?: Record<string, string>;
136
- filters?: Record<string, any>;
137
- }): Promise<any[]> {
138
- throw new Error('Method not implemented.');
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 (_e) {
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 { TABLE_MESSAGES, TABLE_THREADS, TABLE_WORKFLOW_SNAPSHOT, TABLE_EVALS } from '@mastra/core/storage';
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';