@earendil-works/pi-session-backend-sqlite-node 0.84.0 → 0.84.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/CHANGELOG.md +9 -1
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +4 -3
  4. package/dist/index.js.map +1 -1
  5. package/dist/sqlite/branch-cache.d.ts.map +1 -1
  6. package/dist/sqlite/branch-cache.js +12 -13
  7. package/dist/sqlite/branch-cache.js.map +1 -1
  8. package/dist/sqlite/index.d.ts +1 -0
  9. package/dist/sqlite/index.d.ts.map +1 -1
  10. package/dist/sqlite/index.js +1 -0
  11. package/dist/sqlite/index.js.map +1 -1
  12. package/dist/sqlite/migrations/001_initial.sql +5 -2
  13. package/dist/sqlite/migrations.d.ts.map +1 -1
  14. package/dist/sqlite/migrations.js +5 -4
  15. package/dist/sqlite/migrations.js.map +1 -1
  16. package/dist/sqlite/repo.d.ts.map +1 -1
  17. package/dist/sqlite/repo.js +45 -26
  18. package/dist/sqlite/repo.js.map +1 -1
  19. package/dist/sqlite/search-backend.d.ts.map +1 -1
  20. package/dist/sqlite/search-backend.js +27 -29
  21. package/dist/sqlite/search-backend.js.map +1 -1
  22. package/dist/sqlite/sql.d.ts +18 -0
  23. package/dist/sqlite/sql.d.ts.map +1 -0
  24. package/dist/sqlite/sql.js +55 -0
  25. package/dist/sqlite/sql.js.map +1 -0
  26. package/dist/sqlite/storage/branch-entries.d.ts +6 -0
  27. package/dist/sqlite/storage/branch-entries.d.ts.map +1 -1
  28. package/dist/sqlite/storage/branch-entries.js +69 -60
  29. package/dist/sqlite/storage/branch-entries.js.map +1 -1
  30. package/dist/sqlite/storage/branch-tips.d.ts.map +1 -1
  31. package/dist/sqlite/storage/branch-tips.js +8 -11
  32. package/dist/sqlite/storage/branch-tips.js.map +1 -1
  33. package/dist/sqlite/storage/entries.d.ts +1 -0
  34. package/dist/sqlite/storage/entries.d.ts.map +1 -1
  35. package/dist/sqlite/storage/entries.js +15 -23
  36. package/dist/sqlite/storage/entries.js.map +1 -1
  37. package/dist/sqlite/storage/facts.d.ts +1 -0
  38. package/dist/sqlite/storage/facts.d.ts.map +1 -1
  39. package/dist/sqlite/storage/facts.js +25 -30
  40. package/dist/sqlite/storage/facts.js.map +1 -1
  41. package/dist/sqlite/storage/lanes.d.ts +1 -0
  42. package/dist/sqlite/storage/lanes.d.ts.map +1 -1
  43. package/dist/sqlite/storage/lanes.js +41 -52
  44. package/dist/sqlite/storage/lanes.js.map +1 -1
  45. package/dist/sqlite/storage/records.d.ts.map +1 -1
  46. package/dist/sqlite/storage/records.js +27 -47
  47. package/dist/sqlite/storage/records.js.map +1 -1
  48. package/dist/sqlite/storage/session-sequences.d.ts.map +1 -1
  49. package/dist/sqlite/storage/session-sequences.js +5 -6
  50. package/dist/sqlite/storage/session-sequences.js.map +1 -1
  51. package/dist/sqlite/storage/session-stats.d.ts.map +1 -1
  52. package/dist/sqlite/storage/session-stats.js +14 -19
  53. package/dist/sqlite/storage/session-stats.js.map +1 -1
  54. package/dist/sqlite/storage/sessions.d.ts.map +1 -1
  55. package/dist/sqlite/storage/sessions.js +35 -37
  56. package/dist/sqlite/storage/sessions.js.map +1 -1
  57. package/dist/sqlite/storage/writer-leases.d.ts.map +1 -1
  58. package/dist/sqlite/storage/writer-leases.js +18 -17
  59. package/dist/sqlite/storage/writer-leases.js.map +1 -1
  60. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"search-backend.js","sourceRoot":"","sources":["../../src/sqlite/search-backend.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SessionSearch, SessionSearchHit, SessionSearchOptions } from \"@earendil-works/pi-agent-core\";\nimport { getFileSystemResultOrThrow } from \"@earendil-works/pi-agent-core\";\nimport { applyMigrations } from \"./migrations.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 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 configureSqliteDatabase(db: SqliteDatabase): void {\n\tdb.exec(\"PRAGMA journal_mode=WAL\");\n\tdb.exec(\"PRAGMA synchronous=FULL\");\n\tdb.exec(\"PRAGMA busy_timeout=5000\");\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 !!db\n\t\t.prepare(\"SELECT 1 AS found FROM sqlite_master WHERE type = 'table' AND name = ? LIMIT 1\")\n\t\t.get<{ found: number }>(name);\n}\n\nfunction ensureSearchSchema(db: SqliteDatabase): void {\n\tconst ftsExists = tableExists(db, \"session_search_fts\");\n\tdb.exec(`\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`);\n\tif (!ftsExists) db.exec(\"INSERT INTO session_search_fts(session_search_fts) VALUES('rebuild')\");\n}\n\n/** SQLite FTS search over a co-located canonical session database. */\nclass SqliteSessionSearch implements SessionSearch<SqliteSessionMetadata> {\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(options: SessionSearchOptions): Promise<SessionSearchHit<SqliteSessionMetadata>[]> {\n\t\tconst text = options.text.trim();\n\t\tif (!text) return [];\n\t\tconst db = await this.openDatabase();\n\t\ttry {\n\t\t\tconst query = `\"${text.replaceAll('\"', '\"\"')}\"`;\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 session_search_fts MATCH ? AND (? IS NULL OR s.cwd = ?)\n\t\t\t\t\tORDER BY score`,\n\t\t\t\t)\n\t\t\t\t.all<SessionRow & { entry_id: string; timestamp: string; score: number }>(\n\t\t\t\t\tquery,\n\t\t\t\t\toptions.cwd ?? null,\n\t\t\t\t\toptions.cwd ?? null,\n\t\t\t\t);\n\t\t\tconst path = await this.getDatabasePath();\n\t\t\treturn rows.map((row) => ({\n\t\t\t\tmetadata: decodeSessionMetadata(row, path),\n\t\t\t\tentryId: row.entry_id,\n\t\t\t\ttimestamp: row.timestamp,\n\t\t\t\tscore: row.score,\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<SqliteSessionMetadata> {\n\treturn new SqliteSessionSearch(options);\n}\n"]}
1
+ {"version":3,"file":"search-backend.js","sourceRoot":"","sources":["../../src/sqlite/search-backend.ts"],"names":[],"mappings":"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","sourcesContent":["import type { SessionSearch, SessionSearchHit, SessionSearchOptions } from \"@earendil-works/pi-agent-core\";\nimport { getFileSystemResultOrThrow } 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 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 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 ensureSearchSchema(db: SqliteDatabase): void {\n\tconst ftsExists = tableExists(db, \"session_search_fts\");\n\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\tif (!ftsExists) sql`INSERT INTO session_search_fts(session_search_fts) VALUES('rebuild')`.exec(db);\n}\n\n/** SQLite FTS search over a co-located canonical session database. */\nclass SqliteSessionSearch implements SessionSearch<SqliteSessionMetadata> {\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(options: SessionSearchOptions): Promise<SessionSearchHit<SqliteSessionMetadata>[]> {\n\t\tconst text = options.text.trim();\n\t\tif (!text) return [];\n\t\tconst db = await this.openDatabase();\n\t\ttry {\n\t\t\tconst query = `\"${text.replaceAll('\"', '\"\"')}\"`;\n\t\t\tconst cwd = options.cwd ?? null;\n\t\t\tconst rows = sql`SELECT s.id, s.created_at, s.metadata, s.cwd, s.parent_session_id,\n\t\t\t\tname_fact.seq IS NOT NULL AS has_session_name,\n\t\t\t\tname_fact.value AS session_name,\n\t\t\t\tse.id AS entry_id, se.timestamp, bm25(session_search_fts) AS score\n\t\t\tFROM session_search_fts\n\t\t\tJOIN entries AS se ON se.rowid = session_search_fts.rowid\n\t\t\tJOIN sessions AS s ON s.id = se.session_id\n\t\t\tLEFT JOIN facts AS name_fact\n\t\t\t\tON name_fact.session_id = s.id\n\t\t\t\tAND name_fact.kind = 'name'\n\t\t\t\tAND name_fact.key IS NULL\n\t\t\t\tAND name_fact.seq = (\n\t\t\t\t\tSELECT MAX(f.seq)\n\t\t\t\t\tFROM facts AS f\n\t\t\t\t\tWHERE f.session_id = s.id AND f.kind = 'name' AND f.key IS NULL\n\t\t\t\t)\n\t\t\tWHERE session_search_fts MATCH ${query} AND (${cwd} IS NULL OR s.cwd = ${cwd})\n\t\t\tORDER BY score`.all<SessionRow & { entry_id: string; timestamp: string; score: number }>(db);\n\t\t\tconst path = await this.getDatabasePath();\n\t\t\treturn rows.map((row) => ({\n\t\t\t\tmetadata: decodeSessionMetadata(row, path),\n\t\t\t\tentryId: row.entry_id,\n\t\t\t\ttimestamp: row.timestamp,\n\t\t\t\tscore: row.score,\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<SqliteSessionMetadata> {\n\treturn new SqliteSessionSearch(options);\n}\n"]}
@@ -0,0 +1,18 @@
1
+ import type { SqliteDatabase, SqliteRunResult } from "./types.ts";
2
+ type SqlTemplateValue = unknown | SqlQuery;
3
+ /** A parameterized SQLite query produced by {@link sql}. */
4
+ export declare class SqlQuery {
5
+ readonly queryText: string;
6
+ readonly params: readonly unknown[];
7
+ constructor(queryText: string, params?: readonly unknown[]);
8
+ exec(db: SqliteDatabase): void;
9
+ run(db: SqliteDatabase): SqliteRunResult;
10
+ get<TRow extends object>(db: SqliteDatabase): TRow | undefined;
11
+ all<TRow extends object>(db: SqliteDatabase): TRow[];
12
+ }
13
+ /** Builds a parameterized query. Nested queries are inlined; other interpolations become `?` parameters. */
14
+ export declare function sql(strings: TemplateStringsArray, ...values: SqlTemplateValue[]): SqlQuery;
15
+ /** Joins trusted query fragments while preserving their parameter order. */
16
+ export declare function joinSqlFragments(fragments: readonly SqlQuery[], separator: string): SqlQuery;
17
+ export {};
18
+ //# sourceMappingURL=sql.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql.d.ts","sourceRoot":"","sources":["../../src/sqlite/sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElE,KAAK,gBAAgB,GAAG,OAAO,GAAG,QAAQ,CAAC;AAE3C,4DAA4D;AAC5D,qBAAa,QAAQ;IACpB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,CAAC;IAEpC,YAAY,SAAS,EAAE,MAAM,EAAE,MAAM,GAAE,SAAS,OAAO,EAAO,EAG7D;IAED,IAAI,CAAC,EAAE,EAAE,cAAc,GAAG,IAAI,CAG7B;IAED,GAAG,CAAC,EAAE,EAAE,cAAc,GAAG,eAAe,CAEvC;IAED,GAAG,CAAC,IAAI,SAAS,MAAM,EAAE,EAAE,EAAE,cAAc,GAAG,IAAI,GAAG,SAAS,CAE7D;IAED,GAAG,CAAC,IAAI,SAAS,MAAM,EAAE,EAAE,EAAE,cAAc,GAAG,IAAI,EAAE,CAEnD;CACD;AAED,4GAA4G;AAC5G,wBAAgB,GAAG,CAAC,OAAO,EAAE,oBAAoB,EAAE,GAAG,MAAM,EAAE,gBAAgB,EAAE,GAAG,QAAQ,CAe1F;AAED,4EAA4E;AAC5E,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,SAAS,QAAQ,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,QAAQ,CAU5F","sourcesContent":["import type { SqliteDatabase, SqliteRunResult } from \"./types.ts\";\n\ntype SqlTemplateValue = unknown | SqlQuery;\n\n/** A parameterized SQLite query produced by {@link sql}. */\nexport class SqlQuery {\n\treadonly queryText: string;\n\treadonly params: readonly unknown[];\n\n\tconstructor(queryText: string, params: readonly unknown[] = []) {\n\t\tthis.queryText = queryText;\n\t\tthis.params = params;\n\t}\n\n\texec(db: SqliteDatabase): void {\n\t\tif (this.params.length > 0) throw new TypeError(\"SQLite exec queries cannot have parameters\");\n\t\tdb.exec(this.queryText);\n\t}\n\n\trun(db: SqliteDatabase): SqliteRunResult {\n\t\treturn db.prepare(this.queryText).run(...this.params);\n\t}\n\n\tget<TRow extends object>(db: SqliteDatabase): TRow | undefined {\n\t\treturn db.prepare(this.queryText).get<TRow>(...this.params);\n\t}\n\n\tall<TRow extends object>(db: SqliteDatabase): TRow[] {\n\t\treturn db.prepare(this.queryText).all<TRow>(...this.params);\n\t}\n}\n\n/** Builds a parameterized query. Nested queries are inlined; other interpolations become `?` parameters. */\nexport function sql(strings: TemplateStringsArray, ...values: SqlTemplateValue[]): SqlQuery {\n\tlet queryText = strings[0] ?? \"\";\n\tconst params: unknown[] = [];\n\tfor (let index = 0; index < values.length; index++) {\n\t\tconst value = values[index];\n\t\tif (value instanceof SqlQuery) {\n\t\t\tqueryText += value.queryText;\n\t\t\tparams.push(...value.params);\n\t\t} else {\n\t\t\tqueryText += \"?\";\n\t\t\tparams.push(value);\n\t\t}\n\t\tqueryText += strings[index + 1] ?? \"\";\n\t}\n\treturn new SqlQuery(queryText, params);\n}\n\n/** Joins trusted query fragments while preserving their parameter order. */\nexport function joinSqlFragments(fragments: readonly SqlQuery[], separator: string): SqlQuery {\n\tlet queryText = \"\";\n\tconst params: unknown[] = [];\n\tfor (let index = 0; index < fragments.length; index++) {\n\t\tif (index > 0) queryText += separator;\n\t\tconst fragment = fragments[index]!;\n\t\tqueryText += fragment.queryText;\n\t\tparams.push(...fragment.params);\n\t}\n\treturn new SqlQuery(queryText, params);\n}\n"]}
@@ -0,0 +1,55 @@
1
+ /** A parameterized SQLite query produced by {@link sql}. */
2
+ export class SqlQuery {
3
+ queryText;
4
+ params;
5
+ constructor(queryText, params = []) {
6
+ this.queryText = queryText;
7
+ this.params = params;
8
+ }
9
+ exec(db) {
10
+ if (this.params.length > 0)
11
+ throw new TypeError("SQLite exec queries cannot have parameters");
12
+ db.exec(this.queryText);
13
+ }
14
+ run(db) {
15
+ return db.prepare(this.queryText).run(...this.params);
16
+ }
17
+ get(db) {
18
+ return db.prepare(this.queryText).get(...this.params);
19
+ }
20
+ all(db) {
21
+ return db.prepare(this.queryText).all(...this.params);
22
+ }
23
+ }
24
+ /** Builds a parameterized query. Nested queries are inlined; other interpolations become `?` parameters. */
25
+ export function sql(strings, ...values) {
26
+ let queryText = strings[0] ?? "";
27
+ const params = [];
28
+ for (let index = 0; index < values.length; index++) {
29
+ const value = values[index];
30
+ if (value instanceof SqlQuery) {
31
+ queryText += value.queryText;
32
+ params.push(...value.params);
33
+ }
34
+ else {
35
+ queryText += "?";
36
+ params.push(value);
37
+ }
38
+ queryText += strings[index + 1] ?? "";
39
+ }
40
+ return new SqlQuery(queryText, params);
41
+ }
42
+ /** Joins trusted query fragments while preserving their parameter order. */
43
+ export function joinSqlFragments(fragments, separator) {
44
+ let queryText = "";
45
+ const params = [];
46
+ for (let index = 0; index < fragments.length; index++) {
47
+ if (index > 0)
48
+ queryText += separator;
49
+ const fragment = fragments[index];
50
+ queryText += fragment.queryText;
51
+ params.push(...fragment.params);
52
+ }
53
+ return new SqlQuery(queryText, params);
54
+ }
55
+ //# sourceMappingURL=sql.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql.js","sourceRoot":"","sources":["../../src/sqlite/sql.ts"],"names":[],"mappings":"AAIA,4DAA4D;AAC5D,MAAM,OAAO,QAAQ;IACX,SAAS,CAAS;IAClB,MAAM,CAAqB;IAEpC,YAAY,SAAiB,EAAE,MAAM,GAAuB,EAAE,EAAE;QAC/D,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IAAA,CACrB;IAED,IAAI,CAAC,EAAkB,EAAQ;QAC9B,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC;YAAE,MAAM,IAAI,SAAS,CAAC,4CAA4C,CAAC,CAAC;QAC9F,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAAA,CACxB;IAED,GAAG,CAAC,EAAkB,EAAmB;QACxC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;IAAA,CACtD;IAED,GAAG,CAAsB,EAAkB,EAAoB;QAC9D,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,CAAO,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;IAAA,CAC5D;IAED,GAAG,CAAsB,EAAkB,EAAU;QACpD,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,CAAO,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;IAAA,CAC5D;CACD;AAED,4GAA4G;AAC5G,MAAM,UAAU,GAAG,CAAC,OAA6B,EAAE,GAAG,MAA0B,EAAY;IAC3F,IAAI,SAAS,GAAG,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACjC,MAAM,MAAM,GAAc,EAAE,CAAC;IAC7B,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;QACpD,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5B,IAAI,KAAK,YAAY,QAAQ,EAAE,CAAC;YAC/B,SAAS,IAAI,KAAK,CAAC,SAAS,CAAC;YAC7B,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;QAC9B,CAAC;aAAM,CAAC;YACP,SAAS,IAAI,GAAG,CAAC;YACjB,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACpB,CAAC;QACD,SAAS,IAAI,OAAO,CAAC,KAAK,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IACvC,CAAC;IACD,OAAO,IAAI,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;AAAA,CACvC;AAED,4EAA4E;AAC5E,MAAM,UAAU,gBAAgB,CAAC,SAA8B,EAAE,SAAiB,EAAY;IAC7F,IAAI,SAAS,GAAG,EAAE,CAAC;IACnB,MAAM,MAAM,GAAc,EAAE,CAAC;IAC7B,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;QACvD,IAAI,KAAK,GAAG,CAAC;YAAE,SAAS,IAAI,SAAS,CAAC;QACtC,MAAM,QAAQ,GAAG,SAAS,CAAC,KAAK,CAAE,CAAC;QACnC,SAAS,IAAI,QAAQ,CAAC,SAAS,CAAC;QAChC,MAAM,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;IACjC,CAAC;IACD,OAAO,IAAI,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;AAAA,CACvC","sourcesContent":["import type { SqliteDatabase, SqliteRunResult } from \"./types.ts\";\n\ntype SqlTemplateValue = unknown | SqlQuery;\n\n/** A parameterized SQLite query produced by {@link sql}. */\nexport class SqlQuery {\n\treadonly queryText: string;\n\treadonly params: readonly unknown[];\n\n\tconstructor(queryText: string, params: readonly unknown[] = []) {\n\t\tthis.queryText = queryText;\n\t\tthis.params = params;\n\t}\n\n\texec(db: SqliteDatabase): void {\n\t\tif (this.params.length > 0) throw new TypeError(\"SQLite exec queries cannot have parameters\");\n\t\tdb.exec(this.queryText);\n\t}\n\n\trun(db: SqliteDatabase): SqliteRunResult {\n\t\treturn db.prepare(this.queryText).run(...this.params);\n\t}\n\n\tget<TRow extends object>(db: SqliteDatabase): TRow | undefined {\n\t\treturn db.prepare(this.queryText).get<TRow>(...this.params);\n\t}\n\n\tall<TRow extends object>(db: SqliteDatabase): TRow[] {\n\t\treturn db.prepare(this.queryText).all<TRow>(...this.params);\n\t}\n}\n\n/** Builds a parameterized query. Nested queries are inlined; other interpolations become `?` parameters. */\nexport function sql(strings: TemplateStringsArray, ...values: SqlTemplateValue[]): SqlQuery {\n\tlet queryText = strings[0] ?? \"\";\n\tconst params: unknown[] = [];\n\tfor (let index = 0; index < values.length; index++) {\n\t\tconst value = values[index];\n\t\tif (value instanceof SqlQuery) {\n\t\t\tqueryText += value.queryText;\n\t\t\tparams.push(...value.params);\n\t\t} else {\n\t\t\tqueryText += \"?\";\n\t\t\tparams.push(value);\n\t\t}\n\t\tqueryText += strings[index + 1] ?? \"\";\n\t}\n\treturn new SqlQuery(queryText, params);\n}\n\n/** Joins trusted query fragments while preserving their parameter order. */\nexport function joinSqlFragments(fragments: readonly SqlQuery[], separator: string): SqlQuery {\n\tlet queryText = \"\";\n\tconst params: unknown[] = [];\n\tfor (let index = 0; index < fragments.length; index++) {\n\t\tif (index > 0) queryText += separator;\n\t\tconst fragment = fragments[index]!;\n\t\tqueryText += fragment.queryText;\n\t\tparams.push(...fragment.params);\n\t}\n\treturn new SqlQuery(queryText, params);\n}\n"]}
@@ -15,9 +15,15 @@ export interface CachedBranchEntryRow {
15
15
  payload: string;
16
16
  }
17
17
  export interface CachedBranchQuery {
18
+ type?: Entry["type"];
19
+ customType?: string;
18
20
  stopAtType?: Entry["type"];
19
21
  stopAtId?: string;
22
+ cursor?: {
23
+ afterSeq: number;
24
+ };
20
25
  order?: "newestFirst" | "oldestFirst";
26
+ limit?: number;
21
27
  }
22
28
  export declare function readCachedBranch(db: SqliteDatabase, sessionId: string, leafId: string): {
23
29
  branchId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"branch-entries.d.ts","sourceRoot":"","sources":["../../../src/sqlite/storage/branch-entries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,+BAA+B,CAAC;AAC3D,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,UAAU,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC3B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,aAAa,GAAG,aAAa,CAAC;CACtC;AAED,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,0BAuCxB;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,QAOzB;AAED,wBAAgB,0BAA0B,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAiBjH;AAED,wBAAgB,yBAAyB,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;;;cAW/F;AAED,wBAAgB,2BAA2B,CAC1C,EAAE,EAAE,cAAc,EAClB,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,EACtB,cAAc,EAAE,MAAM,EACtB,UAAU,EAAE,MAAM,QAQlB","sourcesContent":["import type { Entry } from \"@earendil-works/pi-agent-core\";\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: string;\n\tpayload: string;\n}\n\nexport interface CachedBranchQuery {\n\tstopAtType?: Entry[\"type\"];\n\tstopAtId?: string;\n\torder?: \"newestFirst\" | \"oldestFirst\";\n}\n\nexport function readCachedBranch(db: SqliteDatabase, sessionId: string, leafId: string) {\n\tconst membership = db\n\t\t.prepare(\n\t\t\t\"SELECT branch_id, entry_seq FROM branch_entries WHERE session_id = ? AND entry_id = ? ORDER BY branch_id LIMIT 1\",\n\t\t)\n\t\t.get<{ branch_id: string; entry_seq: number }>(sessionId, leafId);\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 boundaryParams: unknown[] = [sessionId, branch.branchId, branch.leafSeq];\n\tconst stopPredicates: string[] = [];\n\tif (query.stopAtType !== undefined) {\n\t\tstopPredicates.push(\"stop_entry.type = ?\");\n\t\tboundaryParams.push(query.stopAtType);\n\t}\n\tif (query.stopAtId !== undefined) {\n\t\tstopPredicates.push(\"stop.entry_id = ?\");\n\t\tboundaryParams.push(query.stopAtId);\n\t}\n\n\tconst boundary = stopPredicates.length\n\t\t? `WITH boundary AS (\n\t\t\tSELECT ${oldestFirst ? \"MIN\" : \"MAX\"}(stop.entry_seq) AS entry_seq\n\t\t\tFROM branch_entries AS stop\n\t\t\tJOIN entries AS stop_entry\n\t\t\t\tON stop_entry.session_id = stop.session_id AND stop_entry.id = stop.entry_id\n\t\t\tWHERE stop.session_id = ? AND stop.branch_id = ? AND stop.entry_seq <= ?\n\t\t\t\tAND (${stopPredicates.join(\" OR \")})\n\t\t)`\n\t\t: \"\";\n\tconst range = stopPredicates.length\n\t\t? `AND b.entry_seq ${oldestFirst ? \"<=\" : \">=\"} COALESCE(\n\t\t\t(SELECT entry_seq FROM boundary), ${oldestFirst ? branch.leafSeq : 0}\n\t\t)`\n\t\t: \"\";\n\tconst sql = `${boundary}\n\t\tSELECT 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 b.session_id = ? AND b.branch_id = ? AND b.entry_seq <= ?\n\t\t\t${range}\n\t\tORDER BY b.entry_seq ${oldestFirst ? \"ASC\" : \"DESC\"}`;\n\n\tconst params = [...(stopPredicates.length === 0 ? [] : boundaryParams), sessionId, branch.branchId, branch.leafSeq];\n\treturn db.prepare(sql).all<CachedBranchEntryRow>(...params);\n}\n\nexport function deleteBranchEntries(db: SqliteDatabase, sessionId: string) {\n\tdb.prepare(\"DELETE FROM branch_entries WHERE session_id = ?\").run(sessionId);\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\tdb.prepare(\n\t\t`INSERT INTO branch_entries\n\t\t\t(session_id, branch_id, entry_id, entry_seq, entry_type, custom_type)\n\t\t\tVALUES (?, ?, ?, ?, ?, ?)`,\n\t).run(sessionId, branchId, entryId, entrySeq, entryType, customType);\n}\n\nexport function insertBranchEntriesForPath(db: SqliteDatabase, sessionId: string, branchId: string, leafId: string) {\n\tdb.prepare(\n\t\t`WITH RECURSIVE path(id, entry_seq, parent_id, type, custom_type) AS (\n\t\t\t\tSELECT id, seq, parent_id, type,\n\t\t\t\t\tCASE WHEN type = 'custom' THEN json_extract(payload, '$.customType') ELSE NULL END\n\t\t\t\tFROM entries\n\t\t\t\tWHERE session_id = ? AND id = ?\n\t\t\t\tUNION ALL\n\t\t\t\tSELECT parent.id, parent.seq, parent.parent_id, parent.type,\n\t\t\t\t\tCASE WHEN parent.type = 'custom' THEN json_extract(parent.payload, '$.customType') ELSE NULL END\n\t\t\t\tFROM entries AS parent\n\t\t\t\tJOIN path AS child ON child.parent_id = parent.id\n\t\t\t\tWHERE parent.session_id = ?\n\t\t\t)\n\t\t\tINSERT INTO branch_entries (session_id, branch_id, entry_id, entry_seq, entry_type, custom_type)\n\t\t\tSELECT ?, ?, id, entry_seq, type, custom_type FROM path`,\n\t).run(sessionId, leafId, sessionId, sessionId, branchId);\n}\n\nexport function readBranchContainingEntry(db: SqliteDatabase, sessionId: string, entryId: string) {\n\tconst row = db\n\t\t.prepare(\n\t\t\t`SELECT b.branch_id, b.entry_seq\n\t\t\tFROM branch_entries AS b\n\t\t\tWHERE b.session_id = ? AND b.entry_id = ?\n\t\t\tORDER BY b.branch_id\n\t\t\tLIMIT 1`,\n\t\t)\n\t\t.get<{ branch_id: string; entry_seq: number }>(sessionId, entryId);\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\tdb.prepare(\n\t\t`INSERT INTO branch_entries (session_id, branch_id, entry_id, entry_seq, entry_type, custom_type)\n\t\t\tSELECT session_id, ?, entry_id, entry_seq, entry_type, custom_type\n\t\t\tFROM branch_entries\n\t\t\tWHERE session_id = ? AND branch_id = ? AND entry_seq <= ?`,\n\t).run(targetBranchId, sessionId, sourceBranchId, throughSeq);\n}\n"]}
1
+ {"version":3,"file":"branch-entries.d.ts","sourceRoot":"","sources":["../../../src/sqlite/storage/branch-entries.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,+BAA+B,CAAC;AAE3D,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;AAED,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,0BA4CxB;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;AAED,wBAAgB,0BAA0B,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,QAejH;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 } 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: string;\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\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`WITH boundary AS (\n\t\t\t\tSELECT ${aggregate}(stop.entry_seq) AS 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\t\t\t)`;\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(sql`b.entry_seq ${boundaryComparison} COALESCE(\n\t\t\t(SELECT entry_seq FROM 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`${boundary}\n\t\tSELECT 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\nexport function insertBranchEntriesForPath(db: SqliteDatabase, sessionId: string, branchId: string, leafId: string) {\n\tsql`WITH RECURSIVE path(id, entry_seq, parent_id, type, custom_type) AS (\n\t\t\tSELECT id, seq, parent_id, type,\n\t\t\t\tCASE WHEN type = 'custom' THEN json_extract(payload, '$.customType') ELSE NULL END\n\t\t\tFROM entries\n\t\t\tWHERE session_id = ${sessionId} AND id = ${leafId}\n\t\t\tUNION ALL\n\t\t\tSELECT parent.id, parent.seq, parent.parent_id, parent.type,\n\t\t\t\tCASE WHEN parent.type = 'custom' THEN json_extract(parent.payload, '$.customType') ELSE NULL END\n\t\t\tFROM entries AS parent\n\t\t\tJOIN path AS child ON child.parent_id = parent.id\n\t\t\tWHERE parent.session_id = ${sessionId}\n\t\t)\n\t\tINSERT INTO branch_entries (session_id, branch_id, entry_id, entry_seq, entry_type, custom_type)\n\t\tSELECT ${sessionId}, ${branchId}, id, entry_seq, type, custom_type FROM path`.run(db);\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,86 +1,95 @@
1
+ import { joinSqlFragments, sql } from "../sql.js";
1
2
  export function readCachedBranch(db, sessionId, leafId) {
2
- const membership = db
3
- .prepare("SELECT branch_id, entry_seq FROM branch_entries WHERE session_id = ? AND entry_id = ? ORDER BY branch_id LIMIT 1")
4
- .get(sessionId, leafId);
3
+ const membership = sql `SELECT branch_id, entry_seq
4
+ FROM branch_entries
5
+ WHERE session_id = ${sessionId} AND entry_id = ${leafId}
6
+ ORDER BY branch_id
7
+ LIMIT 1`.get(db);
5
8
  if (!membership)
6
9
  return undefined;
7
10
  return { branchId: membership.branch_id, leafSeq: membership.entry_seq };
8
11
  }
9
12
  export function queryCachedBranchRows(db, sessionId, branch, query) {
10
13
  const oldestFirst = query.order === "oldestFirst";
11
- const boundaryParams = [sessionId, branch.branchId, branch.leafSeq];
12
14
  const stopPredicates = [];
13
- if (query.stopAtType !== undefined) {
14
- stopPredicates.push("stop_entry.type = ?");
15
- boundaryParams.push(query.stopAtType);
16
- }
17
- if (query.stopAtId !== undefined) {
18
- stopPredicates.push("stop.entry_id = ?");
19
- boundaryParams.push(query.stopAtId);
20
- }
21
- const boundary = stopPredicates.length
22
- ? `WITH boundary AS (
23
- SELECT ${oldestFirst ? "MIN" : "MAX"}(stop.entry_seq) AS entry_seq
24
- FROM branch_entries AS stop
25
- JOIN entries AS stop_entry
26
- ON stop_entry.session_id = stop.session_id AND stop_entry.id = stop.entry_id
27
- WHERE stop.session_id = ? AND stop.branch_id = ? AND stop.entry_seq <= ?
28
- AND (${stopPredicates.join(" OR ")})
29
- )`
30
- : "";
31
- const range = stopPredicates.length
32
- ? `AND b.entry_seq ${oldestFirst ? "<=" : ">="} COALESCE(
15
+ if (query.stopAtType !== undefined)
16
+ stopPredicates.push(sql `stop.entry_type = ${query.stopAtType}`);
17
+ if (query.stopAtId !== undefined)
18
+ stopPredicates.push(sql `stop.entry_id = ${query.stopAtId}`);
19
+ const aggregate = oldestFirst ? sql `MIN` : sql `MAX`;
20
+ const boundaryComparison = oldestFirst ? sql `<=` : sql `>=`;
21
+ const cursorComparison = oldestFirst ? sql `>` : sql `<`;
22
+ const direction = oldestFirst ? sql `ASC` : sql `DESC`;
23
+ const boundary = stopPredicates.length === 0
24
+ ? sql ``
25
+ : sql `WITH boundary AS (
26
+ SELECT ${aggregate}(stop.entry_seq) AS 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
+ )`;
33
+ const predicates = [
34
+ sql `b.session_id = ${sessionId}`,
35
+ sql `b.branch_id = ${branch.branchId}`,
36
+ sql `b.entry_seq <= ${branch.leafSeq}`,
37
+ ];
38
+ if (stopPredicates.length > 0) {
39
+ predicates.push(sql `b.entry_seq ${boundaryComparison} COALESCE(
33
40
  (SELECT entry_seq FROM boundary), ${oldestFirst ? branch.leafSeq : 0}
34
- )`
35
- : "";
36
- const sql = `${boundary}
41
+ )`);
42
+ }
43
+ if (query.cursor !== undefined)
44
+ predicates.push(sql `b.entry_seq ${cursorComparison} ${query.cursor.afterSeq}`);
45
+ if (query.type !== undefined)
46
+ predicates.push(sql `b.entry_type = ${query.type}`);
47
+ if (query.customType !== undefined)
48
+ predicates.push(sql `b.custom_type = ${query.customType}`);
49
+ const limit = query.limit === undefined ? sql `` : sql ` LIMIT ${query.limit}`;
50
+ return sql `${boundary}
37
51
  SELECT e.session_id, e.id, e.seq AS entry_seq, e.parent_id, e.type, e.timestamp, e.payload
38
52
  FROM branch_entries AS b
39
53
  JOIN entries AS e ON e.session_id = b.session_id AND e.id = b.entry_id
40
- WHERE b.session_id = ? AND b.branch_id = ? AND b.entry_seq <= ?
41
- ${range}
42
- ORDER BY b.entry_seq ${oldestFirst ? "ASC" : "DESC"}`;
43
- const params = [...(stopPredicates.length === 0 ? [] : boundaryParams), sessionId, branch.branchId, branch.leafSeq];
44
- return db.prepare(sql).all(...params);
54
+ WHERE ${joinSqlFragments(predicates, " AND ")}
55
+ ORDER BY b.entry_seq ${direction}${limit}`.all(db);
45
56
  }
46
57
  export function deleteBranchEntries(db, sessionId) {
47
- db.prepare("DELETE FROM branch_entries WHERE session_id = ?").run(sessionId);
58
+ sql `DELETE FROM branch_entries WHERE session_id = ${sessionId}`.run(db);
48
59
  }
49
60
  export function insertBranchEntry(db, sessionId, branchId, entryId, entrySeq, entryType, customType) {
50
- db.prepare(`INSERT INTO branch_entries
61
+ sql `INSERT INTO branch_entries
51
62
  (session_id, branch_id, entry_id, entry_seq, entry_type, custom_type)
52
- VALUES (?, ?, ?, ?, ?, ?)`).run(sessionId, branchId, entryId, entrySeq, entryType, customType);
63
+ VALUES (${sessionId}, ${branchId}, ${entryId}, ${entrySeq}, ${entryType}, ${customType})`.run(db);
53
64
  }
54
65
  export function insertBranchEntriesForPath(db, sessionId, branchId, leafId) {
55
- db.prepare(`WITH RECURSIVE path(id, entry_seq, parent_id, type, custom_type) AS (
56
- SELECT id, seq, parent_id, type,
57
- CASE WHEN type = 'custom' THEN json_extract(payload, '$.customType') ELSE NULL END
58
- FROM entries
59
- WHERE session_id = ? AND id = ?
60
- UNION ALL
61
- SELECT parent.id, parent.seq, parent.parent_id, parent.type,
62
- CASE WHEN parent.type = 'custom' THEN json_extract(parent.payload, '$.customType') ELSE NULL END
63
- FROM entries AS parent
64
- JOIN path AS child ON child.parent_id = parent.id
65
- WHERE parent.session_id = ?
66
- )
67
- INSERT INTO branch_entries (session_id, branch_id, entry_id, entry_seq, entry_type, custom_type)
68
- SELECT ?, ?, id, entry_seq, type, custom_type FROM path`).run(sessionId, leafId, sessionId, sessionId, branchId);
66
+ sql `WITH RECURSIVE path(id, entry_seq, parent_id, type, custom_type) AS (
67
+ SELECT id, seq, parent_id, type,
68
+ CASE WHEN type = 'custom' THEN json_extract(payload, '$.customType') ELSE NULL END
69
+ FROM entries
70
+ WHERE session_id = ${sessionId} AND id = ${leafId}
71
+ UNION ALL
72
+ SELECT parent.id, parent.seq, parent.parent_id, parent.type,
73
+ CASE WHEN parent.type = 'custom' THEN json_extract(parent.payload, '$.customType') ELSE NULL END
74
+ FROM entries AS parent
75
+ JOIN path AS child ON child.parent_id = parent.id
76
+ WHERE parent.session_id = ${sessionId}
77
+ )
78
+ INSERT INTO branch_entries (session_id, branch_id, entry_id, entry_seq, entry_type, custom_type)
79
+ SELECT ${sessionId}, ${branchId}, id, entry_seq, type, custom_type FROM path`.run(db);
69
80
  }
70
81
  export function readBranchContainingEntry(db, sessionId, entryId) {
71
- const row = db
72
- .prepare(`SELECT b.branch_id, b.entry_seq
73
- FROM branch_entries AS b
74
- WHERE b.session_id = ? AND b.entry_id = ?
75
- ORDER BY b.branch_id
76
- LIMIT 1`)
77
- .get(sessionId, entryId);
82
+ const row = sql `SELECT b.branch_id, b.entry_seq
83
+ FROM branch_entries AS b
84
+ WHERE b.session_id = ${sessionId} AND b.entry_id = ${entryId}
85
+ ORDER BY b.branch_id
86
+ LIMIT 1`.get(db);
78
87
  return row === undefined ? undefined : { branchId: row.branch_id, entrySeq: row.entry_seq };
79
88
  }
80
89
  export function copyBranchEntriesThroughSeq(db, sessionId, targetBranchId, sourceBranchId, throughSeq) {
81
- db.prepare(`INSERT INTO branch_entries (session_id, branch_id, entry_id, entry_seq, entry_type, custom_type)
82
- SELECT session_id, ?, entry_id, entry_seq, entry_type, custom_type
83
- FROM branch_entries
84
- WHERE session_id = ? AND branch_id = ? AND entry_seq <= ?`).run(targetBranchId, sessionId, sourceBranchId, throughSeq);
90
+ sql `INSERT INTO branch_entries (session_id, branch_id, entry_id, entry_seq, entry_type, custom_type)
91
+ SELECT session_id, ${targetBranchId}, entry_id, entry_seq, entry_type, custom_type
92
+ FROM branch_entries
93
+ WHERE session_id = ${sessionId} AND branch_id = ${sourceBranchId} AND entry_seq <= ${throughSeq}`.run(db);
85
94
  }
86
95
  //# sourceMappingURL=branch-entries.js.map
@@ -1 +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 } from \"@earendil-works/pi-agent-core\";\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: string;\n\tpayload: string;\n}\n\nexport interface CachedBranchQuery {\n\tstopAtType?: Entry[\"type\"];\n\tstopAtId?: string;\n\torder?: \"newestFirst\" | \"oldestFirst\";\n}\n\nexport function readCachedBranch(db: SqliteDatabase, sessionId: string, leafId: string) {\n\tconst membership = db\n\t\t.prepare(\n\t\t\t\"SELECT branch_id, entry_seq FROM branch_entries WHERE session_id = ? AND entry_id = ? ORDER BY branch_id LIMIT 1\",\n\t\t)\n\t\t.get<{ branch_id: string; entry_seq: number }>(sessionId, leafId);\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 boundaryParams: unknown[] = [sessionId, branch.branchId, branch.leafSeq];\n\tconst stopPredicates: string[] = [];\n\tif (query.stopAtType !== undefined) {\n\t\tstopPredicates.push(\"stop_entry.type = ?\");\n\t\tboundaryParams.push(query.stopAtType);\n\t}\n\tif (query.stopAtId !== undefined) {\n\t\tstopPredicates.push(\"stop.entry_id = ?\");\n\t\tboundaryParams.push(query.stopAtId);\n\t}\n\n\tconst boundary = stopPredicates.length\n\t\t? `WITH boundary AS (\n\t\t\tSELECT ${oldestFirst ? \"MIN\" : \"MAX\"}(stop.entry_seq) AS entry_seq\n\t\t\tFROM branch_entries AS stop\n\t\t\tJOIN entries AS stop_entry\n\t\t\t\tON stop_entry.session_id = stop.session_id AND stop_entry.id = stop.entry_id\n\t\t\tWHERE stop.session_id = ? AND stop.branch_id = ? AND stop.entry_seq <= ?\n\t\t\t\tAND (${stopPredicates.join(\" OR \")})\n\t\t)`\n\t\t: \"\";\n\tconst range = stopPredicates.length\n\t\t? `AND b.entry_seq ${oldestFirst ? \"<=\" : \">=\"} COALESCE(\n\t\t\t(SELECT entry_seq FROM boundary), ${oldestFirst ? branch.leafSeq : 0}\n\t\t)`\n\t\t: \"\";\n\tconst sql = `${boundary}\n\t\tSELECT 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 b.session_id = ? AND b.branch_id = ? AND b.entry_seq <= ?\n\t\t\t${range}\n\t\tORDER BY b.entry_seq ${oldestFirst ? \"ASC\" : \"DESC\"}`;\n\n\tconst params = [...(stopPredicates.length === 0 ? [] : boundaryParams), sessionId, branch.branchId, branch.leafSeq];\n\treturn db.prepare(sql).all<CachedBranchEntryRow>(...params);\n}\n\nexport function deleteBranchEntries(db: SqliteDatabase, sessionId: string) {\n\tdb.prepare(\"DELETE FROM branch_entries WHERE session_id = ?\").run(sessionId);\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\tdb.prepare(\n\t\t`INSERT INTO branch_entries\n\t\t\t(session_id, branch_id, entry_id, entry_seq, entry_type, custom_type)\n\t\t\tVALUES (?, ?, ?, ?, ?, ?)`,\n\t).run(sessionId, branchId, entryId, entrySeq, entryType, customType);\n}\n\nexport function insertBranchEntriesForPath(db: SqliteDatabase, sessionId: string, branchId: string, leafId: string) {\n\tdb.prepare(\n\t\t`WITH RECURSIVE path(id, entry_seq, parent_id, type, custom_type) AS (\n\t\t\t\tSELECT id, seq, parent_id, type,\n\t\t\t\t\tCASE WHEN type = 'custom' THEN json_extract(payload, '$.customType') ELSE NULL END\n\t\t\t\tFROM entries\n\t\t\t\tWHERE session_id = ? AND id = ?\n\t\t\t\tUNION ALL\n\t\t\t\tSELECT parent.id, parent.seq, parent.parent_id, parent.type,\n\t\t\t\t\tCASE WHEN parent.type = 'custom' THEN json_extract(parent.payload, '$.customType') ELSE NULL END\n\t\t\t\tFROM entries AS parent\n\t\t\t\tJOIN path AS child ON child.parent_id = parent.id\n\t\t\t\tWHERE parent.session_id = ?\n\t\t\t)\n\t\t\tINSERT INTO branch_entries (session_id, branch_id, entry_id, entry_seq, entry_type, custom_type)\n\t\t\tSELECT ?, ?, id, entry_seq, type, custom_type FROM path`,\n\t).run(sessionId, leafId, sessionId, sessionId, branchId);\n}\n\nexport function readBranchContainingEntry(db: SqliteDatabase, sessionId: string, entryId: string) {\n\tconst row = db\n\t\t.prepare(\n\t\t\t`SELECT b.branch_id, b.entry_seq\n\t\t\tFROM branch_entries AS b\n\t\t\tWHERE b.session_id = ? AND b.entry_id = ?\n\t\t\tORDER BY b.branch_id\n\t\t\tLIMIT 1`,\n\t\t)\n\t\t.get<{ branch_id: string; entry_seq: number }>(sessionId, entryId);\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\tdb.prepare(\n\t\t`INSERT INTO branch_entries (session_id, branch_id, entry_id, entry_seq, entry_type, custom_type)\n\t\t\tSELECT session_id, ?, entry_id, entry_seq, entry_type, custom_type\n\t\t\tFROM branch_entries\n\t\t\tWHERE session_id = ? AND branch_id = ? AND entry_seq <= ?`,\n\t).run(targetBranchId, sessionId, sourceBranchId, throughSeq);\n}\n"]}
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 } 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: string;\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\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`WITH boundary AS (\n\t\t\t\tSELECT ${aggregate}(stop.entry_seq) AS 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\t\t\t)`;\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(sql`b.entry_seq ${boundaryComparison} COALESCE(\n\t\t\t(SELECT entry_seq FROM 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`${boundary}\n\t\tSELECT 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\nexport function insertBranchEntriesForPath(db: SqliteDatabase, sessionId: string, branchId: string, leafId: string) {\n\tsql`WITH RECURSIVE path(id, entry_seq, parent_id, type, custom_type) AS (\n\t\t\tSELECT id, seq, parent_id, type,\n\t\t\t\tCASE WHEN type = 'custom' THEN json_extract(payload, '$.customType') ELSE NULL END\n\t\t\tFROM entries\n\t\t\tWHERE session_id = ${sessionId} AND id = ${leafId}\n\t\t\tUNION ALL\n\t\t\tSELECT parent.id, parent.seq, parent.parent_id, parent.type,\n\t\t\t\tCASE WHEN parent.type = 'custom' THEN json_extract(parent.payload, '$.customType') ELSE NULL END\n\t\t\tFROM entries AS parent\n\t\t\tJOIN path AS child ON child.parent_id = parent.id\n\t\t\tWHERE parent.session_id = ${sessionId}\n\t\t)\n\t\tINSERT INTO branch_entries (session_id, branch_id, entry_id, entry_seq, entry_type, custom_type)\n\t\tSELECT ${sessionId}, ${branchId}, id, entry_seq, type, custom_type FROM path`.run(db);\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 +1 @@
1
- {"version":3,"file":"branch-tips.d.ts","sourceRoot":"","sources":["../../../src/sqlite/storage/branch-tips.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,YAKrE;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,QAMrG;AAED,wBAAgB,eAAe,CAC9B,EAAE,EAAE,cAAc,EAClB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,WAMhB;AAED,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,QAErE","sourcesContent":["import type { SqliteDatabase } from \"../types.ts\";\n\nexport function readBranchTipIds(db: SqliteDatabase, sessionId: string) {\n\treturn db\n\t\t.prepare(\"SELECT tip_id FROM branch_tips WHERE session_id = ? ORDER BY tip_id\")\n\t\t.all<{ tip_id: string }>(sessionId)\n\t\t.map((row) => row.tip_id);\n}\n\nexport function readBranchTipBranchId(db: SqliteDatabase, sessionId: string, tipId: string) {\n\tconst tip = db\n\t\t.prepare(\"SELECT branch_id FROM branch_tips WHERE session_id = ? AND tip_id = ?\")\n\t\t.get<{ branch_id: string }>(sessionId, tipId);\n\treturn tip?.branch_id;\n}\n\nexport function insertBranchTip(db: SqliteDatabase, sessionId: string, tipId: string, branchId: string) {\n\tdb.prepare(\"INSERT INTO branch_tips (session_id, tip_id, branch_id) VALUES (?, ?, ?)\").run(\n\t\tsessionId,\n\t\ttipId,\n\t\tbranchId,\n\t);\n}\n\nexport function updateBranchTip(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\tbranchId: string,\n\toldTipId: string,\n\tnewTipId: string,\n) {\n\tconst result = db\n\t\t.prepare(\"UPDATE branch_tips SET tip_id = ? WHERE session_id = ? AND branch_id = ? AND tip_id = ?\")\n\t\t.run(newTipId, sessionId, branchId, oldTipId);\n\treturn result.changes === 1;\n}\n\nexport function deleteBranchTips(db: SqliteDatabase, sessionId: string) {\n\tdb.prepare(\"DELETE FROM branch_tips WHERE session_id = ?\").run(sessionId);\n}\n"]}
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,25 +1,22 @@
1
+ import { sql } from "../sql.js";
1
2
  export function readBranchTipIds(db, sessionId) {
2
- return db
3
- .prepare("SELECT tip_id FROM branch_tips WHERE session_id = ? ORDER BY tip_id")
4
- .all(sessionId)
3
+ return sql `SELECT tip_id FROM branch_tips WHERE session_id = ${sessionId} ORDER BY tip_id`
4
+ .all(db)
5
5
  .map((row) => row.tip_id);
6
6
  }
7
7
  export function readBranchTipBranchId(db, sessionId, tipId) {
8
- const tip = db
9
- .prepare("SELECT branch_id FROM branch_tips WHERE session_id = ? AND tip_id = ?")
10
- .get(sessionId, tipId);
8
+ const tip = sql `SELECT branch_id FROM branch_tips WHERE session_id = ${sessionId} AND tip_id = ${tipId}`.get(db);
11
9
  return tip?.branch_id;
12
10
  }
13
11
  export function insertBranchTip(db, sessionId, tipId, branchId) {
14
- db.prepare("INSERT INTO branch_tips (session_id, tip_id, branch_id) VALUES (?, ?, ?)").run(sessionId, tipId, branchId);
12
+ sql `INSERT INTO branch_tips (session_id, tip_id, branch_id) VALUES (${sessionId}, ${tipId}, ${branchId})`.run(db);
15
13
  }
16
14
  export function updateBranchTip(db, sessionId, branchId, oldTipId, newTipId) {
17
- const result = db
18
- .prepare("UPDATE branch_tips SET tip_id = ? WHERE session_id = ? AND branch_id = ? AND tip_id = ?")
19
- .run(newTipId, sessionId, branchId, oldTipId);
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);
20
17
  return result.changes === 1;
21
18
  }
22
19
  export function deleteBranchTips(db, sessionId) {
23
- db.prepare("DELETE FROM branch_tips WHERE session_id = ?").run(sessionId);
20
+ sql `DELETE FROM branch_tips WHERE session_id = ${sessionId}`.run(db);
24
21
  }
25
22
  //# sourceMappingURL=branch-tips.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"branch-tips.js","sourceRoot":"","sources":["../../../src/sqlite/storage/branch-tips.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,gBAAgB,CAAC,EAAkB,EAAE,SAAiB,EAAE;IACvE,OAAO,EAAE;SACP,OAAO,CAAC,qEAAqE,CAAC;SAC9E,GAAG,CAAqB,SAAS,CAAC;SAClC,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,EAAE;SACZ,OAAO,CAAC,uEAAuE,CAAC;SAChF,GAAG,CAAwB,SAAS,EAAE,KAAK,CAAC,CAAC;IAC/C,OAAO,GAAG,EAAE,SAAS,CAAC;AAAA,CACtB;AAED,MAAM,UAAU,eAAe,CAAC,EAAkB,EAAE,SAAiB,EAAE,KAAa,EAAE,QAAgB,EAAE;IACvG,EAAE,CAAC,OAAO,CAAC,0EAA0E,CAAC,CAAC,GAAG,CACzF,SAAS,EACT,KAAK,EACL,QAAQ,CACR,CAAC;AAAA,CACF;AAED,MAAM,UAAU,eAAe,CAC9B,EAAkB,EAClB,SAAiB,EACjB,QAAgB,EAChB,QAAgB,EAChB,QAAgB,EACf;IACD,MAAM,MAAM,GAAG,EAAE;SACf,OAAO,CAAC,yFAAyF,CAAC;SAClG,GAAG,CAAC,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;IAC/C,OAAO,MAAM,CAAC,OAAO,KAAK,CAAC,CAAC;AAAA,CAC5B;AAED,MAAM,UAAU,gBAAgB,CAAC,EAAkB,EAAE,SAAiB,EAAE;IACvE,EAAE,CAAC,OAAO,CAAC,8CAA8C,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;AAAA,CAC1E","sourcesContent":["import type { SqliteDatabase } from \"../types.ts\";\n\nexport function readBranchTipIds(db: SqliteDatabase, sessionId: string) {\n\treturn db\n\t\t.prepare(\"SELECT tip_id FROM branch_tips WHERE session_id = ? ORDER BY tip_id\")\n\t\t.all<{ tip_id: string }>(sessionId)\n\t\t.map((row) => row.tip_id);\n}\n\nexport function readBranchTipBranchId(db: SqliteDatabase, sessionId: string, tipId: string) {\n\tconst tip = db\n\t\t.prepare(\"SELECT branch_id FROM branch_tips WHERE session_id = ? AND tip_id = ?\")\n\t\t.get<{ branch_id: string }>(sessionId, tipId);\n\treturn tip?.branch_id;\n}\n\nexport function insertBranchTip(db: SqliteDatabase, sessionId: string, tipId: string, branchId: string) {\n\tdb.prepare(\"INSERT INTO branch_tips (session_id, tip_id, branch_id) VALUES (?, ?, ?)\").run(\n\t\tsessionId,\n\t\ttipId,\n\t\tbranchId,\n\t);\n}\n\nexport function updateBranchTip(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\tbranchId: string,\n\toldTipId: string,\n\tnewTipId: string,\n) {\n\tconst result = db\n\t\t.prepare(\"UPDATE branch_tips SET tip_id = ? WHERE session_id = ? AND branch_id = ? AND tip_id = ?\")\n\t\t.run(newTipId, sessionId, branchId, oldTipId);\n\treturn result.changes === 1;\n}\n\nexport function deleteBranchTips(db: SqliteDatabase, sessionId: string) {\n\tdb.prepare(\"DELETE FROM branch_tips WHERE session_id = ?\").run(sessionId);\n}\n"]}
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"]}
@@ -23,6 +23,7 @@ export declare function readEntryRow(db: SqliteDatabase, sessionId: string, entr
23
23
  export declare function readEntryRows(db: SqliteDatabase, sessionId: string, options?: {
24
24
  afterSeq?: number;
25
25
  order?: EntryOrder;
26
+ limit?: number;
26
27
  }): EntryRow[];
27
28
  export declare function idExistsInEntries(db: SqliteDatabase, sessionId: string, id: string): boolean;
28
29
  export declare function deleteEntryRows(db: SqliteDatabase, sessionId: string): void;
@@ -1 +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,+BAA+B,CAAC;AACvE,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;AAMD,wBAAgB,cAAc,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,QAIvF;AAED,wBAAgB,YAAY,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,wBAMlF;AAED,wBAAgB,aAAa,CAC5B,EAAE,EAAE,cAAc,EAClB,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,UAAU,CAAA;CAAO,cAgBvD;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 \"@earendil-works/pi-agent-core\";\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: string;\n\tpayload: string;\n}\n\nexport interface NewEntryRow {\n\tseq: number;\n\tid: string;\n\tparentId: string | null;\n\ttype: Entry[\"type\"];\n\ttimestamp: string;\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\nfunction orderedSql(order: EntryOrder | undefined): string {\n\treturn order === \"oldestFirst\" ? \"ASC\" : \"DESC\";\n}\n\nexport function insertEntryRow(db: SqliteDatabase, sessionId: string, entry: NewEntryRow) {\n\tdb.prepare(\n\t\t\"INSERT INTO entries (session_id, id, seq, parent_id, type, timestamp, payload) VALUES (?, ?, ?, ?, ?, ?, ?)\",\n\t).run(sessionId, entry.id, entry.seq, entry.parentId, entry.type, entry.timestamp, entry.payload);\n}\n\nexport function readEntryRow(db: SqliteDatabase, sessionId: string, entryId: string) {\n\treturn db\n\t\t.prepare(\n\t\t\t\"SELECT session_id, seq, id, parent_id, type, timestamp, payload FROM entries WHERE session_id = ? AND id = ?\",\n\t\t)\n\t\t.get<EntryRow>(sessionId, entryId);\n}\n\nexport function readEntryRows(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\toptions: { afterSeq?: number; order?: EntryOrder } = {},\n) {\n\tconst predicates = [\"session_id = ?\"];\n\tconst params: unknown[] = [sessionId];\n\tif (options.afterSeq !== undefined) {\n\t\tpredicates.push(\"seq > ?\");\n\t\tparams.push(options.afterSeq);\n\t}\n\treturn db\n\t\t.prepare(\n\t\t\t`SELECT session_id, seq, id, parent_id, type, timestamp, payload\n\t\t\tFROM entries\n\t\t\tWHERE ${predicates.join(\" AND \")}\n\t\t\tORDER BY seq ${orderedSql(options.order)}`,\n\t\t)\n\t\t.all<EntryRow>(...params);\n}\n\nexport function idExistsInEntries(db: SqliteDatabase, sessionId: string, id: string) {\n\treturn !!db\n\t\t.prepare(\"SELECT 1 AS found FROM entries WHERE session_id = ? AND id = ? LIMIT 1\")\n\t\t.get<{ found: number }>(sessionId, id);\n}\n\nexport function deleteEntryRows(db: SqliteDatabase, sessionId: string) {\n\tdb.prepare(\"DELETE FROM entries WHERE session_id = ?\").run(sessionId);\n}\n"]}
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,+BAA+B,CAAC;AAEvE,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;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,UAAU,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,cASvE;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 \"@earendil-works/pi-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: string;\n\tpayload: string;\n}\n\nexport interface NewEntryRow {\n\tseq: number;\n\tid: string;\n\tparentId: string | null;\n\ttype: Entry[\"type\"];\n\ttimestamp: string;\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: { afterSeq?: number; order?: EntryOrder; limit?: number } = {},\n) {\n\tconst after = options.afterSeq === undefined ? sql`` : sql` AND seq > ${options.afterSeq}`;\n\tconst direction = options.order === \"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}\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"]}
@@ -1,38 +1,30 @@
1
+ import { sql } from "../sql.js";
1
2
  export function entryPayload(entry) {
2
3
  const { type: _type, id: _id, seq: _seq, parentId: _parentId, timestamp: _timestamp, ...payload } = entry;
3
4
  return payload;
4
5
  }
5
- function orderedSql(order) {
6
- return order === "oldestFirst" ? "ASC" : "DESC";
7
- }
8
6
  export function insertEntryRow(db, sessionId, entry) {
9
- db.prepare("INSERT INTO entries (session_id, id, seq, parent_id, type, timestamp, payload) VALUES (?, ?, ?, ?, ?, ?, ?)").run(sessionId, entry.id, entry.seq, entry.parentId, entry.type, entry.timestamp, entry.payload);
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);
10
9
  }
11
10
  export function readEntryRow(db, sessionId, entryId) {
12
- return db
13
- .prepare("SELECT session_id, seq, id, parent_id, type, timestamp, payload FROM entries WHERE session_id = ? AND id = ?")
14
- .get(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);
15
14
  }
16
15
  export function readEntryRows(db, sessionId, options = {}) {
17
- const predicates = ["session_id = ?"];
18
- const params = [sessionId];
19
- if (options.afterSeq !== undefined) {
20
- predicates.push("seq > ?");
21
- params.push(options.afterSeq);
22
- }
23
- return db
24
- .prepare(`SELECT session_id, seq, id, parent_id, type, timestamp, payload
25
- FROM entries
26
- WHERE ${predicates.join(" AND ")}
27
- ORDER BY seq ${orderedSql(options.order)}`)
28
- .all(...params);
16
+ const after = options.afterSeq === undefined ? sql `` : sql ` AND seq > ${options.afterSeq}`;
17
+ const direction = options.order === "oldestFirst" ? sql `ASC` : sql `DESC`;
18
+ const limit = options.limit === undefined ? sql `` : sql ` LIMIT ${options.limit}`;
19
+ return sql `SELECT session_id, seq, id, parent_id, type, timestamp, payload
20
+ FROM entries
21
+ WHERE session_id = ${sessionId}${after}
22
+ ORDER BY seq ${direction}${limit}`.all(db);
29
23
  }
30
24
  export function idExistsInEntries(db, sessionId, id) {
31
- return !!db
32
- .prepare("SELECT 1 AS found FROM entries WHERE session_id = ? AND id = ? LIMIT 1")
33
- .get(sessionId, id);
25
+ return !!sql `SELECT 1 AS found FROM entries WHERE session_id = ${sessionId} AND id = ${id} LIMIT 1`.get(db);
34
26
  }
35
27
  export function deleteEntryRows(db, sessionId) {
36
- db.prepare("DELETE FROM entries WHERE session_id = ?").run(sessionId);
28
+ sql `DELETE FROM entries WHERE session_id = ${sessionId}`.run(db);
37
29
  }
38
30
  //# sourceMappingURL=entries.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"entries.js","sourceRoot":"","sources":["../../../src/sqlite/storage/entries.ts"],"names":[],"mappings":"AAsBA,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,SAAS,UAAU,CAAC,KAA6B,EAAU;IAC1D,OAAO,KAAK,KAAK,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;AAAA,CAChD;AAED,MAAM,UAAU,cAAc,CAAC,EAAkB,EAAE,SAAiB,EAAE,KAAkB,EAAE;IACzF,EAAE,CAAC,OAAO,CACT,6GAA6G,CAC7G,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;AAAA,CAClG;AAED,MAAM,UAAU,YAAY,CAAC,EAAkB,EAAE,SAAiB,EAAE,OAAe,EAAE;IACpF,OAAO,EAAE;SACP,OAAO,CACP,8GAA8G,CAC9G;SACA,GAAG,CAAW,SAAS,EAAE,OAAO,CAAC,CAAC;AAAA,CACpC;AAED,MAAM,UAAU,aAAa,CAC5B,EAAkB,EAClB,SAAiB,EACjB,OAAO,GAA8C,EAAE,EACtD;IACD,MAAM,UAAU,GAAG,CAAC,gBAAgB,CAAC,CAAC;IACtC,MAAM,MAAM,GAAc,CAAC,SAAS,CAAC,CAAC;IACtC,IAAI,OAAO,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;QACpC,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC3B,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,EAAE;SACP,OAAO,CACP;;WAEQ,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC;kBACjB,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAC1C;SACA,GAAG,CAAW,GAAG,MAAM,CAAC,CAAC;AAAA,CAC3B;AAED,MAAM,UAAU,iBAAiB,CAAC,EAAkB,EAAE,SAAiB,EAAE,EAAU,EAAE;IACpF,OAAO,CAAC,CAAC,EAAE;SACT,OAAO,CAAC,wEAAwE,CAAC;SACjF,GAAG,CAAoB,SAAS,EAAE,EAAE,CAAC,CAAC;AAAA,CACxC;AAED,MAAM,UAAU,eAAe,CAAC,EAAkB,EAAE,SAAiB,EAAE;IACtE,EAAE,CAAC,OAAO,CAAC,0CAA0C,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;AAAA,CACtE","sourcesContent":["import type { Entry, EntryOrder } from \"@earendil-works/pi-agent-core\";\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: string;\n\tpayload: string;\n}\n\nexport interface NewEntryRow {\n\tseq: number;\n\tid: string;\n\tparentId: string | null;\n\ttype: Entry[\"type\"];\n\ttimestamp: string;\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\nfunction orderedSql(order: EntryOrder | undefined): string {\n\treturn order === \"oldestFirst\" ? \"ASC\" : \"DESC\";\n}\n\nexport function insertEntryRow(db: SqliteDatabase, sessionId: string, entry: NewEntryRow) {\n\tdb.prepare(\n\t\t\"INSERT INTO entries (session_id, id, seq, parent_id, type, timestamp, payload) VALUES (?, ?, ?, ?, ?, ?, ?)\",\n\t).run(sessionId, entry.id, entry.seq, entry.parentId, entry.type, entry.timestamp, entry.payload);\n}\n\nexport function readEntryRow(db: SqliteDatabase, sessionId: string, entryId: string) {\n\treturn db\n\t\t.prepare(\n\t\t\t\"SELECT session_id, seq, id, parent_id, type, timestamp, payload FROM entries WHERE session_id = ? AND id = ?\",\n\t\t)\n\t\t.get<EntryRow>(sessionId, entryId);\n}\n\nexport function readEntryRows(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\toptions: { afterSeq?: number; order?: EntryOrder } = {},\n) {\n\tconst predicates = [\"session_id = ?\"];\n\tconst params: unknown[] = [sessionId];\n\tif (options.afterSeq !== undefined) {\n\t\tpredicates.push(\"seq > ?\");\n\t\tparams.push(options.afterSeq);\n\t}\n\treturn db\n\t\t.prepare(\n\t\t\t`SELECT session_id, seq, id, parent_id, type, timestamp, payload\n\t\t\tFROM entries\n\t\t\tWHERE ${predicates.join(\" AND \")}\n\t\t\tORDER BY seq ${orderedSql(options.order)}`,\n\t\t)\n\t\t.all<EntryRow>(...params);\n}\n\nexport function idExistsInEntries(db: SqliteDatabase, sessionId: string, id: string) {\n\treturn !!db\n\t\t.prepare(\"SELECT 1 AS found FROM entries WHERE session_id = ? AND id = ? LIMIT 1\")\n\t\t.get<{ found: number }>(sessionId, id);\n}\n\nexport function deleteEntryRows(db: SqliteDatabase, sessionId: string) {\n\tdb.prepare(\"DELETE FROM entries WHERE session_id = ?\").run(sessionId);\n}\n"]}
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,GAA8D,EAAE,EACtE;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,SAAS,GAAG,OAAO,CAAC,KAAK,KAAK,aAAa,CAAC,CAAC,CAAC,GAAG,CAAA,KAAK,CAAC,CAAC,CAAC,GAAG,CAAA,MAAM,CAAC;IACzE,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;iBACvB,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 \"@earendil-works/pi-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: string;\n\tpayload: string;\n}\n\nexport interface NewEntryRow {\n\tseq: number;\n\tid: string;\n\tparentId: string | null;\n\ttype: Entry[\"type\"];\n\ttimestamp: string;\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: { afterSeq?: number; order?: EntryOrder; limit?: number } = {},\n) {\n\tconst after = options.afterSeq === undefined ? sql`` : sql` AND seq > ${options.afterSeq}`;\n\tconst direction = options.order === \"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}\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"]}