@axiom-lattice/pg-stores 1.0.89 → 1.0.90
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 +9 -0
- package/dist/index.d.mts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +50 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +49 -0
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -3
- package/src/__tests__/PostgreSQLWorkflowTrackingStore.test.ts +71 -1
- package/src/createPgStoreConfig.ts +2 -1
- package/src/migrations/workflow_tracking_migrations.ts +18 -0
- package/src/stores/PostgreSQLWorkflowTrackingStore.ts +39 -1
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.90",
|
|
4
4
|
"description": "PG stores implementation for Axiom Lattice framework",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -25,14 +25,17 @@
|
|
|
25
25
|
"@langchain/core": "1.1.30",
|
|
26
26
|
"pg": "^8.16.3",
|
|
27
27
|
"uuid": "^9.0.1",
|
|
28
|
-
"@axiom-lattice/core": "2.1.
|
|
29
|
-
"@axiom-lattice/protocols": "2.1.
|
|
28
|
+
"@axiom-lattice/core": "2.1.99",
|
|
29
|
+
"@axiom-lattice/protocols": "2.1.51"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
+
"@types/jest": "^29.5.14",
|
|
32
33
|
"@types/node": "^20.11.24",
|
|
33
34
|
"@types/pg": "^8.11.10",
|
|
34
35
|
"@types/uuid": "^9.0.8",
|
|
36
|
+
"jest": "^29.7.0",
|
|
35
37
|
"rimraf": "^5.0.5",
|
|
38
|
+
"ts-jest": "^29.4.0",
|
|
36
39
|
"tsup": "^8.0.1",
|
|
37
40
|
"typescript": "^5.4.2"
|
|
38
41
|
},
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* These are pure-function tests — no database required.
|
|
5
5
|
* They verify that corrupted JSON in stored columns won't crash the API.
|
|
6
6
|
*/
|
|
7
|
-
import { safeParse, mapRowToWorkflowRun, mapRowToRunStep } from "../stores/PostgreSQLWorkflowTrackingStore";
|
|
7
|
+
import { safeParse, mapRowToWorkflowRun, mapRowToRunStep, PostgreSQLWorkflowTrackingStore } from "../stores/PostgreSQLWorkflowTrackingStore";
|
|
8
8
|
|
|
9
9
|
// ─── safeParse ──────────────────────────────────────────────────────────────
|
|
10
10
|
|
|
@@ -231,3 +231,73 @@ describe("mapRowToRunStep", () => {
|
|
|
231
231
|
expect(result.output).toEqual({ deep: { nested: { arr: [1, { x: "y" }] } } });
|
|
232
232
|
});
|
|
233
233
|
});
|
|
234
|
+
|
|
235
|
+
// ─── queryWorkflowRuns ──────────────────────────────────────────────────────
|
|
236
|
+
|
|
237
|
+
describe("queryWorkflowRuns", () => {
|
|
238
|
+
const runRow = {
|
|
239
|
+
id: "run-1",
|
|
240
|
+
tenant_id: "t1",
|
|
241
|
+
assistant_id: "a1",
|
|
242
|
+
thread_id: "th1",
|
|
243
|
+
status: "interrupted",
|
|
244
|
+
topology_edges: [],
|
|
245
|
+
total_edges: 0,
|
|
246
|
+
completed_edges: 0,
|
|
247
|
+
error_message: null,
|
|
248
|
+
metadata: {},
|
|
249
|
+
started_at: new Date("2026-01-02"),
|
|
250
|
+
completed_at: null,
|
|
251
|
+
created_at: new Date("2026-01-02"),
|
|
252
|
+
updated_at: new Date("2026-01-02"),
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
function makeStore(queryImpl: (sql: string, params: unknown[]) => { rows: unknown[] }) {
|
|
256
|
+
const query = jest.fn(async (sql: unknown, params: unknown) =>
|
|
257
|
+
queryImpl(sql as string, params as unknown[]));
|
|
258
|
+
const store = new PostgreSQLWorkflowTrackingStore({ pool: { query } as never });
|
|
259
|
+
return { store, query };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
it("filters by status, paginates, and parses COUNT string to number", async () => {
|
|
263
|
+
const { store, query } = makeStore((sql) =>
|
|
264
|
+
sql.includes("COUNT(*)") ? { rows: [{ count: "7" }] } : { rows: [runRow] });
|
|
265
|
+
|
|
266
|
+
const result = await store.queryWorkflowRuns("t1", { status: "interrupted", limit: 20, offset: 40 });
|
|
267
|
+
|
|
268
|
+
const [countSql, countParams] = query.mock.calls[0] as [string, unknown[]];
|
|
269
|
+
expect(countSql).toBe("SELECT COUNT(*) AS count FROM lattice_workflow_runs WHERE tenant_id = $1 AND status = $2");
|
|
270
|
+
expect(countParams).toEqual(["t1", "interrupted"]);
|
|
271
|
+
|
|
272
|
+
const [selectSql, selectParams] = query.mock.calls[1] as [string, unknown[]];
|
|
273
|
+
expect(selectSql).toBe("SELECT * FROM lattice_workflow_runs WHERE tenant_id = $1 AND status = $2 ORDER BY updated_at DESC, id DESC LIMIT $3 OFFSET $4");
|
|
274
|
+
expect(selectParams).toEqual(["t1", "interrupted", 20, 40]);
|
|
275
|
+
|
|
276
|
+
expect(result.total).toBe(7);
|
|
277
|
+
expect(result.records).toHaveLength(1);
|
|
278
|
+
expect(result.records[0].id).toBe("run-1");
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it("omits LIMIT/OFFSET when not paginating", async () => {
|
|
282
|
+
const { store, query } = makeStore((sql) =>
|
|
283
|
+
sql.includes("COUNT(*)") ? { rows: [{ count: "1" }] } : { rows: [runRow] });
|
|
284
|
+
|
|
285
|
+
await store.queryWorkflowRuns("t1");
|
|
286
|
+
|
|
287
|
+
const [selectSql, selectParams] = query.mock.calls[1] as [string, unknown[]];
|
|
288
|
+
expect(selectSql).toBe("SELECT * FROM lattice_workflow_runs WHERE tenant_id = $1 ORDER BY updated_at DESC, id DESC");
|
|
289
|
+
expect(selectParams).toEqual(["t1"]);
|
|
290
|
+
expect(selectSql).not.toContain("LIMIT");
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("adds assistant_id condition when assistantId is given", async () => {
|
|
294
|
+
const { store, query } = makeStore((sql) =>
|
|
295
|
+
sql.includes("COUNT(*)") ? { rows: [{ count: "0" }] } : { rows: [] });
|
|
296
|
+
|
|
297
|
+
await store.queryWorkflowRuns("t1", { assistantId: "a9", limit: 5 });
|
|
298
|
+
|
|
299
|
+
const [selectSql, selectParams] = query.mock.calls[1] as [string, unknown[]];
|
|
300
|
+
expect(selectSql).toBe("SELECT * FROM lattice_workflow_runs WHERE tenant_id = $1 AND assistant_id = $2 ORDER BY updated_at DESC, id DESC LIMIT $3");
|
|
301
|
+
expect(selectParams).toEqual(["t1", "a9", 5]);
|
|
302
|
+
});
|
|
303
|
+
});
|
|
@@ -59,7 +59,7 @@ import { addPriorityAndCommandColumns } from "./migrations/add_priority_command_
|
|
|
59
59
|
import { addCustomRunConfigColumn } from "./migrations/add_custom_run_config_column";
|
|
60
60
|
import { alterMessageQueueIdColumn } from "./migrations/alter_message_queue_id_column";
|
|
61
61
|
import { addWorkspaceProjectToQueue } from "./migrations/add_workspace_project_to_queue";
|
|
62
|
-
import { createWorkflowTrackingTables, addStepThreadId } from "./migrations/workflow_tracking_migrations";
|
|
62
|
+
import { createWorkflowTrackingTables, addStepThreadId, addWorkflowRunsTenantStatusUpdatedIndex } from "./migrations/workflow_tracking_migrations";
|
|
63
63
|
import { evalMigrations } from "./migrations/eval_migrations";
|
|
64
64
|
import { createA2AApiKeysTable } from "./migrations/a2a_api_key_migration";
|
|
65
65
|
import { createTasksTable } from "./migrations/task_migration";
|
|
@@ -119,6 +119,7 @@ export async function createPgStoreConfig(connectionString: string) {
|
|
|
119
119
|
mm.register(addFileContentType); // v136
|
|
120
120
|
mm.register(createCollectionsTable); // v137
|
|
121
121
|
mm.register(createConnectionConfigsTable); // v160
|
|
122
|
+
mm.register(addWorkflowRunsTenantStatusUpdatedIndex); // v161
|
|
122
123
|
|
|
123
124
|
await mm.migrate();
|
|
124
125
|
|
|
@@ -96,3 +96,21 @@ export const addStepThreadId: Migration = {
|
|
|
96
96
|
`);
|
|
97
97
|
},
|
|
98
98
|
};
|
|
99
|
+
|
|
100
|
+
export const addWorkflowRunsTenantStatusUpdatedIndex: Migration = {
|
|
101
|
+
name: "add_workflow_runs_tenant_status_updated_index",
|
|
102
|
+
version: 161,
|
|
103
|
+
|
|
104
|
+
up: async (client) => {
|
|
105
|
+
await client.query(`
|
|
106
|
+
CREATE INDEX IF NOT EXISTS idx_workflow_runs_tenant_status
|
|
107
|
+
ON lattice_workflow_runs (tenant_id, status, updated_at DESC);
|
|
108
|
+
`);
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
down: async (client) => {
|
|
112
|
+
await client.query(`
|
|
113
|
+
DROP INDEX IF EXISTS idx_workflow_runs_tenant_status;
|
|
114
|
+
`);
|
|
115
|
+
},
|
|
116
|
+
};
|
|
@@ -14,9 +14,11 @@ import {
|
|
|
14
14
|
UpdateRunStepRequest,
|
|
15
15
|
StepType,
|
|
16
16
|
WorkflowRunStatus,
|
|
17
|
+
QueryWorkflowRunsOptions,
|
|
18
|
+
QueryWorkflowRunsResult,
|
|
17
19
|
} from "@axiom-lattice/protocols";
|
|
18
20
|
import { MigrationManager } from "../migrations/migration";
|
|
19
|
-
import { createWorkflowTrackingTables, addStepThreadId } from "../migrations/workflow_tracking_migrations";
|
|
21
|
+
import { createWorkflowTrackingTables, addStepThreadId, addWorkflowRunsTenantStatusUpdatedIndex } from "../migrations/workflow_tracking_migrations";
|
|
20
22
|
|
|
21
23
|
export interface PostgreSQLWorkflowTrackingStoreOptions {
|
|
22
24
|
pool?: Pool;
|
|
@@ -51,6 +53,7 @@ export class PostgreSQLWorkflowTrackingStore implements WorkflowTrackingStore {
|
|
|
51
53
|
this.migrationManager = new MigrationManager(this.pool);
|
|
52
54
|
this.migrationManager.register(createWorkflowTrackingTables);
|
|
53
55
|
this.migrationManager.register(addStepThreadId);
|
|
56
|
+
this.migrationManager.register(addWorkflowRunsTenantStatusUpdatedIndex);
|
|
54
57
|
|
|
55
58
|
if (options.autoMigrate !== false) {
|
|
56
59
|
this.initialize().catch((error) => {
|
|
@@ -180,6 +183,41 @@ export class PostgreSQLWorkflowTrackingStore implements WorkflowTrackingStore {
|
|
|
180
183
|
return result.rows.map(mapRowToWorkflowRun);
|
|
181
184
|
}
|
|
182
185
|
|
|
186
|
+
async queryWorkflowRuns(tenantId: string, options: QueryWorkflowRunsOptions = {}): Promise<QueryWorkflowRunsResult> {
|
|
187
|
+
await this.ensureInitialized();
|
|
188
|
+
const conditions: string[] = ["tenant_id = $1"];
|
|
189
|
+
const whereParams: unknown[] = [tenantId];
|
|
190
|
+
if (options.status) {
|
|
191
|
+
whereParams.push(options.status);
|
|
192
|
+
conditions.push(`status = $${whereParams.length}`);
|
|
193
|
+
}
|
|
194
|
+
if (options.assistantId) {
|
|
195
|
+
whereParams.push(options.assistantId);
|
|
196
|
+
conditions.push(`assistant_id = $${whereParams.length}`);
|
|
197
|
+
}
|
|
198
|
+
const where = conditions.join(" AND ");
|
|
199
|
+
|
|
200
|
+
const countResult = await this.pool.query(
|
|
201
|
+
`SELECT COUNT(*) AS count FROM lattice_workflow_runs WHERE ${where}`,
|
|
202
|
+
whereParams
|
|
203
|
+
);
|
|
204
|
+
const total = parseInt(countResult.rows[0].count, 10);
|
|
205
|
+
|
|
206
|
+
const selectParams: unknown[] = [...whereParams];
|
|
207
|
+
let sql = `SELECT * FROM lattice_workflow_runs WHERE ${where} ORDER BY updated_at DESC, id DESC`;
|
|
208
|
+
if (options.limit !== undefined) {
|
|
209
|
+
selectParams.push(options.limit);
|
|
210
|
+
sql += ` LIMIT $${selectParams.length}`;
|
|
211
|
+
}
|
|
212
|
+
if (options.offset !== undefined) {
|
|
213
|
+
selectParams.push(options.offset);
|
|
214
|
+
sql += ` OFFSET $${selectParams.length}`;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const result = await this.pool.query(sql, selectParams);
|
|
218
|
+
return { records: result.rows.map(mapRowToWorkflowRun), total };
|
|
219
|
+
}
|
|
220
|
+
|
|
183
221
|
async createRunStep(request: CreateRunStepRequest): Promise<RunStep> {
|
|
184
222
|
await this.ensureInitialized();
|
|
185
223
|
const now = new Date();
|