@m4ike1/ion-session-backend-sqlite-node 0.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.
Files changed (64) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/README.md +62 -0
  3. package/dist/index.d.ts +6 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +103 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/sqlite/index.d.ts +5 -0
  8. package/dist/sqlite/index.d.ts.map +1 -0
  9. package/dist/sqlite/index.js +4 -0
  10. package/dist/sqlite/index.js.map +1 -0
  11. package/dist/sqlite/migrations/001_initial.sql +121 -0
  12. package/dist/sqlite/migrations.d.ts +3 -0
  13. package/dist/sqlite/migrations.d.ts.map +1 -0
  14. package/dist/sqlite/migrations.js +7 -0
  15. package/dist/sqlite/migrations.js.map +1 -0
  16. package/dist/sqlite/repo.d.ts +41 -0
  17. package/dist/sqlite/repo.d.ts.map +1 -0
  18. package/dist/sqlite/repo.js +408 -0
  19. package/dist/sqlite/repo.js.map +1 -0
  20. package/dist/sqlite/session/branch-entries.d.ts +6 -0
  21. package/dist/sqlite/session/branch-entries.d.ts.map +1 -0
  22. package/dist/sqlite/session/branch-entries.js +213 -0
  23. package/dist/sqlite/session/branch-entries.js.map +1 -0
  24. package/dist/sqlite/session/entries.d.ts +24 -0
  25. package/dist/sqlite/session/entries.d.ts.map +1 -0
  26. package/dist/sqlite/session/entries.js +126 -0
  27. package/dist/sqlite/session/entries.js.map +1 -0
  28. package/dist/sqlite/session/session-row.d.ts +23 -0
  29. package/dist/sqlite/session/session-row.d.ts.map +1 -0
  30. package/dist/sqlite/session/session-row.js +69 -0
  31. package/dist/sqlite/session/session-row.js.map +1 -0
  32. package/dist/sqlite/session/session-sequences.d.ts +4 -0
  33. package/dist/sqlite/session/session-sequences.d.ts.map +1 -0
  34. package/dist/sqlite/session/session-sequences.js +13 -0
  35. package/dist/sqlite/session/session-sequences.js.map +1 -0
  36. package/dist/sqlite/session/session-stats.d.ts +6 -0
  37. package/dist/sqlite/session/session-stats.d.ts.map +1 -0
  38. package/dist/sqlite/session/session-stats.js +39 -0
  39. package/dist/sqlite/session/session-stats.js.map +1 -0
  40. package/dist/sqlite/session/usage-ledger.d.ts +20 -0
  41. package/dist/sqlite/session/usage-ledger.d.ts.map +1 -0
  42. package/dist/sqlite/session/usage-ledger.js +50 -0
  43. package/dist/sqlite/session/usage-ledger.js.map +1 -0
  44. package/dist/sqlite/session/values.d.ts +23 -0
  45. package/dist/sqlite/session/values.d.ts.map +1 -0
  46. package/dist/sqlite/session/values.js +91 -0
  47. package/dist/sqlite/session/values.js.map +1 -0
  48. package/dist/sqlite/session.d.ts +42 -0
  49. package/dist/sqlite/session.d.ts.map +1 -0
  50. package/dist/sqlite/session.js +163 -0
  51. package/dist/sqlite/session.js.map +1 -0
  52. package/dist/sqlite/sql.d.ts +19 -0
  53. package/dist/sqlite/sql.d.ts.map +1 -0
  54. package/dist/sqlite/sql.js +58 -0
  55. package/dist/sqlite/sql.js.map +1 -0
  56. package/dist/sqlite/storage.d.ts +38 -0
  57. package/dist/sqlite/storage.d.ts.map +1 -0
  58. package/dist/sqlite/storage.js +161 -0
  59. package/dist/sqlite/storage.js.map +1 -0
  60. package/dist/sqlite/types.d.ts +29 -0
  61. package/dist/sqlite/types.d.ts.map +1 -0
  62. package/dist/sqlite/types.js +2 -0
  63. package/dist/sqlite/types.js.map +1 -0
  64. package/package.json +45 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,69 @@
1
+ # Changelog
2
+
3
+ ## [Unreleased]
4
+
5
+ ## [0.1.0] - 2026-09-24
6
+
7
+ ### Breaking Changes
8
+
9
+ - Restarted the `@m4ike1` version line at 0.1.0. The versions inherited from upstream Pi (`0.85.1`, `0.86.1`) stay on npm and remain installable by exact version, but they are a different line: this release sorts below them, so an existing `^0.86.1` range will not pick it up, and inter-package ranges move to `^0.1.0`. The `latest` dist-tag follows ion's own line from this release onward.
10
+
11
+ ## [0.86.1] - 2026-09-24
12
+
13
+ ## [0.86.0] - 2026-09-24
14
+
15
+ ### Breaking Changes
16
+
17
+ - Renamed product `pi` → `ion` (breaking). Package names `@m4ike1/pi-*` → `@m4ike1/ion-*` (`ionConfig` instead of `piConfig`, `@m4ike1/ion-coding-agent`, `@m4ike1/ion-ai`, etc.), binary `pi` → `ion`, config directory `.pi` → `.ion` (XDG now immediate at `~/.config/ion`, `~/.local/share/ion`, `~/.local/state/ion`, `~/.cache/ion`), environment variables `PI_*` → `ION_*` (`PI_CODING_AGENT_DIR` → `ION_CODING_AGENT_DIR`, `PI_DISABLE_SESSION_INDEX` etc., with `PI_*` fallback for one release), and `pi.dev` → `ion.dev`. Legacy `~/.pi` is migrated on first run if `~/.config/ion` is missing. `APP_TITLE` `π` → `⛬`.
18
+ - Renamed this package from `@maikel/ion-session-backend-sqlite-node` to `@m4ike1/ion-session-backend-sqlite-node` (breaking). The `@maikel` npm scope belongs to another account, so the fork publishes under `@m4ike1`. Import specifiers and dependency ranges change accordingly. Nothing was ever published under the old scope, so there is no published version to migrate from.
19
+
20
+
21
+
22
+ ## [0.85.1] - 2026-09-05
23
+
24
+ ## [0.85.0] - 2026-09-04
25
+
26
+ ## [0.84.4] - 2026-08-28
27
+
28
+ ## [0.84.3] - 2026-08-24
29
+
30
+ ## [0.84.2] - 2026-08-14
31
+
32
+ ## [0.84.1] - 2026-08-07
33
+
34
+ ### Added
35
+
36
+ - Added the composable, parameterized `sql` template tag for SQLite queries.
37
+
38
+ ### Fixed
39
+
40
+ - Fixed SQLite branch queries to apply filters, cursors, and limits in SQL; bounded log reads; and added covering indexes for session, record, branch, and fact queries ([#7727](https://github.com/maikel/pi/pull/7727) by [@cristinaponcela](https://github.com/cristinaponcela)).
41
+
42
+ ## [0.84.0] - 2026-08-06
43
+
44
+ ### Breaking Changes
45
+
46
+ - Renamed the package from `@m4ike1/pi-storage-sqlite-node` to `@m4ike1/pi-session-backend-sqlite-node`.
47
+ - Replaced the legacy SQLite session schema and repository with the v4 lane-based `SessionRepo` contract. Existing work-in-progress databases are not migrated.
48
+
49
+ ### Added
50
+
51
+ - Added bounded active-branch queries, durable operation records, global facts, shared sequence allocation, session statistics, and fenced writer leases to the SQLite backend.
52
+
53
+ ### Fixed
54
+
55
+ - Fixed SQLite session listings to avoid acquiring writer claims and include current session names, allowing inventory reads while sessions have active writers ([#7655](https://github.com/maikel/pi/pull/7655)).
56
+
57
+ ## [0.83.0] - 2026-07-29
58
+
59
+ ## [0.82.1] - 2026-07-25
60
+
61
+ ## [0.82.0] - 2026-07-24
62
+
63
+ ## [0.81.1] - 2026-07-21
64
+
65
+ ## [0.81.0] - 2026-07-21
66
+
67
+ ### Added
68
+
69
+ - Added a Node.js SQLite storage backend for agent harness sessions, including migrations and materialized session views ([#6594](https://github.com/maikel/pi/pull/6594) by [@cristinaponcela](https://github.com/cristinaponcela)).
package/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # @m4ike1/ion-session-backend-sqlite-node
2
+
3
+ SQLite session storage backend for `@m4ike1/ion-agent-core`, built on `node:sqlite` (`node >= 22.19`).
4
+
5
+ ```ts
6
+ import { BACKGROUND_CONTEXT } from "@m4ike1/ion-agent-core";
7
+ import {
8
+ createNodeSqliteFactory,
9
+ SqliteSessionRepo,
10
+ } from "@m4ike1/ion-session-backend-sqlite-node";
11
+
12
+ const repository = new SqliteSessionRepo({
13
+ directory: "/var/lib/ion/sessions",
14
+ databaseFactory: createNodeSqliteFactory(),
15
+ });
16
+
17
+ const session = await repository.create({}, BACKGROUND_CONTEXT);
18
+ const main = await session.createBranch("main", null, BACKGROUND_CONTEXT);
19
+ await main.appendMessage(
20
+ { role: "user", content: "hello", timestamp: Date.now() },
21
+ BACKGROUND_CONTEXT,
22
+ );
23
+ await session.close(BACKGROUND_CONTEXT);
24
+ await repository.close(BACKGROUND_CONTEXT);
25
+ ```
26
+
27
+ ## Layout
28
+
29
+ - Default: one file per session under `directory`. Safe IDs (`[A-Za-z0-9_-]+`)
30
+ map to `{sessionId}.sqlite`; all other IDs use a `~`-prefixed base64url
31
+ encoding of the UTF-16LE bytes. The durable session ID is unchanged;
32
+ `metadata.path` carries the canonical physical path.
33
+ - `databasePath` option: store multiple sessions in one shared container.
34
+ Its parent directory is created on demand.
35
+
36
+ ## Lifecycle and ownership
37
+
38
+ - `open()` and `delete()` reject metadata whose path is outside the
39
+ configured repository, and never create a missing database.
40
+ - `list()` is read-only and best-effort: corrupt files, version mismatches,
41
+ and unrelated `*.sqlite` files are skipped.
42
+ - One writable owner per session is the host's responsibility. The repo
43
+ rejects overlapping local create/open/fork/delete for one ID but provides
44
+ no cross-process lease, lock, fence, heartbeat, or takeover. Close a worker
45
+ before deletion.
46
+ - `fork()` of a source open in the same repo queues its snapshot on that
47
+ source's commit queue. Any other source uses an independent read-only
48
+ connection with one deferred WAL transaction; later worker commits may land
49
+ while the snapshot is open. Forks copy entries and scalar/list values, not
50
+ usage ledger rows.
51
+ - `repository.close()` waits for every open session cleanup attempt before
52
+ reporting errors (single error rethrown, multiple aggregated).
53
+ - No search service or FTS index is exported; search is a separate projection.
54
+
55
+ ## Further reading
56
+
57
+ - [Setup and tasks](docs/setup.md) — layout choice, open/list/delete/fork
58
+ recipes, custom factories, shutdown order.
59
+ - [Reference](docs/reference.md) — public API: repo, sessions, factory,
60
+ database capability, `sql` helper, constants.
61
+ - [Concepts](docs/concepts.md) — file layout and ID encoding, ownership
62
+ model, fork snapshots, version gate, schema.
@@ -0,0 +1,6 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+ import type { SqliteDatabase, SqliteDatabaseFactory } from "./sqlite/types.ts";
3
+ export declare function wrapNodeSqliteDatabase(db: DatabaseSync): SqliteDatabase;
4
+ export declare function createNodeSqliteFactory(): SqliteDatabaseFactory;
5
+ export * from "./sqlite/index.ts";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,OAAO,KAAK,EAAE,cAAc,EAAE,qBAAqB,EAAoC,MAAM,mBAAmB,CAAC;AAiGjH,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,YAAY,GAAG,cAAc,CAEvE;AAED,wBAAgB,uBAAuB,IAAI,qBAAqB,CAc/D;AAGD,cAAc,mBAAmB,CAAC","sourcesContent":["import type { SQLInputValue } from \"node:sqlite\";\nimport { DatabaseSync } from \"node:sqlite\";\nimport { pathToFileURL } from \"node:url\";\nimport { sql } from \"./sqlite/sql.ts\";\nimport type { SqliteDatabase, SqliteDatabaseFactory, SqliteRunResult, SqliteStatement } from \"./sqlite/types.ts\";\n\nfunction isNamedParameters(value: unknown): value is Record<string, SQLInputValue> {\n\tif (value === null || typeof value !== \"object\") return false;\n\tif (Array.isArray(value) || ArrayBuffer.isView(value)) return false;\n\treturn true;\n}\n\nfunction isAsyncResult(value: unknown): boolean {\n\treturn value !== null && (typeof value === \"object\" || typeof value === \"function\") && \"then\" in value;\n}\n\nclass NodeSqliteStatement implements SqliteStatement {\n\tprivate readonly statement: ReturnType<DatabaseSync[\"prepare\"]>;\n\n\tconstructor(statement: ReturnType<DatabaseSync[\"prepare\"]>) {\n\t\tthis.statement = statement;\n\t}\n\n\trun(...params: unknown[]): SqliteRunResult {\n\t\tconst [first, ...rest] = params;\n\t\tconst result = isNamedParameters(first)\n\t\t\t? this.statement.run(first, ...(rest as SQLInputValue[]))\n\t\t\t: this.statement.run(...(params as SQLInputValue[]));\n\t\treturn {\n\t\t\tchanges: Number(result.changes),\n\t\t\tlastInsertRowid: result.lastInsertRowid === undefined ? undefined : Number(result.lastInsertRowid),\n\t\t};\n\t}\n\n\tget<TRow extends object>(...params: unknown[]): TRow | undefined {\n\t\tconst [first, ...rest] = params;\n\t\treturn (\n\t\t\tisNamedParameters(first)\n\t\t\t\t? this.statement.get(first, ...(rest as SQLInputValue[]))\n\t\t\t\t: this.statement.get(...(params as SQLInputValue[]))\n\t\t) as TRow | undefined;\n\t}\n\n\tall<TRow extends object>(...params: unknown[]): TRow[] {\n\t\tconst [first, ...rest] = params;\n\t\treturn (\n\t\t\tisNamedParameters(first)\n\t\t\t\t? this.statement.all(first, ...(rest as SQLInputValue[]))\n\t\t\t\t: this.statement.all(...(params as SQLInputValue[]))\n\t\t) as TRow[];\n\t}\n\n\titerate<TRow extends object>(...params: unknown[]): Iterable<TRow> {\n\t\tconst [first, ...rest] = params;\n\t\treturn (\n\t\t\tisNamedParameters(first)\n\t\t\t\t? this.statement.iterate(first, ...(rest as SQLInputValue[]))\n\t\t\t\t: this.statement.iterate(...(params as SQLInputValue[]))\n\t\t) as Iterable<TRow>;\n\t}\n}\n\nclass NodeSqliteDatabase implements SqliteDatabase {\n\tprivate readonly db: DatabaseSync;\n\n\tconstructor(db: DatabaseSync) {\n\t\tthis.db = db;\n\t}\n\n\texec(sql: string): void {\n\t\tthis.db.exec(sql);\n\t}\n\n\tprepare(sql: string): SqliteStatement {\n\t\treturn new NodeSqliteStatement(this.db.prepare(sql));\n\t}\n\n\ttransaction<T>(fn: () => T): T {\n\t\tsql`BEGIN IMMEDIATE`.exec(this);\n\t\ttry {\n\t\t\tconst result = fn();\n\t\t\tif (isAsyncResult(result)) {\n\t\t\t\tthrow new TypeError(\"SQLite transaction callbacks must be synchronous\");\n\t\t\t}\n\t\t\tsql`COMMIT`.exec(this);\n\t\t\treturn result;\n\t\t} catch (error) {\n\t\t\ttry {\n\t\t\t\tsql`ROLLBACK`.exec(this);\n\t\t\t} catch {\n\t\t\t\t// Ignore rollback errors to rethrow original error.\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tclose(): void {\n\t\tthis.db.close();\n\t}\n}\n\nexport function wrapNodeSqliteDatabase(db: DatabaseSync): SqliteDatabase {\n\treturn new NodeSqliteDatabase(db);\n}\n\nexport function createNodeSqliteFactory(): SqliteDatabaseFactory {\n\treturn {\n\t\tasync open(path: string): Promise<SqliteDatabase> {\n\t\t\treturn new NodeSqliteDatabase(new DatabaseSync(path));\n\t\t},\n\t\tasync openExisting(path: string): Promise<SqliteDatabase> {\n\t\t\tconst url = pathToFileURL(path);\n\t\t\turl.searchParams.set(\"mode\", \"rw\");\n\t\t\treturn new NodeSqliteDatabase(new DatabaseSync(url));\n\t\t},\n\t\tasync openReadOnly(path: string): Promise<SqliteDatabase> {\n\t\t\treturn new NodeSqliteDatabase(new DatabaseSync(path, { readOnly: true }));\n\t\t},\n\t};\n}\n\n// Re-export the SQLite session backend and types so this package is a complete node-sqlite backend.\nexport * from \"./sqlite/index.ts\";\n"]}
package/dist/index.js ADDED
@@ -0,0 +1,103 @@
1
+ import { DatabaseSync } from "node:sqlite";
2
+ import { pathToFileURL } from "node:url";
3
+ import { sql } from "./sqlite/sql.js";
4
+ function isNamedParameters(value) {
5
+ if (value === null || typeof value !== "object")
6
+ return false;
7
+ if (Array.isArray(value) || ArrayBuffer.isView(value))
8
+ return false;
9
+ return true;
10
+ }
11
+ function isAsyncResult(value) {
12
+ return value !== null && (typeof value === "object" || typeof value === "function") && "then" in value;
13
+ }
14
+ class NodeSqliteStatement {
15
+ statement;
16
+ constructor(statement) {
17
+ this.statement = statement;
18
+ }
19
+ run(...params) {
20
+ const [first, ...rest] = params;
21
+ const result = isNamedParameters(first)
22
+ ? this.statement.run(first, ...rest)
23
+ : this.statement.run(...params);
24
+ return {
25
+ changes: Number(result.changes),
26
+ lastInsertRowid: result.lastInsertRowid === undefined ? undefined : Number(result.lastInsertRowid),
27
+ };
28
+ }
29
+ get(...params) {
30
+ const [first, ...rest] = params;
31
+ return (isNamedParameters(first)
32
+ ? this.statement.get(first, ...rest)
33
+ : this.statement.get(...params));
34
+ }
35
+ all(...params) {
36
+ const [first, ...rest] = params;
37
+ return (isNamedParameters(first)
38
+ ? this.statement.all(first, ...rest)
39
+ : this.statement.all(...params));
40
+ }
41
+ iterate(...params) {
42
+ const [first, ...rest] = params;
43
+ return (isNamedParameters(first)
44
+ ? this.statement.iterate(first, ...rest)
45
+ : this.statement.iterate(...params));
46
+ }
47
+ }
48
+ class NodeSqliteDatabase {
49
+ db;
50
+ constructor(db) {
51
+ this.db = db;
52
+ }
53
+ exec(sql) {
54
+ this.db.exec(sql);
55
+ }
56
+ prepare(sql) {
57
+ return new NodeSqliteStatement(this.db.prepare(sql));
58
+ }
59
+ transaction(fn) {
60
+ sql `BEGIN IMMEDIATE`.exec(this);
61
+ try {
62
+ const result = fn();
63
+ if (isAsyncResult(result)) {
64
+ throw new TypeError("SQLite transaction callbacks must be synchronous");
65
+ }
66
+ sql `COMMIT`.exec(this);
67
+ return result;
68
+ }
69
+ catch (error) {
70
+ try {
71
+ sql `ROLLBACK`.exec(this);
72
+ }
73
+ catch {
74
+ // Ignore rollback errors to rethrow original error.
75
+ }
76
+ throw error;
77
+ }
78
+ }
79
+ close() {
80
+ this.db.close();
81
+ }
82
+ }
83
+ export function wrapNodeSqliteDatabase(db) {
84
+ return new NodeSqliteDatabase(db);
85
+ }
86
+ export function createNodeSqliteFactory() {
87
+ return {
88
+ async open(path) {
89
+ return new NodeSqliteDatabase(new DatabaseSync(path));
90
+ },
91
+ async openExisting(path) {
92
+ const url = pathToFileURL(path);
93
+ url.searchParams.set("mode", "rw");
94
+ return new NodeSqliteDatabase(new DatabaseSync(url));
95
+ },
96
+ async openReadOnly(path) {
97
+ return new NodeSqliteDatabase(new DatabaseSync(path, { readOnly: true }));
98
+ },
99
+ };
100
+ }
101
+ // Re-export the SQLite session backend and types so this package is a complete node-sqlite backend.
102
+ export * from "./sqlite/index.js";
103
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AAGtC,SAAS,iBAAiB,CAAC,KAAc,EAA0C;IAClF,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC9D,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACpE,OAAO,IAAI,CAAC;AAAA,CACZ;AAED,SAAS,aAAa,CAAC,KAAc,EAAW;IAC/C,OAAO,KAAK,KAAK,IAAI,IAAI,CAAC,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,UAAU,CAAC,IAAI,MAAM,IAAI,KAAK,CAAC;AAAA,CACvG;AAED,MAAM,mBAAmB;IACP,SAAS,CAAsC;IAEhE,YAAY,SAA8C,EAAE;QAC3D,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAAA,CAC3B;IAED,GAAG,CAAC,GAAG,MAAiB,EAAmB;QAC1C,MAAM,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,GAAG,MAAM,CAAC;QAChC,MAAM,MAAM,GAAG,iBAAiB,CAAC,KAAK,CAAC;YACtC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,GAAI,IAAwB,CAAC;YACzD,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAI,MAA0B,CAAC,CAAC;QACtD,OAAO;YACN,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;YAC/B,eAAe,EAAE,MAAM,CAAC,eAAe,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,eAAe,CAAC;SAClG,CAAC;IAAA,CACF;IAED,GAAG,CAAsB,GAAG,MAAiB,EAAoB;QAChE,MAAM,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,GAAG,MAAM,CAAC;QAChC,OAAO,CACN,iBAAiB,CAAC,KAAK,CAAC;YACvB,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,GAAI,IAAwB,CAAC;YACzD,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAI,MAA0B,CAAC,CACjC,CAAC;IAAA,CACtB;IAED,GAAG,CAAsB,GAAG,MAAiB,EAAU;QACtD,MAAM,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,GAAG,MAAM,CAAC;QAChC,OAAO,CACN,iBAAiB,CAAC,KAAK,CAAC;YACvB,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,GAAI,IAAwB,CAAC;YACzD,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAI,MAA0B,CAAC,CAC3C,CAAC;IAAA,CACZ;IAED,OAAO,CAAsB,GAAG,MAAiB,EAAkB;QAClE,MAAM,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,GAAG,MAAM,CAAC;QAChC,OAAO,CACN,iBAAiB,CAAC,KAAK,CAAC;YACvB,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,GAAI,IAAwB,CAAC;YAC7D,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,GAAI,MAA0B,CAAC,CACvC,CAAC;IAAA,CACpB;CACD;AAED,MAAM,kBAAkB;IACN,EAAE,CAAe;IAElC,YAAY,EAAgB,EAAE;QAC7B,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;IAAA,CACb;IAED,IAAI,CAAC,GAAW,EAAQ;QACvB,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAAA,CAClB;IAED,OAAO,CAAC,GAAW,EAAmB;QACrC,OAAO,IAAI,mBAAmB,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;IAAA,CACrD;IAED,WAAW,CAAI,EAAW,EAAK;QAC9B,GAAG,CAAA,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChC,IAAI,CAAC;YACJ,MAAM,MAAM,GAAG,EAAE,EAAE,CAAC;YACpB,IAAI,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC3B,MAAM,IAAI,SAAS,CAAC,kDAAkD,CAAC,CAAC;YACzE,CAAC;YACD,GAAG,CAAA,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACvB,OAAO,MAAM,CAAC;QACf,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,CAAC;gBACJ,GAAG,CAAA,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC1B,CAAC;YAAC,MAAM,CAAC;gBACR,oDAAoD;YACrD,CAAC;YACD,MAAM,KAAK,CAAC;QACb,CAAC;IAAA,CACD;IAED,KAAK,GAAS;QACb,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;IAAA,CAChB;CACD;AAED,MAAM,UAAU,sBAAsB,CAAC,EAAgB,EAAkB;IACxE,OAAO,IAAI,kBAAkB,CAAC,EAAE,CAAC,CAAC;AAAA,CAClC;AAED,MAAM,UAAU,uBAAuB,GAA0B;IAChE,OAAO;QACN,KAAK,CAAC,IAAI,CAAC,IAAY,EAA2B;YACjD,OAAO,IAAI,kBAAkB,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;QAAA,CACtD;QACD,KAAK,CAAC,YAAY,CAAC,IAAY,EAA2B;YACzD,MAAM,GAAG,GAAG,aAAa,CAAC,IAAI,CAAC,CAAC;YAChC,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;YACnC,OAAO,IAAI,kBAAkB,CAAC,IAAI,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC;QAAA,CACrD;QACD,KAAK,CAAC,YAAY,CAAC,IAAY,EAA2B;YACzD,OAAO,IAAI,kBAAkB,CAAC,IAAI,YAAY,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAAA,CAC1E;KACD,CAAC;AAAA,CACF;AAED,oGAAoG;AACpG,cAAc,mBAAmB,CAAC","sourcesContent":["import type { SQLInputValue } from \"node:sqlite\";\nimport { DatabaseSync } from \"node:sqlite\";\nimport { pathToFileURL } from \"node:url\";\nimport { sql } from \"./sqlite/sql.ts\";\nimport type { SqliteDatabase, SqliteDatabaseFactory, SqliteRunResult, SqliteStatement } from \"./sqlite/types.ts\";\n\nfunction isNamedParameters(value: unknown): value is Record<string, SQLInputValue> {\n\tif (value === null || typeof value !== \"object\") return false;\n\tif (Array.isArray(value) || ArrayBuffer.isView(value)) return false;\n\treturn true;\n}\n\nfunction isAsyncResult(value: unknown): boolean {\n\treturn value !== null && (typeof value === \"object\" || typeof value === \"function\") && \"then\" in value;\n}\n\nclass NodeSqliteStatement implements SqliteStatement {\n\tprivate readonly statement: ReturnType<DatabaseSync[\"prepare\"]>;\n\n\tconstructor(statement: ReturnType<DatabaseSync[\"prepare\"]>) {\n\t\tthis.statement = statement;\n\t}\n\n\trun(...params: unknown[]): SqliteRunResult {\n\t\tconst [first, ...rest] = params;\n\t\tconst result = isNamedParameters(first)\n\t\t\t? this.statement.run(first, ...(rest as SQLInputValue[]))\n\t\t\t: this.statement.run(...(params as SQLInputValue[]));\n\t\treturn {\n\t\t\tchanges: Number(result.changes),\n\t\t\tlastInsertRowid: result.lastInsertRowid === undefined ? undefined : Number(result.lastInsertRowid),\n\t\t};\n\t}\n\n\tget<TRow extends object>(...params: unknown[]): TRow | undefined {\n\t\tconst [first, ...rest] = params;\n\t\treturn (\n\t\t\tisNamedParameters(first)\n\t\t\t\t? this.statement.get(first, ...(rest as SQLInputValue[]))\n\t\t\t\t: this.statement.get(...(params as SQLInputValue[]))\n\t\t) as TRow | undefined;\n\t}\n\n\tall<TRow extends object>(...params: unknown[]): TRow[] {\n\t\tconst [first, ...rest] = params;\n\t\treturn (\n\t\t\tisNamedParameters(first)\n\t\t\t\t? this.statement.all(first, ...(rest as SQLInputValue[]))\n\t\t\t\t: this.statement.all(...(params as SQLInputValue[]))\n\t\t) as TRow[];\n\t}\n\n\titerate<TRow extends object>(...params: unknown[]): Iterable<TRow> {\n\t\tconst [first, ...rest] = params;\n\t\treturn (\n\t\t\tisNamedParameters(first)\n\t\t\t\t? this.statement.iterate(first, ...(rest as SQLInputValue[]))\n\t\t\t\t: this.statement.iterate(...(params as SQLInputValue[]))\n\t\t) as Iterable<TRow>;\n\t}\n}\n\nclass NodeSqliteDatabase implements SqliteDatabase {\n\tprivate readonly db: DatabaseSync;\n\n\tconstructor(db: DatabaseSync) {\n\t\tthis.db = db;\n\t}\n\n\texec(sql: string): void {\n\t\tthis.db.exec(sql);\n\t}\n\n\tprepare(sql: string): SqliteStatement {\n\t\treturn new NodeSqliteStatement(this.db.prepare(sql));\n\t}\n\n\ttransaction<T>(fn: () => T): T {\n\t\tsql`BEGIN IMMEDIATE`.exec(this);\n\t\ttry {\n\t\t\tconst result = fn();\n\t\t\tif (isAsyncResult(result)) {\n\t\t\t\tthrow new TypeError(\"SQLite transaction callbacks must be synchronous\");\n\t\t\t}\n\t\t\tsql`COMMIT`.exec(this);\n\t\t\treturn result;\n\t\t} catch (error) {\n\t\t\ttry {\n\t\t\t\tsql`ROLLBACK`.exec(this);\n\t\t\t} catch {\n\t\t\t\t// Ignore rollback errors to rethrow original error.\n\t\t\t}\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tclose(): void {\n\t\tthis.db.close();\n\t}\n}\n\nexport function wrapNodeSqliteDatabase(db: DatabaseSync): SqliteDatabase {\n\treturn new NodeSqliteDatabase(db);\n}\n\nexport function createNodeSqliteFactory(): SqliteDatabaseFactory {\n\treturn {\n\t\tasync open(path: string): Promise<SqliteDatabase> {\n\t\t\treturn new NodeSqliteDatabase(new DatabaseSync(path));\n\t\t},\n\t\tasync openExisting(path: string): Promise<SqliteDatabase> {\n\t\t\tconst url = pathToFileURL(path);\n\t\t\turl.searchParams.set(\"mode\", \"rw\");\n\t\t\treturn new NodeSqliteDatabase(new DatabaseSync(url));\n\t\t},\n\t\tasync openReadOnly(path: string): Promise<SqliteDatabase> {\n\t\t\treturn new NodeSqliteDatabase(new DatabaseSync(path, { readOnly: true }));\n\t\t},\n\t};\n}\n\n// Re-export the SQLite session backend and types so this package is a complete node-sqlite backend.\nexport * from \"./sqlite/index.ts\";\n"]}
@@ -0,0 +1,5 @@
1
+ export * from "./repo.ts";
2
+ export * from "./sql.ts";
3
+ export * from "./storage.ts";
4
+ export type { SqliteDatabase, SqliteDatabaseFactory, SqliteRunResult, SqliteStatement, } from "./types.ts";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sqlite/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC;AAC7B,YAAY,EACX,cAAc,EACd,qBAAqB,EACrB,eAAe,EACf,eAAe,GACf,MAAM,YAAY,CAAC","sourcesContent":["export * from \"./repo.ts\";\nexport * from \"./sql.ts\";\nexport * from \"./storage.ts\";\nexport type {\n\tSqliteDatabase,\n\tSqliteDatabaseFactory,\n\tSqliteRunResult,\n\tSqliteStatement,\n} from \"./types.ts\";\n"]}
@@ -0,0 +1,4 @@
1
+ export * from "./repo.js";
2
+ export * from "./sql.js";
3
+ export * from "./storage.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sqlite/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,UAAU,CAAC;AACzB,cAAc,cAAc,CAAC","sourcesContent":["export * from \"./repo.ts\";\nexport * from \"./sql.ts\";\nexport * from \"./storage.ts\";\nexport type {\n\tSqliteDatabase,\n\tSqliteDatabaseFactory,\n\tSqliteRunResult,\n\tSqliteStatement,\n} from \"./types.ts\";\n"]}
@@ -0,0 +1,121 @@
1
+ -- AgentHarness storage format 4 / storageVersion 1.
2
+ -- A SQLite database file is a session container. The current default repo
3
+ -- placement still creates one file per session, but the schema supports any
4
+ -- number of sessions per file by scoping every durable row with session_id.
5
+ -- Authoritative durable state is entries + scalar_values + list_values +
6
+ -- usage_ledger; branch_* and stats columns on sessions are maintained
7
+ -- projections/caches.
8
+
9
+ CREATE TABLE IF NOT EXISTS sessions (
10
+ id TEXT PRIMARY KEY,
11
+ created_at INTEGER NOT NULL,
12
+ parent_session_id TEXT,
13
+ storage_version INTEGER NOT NULL,
14
+ metadata TEXT,
15
+ message_count INTEGER NOT NULL,
16
+ usage_payload TEXT NOT NULL,
17
+ next_seq INTEGER NOT NULL
18
+ ) WITHOUT ROWID;
19
+
20
+ CREATE TABLE IF NOT EXISTS entries (
21
+ session_id TEXT NOT NULL,
22
+ id TEXT NOT NULL,
23
+ parent_id TEXT,
24
+ seq INTEGER NOT NULL,
25
+ type TEXT NOT NULL,
26
+ custom_type TEXT,
27
+ timestamp INTEGER NOT NULL,
28
+ payload TEXT NOT NULL,
29
+ PRIMARY KEY (session_id, id)
30
+ ) WITHOUT ROWID;
31
+
32
+ CREATE INDEX IF NOT EXISTS ix_entry_parent ON entries(session_id, parent_id);
33
+ CREATE INDEX IF NOT EXISTS ix_entry_seq ON entries(session_id, seq, type);
34
+
35
+ CREATE TABLE IF NOT EXISTS scalar_values (
36
+ session_id TEXT NOT NULL,
37
+ namespace TEXT NOT NULL,
38
+ key TEXT NOT NULL,
39
+ seq INTEGER NOT NULL,
40
+ value TEXT NOT NULL,
41
+ PRIMARY KEY (session_id, namespace, key)
42
+ ) WITHOUT ROWID;
43
+
44
+ CREATE TABLE IF NOT EXISTS list_values (
45
+ session_id TEXT NOT NULL,
46
+ namespace TEXT NOT NULL,
47
+ key TEXT NOT NULL,
48
+ seq INTEGER NOT NULL,
49
+ value TEXT NOT NULL,
50
+ PRIMARY KEY (session_id, namespace, key, seq)
51
+ ) WITHOUT ROWID;
52
+
53
+ CREATE TABLE IF NOT EXISTS usage_ledger (
54
+ session_id TEXT NOT NULL,
55
+ id TEXT NOT NULL,
56
+ seq INTEGER NOT NULL,
57
+ entry_id TEXT,
58
+ adjustment INTEGER NOT NULL,
59
+ usage TEXT NOT NULL,
60
+ details TEXT,
61
+ PRIMARY KEY (session_id, id)
62
+ ) WITHOUT ROWID;
63
+
64
+ CREATE INDEX IF NOT EXISTS ix_usage_seq ON usage_ledger(session_id, seq);
65
+
66
+ -- Storage-level integrity that spans rows/tables. Primary keys enforce
67
+ -- same-table duplicate ids; these triggers enforce the shared entry/usage id
68
+ -- namespace and ordered parent insertion without a TypeScript preflight pass.
69
+ CREATE TRIGGER IF NOT EXISTS trg_entries_validate
70
+ BEFORE INSERT ON entries
71
+ BEGIN
72
+ SELECT RAISE(ABORT, 'missing parent entry')
73
+ WHERE NEW.parent_id IS NOT NULL
74
+ AND NOT EXISTS (
75
+ SELECT 1 FROM entries WHERE session_id = NEW.session_id AND id = NEW.parent_id
76
+ );
77
+
78
+ SELECT RAISE(ABORT, 'duplicate entry or usage id')
79
+ WHERE EXISTS (
80
+ SELECT 1 FROM usage_ledger WHERE session_id = NEW.session_id AND id = NEW.id
81
+ );
82
+ END;
83
+
84
+ CREATE TRIGGER IF NOT EXISTS trg_usage_ledger_validate
85
+ BEFORE INSERT ON usage_ledger
86
+ BEGIN
87
+ SELECT RAISE(ABORT, 'duplicate entry or usage id')
88
+ WHERE EXISTS (
89
+ SELECT 1 FROM entries WHERE session_id = NEW.session_id AND id = NEW.id
90
+ );
91
+ END;
92
+
93
+ -- Private branch index. Not values/lists; no equivalent in the other backends.
94
+ CREATE TABLE IF NOT EXISTS branch_entries (
95
+ session_id TEXT NOT NULL,
96
+ branch_id TEXT NOT NULL,
97
+ entry_id TEXT NOT NULL,
98
+ entry_seq INTEGER NOT NULL,
99
+ entry_type TEXT NOT NULL,
100
+ PRIMARY KEY (session_id, branch_id, entry_id)
101
+ ) WITHOUT ROWID;
102
+
103
+ -- Ordered scans. entry_seq must follow session_id, branch_id directly or ORDER
104
+ -- BY needs a temp b-tree; entry_id and entry_type trail so the index covers
105
+ -- id-only reads.
106
+ CREATE INDEX IF NOT EXISTS ix_be_seq ON branch_entries(session_id, branch_id, entry_seq, entry_id, entry_type);
107
+ -- Type-filtered scans.
108
+ CREATE INDEX IF NOT EXISTS ix_be_type ON branch_entries(session_id, branch_id, entry_type, entry_seq, entry_id);
109
+ CREATE INDEX IF NOT EXISTS ix_be_entry ON branch_entries(session_id, entry_id);
110
+
111
+ CREATE TABLE IF NOT EXISTS branch_meta (
112
+ session_id TEXT NOT NULL,
113
+ branch_id TEXT NOT NULL,
114
+ tip_entry_id TEXT NOT NULL,
115
+ tip_seq INTEGER NOT NULL,
116
+ base_branch_id TEXT,
117
+ base_seq INTEGER,
118
+ PRIMARY KEY (session_id, branch_id)
119
+ ) WITHOUT ROWID;
120
+
121
+ CREATE UNIQUE INDEX IF NOT EXISTS ix_bm_tip ON branch_meta(session_id, tip_entry_id);
@@ -0,0 +1,3 @@
1
+ import type { SqliteDatabase } from "./types.ts";
2
+ export declare function applyInitialSchema(db: SqliteDatabase): Promise<void>;
3
+ //# sourceMappingURL=migrations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../src/sqlite/migrations.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,wBAAsB,kBAAkB,CAAC,EAAE,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAG1E","sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { fileURLToPath } from \"node:url\";\nimport type { SqliteDatabase } from \"./types.ts\";\n\nexport async function applyInitialSchema(db: SqliteDatabase): Promise<void> {\n\tconst migration = await readFile(fileURLToPath(new URL(\"./migrations/001_initial.sql\", import.meta.url)), \"utf8\");\n\tdb.exec(migration);\n}\n"]}
@@ -0,0 +1,7 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { fileURLToPath } from "node:url";
3
+ export async function applyInitialSchema(db) {
4
+ const migration = await readFile(fileURLToPath(new URL("./migrations/001_initial.sql", import.meta.url)), "utf8");
5
+ db.exec(migration);
6
+ }
7
+ //# sourceMappingURL=migrations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrations.js","sourceRoot":"","sources":["../../src/sqlite/migrations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGzC,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,EAAkB,EAAiB;IAC3E,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,aAAa,CAAC,IAAI,GAAG,CAAC,8BAA8B,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAClH,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;AAAA,CACnB","sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { fileURLToPath } from \"node:url\";\nimport type { SqliteDatabase } from \"./types.ts\";\n\nexport async function applyInitialSchema(db: SqliteDatabase): Promise<void> {\n\tconst migration = await readFile(fileURLToPath(new URL(\"./migrations/001_initial.sql\", import.meta.url)), \"utf8\");\n\tdb.exec(migration);\n}\n"]}
@@ -0,0 +1,41 @@
1
+ import type { Context, ForkOptions, SessionCreateOptions } from "@m4ike1/ion-agent-core";
2
+ import { type SqliteSessionMetadata } from "./session/session-row.ts";
3
+ import { SqliteOpenSession } from "./session.ts";
4
+ import type { SqliteDatabaseFactory } from "./types.ts";
5
+ export declare const SQLITE_STORAGE_VERSION = 1;
6
+ export declare const SQLITE_SESSION_EXTENSION = ".sqlite";
7
+ export type SqliteSessionCreateOptions = SessionCreateOptions;
8
+ export interface SqliteSessionRepoOptions {
9
+ directory: string;
10
+ /** Optional single container path. Defaults to one encoded `${id}.sqlite` file per session under directory. */
11
+ databasePath?: string;
12
+ databaseFactory: SqliteDatabaseFactory;
13
+ now?: () => number;
14
+ }
15
+ export declare class SqliteSessionRepo {
16
+ private readonly directory;
17
+ private readonly databasePath;
18
+ private readonly databaseFactory;
19
+ private readonly now;
20
+ private readonly pendingIds;
21
+ private readonly openStorages;
22
+ private readonly openSessions;
23
+ private closed;
24
+ private closePromise;
25
+ constructor(options: SqliteSessionRepoOptions);
26
+ create(options: SqliteSessionCreateOptions | undefined, _context: Context): Promise<SqliteOpenSession>;
27
+ open(metadata: SqliteSessionMetadata, _context: Context): Promise<SqliteOpenSession>;
28
+ list(_options: undefined, _context: Context): Promise<SqliteSessionMetadata[]>;
29
+ delete(metadata: SqliteSessionMetadata, _context: Context): Promise<void>;
30
+ fork(source: SqliteSessionMetadata, options: ForkOptions, context: Context): Promise<SqliteOpenSession>;
31
+ close(context: Context): Promise<void>;
32
+ private createForkSnapshotFromExternalSource;
33
+ private closeOpenSessions;
34
+ private openStorageBackedSession;
35
+ private repositoryPathForMetadata;
36
+ private reserveId;
37
+ private pathForSession;
38
+ private usesSharedDatabase;
39
+ private assertOpen;
40
+ }
41
+ //# sourceMappingURL=repo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repo.d.ts","sourceRoot":"","sources":["../../src/sqlite/repo.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAAS,WAAW,EAAE,oBAAoB,EAAe,MAAM,wBAAwB,CAAC;AAM7G,OAAO,EAON,KAAK,qBAAqB,EAC1B,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAGjD,OAAO,KAAK,EAAkB,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAExE,eAAO,MAAM,sBAAsB,IAAI,CAAC;AACxC,eAAO,MAAM,wBAAwB,YAAY,CAAC;AAKlD,MAAM,MAAM,0BAA0B,GAAG,oBAAoB,CAAC;AAE9D,MAAM,WAAW,wBAAwB;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,+GAA+G;IAC/G,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,qBAAqB,CAAC;IACvC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACnB;AAuHD,qBAAa,iBAAiB;IAC7B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;IAClD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAwB;IACxD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqB;IAChD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAoC;IACjE,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAgC;IAC7D,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,YAAY,CAA4B;IAEhD,YAAY,OAAO,EAAE,wBAAwB,EAK5C;IAEK,MAAM,CAAC,OAAO,EAAE,0BAA0B,GAAG,SAAS,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAiD3G;IAEK,IAAI,CAAC,QAAQ,EAAE,qBAAqB,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAsBzF;IAEK,IAAI,CAAC,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,qBAAqB,EAAE,CAAC,CAmCnF;IAEK,MAAM,CAAC,QAAQ,EAAE,qBAAqB,EAAE,QAAQ,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAuB9E;IAEK,IAAI,CAAC,MAAM,EAAE,qBAAqB,EAAE,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAgE5G;IAED,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAKrC;YAEa,oCAAoC;YAcpC,iBAAiB;IAO/B,OAAO,CAAC,wBAAwB;YAoBlB,yBAAyB;IAWvC,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,kBAAkB;IAI1B,OAAO,CAAC,UAAU;CAGlB","sourcesContent":["import { mkdir, open as openFile, readdir, realpath, rm } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport type { Context, Entry, ForkOptions, SessionCreateOptions, StoredValue } from \"@m4ike1/ion-agent-core\";\nimport { branchTip, createForkSnapshot, StorageBackedSession } from \"@m4ike1/ion-agent-core\";\nimport { uuidv7 } from \"@m4ike1/ion-ai\";\nimport { applyInitialSchema } from \"./migrations.ts\";\nimport { appendEntryToBranchIndex, scanBranchEntries } from \"./session/branch-entries.ts\";\nimport { decodeEntryRow, type EntryRow, EntryRowWriter } from \"./session/entries.ts\";\nimport {\n\tdeleteSessionRows,\n\thasSessionRow,\n\tinsertSessionRow,\n\tmetadataFromSessionRow,\n\treadAllSessionRows,\n\treadSessionRow,\n\ttype SqliteSessionMetadata,\n} from \"./session/session-row.ts\";\nimport { readAllScalarValueRows, setScalarValueRow } from \"./session/values.ts\";\nimport { SqliteOpenSession } from \"./session.ts\";\nimport { sql } from \"./sql.ts\";\nimport { SqliteStorage, type SqliteStorageSnapshot } from \"./storage.ts\";\nimport type { SqliteDatabase, SqliteDatabaseFactory } from \"./types.ts\";\n\nexport const SQLITE_STORAGE_VERSION = 1;\nexport const SQLITE_SESSION_EXTENSION = \".sqlite\";\n\nconst FIRST_AVAILABLE_COMMIT_SEQ = 1;\nconst SAFE_SESSION_FILE_ID = /^[A-Za-z0-9_-]+$/;\n\nexport type SqliteSessionCreateOptions = SessionCreateOptions;\n\nexport interface SqliteSessionRepoOptions {\n\tdirectory: string;\n\t/** Optional single container path. Defaults to one encoded `${id}.sqlite` file per session under directory. */\n\tdatabasePath?: string;\n\tdatabaseFactory: SqliteDatabaseFactory;\n\tnow?: () => number;\n}\n\nfunction sessionFileName(id: string): string {\n\tif (SAFE_SESSION_FILE_ID.test(id)) return `${id}${SQLITE_SESSION_EXTENSION}`;\n\tconst encoded = Buffer.from(id, \"utf16le\").toString(\"base64url\");\n\treturn `~${encoded}${SQLITE_SESSION_EXTENSION}`;\n}\n\nfunction sessionPath(directory: string, id: string): string {\n\treturn join(directory, sessionFileName(id));\n}\n\nfunction storageIdentity(path: string, sessionId: string): string {\n\treturn JSON.stringify([path, sessionId]);\n}\n\nfunction isErrorWithCode(error: unknown, code: string): boolean {\n\treturn typeof error === \"object\" && error !== null && \"code\" in error && error.code === code;\n}\n\nasync function removeSessionFiles(path: string, options: { force: boolean }): Promise<void> {\n\tawait rm(path, { force: options.force });\n\tawait rm(`${path}-wal`, { force: true });\n\tawait rm(`${path}-shm`, { force: true });\n}\n\nfunction configureWritableConnection(db: SqliteDatabase): void {\n\tdb.exec(\"PRAGMA journal_mode = WAL; PRAGMA busy_timeout = 5000;\");\n}\n\nfunction configureReadOnlyConnection(db: SqliteDatabase): void {\n\tdb.exec(\"PRAGMA busy_timeout = 5000;\");\n}\n\ninterface ForkSnapshot {\n\tentries: Entry[];\n\tscalarValues: StoredValue<unknown>[];\n\tmessageCount: number;\n\tnextSeq: number;\n}\n\nfunction readSourceEntries(db: SqliteDatabase, sessionId: string): Entry[] {\n\treturn sql`SELECT id, parent_id, seq, type, custom_type, timestamp, payload\n\t\tFROM entries WHERE session_id = ${sessionId} ORDER BY seq ASC`\n\t\t.all<EntryRow>(db)\n\t\t.map(decodeEntryRow);\n}\n\nfunction buildForkSnapshot(source: SqliteStorageSnapshot, options: ForkOptions): ForkSnapshot {\n\tconst snapshot = createForkSnapshot(\n\t\t{\n\t\t\tentries: source.entries,\n\t\t\tscalarValues: source.scalarValues,\n\t\t\tentriesComplete: source.entriesComplete,\n\t\t},\n\t\toptions,\n\t);\n\tconst entries = [...snapshot.entries.values()].sort((left, right) => left.seq - right.seq);\n\treturn {\n\t\tentries,\n\t\tscalarValues: snapshot.scalarValues,\n\t\tmessageCount: entries.filter((entry) => entry.type === \"message\").length,\n\t\tnextSeq: snapshot.nextSeq,\n\t};\n}\n\n// TODO(WP08): Remove this snapshot path when SQLite forks use streaming staging.\nfunction readForkSourceEntries(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\tscalarValues: readonly StoredValue<unknown>[],\n\toptions: ForkOptions,\n): Entry[] {\n\tif (options.scope === \"tree\") return readSourceEntries(db, sessionId);\n\tconst sourceAddress = branchTip(options.branch);\n\tconst sourceTip = scalarValues.find(\n\t\t(stored) => stored.address.namespace === sourceAddress.namespace && stored.address.key === sourceAddress.key,\n\t) as StoredValue<string | null> | undefined;\n\tif (sourceTip === undefined) throw new Error(`Unknown source branch: ${options.branch}`);\n\treturn sourceTip.value === null\n\t\t? []\n\t\t: scanBranchEntries(db, sessionId, { start: sourceTip.value, order: \"oldestFirst\" });\n}\n\nfunction createSqliteForkSnapshot(\n\tsourceDb: SqliteDatabase,\n\tsource: SqliteSessionMetadata,\n\toptions: ForkOptions,\n): ForkSnapshot {\n\tsourceDb.exec(\"BEGIN\");\n\tlet committed = false;\n\ttry {\n\t\tmetadataFromSessionRow(source.path, readSessionRow(sourceDb, source.id), SQLITE_STORAGE_VERSION);\n\t\tconst scalarValues = readAllScalarValueRows(sourceDb, source.id);\n\t\tconst snapshot = buildForkSnapshot(\n\t\t\t{\n\t\t\t\tentries: readForkSourceEntries(sourceDb, source.id, scalarValues, options),\n\t\t\t\tscalarValues,\n\t\t\t\tentriesComplete: options.scope === \"tree\",\n\t\t\t},\n\t\t\toptions,\n\t\t);\n\t\tsourceDb.exec(\"COMMIT\");\n\t\tcommitted = true;\n\t\treturn snapshot;\n\t} catch (error) {\n\t\tif (!committed) sourceDb.exec(\"ROLLBACK\");\n\t\tthrow error;\n\t}\n}\n\nfunction insertForkValue(db: SqliteDatabase, sessionId: string, stored: StoredValue<unknown>): void {\n\tsetScalarValueRow(db, sessionId, stored.address.namespace, stored.address.key, stored.seq, stored.value);\n}\n\nfunction updateForkSessionStats(db: SqliteDatabase, sessionId: string, messageCount: number): void {\n\tsql`UPDATE sessions SET message_count = ${messageCount} WHERE id = ${sessionId}`.run(db);\n}\n\nexport class SqliteSessionRepo {\n\tprivate readonly directory: string;\n\tprivate readonly databasePath: string | undefined;\n\tprivate readonly databaseFactory: SqliteDatabaseFactory;\n\tprivate readonly now: () => number;\n\tprivate readonly pendingIds = new Set<string>();\n\tprivate readonly openStorages = new Map<string, SqliteStorage>();\n\tprivate readonly openSessions = new Set<SqliteOpenSession>();\n\tprivate closed = false;\n\tprivate closePromise: Promise<void> | undefined;\n\n\tconstructor(options: SqliteSessionRepoOptions) {\n\t\tthis.directory = options.directory;\n\t\tthis.databasePath = options.databasePath;\n\t\tthis.databaseFactory = options.databaseFactory;\n\t\tthis.now = options.now ?? Date.now;\n\t}\n\n\tasync create(options: SqliteSessionCreateOptions | undefined, _context: Context): Promise<SqliteOpenSession> {\n\t\tthis.assertOpen();\n\t\toptions ??= {};\n\t\tconst createdAt = this.now();\n\t\tconst id = options.id ?? uuidv7(createdAt);\n\t\tthis.reserveId(id);\n\t\tconst path = this.pathForSession(id);\n\t\tlet db: SqliteDatabase | undefined;\n\t\tlet reservedFile = false;\n\t\tlet initialized = false;\n\t\tlet session: SqliteOpenSession | undefined;\n\t\ttry {\n\t\t\tawait mkdir(dirname(path), { recursive: true });\n\t\t\tif (!this.usesSharedDatabase()) {\n\t\t\t\tconst file = await openFile(path, \"wx\");\n\t\t\t\tawait file.close();\n\t\t\t\treservedFile = true;\n\t\t\t}\n\t\t\tconst activeDb = await this.databaseFactory.open(path);\n\t\t\tdb = activeDb;\n\t\t\tconfigureWritableConnection(activeDb);\n\t\t\tawait applyInitialSchema(activeDb);\n\t\t\tconst canonicalPath = await realpath(path);\n\t\t\tconst metadata: SqliteSessionMetadata = {\n\t\t\t\tid,\n\t\t\t\tcreatedAt,\n\t\t\t\tstorageVersion: SQLITE_STORAGE_VERSION,\n\t\t\t\t...(options.parentSessionId === undefined ? {} : { parentSessionId: options.parentSessionId }),\n\t\t\t\tpath: canonicalPath,\n\t\t\t};\n\t\t\tactiveDb.transaction(() => {\n\t\t\t\tif (hasSessionRow(activeDb, id)) throw new Error(`SQLite session already exists: ${id}`);\n\t\t\t\tinsertSessionRow(activeDb, metadata, SQLITE_STORAGE_VERSION, FIRST_AVAILABLE_COMMIT_SEQ);\n\t\t\t});\n\t\t\tinitialized = true;\n\t\t\tsession = this.openStorageBackedSession(metadata, activeDb);\n\t\t\treturn session;\n\t\t} catch (error) {\n\t\t\tif (reservedFile && !initialized) await removeSessionFiles(path, { force: true });\n\t\t\tthrow error;\n\t\t} finally {\n\t\t\tif (session === undefined) {\n\t\t\t\ttry {\n\t\t\t\t\tdb?.close();\n\t\t\t\t} finally {\n\t\t\t\t\tthis.pendingIds.delete(id);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tasync open(metadata: SqliteSessionMetadata, _context: Context): Promise<SqliteOpenSession> {\n\t\tthis.assertOpen();\n\t\tthis.reserveId(metadata.id);\n\t\tlet db: SqliteDatabase | undefined;\n\t\tlet session: SqliteOpenSession | undefined;\n\t\ttry {\n\t\t\tconst path = await this.repositoryPathForMetadata(metadata);\n\t\t\tconst activeDb = await this.databaseFactory.openExisting(path);\n\t\t\tdb = activeDb;\n\t\t\tconfigureWritableConnection(activeDb);\n\t\t\tconst stored = metadataFromSessionRow(path, readSessionRow(activeDb, metadata.id), SQLITE_STORAGE_VERSION);\n\t\t\tsession = this.openStorageBackedSession(stored, activeDb);\n\t\t\treturn session;\n\t\t} finally {\n\t\t\tif (session === undefined) {\n\t\t\t\ttry {\n\t\t\t\t\tdb?.close();\n\t\t\t\t} finally {\n\t\t\t\t\tthis.pendingIds.delete(metadata.id);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tasync list(_options: undefined, _context: Context): Promise<SqliteSessionMetadata[]> {\n\t\tthis.assertOpen();\n\t\tlet paths: string[];\n\t\tif (this.usesSharedDatabase()) {\n\t\t\tpaths = [this.databasePath!];\n\t\t} else {\n\t\t\tlet names: string[];\n\t\t\ttry {\n\t\t\t\tnames = await readdir(this.directory);\n\t\t\t} catch (error) {\n\t\t\t\tif (isErrorWithCode(error, \"ENOENT\")) return [];\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t\tpaths = names\n\t\t\t\t.filter((name) => name.endsWith(SQLITE_SESSION_EXTENSION))\n\t\t\t\t.map((name) => join(this.directory, name));\n\t\t}\n\t\tconst sessions: SqliteSessionMetadata[] = [];\n\t\tfor (const path of paths) {\n\t\t\tlet db: SqliteDatabase | undefined;\n\t\t\ttry {\n\t\t\t\tconst canonicalPath = await realpath(path);\n\t\t\t\tdb = await this.databaseFactory.openReadOnly(canonicalPath);\n\t\t\t\tconfigureReadOnlyConnection(db);\n\t\t\t\tfor (const row of readAllSessionRows(db)) {\n\t\t\t\t\tsessions.push(metadataFromSessionRow(canonicalPath, row, SQLITE_STORAGE_VERSION));\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Discovery is best-effort: corrupt files, incompatible versions, and\n\t\t\t\t// unrelated *.sqlite files are reported when explicitly opened.\n\t\t\t} finally {\n\t\t\t\tdb?.close();\n\t\t\t}\n\t\t}\n\t\treturn sessions.sort((left, right) => right.createdAt - left.createdAt);\n\t}\n\n\tasync delete(metadata: SqliteSessionMetadata, _context: Context): Promise<void> {\n\t\tthis.assertOpen();\n\t\tthis.reserveId(metadata.id);\n\t\ttry {\n\t\t\tconst path = await this.repositoryPathForMetadata(metadata);\n\t\t\tconst db = await this.databaseFactory.openExisting(path);\n\t\t\ttry {\n\t\t\t\tconfigureWritableConnection(db);\n\t\t\t\tif (this.usesSharedDatabase()) {\n\t\t\t\t\tdb.transaction(() => {\n\t\t\t\t\t\tmetadataFromSessionRow(path, readSessionRow(db, metadata.id), SQLITE_STORAGE_VERSION);\n\t\t\t\t\t\tdeleteSessionRows(db, metadata.id);\n\t\t\t\t\t});\n\t\t\t\t} else {\n\t\t\t\t\tmetadataFromSessionRow(path, readSessionRow(db, metadata.id), SQLITE_STORAGE_VERSION);\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tdb.close();\n\t\t\t}\n\t\t\tif (!this.usesSharedDatabase()) await removeSessionFiles(path, { force: false });\n\t\t} finally {\n\t\t\tthis.pendingIds.delete(metadata.id);\n\t\t}\n\t}\n\n\tasync fork(source: SqliteSessionMetadata, options: ForkOptions, context: Context): Promise<SqliteOpenSession> {\n\t\tthis.assertOpen();\n\t\tconst createdAt = this.now();\n\t\tconst id = options.id ?? uuidv7(createdAt);\n\t\tthis.reserveId(id);\n\t\tconst sourceStorage = this.openStorages.get(storageIdentity(source.path, source.id));\n\t\tconst activeSourceSnapshot = sourceStorage?.snapshot(options, context);\n\t\tvoid activeSourceSnapshot?.catch(() => undefined);\n\t\tconst path = this.pathForSession(id);\n\t\tlet db: SqliteDatabase | undefined;\n\t\tlet reservedFile = false;\n\t\tlet initialized = false;\n\t\tlet session: SqliteOpenSession | undefined;\n\t\ttry {\n\t\t\tawait mkdir(dirname(path), { recursive: true });\n\t\t\tif (!this.usesSharedDatabase()) {\n\t\t\t\tconst file = await openFile(path, \"wx\");\n\t\t\t\tawait file.close();\n\t\t\t\treservedFile = true;\n\t\t\t}\n\n\t\t\tconst snapshot =\n\t\t\t\tactiveSourceSnapshot === undefined\n\t\t\t\t\t? await this.createForkSnapshotFromExternalSource(source, options)\n\t\t\t\t\t: buildForkSnapshot(await activeSourceSnapshot, options);\n\n\t\t\tconst activeDb = await this.databaseFactory.open(path);\n\t\t\tdb = activeDb;\n\t\t\tconfigureWritableConnection(activeDb);\n\t\t\tawait applyInitialSchema(activeDb);\n\t\t\tconst canonicalPath = await realpath(path);\n\t\t\tconst metadata: SqliteSessionMetadata = {\n\t\t\t\tid,\n\t\t\t\tcreatedAt,\n\t\t\t\tstorageVersion: SQLITE_STORAGE_VERSION,\n\t\t\t\tparentSessionId: source.id,\n\t\t\t\tpath: canonicalPath,\n\t\t\t};\n\t\t\tactiveDb.transaction(() => {\n\t\t\t\tif (hasSessionRow(activeDb, id)) throw new Error(`SQLite session already exists: ${id}`);\n\t\t\t\tinsertSessionRow(activeDb, metadata, SQLITE_STORAGE_VERSION, snapshot.nextSeq);\n\t\t\t\tconst entryWriter = new EntryRowWriter(activeDb, id);\n\t\t\t\tfor (const entry of snapshot.entries) {\n\t\t\t\t\tentryWriter.insert(entry);\n\t\t\t\t\tappendEntryToBranchIndex(activeDb, id, entry);\n\t\t\t\t}\n\t\t\t\tfor (const stored of snapshot.scalarValues) insertForkValue(activeDb, id, stored);\n\t\t\t\tupdateForkSessionStats(activeDb, id, snapshot.messageCount);\n\t\t\t});\n\t\t\tinitialized = true;\n\t\t\tsession = this.openStorageBackedSession(metadata, activeDb);\n\t\t\treturn session;\n\t\t} catch (error) {\n\t\t\tif (reservedFile && !initialized) await removeSessionFiles(path, { force: true });\n\t\t\tthrow error;\n\t\t} finally {\n\t\t\tif (session === undefined) {\n\t\t\t\ttry {\n\t\t\t\t\tdb?.close();\n\t\t\t\t} finally {\n\t\t\t\t\tthis.pendingIds.delete(id);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tclose(context: Context): Promise<void> {\n\t\tif (this.closePromise !== undefined) return this.closePromise;\n\t\tthis.closed = true;\n\t\tthis.closePromise = this.closeOpenSessions(context);\n\t\treturn this.closePromise;\n\t}\n\n\tprivate async createForkSnapshotFromExternalSource(\n\t\tsource: SqliteSessionMetadata,\n\t\toptions: ForkOptions,\n\t): Promise<ForkSnapshot> {\n\t\tconst path = await realpath(source.path);\n\t\tconst sourceDb = await this.databaseFactory.openReadOnly(path);\n\t\ttry {\n\t\t\tconfigureReadOnlyConnection(sourceDb);\n\t\t\treturn createSqliteForkSnapshot(sourceDb, { ...source, path }, options);\n\t\t} finally {\n\t\t\tsourceDb.close();\n\t\t}\n\t}\n\n\tprivate async closeOpenSessions(context: Context): Promise<void> {\n\t\tconst results = await Promise.allSettled([...this.openSessions].map((session) => session.close(context)));\n\t\tconst errors = results.flatMap((result) => (result.status === \"rejected\" ? [result.reason] : []));\n\t\tif (errors.length === 1) throw errors[0];\n\t\tif (errors.length > 1) throw new AggregateError(errors, \"Failed to close SQLite Sessions\");\n\t}\n\n\tprivate openStorageBackedSession(metadata: SqliteSessionMetadata, db: SqliteDatabase): SqliteOpenSession {\n\t\tconst key = storageIdentity(metadata.path, metadata.id);\n\t\tconst storage = new SqliteStorage(db, { sessionId: metadata.id, now: this.now });\n\t\tthis.openStorages.set(key, storage);\n\t\tconst session = new StorageBackedSession(metadata, storage);\n\t\tconst openSession = new SqliteOpenSession(session, {\n\t\t\tonClose: () => {\n\t\t\t\ttry {\n\t\t\t\t\tdb.close();\n\t\t\t\t} finally {\n\t\t\t\t\tif (this.openStorages.get(key) === storage) this.openStorages.delete(key);\n\t\t\t\t\tthis.openSessions.delete(openSession);\n\t\t\t\t\tthis.pendingIds.delete(metadata.id);\n\t\t\t\t}\n\t\t\t},\n\t\t});\n\t\tthis.openSessions.add(openSession);\n\t\treturn openSession;\n\t}\n\n\tprivate async repositoryPathForMetadata(metadata: SqliteSessionMetadata): Promise<string> {\n\t\tconst [expected, actual] = await Promise.all([\n\t\t\trealpath(this.pathForSession(metadata.id)),\n\t\t\trealpath(metadata.path),\n\t\t]);\n\t\tif (expected !== actual) {\n\t\t\tthrow new Error(`SQLite session metadata path is outside this repository: ${metadata.path}`);\n\t\t}\n\t\treturn actual;\n\t}\n\n\tprivate reserveId(id: string): void {\n\t\tif (this.pendingIds.has(id)) throw new Error(`Session is already open: ${id}`);\n\t\tthis.pendingIds.add(id);\n\t}\n\n\tprivate pathForSession(id: string): string {\n\t\treturn this.databasePath ?? sessionPath(this.directory, id);\n\t}\n\n\tprivate usesSharedDatabase(): boolean {\n\t\treturn this.databasePath !== undefined;\n\t}\n\n\tprivate assertOpen(): void {\n\t\tif (this.closed) throw new Error(\"SqliteSessionRepo is closed\");\n\t}\n}\n"]}