@earendil-works/pi-session-backend-sqlite-node 0.84.3 → 0.85.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/CHANGELOG.md +3 -1
- package/README.md +26 -15
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/sqlite/index.d.ts +3 -4
- package/dist/sqlite/index.d.ts.map +1 -1
- package/dist/sqlite/index.js +2 -3
- package/dist/sqlite/index.js.map +1 -1
- package/dist/sqlite/migrations/001_initial.sql +88 -89
- package/dist/sqlite/migrations.d.ts +1 -7
- package/dist/sqlite/migrations.d.ts.map +1 -1
- package/dist/sqlite/migrations.js +3 -35
- package/dist/sqlite/migrations.js.map +1 -1
- package/dist/sqlite/repo.d.ts +38 -38
- package/dist/sqlite/repo.d.ts.map +1 -1
- package/dist/sqlite/repo.js +353 -695
- package/dist/sqlite/repo.js.map +1 -1
- package/dist/sqlite/session/branch-entries.d.ts +6 -0
- package/dist/sqlite/session/branch-entries.d.ts.map +1 -0
- package/dist/sqlite/session/branch-entries.js +213 -0
- package/dist/sqlite/session/branch-entries.js.map +1 -0
- package/dist/sqlite/session/entries.d.ts +24 -0
- package/dist/sqlite/session/entries.d.ts.map +1 -0
- package/dist/sqlite/session/entries.js +126 -0
- package/dist/sqlite/session/entries.js.map +1 -0
- package/dist/sqlite/session/session-row.d.ts +23 -0
- package/dist/sqlite/session/session-row.d.ts.map +1 -0
- package/dist/sqlite/session/session-row.js +69 -0
- package/dist/sqlite/session/session-row.js.map +1 -0
- package/dist/sqlite/session/session-sequences.d.ts +4 -0
- package/dist/sqlite/session/session-sequences.d.ts.map +1 -0
- package/dist/sqlite/session/session-sequences.js +13 -0
- package/dist/sqlite/session/session-sequences.js.map +1 -0
- package/dist/sqlite/session/session-stats.d.ts +6 -0
- package/dist/sqlite/session/session-stats.d.ts.map +1 -0
- package/dist/sqlite/session/session-stats.js +39 -0
- package/dist/sqlite/session/session-stats.js.map +1 -0
- package/dist/sqlite/session/usage-ledger.d.ts +20 -0
- package/dist/sqlite/session/usage-ledger.d.ts.map +1 -0
- package/dist/sqlite/session/usage-ledger.js +50 -0
- package/dist/sqlite/session/usage-ledger.js.map +1 -0
- package/dist/sqlite/session/values.d.ts +23 -0
- package/dist/sqlite/session/values.d.ts.map +1 -0
- package/dist/sqlite/session/values.js +91 -0
- package/dist/sqlite/session/values.js.map +1 -0
- package/dist/sqlite/session.d.ts +42 -0
- package/dist/sqlite/session.d.ts.map +1 -0
- package/dist/sqlite/session.js +163 -0
- package/dist/sqlite/session.js.map +1 -0
- package/dist/sqlite/storage.d.ts +38 -0
- package/dist/sqlite/storage.d.ts.map +1 -0
- package/dist/sqlite/storage.js +161 -0
- package/dist/sqlite/storage.js.map +1 -0
- package/dist/sqlite/types.d.ts +6 -22
- package/dist/sqlite/types.d.ts.map +1 -1
- package/dist/sqlite/types.js.map +1 -1
- package/package.json +5 -4
- package/dist/sqlite/branch-cache.d.ts +0 -6
- package/dist/sqlite/branch-cache.d.ts.map +0 -1
- package/dist/sqlite/branch-cache.js +0 -70
- package/dist/sqlite/branch-cache.js.map +0 -1
- package/dist/sqlite/search-backend.d.ts +0 -14
- package/dist/sqlite/search-backend.d.ts.map +0 -1
- package/dist/sqlite/search-backend.js +0 -153
- package/dist/sqlite/search-backend.js.map +0 -1
- package/dist/sqlite/storage/branch-entries.d.ts +0 -41
- package/dist/sqlite/storage/branch-entries.d.ts.map +0 -1
- package/dist/sqlite/storage/branch-entries.js +0 -112
- package/dist/sqlite/storage/branch-entries.js.map +0 -1
- package/dist/sqlite/storage/branch-tips.d.ts +0 -7
- package/dist/sqlite/storage/branch-tips.d.ts.map +0 -1
- package/dist/sqlite/storage/branch-tips.js +0 -22
- package/dist/sqlite/storage/branch-tips.js.map +0 -1
- package/dist/sqlite/storage/entries.d.ts +0 -34
- package/dist/sqlite/storage/entries.d.ts.map +0 -1
- package/dist/sqlite/storage/entries.js +0 -37
- package/dist/sqlite/storage/entries.js.map +0 -1
- package/dist/sqlite/storage/facts.d.ts +0 -20
- package/dist/sqlite/storage/facts.d.ts.map +0 -1
- package/dist/sqlite/storage/facts.js +0 -38
- package/dist/sqlite/storage/facts.js.map +0 -1
- package/dist/sqlite/storage/lanes.d.ts +0 -35
- package/dist/sqlite/storage/lanes.d.ts.map +0 -1
- package/dist/sqlite/storage/lanes.js +0 -95
- package/dist/sqlite/storage/lanes.js.map +0 -1
- package/dist/sqlite/storage/records.d.ts +0 -38
- package/dist/sqlite/storage/records.d.ts.map +0 -1
- package/dist/sqlite/storage/records.js +0 -49
- package/dist/sqlite/storage/records.js.map +0 -1
- package/dist/sqlite/storage/session-sequences.d.ts +0 -7
- package/dist/sqlite/storage/session-sequences.d.ts.map +0 -1
- package/dist/sqlite/storage/session-sequences.js +0 -22
- package/dist/sqlite/storage/session-sequences.js.map +0 -1
- package/dist/sqlite/storage/session-stats.d.ts +0 -17
- package/dist/sqlite/storage/session-stats.d.ts.map +0 -1
- package/dist/sqlite/storage/session-stats.js +0 -40
- package/dist/sqlite/storage/session-stats.js.map +0 -1
- package/dist/sqlite/storage/sessions.d.ts +0 -26
- package/dist/sqlite/storage/sessions.d.ts.map +0 -1
- package/dist/sqlite/storage/sessions.js +0 -99
- package/dist/sqlite/storage/sessions.js.map +0 -1
- package/dist/sqlite/storage/writer-leases.d.ts +0 -15
- package/dist/sqlite/storage/writer-leases.d.ts.map +0 -1
- package/dist/sqlite/storage/writer-leases.js +0 -31
- package/dist/sqlite/storage/writer-leases.js.map +0 -1
package/dist/sqlite/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/sqlite/types.ts"],"names":[],"mappings":"","sourcesContent":["
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/sqlite/types.ts"],"names":[],"mappings":"","sourcesContent":["/** Result of a prepared SQLite statement execution. */\nexport interface SqliteRunResult {\n\tchanges: number;\n\tlastInsertRowid?: number;\n}\n\n/** Prepared SQLite statement capability used by the SQLite session backend. */\nexport interface SqliteStatement {\n\trun(...params: unknown[]): SqliteRunResult;\n\tget<TRow extends object>(...params: unknown[]): TRow | undefined;\n\tall<TRow extends object>(...params: unknown[]): TRow[];\n\titerate<TRow extends object>(...params: unknown[]): Iterable<TRow>;\n}\n\n/** SQLite database capability used by the SQLite session backend. */\nexport interface SqliteDatabase {\n\texec(sql: string): void;\n\tprepare(sql: string): SqliteStatement;\n\t/** Runs a synchronous write transaction. The callback must not return a promise. */\n\ttransaction<T>(callback: () => T): T;\n\tclose(): void;\n}\n\nexport interface SqliteDatabaseFactory {\n\t/** Open a writable database, creating it when absent. */\n\topen(path: string): Promise<SqliteDatabase>;\n\t/** Open a writable database without creating it. */\n\topenExisting(path: string): Promise<SqliteDatabase>;\n\t/** Open an existing database read-only. */\n\topenReadOnly(path: string): Promise<SqliteDatabase>;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@earendil-works/pi-session-backend-sqlite-node",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.85.0",
|
|
4
4
|
"description": "Node sqlite session backend for @earendil-works/pi-agent-core sessions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
20
|
"clean": "node -e \"import('node:fs/promises').then(fs=>fs.rm('dist',{recursive:true,force:true}))\"",
|
|
21
|
-
"build": "tsgo -p tsconfig.build.json && node
|
|
21
|
+
"build": "tsgo -p tsconfig.build.json && node scripts/copy-migrations.mjs",
|
|
22
|
+
"bench:session:timing": "vitest bench --config vitest.benchmark.config.ts",
|
|
22
23
|
"test": "vitest --run",
|
|
23
24
|
"coverage": "vitest --run --coverage",
|
|
24
25
|
"prepublishOnly": "npm run clean && npm run build"
|
|
@@ -34,8 +35,8 @@
|
|
|
34
35
|
"node": ">=22.19.0"
|
|
35
36
|
},
|
|
36
37
|
"dependencies": {
|
|
37
|
-
"@earendil-works/pi-ai": "^0.
|
|
38
|
-
"@earendil-works/pi-agent-core": "^0.
|
|
38
|
+
"@earendil-works/pi-ai": "^0.85.0",
|
|
39
|
+
"@earendil-works/pi-agent-core": "^0.85.0"
|
|
39
40
|
},
|
|
40
41
|
"devDependencies": {
|
|
41
42
|
"@vitest/coverage-v8": "4.1.9",
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
import type { SqliteDatabase } from "./types.ts";
|
|
2
|
-
export declare function deleteBranchCache(db: SqliteDatabase, sessionId: string): void;
|
|
3
|
-
export declare function rebuildBranchCache(db: SqliteDatabase, sessionId: string): void;
|
|
4
|
-
export declare function buildCachedBranch(db: SqliteDatabase, sessionId: string, leafId: string): void;
|
|
5
|
-
export declare function appendEntryToBranchCache(db: SqliteDatabase, sessionId: string, entryId: string, entrySeq: number, entryType: string, customType: string | null, parentId: string | null): void;
|
|
6
|
-
//# sourceMappingURL=branch-cache.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"branch-cache.d.ts","sourceRoot":"","sources":["../../src/sqlite/branch-cache.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,QAGtE;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,QAUvE;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAqBtF;AAkBD,wBAAgB,wBAAwB,CACvC,EAAE,EAAE,cAAc,EAClB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GAAG,IAAI,EACzB,QAAQ,EAAE,MAAM,GAAG,IAAI,QAwBvB","sourcesContent":["import { SessionError } from \"@earendil-works/pi-agent-core\";\nimport { uuidv7 } from \"@earendil-works/pi-ai\";\nimport { sql } from \"./sql.ts\";\nimport {\n\tcopyBranchEntriesThroughSeq,\n\tdeleteBranchEntries,\n\tinsertBranchEntriesForPath,\n\tinsertBranchEntry,\n\treadBranchContainingEntry,\n} from \"./storage/branch-entries.ts\";\nimport { deleteBranchTips, insertBranchTip, readBranchTipBranchId, updateBranchTip } from \"./storage/branch-tips.ts\";\nimport type { SqliteDatabase } from \"./types.ts\";\n\nexport function deleteBranchCache(db: SqliteDatabase, sessionId: string) {\n\tdeleteBranchTips(db, sessionId);\n\tdeleteBranchEntries(db, sessionId);\n}\n\nexport function rebuildBranchCache(db: SqliteDatabase, sessionId: string) {\n\tconst tips = sql`SELECT leaf.id\n\t\tFROM entries AS leaf\n\t\tWHERE leaf.session_id = ${sessionId}\n\t\t\tAND NOT EXISTS (\n\t\t\t\tSELECT 1 FROM entries AS child WHERE child.session_id = leaf.session_id AND child.parent_id = leaf.id\n\t\t\t)\n\t\tORDER BY leaf.seq`.all<{ id: string }>(db);\n\tdeleteBranchCache(db, sessionId);\n\tfor (const tip of tips) buildCachedBranch(db, sessionId, tip.id);\n}\n\nexport function buildCachedBranch(db: SqliteDatabase, sessionId: string, leafId: string) {\n\tsql`SAVEPOINT build_branch_cache`.exec(db);\n\ttry {\n\t\tconst branchId = uuidv7();\n\t\tinsertBranchEntriesForPath(db, sessionId, branchId, leafId);\n\t\tinsertBranchTip(db, sessionId, leafId, branchId);\n\t\tsql`RELEASE SAVEPOINT build_branch_cache`.exec(db);\n\t} catch (error) {\n\t\ttry {\n\t\t\tsql`ROLLBACK TO SAVEPOINT build_branch_cache`.exec(db);\n\t\t\tsql`RELEASE SAVEPOINT build_branch_cache`.exec(db);\n\t\t} catch {\n\t\t\t// Preserve the original build failure.\n\t\t}\n\t\tif (error instanceof SessionError) throw error;\n\t\tthrow new SessionError(\n\t\t\t\"storage\",\n\t\t\t`Failed to build SQLite branch cache at entry ${leafId}`,\n\t\t\terror instanceof Error ? error : undefined,\n\t\t);\n\t}\n}\n\nfunction extendBranch(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\tbranchId: string,\n\tparentId: string,\n\tentryId: string,\n\tentrySeq: number,\n\tentryType: string,\n\tcustomType: string | null,\n) {\n\tinsertBranchEntry(db, sessionId, branchId, entryId, entrySeq, entryType, customType);\n\tif (!updateBranchTip(db, sessionId, branchId, parentId, entryId)) {\n\t\tthrow new SessionError(\"invalid_entry\", `Branch tip ${parentId} changed during append`);\n\t}\n}\n\nexport function appendEntryToBranchCache(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\tentryId: string,\n\tentrySeq: number,\n\tentryType: string,\n\tcustomType: string | null,\n\tparentId: string | null,\n) {\n\tif (parentId === null) {\n\t\tconst branchId = uuidv7();\n\t\tinsertBranchEntry(db, sessionId, branchId, entryId, entrySeq, entryType, customType);\n\t\tinsertBranchTip(db, sessionId, entryId, branchId);\n\t\treturn;\n\t}\n\n\tconst tipBranchId = readBranchTipBranchId(db, sessionId, parentId);\n\tif (tipBranchId !== undefined) {\n\t\textendBranch(db, sessionId, tipBranchId, parentId, entryId, entrySeq, entryType, customType);\n\t\treturn;\n\t}\n\n\tconst source = readBranchContainingEntry(db, sessionId, parentId);\n\tif (!source) {\n\t\tthrow new SessionError(\"invalid_entry\", `Branch cache has no branch containing parent entry ${parentId}`);\n\t}\n\n\tconst branchId = uuidv7();\n\tcopyBranchEntriesThroughSeq(db, sessionId, branchId, source.branchId, source.entrySeq);\n\tinsertBranchEntry(db, sessionId, branchId, entryId, entrySeq, entryType, customType);\n\tinsertBranchTip(db, sessionId, entryId, branchId);\n}\n"]}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { SessionError } from "@earendil-works/pi-agent-core";
|
|
2
|
-
import { uuidv7 } from "@earendil-works/pi-ai";
|
|
3
|
-
import { sql } from "./sql.js";
|
|
4
|
-
import { copyBranchEntriesThroughSeq, deleteBranchEntries, insertBranchEntriesForPath, insertBranchEntry, readBranchContainingEntry, } from "./storage/branch-entries.js";
|
|
5
|
-
import { deleteBranchTips, insertBranchTip, readBranchTipBranchId, updateBranchTip } from "./storage/branch-tips.js";
|
|
6
|
-
export function deleteBranchCache(db, sessionId) {
|
|
7
|
-
deleteBranchTips(db, sessionId);
|
|
8
|
-
deleteBranchEntries(db, sessionId);
|
|
9
|
-
}
|
|
10
|
-
export function rebuildBranchCache(db, sessionId) {
|
|
11
|
-
const tips = sql `SELECT leaf.id
|
|
12
|
-
FROM entries AS leaf
|
|
13
|
-
WHERE leaf.session_id = ${sessionId}
|
|
14
|
-
AND NOT EXISTS (
|
|
15
|
-
SELECT 1 FROM entries AS child WHERE child.session_id = leaf.session_id AND child.parent_id = leaf.id
|
|
16
|
-
)
|
|
17
|
-
ORDER BY leaf.seq`.all(db);
|
|
18
|
-
deleteBranchCache(db, sessionId);
|
|
19
|
-
for (const tip of tips)
|
|
20
|
-
buildCachedBranch(db, sessionId, tip.id);
|
|
21
|
-
}
|
|
22
|
-
export function buildCachedBranch(db, sessionId, leafId) {
|
|
23
|
-
sql `SAVEPOINT build_branch_cache`.exec(db);
|
|
24
|
-
try {
|
|
25
|
-
const branchId = uuidv7();
|
|
26
|
-
insertBranchEntriesForPath(db, sessionId, branchId, leafId);
|
|
27
|
-
insertBranchTip(db, sessionId, leafId, branchId);
|
|
28
|
-
sql `RELEASE SAVEPOINT build_branch_cache`.exec(db);
|
|
29
|
-
}
|
|
30
|
-
catch (error) {
|
|
31
|
-
try {
|
|
32
|
-
sql `ROLLBACK TO SAVEPOINT build_branch_cache`.exec(db);
|
|
33
|
-
sql `RELEASE SAVEPOINT build_branch_cache`.exec(db);
|
|
34
|
-
}
|
|
35
|
-
catch {
|
|
36
|
-
// Preserve the original build failure.
|
|
37
|
-
}
|
|
38
|
-
if (error instanceof SessionError)
|
|
39
|
-
throw error;
|
|
40
|
-
throw new SessionError("storage", `Failed to build SQLite branch cache at entry ${leafId}`, error instanceof Error ? error : undefined);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
function extendBranch(db, sessionId, branchId, parentId, entryId, entrySeq, entryType, customType) {
|
|
44
|
-
insertBranchEntry(db, sessionId, branchId, entryId, entrySeq, entryType, customType);
|
|
45
|
-
if (!updateBranchTip(db, sessionId, branchId, parentId, entryId)) {
|
|
46
|
-
throw new SessionError("invalid_entry", `Branch tip ${parentId} changed during append`);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
export function appendEntryToBranchCache(db, sessionId, entryId, entrySeq, entryType, customType, parentId) {
|
|
50
|
-
if (parentId === null) {
|
|
51
|
-
const branchId = uuidv7();
|
|
52
|
-
insertBranchEntry(db, sessionId, branchId, entryId, entrySeq, entryType, customType);
|
|
53
|
-
insertBranchTip(db, sessionId, entryId, branchId);
|
|
54
|
-
return;
|
|
55
|
-
}
|
|
56
|
-
const tipBranchId = readBranchTipBranchId(db, sessionId, parentId);
|
|
57
|
-
if (tipBranchId !== undefined) {
|
|
58
|
-
extendBranch(db, sessionId, tipBranchId, parentId, entryId, entrySeq, entryType, customType);
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
const source = readBranchContainingEntry(db, sessionId, parentId);
|
|
62
|
-
if (!source) {
|
|
63
|
-
throw new SessionError("invalid_entry", `Branch cache has no branch containing parent entry ${parentId}`);
|
|
64
|
-
}
|
|
65
|
-
const branchId = uuidv7();
|
|
66
|
-
copyBranchEntriesThroughSeq(db, sessionId, branchId, source.branchId, source.entrySeq);
|
|
67
|
-
insertBranchEntry(db, sessionId, branchId, entryId, entrySeq, entryType, customType);
|
|
68
|
-
insertBranchTip(db, sessionId, entryId, branchId);
|
|
69
|
-
}
|
|
70
|
-
//# sourceMappingURL=branch-cache.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"branch-cache.js","sourceRoot":"","sources":["../../src/sqlite/branch-cache.ts"],"names":[],"mappings":"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","sourcesContent":["import { SessionError } from \"@earendil-works/pi-agent-core\";\nimport { uuidv7 } from \"@earendil-works/pi-ai\";\nimport { sql } from \"./sql.ts\";\nimport {\n\tcopyBranchEntriesThroughSeq,\n\tdeleteBranchEntries,\n\tinsertBranchEntriesForPath,\n\tinsertBranchEntry,\n\treadBranchContainingEntry,\n} from \"./storage/branch-entries.ts\";\nimport { deleteBranchTips, insertBranchTip, readBranchTipBranchId, updateBranchTip } from \"./storage/branch-tips.ts\";\nimport type { SqliteDatabase } from \"./types.ts\";\n\nexport function deleteBranchCache(db: SqliteDatabase, sessionId: string) {\n\tdeleteBranchTips(db, sessionId);\n\tdeleteBranchEntries(db, sessionId);\n}\n\nexport function rebuildBranchCache(db: SqliteDatabase, sessionId: string) {\n\tconst tips = sql`SELECT leaf.id\n\t\tFROM entries AS leaf\n\t\tWHERE leaf.session_id = ${sessionId}\n\t\t\tAND NOT EXISTS (\n\t\t\t\tSELECT 1 FROM entries AS child WHERE child.session_id = leaf.session_id AND child.parent_id = leaf.id\n\t\t\t)\n\t\tORDER BY leaf.seq`.all<{ id: string }>(db);\n\tdeleteBranchCache(db, sessionId);\n\tfor (const tip of tips) buildCachedBranch(db, sessionId, tip.id);\n}\n\nexport function buildCachedBranch(db: SqliteDatabase, sessionId: string, leafId: string) {\n\tsql`SAVEPOINT build_branch_cache`.exec(db);\n\ttry {\n\t\tconst branchId = uuidv7();\n\t\tinsertBranchEntriesForPath(db, sessionId, branchId, leafId);\n\t\tinsertBranchTip(db, sessionId, leafId, branchId);\n\t\tsql`RELEASE SAVEPOINT build_branch_cache`.exec(db);\n\t} catch (error) {\n\t\ttry {\n\t\t\tsql`ROLLBACK TO SAVEPOINT build_branch_cache`.exec(db);\n\t\t\tsql`RELEASE SAVEPOINT build_branch_cache`.exec(db);\n\t\t} catch {\n\t\t\t// Preserve the original build failure.\n\t\t}\n\t\tif (error instanceof SessionError) throw error;\n\t\tthrow new SessionError(\n\t\t\t\"storage\",\n\t\t\t`Failed to build SQLite branch cache at entry ${leafId}`,\n\t\t\terror instanceof Error ? error : undefined,\n\t\t);\n\t}\n}\n\nfunction extendBranch(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\tbranchId: string,\n\tparentId: string,\n\tentryId: string,\n\tentrySeq: number,\n\tentryType: string,\n\tcustomType: string | null,\n) {\n\tinsertBranchEntry(db, sessionId, branchId, entryId, entrySeq, entryType, customType);\n\tif (!updateBranchTip(db, sessionId, branchId, parentId, entryId)) {\n\t\tthrow new SessionError(\"invalid_entry\", `Branch tip ${parentId} changed during append`);\n\t}\n}\n\nexport function appendEntryToBranchCache(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\tentryId: string,\n\tentrySeq: number,\n\tentryType: string,\n\tcustomType: string | null,\n\tparentId: string | null,\n) {\n\tif (parentId === null) {\n\t\tconst branchId = uuidv7();\n\t\tinsertBranchEntry(db, sessionId, branchId, entryId, entrySeq, entryType, customType);\n\t\tinsertBranchTip(db, sessionId, entryId, branchId);\n\t\treturn;\n\t}\n\n\tconst tipBranchId = readBranchTipBranchId(db, sessionId, parentId);\n\tif (tipBranchId !== undefined) {\n\t\textendBranch(db, sessionId, tipBranchId, parentId, entryId, entrySeq, entryType, customType);\n\t\treturn;\n\t}\n\n\tconst source = readBranchContainingEntry(db, sessionId, parentId);\n\tif (!source) {\n\t\tthrow new SessionError(\"invalid_entry\", `Branch cache has no branch containing parent entry ${parentId}`);\n\t}\n\n\tconst branchId = uuidv7();\n\tcopyBranchEntriesThroughSeq(db, sessionId, branchId, source.branchId, source.entrySeq);\n\tinsertBranchEntry(db, sessionId, branchId, entryId, entrySeq, entryType, customType);\n\tinsertBranchTip(db, sessionId, entryId, branchId);\n}\n"]}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { SessionSearch, SessionSearchHit } from "@earendil-works/pi-agent-core";
|
|
2
|
-
import type { SqliteDatabaseFactory, SqliteSessionMetadata, SqliteSessionRepositoryEnv } from "./types.ts";
|
|
3
|
-
export interface SqliteSessionSearchOptions {
|
|
4
|
-
env: Pick<SqliteSessionRepositoryEnv, "absolutePath" | "createDir">;
|
|
5
|
-
sqlite: SqliteDatabaseFactory;
|
|
6
|
-
databasePath: string;
|
|
7
|
-
}
|
|
8
|
-
export interface SqliteSessionSearchHit extends SessionSearchHit {
|
|
9
|
-
readonly metadata: SqliteSessionMetadata;
|
|
10
|
-
readonly timestamp: number;
|
|
11
|
-
readonly score: number;
|
|
12
|
-
}
|
|
13
|
-
export declare function createSqliteSessionSearch(options: SqliteSessionSearchOptions): SessionSearch<SqliteSessionSearchHit>;
|
|
14
|
-
//# sourceMappingURL=search-backend.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"search-backend.d.ts","sourceRoot":"","sources":["../../src/sqlite/search-backend.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGX,aAAa,EACb,gBAAgB,EAEhB,MAAM,+BAA+B,CAAC;AAKvC,OAAO,KAAK,EAEX,qBAAqB,EACrB,qBAAqB,EACrB,0BAA0B,EAC1B,MAAM,YAAY,CAAC;AAgCpB,MAAM,WAAW,0BAA0B;IAC1C,GAAG,EAAE,IAAI,CAAC,0BAA0B,EAAE,cAAc,GAAG,WAAW,CAAC,CAAC;IACpE,MAAM,EAAE,qBAAqB,CAAC;IAC9B,YAAY,EAAE,MAAM,CAAC;CACrB;AAsCD,MAAM,WAAW,sBAAuB,SAAQ,gBAAgB;IAC/D,QAAQ,CAAC,QAAQ,EAAE,qBAAqB,CAAC;IACzC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB;AAiGD,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,0BAA0B,GAAG,aAAa,CAAC,sBAAsB,CAAC,CAEpH","sourcesContent":["import type {\n\tFileError,\n\tResult,\n\tSessionSearch,\n\tSessionSearchHit,\n\tSessionSearchOptions,\n} from \"@earendil-works/pi-agent-core\";\nimport { SessionError } from \"@earendil-works/pi-agent-core\";\nimport { applyMigrations } from \"./migrations.ts\";\nimport { sql } from \"./sql.ts\";\nimport { decodeSessionMetadata, type SessionRow } from \"./storage/sessions.ts\";\nimport type {\n\tSqliteDatabase,\n\tSqliteDatabaseFactory,\n\tSqliteSessionMetadata,\n\tSqliteSessionRepositoryEnv,\n} from \"./types.ts\";\n\nfunction getFileSystemResultOrThrow<TValue>(result: Result<TValue, FileError>, message: string): TValue {\n\tif (!result.ok) {\n\t\tconst code = result.error.code === \"not_found\" ? \"not_found\" : \"storage\";\n\t\tthrow new SessionError(code, `${message}: ${result.error.message}`, result.error);\n\t}\n\treturn result.value;\n}\n\nfunction getParentPath(path: string): string {\n\tconst normalized = path.replace(/[\\\\/]+$/, \"\");\n\tconst lastSlash = Math.max(normalized.lastIndexOf(\"/\"), normalized.lastIndexOf(\"\\\\\"));\n\tif (lastSlash < 0) return \".\";\n\tif (lastSlash === 0) return normalized.slice(0, 1);\n\treturn normalized.slice(0, lastSlash);\n}\n\nfunction throwIfAborted(signal: AbortSignal | undefined): void {\n\tif (!signal?.aborted) return;\n\tif (signal.reason instanceof Error) throw signal.reason;\n\tconst error = new Error(\"The operation was aborted\");\n\terror.name = \"AbortError\";\n\tthrow error;\n}\n\nfunction configureSqliteDatabase(db: SqliteDatabase): void {\n\tsql`PRAGMA journal_mode=WAL`.exec(db);\n\tsql`PRAGMA synchronous=FULL`.exec(db);\n\tsql`PRAGMA busy_timeout=5000`.exec(db);\n}\n\nexport interface SqliteSessionSearchOptions {\n\tenv: Pick<SqliteSessionRepositoryEnv, \"absolutePath\" | \"createDir\">;\n\tsqlite: SqliteDatabaseFactory;\n\tdatabasePath: string;\n}\n\nfunction tableExists(db: SqliteDatabase, name: string): boolean {\n\treturn !!sql`SELECT 1 AS found FROM sqlite_master WHERE type = 'table' AND name = ${name} LIMIT 1`.get<{\n\t\tfound: number;\n\t}>(db);\n}\n\nfunction rebuildSearchIndex(db: SqliteDatabase): void {\n\tsql`INSERT INTO session_search_fts(session_search_fts) VALUES('rebuild')`.run(db);\n}\n\nfunction ensureSearchSchema(db: SqliteDatabase): void {\n\tconst ftsExists = tableExists(db, \"session_search_fts\");\n\tconst entriesExist = tableExists(db, \"entries\");\n\tdb.transaction(() => {\n\t\tsql`\nCREATE VIRTUAL TABLE IF NOT EXISTS session_search_fts USING fts5(\n payload,\n content = 'entries',\n content_rowid = 'rowid',\n tokenize = 'trigram remove_diacritics 1'\n);\nCREATE TRIGGER IF NOT EXISTS session_search_fts_ai AFTER INSERT ON entries BEGIN\n INSERT INTO session_search_fts(rowid, payload) VALUES (new.rowid, new.payload);\nEND;\nCREATE TRIGGER IF NOT EXISTS session_search_fts_ad AFTER DELETE ON entries BEGIN\n INSERT INTO session_search_fts(session_search_fts, rowid, payload) VALUES('delete', old.rowid, old.payload);\nEND;\nCREATE TRIGGER IF NOT EXISTS session_search_fts_au AFTER UPDATE OF payload ON entries BEGIN\n INSERT INTO session_search_fts(session_search_fts, rowid, payload) VALUES('delete', old.rowid, old.payload);\n INSERT INTO session_search_fts(rowid, payload) VALUES (new.rowid, new.payload);\nEND;\n`.exec(db);\n\t\tif (!ftsExists && entriesExist) rebuildSearchIndex(db);\n\t});\n}\n\nexport interface SqliteSessionSearchHit extends SessionSearchHit {\n\treadonly metadata: SqliteSessionMetadata;\n\treadonly timestamp: number;\n\treadonly score: number;\n}\n\n/** SQLite FTS search over a co-located canonical session database. */\nclass SqliteSessionSearch implements SessionSearch<SqliteSessionSearchHit> {\n\tprivate readonly options: SqliteSessionSearchOptions;\n\tprivate databasePath: string | undefined;\n\n\tconstructor(options: SqliteSessionSearchOptions) {\n\t\tthis.options = options;\n\t}\n\n\tprivate async getDatabasePath(): Promise<string> {\n\t\tif (!this.databasePath) {\n\t\t\tthis.databasePath = getFileSystemResultOrThrow(\n\t\t\t\tawait this.options.env.absolutePath(this.options.databasePath),\n\t\t\t\t`Failed to resolve SQLite search database ${this.options.databasePath}`,\n\t\t\t);\n\t\t}\n\t\treturn this.databasePath;\n\t}\n\n\tprivate async openDatabase(): Promise<SqliteDatabase> {\n\t\tconst path = await this.getDatabasePath();\n\t\tconst directory = getParentPath(path);\n\t\tgetFileSystemResultOrThrow(\n\t\t\tawait this.options.env.createDir(directory, { recursive: true }),\n\t\t\t`Failed to create SQLite search directory ${directory}`,\n\t\t);\n\t\tconst db = await this.options.sqlite.open(path);\n\t\ttry {\n\t\t\tconfigureSqliteDatabase(db);\n\t\t\tawait applyMigrations(db);\n\t\t\tensureSearchSchema(db);\n\t\t\treturn db;\n\t\t} catch (error) {\n\t\t\tdb.close();\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tasync *search(text: string, options: SessionSearchOptions = {}): AsyncIterable<SqliteSessionSearchHit> {\n\t\tconst queryText = text.trim();\n\t\tif (!queryText || (options.limit !== undefined && options.limit <= 0)) return;\n\t\tif (options.entryTypes?.length === 0) return;\n\t\tthrowIfAborted(options.signal);\n\t\tconst db = await this.openDatabase();\n\t\ttry {\n\t\t\tconst query = `\"${queryText.replaceAll('\"', '\"\"')}\"`;\n\t\t\tconst predicates = [\"session_search_fts MATCH ?\"];\n\t\t\tconst params: unknown[] = [query];\n\t\t\tif (options.entryTypes !== undefined) {\n\t\t\t\tpredicates.push(`se.type IN (${options.entryTypes.map(() => \"?\").join(\", \")})`);\n\t\t\t\tparams.push(...options.entryTypes);\n\t\t\t}\n\t\t\tconst rows = db\n\t\t\t\t.prepare(\n\t\t\t\t\t`SELECT s.id, s.created_at, s.metadata, s.cwd, s.parent_session_id,\n\t\t\t\t\t\tname_fact.seq IS NOT NULL AS has_session_name,\n\t\t\t\t\t\tname_fact.value AS session_name,\n\t\t\t\t\t\tse.id AS entry_id, se.timestamp, bm25(session_search_fts) AS score\n\t\t\t\t\tFROM session_search_fts\n\t\t\t\t\tJOIN entries AS se ON se.rowid = session_search_fts.rowid\n\t\t\t\t\tJOIN sessions AS s ON s.id = se.session_id\n\t\t\t\t\tLEFT JOIN facts AS name_fact\n\t\t\t\t\t\tON name_fact.session_id = s.id\n\t\t\t\t\t\tAND name_fact.kind = 'name'\n\t\t\t\t\t\tAND name_fact.key IS NULL\n\t\t\t\t\t\tAND name_fact.seq = (\n\t\t\t\t\t\t\tSELECT MAX(f.seq)\n\t\t\t\t\t\t\tFROM facts AS f\n\t\t\t\t\t\t\tWHERE f.session_id = s.id AND f.kind = 'name' AND f.key IS NULL\n\t\t\t\t\t\t)\n\t\t\t\t\tWHERE ${predicates.join(\" AND \")}\n\t\t\t\t\tORDER BY score\n\t\t\t\t\tLIMIT ?`,\n\t\t\t\t)\n\t\t\t\t.iterate<SessionRow & { entry_id: string; timestamp: number; score: number }>(\n\t\t\t\t\t...params,\n\t\t\t\t\toptions.limit ?? -1,\n\t\t\t\t);\n\t\t\tconst path = await this.getDatabasePath();\n\t\t\tfor (const row of rows) {\n\t\t\t\tthrowIfAborted(options.signal);\n\t\t\t\tyield {\n\t\t\t\t\tsessionId: row.id,\n\t\t\t\t\tmetadata: decodeSessionMetadata(row, path),\n\t\t\t\t\tentryId: row.entry_id,\n\t\t\t\t\ttimestamp: row.timestamp,\n\t\t\t\t\tscore: row.score,\n\t\t\t\t};\n\t\t\t}\n\t\t} finally {\n\t\t\tdb.close();\n\t\t}\n\t}\n}\n\nexport function createSqliteSessionSearch(options: SqliteSessionSearchOptions): SessionSearch<SqliteSessionSearchHit> {\n\treturn new SqliteSessionSearch(options);\n}\n"]}
|
|
@@ -1,153 +0,0 @@
|
|
|
1
|
-
import { SessionError } from "@earendil-works/pi-agent-core";
|
|
2
|
-
import { applyMigrations } from "./migrations.js";
|
|
3
|
-
import { sql } from "./sql.js";
|
|
4
|
-
import { decodeSessionMetadata } from "./storage/sessions.js";
|
|
5
|
-
function getFileSystemResultOrThrow(result, message) {
|
|
6
|
-
if (!result.ok) {
|
|
7
|
-
const code = result.error.code === "not_found" ? "not_found" : "storage";
|
|
8
|
-
throw new SessionError(code, `${message}: ${result.error.message}`, result.error);
|
|
9
|
-
}
|
|
10
|
-
return result.value;
|
|
11
|
-
}
|
|
12
|
-
function getParentPath(path) {
|
|
13
|
-
const normalized = path.replace(/[\\/]+$/, "");
|
|
14
|
-
const lastSlash = Math.max(normalized.lastIndexOf("/"), normalized.lastIndexOf("\\"));
|
|
15
|
-
if (lastSlash < 0)
|
|
16
|
-
return ".";
|
|
17
|
-
if (lastSlash === 0)
|
|
18
|
-
return normalized.slice(0, 1);
|
|
19
|
-
return normalized.slice(0, lastSlash);
|
|
20
|
-
}
|
|
21
|
-
function throwIfAborted(signal) {
|
|
22
|
-
if (!signal?.aborted)
|
|
23
|
-
return;
|
|
24
|
-
if (signal.reason instanceof Error)
|
|
25
|
-
throw signal.reason;
|
|
26
|
-
const error = new Error("The operation was aborted");
|
|
27
|
-
error.name = "AbortError";
|
|
28
|
-
throw error;
|
|
29
|
-
}
|
|
30
|
-
function configureSqliteDatabase(db) {
|
|
31
|
-
sql `PRAGMA journal_mode=WAL`.exec(db);
|
|
32
|
-
sql `PRAGMA synchronous=FULL`.exec(db);
|
|
33
|
-
sql `PRAGMA busy_timeout=5000`.exec(db);
|
|
34
|
-
}
|
|
35
|
-
function tableExists(db, name) {
|
|
36
|
-
return !!sql `SELECT 1 AS found FROM sqlite_master WHERE type = 'table' AND name = ${name} LIMIT 1`.get(db);
|
|
37
|
-
}
|
|
38
|
-
function rebuildSearchIndex(db) {
|
|
39
|
-
sql `INSERT INTO session_search_fts(session_search_fts) VALUES('rebuild')`.run(db);
|
|
40
|
-
}
|
|
41
|
-
function ensureSearchSchema(db) {
|
|
42
|
-
const ftsExists = tableExists(db, "session_search_fts");
|
|
43
|
-
const entriesExist = tableExists(db, "entries");
|
|
44
|
-
db.transaction(() => {
|
|
45
|
-
sql `
|
|
46
|
-
CREATE VIRTUAL TABLE IF NOT EXISTS session_search_fts USING fts5(
|
|
47
|
-
payload,
|
|
48
|
-
content = 'entries',
|
|
49
|
-
content_rowid = 'rowid',
|
|
50
|
-
tokenize = 'trigram remove_diacritics 1'
|
|
51
|
-
);
|
|
52
|
-
CREATE TRIGGER IF NOT EXISTS session_search_fts_ai AFTER INSERT ON entries BEGIN
|
|
53
|
-
INSERT INTO session_search_fts(rowid, payload) VALUES (new.rowid, new.payload);
|
|
54
|
-
END;
|
|
55
|
-
CREATE TRIGGER IF NOT EXISTS session_search_fts_ad AFTER DELETE ON entries BEGIN
|
|
56
|
-
INSERT INTO session_search_fts(session_search_fts, rowid, payload) VALUES('delete', old.rowid, old.payload);
|
|
57
|
-
END;
|
|
58
|
-
CREATE TRIGGER IF NOT EXISTS session_search_fts_au AFTER UPDATE OF payload ON entries BEGIN
|
|
59
|
-
INSERT INTO session_search_fts(session_search_fts, rowid, payload) VALUES('delete', old.rowid, old.payload);
|
|
60
|
-
INSERT INTO session_search_fts(rowid, payload) VALUES (new.rowid, new.payload);
|
|
61
|
-
END;
|
|
62
|
-
`.exec(db);
|
|
63
|
-
if (!ftsExists && entriesExist)
|
|
64
|
-
rebuildSearchIndex(db);
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
/** SQLite FTS search over a co-located canonical session database. */
|
|
68
|
-
class SqliteSessionSearch {
|
|
69
|
-
options;
|
|
70
|
-
databasePath;
|
|
71
|
-
constructor(options) {
|
|
72
|
-
this.options = options;
|
|
73
|
-
}
|
|
74
|
-
async getDatabasePath() {
|
|
75
|
-
if (!this.databasePath) {
|
|
76
|
-
this.databasePath = getFileSystemResultOrThrow(await this.options.env.absolutePath(this.options.databasePath), `Failed to resolve SQLite search database ${this.options.databasePath}`);
|
|
77
|
-
}
|
|
78
|
-
return this.databasePath;
|
|
79
|
-
}
|
|
80
|
-
async openDatabase() {
|
|
81
|
-
const path = await this.getDatabasePath();
|
|
82
|
-
const directory = getParentPath(path);
|
|
83
|
-
getFileSystemResultOrThrow(await this.options.env.createDir(directory, { recursive: true }), `Failed to create SQLite search directory ${directory}`);
|
|
84
|
-
const db = await this.options.sqlite.open(path);
|
|
85
|
-
try {
|
|
86
|
-
configureSqliteDatabase(db);
|
|
87
|
-
await applyMigrations(db);
|
|
88
|
-
ensureSearchSchema(db);
|
|
89
|
-
return db;
|
|
90
|
-
}
|
|
91
|
-
catch (error) {
|
|
92
|
-
db.close();
|
|
93
|
-
throw error;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
async *search(text, options = {}) {
|
|
97
|
-
const queryText = text.trim();
|
|
98
|
-
if (!queryText || (options.limit !== undefined && options.limit <= 0))
|
|
99
|
-
return;
|
|
100
|
-
if (options.entryTypes?.length === 0)
|
|
101
|
-
return;
|
|
102
|
-
throwIfAborted(options.signal);
|
|
103
|
-
const db = await this.openDatabase();
|
|
104
|
-
try {
|
|
105
|
-
const query = `"${queryText.replaceAll('"', '""')}"`;
|
|
106
|
-
const predicates = ["session_search_fts MATCH ?"];
|
|
107
|
-
const params = [query];
|
|
108
|
-
if (options.entryTypes !== undefined) {
|
|
109
|
-
predicates.push(`se.type IN (${options.entryTypes.map(() => "?").join(", ")})`);
|
|
110
|
-
params.push(...options.entryTypes);
|
|
111
|
-
}
|
|
112
|
-
const rows = db
|
|
113
|
-
.prepare(`SELECT s.id, s.created_at, s.metadata, s.cwd, s.parent_session_id,
|
|
114
|
-
name_fact.seq IS NOT NULL AS has_session_name,
|
|
115
|
-
name_fact.value AS session_name,
|
|
116
|
-
se.id AS entry_id, se.timestamp, bm25(session_search_fts) AS score
|
|
117
|
-
FROM session_search_fts
|
|
118
|
-
JOIN entries AS se ON se.rowid = session_search_fts.rowid
|
|
119
|
-
JOIN sessions AS s ON s.id = se.session_id
|
|
120
|
-
LEFT JOIN facts AS name_fact
|
|
121
|
-
ON name_fact.session_id = s.id
|
|
122
|
-
AND name_fact.kind = 'name'
|
|
123
|
-
AND name_fact.key IS NULL
|
|
124
|
-
AND name_fact.seq = (
|
|
125
|
-
SELECT MAX(f.seq)
|
|
126
|
-
FROM facts AS f
|
|
127
|
-
WHERE f.session_id = s.id AND f.kind = 'name' AND f.key IS NULL
|
|
128
|
-
)
|
|
129
|
-
WHERE ${predicates.join(" AND ")}
|
|
130
|
-
ORDER BY score
|
|
131
|
-
LIMIT ?`)
|
|
132
|
-
.iterate(...params, options.limit ?? -1);
|
|
133
|
-
const path = await this.getDatabasePath();
|
|
134
|
-
for (const row of rows) {
|
|
135
|
-
throwIfAborted(options.signal);
|
|
136
|
-
yield {
|
|
137
|
-
sessionId: row.id,
|
|
138
|
-
metadata: decodeSessionMetadata(row, path),
|
|
139
|
-
entryId: row.entry_id,
|
|
140
|
-
timestamp: row.timestamp,
|
|
141
|
-
score: row.score,
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
finally {
|
|
146
|
-
db.close();
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
export function createSqliteSessionSearch(options) {
|
|
151
|
-
return new SqliteSessionSearch(options);
|
|
152
|
-
}
|
|
153
|
-
//# sourceMappingURL=search-backend.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"search-backend.js","sourceRoot":"","sources":["../../src/sqlite/search-backend.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n\tFileError,\n\tResult,\n\tSessionSearch,\n\tSessionSearchHit,\n\tSessionSearchOptions,\n} from \"@earendil-works/pi-agent-core\";\nimport { SessionError } from \"@earendil-works/pi-agent-core\";\nimport { applyMigrations } from \"./migrations.ts\";\nimport { sql } from \"./sql.ts\";\nimport { decodeSessionMetadata, type SessionRow } from \"./storage/sessions.ts\";\nimport type {\n\tSqliteDatabase,\n\tSqliteDatabaseFactory,\n\tSqliteSessionMetadata,\n\tSqliteSessionRepositoryEnv,\n} from \"./types.ts\";\n\nfunction getFileSystemResultOrThrow<TValue>(result: Result<TValue, FileError>, message: string): TValue {\n\tif (!result.ok) {\n\t\tconst code = result.error.code === \"not_found\" ? \"not_found\" : \"storage\";\n\t\tthrow new SessionError(code, `${message}: ${result.error.message}`, result.error);\n\t}\n\treturn result.value;\n}\n\nfunction getParentPath(path: string): string {\n\tconst normalized = path.replace(/[\\\\/]+$/, \"\");\n\tconst lastSlash = Math.max(normalized.lastIndexOf(\"/\"), normalized.lastIndexOf(\"\\\\\"));\n\tif (lastSlash < 0) return \".\";\n\tif (lastSlash === 0) return normalized.slice(0, 1);\n\treturn normalized.slice(0, lastSlash);\n}\n\nfunction throwIfAborted(signal: AbortSignal | undefined): void {\n\tif (!signal?.aborted) return;\n\tif (signal.reason instanceof Error) throw signal.reason;\n\tconst error = new Error(\"The operation was aborted\");\n\terror.name = \"AbortError\";\n\tthrow error;\n}\n\nfunction configureSqliteDatabase(db: SqliteDatabase): void {\n\tsql`PRAGMA journal_mode=WAL`.exec(db);\n\tsql`PRAGMA synchronous=FULL`.exec(db);\n\tsql`PRAGMA busy_timeout=5000`.exec(db);\n}\n\nexport interface SqliteSessionSearchOptions {\n\tenv: Pick<SqliteSessionRepositoryEnv, \"absolutePath\" | \"createDir\">;\n\tsqlite: SqliteDatabaseFactory;\n\tdatabasePath: string;\n}\n\nfunction tableExists(db: SqliteDatabase, name: string): boolean {\n\treturn !!sql`SELECT 1 AS found FROM sqlite_master WHERE type = 'table' AND name = ${name} LIMIT 1`.get<{\n\t\tfound: number;\n\t}>(db);\n}\n\nfunction rebuildSearchIndex(db: SqliteDatabase): void {\n\tsql`INSERT INTO session_search_fts(session_search_fts) VALUES('rebuild')`.run(db);\n}\n\nfunction ensureSearchSchema(db: SqliteDatabase): void {\n\tconst ftsExists = tableExists(db, \"session_search_fts\");\n\tconst entriesExist = tableExists(db, \"entries\");\n\tdb.transaction(() => {\n\t\tsql`\nCREATE VIRTUAL TABLE IF NOT EXISTS session_search_fts USING fts5(\n payload,\n content = 'entries',\n content_rowid = 'rowid',\n tokenize = 'trigram remove_diacritics 1'\n);\nCREATE TRIGGER IF NOT EXISTS session_search_fts_ai AFTER INSERT ON entries BEGIN\n INSERT INTO session_search_fts(rowid, payload) VALUES (new.rowid, new.payload);\nEND;\nCREATE TRIGGER IF NOT EXISTS session_search_fts_ad AFTER DELETE ON entries BEGIN\n INSERT INTO session_search_fts(session_search_fts, rowid, payload) VALUES('delete', old.rowid, old.payload);\nEND;\nCREATE TRIGGER IF NOT EXISTS session_search_fts_au AFTER UPDATE OF payload ON entries BEGIN\n INSERT INTO session_search_fts(session_search_fts, rowid, payload) VALUES('delete', old.rowid, old.payload);\n INSERT INTO session_search_fts(rowid, payload) VALUES (new.rowid, new.payload);\nEND;\n`.exec(db);\n\t\tif (!ftsExists && entriesExist) rebuildSearchIndex(db);\n\t});\n}\n\nexport interface SqliteSessionSearchHit extends SessionSearchHit {\n\treadonly metadata: SqliteSessionMetadata;\n\treadonly timestamp: number;\n\treadonly score: number;\n}\n\n/** SQLite FTS search over a co-located canonical session database. */\nclass SqliteSessionSearch implements SessionSearch<SqliteSessionSearchHit> {\n\tprivate readonly options: SqliteSessionSearchOptions;\n\tprivate databasePath: string | undefined;\n\n\tconstructor(options: SqliteSessionSearchOptions) {\n\t\tthis.options = options;\n\t}\n\n\tprivate async getDatabasePath(): Promise<string> {\n\t\tif (!this.databasePath) {\n\t\t\tthis.databasePath = getFileSystemResultOrThrow(\n\t\t\t\tawait this.options.env.absolutePath(this.options.databasePath),\n\t\t\t\t`Failed to resolve SQLite search database ${this.options.databasePath}`,\n\t\t\t);\n\t\t}\n\t\treturn this.databasePath;\n\t}\n\n\tprivate async openDatabase(): Promise<SqliteDatabase> {\n\t\tconst path = await this.getDatabasePath();\n\t\tconst directory = getParentPath(path);\n\t\tgetFileSystemResultOrThrow(\n\t\t\tawait this.options.env.createDir(directory, { recursive: true }),\n\t\t\t`Failed to create SQLite search directory ${directory}`,\n\t\t);\n\t\tconst db = await this.options.sqlite.open(path);\n\t\ttry {\n\t\t\tconfigureSqliteDatabase(db);\n\t\t\tawait applyMigrations(db);\n\t\t\tensureSearchSchema(db);\n\t\t\treturn db;\n\t\t} catch (error) {\n\t\t\tdb.close();\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tasync *search(text: string, options: SessionSearchOptions = {}): AsyncIterable<SqliteSessionSearchHit> {\n\t\tconst queryText = text.trim();\n\t\tif (!queryText || (options.limit !== undefined && options.limit <= 0)) return;\n\t\tif (options.entryTypes?.length === 0) return;\n\t\tthrowIfAborted(options.signal);\n\t\tconst db = await this.openDatabase();\n\t\ttry {\n\t\t\tconst query = `\"${queryText.replaceAll('\"', '\"\"')}\"`;\n\t\t\tconst predicates = [\"session_search_fts MATCH ?\"];\n\t\t\tconst params: unknown[] = [query];\n\t\t\tif (options.entryTypes !== undefined) {\n\t\t\t\tpredicates.push(`se.type IN (${options.entryTypes.map(() => \"?\").join(\", \")})`);\n\t\t\t\tparams.push(...options.entryTypes);\n\t\t\t}\n\t\t\tconst rows = db\n\t\t\t\t.prepare(\n\t\t\t\t\t`SELECT s.id, s.created_at, s.metadata, s.cwd, s.parent_session_id,\n\t\t\t\t\t\tname_fact.seq IS NOT NULL AS has_session_name,\n\t\t\t\t\t\tname_fact.value AS session_name,\n\t\t\t\t\t\tse.id AS entry_id, se.timestamp, bm25(session_search_fts) AS score\n\t\t\t\t\tFROM session_search_fts\n\t\t\t\t\tJOIN entries AS se ON se.rowid = session_search_fts.rowid\n\t\t\t\t\tJOIN sessions AS s ON s.id = se.session_id\n\t\t\t\t\tLEFT JOIN facts AS name_fact\n\t\t\t\t\t\tON name_fact.session_id = s.id\n\t\t\t\t\t\tAND name_fact.kind = 'name'\n\t\t\t\t\t\tAND name_fact.key IS NULL\n\t\t\t\t\t\tAND name_fact.seq = (\n\t\t\t\t\t\t\tSELECT MAX(f.seq)\n\t\t\t\t\t\t\tFROM facts AS f\n\t\t\t\t\t\t\tWHERE f.session_id = s.id AND f.kind = 'name' AND f.key IS NULL\n\t\t\t\t\t\t)\n\t\t\t\t\tWHERE ${predicates.join(\" AND \")}\n\t\t\t\t\tORDER BY score\n\t\t\t\t\tLIMIT ?`,\n\t\t\t\t)\n\t\t\t\t.iterate<SessionRow & { entry_id: string; timestamp: number; score: number }>(\n\t\t\t\t\t...params,\n\t\t\t\t\toptions.limit ?? -1,\n\t\t\t\t);\n\t\t\tconst path = await this.getDatabasePath();\n\t\t\tfor (const row of rows) {\n\t\t\t\tthrowIfAborted(options.signal);\n\t\t\t\tyield {\n\t\t\t\t\tsessionId: row.id,\n\t\t\t\t\tmetadata: decodeSessionMetadata(row, path),\n\t\t\t\t\tentryId: row.entry_id,\n\t\t\t\t\ttimestamp: row.timestamp,\n\t\t\t\t\tscore: row.score,\n\t\t\t\t};\n\t\t\t}\n\t\t} finally {\n\t\t\tdb.close();\n\t\t}\n\t}\n}\n\nexport function createSqliteSessionSearch(options: SqliteSessionSearchOptions): SessionSearch<SqliteSessionSearchHit> {\n\treturn new SqliteSessionSearch(options);\n}\n"]}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { type Entry } from "@earendil-works/pi-agent-core";
|
|
2
|
-
import type { SqliteDatabase } from "../types.ts";
|
|
3
|
-
/** Derived root-to-tip branch cache membership. Canonical parent links remain in entries. */
|
|
4
|
-
export interface CachedBranch {
|
|
5
|
-
branchId: string;
|
|
6
|
-
leafSeq: number;
|
|
7
|
-
}
|
|
8
|
-
export interface CachedBranchEntryRow {
|
|
9
|
-
session_id: string;
|
|
10
|
-
id: string;
|
|
11
|
-
entry_seq: number;
|
|
12
|
-
parent_id: string | null;
|
|
13
|
-
type: Entry["type"];
|
|
14
|
-
timestamp: number;
|
|
15
|
-
payload: string;
|
|
16
|
-
}
|
|
17
|
-
export interface CachedBranchQuery {
|
|
18
|
-
type?: Entry["type"];
|
|
19
|
-
customType?: string;
|
|
20
|
-
stopAtType?: Entry["type"];
|
|
21
|
-
stopAtId?: string;
|
|
22
|
-
cursor?: {
|
|
23
|
-
afterSeq: number;
|
|
24
|
-
};
|
|
25
|
-
order?: "newestFirst" | "oldestFirst";
|
|
26
|
-
limit?: number;
|
|
27
|
-
}
|
|
28
|
-
export declare function readCachedBranch(db: SqliteDatabase, sessionId: string, leafId: string): {
|
|
29
|
-
branchId: string;
|
|
30
|
-
leafSeq: number;
|
|
31
|
-
} | undefined;
|
|
32
|
-
export declare function queryCachedBranchRows(db: SqliteDatabase, sessionId: string, branch: CachedBranch, query: CachedBranchQuery): CachedBranchEntryRow[];
|
|
33
|
-
export declare function deleteBranchEntries(db: SqliteDatabase, sessionId: string): void;
|
|
34
|
-
export declare function insertBranchEntry(db: SqliteDatabase, sessionId: string, branchId: string, entryId: string, entrySeq: number, entryType: string, customType: string | null): void;
|
|
35
|
-
export declare function insertBranchEntriesForPath(db: SqliteDatabase, sessionId: string, branchId: string, leafId: string): void;
|
|
36
|
-
export declare function readBranchContainingEntry(db: SqliteDatabase, sessionId: string, entryId: string): {
|
|
37
|
-
branchId: string;
|
|
38
|
-
entrySeq: number;
|
|
39
|
-
} | undefined;
|
|
40
|
-
export declare function copyBranchEntriesThroughSeq(db: SqliteDatabase, sessionId: string, targetBranchId: string, sourceBranchId: string, throughSeq: number): void;
|
|
41
|
-
//# sourceMappingURL=branch-entries.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"branch-entries.d.ts","sourceRoot":"","sources":["../../../src/sqlite/storage/branch-entries.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,KAAK,EAAgB,MAAM,+BAA+B,CAAC;AAEzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,6FAA6F;AAC7F,MAAM,WAAW,YAAY;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,oBAAoB;IACpC,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IACjC,IAAI,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9B,KAAK,CAAC,EAAE,aAAa,GAAG,aAAa,CAAC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAUD,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;;;cAQrF;AAED,wBAAgB,qBAAqB,CACpC,EAAE,EAAE,cAAc,EAClB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,iBAAiB,0BAyCxB;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,QAExE;AAED,wBAAgB,iBAAiB,CAChC,EAAE,EAAE,cAAc,EAClB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GAAG,IAAI,QAKzB;AAqBD,wBAAgB,0BAA0B,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAmBjH;AAED,wBAAgB,yBAAyB,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;;;cAO/F;AAED,wBAAgB,2BAA2B,CAC1C,EAAE,EAAE,cAAc,EAClB,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,EACtB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,QAMlB","sourcesContent":["import { type Entry, SessionError } from \"@earendil-works/pi-agent-core\";\nimport { joinSqlFragments, sql } from \"../sql.ts\";\nimport type { SqliteDatabase } from \"../types.ts\";\n\n/** Derived root-to-tip branch cache membership. Canonical parent links remain in entries. */\nexport interface CachedBranch {\n\tbranchId: string;\n\tleafSeq: number;\n}\n\nexport interface CachedBranchEntryRow {\n\tsession_id: string;\n\tid: string;\n\tentry_seq: number;\n\tparent_id: string | null;\n\ttype: Entry[\"type\"];\n\ttimestamp: number;\n\tpayload: string;\n}\n\nexport interface CachedBranchQuery {\n\ttype?: Entry[\"type\"];\n\tcustomType?: string;\n\tstopAtType?: Entry[\"type\"];\n\tstopAtId?: string;\n\tcursor?: { afterSeq: number };\n\torder?: \"newestFirst\" | \"oldestFirst\";\n\tlimit?: number;\n}\n\ninterface BranchPathEntryRow {\n\tid: string;\n\tseq: number;\n\tparent_id: string | null;\n\ttype: Entry[\"type\"];\n\tpayload: string;\n}\n\nexport function readCachedBranch(db: SqliteDatabase, sessionId: string, leafId: string) {\n\tconst membership = sql`SELECT branch_id, entry_seq\n\t\tFROM branch_entries\n\t\tWHERE session_id = ${sessionId} AND entry_id = ${leafId}\n\t\tORDER BY branch_id\n\t\tLIMIT 1`.get<{ branch_id: string; entry_seq: number }>(db);\n\tif (!membership) return undefined;\n\treturn { branchId: membership.branch_id, leafSeq: membership.entry_seq };\n}\n\nexport function queryCachedBranchRows(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\tbranch: CachedBranch,\n\tquery: CachedBranchQuery,\n) {\n\tconst oldestFirst = query.order === \"oldestFirst\";\n\tconst stopPredicates: ReturnType<typeof sql>[] = [];\n\tif (query.stopAtType !== undefined) stopPredicates.push(sql`stop.entry_type = ${query.stopAtType}`);\n\tif (query.stopAtId !== undefined) stopPredicates.push(sql`stop.entry_id = ${query.stopAtId}`);\n\n\tconst aggregate = oldestFirst ? sql`MIN` : sql`MAX`;\n\tconst boundaryComparison = oldestFirst ? sql`<=` : sql`>=`;\n\tconst cursorComparison = oldestFirst ? sql`>` : sql`<`;\n\tconst direction = oldestFirst ? sql`ASC` : sql`DESC`;\n\tconst boundary =\n\t\tstopPredicates.length === 0\n\t\t\t? sql``\n\t\t\t: sql`SELECT ${aggregate}(stop.entry_seq)\n\t\t\t\tFROM branch_entries AS stop\n\t\t\t\tWHERE stop.session_id = ${sessionId}\n\t\t\t\t\tAND stop.branch_id = ${branch.branchId}\n\t\t\t\t\tAND stop.entry_seq <= ${branch.leafSeq}\n\t\t\t\t\tAND (${joinSqlFragments(stopPredicates, \" OR \")})`;\n\n\tconst predicates = [\n\t\tsql`b.session_id = ${sessionId}`,\n\t\tsql`b.branch_id = ${branch.branchId}`,\n\t\tsql`b.entry_seq <= ${branch.leafSeq}`,\n\t];\n\tif (stopPredicates.length > 0) {\n\t\tpredicates.push(\n\t\t\tsql`b.entry_seq ${boundaryComparison} COALESCE((${boundary}), ${oldestFirst ? branch.leafSeq : 0})`,\n\t\t);\n\t}\n\tif (query.cursor !== undefined) predicates.push(sql`b.entry_seq ${cursorComparison} ${query.cursor.afterSeq}`);\n\tif (query.type !== undefined) predicates.push(sql`b.entry_type = ${query.type}`);\n\tif (query.customType !== undefined) predicates.push(sql`b.custom_type = ${query.customType}`);\n\tconst limit = query.limit === undefined ? sql`` : sql` LIMIT ${query.limit}`;\n\n\treturn sql`SELECT e.session_id, e.id, e.seq AS entry_seq, e.parent_id, e.type, e.timestamp, e.payload\n\t\tFROM branch_entries AS b\n\t\tJOIN entries AS e ON e.session_id = b.session_id AND e.id = b.entry_id\n\t\tWHERE ${joinSqlFragments(predicates, \" AND \")}\n\t\tORDER BY b.entry_seq ${direction}${limit}`.all<CachedBranchEntryRow>(db);\n}\n\nexport function deleteBranchEntries(db: SqliteDatabase, sessionId: string) {\n\tsql`DELETE FROM branch_entries WHERE session_id = ${sessionId}`.run(db);\n}\n\nexport function insertBranchEntry(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\tbranchId: string,\n\tentryId: string,\n\tentrySeq: number,\n\tentryType: string,\n\tcustomType: string | null,\n) {\n\tsql`INSERT INTO branch_entries\n\t\t\t(session_id, branch_id, entry_id, entry_seq, entry_type, custom_type)\n\t\t\tVALUES (${sessionId}, ${branchId}, ${entryId}, ${entrySeq}, ${entryType}, ${customType})`.run(db);\n}\n\nfunction customTypeFromPayload(row: BranchPathEntryRow): string | null {\n\tif (row.type !== \"custom\") return null;\n\ttry {\n\t\tconst payload = JSON.parse(row.payload) as unknown;\n\t\tif (typeof payload !== \"object\" || payload === null || Array.isArray(payload)) {\n\t\t\tthrow new Error(\"Payload is not an object\");\n\t\t}\n\t\tconst customType = (payload as { customType?: unknown }).customType;\n\t\tif (typeof customType !== \"string\") throw new Error(\"Invalid custom payload\");\n\t\treturn customType;\n\t} catch (error) {\n\t\tthrow new SessionError(\n\t\t\t\"invalid_entry\",\n\t\t\t`Invalid SQLite session entry ${row.id}: failed to decode entry ${row.id}`,\n\t\t\terror instanceof Error ? error : undefined,\n\t\t);\n\t}\n}\n\nexport function insertBranchEntriesForPath(db: SqliteDatabase, sessionId: string, branchId: string, leafId: string) {\n\tconst path: BranchPathEntryRow[] = [];\n\tconst seen = new Set<string>();\n\tlet entryId: string | null = leafId;\n\n\twhile (entryId !== null) {\n\t\tif (seen.has(entryId)) throw new SessionError(\"invalid_entry\", `Entry parent cycle at ${entryId}`);\n\t\tseen.add(entryId);\n\t\tconst row: BranchPathEntryRow | undefined = sql`SELECT id, seq, parent_id, type, payload\n\t\t\tFROM entries\n\t\t\tWHERE session_id = ${sessionId} AND id = ${entryId}`.get<BranchPathEntryRow>(db);\n\t\tif (!row) throw new SessionError(\"invalid_entry\", `Entry ${entryId} not found`);\n\t\tpath.push(row);\n\t\tentryId = row.parent_id;\n\t}\n\n\tfor (const row of path.reverse()) {\n\t\tinsertBranchEntry(db, sessionId, branchId, row.id, row.seq, row.type, customTypeFromPayload(row));\n\t}\n}\n\nexport function readBranchContainingEntry(db: SqliteDatabase, sessionId: string, entryId: string) {\n\tconst row = sql`SELECT b.branch_id, b.entry_seq\n\t\tFROM branch_entries AS b\n\t\tWHERE b.session_id = ${sessionId} AND b.entry_id = ${entryId}\n\t\tORDER BY b.branch_id\n\t\tLIMIT 1`.get<{ branch_id: string; entry_seq: number }>(db);\n\treturn row === undefined ? undefined : { branchId: row.branch_id, entrySeq: row.entry_seq };\n}\n\nexport function copyBranchEntriesThroughSeq(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\ttargetBranchId: string,\n\tsourceBranchId: string,\n\tthroughSeq: number,\n) {\n\tsql`INSERT INTO branch_entries (session_id, branch_id, entry_id, entry_seq, entry_type, custom_type)\n\t\tSELECT session_id, ${targetBranchId}, entry_id, entry_seq, entry_type, custom_type\n\t\tFROM branch_entries\n\t\tWHERE session_id = ${sessionId} AND branch_id = ${sourceBranchId} AND entry_seq <= ${throughSeq}`.run(db);\n}\n"]}
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { SessionError } from "@earendil-works/pi-agent-core";
|
|
2
|
-
import { joinSqlFragments, sql } from "../sql.js";
|
|
3
|
-
export function readCachedBranch(db, sessionId, leafId) {
|
|
4
|
-
const membership = sql `SELECT branch_id, entry_seq
|
|
5
|
-
FROM branch_entries
|
|
6
|
-
WHERE session_id = ${sessionId} AND entry_id = ${leafId}
|
|
7
|
-
ORDER BY branch_id
|
|
8
|
-
LIMIT 1`.get(db);
|
|
9
|
-
if (!membership)
|
|
10
|
-
return undefined;
|
|
11
|
-
return { branchId: membership.branch_id, leafSeq: membership.entry_seq };
|
|
12
|
-
}
|
|
13
|
-
export function queryCachedBranchRows(db, sessionId, branch, query) {
|
|
14
|
-
const oldestFirst = query.order === "oldestFirst";
|
|
15
|
-
const stopPredicates = [];
|
|
16
|
-
if (query.stopAtType !== undefined)
|
|
17
|
-
stopPredicates.push(sql `stop.entry_type = ${query.stopAtType}`);
|
|
18
|
-
if (query.stopAtId !== undefined)
|
|
19
|
-
stopPredicates.push(sql `stop.entry_id = ${query.stopAtId}`);
|
|
20
|
-
const aggregate = oldestFirst ? sql `MIN` : sql `MAX`;
|
|
21
|
-
const boundaryComparison = oldestFirst ? sql `<=` : sql `>=`;
|
|
22
|
-
const cursorComparison = oldestFirst ? sql `>` : sql `<`;
|
|
23
|
-
const direction = oldestFirst ? sql `ASC` : sql `DESC`;
|
|
24
|
-
const boundary = stopPredicates.length === 0
|
|
25
|
-
? sql ``
|
|
26
|
-
: sql `SELECT ${aggregate}(stop.entry_seq)
|
|
27
|
-
FROM branch_entries AS stop
|
|
28
|
-
WHERE stop.session_id = ${sessionId}
|
|
29
|
-
AND stop.branch_id = ${branch.branchId}
|
|
30
|
-
AND stop.entry_seq <= ${branch.leafSeq}
|
|
31
|
-
AND (${joinSqlFragments(stopPredicates, " OR ")})`;
|
|
32
|
-
const predicates = [
|
|
33
|
-
sql `b.session_id = ${sessionId}`,
|
|
34
|
-
sql `b.branch_id = ${branch.branchId}`,
|
|
35
|
-
sql `b.entry_seq <= ${branch.leafSeq}`,
|
|
36
|
-
];
|
|
37
|
-
if (stopPredicates.length > 0) {
|
|
38
|
-
predicates.push(sql `b.entry_seq ${boundaryComparison} COALESCE((${boundary}), ${oldestFirst ? branch.leafSeq : 0})`);
|
|
39
|
-
}
|
|
40
|
-
if (query.cursor !== undefined)
|
|
41
|
-
predicates.push(sql `b.entry_seq ${cursorComparison} ${query.cursor.afterSeq}`);
|
|
42
|
-
if (query.type !== undefined)
|
|
43
|
-
predicates.push(sql `b.entry_type = ${query.type}`);
|
|
44
|
-
if (query.customType !== undefined)
|
|
45
|
-
predicates.push(sql `b.custom_type = ${query.customType}`);
|
|
46
|
-
const limit = query.limit === undefined ? sql `` : sql ` LIMIT ${query.limit}`;
|
|
47
|
-
return sql `SELECT e.session_id, e.id, e.seq AS entry_seq, e.parent_id, e.type, e.timestamp, e.payload
|
|
48
|
-
FROM branch_entries AS b
|
|
49
|
-
JOIN entries AS e ON e.session_id = b.session_id AND e.id = b.entry_id
|
|
50
|
-
WHERE ${joinSqlFragments(predicates, " AND ")}
|
|
51
|
-
ORDER BY b.entry_seq ${direction}${limit}`.all(db);
|
|
52
|
-
}
|
|
53
|
-
export function deleteBranchEntries(db, sessionId) {
|
|
54
|
-
sql `DELETE FROM branch_entries WHERE session_id = ${sessionId}`.run(db);
|
|
55
|
-
}
|
|
56
|
-
export function insertBranchEntry(db, sessionId, branchId, entryId, entrySeq, entryType, customType) {
|
|
57
|
-
sql `INSERT INTO branch_entries
|
|
58
|
-
(session_id, branch_id, entry_id, entry_seq, entry_type, custom_type)
|
|
59
|
-
VALUES (${sessionId}, ${branchId}, ${entryId}, ${entrySeq}, ${entryType}, ${customType})`.run(db);
|
|
60
|
-
}
|
|
61
|
-
function customTypeFromPayload(row) {
|
|
62
|
-
if (row.type !== "custom")
|
|
63
|
-
return null;
|
|
64
|
-
try {
|
|
65
|
-
const payload = JSON.parse(row.payload);
|
|
66
|
-
if (typeof payload !== "object" || payload === null || Array.isArray(payload)) {
|
|
67
|
-
throw new Error("Payload is not an object");
|
|
68
|
-
}
|
|
69
|
-
const customType = payload.customType;
|
|
70
|
-
if (typeof customType !== "string")
|
|
71
|
-
throw new Error("Invalid custom payload");
|
|
72
|
-
return customType;
|
|
73
|
-
}
|
|
74
|
-
catch (error) {
|
|
75
|
-
throw new SessionError("invalid_entry", `Invalid SQLite session entry ${row.id}: failed to decode entry ${row.id}`, error instanceof Error ? error : undefined);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
export function insertBranchEntriesForPath(db, sessionId, branchId, leafId) {
|
|
79
|
-
const path = [];
|
|
80
|
-
const seen = new Set();
|
|
81
|
-
let entryId = leafId;
|
|
82
|
-
while (entryId !== null) {
|
|
83
|
-
if (seen.has(entryId))
|
|
84
|
-
throw new SessionError("invalid_entry", `Entry parent cycle at ${entryId}`);
|
|
85
|
-
seen.add(entryId);
|
|
86
|
-
const row = sql `SELECT id, seq, parent_id, type, payload
|
|
87
|
-
FROM entries
|
|
88
|
-
WHERE session_id = ${sessionId} AND id = ${entryId}`.get(db);
|
|
89
|
-
if (!row)
|
|
90
|
-
throw new SessionError("invalid_entry", `Entry ${entryId} not found`);
|
|
91
|
-
path.push(row);
|
|
92
|
-
entryId = row.parent_id;
|
|
93
|
-
}
|
|
94
|
-
for (const row of path.reverse()) {
|
|
95
|
-
insertBranchEntry(db, sessionId, branchId, row.id, row.seq, row.type, customTypeFromPayload(row));
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
export function readBranchContainingEntry(db, sessionId, entryId) {
|
|
99
|
-
const row = sql `SELECT b.branch_id, b.entry_seq
|
|
100
|
-
FROM branch_entries AS b
|
|
101
|
-
WHERE b.session_id = ${sessionId} AND b.entry_id = ${entryId}
|
|
102
|
-
ORDER BY b.branch_id
|
|
103
|
-
LIMIT 1`.get(db);
|
|
104
|
-
return row === undefined ? undefined : { branchId: row.branch_id, entrySeq: row.entry_seq };
|
|
105
|
-
}
|
|
106
|
-
export function copyBranchEntriesThroughSeq(db, sessionId, targetBranchId, sourceBranchId, throughSeq) {
|
|
107
|
-
sql `INSERT INTO branch_entries (session_id, branch_id, entry_id, entry_seq, entry_type, custom_type)
|
|
108
|
-
SELECT session_id, ${targetBranchId}, entry_id, entry_seq, entry_type, custom_type
|
|
109
|
-
FROM branch_entries
|
|
110
|
-
WHERE session_id = ${sessionId} AND branch_id = ${sourceBranchId} AND entry_seq <= ${throughSeq}`.run(db);
|
|
111
|
-
}
|
|
112
|
-
//# sourceMappingURL=branch-entries.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"branch-entries.js","sourceRoot":"","sources":["../../../src/sqlite/storage/branch-entries.ts"],"names":[],"mappings":"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","sourcesContent":["import { type Entry, SessionError } from \"@earendil-works/pi-agent-core\";\nimport { joinSqlFragments, sql } from \"../sql.ts\";\nimport type { SqliteDatabase } from \"../types.ts\";\n\n/** Derived root-to-tip branch cache membership. Canonical parent links remain in entries. */\nexport interface CachedBranch {\n\tbranchId: string;\n\tleafSeq: number;\n}\n\nexport interface CachedBranchEntryRow {\n\tsession_id: string;\n\tid: string;\n\tentry_seq: number;\n\tparent_id: string | null;\n\ttype: Entry[\"type\"];\n\ttimestamp: number;\n\tpayload: string;\n}\n\nexport interface CachedBranchQuery {\n\ttype?: Entry[\"type\"];\n\tcustomType?: string;\n\tstopAtType?: Entry[\"type\"];\n\tstopAtId?: string;\n\tcursor?: { afterSeq: number };\n\torder?: \"newestFirst\" | \"oldestFirst\";\n\tlimit?: number;\n}\n\ninterface BranchPathEntryRow {\n\tid: string;\n\tseq: number;\n\tparent_id: string | null;\n\ttype: Entry[\"type\"];\n\tpayload: string;\n}\n\nexport function readCachedBranch(db: SqliteDatabase, sessionId: string, leafId: string) {\n\tconst membership = sql`SELECT branch_id, entry_seq\n\t\tFROM branch_entries\n\t\tWHERE session_id = ${sessionId} AND entry_id = ${leafId}\n\t\tORDER BY branch_id\n\t\tLIMIT 1`.get<{ branch_id: string; entry_seq: number }>(db);\n\tif (!membership) return undefined;\n\treturn { branchId: membership.branch_id, leafSeq: membership.entry_seq };\n}\n\nexport function queryCachedBranchRows(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\tbranch: CachedBranch,\n\tquery: CachedBranchQuery,\n) {\n\tconst oldestFirst = query.order === \"oldestFirst\";\n\tconst stopPredicates: ReturnType<typeof sql>[] = [];\n\tif (query.stopAtType !== undefined) stopPredicates.push(sql`stop.entry_type = ${query.stopAtType}`);\n\tif (query.stopAtId !== undefined) stopPredicates.push(sql`stop.entry_id = ${query.stopAtId}`);\n\n\tconst aggregate = oldestFirst ? sql`MIN` : sql`MAX`;\n\tconst boundaryComparison = oldestFirst ? sql`<=` : sql`>=`;\n\tconst cursorComparison = oldestFirst ? sql`>` : sql`<`;\n\tconst direction = oldestFirst ? sql`ASC` : sql`DESC`;\n\tconst boundary =\n\t\tstopPredicates.length === 0\n\t\t\t? sql``\n\t\t\t: sql`SELECT ${aggregate}(stop.entry_seq)\n\t\t\t\tFROM branch_entries AS stop\n\t\t\t\tWHERE stop.session_id = ${sessionId}\n\t\t\t\t\tAND stop.branch_id = ${branch.branchId}\n\t\t\t\t\tAND stop.entry_seq <= ${branch.leafSeq}\n\t\t\t\t\tAND (${joinSqlFragments(stopPredicates, \" OR \")})`;\n\n\tconst predicates = [\n\t\tsql`b.session_id = ${sessionId}`,\n\t\tsql`b.branch_id = ${branch.branchId}`,\n\t\tsql`b.entry_seq <= ${branch.leafSeq}`,\n\t];\n\tif (stopPredicates.length > 0) {\n\t\tpredicates.push(\n\t\t\tsql`b.entry_seq ${boundaryComparison} COALESCE((${boundary}), ${oldestFirst ? branch.leafSeq : 0})`,\n\t\t);\n\t}\n\tif (query.cursor !== undefined) predicates.push(sql`b.entry_seq ${cursorComparison} ${query.cursor.afterSeq}`);\n\tif (query.type !== undefined) predicates.push(sql`b.entry_type = ${query.type}`);\n\tif (query.customType !== undefined) predicates.push(sql`b.custom_type = ${query.customType}`);\n\tconst limit = query.limit === undefined ? sql`` : sql` LIMIT ${query.limit}`;\n\n\treturn sql`SELECT e.session_id, e.id, e.seq AS entry_seq, e.parent_id, e.type, e.timestamp, e.payload\n\t\tFROM branch_entries AS b\n\t\tJOIN entries AS e ON e.session_id = b.session_id AND e.id = b.entry_id\n\t\tWHERE ${joinSqlFragments(predicates, \" AND \")}\n\t\tORDER BY b.entry_seq ${direction}${limit}`.all<CachedBranchEntryRow>(db);\n}\n\nexport function deleteBranchEntries(db: SqliteDatabase, sessionId: string) {\n\tsql`DELETE FROM branch_entries WHERE session_id = ${sessionId}`.run(db);\n}\n\nexport function insertBranchEntry(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\tbranchId: string,\n\tentryId: string,\n\tentrySeq: number,\n\tentryType: string,\n\tcustomType: string | null,\n) {\n\tsql`INSERT INTO branch_entries\n\t\t\t(session_id, branch_id, entry_id, entry_seq, entry_type, custom_type)\n\t\t\tVALUES (${sessionId}, ${branchId}, ${entryId}, ${entrySeq}, ${entryType}, ${customType})`.run(db);\n}\n\nfunction customTypeFromPayload(row: BranchPathEntryRow): string | null {\n\tif (row.type !== \"custom\") return null;\n\ttry {\n\t\tconst payload = JSON.parse(row.payload) as unknown;\n\t\tif (typeof payload !== \"object\" || payload === null || Array.isArray(payload)) {\n\t\t\tthrow new Error(\"Payload is not an object\");\n\t\t}\n\t\tconst customType = (payload as { customType?: unknown }).customType;\n\t\tif (typeof customType !== \"string\") throw new Error(\"Invalid custom payload\");\n\t\treturn customType;\n\t} catch (error) {\n\t\tthrow new SessionError(\n\t\t\t\"invalid_entry\",\n\t\t\t`Invalid SQLite session entry ${row.id}: failed to decode entry ${row.id}`,\n\t\t\terror instanceof Error ? error : undefined,\n\t\t);\n\t}\n}\n\nexport function insertBranchEntriesForPath(db: SqliteDatabase, sessionId: string, branchId: string, leafId: string) {\n\tconst path: BranchPathEntryRow[] = [];\n\tconst seen = new Set<string>();\n\tlet entryId: string | null = leafId;\n\n\twhile (entryId !== null) {\n\t\tif (seen.has(entryId)) throw new SessionError(\"invalid_entry\", `Entry parent cycle at ${entryId}`);\n\t\tseen.add(entryId);\n\t\tconst row: BranchPathEntryRow | undefined = sql`SELECT id, seq, parent_id, type, payload\n\t\t\tFROM entries\n\t\t\tWHERE session_id = ${sessionId} AND id = ${entryId}`.get<BranchPathEntryRow>(db);\n\t\tif (!row) throw new SessionError(\"invalid_entry\", `Entry ${entryId} not found`);\n\t\tpath.push(row);\n\t\tentryId = row.parent_id;\n\t}\n\n\tfor (const row of path.reverse()) {\n\t\tinsertBranchEntry(db, sessionId, branchId, row.id, row.seq, row.type, customTypeFromPayload(row));\n\t}\n}\n\nexport function readBranchContainingEntry(db: SqliteDatabase, sessionId: string, entryId: string) {\n\tconst row = sql`SELECT b.branch_id, b.entry_seq\n\t\tFROM branch_entries AS b\n\t\tWHERE b.session_id = ${sessionId} AND b.entry_id = ${entryId}\n\t\tORDER BY b.branch_id\n\t\tLIMIT 1`.get<{ branch_id: string; entry_seq: number }>(db);\n\treturn row === undefined ? undefined : { branchId: row.branch_id, entrySeq: row.entry_seq };\n}\n\nexport function copyBranchEntriesThroughSeq(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\ttargetBranchId: string,\n\tsourceBranchId: string,\n\tthroughSeq: number,\n) {\n\tsql`INSERT INTO branch_entries (session_id, branch_id, entry_id, entry_seq, entry_type, custom_type)\n\t\tSELECT session_id, ${targetBranchId}, entry_id, entry_seq, entry_type, custom_type\n\t\tFROM branch_entries\n\t\tWHERE session_id = ${sessionId} AND branch_id = ${sourceBranchId} AND entry_seq <= ${throughSeq}`.run(db);\n}\n"]}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { SqliteDatabase } from "../types.ts";
|
|
2
|
-
export declare function readBranchTipIds(db: SqliteDatabase, sessionId: string): string[];
|
|
3
|
-
export declare function readBranchTipBranchId(db: SqliteDatabase, sessionId: string, tipId: string): string | undefined;
|
|
4
|
-
export declare function insertBranchTip(db: SqliteDatabase, sessionId: string, tipId: string, branchId: string): void;
|
|
5
|
-
export declare function updateBranchTip(db: SqliteDatabase, sessionId: string, branchId: string, oldTipId: string, newTipId: string): boolean;
|
|
6
|
-
export declare function deleteBranchTips(db: SqliteDatabase, sessionId: string): void;
|
|
7
|
-
//# sourceMappingURL=branch-tips.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"branch-tips.d.ts","sourceRoot":"","sources":["../../../src/sqlite/storage/branch-tips.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,YAIrE;AAED,wBAAgB,qBAAqB,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,sBAKzF;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,QAErG;AAED,wBAAgB,eAAe,CAC9B,EAAE,EAAE,cAAc,EAClB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,WAKhB;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,QAErE","sourcesContent":["import { sql } from \"../sql.ts\";\nimport type { SqliteDatabase } from \"../types.ts\";\n\nexport function readBranchTipIds(db: SqliteDatabase, sessionId: string) {\n\treturn sql`SELECT tip_id FROM branch_tips WHERE session_id = ${sessionId} ORDER BY tip_id`\n\t\t.all<{ tip_id: string }>(db)\n\t\t.map((row) => row.tip_id);\n}\n\nexport function readBranchTipBranchId(db: SqliteDatabase, sessionId: string, tipId: string) {\n\tconst tip = sql`SELECT branch_id FROM branch_tips WHERE session_id = ${sessionId} AND tip_id = ${tipId}`.get<{\n\t\tbranch_id: string;\n\t}>(db);\n\treturn tip?.branch_id;\n}\n\nexport function insertBranchTip(db: SqliteDatabase, sessionId: string, tipId: string, branchId: string) {\n\tsql`INSERT INTO branch_tips (session_id, tip_id, branch_id) VALUES (${sessionId}, ${tipId}, ${branchId})`.run(db);\n}\n\nexport function updateBranchTip(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\tbranchId: string,\n\toldTipId: string,\n\tnewTipId: string,\n) {\n\tconst result = sql`UPDATE branch_tips SET tip_id = ${newTipId}\n\t\tWHERE session_id = ${sessionId} AND branch_id = ${branchId} AND tip_id = ${oldTipId}`.run(db);\n\treturn result.changes === 1;\n}\n\nexport function deleteBranchTips(db: SqliteDatabase, sessionId: string) {\n\tsql`DELETE FROM branch_tips WHERE session_id = ${sessionId}`.run(db);\n}\n"]}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { sql } from "../sql.js";
|
|
2
|
-
export function readBranchTipIds(db, sessionId) {
|
|
3
|
-
return sql `SELECT tip_id FROM branch_tips WHERE session_id = ${sessionId} ORDER BY tip_id`
|
|
4
|
-
.all(db)
|
|
5
|
-
.map((row) => row.tip_id);
|
|
6
|
-
}
|
|
7
|
-
export function readBranchTipBranchId(db, sessionId, tipId) {
|
|
8
|
-
const tip = sql `SELECT branch_id FROM branch_tips WHERE session_id = ${sessionId} AND tip_id = ${tipId}`.get(db);
|
|
9
|
-
return tip?.branch_id;
|
|
10
|
-
}
|
|
11
|
-
export function insertBranchTip(db, sessionId, tipId, branchId) {
|
|
12
|
-
sql `INSERT INTO branch_tips (session_id, tip_id, branch_id) VALUES (${sessionId}, ${tipId}, ${branchId})`.run(db);
|
|
13
|
-
}
|
|
14
|
-
export function updateBranchTip(db, sessionId, branchId, oldTipId, newTipId) {
|
|
15
|
-
const result = sql `UPDATE branch_tips SET tip_id = ${newTipId}
|
|
16
|
-
WHERE session_id = ${sessionId} AND branch_id = ${branchId} AND tip_id = ${oldTipId}`.run(db);
|
|
17
|
-
return result.changes === 1;
|
|
18
|
-
}
|
|
19
|
-
export function deleteBranchTips(db, sessionId) {
|
|
20
|
-
sql `DELETE FROM branch_tips WHERE session_id = ${sessionId}`.run(db);
|
|
21
|
-
}
|
|
22
|
-
//# sourceMappingURL=branch-tips.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"branch-tips.js","sourceRoot":"","sources":["../../../src/sqlite/storage/branch-tips.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAGhC,MAAM,UAAU,gBAAgB,CAAC,EAAkB,EAAE,SAAiB,EAAE;IACvE,OAAO,GAAG,CAAA,qDAAqD,SAAS,kBAAkB;SACxF,GAAG,CAAqB,EAAE,CAAC;SAC3B,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;AAAA,CAC3B;AAED,MAAM,UAAU,qBAAqB,CAAC,EAAkB,EAAE,SAAiB,EAAE,KAAa,EAAE;IAC3F,MAAM,GAAG,GAAG,GAAG,CAAA,wDAAwD,SAAS,iBAAiB,KAAK,EAAE,CAAC,GAAG,CAEzG,EAAE,CAAC,CAAC;IACP,OAAO,GAAG,EAAE,SAAS,CAAC;AAAA,CACtB;AAED,MAAM,UAAU,eAAe,CAAC,EAAkB,EAAE,SAAiB,EAAE,KAAa,EAAE,QAAgB,EAAE;IACvG,GAAG,CAAA,mEAAmE,SAAS,KAAK,KAAK,KAAK,QAAQ,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAAA,CAClH;AAED,MAAM,UAAU,eAAe,CAC9B,EAAkB,EAClB,SAAiB,EACjB,QAAgB,EAChB,QAAgB,EAChB,QAAgB,EACf;IACD,MAAM,MAAM,GAAG,GAAG,CAAA,mCAAmC,QAAQ;uBACvC,SAAS,oBAAoB,QAAQ,iBAAiB,QAAQ,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC/F,OAAO,MAAM,CAAC,OAAO,KAAK,CAAC,CAAC;AAAA,CAC5B;AAED,MAAM,UAAU,gBAAgB,CAAC,EAAkB,EAAE,SAAiB,EAAE;IACvE,GAAG,CAAA,8CAA8C,SAAS,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAAA,CACrE","sourcesContent":["import { sql } from \"../sql.ts\";\nimport type { SqliteDatabase } from \"../types.ts\";\n\nexport function readBranchTipIds(db: SqliteDatabase, sessionId: string) {\n\treturn sql`SELECT tip_id FROM branch_tips WHERE session_id = ${sessionId} ORDER BY tip_id`\n\t\t.all<{ tip_id: string }>(db)\n\t\t.map((row) => row.tip_id);\n}\n\nexport function readBranchTipBranchId(db: SqliteDatabase, sessionId: string, tipId: string) {\n\tconst tip = sql`SELECT branch_id FROM branch_tips WHERE session_id = ${sessionId} AND tip_id = ${tipId}`.get<{\n\t\tbranch_id: string;\n\t}>(db);\n\treturn tip?.branch_id;\n}\n\nexport function insertBranchTip(db: SqliteDatabase, sessionId: string, tipId: string, branchId: string) {\n\tsql`INSERT INTO branch_tips (session_id, tip_id, branch_id) VALUES (${sessionId}, ${tipId}, ${branchId})`.run(db);\n}\n\nexport function updateBranchTip(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\tbranchId: string,\n\toldTipId: string,\n\tnewTipId: string,\n) {\n\tconst result = sql`UPDATE branch_tips SET tip_id = ${newTipId}\n\t\tWHERE session_id = ${sessionId} AND branch_id = ${branchId} AND tip_id = ${oldTipId}`.run(db);\n\treturn result.changes === 1;\n}\n\nexport function deleteBranchTips(db: SqliteDatabase, sessionId: string) {\n\tsql`DELETE FROM branch_tips WHERE session_id = ${sessionId}`.run(db);\n}\n"]}
|