@m4ike1/ion-session-backend-sqlite-node 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/README.md +62 -0
  3. package/dist/index.d.ts +6 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +103 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/sqlite/index.d.ts +5 -0
  8. package/dist/sqlite/index.d.ts.map +1 -0
  9. package/dist/sqlite/index.js +4 -0
  10. package/dist/sqlite/index.js.map +1 -0
  11. package/dist/sqlite/migrations/001_initial.sql +121 -0
  12. package/dist/sqlite/migrations.d.ts +3 -0
  13. package/dist/sqlite/migrations.d.ts.map +1 -0
  14. package/dist/sqlite/migrations.js +7 -0
  15. package/dist/sqlite/migrations.js.map +1 -0
  16. package/dist/sqlite/repo.d.ts +41 -0
  17. package/dist/sqlite/repo.d.ts.map +1 -0
  18. package/dist/sqlite/repo.js +408 -0
  19. package/dist/sqlite/repo.js.map +1 -0
  20. package/dist/sqlite/session/branch-entries.d.ts +6 -0
  21. package/dist/sqlite/session/branch-entries.d.ts.map +1 -0
  22. package/dist/sqlite/session/branch-entries.js +213 -0
  23. package/dist/sqlite/session/branch-entries.js.map +1 -0
  24. package/dist/sqlite/session/entries.d.ts +24 -0
  25. package/dist/sqlite/session/entries.d.ts.map +1 -0
  26. package/dist/sqlite/session/entries.js +126 -0
  27. package/dist/sqlite/session/entries.js.map +1 -0
  28. package/dist/sqlite/session/session-row.d.ts +23 -0
  29. package/dist/sqlite/session/session-row.d.ts.map +1 -0
  30. package/dist/sqlite/session/session-row.js +69 -0
  31. package/dist/sqlite/session/session-row.js.map +1 -0
  32. package/dist/sqlite/session/session-sequences.d.ts +4 -0
  33. package/dist/sqlite/session/session-sequences.d.ts.map +1 -0
  34. package/dist/sqlite/session/session-sequences.js +13 -0
  35. package/dist/sqlite/session/session-sequences.js.map +1 -0
  36. package/dist/sqlite/session/session-stats.d.ts +6 -0
  37. package/dist/sqlite/session/session-stats.d.ts.map +1 -0
  38. package/dist/sqlite/session/session-stats.js +39 -0
  39. package/dist/sqlite/session/session-stats.js.map +1 -0
  40. package/dist/sqlite/session/usage-ledger.d.ts +20 -0
  41. package/dist/sqlite/session/usage-ledger.d.ts.map +1 -0
  42. package/dist/sqlite/session/usage-ledger.js +50 -0
  43. package/dist/sqlite/session/usage-ledger.js.map +1 -0
  44. package/dist/sqlite/session/values.d.ts +23 -0
  45. package/dist/sqlite/session/values.d.ts.map +1 -0
  46. package/dist/sqlite/session/values.js +91 -0
  47. package/dist/sqlite/session/values.js.map +1 -0
  48. package/dist/sqlite/session.d.ts +42 -0
  49. package/dist/sqlite/session.d.ts.map +1 -0
  50. package/dist/sqlite/session.js +163 -0
  51. package/dist/sqlite/session.js.map +1 -0
  52. package/dist/sqlite/sql.d.ts +19 -0
  53. package/dist/sqlite/sql.d.ts.map +1 -0
  54. package/dist/sqlite/sql.js +58 -0
  55. package/dist/sqlite/sql.js.map +1 -0
  56. package/dist/sqlite/storage.d.ts +38 -0
  57. package/dist/sqlite/storage.d.ts.map +1 -0
  58. package/dist/sqlite/storage.js +161 -0
  59. package/dist/sqlite/storage.js.map +1 -0
  60. package/dist/sqlite/types.d.ts +29 -0
  61. package/dist/sqlite/types.d.ts.map +1 -0
  62. package/dist/sqlite/types.js +2 -0
  63. package/dist/sqlite/types.js.map +1 -0
  64. package/package.json +45 -0
@@ -0,0 +1,39 @@
1
+ import { sql } from "../sql.js";
2
+ import { readSessionRow } from "./session-row.js";
3
+ function addUsage(left, right) {
4
+ return {
5
+ input: left.input + right.input,
6
+ output: left.output + right.output,
7
+ cacheRead: left.cacheRead + right.cacheRead,
8
+ cacheWrite: left.cacheWrite + right.cacheWrite,
9
+ ...(left.cacheWrite1h === undefined && right.cacheWrite1h === undefined
10
+ ? {}
11
+ : { cacheWrite1h: (left.cacheWrite1h ?? 0) + (right.cacheWrite1h ?? 0) }),
12
+ ...(left.reasoning === undefined && right.reasoning === undefined
13
+ ? {}
14
+ : { reasoning: (left.reasoning ?? 0) + (right.reasoning ?? 0) }),
15
+ totalTokens: left.totalTokens + right.totalTokens,
16
+ cost: {
17
+ input: left.cost.input + right.cost.input,
18
+ output: left.cost.output + right.cost.output,
19
+ cacheRead: left.cost.cacheRead + right.cost.cacheRead,
20
+ cacheWrite: left.cost.cacheWrite + right.cost.cacheWrite,
21
+ total: left.cost.total + right.cost.total,
22
+ },
23
+ };
24
+ }
25
+ export function readSessionStats(db, sessionId) {
26
+ const row = readSessionRow(db, sessionId);
27
+ return {
28
+ messageCount: row.message_count,
29
+ usage: JSON.parse(row.usage_payload),
30
+ };
31
+ }
32
+ export function incrementMessageCount(db, sessionId) {
33
+ sql `UPDATE sessions SET message_count = message_count + 1 WHERE id = ${sessionId}`.run(db);
34
+ }
35
+ export function addUsageToSessionStats(db, sessionId, usage) {
36
+ const current = readSessionStats(db, sessionId).usage;
37
+ sql `UPDATE sessions SET usage_payload = ${JSON.stringify(addUsage(current, usage))} WHERE id = ${sessionId}`.run(db);
38
+ }
39
+ //# sourceMappingURL=session-stats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-stats.js","sourceRoot":"","sources":["../../../src/sqlite/session/session-stats.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAEhC,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAElD,SAAS,QAAQ,CAAC,IAAW,EAAE,KAAY,EAAS;IACnD,OAAO;QACN,KAAK,EAAE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK;QAC/B,MAAM,EAAE,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM;QAClC,SAAS,EAAE,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC,SAAS;QAC3C,UAAU,EAAE,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU;QAC9C,GAAG,CAAC,IAAI,CAAC,YAAY,KAAK,SAAS,IAAI,KAAK,CAAC,YAAY,KAAK,SAAS;YACtE,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,IAAI,CAAC,YAAY,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,YAAY,IAAI,CAAC,CAAC,EAAE,CAAC;QAC1E,GAAG,CAAC,IAAI,CAAC,SAAS,KAAK,SAAS,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS;YAChE,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,IAAI,CAAC,SAAS,IAAI,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,IAAI,CAAC,CAAC,EAAE,CAAC;QACjE,WAAW,EAAE,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC,WAAW;QACjD,IAAI,EAAE;YACL,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK;YACzC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,IAAI,CAAC,MAAM;YAC5C,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,SAAS;YACrD,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,UAAU;YACxD,KAAK,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK;SACzC;KACD,CAAC;AAAA,CACF;AAED,MAAM,UAAU,gBAAgB,CAAC,EAAkB,EAAE,SAAiB,EAAgB;IACrF,MAAM,GAAG,GAAG,cAAc,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;IAC1C,OAAO;QACN,YAAY,EAAE,GAAG,CAAC,aAAa;QAC/B,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,aAAa,CAAU;KAC7C,CAAC;AAAA,CACF;AAED,MAAM,UAAU,qBAAqB,CAAC,EAAkB,EAAE,SAAiB,EAAQ;IAClF,GAAG,CAAA,oEAAoE,SAAS,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAAA,CAC3F;AAED,MAAM,UAAU,sBAAsB,CAAC,EAAkB,EAAE,SAAiB,EAAE,KAAwB,EAAQ;IAC7G,MAAM,OAAO,GAAG,gBAAgB,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC,KAAK,CAAC;IACtD,GAAG,CAAA,uCAAuC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,eAAe,SAAS,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;AAAA,CACrH","sourcesContent":["import type { SessionStats, UsageRow } from \"@m4ike1/ion-agent-core\";\nimport type { Usage } from \"@m4ike1/ion-ai\";\nimport { sql } from \"../sql.ts\";\nimport type { SqliteDatabase } from \"../types.ts\";\nimport { readSessionRow } from \"./session-row.ts\";\n\nfunction addUsage(left: Usage, right: Usage): Usage {\n\treturn {\n\t\tinput: left.input + right.input,\n\t\toutput: left.output + right.output,\n\t\tcacheRead: left.cacheRead + right.cacheRead,\n\t\tcacheWrite: left.cacheWrite + right.cacheWrite,\n\t\t...(left.cacheWrite1h === undefined && right.cacheWrite1h === undefined\n\t\t\t? {}\n\t\t\t: { cacheWrite1h: (left.cacheWrite1h ?? 0) + (right.cacheWrite1h ?? 0) }),\n\t\t...(left.reasoning === undefined && right.reasoning === undefined\n\t\t\t? {}\n\t\t\t: { reasoning: (left.reasoning ?? 0) + (right.reasoning ?? 0) }),\n\t\ttotalTokens: left.totalTokens + right.totalTokens,\n\t\tcost: {\n\t\t\tinput: left.cost.input + right.cost.input,\n\t\t\toutput: left.cost.output + right.cost.output,\n\t\t\tcacheRead: left.cost.cacheRead + right.cost.cacheRead,\n\t\t\tcacheWrite: left.cost.cacheWrite + right.cost.cacheWrite,\n\t\t\ttotal: left.cost.total + right.cost.total,\n\t\t},\n\t};\n}\n\nexport function readSessionStats(db: SqliteDatabase, sessionId: string): SessionStats {\n\tconst row = readSessionRow(db, sessionId);\n\treturn {\n\t\tmessageCount: row.message_count,\n\t\tusage: JSON.parse(row.usage_payload) as Usage,\n\t};\n}\n\nexport function incrementMessageCount(db: SqliteDatabase, sessionId: string): void {\n\tsql`UPDATE sessions SET message_count = message_count + 1 WHERE id = ${sessionId}`.run(db);\n}\n\nexport function addUsageToSessionStats(db: SqliteDatabase, sessionId: string, usage: UsageRow[\"usage\"]): void {\n\tconst current = readSessionStats(db, sessionId).usage;\n\tsql`UPDATE sessions SET usage_payload = ${JSON.stringify(addUsage(current, usage))} WHERE id = ${sessionId}`.run(db);\n}\n"]}
@@ -0,0 +1,20 @@
1
+ import type { UsageRow, UsageScan } from "@m4ike1/ion-agent-core";
2
+ import type { SqliteDatabase } from "../types.ts";
3
+ export interface UsageLedgerRow {
4
+ id: string;
5
+ seq: number;
6
+ entry_id: string | null;
7
+ adjustment: number;
8
+ usage: string;
9
+ details: string | null;
10
+ }
11
+ export declare class UsageLedgerRowWriter {
12
+ private readonly insertStatement;
13
+ private readonly sessionId;
14
+ constructor(db: SqliteDatabase, sessionId: string);
15
+ insert(row: UsageRow): void;
16
+ }
17
+ export declare function insertUsageLedgerRow(db: SqliteDatabase, sessionId: string, row: UsageRow): void;
18
+ export declare function decodeUsageLedgerRow(row: UsageLedgerRow): UsageRow;
19
+ export declare function scanUsageLedgerRows(db: SqliteDatabase, sessionId: string, query: UsageScan): UsageLedgerRow[];
20
+ //# sourceMappingURL=usage-ledger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage-ledger.d.ts","sourceRoot":"","sources":["../../../src/sqlite/session/usage-ledger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAElE,OAAO,KAAK,EAAE,cAAc,EAAmB,MAAM,aAAa,CAAC;AAEnE,MAAM,WAAW,cAAc;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAiBD,qBAAa,oBAAoB;IAChC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAClD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IAEnC,YAAY,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAGhD;IAED,MAAM,CAAC,GAAG,EAAE,QAAQ,GAAG,IAAI,CAE1B;CACD;AAED,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,GAAG,IAAI,CAE/F;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,cAAc,GAAG,QAAQ,CASlE;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,cAAc,EAAE,CAS7G","sourcesContent":["import type { UsageRow, UsageScan } from \"@m4ike1/ion-agent-core\";\nimport { joinSqlFragments, type SqlQuery, sql } from \"../sql.ts\";\nimport type { SqliteDatabase, SqliteStatement } from \"../types.ts\";\n\nexport interface UsageLedgerRow {\n\tid: string;\n\tseq: number;\n\tentry_id: string | null;\n\tadjustment: number;\n\tusage: string;\n\tdetails: string | null;\n}\n\nconst INSERT_USAGE_LEDGER_SQL = `INSERT INTO usage_ledger (session_id, id, seq, entry_id, adjustment, usage, details)\n\tVALUES (?, ?, ?, ?, ?, ?, ?)`;\n\nfunction usageLedgerRowParams(sessionId: string, row: UsageRow): unknown[] {\n\treturn [\n\t\tsessionId,\n\t\trow.id,\n\t\trow.seq,\n\t\trow.entryId ?? null,\n\t\trow.adjustment ? 1 : 0,\n\t\tJSON.stringify(row.usage),\n\t\trow.details === undefined ? null : JSON.stringify(row.details),\n\t];\n}\n\nexport class UsageLedgerRowWriter {\n\tprivate readonly insertStatement: SqliteStatement;\n\tprivate readonly sessionId: string;\n\n\tconstructor(db: SqliteDatabase, sessionId: string) {\n\t\tthis.insertStatement = db.prepare(INSERT_USAGE_LEDGER_SQL);\n\t\tthis.sessionId = sessionId;\n\t}\n\n\tinsert(row: UsageRow): void {\n\t\tthis.insertStatement.run(...usageLedgerRowParams(this.sessionId, row));\n\t}\n}\n\nexport function insertUsageLedgerRow(db: SqliteDatabase, sessionId: string, row: UsageRow): void {\n\tdb.prepare(INSERT_USAGE_LEDGER_SQL).run(...usageLedgerRowParams(sessionId, row));\n}\n\nexport function decodeUsageLedgerRow(row: UsageLedgerRow): UsageRow {\n\treturn {\n\t\tid: row.id,\n\t\tseq: row.seq,\n\t\tusage: JSON.parse(row.usage) as UsageRow[\"usage\"],\n\t\t...(row.entry_id === null ? {} : { entryId: row.entry_id }),\n\t\tadjustment: row.adjustment !== 0,\n\t\t...(row.details === null ? {} : { details: JSON.parse(row.details) as UsageRow[\"details\"] }),\n\t};\n}\n\nexport function scanUsageLedgerRows(db: SqliteDatabase, sessionId: string, query: UsageScan): UsageLedgerRow[] {\n\tconst filters: SqlQuery[] = [sql`session_id = ${sessionId}`];\n\tif (query.fromSeq !== undefined) filters.push(sql`seq >= ${query.fromSeq}`);\n\tif (query.toSeq !== undefined) filters.push(sql`seq <= ${query.toSeq}`);\n\n\tconst order = query.order === \"desc\" ? sql`ORDER BY seq DESC` : sql`ORDER BY seq ASC`;\n\tconst limit = query.limit === undefined ? sql`` : sql`LIMIT ${Math.max(0, query.limit)}`;\n\treturn sql`SELECT id, seq, entry_id, adjustment, usage, details\n\t\tFROM usage_ledger WHERE ${joinSqlFragments(filters, \" AND \")} ${order} ${limit}`.all<UsageLedgerRow>(db);\n}\n"]}
@@ -0,0 +1,50 @@
1
+ import { joinSqlFragments, sql } from "../sql.js";
2
+ const INSERT_USAGE_LEDGER_SQL = `INSERT INTO usage_ledger (session_id, id, seq, entry_id, adjustment, usage, details)
3
+ VALUES (?, ?, ?, ?, ?, ?, ?)`;
4
+ function usageLedgerRowParams(sessionId, row) {
5
+ return [
6
+ sessionId,
7
+ row.id,
8
+ row.seq,
9
+ row.entryId ?? null,
10
+ row.adjustment ? 1 : 0,
11
+ JSON.stringify(row.usage),
12
+ row.details === undefined ? null : JSON.stringify(row.details),
13
+ ];
14
+ }
15
+ export class UsageLedgerRowWriter {
16
+ insertStatement;
17
+ sessionId;
18
+ constructor(db, sessionId) {
19
+ this.insertStatement = db.prepare(INSERT_USAGE_LEDGER_SQL);
20
+ this.sessionId = sessionId;
21
+ }
22
+ insert(row) {
23
+ this.insertStatement.run(...usageLedgerRowParams(this.sessionId, row));
24
+ }
25
+ }
26
+ export function insertUsageLedgerRow(db, sessionId, row) {
27
+ db.prepare(INSERT_USAGE_LEDGER_SQL).run(...usageLedgerRowParams(sessionId, row));
28
+ }
29
+ export function decodeUsageLedgerRow(row) {
30
+ return {
31
+ id: row.id,
32
+ seq: row.seq,
33
+ usage: JSON.parse(row.usage),
34
+ ...(row.entry_id === null ? {} : { entryId: row.entry_id }),
35
+ adjustment: row.adjustment !== 0,
36
+ ...(row.details === null ? {} : { details: JSON.parse(row.details) }),
37
+ };
38
+ }
39
+ export function scanUsageLedgerRows(db, sessionId, query) {
40
+ const filters = [sql `session_id = ${sessionId}`];
41
+ if (query.fromSeq !== undefined)
42
+ filters.push(sql `seq >= ${query.fromSeq}`);
43
+ if (query.toSeq !== undefined)
44
+ filters.push(sql `seq <= ${query.toSeq}`);
45
+ const order = query.order === "desc" ? sql `ORDER BY seq DESC` : sql `ORDER BY seq ASC`;
46
+ const limit = query.limit === undefined ? sql `` : sql `LIMIT ${Math.max(0, query.limit)}`;
47
+ return sql `SELECT id, seq, entry_id, adjustment, usage, details
48
+ FROM usage_ledger WHERE ${joinSqlFragments(filters, " AND ")} ${order} ${limit}`.all(db);
49
+ }
50
+ //# sourceMappingURL=usage-ledger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage-ledger.js","sourceRoot":"","sources":["../../../src/sqlite/session/usage-ledger.ts"],"names":[],"mappings":"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","sourcesContent":["import type { UsageRow, UsageScan } from \"@m4ike1/ion-agent-core\";\nimport { joinSqlFragments, type SqlQuery, sql } from \"../sql.ts\";\nimport type { SqliteDatabase, SqliteStatement } from \"../types.ts\";\n\nexport interface UsageLedgerRow {\n\tid: string;\n\tseq: number;\n\tentry_id: string | null;\n\tadjustment: number;\n\tusage: string;\n\tdetails: string | null;\n}\n\nconst INSERT_USAGE_LEDGER_SQL = `INSERT INTO usage_ledger (session_id, id, seq, entry_id, adjustment, usage, details)\n\tVALUES (?, ?, ?, ?, ?, ?, ?)`;\n\nfunction usageLedgerRowParams(sessionId: string, row: UsageRow): unknown[] {\n\treturn [\n\t\tsessionId,\n\t\trow.id,\n\t\trow.seq,\n\t\trow.entryId ?? null,\n\t\trow.adjustment ? 1 : 0,\n\t\tJSON.stringify(row.usage),\n\t\trow.details === undefined ? null : JSON.stringify(row.details),\n\t];\n}\n\nexport class UsageLedgerRowWriter {\n\tprivate readonly insertStatement: SqliteStatement;\n\tprivate readonly sessionId: string;\n\n\tconstructor(db: SqliteDatabase, sessionId: string) {\n\t\tthis.insertStatement = db.prepare(INSERT_USAGE_LEDGER_SQL);\n\t\tthis.sessionId = sessionId;\n\t}\n\n\tinsert(row: UsageRow): void {\n\t\tthis.insertStatement.run(...usageLedgerRowParams(this.sessionId, row));\n\t}\n}\n\nexport function insertUsageLedgerRow(db: SqliteDatabase, sessionId: string, row: UsageRow): void {\n\tdb.prepare(INSERT_USAGE_LEDGER_SQL).run(...usageLedgerRowParams(sessionId, row));\n}\n\nexport function decodeUsageLedgerRow(row: UsageLedgerRow): UsageRow {\n\treturn {\n\t\tid: row.id,\n\t\tseq: row.seq,\n\t\tusage: JSON.parse(row.usage) as UsageRow[\"usage\"],\n\t\t...(row.entry_id === null ? {} : { entryId: row.entry_id }),\n\t\tadjustment: row.adjustment !== 0,\n\t\t...(row.details === null ? {} : { details: JSON.parse(row.details) as UsageRow[\"details\"] }),\n\t};\n}\n\nexport function scanUsageLedgerRows(db: SqliteDatabase, sessionId: string, query: UsageScan): UsageLedgerRow[] {\n\tconst filters: SqlQuery[] = [sql`session_id = ${sessionId}`];\n\tif (query.fromSeq !== undefined) filters.push(sql`seq >= ${query.fromSeq}`);\n\tif (query.toSeq !== undefined) filters.push(sql`seq <= ${query.toSeq}`);\n\n\tconst order = query.order === \"desc\" ? sql`ORDER BY seq DESC` : sql`ORDER BY seq ASC`;\n\tconst limit = query.limit === undefined ? sql`` : sql`LIMIT ${Math.max(0, query.limit)}`;\n\treturn sql`SELECT id, seq, entry_id, adjustment, usage, details\n\t\tFROM usage_ledger WHERE ${joinSqlFragments(filters, \" AND \")} ${order} ${limit}`.all<UsageLedgerRow>(db);\n}\n"]}
@@ -0,0 +1,23 @@
1
+ import { type ListElement, type ListReadOptions, type StoredValue, type Value, type ValueList } from "@m4ike1/ion-agent-core";
2
+ import { type SqlQuery } from "../sql.ts";
3
+ import type { SqliteDatabase } from "../types.ts";
4
+ export interface ScalarValueRow {
5
+ namespace: string;
6
+ key: string;
7
+ seq: number;
8
+ value: string;
9
+ }
10
+ export interface ListValueRow {
11
+ seq: number;
12
+ value: string;
13
+ }
14
+ export declare function setScalarValueRow(db: SqliteDatabase, sessionId: string, namespace: string, key: string, seq: number, storedValue: unknown): void;
15
+ export declare function deleteScalarValueRow(db: SqliteDatabase, sessionId: string, namespace: string, key: string): void;
16
+ export declare function appendListValueRow(db: SqliteDatabase, sessionId: string, namespace: string, key: string, seq: number, element: unknown): void;
17
+ export declare function deleteListValueRows(db: SqliteDatabase, sessionId: string, namespace: string, key: string): void;
18
+ export declare function readScalarValueRow<T>(db: SqliteDatabase, sessionId: string, address: Value<T>): StoredValue<T> | undefined;
19
+ export declare function readAllScalarValueRows(db: SqliteDatabase, sessionId: string): StoredValue<unknown>[];
20
+ export declare function scanScalarValueRows<T>(db: SqliteDatabase, sessionId: string, prefix: Value<T>): StoredValue<T>[];
21
+ export declare function listValueReadQuery<T>(sessionId: string, address: ValueList<T>, options?: ListReadOptions): SqlQuery;
22
+ export declare function readListValueRows<T>(db: SqliteDatabase, sessionId: string, address: ValueList<T>, options?: ListReadOptions): ListElement<T>[];
23
+ //# sourceMappingURL=values.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"values.d.ts","sourceRoot":"","sources":["../../../src/sqlite/session/values.ts"],"names":[],"mappings":"AAAA,OAAO,EACN,KAAK,WAAW,EAChB,KAAK,eAAe,EAEpB,KAAK,WAAW,EAChB,KAAK,KAAK,EACV,KAAK,SAAS,EAEd,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,KAAK,QAAQ,EAAO,MAAM,WAAW,CAAC;AAC/C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,MAAM,WAAW,cAAc;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,YAAY;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,iBAAiB,CAChC,EAAE,EAAE,cAAc,EAClB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,WAAW,EAAE,OAAO,GAClB,IAAI,CAIN;AAED,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAGhH;AAED,wBAAgB,kBAAkB,CACjC,EAAE,EAAE,cAAc,EAClB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,OAAO,GACd,IAAI,CAGN;AAED,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAG/G;AASD,wBAAgB,kBAAkB,CAAC,CAAC,EACnC,EAAE,EAAE,cAAc,EAClB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,GACf,WAAW,CAAC,CAAC,CAAC,GAAG,SAAS,CAM5B;AAED,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,GAAG,WAAW,CAAC,OAAO,CAAC,EAAE,CASpG;AAgBD,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,EAAE,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,EAAE,CAWhH;AAED,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,eAAe,GAAG,QAAQ,CAkBnH;AAED,wBAAgB,iBAAiB,CAAC,CAAC,EAClC,EAAE,EAAE,cAAc,EAClB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC,EACrB,OAAO,CAAC,EAAE,eAAe,GACvB,WAAW,CAAC,CAAC,CAAC,EAAE,CAIlB","sourcesContent":["import {\n\ttype ListElement,\n\ttype ListReadOptions,\n\tresolveListReadOptions,\n\ttype StoredValue,\n\ttype Value,\n\ttype ValueList,\n\tvalue,\n} from \"@m4ike1/ion-agent-core\";\nimport { type SqlQuery, sql } from \"../sql.ts\";\nimport type { SqliteDatabase } from \"../types.ts\";\n\nexport interface ScalarValueRow {\n\tnamespace: string;\n\tkey: string;\n\tseq: number;\n\tvalue: string;\n}\n\nexport interface ListValueRow {\n\tseq: number;\n\tvalue: string;\n}\n\nexport function setScalarValueRow(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\tnamespace: string,\n\tkey: string,\n\tseq: number,\n\tstoredValue: unknown,\n): void {\n\tsql`INSERT INTO scalar_values (session_id, namespace, key, seq, value)\n\t\tVALUES (${sessionId}, ${namespace}, ${key}, ${seq}, ${JSON.stringify(storedValue)})\n\t\tON CONFLICT(session_id, namespace, key) DO UPDATE SET seq = excluded.seq, value = excluded.value`.run(db);\n}\n\nexport function deleteScalarValueRow(db: SqliteDatabase, sessionId: string, namespace: string, key: string): void {\n\tsql`DELETE FROM scalar_values\n\t\tWHERE session_id = ${sessionId} AND namespace = ${namespace} AND key = ${key}`.run(db);\n}\n\nexport function appendListValueRow(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\tnamespace: string,\n\tkey: string,\n\tseq: number,\n\telement: unknown,\n): void {\n\tsql`INSERT INTO list_values (session_id, namespace, key, seq, value)\n\t\tVALUES (${sessionId}, ${namespace}, ${key}, ${seq}, ${JSON.stringify(element)})`.run(db);\n}\n\nexport function deleteListValueRows(db: SqliteDatabase, sessionId: string, namespace: string, key: string): void {\n\tsql`DELETE FROM list_values\n\t\tWHERE session_id = ${sessionId} AND namespace = ${namespace} AND key = ${key}`.run(db);\n}\n\nfunction decodeScalarValueRow<T>(address: Value<T>, row: ScalarValueRow): StoredValue<T> {\n\tif (row.namespace !== address.namespace || row.key !== address.key) {\n\t\tthrow new Error(`Expected value ${address.namespace}:${address.key}, found ${row.namespace}:${row.key}`);\n\t}\n\treturn { address, seq: row.seq, value: JSON.parse(row.value) as T };\n}\n\nexport function readScalarValueRow<T>(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\taddress: Value<T>,\n): StoredValue<T> | undefined {\n\tconst row = sql`SELECT namespace, key, seq, value FROM scalar_values\n\t\tWHERE session_id = ${sessionId} AND namespace = ${address.namespace} AND key = ${address.key}`.get<ScalarValueRow>(\n\t\tdb,\n\t);\n\treturn row === undefined ? undefined : decodeScalarValueRow(address, row);\n}\n\nexport function readAllScalarValueRows(db: SqliteDatabase, sessionId: string): StoredValue<unknown>[] {\n\treturn sql`SELECT namespace, key, seq, value FROM scalar_values\n\t\tWHERE session_id = ${sessionId} ORDER BY seq ASC`\n\t\t.all<ScalarValueRow>(db)\n\t\t.map((row) => ({\n\t\t\taddress: value<unknown>(row.namespace, row.key),\n\t\t\tseq: row.seq,\n\t\t\tvalue: JSON.parse(row.value),\n\t\t}));\n}\n\nfunction nextPrefixBoundary(prefix: string): string | undefined {\n\tif (prefix === \"\") return undefined;\n\tconst codePoints = Array.from(prefix);\n\tfor (let index = codePoints.length - 1; index >= 0; index--) {\n\t\tconst codePoint = codePoints[index]?.codePointAt(0);\n\t\tif (codePoint === undefined) throw new Error(\"Invalid value key prefix\");\n\t\tif (codePoint < 0x10ffff) {\n\t\t\tconst nextCodePoint = codePoint >= 0xd7ff && codePoint < 0xe000 ? 0xe000 : codePoint + 1;\n\t\t\treturn `${codePoints.slice(0, index).join(\"\")}${String.fromCodePoint(nextCodePoint)}`;\n\t\t}\n\t}\n\treturn undefined;\n}\n\nexport function scanScalarValueRows<T>(db: SqliteDatabase, sessionId: string, prefix: Value<T>): StoredValue<T>[] {\n\tconst upperBound = nextPrefixBoundary(prefix.key);\n\tconst rows =\n\t\tupperBound === undefined\n\t\t\t? sql`SELECT namespace, key, seq, value FROM scalar_values\n\t\t\t\tWHERE session_id = ${sessionId} AND namespace = ${prefix.namespace} AND key >= ${prefix.key}\n\t\t\t\tORDER BY key ASC`.all<ScalarValueRow>(db)\n\t\t\t: sql`SELECT namespace, key, seq, value FROM scalar_values\n\t\t\t\tWHERE session_id = ${sessionId} AND namespace = ${prefix.namespace} AND key >= ${prefix.key} AND key < ${upperBound}\n\t\t\t\tORDER BY key ASC`.all<ScalarValueRow>(db);\n\treturn rows.map((row) => decodeScalarValueRow(value<T>(row.namespace, row.key), row));\n}\n\nexport function listValueReadQuery<T>(sessionId: string, address: ValueList<T>, options?: ListReadOptions): SqlQuery {\n\tconst resolved = resolveListReadOptions(options);\n\tif (resolved.order === \"asc\") {\n\t\treturn resolved.cursor === undefined\n\t\t\t? sql`SELECT seq, value FROM list_values\n\t\t\t\tWHERE session_id = ${sessionId} AND namespace = ${address.namespace} AND key = ${address.key}\n\t\t\t\tORDER BY seq ASC LIMIT ${resolved.limit}`\n\t\t\t: sql`SELECT seq, value FROM list_values\n\t\t\t\tWHERE session_id = ${sessionId} AND namespace = ${address.namespace} AND key = ${address.key} AND seq > ${resolved.cursor.seq}\n\t\t\t\tORDER BY seq ASC LIMIT ${resolved.limit}`;\n\t}\n\treturn resolved.cursor === undefined\n\t\t? sql`SELECT seq, value FROM list_values\n\t\t\tWHERE session_id = ${sessionId} AND namespace = ${address.namespace} AND key = ${address.key}\n\t\t\tORDER BY seq DESC LIMIT ${resolved.limit}`\n\t\t: sql`SELECT seq, value FROM list_values\n\t\t\tWHERE session_id = ${sessionId} AND namespace = ${address.namespace} AND key = ${address.key} AND seq < ${resolved.cursor.seq}\n\t\t\tORDER BY seq DESC LIMIT ${resolved.limit}`;\n}\n\nexport function readListValueRows<T>(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\taddress: ValueList<T>,\n\toptions?: ListReadOptions,\n): ListElement<T>[] {\n\treturn listValueReadQuery(sessionId, address, options)\n\t\t.all<ListValueRow>(db)\n\t\t.map((row) => ({ seq: row.seq, value: JSON.parse(row.value) as T }));\n}\n"]}
@@ -0,0 +1,91 @@
1
+ import { resolveListReadOptions, value, } from "@m4ike1/ion-agent-core";
2
+ import { sql } from "../sql.js";
3
+ export function setScalarValueRow(db, sessionId, namespace, key, seq, storedValue) {
4
+ sql `INSERT INTO scalar_values (session_id, namespace, key, seq, value)
5
+ VALUES (${sessionId}, ${namespace}, ${key}, ${seq}, ${JSON.stringify(storedValue)})
6
+ ON CONFLICT(session_id, namespace, key) DO UPDATE SET seq = excluded.seq, value = excluded.value`.run(db);
7
+ }
8
+ export function deleteScalarValueRow(db, sessionId, namespace, key) {
9
+ sql `DELETE FROM scalar_values
10
+ WHERE session_id = ${sessionId} AND namespace = ${namespace} AND key = ${key}`.run(db);
11
+ }
12
+ export function appendListValueRow(db, sessionId, namespace, key, seq, element) {
13
+ sql `INSERT INTO list_values (session_id, namespace, key, seq, value)
14
+ VALUES (${sessionId}, ${namespace}, ${key}, ${seq}, ${JSON.stringify(element)})`.run(db);
15
+ }
16
+ export function deleteListValueRows(db, sessionId, namespace, key) {
17
+ sql `DELETE FROM list_values
18
+ WHERE session_id = ${sessionId} AND namespace = ${namespace} AND key = ${key}`.run(db);
19
+ }
20
+ function decodeScalarValueRow(address, row) {
21
+ if (row.namespace !== address.namespace || row.key !== address.key) {
22
+ throw new Error(`Expected value ${address.namespace}:${address.key}, found ${row.namespace}:${row.key}`);
23
+ }
24
+ return { address, seq: row.seq, value: JSON.parse(row.value) };
25
+ }
26
+ export function readScalarValueRow(db, sessionId, address) {
27
+ const row = sql `SELECT namespace, key, seq, value FROM scalar_values
28
+ WHERE session_id = ${sessionId} AND namespace = ${address.namespace} AND key = ${address.key}`.get(db);
29
+ return row === undefined ? undefined : decodeScalarValueRow(address, row);
30
+ }
31
+ export function readAllScalarValueRows(db, sessionId) {
32
+ return sql `SELECT namespace, key, seq, value FROM scalar_values
33
+ WHERE session_id = ${sessionId} ORDER BY seq ASC`
34
+ .all(db)
35
+ .map((row) => ({
36
+ address: value(row.namespace, row.key),
37
+ seq: row.seq,
38
+ value: JSON.parse(row.value),
39
+ }));
40
+ }
41
+ function nextPrefixBoundary(prefix) {
42
+ if (prefix === "")
43
+ return undefined;
44
+ const codePoints = Array.from(prefix);
45
+ for (let index = codePoints.length - 1; index >= 0; index--) {
46
+ const codePoint = codePoints[index]?.codePointAt(0);
47
+ if (codePoint === undefined)
48
+ throw new Error("Invalid value key prefix");
49
+ if (codePoint < 0x10ffff) {
50
+ const nextCodePoint = codePoint >= 0xd7ff && codePoint < 0xe000 ? 0xe000 : codePoint + 1;
51
+ return `${codePoints.slice(0, index).join("")}${String.fromCodePoint(nextCodePoint)}`;
52
+ }
53
+ }
54
+ return undefined;
55
+ }
56
+ export function scanScalarValueRows(db, sessionId, prefix) {
57
+ const upperBound = nextPrefixBoundary(prefix.key);
58
+ const rows = upperBound === undefined
59
+ ? sql `SELECT namespace, key, seq, value FROM scalar_values
60
+ WHERE session_id = ${sessionId} AND namespace = ${prefix.namespace} AND key >= ${prefix.key}
61
+ ORDER BY key ASC`.all(db)
62
+ : sql `SELECT namespace, key, seq, value FROM scalar_values
63
+ WHERE session_id = ${sessionId} AND namespace = ${prefix.namespace} AND key >= ${prefix.key} AND key < ${upperBound}
64
+ ORDER BY key ASC`.all(db);
65
+ return rows.map((row) => decodeScalarValueRow(value(row.namespace, row.key), row));
66
+ }
67
+ export function listValueReadQuery(sessionId, address, options) {
68
+ const resolved = resolveListReadOptions(options);
69
+ if (resolved.order === "asc") {
70
+ return resolved.cursor === undefined
71
+ ? sql `SELECT seq, value FROM list_values
72
+ WHERE session_id = ${sessionId} AND namespace = ${address.namespace} AND key = ${address.key}
73
+ ORDER BY seq ASC LIMIT ${resolved.limit}`
74
+ : sql `SELECT seq, value FROM list_values
75
+ WHERE session_id = ${sessionId} AND namespace = ${address.namespace} AND key = ${address.key} AND seq > ${resolved.cursor.seq}
76
+ ORDER BY seq ASC LIMIT ${resolved.limit}`;
77
+ }
78
+ return resolved.cursor === undefined
79
+ ? sql `SELECT seq, value FROM list_values
80
+ WHERE session_id = ${sessionId} AND namespace = ${address.namespace} AND key = ${address.key}
81
+ ORDER BY seq DESC LIMIT ${resolved.limit}`
82
+ : sql `SELECT seq, value FROM list_values
83
+ WHERE session_id = ${sessionId} AND namespace = ${address.namespace} AND key = ${address.key} AND seq < ${resolved.cursor.seq}
84
+ ORDER BY seq DESC LIMIT ${resolved.limit}`;
85
+ }
86
+ export function readListValueRows(db, sessionId, address, options) {
87
+ return listValueReadQuery(sessionId, address, options)
88
+ .all(db)
89
+ .map((row) => ({ seq: row.seq, value: JSON.parse(row.value) }));
90
+ }
91
+ //# sourceMappingURL=values.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"values.js","sourceRoot":"","sources":["../../../src/sqlite/session/values.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\ttype ListElement,\n\ttype ListReadOptions,\n\tresolveListReadOptions,\n\ttype StoredValue,\n\ttype Value,\n\ttype ValueList,\n\tvalue,\n} from \"@m4ike1/ion-agent-core\";\nimport { type SqlQuery, sql } from \"../sql.ts\";\nimport type { SqliteDatabase } from \"../types.ts\";\n\nexport interface ScalarValueRow {\n\tnamespace: string;\n\tkey: string;\n\tseq: number;\n\tvalue: string;\n}\n\nexport interface ListValueRow {\n\tseq: number;\n\tvalue: string;\n}\n\nexport function setScalarValueRow(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\tnamespace: string,\n\tkey: string,\n\tseq: number,\n\tstoredValue: unknown,\n): void {\n\tsql`INSERT INTO scalar_values (session_id, namespace, key, seq, value)\n\t\tVALUES (${sessionId}, ${namespace}, ${key}, ${seq}, ${JSON.stringify(storedValue)})\n\t\tON CONFLICT(session_id, namespace, key) DO UPDATE SET seq = excluded.seq, value = excluded.value`.run(db);\n}\n\nexport function deleteScalarValueRow(db: SqliteDatabase, sessionId: string, namespace: string, key: string): void {\n\tsql`DELETE FROM scalar_values\n\t\tWHERE session_id = ${sessionId} AND namespace = ${namespace} AND key = ${key}`.run(db);\n}\n\nexport function appendListValueRow(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\tnamespace: string,\n\tkey: string,\n\tseq: number,\n\telement: unknown,\n): void {\n\tsql`INSERT INTO list_values (session_id, namespace, key, seq, value)\n\t\tVALUES (${sessionId}, ${namespace}, ${key}, ${seq}, ${JSON.stringify(element)})`.run(db);\n}\n\nexport function deleteListValueRows(db: SqliteDatabase, sessionId: string, namespace: string, key: string): void {\n\tsql`DELETE FROM list_values\n\t\tWHERE session_id = ${sessionId} AND namespace = ${namespace} AND key = ${key}`.run(db);\n}\n\nfunction decodeScalarValueRow<T>(address: Value<T>, row: ScalarValueRow): StoredValue<T> {\n\tif (row.namespace !== address.namespace || row.key !== address.key) {\n\t\tthrow new Error(`Expected value ${address.namespace}:${address.key}, found ${row.namespace}:${row.key}`);\n\t}\n\treturn { address, seq: row.seq, value: JSON.parse(row.value) as T };\n}\n\nexport function readScalarValueRow<T>(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\taddress: Value<T>,\n): StoredValue<T> | undefined {\n\tconst row = sql`SELECT namespace, key, seq, value FROM scalar_values\n\t\tWHERE session_id = ${sessionId} AND namespace = ${address.namespace} AND key = ${address.key}`.get<ScalarValueRow>(\n\t\tdb,\n\t);\n\treturn row === undefined ? undefined : decodeScalarValueRow(address, row);\n}\n\nexport function readAllScalarValueRows(db: SqliteDatabase, sessionId: string): StoredValue<unknown>[] {\n\treturn sql`SELECT namespace, key, seq, value FROM scalar_values\n\t\tWHERE session_id = ${sessionId} ORDER BY seq ASC`\n\t\t.all<ScalarValueRow>(db)\n\t\t.map((row) => ({\n\t\t\taddress: value<unknown>(row.namespace, row.key),\n\t\t\tseq: row.seq,\n\t\t\tvalue: JSON.parse(row.value),\n\t\t}));\n}\n\nfunction nextPrefixBoundary(prefix: string): string | undefined {\n\tif (prefix === \"\") return undefined;\n\tconst codePoints = Array.from(prefix);\n\tfor (let index = codePoints.length - 1; index >= 0; index--) {\n\t\tconst codePoint = codePoints[index]?.codePointAt(0);\n\t\tif (codePoint === undefined) throw new Error(\"Invalid value key prefix\");\n\t\tif (codePoint < 0x10ffff) {\n\t\t\tconst nextCodePoint = codePoint >= 0xd7ff && codePoint < 0xe000 ? 0xe000 : codePoint + 1;\n\t\t\treturn `${codePoints.slice(0, index).join(\"\")}${String.fromCodePoint(nextCodePoint)}`;\n\t\t}\n\t}\n\treturn undefined;\n}\n\nexport function scanScalarValueRows<T>(db: SqliteDatabase, sessionId: string, prefix: Value<T>): StoredValue<T>[] {\n\tconst upperBound = nextPrefixBoundary(prefix.key);\n\tconst rows =\n\t\tupperBound === undefined\n\t\t\t? sql`SELECT namespace, key, seq, value FROM scalar_values\n\t\t\t\tWHERE session_id = ${sessionId} AND namespace = ${prefix.namespace} AND key >= ${prefix.key}\n\t\t\t\tORDER BY key ASC`.all<ScalarValueRow>(db)\n\t\t\t: sql`SELECT namespace, key, seq, value FROM scalar_values\n\t\t\t\tWHERE session_id = ${sessionId} AND namespace = ${prefix.namespace} AND key >= ${prefix.key} AND key < ${upperBound}\n\t\t\t\tORDER BY key ASC`.all<ScalarValueRow>(db);\n\treturn rows.map((row) => decodeScalarValueRow(value<T>(row.namespace, row.key), row));\n}\n\nexport function listValueReadQuery<T>(sessionId: string, address: ValueList<T>, options?: ListReadOptions): SqlQuery {\n\tconst resolved = resolveListReadOptions(options);\n\tif (resolved.order === \"asc\") {\n\t\treturn resolved.cursor === undefined\n\t\t\t? sql`SELECT seq, value FROM list_values\n\t\t\t\tWHERE session_id = ${sessionId} AND namespace = ${address.namespace} AND key = ${address.key}\n\t\t\t\tORDER BY seq ASC LIMIT ${resolved.limit}`\n\t\t\t: sql`SELECT seq, value FROM list_values\n\t\t\t\tWHERE session_id = ${sessionId} AND namespace = ${address.namespace} AND key = ${address.key} AND seq > ${resolved.cursor.seq}\n\t\t\t\tORDER BY seq ASC LIMIT ${resolved.limit}`;\n\t}\n\treturn resolved.cursor === undefined\n\t\t? sql`SELECT seq, value FROM list_values\n\t\t\tWHERE session_id = ${sessionId} AND namespace = ${address.namespace} AND key = ${address.key}\n\t\t\tORDER BY seq DESC LIMIT ${resolved.limit}`\n\t\t: sql`SELECT seq, value FROM list_values\n\t\t\tWHERE session_id = ${sessionId} AND namespace = ${address.namespace} AND key = ${address.key} AND seq < ${resolved.cursor.seq}\n\t\t\tORDER BY seq DESC LIMIT ${resolved.limit}`;\n}\n\nexport function readListValueRows<T>(\n\tdb: SqliteDatabase,\n\tsessionId: string,\n\taddress: ValueList<T>,\n\toptions?: ListReadOptions,\n): ListElement<T>[] {\n\treturn listValueReadQuery(sessionId, address, options)\n\t\t.all<ListValueRow>(db)\n\t\t.map((row) => ({ seq: row.seq, value: JSON.parse(row.value) as T }));\n}\n"]}
@@ -0,0 +1,42 @@
1
+ import type { Branch, Context, Entry, EntryQuery, ListElement, ListReadOptions, Session, SessionMutation, SessionMutationCallback, SessionStats, StorageBranchScan, StoredValue, Value, ValueList } from "@m4ike1/ion-agent-core";
2
+ import type { SqliteSessionMetadata } from "./session/session-row.ts";
3
+ export interface SqliteOpenSessionOptions {
4
+ onClose: () => void;
5
+ }
6
+ /** SQLite-specific open-session lifecycle wrapper. */
7
+ export declare class SqliteOpenSession implements Session<SqliteSessionMetadata> {
8
+ readonly metadata: SqliteSessionMetadata;
9
+ readonly idGenerator: Session<SqliteSessionMetadata>["idGenerator"];
10
+ private readonly session;
11
+ private readonly onClose;
12
+ private readonly admitted;
13
+ private readonly closedError;
14
+ private state;
15
+ private closePromise;
16
+ constructor(session: Session<SqliteSessionMetadata>, options: SqliteOpenSessionOptions);
17
+ beginMutation(context: Context): Promise<SessionMutation>;
18
+ mutate<T>(mutation: SessionMutationCallback<T>, context: Context): Promise<T>;
19
+ getEntries(ids: string[], context: Context): Promise<Map<string, Entry>>;
20
+ getEntry(id: string, context: Context): Promise<Entry | undefined>;
21
+ getValue<T>(address: Value<T>, context: Context): Promise<StoredValue<T> | undefined>;
22
+ scanValues<T>(prefix: Value<T>, context: Context): Promise<StoredValue<T>[]>;
23
+ readList<T>(address: ValueList<T>, options: ListReadOptions | undefined, context: Context): Promise<ListElement<T>[]>;
24
+ scanBranch(query: StorageBranchScan, context: Context): Promise<Entry[]>;
25
+ getStats(context: Context): Promise<SessionStats>;
26
+ getName(context: Context): Promise<string | undefined>;
27
+ getLabel(targetId: string, context: Context): Promise<string | undefined>;
28
+ findEntries(query: EntryQuery | undefined, context: Context): Promise<Entry[]>;
29
+ findEntry(query: EntryQuery | undefined, context: Context): Promise<Entry | undefined>;
30
+ branch(name: string, context: Context): Promise<Branch | undefined>;
31
+ createBranch(name: string, at: string | null, context: Context): Promise<Branch>;
32
+ setValue<T>(address: Value<T>, next: NoInfer<T>, context: Context): Promise<void>;
33
+ deleteValue<T>(address: Value<T>, context: Context): Promise<void>;
34
+ appendList<T>(address: ValueList<T>, element: NoInfer<T>, context: Context): Promise<void>;
35
+ deleteList<T>(address: ValueList<T>, context: Context): Promise<void>;
36
+ setName(name: string | undefined, context: Context): Promise<void>;
37
+ setLabel(targetId: string, label: string | undefined, context: Context): Promise<void>;
38
+ close(context: Context): Promise<void>;
39
+ private wrapBranch;
40
+ private admit;
41
+ }
42
+ //# sourceMappingURL=session.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/sqlite/session.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n\tBranch,\n\tContext,\n\tEntry,\n\tEntryQuery,\n\tListElement,\n\tListReadOptions,\n\tSession,\n\tSessionMutation,\n\tSessionMutationCallback,\n\tSessionStats,\n\tStorageBranchScan,\n\tStoredValue,\n\tValue,\n\tValueList,\n} from \"@m4ike1/ion-agent-core\";\nimport type { SqliteSessionMetadata } from \"./session/session-row.ts\";\n\nexport interface SqliteOpenSessionOptions {\n\tonClose: () => void;\n}\n\n/** SQLite-specific open-session lifecycle wrapper. */\nexport class SqliteOpenSession implements Session<SqliteSessionMetadata> {\n\treadonly metadata: SqliteSessionMetadata;\n\treadonly idGenerator: Session<SqliteSessionMetadata>[\"idGenerator\"];\n\tprivate readonly session: Session<SqliteSessionMetadata>;\n\tprivate readonly onClose: () => void;\n\tprivate readonly admitted = new Set<Promise<unknown>>();\n\tprivate readonly closedError = new Error(\"Session is closed\");\n\tprivate state: \"open\" | \"closing\" | \"closed\" = \"open\";\n\tprivate closePromise: Promise<void> | undefined;\n\n\tconstructor(session: Session<SqliteSessionMetadata>, options: SqliteOpenSessionOptions) {\n\t\tthis.session = session;\n\t\tthis.metadata = session.metadata;\n\t\tthis.idGenerator = session.idGenerator;\n\t\tthis.onClose = options.onClose;\n\t}\n\n\tasync beginMutation(context: Context): Promise<SessionMutation> {\n\t\tlet resolveFinished!: () => void;\n\t\tconst finished = new Promise<void>((resolve) => {\n\t\t\tresolveFinished = resolve;\n\t\t});\n\t\tthis.admitted.add(finished);\n\t\tlet source: SessionMutation;\n\t\ttry {\n\t\t\tsource = await this.admit(() => this.session.beginMutation(context));\n\t\t} catch (error) {\n\t\t\tthis.admitted.delete(finished);\n\t\t\tresolveFinished();\n\t\t\tthrow error;\n\t\t}\n\t\tif (this.state !== \"open\") {\n\t\t\tawait source.end(context);\n\t\t\tthis.admitted.delete(finished);\n\t\t\tresolveFinished();\n\t\t\tthrow this.closedError;\n\t\t}\n\t\tlet ended = false;\n\t\treturn {\n\t\t\tcommit: (writes, commitContext) => source.commit(writes, commitContext),\n\t\t\tend: async (endContext) => {\n\t\t\t\ttry {\n\t\t\t\t\tawait source.end(endContext);\n\t\t\t\t} finally {\n\t\t\t\t\tif (!ended) {\n\t\t\t\t\t\tended = true;\n\t\t\t\t\t\tthis.admitted.delete(finished);\n\t\t\t\t\t\tresolveFinished();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\tgetEntries: (ids, readContext) => source.getEntries(ids, readContext),\n\t\t\tgetStats: (readContext) => source.getStats(readContext),\n\t\t\tgetValue: (address, readContext) => source.getValue(address, readContext),\n\t\t\tscanValues: (prefix, readContext) => source.scanValues(prefix, readContext),\n\t\t\treadList: (address, options, readContext) => source.readList(address, options, readContext),\n\t\t\tscanBranch: (query, readContext) => source.scanBranch(query, readContext),\n\t\t};\n\t}\n\n\tmutate<T>(mutation: SessionMutationCallback<T>, context: Context): Promise<T> {\n\t\treturn this.admit(() =>\n\t\t\tthis.session.mutate((mutator, mutationContext) => {\n\t\t\t\tif (this.state !== \"open\") throw this.closedError;\n\t\t\t\treturn mutation(mutator, mutationContext);\n\t\t\t}, context),\n\t\t);\n\t}\n\n\tgetEntries(ids: string[], context: Context): Promise<Map<string, Entry>> {\n\t\treturn this.admit(() => this.session.getEntries(ids, context));\n\t}\n\n\tgetEntry(id: string, context: Context): Promise<Entry | undefined> {\n\t\treturn this.admit(() => this.session.getEntry(id, context));\n\t}\n\n\tgetValue<T>(address: Value<T>, context: Context): Promise<StoredValue<T> | undefined> {\n\t\treturn this.admit(() => this.session.getValue(address, context));\n\t}\n\n\tscanValues<T>(prefix: Value<T>, context: Context): Promise<StoredValue<T>[]> {\n\t\treturn this.admit(() => this.session.scanValues(prefix, context));\n\t}\n\n\treadList<T>(\n\t\taddress: ValueList<T>,\n\t\toptions: ListReadOptions | undefined,\n\t\tcontext: Context,\n\t): Promise<ListElement<T>[]> {\n\t\treturn this.admit(() => this.session.readList(address, options, context));\n\t}\n\n\tscanBranch(query: StorageBranchScan, context: Context): Promise<Entry[]> {\n\t\treturn this.admit(() => this.session.scanBranch(query, context));\n\t}\n\n\tgetStats(context: Context): Promise<SessionStats> {\n\t\treturn this.admit(() => this.session.getStats(context));\n\t}\n\n\tgetName(context: Context): Promise<string | undefined> {\n\t\treturn this.admit(() => this.session.getName(context));\n\t}\n\n\tgetLabel(targetId: string, context: Context): Promise<string | undefined> {\n\t\treturn this.admit(() => this.session.getLabel(targetId, context));\n\t}\n\n\tfindEntries(query: EntryQuery | undefined, context: Context): Promise<Entry[]> {\n\t\treturn this.admit(() => this.session.findEntries(query, context));\n\t}\n\n\tfindEntry(query: EntryQuery | undefined, context: Context): Promise<Entry | undefined> {\n\t\treturn this.admit(() => this.session.findEntry(query, context));\n\t}\n\n\tasync branch(name: string, context: Context): Promise<Branch | undefined> {\n\t\tconst branch = await this.admit(() => this.session.branch(name, context));\n\t\treturn branch === undefined ? undefined : this.wrapBranch(branch);\n\t}\n\n\tasync createBranch(name: string, at: string | null, context: Context): Promise<Branch> {\n\t\treturn this.wrapBranch(await this.admit(() => this.session.createBranch(name, at, context)));\n\t}\n\n\tsetValue<T>(address: Value<T>, next: NoInfer<T>, context: Context): Promise<void> {\n\t\treturn this.admit(() => this.session.setValue(address, next, context));\n\t}\n\n\tdeleteValue<T>(address: Value<T>, context: Context): Promise<void> {\n\t\treturn this.admit(() => this.session.deleteValue(address, context));\n\t}\n\n\tappendList<T>(address: ValueList<T>, element: NoInfer<T>, context: Context): Promise<void> {\n\t\treturn this.admit(() => this.session.appendList(address, element, context));\n\t}\n\n\tdeleteList<T>(address: ValueList<T>, context: Context): Promise<void> {\n\t\treturn this.admit(() => this.session.deleteList(address, context));\n\t}\n\n\tsetName(name: string | undefined, context: Context): Promise<void> {\n\t\treturn this.admit(() => this.session.setName(name, context));\n\t}\n\n\tsetLabel(targetId: string, label: string | undefined, context: Context): Promise<void> {\n\t\treturn this.admit(() => this.session.setLabel(targetId, label, context));\n\t}\n\n\tclose(context: Context): Promise<void> {\n\t\tif (this.closePromise !== undefined) return this.closePromise;\n\t\tthis.state = \"closing\";\n\t\tthis.closePromise = Promise.allSettled([...this.admitted])\n\t\t\t.then(() => this.session.close(context))\n\t\t\t.finally(() => {\n\t\t\t\tthis.state = \"closed\";\n\t\t\t\tthis.onClose();\n\t\t\t});\n\t\treturn this.closePromise;\n\t}\n\n\tprivate wrapBranch(branch: Branch): Branch {\n\t\treturn {\n\t\t\tname: branch.name,\n\t\t\tgetTipId: (context) => this.admit(() => branch.getTipId(context)),\n\t\t\tfindEntries: (query, context) => this.admit(() => branch.findEntries(query, context)),\n\t\t\tfindEntry: (query, context) => this.admit(() => branch.findEntry(query, context)),\n\t\t\tappendMessage: (message, context) => this.admit(() => branch.appendMessage(message, context)),\n\t\t\tappendCustomEntry: (customType, data, context) =>\n\t\t\t\tthis.admit(() => branch.appendCustomEntry(customType, data, context)),\n\t\t};\n\t}\n\n\tprivate admit<T>(operation: () => Promise<T>): Promise<T> {\n\t\tif (this.state !== \"open\") return Promise.reject(this.closedError);\n\t\tlet result: Promise<T>;\n\t\ttry {\n\t\t\tresult = operation();\n\t\t} catch (error) {\n\t\t\tresult = Promise.reject(error);\n\t\t}\n\t\tthis.admitted.add(result);\n\t\tvoid result.then(\n\t\t\t() => this.admitted.delete(result),\n\t\t\t() => this.admitted.delete(result),\n\t\t);\n\t\treturn result;\n\t}\n}\n"]}
@@ -0,0 +1,163 @@
1
+ /** SQLite-specific open-session lifecycle wrapper. */
2
+ export class SqliteOpenSession {
3
+ metadata;
4
+ idGenerator;
5
+ session;
6
+ onClose;
7
+ admitted = new Set();
8
+ closedError = new Error("Session is closed");
9
+ state = "open";
10
+ closePromise;
11
+ constructor(session, options) {
12
+ this.session = session;
13
+ this.metadata = session.metadata;
14
+ this.idGenerator = session.idGenerator;
15
+ this.onClose = options.onClose;
16
+ }
17
+ async beginMutation(context) {
18
+ let resolveFinished;
19
+ const finished = new Promise((resolve) => {
20
+ resolveFinished = resolve;
21
+ });
22
+ this.admitted.add(finished);
23
+ let source;
24
+ try {
25
+ source = await this.admit(() => this.session.beginMutation(context));
26
+ }
27
+ catch (error) {
28
+ this.admitted.delete(finished);
29
+ resolveFinished();
30
+ throw error;
31
+ }
32
+ if (this.state !== "open") {
33
+ await source.end(context);
34
+ this.admitted.delete(finished);
35
+ resolveFinished();
36
+ throw this.closedError;
37
+ }
38
+ let ended = false;
39
+ return {
40
+ commit: (writes, commitContext) => source.commit(writes, commitContext),
41
+ end: async (endContext) => {
42
+ try {
43
+ await source.end(endContext);
44
+ }
45
+ finally {
46
+ if (!ended) {
47
+ ended = true;
48
+ this.admitted.delete(finished);
49
+ resolveFinished();
50
+ }
51
+ }
52
+ },
53
+ getEntries: (ids, readContext) => source.getEntries(ids, readContext),
54
+ getStats: (readContext) => source.getStats(readContext),
55
+ getValue: (address, readContext) => source.getValue(address, readContext),
56
+ scanValues: (prefix, readContext) => source.scanValues(prefix, readContext),
57
+ readList: (address, options, readContext) => source.readList(address, options, readContext),
58
+ scanBranch: (query, readContext) => source.scanBranch(query, readContext),
59
+ };
60
+ }
61
+ mutate(mutation, context) {
62
+ return this.admit(() => this.session.mutate((mutator, mutationContext) => {
63
+ if (this.state !== "open")
64
+ throw this.closedError;
65
+ return mutation(mutator, mutationContext);
66
+ }, context));
67
+ }
68
+ getEntries(ids, context) {
69
+ return this.admit(() => this.session.getEntries(ids, context));
70
+ }
71
+ getEntry(id, context) {
72
+ return this.admit(() => this.session.getEntry(id, context));
73
+ }
74
+ getValue(address, context) {
75
+ return this.admit(() => this.session.getValue(address, context));
76
+ }
77
+ scanValues(prefix, context) {
78
+ return this.admit(() => this.session.scanValues(prefix, context));
79
+ }
80
+ readList(address, options, context) {
81
+ return this.admit(() => this.session.readList(address, options, context));
82
+ }
83
+ scanBranch(query, context) {
84
+ return this.admit(() => this.session.scanBranch(query, context));
85
+ }
86
+ getStats(context) {
87
+ return this.admit(() => this.session.getStats(context));
88
+ }
89
+ getName(context) {
90
+ return this.admit(() => this.session.getName(context));
91
+ }
92
+ getLabel(targetId, context) {
93
+ return this.admit(() => this.session.getLabel(targetId, context));
94
+ }
95
+ findEntries(query, context) {
96
+ return this.admit(() => this.session.findEntries(query, context));
97
+ }
98
+ findEntry(query, context) {
99
+ return this.admit(() => this.session.findEntry(query, context));
100
+ }
101
+ async branch(name, context) {
102
+ const branch = await this.admit(() => this.session.branch(name, context));
103
+ return branch === undefined ? undefined : this.wrapBranch(branch);
104
+ }
105
+ async createBranch(name, at, context) {
106
+ return this.wrapBranch(await this.admit(() => this.session.createBranch(name, at, context)));
107
+ }
108
+ setValue(address, next, context) {
109
+ return this.admit(() => this.session.setValue(address, next, context));
110
+ }
111
+ deleteValue(address, context) {
112
+ return this.admit(() => this.session.deleteValue(address, context));
113
+ }
114
+ appendList(address, element, context) {
115
+ return this.admit(() => this.session.appendList(address, element, context));
116
+ }
117
+ deleteList(address, context) {
118
+ return this.admit(() => this.session.deleteList(address, context));
119
+ }
120
+ setName(name, context) {
121
+ return this.admit(() => this.session.setName(name, context));
122
+ }
123
+ setLabel(targetId, label, context) {
124
+ return this.admit(() => this.session.setLabel(targetId, label, context));
125
+ }
126
+ close(context) {
127
+ if (this.closePromise !== undefined)
128
+ return this.closePromise;
129
+ this.state = "closing";
130
+ this.closePromise = Promise.allSettled([...this.admitted])
131
+ .then(() => this.session.close(context))
132
+ .finally(() => {
133
+ this.state = "closed";
134
+ this.onClose();
135
+ });
136
+ return this.closePromise;
137
+ }
138
+ wrapBranch(branch) {
139
+ return {
140
+ name: branch.name,
141
+ getTipId: (context) => this.admit(() => branch.getTipId(context)),
142
+ findEntries: (query, context) => this.admit(() => branch.findEntries(query, context)),
143
+ findEntry: (query, context) => this.admit(() => branch.findEntry(query, context)),
144
+ appendMessage: (message, context) => this.admit(() => branch.appendMessage(message, context)),
145
+ appendCustomEntry: (customType, data, context) => this.admit(() => branch.appendCustomEntry(customType, data, context)),
146
+ };
147
+ }
148
+ admit(operation) {
149
+ if (this.state !== "open")
150
+ return Promise.reject(this.closedError);
151
+ let result;
152
+ try {
153
+ result = operation();
154
+ }
155
+ catch (error) {
156
+ result = Promise.reject(error);
157
+ }
158
+ this.admitted.add(result);
159
+ void result.then(() => this.admitted.delete(result), () => this.admitted.delete(result));
160
+ return result;
161
+ }
162
+ }
163
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/sqlite/session.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n\tBranch,\n\tContext,\n\tEntry,\n\tEntryQuery,\n\tListElement,\n\tListReadOptions,\n\tSession,\n\tSessionMutation,\n\tSessionMutationCallback,\n\tSessionStats,\n\tStorageBranchScan,\n\tStoredValue,\n\tValue,\n\tValueList,\n} from \"@m4ike1/ion-agent-core\";\nimport type { SqliteSessionMetadata } from \"./session/session-row.ts\";\n\nexport interface SqliteOpenSessionOptions {\n\tonClose: () => void;\n}\n\n/** SQLite-specific open-session lifecycle wrapper. */\nexport class SqliteOpenSession implements Session<SqliteSessionMetadata> {\n\treadonly metadata: SqliteSessionMetadata;\n\treadonly idGenerator: Session<SqliteSessionMetadata>[\"idGenerator\"];\n\tprivate readonly session: Session<SqliteSessionMetadata>;\n\tprivate readonly onClose: () => void;\n\tprivate readonly admitted = new Set<Promise<unknown>>();\n\tprivate readonly closedError = new Error(\"Session is closed\");\n\tprivate state: \"open\" | \"closing\" | \"closed\" = \"open\";\n\tprivate closePromise: Promise<void> | undefined;\n\n\tconstructor(session: Session<SqliteSessionMetadata>, options: SqliteOpenSessionOptions) {\n\t\tthis.session = session;\n\t\tthis.metadata = session.metadata;\n\t\tthis.idGenerator = session.idGenerator;\n\t\tthis.onClose = options.onClose;\n\t}\n\n\tasync beginMutation(context: Context): Promise<SessionMutation> {\n\t\tlet resolveFinished!: () => void;\n\t\tconst finished = new Promise<void>((resolve) => {\n\t\t\tresolveFinished = resolve;\n\t\t});\n\t\tthis.admitted.add(finished);\n\t\tlet source: SessionMutation;\n\t\ttry {\n\t\t\tsource = await this.admit(() => this.session.beginMutation(context));\n\t\t} catch (error) {\n\t\t\tthis.admitted.delete(finished);\n\t\t\tresolveFinished();\n\t\t\tthrow error;\n\t\t}\n\t\tif (this.state !== \"open\") {\n\t\t\tawait source.end(context);\n\t\t\tthis.admitted.delete(finished);\n\t\t\tresolveFinished();\n\t\t\tthrow this.closedError;\n\t\t}\n\t\tlet ended = false;\n\t\treturn {\n\t\t\tcommit: (writes, commitContext) => source.commit(writes, commitContext),\n\t\t\tend: async (endContext) => {\n\t\t\t\ttry {\n\t\t\t\t\tawait source.end(endContext);\n\t\t\t\t} finally {\n\t\t\t\t\tif (!ended) {\n\t\t\t\t\t\tended = true;\n\t\t\t\t\t\tthis.admitted.delete(finished);\n\t\t\t\t\t\tresolveFinished();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\tgetEntries: (ids, readContext) => source.getEntries(ids, readContext),\n\t\t\tgetStats: (readContext) => source.getStats(readContext),\n\t\t\tgetValue: (address, readContext) => source.getValue(address, readContext),\n\t\t\tscanValues: (prefix, readContext) => source.scanValues(prefix, readContext),\n\t\t\treadList: (address, options, readContext) => source.readList(address, options, readContext),\n\t\t\tscanBranch: (query, readContext) => source.scanBranch(query, readContext),\n\t\t};\n\t}\n\n\tmutate<T>(mutation: SessionMutationCallback<T>, context: Context): Promise<T> {\n\t\treturn this.admit(() =>\n\t\t\tthis.session.mutate((mutator, mutationContext) => {\n\t\t\t\tif (this.state !== \"open\") throw this.closedError;\n\t\t\t\treturn mutation(mutator, mutationContext);\n\t\t\t}, context),\n\t\t);\n\t}\n\n\tgetEntries(ids: string[], context: Context): Promise<Map<string, Entry>> {\n\t\treturn this.admit(() => this.session.getEntries(ids, context));\n\t}\n\n\tgetEntry(id: string, context: Context): Promise<Entry | undefined> {\n\t\treturn this.admit(() => this.session.getEntry(id, context));\n\t}\n\n\tgetValue<T>(address: Value<T>, context: Context): Promise<StoredValue<T> | undefined> {\n\t\treturn this.admit(() => this.session.getValue(address, context));\n\t}\n\n\tscanValues<T>(prefix: Value<T>, context: Context): Promise<StoredValue<T>[]> {\n\t\treturn this.admit(() => this.session.scanValues(prefix, context));\n\t}\n\n\treadList<T>(\n\t\taddress: ValueList<T>,\n\t\toptions: ListReadOptions | undefined,\n\t\tcontext: Context,\n\t): Promise<ListElement<T>[]> {\n\t\treturn this.admit(() => this.session.readList(address, options, context));\n\t}\n\n\tscanBranch(query: StorageBranchScan, context: Context): Promise<Entry[]> {\n\t\treturn this.admit(() => this.session.scanBranch(query, context));\n\t}\n\n\tgetStats(context: Context): Promise<SessionStats> {\n\t\treturn this.admit(() => this.session.getStats(context));\n\t}\n\n\tgetName(context: Context): Promise<string | undefined> {\n\t\treturn this.admit(() => this.session.getName(context));\n\t}\n\n\tgetLabel(targetId: string, context: Context): Promise<string | undefined> {\n\t\treturn this.admit(() => this.session.getLabel(targetId, context));\n\t}\n\n\tfindEntries(query: EntryQuery | undefined, context: Context): Promise<Entry[]> {\n\t\treturn this.admit(() => this.session.findEntries(query, context));\n\t}\n\n\tfindEntry(query: EntryQuery | undefined, context: Context): Promise<Entry | undefined> {\n\t\treturn this.admit(() => this.session.findEntry(query, context));\n\t}\n\n\tasync branch(name: string, context: Context): Promise<Branch | undefined> {\n\t\tconst branch = await this.admit(() => this.session.branch(name, context));\n\t\treturn branch === undefined ? undefined : this.wrapBranch(branch);\n\t}\n\n\tasync createBranch(name: string, at: string | null, context: Context): Promise<Branch> {\n\t\treturn this.wrapBranch(await this.admit(() => this.session.createBranch(name, at, context)));\n\t}\n\n\tsetValue<T>(address: Value<T>, next: NoInfer<T>, context: Context): Promise<void> {\n\t\treturn this.admit(() => this.session.setValue(address, next, context));\n\t}\n\n\tdeleteValue<T>(address: Value<T>, context: Context): Promise<void> {\n\t\treturn this.admit(() => this.session.deleteValue(address, context));\n\t}\n\n\tappendList<T>(address: ValueList<T>, element: NoInfer<T>, context: Context): Promise<void> {\n\t\treturn this.admit(() => this.session.appendList(address, element, context));\n\t}\n\n\tdeleteList<T>(address: ValueList<T>, context: Context): Promise<void> {\n\t\treturn this.admit(() => this.session.deleteList(address, context));\n\t}\n\n\tsetName(name: string | undefined, context: Context): Promise<void> {\n\t\treturn this.admit(() => this.session.setName(name, context));\n\t}\n\n\tsetLabel(targetId: string, label: string | undefined, context: Context): Promise<void> {\n\t\treturn this.admit(() => this.session.setLabel(targetId, label, context));\n\t}\n\n\tclose(context: Context): Promise<void> {\n\t\tif (this.closePromise !== undefined) return this.closePromise;\n\t\tthis.state = \"closing\";\n\t\tthis.closePromise = Promise.allSettled([...this.admitted])\n\t\t\t.then(() => this.session.close(context))\n\t\t\t.finally(() => {\n\t\t\t\tthis.state = \"closed\";\n\t\t\t\tthis.onClose();\n\t\t\t});\n\t\treturn this.closePromise;\n\t}\n\n\tprivate wrapBranch(branch: Branch): Branch {\n\t\treturn {\n\t\t\tname: branch.name,\n\t\t\tgetTipId: (context) => this.admit(() => branch.getTipId(context)),\n\t\t\tfindEntries: (query, context) => this.admit(() => branch.findEntries(query, context)),\n\t\t\tfindEntry: (query, context) => this.admit(() => branch.findEntry(query, context)),\n\t\t\tappendMessage: (message, context) => this.admit(() => branch.appendMessage(message, context)),\n\t\t\tappendCustomEntry: (customType, data, context) =>\n\t\t\t\tthis.admit(() => branch.appendCustomEntry(customType, data, context)),\n\t\t};\n\t}\n\n\tprivate admit<T>(operation: () => Promise<T>): Promise<T> {\n\t\tif (this.state !== \"open\") return Promise.reject(this.closedError);\n\t\tlet result: Promise<T>;\n\t\ttry {\n\t\t\tresult = operation();\n\t\t} catch (error) {\n\t\t\tresult = Promise.reject(error);\n\t\t}\n\t\tthis.admitted.add(result);\n\t\tvoid result.then(\n\t\t\t() => this.admitted.delete(result),\n\t\t\t() => this.admitted.delete(result),\n\t\t);\n\t\treturn result;\n\t}\n}\n"]}
@@ -0,0 +1,19 @@
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
+ iterate<TRow extends object>(db: SqliteDatabase): Iterable<TRow>;
13
+ }
14
+ /** Builds a parameterized query. Nested queries are inlined; other interpolations become `?` parameters. */
15
+ export declare function sql(strings: TemplateStringsArray, ...values: SqlTemplateValue[]): SqlQuery;
16
+ /** Joins trusted query fragments while preserving their parameter order. */
17
+ export declare function joinSqlFragments(fragments: readonly SqlQuery[], separator: string): SqlQuery;
18
+ export {};
19
+ //# 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;IAED,OAAO,CAAC,IAAI,SAAS,MAAM,EAAE,EAAE,EAAE,cAAc,GAAG,QAAQ,CAAC,IAAI,CAAC,CAE/D;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\titerate<TRow extends object>(db: SqliteDatabase): Iterable<TRow> {\n\t\treturn db.prepare(this.queryText).iterate<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"]}