@sammorrowdrums/mcpi-session-backend-sqlite-node 0.85.0-bootstrap.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 +48 -0
- package/LICENSE +21 -0
- package/README.md +22 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +94 -0
- package/dist/index.js.map +1 -0
- package/dist/sqlite/branch-cache.d.ts +6 -0
- package/dist/sqlite/branch-cache.d.ts.map +1 -0
- package/dist/sqlite/branch-cache.js +70 -0
- package/dist/sqlite/branch-cache.js.map +1 -0
- package/dist/sqlite/index.d.ts +6 -0
- package/dist/sqlite/index.d.ts.map +1 -0
- package/dist/sqlite/index.js +5 -0
- package/dist/sqlite/index.js.map +1 -0
- package/dist/sqlite/migrations/001_initial.sql +122 -0
- package/dist/sqlite/migrations.d.ts +9 -0
- package/dist/sqlite/migrations.d.ts.map +1 -0
- package/dist/sqlite/migrations.js +39 -0
- package/dist/sqlite/migrations.js.map +1 -0
- package/dist/sqlite/repo.d.ts +41 -0
- package/dist/sqlite/repo.d.ts.map +1 -0
- package/dist/sqlite/repo.js +750 -0
- package/dist/sqlite/repo.js.map +1 -0
- package/dist/sqlite/search-backend.d.ts +14 -0
- package/dist/sqlite/search-backend.d.ts.map +1 -0
- package/dist/sqlite/search-backend.js +153 -0
- package/dist/sqlite/search-backend.js.map +1 -0
- package/dist/sqlite/sql.d.ts +19 -0
- package/dist/sqlite/sql.d.ts.map +1 -0
- package/dist/sqlite/sql.js +58 -0
- package/dist/sqlite/sql.js.map +1 -0
- package/dist/sqlite/storage/branch-entries.d.ts +41 -0
- package/dist/sqlite/storage/branch-entries.d.ts.map +1 -0
- package/dist/sqlite/storage/branch-entries.js +112 -0
- package/dist/sqlite/storage/branch-entries.js.map +1 -0
- package/dist/sqlite/storage/branch-tips.d.ts +7 -0
- package/dist/sqlite/storage/branch-tips.d.ts.map +1 -0
- package/dist/sqlite/storage/branch-tips.js +22 -0
- package/dist/sqlite/storage/branch-tips.js.map +1 -0
- package/dist/sqlite/storage/entries.d.ts +34 -0
- package/dist/sqlite/storage/entries.d.ts.map +1 -0
- package/dist/sqlite/storage/entries.js +37 -0
- package/dist/sqlite/storage/entries.js.map +1 -0
- package/dist/sqlite/storage/facts.d.ts +20 -0
- package/dist/sqlite/storage/facts.d.ts.map +1 -0
- package/dist/sqlite/storage/facts.js +38 -0
- package/dist/sqlite/storage/facts.js.map +1 -0
- package/dist/sqlite/storage/lanes.d.ts +35 -0
- package/dist/sqlite/storage/lanes.d.ts.map +1 -0
- package/dist/sqlite/storage/lanes.js +95 -0
- package/dist/sqlite/storage/lanes.js.map +1 -0
- package/dist/sqlite/storage/records.d.ts +38 -0
- package/dist/sqlite/storage/records.d.ts.map +1 -0
- package/dist/sqlite/storage/records.js +49 -0
- package/dist/sqlite/storage/records.js.map +1 -0
- package/dist/sqlite/storage/session-sequences.d.ts +7 -0
- package/dist/sqlite/storage/session-sequences.d.ts.map +1 -0
- package/dist/sqlite/storage/session-sequences.js +22 -0
- package/dist/sqlite/storage/session-sequences.js.map +1 -0
- package/dist/sqlite/storage/session-stats.d.ts +17 -0
- package/dist/sqlite/storage/session-stats.d.ts.map +1 -0
- package/dist/sqlite/storage/session-stats.js +40 -0
- package/dist/sqlite/storage/session-stats.js.map +1 -0
- package/dist/sqlite/storage/sessions.d.ts +26 -0
- package/dist/sqlite/storage/sessions.d.ts.map +1 -0
- package/dist/sqlite/storage/sessions.js +99 -0
- package/dist/sqlite/storage/sessions.js.map +1 -0
- package/dist/sqlite/storage/writer-leases.d.ts +15 -0
- package/dist/sqlite/storage/writer-leases.d.ts.map +1 -0
- package/dist/sqlite/storage/writer-leases.js +31 -0
- package/dist/sqlite/storage/writer-leases.js.map +1 -0
- package/dist/sqlite/types.d.ts +45 -0
- package/dist/sqlite/types.d.ts.map +1 -0
- package/dist/sqlite/types.js +2 -0
- package/dist/sqlite/types.js.map +1 -0
- package/package.json +53 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { SessionError } from "@sammorrowdrums/mcpi-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
|
|
@@ -0,0 +1 @@
|
|
|
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 \"@sammorrowdrums/mcpi-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"]}
|
|
@@ -0,0 +1,7 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"]}
|
|
@@ -0,0 +1,22 @@
|
|
|
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
|
|
@@ -0,0 +1 @@
|
|
|
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"]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Entry, EntryOrder } from "@sammorrowdrums/mcpi-agent-core";
|
|
2
|
+
import type { SqliteDatabase } from "../types.ts";
|
|
3
|
+
export interface EntryRow {
|
|
4
|
+
session_id: string;
|
|
5
|
+
seq: number;
|
|
6
|
+
id: string;
|
|
7
|
+
parent_id: string | null;
|
|
8
|
+
type: Entry["type"];
|
|
9
|
+
timestamp: number;
|
|
10
|
+
payload: string;
|
|
11
|
+
}
|
|
12
|
+
export interface NewEntryRow {
|
|
13
|
+
seq: number;
|
|
14
|
+
id: string;
|
|
15
|
+
parentId: string | null;
|
|
16
|
+
type: Entry["type"];
|
|
17
|
+
timestamp: number;
|
|
18
|
+
payload: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function entryPayload(entry: Entry): Record<string, unknown>;
|
|
21
|
+
export declare function insertEntryRow(db: SqliteDatabase, sessionId: string, entry: NewEntryRow): void;
|
|
22
|
+
export declare function readEntryRow(db: SqliteDatabase, sessionId: string, entryId: string): EntryRow | undefined;
|
|
23
|
+
export declare function readEntryRows(db: SqliteDatabase, sessionId: string, options?: {
|
|
24
|
+
afterSeq?: number;
|
|
25
|
+
cursor?: {
|
|
26
|
+
afterSeq: number;
|
|
27
|
+
};
|
|
28
|
+
type?: Entry["type"];
|
|
29
|
+
order?: EntryOrder;
|
|
30
|
+
limit?: number;
|
|
31
|
+
}): EntryRow[];
|
|
32
|
+
export declare function idExistsInEntries(db: SqliteDatabase, sessionId: string, id: string): boolean;
|
|
33
|
+
export declare function deleteEntryRows(db: SqliteDatabase, sessionId: string): void;
|
|
34
|
+
//# sourceMappingURL=entries.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entries.d.ts","sourceRoot":"","sources":["../../../src/sqlite/storage/entries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAC;AAEzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,MAAM,WAAW,QAAQ;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,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,WAAW;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAGlE;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,QAKvF;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,wBAIlF;AAED,wBAAgB,aAAa,CAC5B,EAAE,EAAE,cAAc,EAClB,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE;IACR,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9B,IAAI,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACrB,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;CACV,cAiBN;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,WAIlF;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,QAEpE","sourcesContent":["import type { Entry, EntryOrder } from \"@sammorrowdrums/mcpi-agent-core\";\nimport { sql } from \"../sql.ts\";\nimport type { SqliteDatabase } from \"../types.ts\";\n\nexport interface EntryRow {\n\tsession_id: string;\n\tseq: number;\n\tid: string;\n\tparent_id: string | null;\n\ttype: Entry[\"type\"];\n\ttimestamp: number;\n\tpayload: string;\n}\n\nexport interface NewEntryRow {\n\tseq: number;\n\tid: string;\n\tparentId: string | null;\n\ttype: Entry[\"type\"];\n\ttimestamp: number;\n\tpayload: string;\n}\n\nexport function entryPayload(entry: Entry): Record<string, unknown> {\n\tconst { type: _type, id: _id, seq: _seq, parentId: _parentId, timestamp: _timestamp, ...payload } = entry;\n\treturn payload;\n}\n\nexport function insertEntryRow(db: SqliteDatabase, sessionId: string, entry: NewEntryRow) {\n\tsql`INSERT INTO entries (session_id, id, seq, parent_id, type, timestamp, payload)\n\t\tVALUES (${sessionId}, ${entry.id}, ${entry.seq}, ${entry.parentId}, ${entry.type}, ${entry.timestamp}, ${entry.payload})`.run(\n\t\tdb,\n\t);\n}\n\nexport function readEntryRow(db: SqliteDatabase, sessionId: string, entryId: string) {\n\treturn sql`SELECT session_id, seq, id, parent_id, type, timestamp, payload\n\t\tFROM entries\n\t\tWHERE session_id = ${sessionId} AND id = ${entryId}`.get<EntryRow>(db);\n}\n\nexport function readEntryRows(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\toptions: {\n\t\tafterSeq?: number;\n\t\tcursor?: { afterSeq: number };\n\t\ttype?: Entry[\"type\"];\n\t\torder?: EntryOrder;\n\t\tlimit?: number;\n\t} = {},\n) {\n\tconst oldestFirst = options.order === \"oldestFirst\";\n\tconst after = options.afterSeq === undefined ? sql`` : sql` AND seq > ${options.afterSeq}`;\n\tconst cursor =\n\t\toptions.cursor === undefined\n\t\t\t? sql``\n\t\t\t: oldestFirst\n\t\t\t\t? sql` AND seq > ${options.cursor.afterSeq}`\n\t\t\t\t: sql` AND seq < ${options.cursor.afterSeq}`;\n\tconst type = options.type === undefined ? sql`` : sql` AND type = ${options.type}`;\n\tconst direction = oldestFirst ? sql`ASC` : sql`DESC`;\n\tconst limit = options.limit === undefined ? sql`` : sql` LIMIT ${options.limit}`;\n\treturn sql`SELECT session_id, seq, id, parent_id, type, timestamp, payload\n\t\tFROM entries\n\t\tWHERE session_id = ${sessionId}${after}${cursor}${type}\n\t\tORDER BY seq ${direction}${limit}`.all<EntryRow>(db);\n}\n\nexport function idExistsInEntries(db: SqliteDatabase, sessionId: string, id: string) {\n\treturn !!sql`SELECT 1 AS found FROM entries WHERE session_id = ${sessionId} AND id = ${id} LIMIT 1`.get<{\n\t\tfound: number;\n\t}>(db);\n}\n\nexport function deleteEntryRows(db: SqliteDatabase, sessionId: string) {\n\tsql`DELETE FROM entries WHERE session_id = ${sessionId}`.run(db);\n}\n"]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { sql } from "../sql.js";
|
|
2
|
+
export function entryPayload(entry) {
|
|
3
|
+
const { type: _type, id: _id, seq: _seq, parentId: _parentId, timestamp: _timestamp, ...payload } = entry;
|
|
4
|
+
return payload;
|
|
5
|
+
}
|
|
6
|
+
export function insertEntryRow(db, sessionId, entry) {
|
|
7
|
+
sql `INSERT INTO entries (session_id, id, seq, parent_id, type, timestamp, payload)
|
|
8
|
+
VALUES (${sessionId}, ${entry.id}, ${entry.seq}, ${entry.parentId}, ${entry.type}, ${entry.timestamp}, ${entry.payload})`.run(db);
|
|
9
|
+
}
|
|
10
|
+
export function readEntryRow(db, sessionId, entryId) {
|
|
11
|
+
return sql `SELECT session_id, seq, id, parent_id, type, timestamp, payload
|
|
12
|
+
FROM entries
|
|
13
|
+
WHERE session_id = ${sessionId} AND id = ${entryId}`.get(db);
|
|
14
|
+
}
|
|
15
|
+
export function readEntryRows(db, sessionId, options = {}) {
|
|
16
|
+
const oldestFirst = options.order === "oldestFirst";
|
|
17
|
+
const after = options.afterSeq === undefined ? sql `` : sql ` AND seq > ${options.afterSeq}`;
|
|
18
|
+
const cursor = options.cursor === undefined
|
|
19
|
+
? sql ``
|
|
20
|
+
: oldestFirst
|
|
21
|
+
? sql ` AND seq > ${options.cursor.afterSeq}`
|
|
22
|
+
: sql ` AND seq < ${options.cursor.afterSeq}`;
|
|
23
|
+
const type = options.type === undefined ? sql `` : sql ` AND type = ${options.type}`;
|
|
24
|
+
const direction = oldestFirst ? sql `ASC` : sql `DESC`;
|
|
25
|
+
const limit = options.limit === undefined ? sql `` : sql ` LIMIT ${options.limit}`;
|
|
26
|
+
return sql `SELECT session_id, seq, id, parent_id, type, timestamp, payload
|
|
27
|
+
FROM entries
|
|
28
|
+
WHERE session_id = ${sessionId}${after}${cursor}${type}
|
|
29
|
+
ORDER BY seq ${direction}${limit}`.all(db);
|
|
30
|
+
}
|
|
31
|
+
export function idExistsInEntries(db, sessionId, id) {
|
|
32
|
+
return !!sql `SELECT 1 AS found FROM entries WHERE session_id = ${sessionId} AND id = ${id} LIMIT 1`.get(db);
|
|
33
|
+
}
|
|
34
|
+
export function deleteEntryRows(db, sessionId) {
|
|
35
|
+
sql `DELETE FROM entries WHERE session_id = ${sessionId}`.run(db);
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=entries.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entries.js","sourceRoot":"","sources":["../../../src/sqlite/storage/entries.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAsBhC,MAAM,UAAU,YAAY,CAAC,KAAY,EAA2B;IACnE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,GAAG,OAAO,EAAE,GAAG,KAAK,CAAC;IAC1G,OAAO,OAAO,CAAC;AAAA,CACf;AAED,MAAM,UAAU,cAAc,CAAC,EAAkB,EAAE,SAAiB,EAAE,KAAkB,EAAE;IACzF,GAAG,CAAA;YACQ,SAAS,KAAK,KAAK,CAAC,EAAE,KAAK,KAAK,CAAC,GAAG,KAAK,KAAK,CAAC,QAAQ,KAAK,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,SAAS,KAAK,KAAK,CAAC,OAAO,GAAG,CAAC,GAAG,CAC7H,EAAE,CACF,CAAC;AAAA,CACF;AAED,MAAM,UAAU,YAAY,CAAC,EAAkB,EAAE,SAAiB,EAAE,OAAe,EAAE;IACpF,OAAO,GAAG,CAAA;;uBAEY,SAAS,aAAa,OAAO,EAAE,CAAC,GAAG,CAAW,EAAE,CAAC,CAAC;AAAA,CACxE;AAED,MAAM,UAAU,aAAa,CAC5B,EAAkB,EAClB,SAAiB,EACjB,OAAO,GAMH,EAAE,EACL;IACD,MAAM,WAAW,GAAG,OAAO,CAAC,KAAK,KAAK,aAAa,CAAC;IACpD,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAA,EAAE,CAAC,CAAC,CAAC,GAAG,CAAA,cAAc,OAAO,CAAC,QAAQ,EAAE,CAAC;IAC3F,MAAM,MAAM,GACX,OAAO,CAAC,MAAM,KAAK,SAAS;QAC3B,CAAC,CAAC,GAAG,CAAA,EAAE;QACP,CAAC,CAAC,WAAW;YACZ,CAAC,CAAC,GAAG,CAAA,cAAc,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE;YAC5C,CAAC,CAAC,GAAG,CAAA,cAAc,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC;IAChD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAA,EAAE,CAAC,CAAC,CAAC,GAAG,CAAA,eAAe,OAAO,CAAC,IAAI,EAAE,CAAC;IACnF,MAAM,SAAS,GAAG,WAAW,CAAC,CAAC,CAAC,GAAG,CAAA,KAAK,CAAC,CAAC,CAAC,GAAG,CAAA,MAAM,CAAC;IACrD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAA,EAAE,CAAC,CAAC,CAAC,GAAG,CAAA,UAAU,OAAO,CAAC,KAAK,EAAE,CAAC;IACjF,OAAO,GAAG,CAAA;;uBAEY,SAAS,GAAG,KAAK,GAAG,MAAM,GAAG,IAAI;iBACvC,SAAS,GAAG,KAAK,EAAE,CAAC,GAAG,CAAW,EAAE,CAAC,CAAC;AAAA,CACtD;AAED,MAAM,UAAU,iBAAiB,CAAC,EAAkB,EAAE,SAAiB,EAAE,EAAU,EAAE;IACpF,OAAO,CAAC,CAAC,GAAG,CAAA,qDAAqD,SAAS,aAAa,EAAE,UAAU,CAAC,GAAG,CAEpG,EAAE,CAAC,CAAC;AAAA,CACP;AAED,MAAM,UAAU,eAAe,CAAC,EAAkB,EAAE,SAAiB,EAAE;IACtE,GAAG,CAAA,0CAA0C,SAAS,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAAA,CACjE","sourcesContent":["import type { Entry, EntryOrder } from \"@sammorrowdrums/mcpi-agent-core\";\nimport { sql } from \"../sql.ts\";\nimport type { SqliteDatabase } from \"../types.ts\";\n\nexport interface EntryRow {\n\tsession_id: string;\n\tseq: number;\n\tid: string;\n\tparent_id: string | null;\n\ttype: Entry[\"type\"];\n\ttimestamp: number;\n\tpayload: string;\n}\n\nexport interface NewEntryRow {\n\tseq: number;\n\tid: string;\n\tparentId: string | null;\n\ttype: Entry[\"type\"];\n\ttimestamp: number;\n\tpayload: string;\n}\n\nexport function entryPayload(entry: Entry): Record<string, unknown> {\n\tconst { type: _type, id: _id, seq: _seq, parentId: _parentId, timestamp: _timestamp, ...payload } = entry;\n\treturn payload;\n}\n\nexport function insertEntryRow(db: SqliteDatabase, sessionId: string, entry: NewEntryRow) {\n\tsql`INSERT INTO entries (session_id, id, seq, parent_id, type, timestamp, payload)\n\t\tVALUES (${sessionId}, ${entry.id}, ${entry.seq}, ${entry.parentId}, ${entry.type}, ${entry.timestamp}, ${entry.payload})`.run(\n\t\tdb,\n\t);\n}\n\nexport function readEntryRow(db: SqliteDatabase, sessionId: string, entryId: string) {\n\treturn sql`SELECT session_id, seq, id, parent_id, type, timestamp, payload\n\t\tFROM entries\n\t\tWHERE session_id = ${sessionId} AND id = ${entryId}`.get<EntryRow>(db);\n}\n\nexport function readEntryRows(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\toptions: {\n\t\tafterSeq?: number;\n\t\tcursor?: { afterSeq: number };\n\t\ttype?: Entry[\"type\"];\n\t\torder?: EntryOrder;\n\t\tlimit?: number;\n\t} = {},\n) {\n\tconst oldestFirst = options.order === \"oldestFirst\";\n\tconst after = options.afterSeq === undefined ? sql`` : sql` AND seq > ${options.afterSeq}`;\n\tconst cursor =\n\t\toptions.cursor === undefined\n\t\t\t? sql``\n\t\t\t: oldestFirst\n\t\t\t\t? sql` AND seq > ${options.cursor.afterSeq}`\n\t\t\t\t: sql` AND seq < ${options.cursor.afterSeq}`;\n\tconst type = options.type === undefined ? sql`` : sql` AND type = ${options.type}`;\n\tconst direction = oldestFirst ? sql`ASC` : sql`DESC`;\n\tconst limit = options.limit === undefined ? sql`` : sql` LIMIT ${options.limit}`;\n\treturn sql`SELECT session_id, seq, id, parent_id, type, timestamp, payload\n\t\tFROM entries\n\t\tWHERE session_id = ${sessionId}${after}${cursor}${type}\n\t\tORDER BY seq ${direction}${limit}`.all<EntryRow>(db);\n}\n\nexport function idExistsInEntries(db: SqliteDatabase, sessionId: string, id: string) {\n\treturn !!sql`SELECT 1 AS found FROM entries WHERE session_id = ${sessionId} AND id = ${id} LIMIT 1`.get<{\n\t\tfound: number;\n\t}>(db);\n}\n\nexport function deleteEntryRows(db: SqliteDatabase, sessionId: string) {\n\tsql`DELETE FROM entries WHERE session_id = ${sessionId}`.run(db);\n}\n"]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { SqliteDatabase } from "../types.ts";
|
|
2
|
+
export interface FactRow {
|
|
3
|
+
session_id: string;
|
|
4
|
+
seq: number;
|
|
5
|
+
kind: string;
|
|
6
|
+
key: string | null;
|
|
7
|
+
value: string | null;
|
|
8
|
+
}
|
|
9
|
+
export declare function appendFact(db: SqliteDatabase, sessionId: string, seq: number, kind: string, key: string | null, value: string | null): void;
|
|
10
|
+
export declare function readLatestFact(db: SqliteDatabase, sessionId: string, kind: string, key: string | null): FactRow | undefined;
|
|
11
|
+
export declare function readLatestLabelFacts(db: SqliteDatabase, sessionId: string): {
|
|
12
|
+
key: string;
|
|
13
|
+
value: string;
|
|
14
|
+
}[];
|
|
15
|
+
export declare function readFactRows(db: SqliteDatabase, sessionId: string, options?: {
|
|
16
|
+
afterSeq?: number;
|
|
17
|
+
limit?: number;
|
|
18
|
+
}): FactRow[];
|
|
19
|
+
export declare function deleteFactRows(db: SqliteDatabase, sessionId: string): void;
|
|
20
|
+
//# sourceMappingURL=facts.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"facts.d.ts","sourceRoot":"","sources":["../../../src/sqlite/storage/facts.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,MAAM,WAAW,OAAO;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAED,wBAAgB,UAAU,CACzB,EAAE,EAAE,cAAc,EAClB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,GAAG,IAAI,EAClB,KAAK,EAAE,MAAM,GAAG,IAAI,QAKpB;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,uBAMrG;AAED,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM;;;IAczE;AAED,wBAAgB,YAAY,CAC3B,EAAE,EAAE,cAAc,EAClB,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,aAQnD;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,QAEnE","sourcesContent":["import { sql } from \"../sql.ts\";\nimport type { SqliteDatabase } from \"../types.ts\";\n\nexport interface FactRow {\n\tsession_id: string;\n\tseq: number;\n\tkind: string;\n\tkey: string | null;\n\tvalue: string | null;\n}\n\nexport function appendFact(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\tseq: number,\n\tkind: string,\n\tkey: string | null,\n\tvalue: string | null,\n) {\n\tsql`INSERT INTO facts (session_id, seq, kind, key, value) VALUES (${sessionId}, ${seq}, ${kind}, ${key}, ${value})`.run(\n\t\tdb,\n\t);\n}\n\nexport function readLatestFact(db: SqliteDatabase, sessionId: string, kind: string, key: string | null) {\n\treturn sql`SELECT session_id, seq, kind, key, value\n\t\tFROM facts INDEXED BY idx_facts_session_kind_key_seq\n\t\tWHERE session_id = ${sessionId} AND kind = ${kind} AND key IS ${key}\n\t\tORDER BY seq DESC\n\t\tLIMIT 1`.get<FactRow>(db);\n}\n\nexport function readLatestLabelFacts(db: SqliteDatabase, sessionId: string) {\n\treturn sql`SELECT f.key, f.value\n\t\tFROM facts AS f INDEXED BY idx_facts_session_kind_key_seq\n\t\tWHERE f.session_id = ${sessionId}\n\t\t\tAND f.kind = 'label'\n\t\t\tAND f.value IS NOT NULL\n\t\t\tAND f.seq = (\n\t\t\t\tSELECT MAX(candidate.seq)\n\t\t\t\tFROM facts AS candidate INDEXED BY idx_facts_session_kind_key_seq\n\t\t\t\tWHERE candidate.session_id = f.session_id\n\t\t\t\t\tAND candidate.kind = f.kind\n\t\t\t\t\tAND candidate.key IS f.key\n\t\t\t)\n\t\tORDER BY f.key`.all<{ key: string; value: string }>(db);\n}\n\nexport function readFactRows(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\toptions: { afterSeq?: number; limit?: number } = {},\n) {\n\tconst after = options.afterSeq === undefined ? sql`` : sql` AND seq > ${options.afterSeq}`;\n\tconst limit = options.limit === undefined ? sql`` : sql` LIMIT ${options.limit}`;\n\treturn sql`SELECT session_id, seq, kind, key, value\n\t\tFROM facts\n\t\tWHERE session_id = ${sessionId}${after}\n\t\tORDER BY seq${limit}`.all<FactRow>(db);\n}\n\nexport function deleteFactRows(db: SqliteDatabase, sessionId: string) {\n\tsql`DELETE FROM facts WHERE session_id = ${sessionId}`.run(db);\n}\n"]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { sql } from "../sql.js";
|
|
2
|
+
export function appendFact(db, sessionId, seq, kind, key, value) {
|
|
3
|
+
sql `INSERT INTO facts (session_id, seq, kind, key, value) VALUES (${sessionId}, ${seq}, ${kind}, ${key}, ${value})`.run(db);
|
|
4
|
+
}
|
|
5
|
+
export function readLatestFact(db, sessionId, kind, key) {
|
|
6
|
+
return sql `SELECT session_id, seq, kind, key, value
|
|
7
|
+
FROM facts INDEXED BY idx_facts_session_kind_key_seq
|
|
8
|
+
WHERE session_id = ${sessionId} AND kind = ${kind} AND key IS ${key}
|
|
9
|
+
ORDER BY seq DESC
|
|
10
|
+
LIMIT 1`.get(db);
|
|
11
|
+
}
|
|
12
|
+
export function readLatestLabelFacts(db, sessionId) {
|
|
13
|
+
return sql `SELECT f.key, f.value
|
|
14
|
+
FROM facts AS f INDEXED BY idx_facts_session_kind_key_seq
|
|
15
|
+
WHERE f.session_id = ${sessionId}
|
|
16
|
+
AND f.kind = 'label'
|
|
17
|
+
AND f.value IS NOT NULL
|
|
18
|
+
AND f.seq = (
|
|
19
|
+
SELECT MAX(candidate.seq)
|
|
20
|
+
FROM facts AS candidate INDEXED BY idx_facts_session_kind_key_seq
|
|
21
|
+
WHERE candidate.session_id = f.session_id
|
|
22
|
+
AND candidate.kind = f.kind
|
|
23
|
+
AND candidate.key IS f.key
|
|
24
|
+
)
|
|
25
|
+
ORDER BY f.key`.all(db);
|
|
26
|
+
}
|
|
27
|
+
export function readFactRows(db, sessionId, options = {}) {
|
|
28
|
+
const after = options.afterSeq === undefined ? sql `` : sql ` AND seq > ${options.afterSeq}`;
|
|
29
|
+
const limit = options.limit === undefined ? sql `` : sql ` LIMIT ${options.limit}`;
|
|
30
|
+
return sql `SELECT session_id, seq, kind, key, value
|
|
31
|
+
FROM facts
|
|
32
|
+
WHERE session_id = ${sessionId}${after}
|
|
33
|
+
ORDER BY seq${limit}`.all(db);
|
|
34
|
+
}
|
|
35
|
+
export function deleteFactRows(db, sessionId) {
|
|
36
|
+
sql `DELETE FROM facts WHERE session_id = ${sessionId}`.run(db);
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=facts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"facts.js","sourceRoot":"","sources":["../../../src/sqlite/storage/facts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAWhC,MAAM,UAAU,UAAU,CACzB,EAAkB,EAClB,SAAiB,EACjB,GAAW,EACX,IAAY,EACZ,GAAkB,EAClB,KAAoB,EACnB;IACD,GAAG,CAAA,iEAAiE,SAAS,KAAK,GAAG,KAAK,IAAI,KAAK,GAAG,KAAK,KAAK,GAAG,CAAC,GAAG,CACtH,EAAE,CACF,CAAC;AAAA,CACF;AAED,MAAM,UAAU,cAAc,CAAC,EAAkB,EAAE,SAAiB,EAAE,IAAY,EAAE,GAAkB,EAAE;IACvG,OAAO,GAAG,CAAA;;uBAEY,SAAS,eAAe,IAAI,eAAe,GAAG;;UAE3D,CAAC,GAAG,CAAU,EAAE,CAAC,CAAC;AAAA,CAC3B;AAED,MAAM,UAAU,oBAAoB,CAAC,EAAkB,EAAE,SAAiB,EAAE;IAC3E,OAAO,GAAG,CAAA;;yBAEc,SAAS;;;;;;;;;;iBAUjB,CAAC,GAAG,CAAiC,EAAE,CAAC,CAAC;AAAA,CACzD;AAED,MAAM,UAAU,YAAY,CAC3B,EAAkB,EAClB,SAAiB,EACjB,OAAO,GAA0C,EAAE,EAClD;IACD,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAA,EAAE,CAAC,CAAC,CAAC,GAAG,CAAA,cAAc,OAAO,CAAC,QAAQ,EAAE,CAAC;IAC3F,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAA,EAAE,CAAC,CAAC,CAAC,GAAG,CAAA,UAAU,OAAO,CAAC,KAAK,EAAE,CAAC;IACjF,OAAO,GAAG,CAAA;;uBAEY,SAAS,GAAG,KAAK;gBACxB,KAAK,EAAE,CAAC,GAAG,CAAU,EAAE,CAAC,CAAC;AAAA,CACxC;AAED,MAAM,UAAU,cAAc,CAAC,EAAkB,EAAE,SAAiB,EAAE;IACrE,GAAG,CAAA,wCAAwC,SAAS,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAAA,CAC/D","sourcesContent":["import { sql } from \"../sql.ts\";\nimport type { SqliteDatabase } from \"../types.ts\";\n\nexport interface FactRow {\n\tsession_id: string;\n\tseq: number;\n\tkind: string;\n\tkey: string | null;\n\tvalue: string | null;\n}\n\nexport function appendFact(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\tseq: number,\n\tkind: string,\n\tkey: string | null,\n\tvalue: string | null,\n) {\n\tsql`INSERT INTO facts (session_id, seq, kind, key, value) VALUES (${sessionId}, ${seq}, ${kind}, ${key}, ${value})`.run(\n\t\tdb,\n\t);\n}\n\nexport function readLatestFact(db: SqliteDatabase, sessionId: string, kind: string, key: string | null) {\n\treturn sql`SELECT session_id, seq, kind, key, value\n\t\tFROM facts INDEXED BY idx_facts_session_kind_key_seq\n\t\tWHERE session_id = ${sessionId} AND kind = ${kind} AND key IS ${key}\n\t\tORDER BY seq DESC\n\t\tLIMIT 1`.get<FactRow>(db);\n}\n\nexport function readLatestLabelFacts(db: SqliteDatabase, sessionId: string) {\n\treturn sql`SELECT f.key, f.value\n\t\tFROM facts AS f INDEXED BY idx_facts_session_kind_key_seq\n\t\tWHERE f.session_id = ${sessionId}\n\t\t\tAND f.kind = 'label'\n\t\t\tAND f.value IS NOT NULL\n\t\t\tAND f.seq = (\n\t\t\t\tSELECT MAX(candidate.seq)\n\t\t\t\tFROM facts AS candidate INDEXED BY idx_facts_session_kind_key_seq\n\t\t\t\tWHERE candidate.session_id = f.session_id\n\t\t\t\t\tAND candidate.kind = f.kind\n\t\t\t\t\tAND candidate.key IS f.key\n\t\t\t)\n\t\tORDER BY f.key`.all<{ key: string; value: string }>(db);\n}\n\nexport function readFactRows(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\toptions: { afterSeq?: number; limit?: number } = {},\n) {\n\tconst after = options.afterSeq === undefined ? sql`` : sql` AND seq > ${options.afterSeq}`;\n\tconst limit = options.limit === undefined ? sql`` : sql` LIMIT ${options.limit}`;\n\treturn sql`SELECT session_id, seq, kind, key, value\n\t\tFROM facts\n\t\tWHERE session_id = ${sessionId}${after}\n\t\tORDER BY seq${limit}`.all<FactRow>(db);\n}\n\nexport function deleteFactRows(db: SqliteDatabase, sessionId: string) {\n\tsql`DELETE FROM facts WHERE session_id = ${sessionId}`.run(db);\n}\n"]}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { SqliteDatabase } from "../types.ts";
|
|
2
|
+
export interface LaneRow {
|
|
3
|
+
session_id: string;
|
|
4
|
+
lane: string;
|
|
5
|
+
leaf_id: string | null;
|
|
6
|
+
open_operation_id: string | null;
|
|
7
|
+
}
|
|
8
|
+
export interface LaneMoveRow {
|
|
9
|
+
session_id: string;
|
|
10
|
+
seq: number;
|
|
11
|
+
lane: string;
|
|
12
|
+
leaf_id: string | null;
|
|
13
|
+
}
|
|
14
|
+
export declare function createInitialLane(db: SqliteDatabase, sessionId: string, lane?: string, leafId?: string | null): void;
|
|
15
|
+
export declare function readLanes(db: SqliteDatabase, sessionId: string): {
|
|
16
|
+
session_id: string;
|
|
17
|
+
lane: string;
|
|
18
|
+
leaf_id: string | null;
|
|
19
|
+
open_operation_id: string | null;
|
|
20
|
+
}[];
|
|
21
|
+
export declare function readLane(db: SqliteDatabase, sessionId: string, lane: string): LaneRow | undefined;
|
|
22
|
+
export declare function readLaneHead(db: SqliteDatabase, sessionId: string, lane: string): {
|
|
23
|
+
leafId: string | null;
|
|
24
|
+
};
|
|
25
|
+
export declare function createLane(db: SqliteDatabase, sessionId: string, seq: number, lane: string, leafId: string | null): void;
|
|
26
|
+
export declare function moveLane(db: SqliteDatabase, sessionId: string, seq: number, lane: string, leafId: string | null): void;
|
|
27
|
+
export declare function setLaneLeaf(db: SqliteDatabase, sessionId: string, lane: string, leafId: string | null): void;
|
|
28
|
+
export declare function startLaneOperation(db: SqliteDatabase, sessionId: string, lane: string, runId: string): void;
|
|
29
|
+
export declare function finishLaneOperation(db: SqliteDatabase, sessionId: string, lane: string, runId: string): void;
|
|
30
|
+
export declare function readLaneMoveRows(db: SqliteDatabase, sessionId: string, options?: {
|
|
31
|
+
afterSeq?: number;
|
|
32
|
+
limit?: number;
|
|
33
|
+
}): LaneMoveRow[];
|
|
34
|
+
export declare function deleteLaneRows(db: SqliteDatabase, sessionId: string): void;
|
|
35
|
+
//# sourceMappingURL=lanes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lanes.d.ts","sourceRoot":"","sources":["../../../src/sqlite/storage/lanes.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,MAAM,WAAW,OAAO;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC;AAED,MAAM,WAAW,WAAW;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,SAAS,EAAE,MAAM,GAAE,MAAM,GAAG,IAAW,QAGnH;AAED,wBAAgB,SAAS,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM;;;;;IAuB9D;AAED,wBAAgB,QAAQ,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,uBAI3E;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM;;EAc/E;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,QAIjH;AAED,wBAAgB,QAAQ,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,QAI/G;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,QAGrG;AAED,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAOpG;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAGrG;AAED,wBAAgB,gBAAgB,CAC/B,EAAE,EAAE,cAAc,EAClB,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,iBAQnD;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,QAGnE","sourcesContent":["import { SessionError } from \"@sammorrowdrums/mcpi-agent-core\";\nimport { sql } from \"../sql.ts\";\nimport type { SqliteDatabase } from \"../types.ts\";\n\nexport interface LaneRow {\n\tsession_id: string;\n\tlane: string;\n\tleaf_id: string | null;\n\topen_operation_id: string | null;\n}\n\nexport interface LaneMoveRow {\n\tsession_id: string;\n\tseq: number;\n\tlane: string;\n\tleaf_id: string | null;\n}\n\nexport function createInitialLane(db: SqliteDatabase, sessionId: string, lane = \"main\", leafId: string | null = null) {\n\tsql`INSERT INTO lanes (session_id, lane, leaf_id, open_operation_id)\n\t\tVALUES (${sessionId}, ${lane}, ${leafId}, NULL)`.run(db);\n}\n\nexport function readLanes(db: SqliteDatabase, sessionId: string) {\n\tconst rows = sql`SELECT\n\t\t\tl.session_id,\n\t\t\tl.lane,\n\t\t\tl.leaf_id,\n\t\t\tl.open_operation_id,\n\t\t\t(l.leaf_id IS NULL OR EXISTS (\n\t\t\t\tSELECT 1 FROM entries AS e WHERE e.session_id = l.session_id AND e.id = l.leaf_id\n\t\t\t)) AS leaf_exists\n\t\tFROM lanes AS l\n\t\tWHERE l.session_id = ${sessionId}\n\t\tORDER BY l.lane`.all<LaneRow & { leaf_exists: number }>(db);\n\tfor (const row of rows) {\n\t\tif (row.leaf_exists === 0) {\n\t\t\tthrow new SessionError(\"storage\", `Lane ${row.lane} points at missing entry ${row.leaf_id}`);\n\t\t}\n\t}\n\treturn rows.map(({ session_id, lane, leaf_id, open_operation_id }) => ({\n\t\tsession_id,\n\t\tlane,\n\t\tleaf_id,\n\t\topen_operation_id,\n\t}));\n}\n\nexport function readLane(db: SqliteDatabase, sessionId: string, lane: string) {\n\treturn sql`SELECT session_id, lane, leaf_id, open_operation_id\n\t\tFROM lanes\n\t\tWHERE session_id = ${sessionId} AND lane = ${lane}`.get<LaneRow>(db);\n}\n\nexport function readLaneHead(db: SqliteDatabase, sessionId: string, lane: string) {\n\tconst row = sql`SELECT\n\t\t\tl.leaf_id,\n\t\t\t(l.leaf_id IS NULL OR EXISTS (\n\t\t\t\tSELECT 1 FROM entries AS e WHERE e.session_id = l.session_id AND e.id = l.leaf_id\n\t\t\t)) AS leaf_exists\n\t\tFROM lanes AS l\n\t\tWHERE l.session_id = ${sessionId} AND l.lane = ${lane}`.get<{\n\t\tleaf_id: string | null;\n\t\tleaf_exists: number;\n\t}>(db);\n\tif (!row) throw new SessionError(\"invalid_lane\", `Lane not found: ${lane}`);\n\tif (row.leaf_exists === 0) throw new SessionError(\"storage\", `Entry ${row.leaf_id} not found`);\n\treturn { leafId: row.leaf_id };\n}\n\nexport function createLane(db: SqliteDatabase, sessionId: string, seq: number, lane: string, leafId: string | null) {\n\tsql`INSERT INTO lanes (session_id, lane, leaf_id, open_operation_id)\n\t\tVALUES (${sessionId}, ${lane}, ${leafId}, NULL)`.run(db);\n\tappendLaneMove(db, sessionId, seq, lane, leafId);\n}\n\nexport function moveLane(db: SqliteDatabase, sessionId: string, seq: number, lane: string, leafId: string | null) {\n\tconst result = sql`UPDATE lanes SET leaf_id = ${leafId} WHERE session_id = ${sessionId} AND lane = ${lane}`.run(db);\n\tif (result.changes !== 1) throw new SessionError(\"invalid_lane\", `Lane not found: ${lane}`);\n\tappendLaneMove(db, sessionId, seq, lane, leafId);\n}\n\nexport function setLaneLeaf(db: SqliteDatabase, sessionId: string, lane: string, leafId: string | null) {\n\tconst result = sql`UPDATE lanes SET leaf_id = ${leafId} WHERE session_id = ${sessionId} AND lane = ${lane}`.run(db);\n\tif (result.changes !== 1) throw new SessionError(\"invalid_lane\", `Lane not found: ${lane}`);\n}\n\nexport function startLaneOperation(db: SqliteDatabase, sessionId: string, lane: string, runId: string) {\n\tconst result = sql`UPDATE lanes SET open_operation_id = ${runId}\n\t\tWHERE session_id = ${sessionId} AND lane = ${lane} AND open_operation_id IS NULL`.run(db);\n\tif (result.changes === 1) return;\n\tconst current = readLane(db, sessionId, lane);\n\tif (!current) throw new SessionError(\"invalid_lane\", `Lane not found: ${lane}`);\n\tthrow new SessionError(\"storage\", `Lane ${lane} already has an open operation ${current.open_operation_id}`);\n}\n\nexport function finishLaneOperation(db: SqliteDatabase, sessionId: string, lane: string, runId: string) {\n\tsql`UPDATE lanes SET open_operation_id = NULL\n\t\tWHERE session_id = ${sessionId} AND lane = ${lane} AND open_operation_id = ${runId}`.run(db);\n}\n\nexport function readLaneMoveRows(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\toptions: { afterSeq?: number; limit?: number } = {},\n) {\n\tconst after = options.afterSeq === undefined ? sql`` : sql` AND seq > ${options.afterSeq}`;\n\tconst limit = options.limit === undefined ? sql`` : sql` LIMIT ${options.limit}`;\n\treturn sql`SELECT session_id, seq, lane, leaf_id\n\t\tFROM lane_moves\n\t\tWHERE session_id = ${sessionId}${after}\n\t\tORDER BY seq${limit}`.all<LaneMoveRow>(db);\n}\n\nexport function deleteLaneRows(db: SqliteDatabase, sessionId: string) {\n\tsql`DELETE FROM lane_moves WHERE session_id = ${sessionId}`.run(db);\n\tsql`DELETE FROM lanes WHERE session_id = ${sessionId}`.run(db);\n}\n\nfunction appendLaneMove(db: SqliteDatabase, sessionId: string, seq: number, lane: string, leafId: string | null) {\n\tsql`INSERT INTO lane_moves (session_id, seq, lane, leaf_id) VALUES (${sessionId}, ${seq}, ${lane}, ${leafId})`.run(\n\t\tdb,\n\t);\n}\n"]}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { SessionError } from "@sammorrowdrums/mcpi-agent-core";
|
|
2
|
+
import { sql } from "../sql.js";
|
|
3
|
+
export function createInitialLane(db, sessionId, lane = "main", leafId = null) {
|
|
4
|
+
sql `INSERT INTO lanes (session_id, lane, leaf_id, open_operation_id)
|
|
5
|
+
VALUES (${sessionId}, ${lane}, ${leafId}, NULL)`.run(db);
|
|
6
|
+
}
|
|
7
|
+
export function readLanes(db, sessionId) {
|
|
8
|
+
const rows = sql `SELECT
|
|
9
|
+
l.session_id,
|
|
10
|
+
l.lane,
|
|
11
|
+
l.leaf_id,
|
|
12
|
+
l.open_operation_id,
|
|
13
|
+
(l.leaf_id IS NULL OR EXISTS (
|
|
14
|
+
SELECT 1 FROM entries AS e WHERE e.session_id = l.session_id AND e.id = l.leaf_id
|
|
15
|
+
)) AS leaf_exists
|
|
16
|
+
FROM lanes AS l
|
|
17
|
+
WHERE l.session_id = ${sessionId}
|
|
18
|
+
ORDER BY l.lane`.all(db);
|
|
19
|
+
for (const row of rows) {
|
|
20
|
+
if (row.leaf_exists === 0) {
|
|
21
|
+
throw new SessionError("storage", `Lane ${row.lane} points at missing entry ${row.leaf_id}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return rows.map(({ session_id, lane, leaf_id, open_operation_id }) => ({
|
|
25
|
+
session_id,
|
|
26
|
+
lane,
|
|
27
|
+
leaf_id,
|
|
28
|
+
open_operation_id,
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
export function readLane(db, sessionId, lane) {
|
|
32
|
+
return sql `SELECT session_id, lane, leaf_id, open_operation_id
|
|
33
|
+
FROM lanes
|
|
34
|
+
WHERE session_id = ${sessionId} AND lane = ${lane}`.get(db);
|
|
35
|
+
}
|
|
36
|
+
export function readLaneHead(db, sessionId, lane) {
|
|
37
|
+
const row = sql `SELECT
|
|
38
|
+
l.leaf_id,
|
|
39
|
+
(l.leaf_id IS NULL OR EXISTS (
|
|
40
|
+
SELECT 1 FROM entries AS e WHERE e.session_id = l.session_id AND e.id = l.leaf_id
|
|
41
|
+
)) AS leaf_exists
|
|
42
|
+
FROM lanes AS l
|
|
43
|
+
WHERE l.session_id = ${sessionId} AND l.lane = ${lane}`.get(db);
|
|
44
|
+
if (!row)
|
|
45
|
+
throw new SessionError("invalid_lane", `Lane not found: ${lane}`);
|
|
46
|
+
if (row.leaf_exists === 0)
|
|
47
|
+
throw new SessionError("storage", `Entry ${row.leaf_id} not found`);
|
|
48
|
+
return { leafId: row.leaf_id };
|
|
49
|
+
}
|
|
50
|
+
export function createLane(db, sessionId, seq, lane, leafId) {
|
|
51
|
+
sql `INSERT INTO lanes (session_id, lane, leaf_id, open_operation_id)
|
|
52
|
+
VALUES (${sessionId}, ${lane}, ${leafId}, NULL)`.run(db);
|
|
53
|
+
appendLaneMove(db, sessionId, seq, lane, leafId);
|
|
54
|
+
}
|
|
55
|
+
export function moveLane(db, sessionId, seq, lane, leafId) {
|
|
56
|
+
const result = sql `UPDATE lanes SET leaf_id = ${leafId} WHERE session_id = ${sessionId} AND lane = ${lane}`.run(db);
|
|
57
|
+
if (result.changes !== 1)
|
|
58
|
+
throw new SessionError("invalid_lane", `Lane not found: ${lane}`);
|
|
59
|
+
appendLaneMove(db, sessionId, seq, lane, leafId);
|
|
60
|
+
}
|
|
61
|
+
export function setLaneLeaf(db, sessionId, lane, leafId) {
|
|
62
|
+
const result = sql `UPDATE lanes SET leaf_id = ${leafId} WHERE session_id = ${sessionId} AND lane = ${lane}`.run(db);
|
|
63
|
+
if (result.changes !== 1)
|
|
64
|
+
throw new SessionError("invalid_lane", `Lane not found: ${lane}`);
|
|
65
|
+
}
|
|
66
|
+
export function startLaneOperation(db, sessionId, lane, runId) {
|
|
67
|
+
const result = sql `UPDATE lanes SET open_operation_id = ${runId}
|
|
68
|
+
WHERE session_id = ${sessionId} AND lane = ${lane} AND open_operation_id IS NULL`.run(db);
|
|
69
|
+
if (result.changes === 1)
|
|
70
|
+
return;
|
|
71
|
+
const current = readLane(db, sessionId, lane);
|
|
72
|
+
if (!current)
|
|
73
|
+
throw new SessionError("invalid_lane", `Lane not found: ${lane}`);
|
|
74
|
+
throw new SessionError("storage", `Lane ${lane} already has an open operation ${current.open_operation_id}`);
|
|
75
|
+
}
|
|
76
|
+
export function finishLaneOperation(db, sessionId, lane, runId) {
|
|
77
|
+
sql `UPDATE lanes SET open_operation_id = NULL
|
|
78
|
+
WHERE session_id = ${sessionId} AND lane = ${lane} AND open_operation_id = ${runId}`.run(db);
|
|
79
|
+
}
|
|
80
|
+
export function readLaneMoveRows(db, sessionId, options = {}) {
|
|
81
|
+
const after = options.afterSeq === undefined ? sql `` : sql ` AND seq > ${options.afterSeq}`;
|
|
82
|
+
const limit = options.limit === undefined ? sql `` : sql ` LIMIT ${options.limit}`;
|
|
83
|
+
return sql `SELECT session_id, seq, lane, leaf_id
|
|
84
|
+
FROM lane_moves
|
|
85
|
+
WHERE session_id = ${sessionId}${after}
|
|
86
|
+
ORDER BY seq${limit}`.all(db);
|
|
87
|
+
}
|
|
88
|
+
export function deleteLaneRows(db, sessionId) {
|
|
89
|
+
sql `DELETE FROM lane_moves WHERE session_id = ${sessionId}`.run(db);
|
|
90
|
+
sql `DELETE FROM lanes WHERE session_id = ${sessionId}`.run(db);
|
|
91
|
+
}
|
|
92
|
+
function appendLaneMove(db, sessionId, seq, lane, leafId) {
|
|
93
|
+
sql `INSERT INTO lane_moves (session_id, seq, lane, leaf_id) VALUES (${sessionId}, ${seq}, ${lane}, ${leafId})`.run(db);
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=lanes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lanes.js","sourceRoot":"","sources":["../../../src/sqlite/storage/lanes.ts"],"names":[],"mappings":"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","sourcesContent":["import { SessionError } from \"@sammorrowdrums/mcpi-agent-core\";\nimport { sql } from \"../sql.ts\";\nimport type { SqliteDatabase } from \"../types.ts\";\n\nexport interface LaneRow {\n\tsession_id: string;\n\tlane: string;\n\tleaf_id: string | null;\n\topen_operation_id: string | null;\n}\n\nexport interface LaneMoveRow {\n\tsession_id: string;\n\tseq: number;\n\tlane: string;\n\tleaf_id: string | null;\n}\n\nexport function createInitialLane(db: SqliteDatabase, sessionId: string, lane = \"main\", leafId: string | null = null) {\n\tsql`INSERT INTO lanes (session_id, lane, leaf_id, open_operation_id)\n\t\tVALUES (${sessionId}, ${lane}, ${leafId}, NULL)`.run(db);\n}\n\nexport function readLanes(db: SqliteDatabase, sessionId: string) {\n\tconst rows = sql`SELECT\n\t\t\tl.session_id,\n\t\t\tl.lane,\n\t\t\tl.leaf_id,\n\t\t\tl.open_operation_id,\n\t\t\t(l.leaf_id IS NULL OR EXISTS (\n\t\t\t\tSELECT 1 FROM entries AS e WHERE e.session_id = l.session_id AND e.id = l.leaf_id\n\t\t\t)) AS leaf_exists\n\t\tFROM lanes AS l\n\t\tWHERE l.session_id = ${sessionId}\n\t\tORDER BY l.lane`.all<LaneRow & { leaf_exists: number }>(db);\n\tfor (const row of rows) {\n\t\tif (row.leaf_exists === 0) {\n\t\t\tthrow new SessionError(\"storage\", `Lane ${row.lane} points at missing entry ${row.leaf_id}`);\n\t\t}\n\t}\n\treturn rows.map(({ session_id, lane, leaf_id, open_operation_id }) => ({\n\t\tsession_id,\n\t\tlane,\n\t\tleaf_id,\n\t\topen_operation_id,\n\t}));\n}\n\nexport function readLane(db: SqliteDatabase, sessionId: string, lane: string) {\n\treturn sql`SELECT session_id, lane, leaf_id, open_operation_id\n\t\tFROM lanes\n\t\tWHERE session_id = ${sessionId} AND lane = ${lane}`.get<LaneRow>(db);\n}\n\nexport function readLaneHead(db: SqliteDatabase, sessionId: string, lane: string) {\n\tconst row = sql`SELECT\n\t\t\tl.leaf_id,\n\t\t\t(l.leaf_id IS NULL OR EXISTS (\n\t\t\t\tSELECT 1 FROM entries AS e WHERE e.session_id = l.session_id AND e.id = l.leaf_id\n\t\t\t)) AS leaf_exists\n\t\tFROM lanes AS l\n\t\tWHERE l.session_id = ${sessionId} AND l.lane = ${lane}`.get<{\n\t\tleaf_id: string | null;\n\t\tleaf_exists: number;\n\t}>(db);\n\tif (!row) throw new SessionError(\"invalid_lane\", `Lane not found: ${lane}`);\n\tif (row.leaf_exists === 0) throw new SessionError(\"storage\", `Entry ${row.leaf_id} not found`);\n\treturn { leafId: row.leaf_id };\n}\n\nexport function createLane(db: SqliteDatabase, sessionId: string, seq: number, lane: string, leafId: string | null) {\n\tsql`INSERT INTO lanes (session_id, lane, leaf_id, open_operation_id)\n\t\tVALUES (${sessionId}, ${lane}, ${leafId}, NULL)`.run(db);\n\tappendLaneMove(db, sessionId, seq, lane, leafId);\n}\n\nexport function moveLane(db: SqliteDatabase, sessionId: string, seq: number, lane: string, leafId: string | null) {\n\tconst result = sql`UPDATE lanes SET leaf_id = ${leafId} WHERE session_id = ${sessionId} AND lane = ${lane}`.run(db);\n\tif (result.changes !== 1) throw new SessionError(\"invalid_lane\", `Lane not found: ${lane}`);\n\tappendLaneMove(db, sessionId, seq, lane, leafId);\n}\n\nexport function setLaneLeaf(db: SqliteDatabase, sessionId: string, lane: string, leafId: string | null) {\n\tconst result = sql`UPDATE lanes SET leaf_id = ${leafId} WHERE session_id = ${sessionId} AND lane = ${lane}`.run(db);\n\tif (result.changes !== 1) throw new SessionError(\"invalid_lane\", `Lane not found: ${lane}`);\n}\n\nexport function startLaneOperation(db: SqliteDatabase, sessionId: string, lane: string, runId: string) {\n\tconst result = sql`UPDATE lanes SET open_operation_id = ${runId}\n\t\tWHERE session_id = ${sessionId} AND lane = ${lane} AND open_operation_id IS NULL`.run(db);\n\tif (result.changes === 1) return;\n\tconst current = readLane(db, sessionId, lane);\n\tif (!current) throw new SessionError(\"invalid_lane\", `Lane not found: ${lane}`);\n\tthrow new SessionError(\"storage\", `Lane ${lane} already has an open operation ${current.open_operation_id}`);\n}\n\nexport function finishLaneOperation(db: SqliteDatabase, sessionId: string, lane: string, runId: string) {\n\tsql`UPDATE lanes SET open_operation_id = NULL\n\t\tWHERE session_id = ${sessionId} AND lane = ${lane} AND open_operation_id = ${runId}`.run(db);\n}\n\nexport function readLaneMoveRows(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\toptions: { afterSeq?: number; limit?: number } = {},\n) {\n\tconst after = options.afterSeq === undefined ? sql`` : sql` AND seq > ${options.afterSeq}`;\n\tconst limit = options.limit === undefined ? sql`` : sql` LIMIT ${options.limit}`;\n\treturn sql`SELECT session_id, seq, lane, leaf_id\n\t\tFROM lane_moves\n\t\tWHERE session_id = ${sessionId}${after}\n\t\tORDER BY seq${limit}`.all<LaneMoveRow>(db);\n}\n\nexport function deleteLaneRows(db: SqliteDatabase, sessionId: string) {\n\tsql`DELETE FROM lane_moves WHERE session_id = ${sessionId}`.run(db);\n\tsql`DELETE FROM lanes WHERE session_id = ${sessionId}`.run(db);\n}\n\nfunction appendLaneMove(db: SqliteDatabase, sessionId: string, seq: number, lane: string, leafId: string | null) {\n\tsql`INSERT INTO lane_moves (session_id, seq, lane, leaf_id) VALUES (${sessionId}, ${seq}, ${lane}, ${leafId})`.run(\n\t\tdb,\n\t);\n}\n"]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { SqliteDatabase } from "../types.ts";
|
|
2
|
+
export interface RecordRow {
|
|
3
|
+
session_id: string;
|
|
4
|
+
seq: number;
|
|
5
|
+
id: string;
|
|
6
|
+
lane: string;
|
|
7
|
+
run_id: string | null;
|
|
8
|
+
type: string;
|
|
9
|
+
op_kind: string | null;
|
|
10
|
+
timestamp: number;
|
|
11
|
+
payload: string;
|
|
12
|
+
}
|
|
13
|
+
export interface NewRecordRow {
|
|
14
|
+
seq: number;
|
|
15
|
+
id: string;
|
|
16
|
+
lane: string;
|
|
17
|
+
runId?: string;
|
|
18
|
+
type: string;
|
|
19
|
+
opKind?: string;
|
|
20
|
+
timestamp: number;
|
|
21
|
+
payload: string;
|
|
22
|
+
}
|
|
23
|
+
export declare function appendRecordRow(db: SqliteDatabase, sessionId: string, record: NewRecordRow): void;
|
|
24
|
+
export declare function idExistsInRecords(db: SqliteDatabase, sessionId: string, id: string): boolean;
|
|
25
|
+
export declare function deleteRecordRows(db: SqliteDatabase, sessionId: string): void;
|
|
26
|
+
export declare function readRecordRows(db: SqliteDatabase, sessionId: string, query?: {
|
|
27
|
+
lane?: string;
|
|
28
|
+
type?: string;
|
|
29
|
+
runId?: string;
|
|
30
|
+
operationKind?: string;
|
|
31
|
+
afterSeq?: number;
|
|
32
|
+
order?: "newestFirst" | "oldestFirst";
|
|
33
|
+
limit?: number;
|
|
34
|
+
}): RecordRow[];
|
|
35
|
+
export declare function readOpenOperationRows(db: SqliteDatabase, sessionId: string, lane: string, _options?: {
|
|
36
|
+
limit?: number;
|
|
37
|
+
}): RecordRow[];
|
|
38
|
+
//# sourceMappingURL=records.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"records.d.ts","sourceRoot":"","sources":["../../../src/sqlite/storage/records.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,MAAM,WAAW,SAAS;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,YAAY;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,eAAe,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,QAM1F;AAED,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,WAIlF;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,QAErE;AAED,wBAAgB,cAAc,CAC7B,EAAE,EAAE,cAAc,EAClB,SAAS,EAAE,MAAM,EACjB,KAAK,GAAE;IACN,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,aAAa,GAAG,aAAa,CAAC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;CACV,eAcN;AAED,wBAAgB,qBAAqB,CACpC,EAAE,EAAE,cAAc,EAClB,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,MAAM,EACZ,QAAQ,GAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC/B,SAAS,EAAE,CAiBb","sourcesContent":["import { SessionError } from \"@sammorrowdrums/mcpi-agent-core\";\nimport { joinSqlFragments, sql } from \"../sql.ts\";\nimport type { SqliteDatabase } from \"../types.ts\";\n\nexport interface RecordRow {\n\tsession_id: string;\n\tseq: number;\n\tid: string;\n\tlane: string;\n\trun_id: string | null;\n\ttype: string;\n\top_kind: string | null;\n\ttimestamp: number;\n\tpayload: string;\n}\n\nexport interface NewRecordRow {\n\tseq: number;\n\tid: string;\n\tlane: string;\n\trunId?: string;\n\ttype: string;\n\topKind?: string;\n\ttimestamp: number;\n\tpayload: string;\n}\n\nexport function appendRecordRow(db: SqliteDatabase, sessionId: string, record: NewRecordRow) {\n\tsql`INSERT INTO records\n\t\t\t(session_id, seq, id, lane, run_id, type, op_kind, timestamp, payload)\n\t\t\tVALUES (${sessionId}, ${record.seq}, ${record.id}, ${record.lane}, ${record.runId ?? null}, ${record.type}, ${record.opKind ?? null}, ${record.timestamp}, ${record.payload})`.run(\n\t\tdb,\n\t);\n}\n\nexport function idExistsInRecords(db: SqliteDatabase, sessionId: string, id: string) {\n\treturn !!sql`SELECT 1 AS found FROM records WHERE session_id = ${sessionId} AND id = ${id} LIMIT 1`.get<{\n\t\tfound: number;\n\t}>(db);\n}\n\nexport function deleteRecordRows(db: SqliteDatabase, sessionId: string) {\n\tsql`DELETE FROM records WHERE session_id = ${sessionId}`.run(db);\n}\n\nexport function readRecordRows(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\tquery: {\n\t\tlane?: string;\n\t\ttype?: string;\n\t\trunId?: string;\n\t\toperationKind?: string;\n\t\tafterSeq?: number;\n\t\torder?: \"newestFirst\" | \"oldestFirst\";\n\t\tlimit?: number;\n\t} = {},\n) {\n\tconst predicates = [sql`session_id = ${sessionId}`];\n\tif (query.lane !== undefined) predicates.push(sql`lane = ${query.lane}`);\n\tif (query.type !== undefined) predicates.push(sql`type = ${query.type}`);\n\tif (query.runId !== undefined) predicates.push(sql`run_id = ${query.runId}`);\n\tif (query.operationKind !== undefined) predicates.push(sql`op_kind = ${query.operationKind}`);\n\tif (query.afterSeq !== undefined) predicates.push(sql`seq > ${query.afterSeq}`);\n\tconst direction = query.order === \"oldestFirst\" ? sql`ASC` : sql`DESC`;\n\tconst limit = query.limit === undefined ? sql`` : sql` LIMIT ${query.limit}`;\n\treturn sql`SELECT session_id, seq, id, lane, run_id, type, op_kind, timestamp, payload\n\t\tFROM records\n\t\tWHERE ${joinSqlFragments(predicates, \" AND \")}\n\t\tORDER BY seq ${direction}${limit}`.all<RecordRow>(db);\n}\n\nexport function readOpenOperationRows(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\tlane: string,\n\t_options: { limit?: number } = {},\n): RecordRow[] {\n\tconst laneRow = sql`SELECT open_operation_id FROM lanes WHERE session_id = ${sessionId} AND lane = ${lane}`.get<{\n\t\topen_operation_id: string | null;\n\t}>(db);\n\tif (!laneRow?.open_operation_id) return [];\n\n\tconst record = sql`SELECT session_id, seq, id, lane, run_id, type, op_kind, timestamp, payload\n\t\tFROM records\n\t\tWHERE session_id = ${sessionId}\n\t\t\tAND id = ${laneRow.open_operation_id}`.get<RecordRow>(db);\n\tif (!record) {\n\t\tthrow new SessionError(\"storage\", `Lane ${lane} points at missing open operation ${laneRow.open_operation_id}`);\n\t}\n\tif (record.lane !== lane || record.type !== \"operation_started\") {\n\t\tthrow new SessionError(\"storage\", `Lane ${lane} points at invalid open operation ${laneRow.open_operation_id}`);\n\t}\n\treturn [record];\n}\n"]}
|