@axiom-lattice/pg-stores 1.0.74 → 1.0.76
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 +10 -10
- package/CHANGELOG.md +15 -0
- package/dist/index.d.mts +4 -3
- package/dist/index.d.ts +4 -3
- package/dist/index.js +66 -51
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +62 -50
- package/dist/index.mjs.map +1 -1
- package/jest.config.js +19 -0
- package/package.json +3 -2
- package/src/__tests__/PostgreSQLWorkflowTrackingStore.test.ts +233 -0
- package/src/stores/PostgreSQLWorkflowTrackingStore.ts +59 -48
package/jest.config.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
preset: "ts-jest",
|
|
3
|
+
testEnvironment: "node",
|
|
4
|
+
roots: ["<rootDir>/src"],
|
|
5
|
+
testMatch: ["**/__tests__/**/*.test.ts"],
|
|
6
|
+
transform: {
|
|
7
|
+
"^.+\\.ts$": [
|
|
8
|
+
"ts-jest",
|
|
9
|
+
{
|
|
10
|
+
isolatedModules: true,
|
|
11
|
+
},
|
|
12
|
+
],
|
|
13
|
+
},
|
|
14
|
+
moduleNameMapper: {
|
|
15
|
+
"^@axiom-lattice/protocols$": "<rootDir>/../protocols/src/index.ts",
|
|
16
|
+
},
|
|
17
|
+
transformIgnorePatterns: [],
|
|
18
|
+
testTimeout: 10000,
|
|
19
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axiom-lattice/pg-stores",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.76",
|
|
4
4
|
"description": "PG stores implementation for Axiom Lattice framework",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"@langchain/langgraph-checkpoint-postgres": "1.0.0",
|
|
24
24
|
"pg": "^8.16.3",
|
|
25
25
|
"uuid": "^9.0.1",
|
|
26
|
-
"@axiom-lattice/core": "2.1.
|
|
26
|
+
"@axiom-lattice/core": "2.1.85",
|
|
27
27
|
"@axiom-lattice/protocols": "2.1.43"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"build": "tsup src/index.ts --format cjs,esm --dts --sourcemap",
|
|
39
39
|
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
40
40
|
"lint": "tsc --noEmit && node scripts/check-migration-versions.mjs",
|
|
41
|
+
"test": "jest",
|
|
41
42
|
"clean": "rimraf dist"
|
|
42
43
|
}
|
|
43
44
|
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for row mapping and safeParse utilities.
|
|
3
|
+
*
|
|
4
|
+
* These are pure-function tests — no database required.
|
|
5
|
+
* They verify that corrupted JSON in stored columns won't crash the API.
|
|
6
|
+
*/
|
|
7
|
+
import { safeParse, mapRowToWorkflowRun, mapRowToRunStep } from "../stores/PostgreSQLWorkflowTrackingStore";
|
|
8
|
+
|
|
9
|
+
// ─── safeParse ──────────────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
describe("safeParse", () => {
|
|
12
|
+
test("parses valid JSON string", () => {
|
|
13
|
+
expect(safeParse('{"a":1}', {})).toEqual({ a: 1 });
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test("parses JSON array", () => {
|
|
17
|
+
expect(safeParse("[1,2,3]", [])).toEqual([1, 2, 3]);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("returns fallback for invalid JSON", () => {
|
|
21
|
+
expect(safeParse("not json", {})).toEqual({});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("returns fallback for empty string", () => {
|
|
25
|
+
expect(safeParse("", [])).toEqual([]);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test("returns fallback for malformed JSON (unclosed brace)", () => {
|
|
29
|
+
expect(safeParse('{"a":1', {})).toEqual({});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("returns fallback for plain text", () => {
|
|
33
|
+
expect(safeParse("hello world", "fallback")).toBe("fallback");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("passes through non-string value (object)", () => {
|
|
37
|
+
const obj = { x: 1 };
|
|
38
|
+
expect(safeParse(obj, {})).toBe(obj);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("returns fallback for null", () => {
|
|
42
|
+
expect(safeParse(null, "fb")).toBe("fb");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("returns fallback for undefined", () => {
|
|
46
|
+
expect(safeParse(undefined, "fb")).toBe("fb");
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("passes through number", () => {
|
|
50
|
+
expect(safeParse(42, 0)).toBe(42);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("parses nested JSON safely", () => {
|
|
54
|
+
const json = '{"a":{"b":{"c":[1,2,3]}},"d":"text"}';
|
|
55
|
+
expect(safeParse(json, {})).toEqual({ a: { b: { c: [1, 2, 3] } }, d: "text" });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("handles JSON with unicode", () => {
|
|
59
|
+
expect(safeParse('{"msg":"你好世界"}', {})).toEqual({ msg: "你好世界" });
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// ─── mapRowToWorkflowRun ────────────────────────────────────────────────────
|
|
64
|
+
|
|
65
|
+
describe("mapRowToWorkflowRun", () => {
|
|
66
|
+
const baseRow = {
|
|
67
|
+
id: "run-1",
|
|
68
|
+
tenant_id: "t1",
|
|
69
|
+
assistant_id: "a1",
|
|
70
|
+
thread_id: "th1",
|
|
71
|
+
status: "running",
|
|
72
|
+
topology_edges: null, // JSONB returns parsed object, not string
|
|
73
|
+
total_edges: 3,
|
|
74
|
+
completed_edges: 1,
|
|
75
|
+
error_message: null,
|
|
76
|
+
metadata: null,
|
|
77
|
+
started_at: new Date("2026-01-01"),
|
|
78
|
+
completed_at: null,
|
|
79
|
+
created_at: new Date("2026-01-01"),
|
|
80
|
+
updated_at: new Date("2026-01-01"),
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
test("maps a clean row", () => {
|
|
84
|
+
const result = mapRowToWorkflowRun({
|
|
85
|
+
...baseRow,
|
|
86
|
+
topology_edges: [{ from: "A", to: "B", purpose: "test" }],
|
|
87
|
+
metadata: { key: "value" },
|
|
88
|
+
});
|
|
89
|
+
expect(result.id).toBe("run-1");
|
|
90
|
+
expect(result.topologyEdges).toEqual([{ from: "A", to: "B", purpose: "test" }]);
|
|
91
|
+
expect(result.metadata).toEqual({ key: "value" });
|
|
92
|
+
expect(result.status).toBe("running");
|
|
93
|
+
expect(result.totalEdges).toBe(3);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("falls back to empty array for null topology_edges", () => {
|
|
97
|
+
const result = mapRowToWorkflowRun({ ...baseRow, topology_edges: null });
|
|
98
|
+
expect(result.topologyEdges).toEqual([]);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("falls back to empty object for null metadata", () => {
|
|
102
|
+
const result = mapRowToWorkflowRun({ ...baseRow, metadata: null });
|
|
103
|
+
expect(result.metadata).toEqual({});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("handles topology_edges as JSON string (legacy TEXT column)", () => {
|
|
107
|
+
const result = mapRowToWorkflowRun({
|
|
108
|
+
...baseRow,
|
|
109
|
+
topology_edges: JSON.stringify([{ from: "X", to: "Y", purpose: "legacy" }]),
|
|
110
|
+
});
|
|
111
|
+
expect(result.topologyEdges).toEqual([{ from: "X", to: "Y", purpose: "legacy" }]);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("handles metadata as JSON string (legacy TEXT column)", () => {
|
|
115
|
+
const result = mapRowToWorkflowRun({
|
|
116
|
+
...baseRow,
|
|
117
|
+
metadata: JSON.stringify({ legacy: true }),
|
|
118
|
+
});
|
|
119
|
+
expect(result.metadata).toEqual({ legacy: true });
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("safe-falls back on corrupted topology_edges string — does NOT throw", () => {
|
|
123
|
+
expect(() =>
|
|
124
|
+
mapRowToWorkflowRun({ ...baseRow, topology_edges: "{{{bad}}" })
|
|
125
|
+
).not.toThrow();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test("safe-falls back on corrupted metadata string — does NOT throw", () => {
|
|
129
|
+
expect(() =>
|
|
130
|
+
mapRowToWorkflowRun({ ...baseRow, metadata: "not-json-at-all" })
|
|
131
|
+
).not.toThrow();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("returns fallback values for corrupted strings", () => {
|
|
135
|
+
const result = mapRowToWorkflowRun({
|
|
136
|
+
...baseRow,
|
|
137
|
+
topology_edges: "bad",
|
|
138
|
+
metadata: "also bad",
|
|
139
|
+
});
|
|
140
|
+
expect(result.topologyEdges).toEqual([]);
|
|
141
|
+
expect(result.metadata).toEqual({});
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// ─── mapRowToRunStep ────────────────────────────────────────────────────────
|
|
146
|
+
|
|
147
|
+
describe("mapRowToRunStep", () => {
|
|
148
|
+
const baseRow = {
|
|
149
|
+
id: "step-1",
|
|
150
|
+
run_id: "run-1",
|
|
151
|
+
tenant_id: "t1",
|
|
152
|
+
step_type: "agent",
|
|
153
|
+
step_name: "MyStep",
|
|
154
|
+
thread_id: null,
|
|
155
|
+
edge_from: null,
|
|
156
|
+
edge_to: null,
|
|
157
|
+
edge_purpose: null,
|
|
158
|
+
input: null,
|
|
159
|
+
output: null,
|
|
160
|
+
status: "running",
|
|
161
|
+
error_message: null,
|
|
162
|
+
started_at: new Date("2026-01-01"),
|
|
163
|
+
completed_at: null,
|
|
164
|
+
duration_ms: null,
|
|
165
|
+
created_at: new Date("2026-01-01"),
|
|
166
|
+
updated_at: new Date("2026-01-01"),
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
test("maps a clean row", () => {
|
|
170
|
+
const result = mapRowToRunStep({
|
|
171
|
+
...baseRow,
|
|
172
|
+
input: { template: "hello", rendered: "world" },
|
|
173
|
+
output: { result: "done" },
|
|
174
|
+
thread_id: "sub-thread-1",
|
|
175
|
+
});
|
|
176
|
+
expect(result.id).toBe("step-1");
|
|
177
|
+
expect(result.input).toEqual({ template: "hello", rendered: "world" });
|
|
178
|
+
expect(result.output).toEqual({ result: "done" });
|
|
179
|
+
expect(result.threadId).toBe("sub-thread-1");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("handles null input and output (maps to undefined)", () => {
|
|
183
|
+
const result = mapRowToRunStep(baseRow);
|
|
184
|
+
expect(result.input).toBeUndefined();
|
|
185
|
+
expect(result.output).toBeUndefined();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test("handles input as JSON string (legacy TEXT column)", () => {
|
|
189
|
+
const result = mapRowToRunStep({
|
|
190
|
+
...baseRow,
|
|
191
|
+
input: JSON.stringify({ template: "t", rendered: "r" }),
|
|
192
|
+
});
|
|
193
|
+
expect(result.input).toEqual({ template: "t", rendered: "r" });
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("handles output as JSON string (legacy TEXT column)", () => {
|
|
197
|
+
const result = mapRowToRunStep({
|
|
198
|
+
...baseRow,
|
|
199
|
+
output: JSON.stringify({ result: "ok" }),
|
|
200
|
+
});
|
|
201
|
+
expect(result.output).toEqual({ result: "ok" });
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("safe-falls back on corrupted input string — does NOT throw", () => {
|
|
205
|
+
expect(() =>
|
|
206
|
+
mapRowToRunStep({ ...baseRow, input: "{{{corrupt" })
|
|
207
|
+
).not.toThrow();
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("safe-falls back on corrupted output string — does NOT throw", () => {
|
|
211
|
+
expect(() =>
|
|
212
|
+
mapRowToRunStep({ ...baseRow, output: "bad-json-here" })
|
|
213
|
+
).not.toThrow();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test("returns undefined for corrupted input/output strings", () => {
|
|
217
|
+
const result = mapRowToRunStep({
|
|
218
|
+
...baseRow,
|
|
219
|
+
input: "invalid",
|
|
220
|
+
output: "also invalid",
|
|
221
|
+
});
|
|
222
|
+
expect(result.input).toBeUndefined();
|
|
223
|
+
expect(result.output).toBeUndefined();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("handles deeply nested objects", () => {
|
|
227
|
+
const result = mapRowToRunStep({
|
|
228
|
+
...baseRow,
|
|
229
|
+
output: { deep: { nested: { arr: [1, { x: "y" }] } } },
|
|
230
|
+
});
|
|
231
|
+
expect(result.output).toEqual({ deep: { nested: { arr: [1, { x: "y" }] } } });
|
|
232
|
+
});
|
|
233
|
+
});
|
|
@@ -98,7 +98,7 @@ export class PostgreSQLWorkflowTrackingStore implements WorkflowTrackingStore {
|
|
|
98
98
|
`SELECT * FROM lattice_workflow_runs WHERE id = $1`, [runId]
|
|
99
99
|
);
|
|
100
100
|
if (result.rows.length === 0) return null;
|
|
101
|
-
return
|
|
101
|
+
return mapRowToWorkflowRun(result.rows[0]);
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
async updateWorkflowRun(runId: string, updates: UpdateWorkflowRunRequest): Promise<WorkflowRun | null> {
|
|
@@ -145,7 +145,7 @@ export class PostgreSQLWorkflowTrackingStore implements WorkflowTrackingStore {
|
|
|
145
145
|
`SELECT * FROM lattice_workflow_runs WHERE tenant_id = $1 AND thread_id = $2 ORDER BY created_at DESC`,
|
|
146
146
|
[tenantId, threadId]
|
|
147
147
|
);
|
|
148
|
-
return result.rows.map(
|
|
148
|
+
return result.rows.map(mapRowToWorkflowRun);
|
|
149
149
|
}
|
|
150
150
|
|
|
151
151
|
async getWorkflowRunsByAssistantId(tenantId: string, assistantId: string): Promise<WorkflowRun[]> {
|
|
@@ -154,7 +154,7 @@ export class PostgreSQLWorkflowTrackingStore implements WorkflowTrackingStore {
|
|
|
154
154
|
`SELECT * FROM lattice_workflow_runs WHERE tenant_id = $1 AND assistant_id = $2 ORDER BY created_at DESC`,
|
|
155
155
|
[tenantId, assistantId]
|
|
156
156
|
);
|
|
157
|
-
return result.rows.map(
|
|
157
|
+
return result.rows.map(mapRowToWorkflowRun);
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
async getWorkflowRunsByTenantId(tenantId: string): Promise<WorkflowRun[]> {
|
|
@@ -163,7 +163,7 @@ export class PostgreSQLWorkflowTrackingStore implements WorkflowTrackingStore {
|
|
|
163
163
|
`SELECT * FROM lattice_workflow_runs WHERE tenant_id = $1 ORDER BY created_at DESC`,
|
|
164
164
|
[tenantId]
|
|
165
165
|
);
|
|
166
|
-
return result.rows.map(
|
|
166
|
+
return result.rows.map(mapRowToWorkflowRun);
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
async createRunStep(request: CreateRunStepRequest): Promise<RunStep> {
|
|
@@ -199,7 +199,7 @@ export class PostgreSQLWorkflowTrackingStore implements WorkflowTrackingStore {
|
|
|
199
199
|
[request.runId, request.stepType, request.stepName]
|
|
200
200
|
);
|
|
201
201
|
if (result.rows.length > 0) {
|
|
202
|
-
const existing =
|
|
202
|
+
const existing = mapRowToRunStep(result.rows[0]);
|
|
203
203
|
// Update threadId if not already set and request has one
|
|
204
204
|
if (!existing.threadId && request.threadId) {
|
|
205
205
|
await this.pool.query(
|
|
@@ -250,7 +250,7 @@ export class PostgreSQLWorkflowTrackingStore implements WorkflowTrackingStore {
|
|
|
250
250
|
`SELECT * FROM lattice_workflow_steps WHERE run_id = $1 AND id = $2`,
|
|
251
251
|
[runId, stepId]
|
|
252
252
|
);
|
|
253
|
-
return row.rows.length > 0 ?
|
|
253
|
+
return row.rows.length > 0 ? mapRowToRunStep(row.rows[0]) : null;
|
|
254
254
|
}
|
|
255
255
|
|
|
256
256
|
async getRunSteps(runId: string): Promise<RunStep[]> {
|
|
@@ -259,7 +259,7 @@ export class PostgreSQLWorkflowTrackingStore implements WorkflowTrackingStore {
|
|
|
259
259
|
`SELECT * FROM lattice_workflow_steps WHERE run_id = $1 ORDER BY created_at ASC`,
|
|
260
260
|
[runId]
|
|
261
261
|
);
|
|
262
|
-
return result.rows.map(
|
|
262
|
+
return result.rows.map(mapRowToRunStep);
|
|
263
263
|
}
|
|
264
264
|
|
|
265
265
|
async getRunStepsByType(runId: string, stepType: StepType): Promise<RunStep[]> {
|
|
@@ -268,7 +268,7 @@ export class PostgreSQLWorkflowTrackingStore implements WorkflowTrackingStore {
|
|
|
268
268
|
`SELECT * FROM lattice_workflow_steps WHERE run_id = $1 AND step_type = $2 ORDER BY created_at ASC`,
|
|
269
269
|
[runId, stepType]
|
|
270
270
|
);
|
|
271
|
-
return result.rows.map(
|
|
271
|
+
return result.rows.map(mapRowToRunStep);
|
|
272
272
|
}
|
|
273
273
|
|
|
274
274
|
async getInterruptedSteps(runId: string): Promise<RunStep[]> {
|
|
@@ -277,48 +277,59 @@ export class PostgreSQLWorkflowTrackingStore implements WorkflowTrackingStore {
|
|
|
277
277
|
`SELECT * FROM lattice_workflow_steps WHERE run_id = $1 AND status = 'interrupted' ORDER BY created_at ASC`,
|
|
278
278
|
[runId]
|
|
279
279
|
);
|
|
280
|
-
return result.rows.map(
|
|
280
|
+
return result.rows.map(mapRowToRunStep);
|
|
281
281
|
}
|
|
282
|
+
}
|
|
282
283
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
topologyEdges: typeof row.topology_edges === 'string' ? JSON.parse(row.topology_edges) : row.topology_edges || [],
|
|
291
|
-
totalEdges: row.total_edges,
|
|
292
|
-
completedEdges: row.completed_edges,
|
|
293
|
-
errorMessage: row.error_message,
|
|
294
|
-
metadata: typeof row.metadata === 'string' ? JSON.parse(row.metadata) : row.metadata || {},
|
|
295
|
-
startedAt: row.started_at,
|
|
296
|
-
completedAt: row.completed_at,
|
|
297
|
-
createdAt: row.created_at,
|
|
298
|
-
updatedAt: row.updated_at,
|
|
299
|
-
};
|
|
284
|
+
export function safeParse<T>(value: unknown, fallback: T): T {
|
|
285
|
+
if (value === null || value === undefined) return fallback;
|
|
286
|
+
if (typeof value !== 'string') return value as unknown as T;
|
|
287
|
+
try {
|
|
288
|
+
return JSON.parse(value) as T;
|
|
289
|
+
} catch {
|
|
290
|
+
return fallback;
|
|
300
291
|
}
|
|
292
|
+
}
|
|
301
293
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
durationMs: row.duration_ms,
|
|
320
|
-
createdAt: row.created_at,
|
|
321
|
-
updatedAt: row.updated_at,
|
|
322
|
-
};
|
|
323
|
-
}
|
|
294
|
+
export function mapRowToWorkflowRun(row: any): WorkflowRun {
|
|
295
|
+
return {
|
|
296
|
+
id: row.id,
|
|
297
|
+
tenantId: row.tenant_id,
|
|
298
|
+
assistantId: row.assistant_id,
|
|
299
|
+
threadId: row.thread_id,
|
|
300
|
+
status: row.status as WorkflowRunStatus,
|
|
301
|
+
topologyEdges: safeParse(row.topology_edges, []),
|
|
302
|
+
totalEdges: row.total_edges,
|
|
303
|
+
completedEdges: row.completed_edges,
|
|
304
|
+
errorMessage: row.error_message,
|
|
305
|
+
metadata: safeParse(row.metadata, {}),
|
|
306
|
+
startedAt: row.started_at,
|
|
307
|
+
completedAt: row.completed_at,
|
|
308
|
+
createdAt: row.created_at,
|
|
309
|
+
updatedAt: row.updated_at,
|
|
310
|
+
};
|
|
324
311
|
}
|
|
312
|
+
|
|
313
|
+
export function mapRowToRunStep(row: any): RunStep {
|
|
314
|
+
return {
|
|
315
|
+
id: row.id,
|
|
316
|
+
runId: row.run_id,
|
|
317
|
+
tenantId: row.tenant_id,
|
|
318
|
+
stepType: row.step_type as StepType,
|
|
319
|
+
stepName: row.step_name,
|
|
320
|
+
threadId: row.thread_id,
|
|
321
|
+
edgeFrom: row.edge_from,
|
|
322
|
+
edgeTo: row.edge_to,
|
|
323
|
+
edgePurpose: row.edge_purpose,
|
|
324
|
+
input: safeParse(row.input, undefined),
|
|
325
|
+
output: safeParse(row.output, undefined),
|
|
326
|
+
status: row.status,
|
|
327
|
+
errorMessage: row.error_message,
|
|
328
|
+
startedAt: row.started_at,
|
|
329
|
+
completedAt: row.completed_at,
|
|
330
|
+
durationMs: row.duration_ms,
|
|
331
|
+
createdAt: row.created_at,
|
|
332
|
+
updatedAt: row.updated_at,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|