@axiom-lattice/pg-stores 1.0.90 → 1.0.93

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axiom-lattice/pg-stores",
3
- "version": "1.0.90",
3
+ "version": "1.0.93",
4
4
  "description": "PG stores implementation for Axiom Lattice framework",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -25,8 +25,8 @@
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.99",
29
- "@axiom-lattice/protocols": "2.1.51"
28
+ "@axiom-lattice/core": "2.1.102",
29
+ "@axiom-lattice/protocols": "2.1.53"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/jest": "^29.5.14",
@@ -220,7 +220,7 @@ describe("mapRowToRunStep", () => {
220
220
  output: "also invalid",
221
221
  });
222
222
  expect(result.input).toBeUndefined();
223
- expect(result.output).toBeUndefined();
223
+ expect(result.output).toEqual("also invalid"); // text output preserved as-is (jsonb column)
224
224
  });
225
225
 
226
226
  test("handles deeply nested objects", () => {
@@ -19,6 +19,7 @@ import { PostgreSQLChannelInstallationStore } from "./stores/PostgreSQLChannelIn
19
19
  import { PostgreSQLA2AApiKeyStore } from "./stores/PostgreSQLA2AApiKeyStore";
20
20
  import { PostgreSQLScheduleStorage } from "./stores/PostgreSQLScheduleStorage";
21
21
  import { PostgreSQLTaskStore } from "./stores/PostgreSQLTaskStore";
22
+ import { PostgreSQLTaskWorkItemStore } from "./stores/PostgreSQLTaskWorkItemStore";
22
23
  import { MenuStore } from "./stores/MenuStore";
23
24
  import { PostgresSharedResourceStore } from "./stores/PostgresSharedResourceStore";
24
25
  import { PostgreSQLCollectionStore } from "./stores/PostgreSQLCollectionStore";
@@ -62,11 +63,12 @@ import { addWorkspaceProjectToQueue } from "./migrations/add_workspace_project_t
62
63
  import { createWorkflowTrackingTables, addStepThreadId, addWorkflowRunsTenantStatusUpdatedIndex } from "./migrations/workflow_tracking_migrations";
63
64
  import { evalMigrations } from "./migrations/eval_migrations";
64
65
  import { createA2AApiKeysTable } from "./migrations/a2a_api_key_migration";
65
- import { createTasksTable } from "./migrations/task_migration";
66
+ import { createTasksTable, addTaskFieldsMigration, addTaskProjectFieldsMigration } from "./migrations/task_migration";
66
67
  import { createMenuItemsTable } from "./migrations/menu_items_migration";
67
68
  import { addFileContentType } from "./migrations/menu_items_add_file_type";
68
69
  import { createSharedResourcesTable } from "./migrations/shared_resources_migration";
69
70
  import { createCollectionsTable } from "./migrations/collection_migrations";
71
+ import { createTaskWorkItemsMigration, addWorkItemProjectFieldsMigration } from "./migrations/task_work_items_migration";
70
72
 
71
73
  export async function createPgStoreConfig(connectionString: string) {
72
74
  const pool = new Pool({ connectionString });
@@ -118,6 +120,10 @@ export async function createPgStoreConfig(connectionString: string) {
118
120
  mm.register(createSharedResourcesTable); // v135
119
121
  mm.register(addFileContentType); // v136
120
122
  mm.register(createCollectionsTable); // v137
123
+ mm.register(addTaskFieldsMigration); // v139
124
+ mm.register(createTaskWorkItemsMigration); // v138
125
+ mm.register(addTaskProjectFieldsMigration); // v140
126
+ mm.register(addWorkItemProjectFieldsMigration); // v140
121
127
  mm.register(createConnectionConfigsTable); // v160
122
128
  mm.register(addWorkflowRunsTenantStatusUpdatedIndex); // v161
123
129
 
@@ -130,6 +136,8 @@ export async function createPgStoreConfig(connectionString: string) {
130
136
 
131
137
  const opts = { pool };
132
138
 
139
+ const taskWorkItemStore = new PostgreSQLTaskWorkItemStore(pool);
140
+
133
141
  return {
134
142
  workspace: new PostgreSQLWorkspaceStore(opts),
135
143
  project: new PostgreSQLProjectStore(opts),
@@ -148,6 +156,7 @@ export async function createPgStoreConfig(connectionString: string) {
148
156
  workflowTracking: new PostgreSQLWorkflowTrackingStore(opts),
149
157
  threadMessageQueue: new ThreadMessageQueueStore(opts),
150
158
  task: new PostgreSQLTaskStore(opts),
159
+ taskWorkItem: taskWorkItemStore,
151
160
  a2aApiKey: new PostgreSQLA2AApiKeyStore(opts),
152
161
  schedule: new PostgreSQLScheduleStorage(opts),
153
162
  menu: new MenuStore(opts),
@@ -49,3 +49,34 @@ export const createTasksTable: Migration = {
49
49
  await client.query("DROP TABLE IF EXISTS lattice_tasks");
50
50
  },
51
51
  };
52
+
53
+ export const addTaskFieldsMigration: Migration = {
54
+ version: 139,
55
+ name: "add_task_fields_require_review_deps_result_failure",
56
+ up: async (client: PoolClient) => {
57
+ await client.query(`
58
+ ALTER TABLE lattice_tasks
59
+ ADD COLUMN IF NOT EXISTS require_review BOOLEAN DEFAULT FALSE,
60
+ ADD COLUMN IF NOT EXISTS dependencies JSONB,
61
+ ADD COLUMN IF NOT EXISTS result TEXT,
62
+ ADD COLUMN IF NOT EXISTS failure_reason TEXT
63
+ `);
64
+ },
65
+ };
66
+
67
+ export const addTaskProjectFieldsMigration: Migration = {
68
+ version: 140,
69
+ name: "add_task_project_workspace_fields",
70
+ up: async (client: PoolClient) => {
71
+ await client.query(`
72
+ ALTER TABLE lattice_tasks
73
+ ADD COLUMN IF NOT EXISTS workspace_id TEXT,
74
+ ADD COLUMN IF NOT EXISTS project_id TEXT
75
+ `);
76
+
77
+ await client.query(`
78
+ CREATE INDEX IF NOT EXISTS idx_tasks_project
79
+ ON lattice_tasks (tenant_id, project_id)
80
+ `);
81
+ },
82
+ };
@@ -0,0 +1,51 @@
1
+ import type { PoolClient } from "pg";
2
+ import type { Migration } from "./migration";
3
+
4
+ export const createTaskWorkItemsMigration: Migration = {
5
+ version: 138,
6
+ name: "create_task_work_items_table",
7
+ up: async (client: PoolClient) => {
8
+ await client.query(`
9
+ CREATE TABLE IF NOT EXISTS lattice_task_work_items (
10
+ id TEXT NOT NULL,
11
+ tenant_id TEXT NOT NULL,
12
+ task_id TEXT NOT NULL,
13
+ action TEXT NOT NULL,
14
+ actor TEXT NOT NULL,
15
+ thread_id TEXT,
16
+ summary TEXT,
17
+ detail JSONB,
18
+ attempt INTEGER,
19
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
20
+ PRIMARY KEY (tenant_id, id)
21
+ )
22
+ `);
23
+
24
+ await client.query(`
25
+ CREATE INDEX IF NOT EXISTS idx_task_work_items_task_id
26
+ ON lattice_task_work_items (tenant_id, task_id)
27
+ `);
28
+
29
+ await client.query(`
30
+ CREATE INDEX IF NOT EXISTS idx_task_work_items_action
31
+ ON lattice_task_work_items (tenant_id, task_id, action)
32
+ `);
33
+ },
34
+ };
35
+
36
+ export const addWorkItemProjectFieldsMigration: Migration = {
37
+ version: 140,
38
+ name: "add_task_work_item_project_fields",
39
+ up: async (client: PoolClient) => {
40
+ await client.query(`
41
+ ALTER TABLE lattice_task_work_items
42
+ ADD COLUMN IF NOT EXISTS workspace_id TEXT,
43
+ ADD COLUMN IF NOT EXISTS project_id TEXT
44
+ `);
45
+
46
+ await client.query(`
47
+ CREATE INDEX IF NOT EXISTS idx_task_work_items_project
48
+ ON lattice_task_work_items (tenant_id, project_id)
49
+ `);
50
+ },
51
+ };
@@ -30,6 +30,14 @@ export class PostgreSQLConnectionStore implements ConnectionStore {
30
30
  this.db = db;
31
31
  }
32
32
 
33
+ async listByTenant(tenantId: string): Promise<ConnectionEntry[]> {
34
+ const result = await this.db.query<ConnectionRow>(
35
+ `SELECT * FROM ${TABLE} WHERE tenant_id = $1 ORDER BY created_at`,
36
+ [tenantId],
37
+ );
38
+ return result.rows.map((row) => this.mapRow(row));
39
+ }
40
+
33
41
  async listByType(tenantId: string, type: string): Promise<ConnectionEntry[]> {
34
42
  const result = await this.db.query<ConnectionRow>(
35
43
  `SELECT * FROM ${TABLE} WHERE tenant_id = $1 AND type = $2 ORDER BY created_at`,
@@ -828,6 +828,18 @@ export class PostgreSQLEvalStore implements EvalStore {
828
828
  return rows.length ? this.mapRowToRunResult(rows[0] as unknown as Record<string, unknown>) : null;
829
829
  }
830
830
 
831
+ /** Delete a run result by ID */
832
+ async deleteRunResult(tenantId: string, id: string): Promise<boolean> {
833
+ await this.ensureInitialized();
834
+ const result = await this.pool.query(
835
+ `DELETE FROM lattice_eval_run_results
836
+ WHERE id = $1
837
+ AND run_id IN (SELECT id FROM lattice_eval_runs WHERE tenant_id = $2)`,
838
+ [id, tenantId],
839
+ );
840
+ return (result.rowCount ?? 0) > 0;
841
+ }
842
+
831
843
  /** Get a single run result by ID with tenant isolation */
832
844
  private async getRunResultById(tenantId: string, id: string): Promise<EvalRunResult | null> {
833
845
  await this.ensureInitialized();
@@ -12,7 +12,7 @@ import type {
12
12
  TaskListFilter,
13
13
  } from "@axiom-lattice/protocols";
14
14
  import { MigrationManager } from "../migrations/migration";
15
- import { createTasksTable } from "../migrations/task_migration";
15
+ import { createTasksTable, addTaskFieldsMigration, addTaskProjectFieldsMigration } from "../migrations/task_migration";
16
16
  import { v4 as uuidv4 } from "uuid";
17
17
 
18
18
  interface TaskRow {
@@ -29,6 +29,12 @@ interface TaskRow {
29
29
  parent_id: string | null;
30
30
  source_id: string | null;
31
31
  context: Record<string, unknown> | null;
32
+ require_review: boolean | null;
33
+ dependencies: string[] | null;
34
+ result: string | null;
35
+ failure_reason: string | null;
36
+ workspace_id: string | null;
37
+ project_id: string | null;
32
38
  created_at: string;
33
39
  updated_at: string;
34
40
  }
@@ -48,6 +54,12 @@ function mapRowToTask(row: TaskRow): TaskItem {
48
54
  parentId: row.parent_id ?? undefined,
49
55
  sourceId: row.source_id ?? undefined,
50
56
  context: row.context ?? undefined,
57
+ requireReview: row.require_review ?? undefined,
58
+ dependencies: row.dependencies ?? undefined,
59
+ result: row.result ?? undefined,
60
+ failureReason: row.failure_reason ?? undefined,
61
+ workspaceId: row.workspace_id ?? undefined,
62
+ projectId: row.project_id ?? undefined,
51
63
  createdAt: new Date(row.created_at),
52
64
  updatedAt: new Date(row.updated_at),
53
65
  };
@@ -85,6 +97,8 @@ export class PostgreSQLTaskStore implements TaskStore {
85
97
 
86
98
  this.migrationManager = new MigrationManager(this.pool);
87
99
  this.migrationManager.register(createTasksTable);
100
+ this.migrationManager.register(addTaskFieldsMigration);
101
+ this.migrationManager.register(addTaskProjectFieldsMigration);
88
102
 
89
103
  if (options.autoMigrate !== false) {
90
104
  this.initialize().catch((error) => {
@@ -123,8 +137,8 @@ export class PostgreSQLTaskStore implements TaskStore {
123
137
  const now = new Date().toISOString();
124
138
 
125
139
  await this.pool.query(
126
- `INSERT INTO lattice_tasks (id, tenant_id, owner_type, owner_id, title, description, status, priority, due_date, metadata, parent_id, source_id, context, created_at, updated_at)
127
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)`,
140
+ `INSERT INTO lattice_tasks (id, tenant_id, owner_type, owner_id, title, description, status, priority, due_date, metadata, parent_id, source_id, context, require_review, dependencies, result, failure_reason, workspace_id, project_id, created_at, updated_at)
141
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21)`,
128
142
  [
129
143
  id,
130
144
  params.tenantId,
@@ -139,6 +153,12 @@ export class PostgreSQLTaskStore implements TaskStore {
139
153
  params.parentId || null,
140
154
  params.sourceId || null,
141
155
  params.context ? JSON.stringify(params.context) : null,
156
+ params.requireReview ?? null,
157
+ params.dependencies ? JSON.stringify(params.dependencies) : null,
158
+ params.result ?? null,
159
+ params.failureReason ?? null,
160
+ params.workspaceId || null,
161
+ params.projectId || null,
142
162
  now,
143
163
  now,
144
164
  ],
@@ -172,13 +192,24 @@ export class PostgreSQLTaskStore implements TaskStore {
172
192
  params.push(filter.ownerId);
173
193
  }
174
194
  if (filter.status) {
175
- conditions.push(`status = $${paramIndex++}`);
176
- params.push(filter.status);
195
+ const statuses = filter.status.split(',').map(s => s.trim()).filter(Boolean);
196
+ const placeholders = statuses.map((_, i) => `$${paramIndex + i}`).join(', ');
197
+ conditions.push(`status IN (${placeholders})`);
198
+ params.push(...statuses);
199
+ paramIndex += statuses.length;
177
200
  }
178
201
  if (filter.priority) {
179
202
  conditions.push(`priority = $${paramIndex++}`);
180
203
  params.push(filter.priority);
181
204
  }
205
+ if (filter.workspaceId) {
206
+ conditions.push(`workspace_id = $${paramIndex++}`);
207
+ params.push(filter.workspaceId);
208
+ }
209
+ if (filter.projectId) {
210
+ conditions.push(`project_id = $${paramIndex++}`);
211
+ params.push(filter.projectId);
212
+ }
182
213
  if (filter.parentId) {
183
214
  conditions.push(`parent_id = $${paramIndex++}`);
184
215
  params.push(filter.parentId);
@@ -265,6 +296,30 @@ export class PostgreSQLTaskStore implements TaskStore {
265
296
  setClauses.push(`owner_id = $${paramIndex++}`);
266
297
  params.push(updates.ownerId);
267
298
  }
299
+ if (updates.requireReview !== undefined) {
300
+ setClauses.push(`require_review = $${paramIndex++}`);
301
+ params.push(updates.requireReview);
302
+ }
303
+ if (updates.dependencies !== undefined) {
304
+ setClauses.push(`dependencies = $${paramIndex++}`);
305
+ params.push(JSON.stringify(updates.dependencies));
306
+ }
307
+ if (updates.result !== undefined) {
308
+ setClauses.push(`result = $${paramIndex++}`);
309
+ params.push(updates.result);
310
+ }
311
+ if (updates.failureReason !== undefined) {
312
+ setClauses.push(`failure_reason = $${paramIndex++}`);
313
+ params.push(updates.failureReason);
314
+ }
315
+ if (updates.workspaceId !== undefined) {
316
+ setClauses.push(`workspace_id = $${paramIndex++}`);
317
+ params.push(updates.workspaceId);
318
+ }
319
+ if (updates.projectId !== undefined) {
320
+ setClauses.push(`project_id = $${paramIndex++}`);
321
+ params.push(updates.projectId);
322
+ }
268
323
 
269
324
  if (setClauses.length === 0) {
270
325
  return existing;
@@ -0,0 +1,67 @@
1
+ import type { Pool } from "pg";
2
+ import type { TaskWorkItemStore, TaskWorkItem, CreateWorkItemRequest, TaskWorkItemListFilter } from "@axiom-lattice/protocols";
3
+ import { v4 } from "uuid";
4
+
5
+ export class PostgreSQLTaskWorkItemStore implements TaskWorkItemStore {
6
+ constructor(private pool: Pool) {}
7
+
8
+ async create(params: CreateWorkItemRequest): Promise<TaskWorkItem> {
9
+ const id = v4();
10
+ const result = await this.pool.query(
11
+ `INSERT INTO lattice_task_work_items
12
+ (id, tenant_id, task_id, action, actor, thread_id, summary, detail, attempt, workspace_id, project_id)
13
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
14
+ RETURNING *`,
15
+ [
16
+ id, params.tenantId, params.taskId, params.action, params.actor,
17
+ params.threadId || null, params.summary || null,
18
+ params.detail ? JSON.stringify(params.detail) : null,
19
+ params.attempt || null,
20
+ params.workspaceId || null,
21
+ params.projectId || null,
22
+ ]
23
+ );
24
+ return this.rowToItem(result.rows[0]);
25
+ }
26
+
27
+ async list(filter: TaskWorkItemListFilter): Promise<TaskWorkItem[]> {
28
+ let query = `SELECT * FROM lattice_task_work_items WHERE tenant_id = $1 AND task_id = $2`;
29
+ const params: unknown[] = [filter.tenantId, filter.taskId];
30
+
31
+ if (filter.action) {
32
+ query += ` AND action = $${params.length + 1}`;
33
+ params.push(filter.action);
34
+ }
35
+
36
+ query += ` ORDER BY created_at ASC`;
37
+
38
+ if (filter.limit) {
39
+ query += ` LIMIT $${params.length + 1}`;
40
+ params.push(filter.limit);
41
+ }
42
+ if (filter.offset) {
43
+ query += ` OFFSET $${params.length + 1}`;
44
+ params.push(filter.offset);
45
+ }
46
+
47
+ const result = await this.pool.query(query, params);
48
+ return result.rows.map((row: Record<string, unknown>) => this.rowToItem(row));
49
+ }
50
+
51
+ private rowToItem(row: Record<string, unknown>): TaskWorkItem {
52
+ return {
53
+ id: row.id as string,
54
+ taskId: row.task_id as string,
55
+ tenantId: row.tenant_id as string,
56
+ action: row.action as string,
57
+ actor: row.actor as string,
58
+ threadId: row.thread_id as string | undefined,
59
+ summary: row.summary as string | undefined,
60
+ detail: row.detail as Record<string, unknown> | undefined,
61
+ attempt: row.attempt as number | undefined,
62
+ workspaceId: row.workspace_id as string | undefined,
63
+ projectId: row.project_id as string | undefined,
64
+ createdAt: new Date(row.created_at as string),
65
+ };
66
+ }
67
+ }
@@ -261,8 +261,8 @@ export class PostgreSQLTenantStore implements TenantStore {
261
261
  }
262
262
 
263
263
  if (updates.metadata !== undefined) {
264
- updateFields.push(`metadata = $${paramIndex++}`);
265
- updateValues.push(updates.metadata);
264
+ updateFields.push(`metadata = COALESCE(lattice_tenants.metadata, '{}'::jsonb) || $${paramIndex++}::jsonb`);
265
+ updateValues.push(JSON.stringify(updates.metadata));
266
266
  }
267
267
 
268
268
  if (updateFields.length === 0) {
@@ -374,7 +374,9 @@ export function mapRowToRunStep(row: any): RunStep {
374
374
  edgeTo: row.edge_to,
375
375
  edgePurpose: row.edge_purpose,
376
376
  input: safeParse(row.input, undefined),
377
- output: safeParse(row.output, undefined),
377
+ output: typeof row.output === "string"
378
+ ? safeParse(row.output, row.output as any)
379
+ : row.output ?? undefined,
378
380
  status: row.status,
379
381
  errorMessage: row.error_message,
380
382
  startedAt: row.started_at,