@axiom-lattice/local-stores 1.0.1

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.
@@ -0,0 +1,299 @@
1
+ /**
2
+ * Local SQLite implementation of WorkflowTrackingStore.
3
+ */
4
+
5
+ import { DatabaseWrapper } from "../database";
6
+ import type {
7
+ WorkflowTrackingStore,
8
+ WorkflowRun,
9
+ RunStep,
10
+ CreateWorkflowRunRequest,
11
+ UpdateWorkflowRunRequest,
12
+ CreateRunStepRequest,
13
+ UpdateRunStepRequest,
14
+ StepType,
15
+ WorkflowRunStatus,
16
+ } from "@axiom-lattice/protocols";
17
+ import { ensureTable, nowISO, parseISO } from "../database";
18
+
19
+ const DDL = `
20
+ CREATE TABLE IF NOT EXISTS lt_workflow_runs (
21
+ id TEXT PRIMARY KEY,
22
+ tenant_id TEXT NOT NULL,
23
+ assistant_id TEXT NOT NULL,
24
+ thread_id TEXT NOT NULL,
25
+ status TEXT NOT NULL DEFAULT 'running',
26
+ topology_edges TEXT NOT NULL DEFAULT '[]',
27
+ total_edges INTEGER NOT NULL DEFAULT 0,
28
+ completed_edges INTEGER NOT NULL DEFAULT 0,
29
+ error_message TEXT,
30
+ metadata TEXT DEFAULT '{}',
31
+ started_at TEXT NOT NULL,
32
+ completed_at TEXT,
33
+ created_at TEXT NOT NULL,
34
+ updated_at TEXT NOT NULL
35
+ );
36
+ CREATE INDEX IF NOT EXISTS idx_lt_wr_thread ON lt_workflow_runs(tenant_id, thread_id);
37
+ CREATE INDEX IF NOT EXISTS idx_lt_wr_assistant ON lt_workflow_runs(tenant_id, assistant_id);
38
+
39
+ CREATE TABLE IF NOT EXISTS lt_workflow_steps (
40
+ id TEXT NOT NULL,
41
+ run_id TEXT NOT NULL,
42
+ tenant_id TEXT NOT NULL,
43
+ step_type TEXT NOT NULL,
44
+ step_name TEXT NOT NULL,
45
+ edge_from TEXT,
46
+ edge_to TEXT,
47
+ edge_purpose TEXT,
48
+ input TEXT,
49
+ output TEXT,
50
+ status TEXT NOT NULL DEFAULT 'running',
51
+ error_message TEXT,
52
+ started_at TEXT NOT NULL,
53
+ completed_at TEXT,
54
+ duration_ms INTEGER,
55
+ created_at TEXT NOT NULL,
56
+ updated_at TEXT NOT NULL,
57
+ PRIMARY KEY (run_id, id)
58
+ );
59
+ CREATE INDEX IF NOT EXISTS idx_lt_ws_type ON lt_workflow_steps(run_id, step_type);
60
+ `;
61
+
62
+ interface RunRow {
63
+ id: string;
64
+ tenant_id: string;
65
+ assistant_id: string;
66
+ thread_id: string;
67
+ status: string;
68
+ topology_edges: string;
69
+ total_edges: number;
70
+ completed_edges: number;
71
+ error_message: string | null;
72
+ metadata: string;
73
+ started_at: string;
74
+ completed_at: string | null;
75
+ created_at: string;
76
+ updated_at: string;
77
+ }
78
+
79
+ interface StepRow {
80
+ id: string;
81
+ run_id: string;
82
+ tenant_id: string;
83
+ step_type: string;
84
+ step_name: string;
85
+ edge_from: string | null;
86
+ edge_to: string | null;
87
+ edge_purpose: string | null;
88
+ input: string | null;
89
+ output: string | null;
90
+ status: string;
91
+ error_message: string | null;
92
+ started_at: string;
93
+ completed_at: string | null;
94
+ duration_ms: number | null;
95
+ created_at: string;
96
+ updated_at: string;
97
+ }
98
+
99
+ export class LocalWorkflowTrackingStore implements WorkflowTrackingStore {
100
+ private db: DatabaseWrapper;
101
+
102
+ constructor(db: DatabaseWrapper) {
103
+ this.db = db;
104
+ ensureTable(db, DDL);
105
+ }
106
+
107
+ async createWorkflowRun(request: CreateWorkflowRunRequest): Promise<WorkflowRun> {
108
+ const now = nowISO();
109
+ const id = `${request.threadId}_${Date.now()}`;
110
+
111
+ this.db.prepare(
112
+ `INSERT INTO lt_workflow_runs (id, tenant_id, assistant_id, thread_id, status, topology_edges, total_edges, completed_edges, metadata, started_at, created_at, updated_at)
113
+ VALUES (?, ?, ?, ?, 'running', ?, ?, 0, ?, ?, ?, ?)`,
114
+ ).run(id, request.tenantId, request.assistantId, request.threadId,
115
+ JSON.stringify(request.topologyEdges), request.topologyEdges.length,
116
+ JSON.stringify(request.metadata || {}), now, now, now);
117
+
118
+ return {
119
+ id, tenantId: request.tenantId, assistantId: request.assistantId,
120
+ threadId: request.threadId, status: 'running',
121
+ topologyEdges: request.topologyEdges, totalEdges: request.topologyEdges.length,
122
+ completedEdges: 0, metadata: request.metadata,
123
+ startedAt: parseISO(now), completedAt: undefined,
124
+ createdAt: parseISO(now), updatedAt: parseISO(now),
125
+ };
126
+ }
127
+
128
+ async getWorkflowRun(runId: string): Promise<WorkflowRun | null> {
129
+ const row = this.db.prepare(`SELECT * FROM lt_workflow_runs WHERE id = ?`).get(runId) as unknown as RunRow | undefined;
130
+ return row ? mapRowToRun(row) : null;
131
+ }
132
+
133
+ async updateWorkflowRun(runId: string, updates: UpdateWorkflowRunRequest): Promise<WorkflowRun | null> {
134
+ const existing = await this.getWorkflowRun(runId);
135
+ if (!existing) return null;
136
+
137
+ const setClauses: string[] = [];
138
+ const values: unknown[] = [];
139
+
140
+ if (updates.status !== undefined) { setClauses.push("status = ?"); values.push(updates.status); }
141
+ if (updates.completedEdges !== undefined) { setClauses.push("completed_edges = ?"); values.push(updates.completedEdges); }
142
+ if (updates.errorMessage !== undefined) { setClauses.push("error_message = ?"); values.push(updates.errorMessage); }
143
+ if (updates.completedAt !== undefined) { setClauses.push("completed_at = ?"); values.push(updates.completedAt ? updates.completedAt.toISOString() : null); }
144
+ if (updates.metadata !== undefined) { setClauses.push("metadata = ?"); values.push(JSON.stringify(updates.metadata)); }
145
+
146
+ if (setClauses.length === 0) return existing;
147
+
148
+ const now = nowISO();
149
+ setClauses.push("updated_at = ?");
150
+ values.push(now);
151
+ values.push(runId);
152
+
153
+ this.db.prepare(
154
+ `UPDATE lt_workflow_runs SET ${setClauses.join(", ")} WHERE id = ?`,
155
+ ).run(...values);
156
+
157
+ return this.getWorkflowRun(runId);
158
+ }
159
+
160
+ async deleteWorkflowRun(runId: string): Promise<void> {
161
+ this.db.prepare(`DELETE FROM lt_workflow_runs WHERE id = ?`).run(runId);
162
+ }
163
+
164
+ async getWorkflowRunsByThreadId(tenantId: string, threadId: string): Promise<WorkflowRun[]> {
165
+ const rows = this.db.prepare(
166
+ `SELECT * FROM lt_workflow_runs WHERE tenant_id = ? AND thread_id = ? ORDER BY created_at DESC`,
167
+ ).all(tenantId, threadId) as unknown as RunRow[];
168
+ return rows.map(mapRowToRun);
169
+ }
170
+
171
+ async getWorkflowRunsByAssistantId(tenantId: string, assistantId: string): Promise<WorkflowRun[]> {
172
+ const rows = this.db.prepare(
173
+ `SELECT * FROM lt_workflow_runs WHERE tenant_id = ? AND assistant_id = ? ORDER BY created_at DESC`,
174
+ ).all(tenantId, assistantId) as unknown as RunRow[];
175
+ return rows.map(mapRowToRun);
176
+ }
177
+
178
+ async getWorkflowRunsByTenantId(tenantId: string): Promise<WorkflowRun[]> {
179
+ const rows = this.db.prepare(
180
+ `SELECT * FROM lt_workflow_runs WHERE tenant_id = ? ORDER BY created_at DESC`,
181
+ ).all(tenantId) as unknown as RunRow[];
182
+ return rows.map(mapRowToRun);
183
+ }
184
+
185
+ async createRunStep(request: CreateRunStepRequest): Promise<RunStep> {
186
+ const now = nowISO();
187
+ const id = `step_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
188
+
189
+ this.db.prepare(
190
+ `INSERT INTO lt_workflow_steps (id, run_id, tenant_id, step_type, step_name, edge_from, edge_to, edge_purpose, input, status, started_at, created_at, updated_at)
191
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'running', ?, ?, ?)`,
192
+ ).run(id, request.runId, request.tenantId, request.stepType, request.stepName,
193
+ request.edgeFrom || null, request.edgeTo || null, request.edgePurpose || null,
194
+ request.input ? JSON.stringify(request.input) : null, now, now, now);
195
+
196
+ return {
197
+ id, runId: request.runId, tenantId: request.tenantId,
198
+ stepType: request.stepType, stepName: request.stepName,
199
+ edgeFrom: request.edgeFrom, edgeTo: request.edgeTo, edgePurpose: request.edgePurpose,
200
+ input: request.input, status: 'running', startedAt: parseISO(now),
201
+ createdAt: parseISO(now), updatedAt: parseISO(now),
202
+ };
203
+ }
204
+
205
+ async updateRunStep(runId: string, stepId: string, updates: UpdateRunStepRequest): Promise<RunStep | null> {
206
+ const setClauses: string[] = [];
207
+ const values: unknown[] = [];
208
+
209
+ if (updates.status !== undefined) { setClauses.push("status = ?"); values.push(updates.status); }
210
+ if (updates.output !== undefined) { setClauses.push("output = ?"); values.push(JSON.stringify(updates.output)); }
211
+ if (updates.errorMessage !== undefined) { setClauses.push("error_message = ?"); values.push(updates.errorMessage); }
212
+ if (updates.completedAt !== undefined) { setClauses.push("completed_at = ?"); values.push(updates.completedAt ? updates.completedAt.toISOString() : null); }
213
+ if (updates.durationMs !== undefined) { setClauses.push("duration_ms = ?"); values.push(updates.durationMs); }
214
+
215
+ if (setClauses.length === 0) {
216
+ return this.getStepById(runId, stepId);
217
+ }
218
+
219
+ const now = nowISO();
220
+ setClauses.push("updated_at = ?");
221
+ values.push(now);
222
+ values.push(runId, stepId);
223
+
224
+ this.db.prepare(
225
+ `UPDATE lt_workflow_steps SET ${setClauses.join(", ")} WHERE run_id = ? AND id = ?`,
226
+ ).run(...values);
227
+
228
+ return this.getStepById(runId, stepId);
229
+ }
230
+
231
+ async getRunSteps(runId: string): Promise<RunStep[]> {
232
+ const rows = this.db.prepare(
233
+ `SELECT * FROM lt_workflow_steps WHERE run_id = ? ORDER BY created_at ASC`,
234
+ ).all(runId) as unknown as StepRow[];
235
+ return rows.map(mapRowToStep);
236
+ }
237
+
238
+ async getRunStepsByType(runId: string, stepType: StepType): Promise<RunStep[]> {
239
+ const rows = this.db.prepare(
240
+ `SELECT * FROM lt_workflow_steps WHERE run_id = ? AND step_type = ? ORDER BY created_at ASC`,
241
+ ).all(runId, stepType) as unknown as StepRow[];
242
+ return rows.map(mapRowToStep);
243
+ }
244
+
245
+ async getInterruptedSteps(runId: string): Promise<RunStep[]> {
246
+ const rows = this.db.prepare(
247
+ `SELECT * FROM lt_workflow_steps WHERE run_id = ? AND status = 'interrupted' ORDER BY created_at ASC`,
248
+ ).all(runId) as unknown as StepRow[];
249
+ return rows.map(mapRowToStep);
250
+ }
251
+
252
+ private getStepById(runId: string, id: string): RunStep | null {
253
+ const row = this.db.prepare(
254
+ `SELECT * FROM lt_workflow_steps WHERE run_id = ? AND id = ?`,
255
+ ).get(runId, id) as unknown as StepRow | undefined;
256
+ return row ? mapRowToStep(row) : null;
257
+ }
258
+ }
259
+
260
+ function mapRowToRun(row: RunRow): WorkflowRun {
261
+ return {
262
+ id: row.id,
263
+ tenantId: row.tenant_id,
264
+ assistantId: row.assistant_id,
265
+ threadId: row.thread_id,
266
+ status: row.status as WorkflowRunStatus,
267
+ topologyEdges: JSON.parse(row.topology_edges || "[]"),
268
+ totalEdges: row.total_edges,
269
+ completedEdges: row.completed_edges,
270
+ errorMessage: row.error_message || undefined,
271
+ metadata: JSON.parse(row.metadata || "{}"),
272
+ startedAt: parseISO(row.started_at),
273
+ completedAt: row.completed_at ? parseISO(row.completed_at) : undefined,
274
+ createdAt: parseISO(row.created_at),
275
+ updatedAt: parseISO(row.updated_at),
276
+ };
277
+ }
278
+
279
+ function mapRowToStep(row: StepRow): RunStep {
280
+ return {
281
+ id: row.id,
282
+ runId: row.run_id,
283
+ tenantId: row.tenant_id,
284
+ stepType: row.step_type as StepType,
285
+ stepName: row.step_name,
286
+ edgeFrom: row.edge_from || undefined,
287
+ edgeTo: row.edge_to || undefined,
288
+ edgePurpose: row.edge_purpose || undefined,
289
+ input: row.input ? JSON.parse(row.input) : undefined,
290
+ output: row.output ? JSON.parse(row.output) : undefined,
291
+ status: row.status as RunStep["status"],
292
+ errorMessage: row.error_message || undefined,
293
+ startedAt: parseISO(row.started_at),
294
+ completedAt: row.completed_at ? parseISO(row.completed_at) : undefined,
295
+ durationMs: row.duration_ms ?? undefined,
296
+ createdAt: parseISO(row.created_at),
297
+ updatedAt: parseISO(row.updated_at),
298
+ };
299
+ }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Local SQLite implementation of WorkspaceStore.
3
+ */
4
+
5
+ import { DatabaseWrapper } from "../database";
6
+ import type {
7
+ WorkspaceStore,
8
+ Workspace,
9
+ CreateWorkspaceRequest,
10
+ UpdateWorkspaceRequest,
11
+ } from "@axiom-lattice/protocols";
12
+ import { ensureTable, nowISO, parseISO } from "../database";
13
+
14
+ const DDL = `
15
+ CREATE TABLE IF NOT EXISTS lt_workspaces (
16
+ id TEXT NOT NULL,
17
+ tenant_id TEXT NOT NULL,
18
+ name TEXT NOT NULL,
19
+ description TEXT,
20
+ storage_type TEXT NOT NULL,
21
+ created_at TEXT NOT NULL,
22
+ updated_at TEXT NOT NULL,
23
+ PRIMARY KEY (tenant_id, id)
24
+ );
25
+ `;
26
+
27
+ interface WorkspaceRow {
28
+ id: string;
29
+ tenant_id: string;
30
+ name: string;
31
+ description: string | null;
32
+ storage_type: string;
33
+ created_at: string;
34
+ updated_at: string;
35
+ }
36
+
37
+ export class LocalWorkspaceStore implements WorkspaceStore {
38
+ private db: DatabaseWrapper;
39
+
40
+ constructor(db: DatabaseWrapper) {
41
+ this.db = db;
42
+ ensureTable(db, DDL);
43
+ }
44
+
45
+ async getAllWorkspaces(tenantId: string): Promise<Workspace[]> {
46
+ const rows = this.db.prepare(
47
+ `SELECT * FROM lt_workspaces WHERE tenant_id = ? ORDER BY created_at DESC`,
48
+ ).all(tenantId) as unknown as WorkspaceRow[];
49
+ return rows.map(mapRowToWorkspace);
50
+ }
51
+
52
+ async getWorkspaceById(tenantId: string, id: string): Promise<Workspace | null> {
53
+ const row = this.db.prepare(
54
+ `SELECT * FROM lt_workspaces WHERE tenant_id = ? AND id = ?`,
55
+ ).get(tenantId, id) as unknown as WorkspaceRow | undefined;
56
+ return row ? mapRowToWorkspace(row) : null;
57
+ }
58
+
59
+ async createWorkspace(
60
+ tenantId: string,
61
+ id: string,
62
+ data: CreateWorkspaceRequest,
63
+ ): Promise<Workspace> {
64
+ const now = nowISO();
65
+
66
+ this.db.prepare(
67
+ `INSERT INTO lt_workspaces (id, tenant_id, name, description, storage_type, created_at, updated_at)
68
+ VALUES (?, ?, ?, ?, ?, ?, ?)
69
+ ON CONFLICT(tenant_id, id) DO UPDATE SET
70
+ name = excluded.name,
71
+ description = excluded.description,
72
+ storage_type = excluded.storage_type,
73
+ updated_at = excluded.updated_at`,
74
+ ).run(id, tenantId, data.name, data.description || null, data.storageType, now, now);
75
+
76
+ return {
77
+ id,
78
+ tenantId,
79
+ name: data.name,
80
+ description: data.description,
81
+ storageType: data.storageType,
82
+ createdAt: parseISO(now),
83
+ updatedAt: parseISO(now),
84
+ };
85
+ }
86
+
87
+ async updateWorkspace(
88
+ tenantId: string,
89
+ id: string,
90
+ updates: UpdateWorkspaceRequest,
91
+ ): Promise<Workspace | null> {
92
+ const existing = await this.getWorkspaceById(tenantId, id);
93
+ if (!existing) return null;
94
+
95
+ const setClauses: string[] = [];
96
+ const values: unknown[] = [];
97
+
98
+ if (updates.name !== undefined) {
99
+ setClauses.push("name = ?");
100
+ values.push(updates.name);
101
+ }
102
+ if (updates.description !== undefined) {
103
+ setClauses.push("description = ?");
104
+ values.push(updates.description || null);
105
+ }
106
+ if (updates.storageType !== undefined) {
107
+ setClauses.push("storage_type = ?");
108
+ values.push(updates.storageType);
109
+ }
110
+
111
+ if (setClauses.length === 0) return existing;
112
+
113
+ const now = nowISO();
114
+ setClauses.push("updated_at = ?");
115
+ values.push(now);
116
+ values.push(tenantId, id);
117
+
118
+ this.db.prepare(
119
+ `UPDATE lt_workspaces SET ${setClauses.join(", ")} WHERE tenant_id = ? AND id = ?`,
120
+ ).run(...values);
121
+
122
+ return this.getWorkspaceById(tenantId, id);
123
+ }
124
+
125
+ async deleteWorkspace(tenantId: string, id: string): Promise<boolean> {
126
+ const result = this.db.prepare(
127
+ `DELETE FROM lt_workspaces WHERE tenant_id = ? AND id = ?`,
128
+ ).run(tenantId, id);
129
+ return result.changes > 0;
130
+ }
131
+ }
132
+
133
+ function mapRowToWorkspace(row: WorkspaceRow): Workspace {
134
+ return {
135
+ id: row.id,
136
+ tenantId: row.tenant_id,
137
+ name: row.name,
138
+ description: row.description || undefined,
139
+ storageType: row.storage_type as Workspace["storageType"],
140
+ createdAt: parseISO(row.created_at),
141
+ updatedAt: parseISO(row.updated_at),
142
+ };
143
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "preserve",
5
+ "lib": ["ES2020"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "moduleResolution": "Bundler",
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "resolveJsonModule": true,
14
+ "declaration": true,
15
+ "declarationMap": true,
16
+ "types": ["node"],
17
+ "sourceMap": true,
18
+ "incremental": true,
19
+ "tsBuildInfoFile": "./.tsbuildinfo"
20
+ },
21
+ "include": ["src/index.ts"],
22
+ "exclude": ["node_modules", "dist"]
23
+ }