@axiom-lattice/local-stores 3.0.2 → 3.1.0

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/local-stores",
3
- "version": "3.0.2",
3
+ "version": "3.1.0",
4
4
  "description": "Local SQLite-based stores for Axiom Lattice framework",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -25,8 +25,8 @@
25
25
  "@types/sql.js": "^1.4.11",
26
26
  "sql.js": "^1.14.1",
27
27
  "uuid": "^14.0.1",
28
- "@axiom-lattice/core": "4.1.0",
29
- "@axiom-lattice/protocols": "4.0.1"
28
+ "@axiom-lattice/core": "4.2.0",
29
+ "@axiom-lattice/protocols": "4.1.0"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/jest": "^29.5.14",
@@ -8,7 +8,7 @@
8
8
  import { describe, expect, it, beforeEach, afterEach } from "@jest/globals";
9
9
  import { tmpdir } from "os";
10
10
  import path from "path";
11
- import { initDatabase, closeDatabase, ensureTable } from "../database";
11
+ import { initDatabase, closeDatabase, ensureTable, type DatabaseWrapper } from "../database";
12
12
  import { LocalEvalStore, DDL } from "../stores/LocalEvalStore";
13
13
 
14
14
  // sql.js persists to disk via DatabaseWrapper.save(), so each test needs a
@@ -21,9 +21,10 @@ const nextDbPath = () => {
21
21
 
22
22
  describe("LocalEvalStore project name uniqueness", () => {
23
23
  let store: LocalEvalStore;
24
+ let db: DatabaseWrapper;
24
25
 
25
26
  beforeEach(async () => {
26
- const db = await initDatabase({ dbPath: nextDbPath() });
27
+ db = await initDatabase({ dbPath: nextDbPath() });
27
28
  store = new LocalEvalStore(db);
28
29
  });
29
30
 
@@ -93,4 +94,42 @@ describe("LocalEvalStore project name uniqueness", () => {
93
94
  const runs = db.prepare(`SELECT project_id FROM lt_eval_runs WHERE id = 'run-1'`).get() as { project_id: string };
94
95
  expect(runs.project_id).toBe("dup-new");
95
96
  });
97
+
98
+ it("deletes project suites, cases, runs, and results without affecting another project", async () => {
99
+ await store.createProject("tenant-1", "project-delete", {
100
+ name: "delete-me", judgeModelConfig: {}, targetServerConfig: {},
101
+ });
102
+ await store.createProject("tenant-1", "project-keep", {
103
+ name: "keep-me", judgeModelConfig: {}, targetServerConfig: {},
104
+ });
105
+ await store.createSuite("tenant-1", "project-delete", "suite-delete", { name: "delete-suite" });
106
+ await store.createSuite("tenant-1", "project-keep", "suite-keep", { name: "keep-suite" });
107
+ await store.createCase("tenant-1", "suite-delete", "case-delete", {
108
+ inputMessage: "delete", steps: [], outputType: "message_content", contentAssertion: "delete",
109
+ });
110
+ await store.createCase("tenant-1", "suite-keep", "case-keep", {
111
+ inputMessage: "keep", steps: [], outputType: "message_content", contentAssertion: "keep",
112
+ });
113
+ await store.createRun("tenant-1", "project-delete", "run-delete", { concurrency: 1, totalCases: 1 });
114
+ await store.createRun("tenant-1", "project-keep", "run-keep", { concurrency: 1, totalCases: 1 });
115
+ await store.createRunResult("tenant-1", "run-delete", "result-delete", {
116
+ suiteName: "delete-suite", caseId: "case-delete", pass: true, score: 100,
117
+ });
118
+ await store.createRunResult("tenant-1", "run-keep", "result-keep", {
119
+ suiteName: "keep-suite", caseId: "case-keep", pass: true, score: 100,
120
+ });
121
+
122
+ await expect(store.deleteProject("tenant-1", "project-delete")).resolves.toBe(true);
123
+
124
+ for (const table of ["lt_eval_projects", "lt_eval_suites", "lt_eval_cases", "lt_eval_runs", "lt_eval_run_results"]) {
125
+ const deleted = db.prepare(`SELECT COUNT(*) AS count FROM ${table} WHERE id LIKE '%delete'`).get() as { count: number };
126
+ const kept = db.prepare(`SELECT COUNT(*) AS count FROM ${table} WHERE id LIKE '%keep'`).get() as { count: number };
127
+ expect(deleted.count).toBe(0);
128
+ expect(kept.count).toBe(1);
129
+ }
130
+ });
131
+
132
+ it("returns false when deleting a project that does not exist", async () => {
133
+ await expect(store.deleteProject("tenant-1", "missing-project")).resolves.toBe(false);
134
+ });
96
135
  });
@@ -264,8 +264,34 @@ export class LocalEvalStore implements EvalStore {
264
264
  }
265
265
 
266
266
  async deleteProject(tenantId: string, id: string): Promise<boolean> {
267
- const result = this.db.prepare(`DELETE FROM lt_eval_projects WHERE tenant_id = ? AND id = ?`).run(tenantId, id);
268
- return result.changes > 0;
267
+ const db = this.db.getRawDb();
268
+ db.run("BEGIN TRANSACTION");
269
+ try {
270
+ db.run(
271
+ `DELETE FROM lt_eval_run_results
272
+ WHERE run_id IN (
273
+ SELECT id FROM lt_eval_runs WHERE tenant_id = ? AND project_id = ?
274
+ )`,
275
+ [tenantId, id]
276
+ );
277
+ db.run(
278
+ `DELETE FROM lt_eval_cases
279
+ WHERE tenant_id = ? AND suite_id IN (
280
+ SELECT id FROM lt_eval_suites WHERE tenant_id = ? AND project_id = ?
281
+ )`,
282
+ [tenantId, tenantId, id]
283
+ );
284
+ db.run(`DELETE FROM lt_eval_runs WHERE tenant_id = ? AND project_id = ?`, [tenantId, id]);
285
+ db.run(`DELETE FROM lt_eval_suites WHERE tenant_id = ? AND project_id = ?`, [tenantId, id]);
286
+ db.run(`DELETE FROM lt_eval_projects WHERE tenant_id = ? AND id = ?`, [tenantId, id]);
287
+ const deleted = db.getRowsModified() > 0;
288
+ db.run("COMMIT");
289
+ this.db.save();
290
+ return deleted;
291
+ } catch (error) {
292
+ db.run("ROLLBACK");
293
+ throw error;
294
+ }
269
295
  }
270
296
 
271
297
  // -------------------------------------------------------------------------