@rotorsoft/act-sqlite 1.4.2 → 1.5.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.
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite-store.d.ts","sourceRoot":"","sources":["../../src/sqlite-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sqlite-store.d.ts","sourceRoot":"","sources":["../../src/sqlite-store.ts"],"names":[],"mappings":"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"}
package/dist/index.cjs CHANGED
@@ -37,11 +37,10 @@ module.exports = __toCommonJS(index_exports);
37
37
 
38
38
  // src/sqlite-store.ts
39
39
  var import_client = require("@libsql/client");
40
+ var import_act_crypto = require("@rotorsoft/act-crypto");
40
41
  var DEFAULT_CONFIG = {
41
42
  url: "file::memory:"
42
43
  };
43
- var parse_pii = (raw) => raw == null ? null : JSON.parse(raw);
44
- var stringify_pii = (pii) => pii == null ? null : JSON.stringify(pii);
45
44
  function streamPatternToLike(input) {
46
45
  let s = input;
47
46
  const start = s.startsWith("^");
@@ -54,12 +53,53 @@ function streamPatternToLike(input) {
54
53
  }
55
54
  var SqliteStore = class {
56
55
  client;
56
+ /**
57
+ * Memoized key resolver for the optional `pii_encryption` envelope.
58
+ * Initialized in the constructor when encryption is configured;
59
+ * `undefined` otherwise. The resolver caches the operator's key on
60
+ * first use — rotation means restarting the store with a fresh
61
+ * provider.
62
+ */
63
+ _resolve_pii_key;
57
64
  constructor(config = {}) {
58
65
  const cfg = { ...DEFAULT_CONFIG, ...config };
59
66
  this.client = (0, import_client.createClient)({
60
67
  url: cfg.url,
61
68
  authToken: cfg.authToken
62
69
  });
70
+ this._resolve_pii_key = cfg.pii_encryption ? (0, import_act_crypto.makeKeyResolver)(cfg.pii_encryption) : void 0;
71
+ }
72
+ /**
73
+ * Write-side pii encoder: encrypts when `pii_encryption` is configured,
74
+ * stringifies for the TEXT column either way. `null` passes through so
75
+ * `forget_pii` semantics survive intact (the row format skips the
76
+ * column entirely on a `NULL` write).
77
+ *
78
+ * @internal
79
+ */
80
+ async _stringify_pii_for_write(pii) {
81
+ if (pii == null) return null;
82
+ if (this._resolve_pii_key) {
83
+ return JSON.stringify(await (0, import_act_crypto.encrypt)(pii, this._resolve_pii_key));
84
+ }
85
+ return JSON.stringify(pii);
86
+ }
87
+ /**
88
+ * Read-side pii decoder: parses the TEXT column, and when
89
+ * `pii_encryption` is configured, transparently decrypts any value
90
+ * that came back as a JSON string. Plaintext writes deserialize to
91
+ * objects and pass through — which is what makes mixed-data
92
+ * rollouts (some pre-encryption rows, some post-) read cleanly.
93
+ *
94
+ * @internal
95
+ */
96
+ async _parse_pii_from_read(raw) {
97
+ if (raw == null) return null;
98
+ const parsed = JSON.parse(raw);
99
+ if (this._resolve_pii_key && typeof parsed === "string") {
100
+ return await (0, import_act_crypto.decrypt)(parsed, this._resolve_pii_key);
101
+ }
102
+ return parsed;
63
103
  }
64
104
  async seed() {
65
105
  await this.client.execute("PRAGMA journal_mode=WAL");
@@ -149,6 +189,7 @@ var SqliteStore = class {
149
189
  const committed = [];
150
190
  let version = current_version + 1;
151
191
  for (const { name, data, pii } of msgs) {
192
+ const pii_for_write = await this._stringify_pii_for_write(pii);
152
193
  const result = await tx.execute({
153
194
  sql: "INSERT INTO events (stream, version, name, data, meta, created, pii) VALUES (?, ?, ?, ?, ?, ?, ?)",
154
195
  args: [
@@ -158,7 +199,7 @@ var SqliteStore = class {
158
199
  JSON.stringify(data),
159
200
  JSON.stringify(meta),
160
201
  now,
161
- stringify_pii(pii)
202
+ pii_for_write
162
203
  ]
163
204
  });
164
205
  committed.push({
@@ -228,6 +269,7 @@ var SqliteStore = class {
228
269
  const result = await this.client.execute({ sql, args });
229
270
  let count = 0;
230
271
  for (const row of result.rows) {
272
+ const pii_value = await this._parse_pii_from_read(row.pii);
231
273
  await Promise.resolve(
232
274
  callback({
233
275
  id: Number(row.id),
@@ -237,7 +279,7 @@ var SqliteStore = class {
237
279
  name: row.name,
238
280
  data: JSON.parse(row.data),
239
281
  meta: JSON.parse(row.meta),
240
- pii: parse_pii(row.pii)
282
+ pii: pii_value
241
283
  })
242
284
  );
243
285
  count++;
@@ -659,7 +701,7 @@ var SqliteStore = class {
659
701
  this.client.execute({ sql: head_sql, args }),
660
702
  tail_sql ? this.client.execute({ sql: tail_sql, args }) : null
661
703
  ]);
662
- const to_committed = (row) => ({
704
+ const to_committed = async (row) => ({
663
705
  id: Number(row.id),
664
706
  stream: row.stream,
665
707
  version: Number(row.version),
@@ -667,17 +709,17 @@ var SqliteStore = class {
667
709
  data: JSON.parse(row.data),
668
710
  meta: JSON.parse(row.meta),
669
711
  created: new Date(row.created),
670
- pii: parse_pii(row.pii)
712
+ pii: await this._parse_pii_from_read(row.pii)
671
713
  });
672
714
  const out = /* @__PURE__ */ new Map();
673
715
  for (const row of headRes.rows) {
674
716
  out.set(row.stream, {
675
- head: to_committed(row)
717
+ head: await to_committed(row)
676
718
  });
677
719
  }
678
720
  if (tailRes) {
679
721
  for (const row of tailRes.rows) {
680
- out.get(row.stream).tail = to_committed(row);
722
+ out.get(row.stream).tail = await to_committed(row);
681
723
  }
682
724
  }
683
725
  return out;
@@ -729,7 +771,7 @@ var SqliteStore = class {
729
771
  ${tail_join}
730
772
  `;
731
773
  const res = await this.client.execute({ sql, args });
732
- const to_committed = (id, stream, version, name, data, meta, created, pii) => ({
774
+ const to_committed = async (id, stream, version, name, data, meta, created, pii) => ({
733
775
  id: Number(id),
734
776
  stream,
735
777
  version: Number(version),
@@ -737,13 +779,13 @@ var SqliteStore = class {
737
779
  data: JSON.parse(data),
738
780
  meta: JSON.parse(meta),
739
781
  created: new Date(created),
740
- pii: parse_pii(pii)
782
+ pii: await this._parse_pii_from_read(pii)
741
783
  });
742
784
  const out = /* @__PURE__ */ new Map();
743
785
  for (const row of res.rows) {
744
786
  const r = row;
745
787
  const stats = {
746
- head: to_committed(
788
+ head: await to_committed(
747
789
  r.id,
748
790
  r.stream,
749
791
  r.version,
@@ -755,7 +797,7 @@ var SqliteStore = class {
755
797
  )
756
798
  };
757
799
  if (want_tail && r.t_id !== null && r.t_id !== void 0) {
758
- stats.tail = to_committed(
800
+ stats.tail = await to_committed(
759
801
  r.t_id,
760
802
  r.t_stream,
761
803
  r.t_version,
@@ -853,6 +895,7 @@ var SqliteStore = class {
853
895
  await tx.execute("DELETE FROM streams");
854
896
  await tx.execute("DELETE FROM sqlite_sequence WHERE name = 'events'");
855
897
  await driver(async (event) => {
898
+ const pii_for_write = await this._stringify_pii_for_write(event.pii);
856
899
  const ins = await tx.execute({
857
900
  sql: "INSERT INTO events (stream, version, name, data, meta, created, pii) VALUES (?, ?, ?, ?, ?, ?, ?)",
858
901
  args: [
@@ -862,7 +905,7 @@ var SqliteStore = class {
862
905
  JSON.stringify(event.data),
863
906
  JSON.stringify(event.meta),
864
907
  event.created.toISOString(),
865
- stringify_pii(event.pii)
908
+ pii_for_write
866
909
  ]
867
910
  });
868
911
  return Number(ins.lastInsertRowid);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/sqlite-store.ts"],"sourcesContent":["/**\n * @packageDocumentation\n * @module act-sqlite\n * Main entry point for the Act-SQLite adapter. Re-exports all core APIs\n */\nexport * from \"./sqlite-store.js\";\n","import { type Client, createClient } from \"@libsql/client\";\nimport type {\n BlockedLease,\n Committed,\n EventMeta,\n Lease,\n Message,\n Query,\n QueryStatsOptions,\n QueryStreams,\n QueryStreamsResult,\n Schemas,\n Store,\n StreamFilter,\n StreamPosition,\n StreamStats,\n} from \"@rotorsoft/act\";\n\n/**\n * SQLite store configuration\n */\nexport interface SqliteConfig {\n /** Path to the SQLite database file (default: \":memory:\") */\n url: string;\n /** Auth token for libSQL server connections (optional) */\n authToken?: string;\n}\n\nconst DEFAULT_CONFIG: SqliteConfig = {\n url: \"file::memory:\",\n};\n\n/** Parse the JSON-stringified `pii` text column back to the in-memory\n * shape. Returns `null` when the column was NULL (the common case —\n * events without sensitive declarations). Consolidated so every read\n * path (`query`, both `query_stats` overloads) uses one branch instead\n * of repeating the ternary inline. */\nconst parse_pii = (raw: unknown): Record<string, unknown> | null =>\n raw == null ? null : JSON.parse(raw as string);\n\n/** Stringify the in-memory `pii` shape for the TEXT column. Returns\n * `null` when `pii` is absent so SQLite's row format can skip the\n * column entirely (zero extra bytes). Used by commit + restore. */\nconst stringify_pii = (\n pii: Readonly<Record<string, unknown>> | null | undefined\n): string | null => (pii == null ? null : JSON.stringify(pii));\n\n/** Translate a stream filter (regex-shaped or plain substring) into a\n * SQL LIKE pattern. Honors `^` / `$` anchors and converts `.*` → `%`,\n * `.` → `_`. Unanchored input gets `%` wildcards on both sides.\n *\n * Examples:\n * - `^abc$` → `abc` (exact)\n * - `^abc.*` → `abc%` (starts-with)\n * - `.*abc$` → `%abc` (ends-with)\n * - `abc` → `%abc%` (contains)\n * - `a.c` → `%a_c%` (single-char wildcard, contains)\n *\n * @internal exported for testing\n */\nexport function streamPatternToLike(input: string): string {\n let s = input;\n const start = s.startsWith(\"^\");\n const end = s.endsWith(\"$\");\n if (start) s = s.slice(1);\n if (end) s = s.slice(0, -1);\n s = s.replace(/\\.\\*/g, \"%\").replace(/\\./g, \"_\");\n const out = (start ? \"\" : \"%\") + s + (end ? \"\" : \"%\");\n // Collapse adjacent `%` — e.g. `^a.*` would otherwise yield `a%%`.\n // Same matching semantics, cleaner output.\n return out.replace(/%+/g, \"%\");\n}\n\n/**\n * SQLite event store adapter for [@rotorsoft/act](https://www.npmjs.com/package/@rotorsoft/act).\n *\n * Provides persistent event storage using SQLite via `@libsql/client`.\n * All write operations use transactions for ACID guarantees.\n * Since SQLite serializes writes at the database level, the concurrency\n * model is equivalent to PostgreSQL's `FOR UPDATE SKIP LOCKED` for\n * single-server deployments.\n *\n * **`Store.notify` is intentionally not implemented.** The notify hook is\n * a cross-process wake-up signal that lets a horizontally-scaled Act\n * deployment wake `settle()` immediately on remote commits. SQLite is\n * single-node by design — there is no remote writer to be notified of —\n * so the {@link Act} orchestrator falls back to the existing\n * debounce/poll path, which is correct for this topology.\n *\n * @example\n * ```typescript\n * import { store } from \"@rotorsoft/act\";\n * import { SqliteStore } from \"@rotorsoft/act-sqlite\";\n *\n * store(new SqliteStore({ url: \"file:myapp.db\" }));\n * await store().seed();\n * ```\n */\nexport class SqliteStore implements Store {\n private client: Client;\n\n constructor(config: Partial<SqliteConfig> = {}) {\n const cfg = { ...DEFAULT_CONFIG, ...config };\n this.client = createClient({\n url: cfg.url,\n authToken: cfg.authToken,\n });\n }\n\n async seed() {\n await this.client.execute(\"PRAGMA journal_mode=WAL\");\n await this.client.execute(`\n CREATE TABLE IF NOT EXISTS events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n stream TEXT NOT NULL,\n version INTEGER NOT NULL,\n name TEXT NOT NULL,\n data TEXT NOT NULL,\n meta TEXT NOT NULL,\n created TEXT NOT NULL,\n pii TEXT,\n UNIQUE(stream, version)\n )\n `);\n // Migration for tables created before pii_isolation (#871).\n // libSQL surfaces \"duplicate column\" as an error, hence the\n // try/swallow — mirrors PG's `ADD COLUMN IF NOT EXISTS`. SQLite's\n // row format skips NULL columns, so events without sensitive\n // declarations pay zero extra bytes.\n try {\n await this.client.execute(\"ALTER TABLE events ADD COLUMN pii TEXT\");\n } catch {\n // already present\n }\n await this.client.execute(\n \"CREATE INDEX IF NOT EXISTS idx_events_stream ON events(stream)\"\n );\n await this.client.execute(\n \"CREATE INDEX IF NOT EXISTS idx_events_name ON events(name)\"\n );\n await this.client.execute(`\n CREATE TABLE IF NOT EXISTS streams (\n stream TEXT PRIMARY KEY,\n source TEXT,\n at INTEGER NOT NULL DEFAULT -1,\n retry INTEGER NOT NULL DEFAULT 0,\n blocked INTEGER NOT NULL DEFAULT 0,\n error TEXT NOT NULL DEFAULT '',\n leased_by TEXT,\n leased_until TEXT,\n priority INTEGER NOT NULL DEFAULT 0,\n lane TEXT NOT NULL DEFAULT 'default'\n )\n `);\n // Migration for tables created before priority lanes (ACT-102).\n // libSQL surfaces \"duplicate column\" as an error, hence the\n // try/swallow — this mirrors PG's `ADD COLUMN IF NOT EXISTS`.\n try {\n await this.client.execute(\n \"ALTER TABLE streams ADD COLUMN priority INTEGER NOT NULL DEFAULT 0\"\n );\n } catch {\n // already present\n }\n // Migration for tables created before drain lanes (ACT-1103).\n try {\n await this.client.execute(\n \"ALTER TABLE streams ADD COLUMN lane TEXT NOT NULL DEFAULT 'default'\"\n );\n } catch {\n // already present\n }\n await this.client.execute(\n \"CREATE INDEX IF NOT EXISTS idx_streams_claim ON streams(blocked, priority DESC, at)\"\n );\n // Lane filter index (ACT-1103).\n await this.client.execute(\n \"CREATE INDEX IF NOT EXISTS idx_streams_lane ON streams(lane)\"\n );\n }\n\n async drop() {\n await this.client.execute(\"DROP TABLE IF EXISTS events\");\n await this.client.execute(\"DROP TABLE IF EXISTS streams\");\n }\n\n async dispose() {\n await Promise.resolve();\n this.client.close();\n }\n\n // --- commit: transaction + optimistic concurrency ---\n async commit<E extends Schemas>(\n stream: string,\n msgs: Message<E, keyof E>[],\n meta: EventMeta,\n expectedVersion?: number\n ): Promise<Committed<E, keyof E>[]> {\n const tx = await this.client.transaction(\"write\");\n try {\n const version_row = await tx.execute({\n sql: \"SELECT COALESCE(MAX(version), -1) as v FROM events WHERE stream = ?\",\n args: [stream],\n });\n const current_version = Number(version_row.rows[0].v);\n\n if (\n typeof expectedVersion === \"number\" &&\n current_version !== expectedVersion\n ) {\n const { ConcurrencyError } = await import(\"@rotorsoft/act\");\n throw new ConcurrencyError(\n stream,\n current_version,\n msgs as Message<Schemas, keyof Schemas>[],\n expectedVersion\n );\n }\n\n const now = new Date().toISOString();\n const committed: Committed<E, keyof E>[] = [];\n let version = current_version + 1;\n\n for (const { name, data, pii } of msgs) {\n const result = await tx.execute({\n sql: \"INSERT INTO events (stream, version, name, data, meta, created, pii) VALUES (?, ?, ?, ?, ?, ?, ?)\",\n args: [\n stream,\n version,\n name as string,\n JSON.stringify(data),\n JSON.stringify(meta),\n now,\n stringify_pii(pii),\n ],\n });\n committed.push({\n id: Number(result.lastInsertRowid),\n stream,\n version,\n created: new Date(now),\n name,\n data,\n meta,\n ...(pii == null ? {} : { pii }),\n });\n version++;\n }\n\n await tx.commit();\n return committed;\n } catch (e) {\n await tx.rollback();\n throw e;\n }\n }\n\n // --- query: read-only, no transaction needed ---\n async query<E extends Schemas>(\n callback: (event: Committed<E, keyof E>) => void,\n query?: Query\n ): Promise<number> {\n let sql = \"SELECT * FROM events WHERE 1=1\";\n const args: unknown[] = [];\n\n if (query?.stream) {\n if (query.stream_exact) {\n sql += \" AND stream = ?\";\n args.push(query.stream);\n } else {\n sql += \" AND stream LIKE ?\";\n args.push(streamPatternToLike(query.stream));\n }\n }\n if (query?.names) {\n sql += ` AND name IN (${query.names.map(() => \"?\").join(\",\")})`;\n args.push(...query.names);\n }\n if ((query as any)?.correlation) {\n sql += \" AND json_extract(meta, '$.correlation') = ?\";\n args.push((query as any).correlation);\n }\n if (query?.after !== undefined) {\n sql += \" AND id > ?\";\n args.push(query.after);\n }\n if (query?.before !== undefined) {\n sql += \" AND id < ?\";\n args.push(query.before);\n }\n if (query?.created_after) {\n sql += \" AND created > ?\";\n args.push(query.created_after.toISOString());\n }\n if (query?.created_before) {\n sql += \" AND created < ?\";\n args.push(query.created_before.toISOString());\n }\n if (!query?.with_snaps) {\n sql += \" AND name != '__snapshot__'\";\n }\n\n sql += query?.backward ? \" ORDER BY id DESC\" : \" ORDER BY id ASC\";\n\n if (query?.limit) {\n sql += \" LIMIT ?\";\n args.push(query.limit);\n }\n\n const result = await this.client.execute({ sql, args: args as any[] });\n let count = 0;\n\n for (const row of result.rows) {\n await Promise.resolve(\n callback({\n id: Number(row.id),\n stream: row.stream as string,\n version: Number(row.version),\n created: new Date(row.created as string),\n name: row.name as string,\n data: JSON.parse(row.data as string),\n meta: JSON.parse(row.meta as string),\n pii: parse_pii(row.pii),\n })\n );\n count++;\n }\n\n return count;\n }\n\n // --- subscribe: idempotent INSERT OR IGNORE (= PG ON CONFLICT DO NOTHING)\n // plus a UPDATE pass to keep the *max* priority across reactions\n // targeting the same stream (ACT-102). Operator overrides go\n // through `prioritize()` instead.\n async subscribe(\n streams: Array<{\n stream: string;\n source?: string;\n priority?: number;\n lane?: string;\n }>\n ) {\n const tx = await this.client.transaction(\"write\");\n try {\n let subscribed = 0;\n for (const {\n stream,\n source,\n priority = 0,\n lane = \"default\",\n } of streams) {\n const inserted = await tx.execute({\n sql: \"INSERT OR IGNORE INTO streams (stream, source, priority, lane) VALUES (?, ?, ?, ?)\",\n args: [stream, source ?? null, priority, lane],\n });\n if (inserted.rowsAffected > 0) {\n subscribed++;\n } else {\n if (priority > 0) {\n await tx.execute({\n sql: \"UPDATE streams SET priority = ? WHERE stream = ? AND priority < ?\",\n args: [priority, stream, priority],\n });\n }\n // ACT-1103: current subscribe wins on lane.\n await tx.execute({\n sql: \"UPDATE streams SET lane = ? WHERE stream = ? AND lane <> ?\",\n args: [lane, stream, lane],\n });\n }\n }\n const wm = await tx.execute(\n \"SELECT COALESCE(MAX(at), -1) as w FROM streams\"\n );\n await tx.commit();\n return { subscribed, watermark: Number(wm.rows[0].w) };\n } catch (e) {\n await tx.rollback();\n throw e;\n }\n }\n\n // --- claim: write transaction (SQLite serializes writes = equivalent\n // to PG FOR UPDATE SKIP LOCKED for single-server) ---\n async claim(\n lagging: number,\n leading: number,\n by: string,\n millis: number,\n lane?: string\n ) {\n const tx = await this.client.transaction(\"write\");\n try {\n const now = new Date().toISOString();\n\n const lane_clause = lane !== undefined ? \" AND lane = ?\" : \"\";\n const result = await tx.execute({\n sql: `SELECT stream, source, at, priority, lane FROM streams\n WHERE blocked = 0 AND (leased_until IS NULL OR leased_until <= ?)${lane_clause}\n ORDER BY priority DESC, at ASC`,\n args: lane !== undefined ? [now, lane] : [now],\n });\n\n const candidates: {\n stream: string;\n source: string | undefined;\n at: number;\n priority: number;\n lane: string;\n }[] = [];\n for (const row of result.rows) {\n const stream = row.stream as string;\n const source = row.source as string | null;\n const at = Number(row.at);\n\n let has_events: boolean;\n if (source) {\n const check = await tx.execute({\n sql: `SELECT 1 FROM events WHERE id > ? AND name != '__snapshot__' AND stream LIKE ? LIMIT 1`,\n args: [at, streamPatternToLike(source)],\n });\n has_events = check.rows.length > 0;\n } else {\n const check = await tx.execute({\n sql: `SELECT 1 FROM events WHERE id > ? AND name != '__snapshot__' LIMIT 1`,\n args: [at],\n });\n has_events = check.rows.length > 0;\n }\n\n if (has_events) {\n candidates.push({\n stream,\n source: source ?? undefined,\n at,\n priority: Number(row.priority),\n lane: row.lane as string,\n });\n }\n }\n\n // Dual frontier: lagging (priority DESC, watermark ASC — ACT-102)\n // + leading (newest first). The candidates list arrives sorted\n // by `priority DESC, at ASC` from the SELECT above, so the\n // `slice(0, lagging)` already does the right thing.\n const lag = candidates.slice(0, lagging);\n const lead = [...candidates]\n .sort((a, b) => b.at - a.at)\n .slice(0, leading);\n const seen = new Set<string>();\n const combined = [...lag, ...lead].filter((p) => {\n if (seen.has(p.stream)) return false;\n seen.add(p.stream);\n return true;\n });\n\n const leases: Lease[] = [];\n const until = new Date(Date.now() + millis).toISOString();\n for (const row of combined) {\n await tx.execute({\n sql: \"UPDATE streams SET leased_by = ?, leased_until = ?, retry = retry + 1 WHERE stream = ?\",\n args: [by, until, row.stream],\n });\n leases.push({\n stream: row.stream,\n source: row.source,\n at: row.at,\n by,\n retry: 0,\n lagging: row.at < 0,\n lane: row.lane,\n });\n }\n\n await tx.commit();\n return leases;\n } catch (e) {\n await tx.rollback();\n throw e;\n }\n }\n\n // --- ack: transaction + ownership check (= PG WHERE leased_by) ---\n async ack(leases: Lease[]) {\n const tx = await this.client.transaction(\"write\");\n try {\n const result: Lease[] = [];\n for (const l of leases) {\n const r = await tx.execute({\n sql: `UPDATE streams SET at = ?, leased_by = NULL, leased_until = NULL, retry = -1\n WHERE stream = ? AND leased_by = ?`,\n args: [l.at, l.stream, l.by],\n });\n if (r.rowsAffected > 0) result.push(l);\n }\n await tx.commit();\n return result;\n } catch (e) {\n await tx.rollback();\n throw e;\n }\n }\n\n // --- block: transaction + ownership + idempotent (= PG) ---\n async block(leases: BlockedLease[]) {\n const tx = await this.client.transaction(\"write\");\n try {\n const result: BlockedLease[] = [];\n for (const l of leases) {\n const r = await tx.execute({\n sql: `UPDATE streams SET blocked = 1, error = ?\n WHERE stream = ? AND leased_by = ? AND blocked = 0`,\n args: [l.error, l.stream, l.by],\n });\n if (r.rowsAffected > 0) result.push(l);\n }\n await tx.commit();\n return result;\n } catch (e) {\n await tx.rollback();\n throw e;\n }\n }\n\n /**\n * Translate a {@link StreamFilter} to a SQLite `WHERE` clause fragment\n * plus positional args. Returns `\"1\"` (always true) when empty so\n * callers can compose it unconditionally.\n */\n private _filter_clause(filter: StreamFilter): {\n clause: string;\n args: unknown[];\n } {\n const conditions: string[] = [];\n const args: unknown[] = [];\n if (filter.stream !== undefined) {\n if (filter.stream_exact) {\n conditions.push(\"stream = ?\");\n args.push(filter.stream);\n } else {\n conditions.push(\"stream LIKE ?\");\n args.push(streamPatternToLike(filter.stream));\n }\n }\n if (filter.source !== undefined) {\n conditions.push(\"source IS NOT NULL\");\n if (filter.source_exact) {\n conditions.push(\"source = ?\");\n args.push(filter.source);\n } else {\n conditions.push(\"source LIKE ?\");\n args.push(streamPatternToLike(filter.source));\n }\n }\n if (filter.blocked !== undefined) {\n conditions.push(\"blocked = ?\");\n args.push(filter.blocked ? 1 : 0);\n }\n if (filter.lane !== undefined) {\n conditions.push(\"lane = ?\");\n args.push(filter.lane);\n }\n return { clause: conditions.length ? conditions.join(\" AND \") : \"1\", args };\n }\n\n // --- reset: transactional, accepts names or filter ---\n async reset(input: string[] | StreamFilter) {\n const set_clause = `SET at = -1, retry = 0, blocked = 0, error = '',\n leased_by = NULL, leased_until = NULL`;\n const tx = await this.client.transaction(\"write\");\n try {\n let count = 0;\n if (Array.isArray(input)) {\n for (const stream of input) {\n const r = await tx.execute({\n sql: `UPDATE streams ${set_clause} WHERE stream = ?`,\n args: [stream],\n });\n count += r.rowsAffected;\n }\n } else {\n const { clause, args } = this._filter_clause(input);\n const r = await tx.execute({\n sql: `UPDATE streams ${set_clause} WHERE ${clause}`,\n args: args as any[],\n });\n count = r.rowsAffected;\n }\n await tx.commit();\n return count;\n } catch (e) {\n await tx.rollback();\n throw e;\n }\n }\n\n // --- unblock: clear blocked + retry + lease without touching watermark ---\n // `retry = -1` so claim's post-bump returns retry=0 (first attempt),\n // matching the InMemoryStore convention.\n async unblock(input: string[] | StreamFilter) {\n const set_clause = `SET retry = -1, blocked = 0, error = '',\n leased_by = NULL, leased_until = NULL`;\n const tx = await this.client.transaction(\"write\");\n try {\n let count = 0;\n if (Array.isArray(input)) {\n for (const stream of input) {\n const r = await tx.execute({\n sql: `UPDATE streams ${set_clause}\n WHERE stream = ? AND blocked = 1`,\n args: [stream],\n });\n count += r.rowsAffected;\n }\n } else {\n // Filter form: force blocked = true regardless of what the\n // caller passed.\n const { clause, args } = this._filter_clause({\n ...input,\n blocked: true,\n });\n const r = await tx.execute({\n sql: `UPDATE streams ${set_clause} WHERE ${clause}`,\n args: args as any[],\n });\n count = r.rowsAffected;\n }\n await tx.commit();\n return count;\n } catch (e) {\n await tx.rollback();\n throw e;\n }\n }\n\n // --- query_streams: read-only introspection with filters ---\n async query_streams(\n callback: (position: StreamPosition) => void,\n query?: QueryStreams\n ): Promise<QueryStreamsResult> {\n const limit = query?.limit ?? 100;\n let sql =\n \"SELECT stream, source, at, retry, blocked, error, leased_by, leased_until, priority, lane FROM streams WHERE 1=1\";\n const args: unknown[] = [];\n\n if (query?.stream !== undefined) {\n if (query.stream_exact) {\n sql += \" AND stream = ?\";\n args.push(query.stream);\n } else {\n sql += \" AND stream LIKE ?\";\n args.push(streamPatternToLike(query.stream));\n }\n }\n if (query?.source !== undefined) {\n sql += \" AND source IS NOT NULL\";\n if (query.source_exact) {\n sql += \" AND source = ?\";\n args.push(query.source);\n } else {\n sql += \" AND source LIKE ?\";\n args.push(streamPatternToLike(query.source));\n }\n }\n if (query?.blocked !== undefined) {\n sql += \" AND blocked = ?\";\n args.push(query.blocked ? 1 : 0);\n }\n if (query?.lane !== undefined) {\n sql += \" AND lane = ?\";\n args.push(query.lane);\n }\n if (query?.after !== undefined) {\n sql += \" AND stream > ?\";\n args.push(query.after);\n }\n sql += \" ORDER BY stream LIMIT ?\";\n args.push(limit);\n\n const [streamsResult, maxResult] = await Promise.all([\n this.client.execute({ sql, args: args as any[] }),\n this.client.execute(\"SELECT COALESCE(MAX(id), -1) AS m FROM events\"),\n ]);\n\n let count = 0;\n for (const row of streamsResult.rows) {\n const leased_until = row.leased_until as string | null;\n callback({\n stream: row.stream as string,\n source: (row.source as string | null) ?? undefined,\n at: Number(row.at),\n retry: Number(row.retry),\n blocked: Number(row.blocked) === 1,\n error: row.error as string,\n priority: Number(row.priority),\n leased_by: (row.leased_by as string | null) ?? undefined,\n leased_until: leased_until ? new Date(leased_until) : undefined,\n lane: row.lane as string,\n });\n count++;\n }\n\n return { maxEventId: Number(maxResult.rows[0].m), count };\n }\n\n /**\n * Per-stream aggregated stats — see {@link Store.query_stats}.\n *\n * Two code paths (mirrors the PostgresStore strategy):\n *\n * - **Heads-only path** (no `count`, no `names`): one or two queries\n * using `ROW_NUMBER() OVER (PARTITION BY stream ORDER BY version\n * DESC|ASC)` (SQLite lacks PG's `DISTINCT ON`). Window function +\n * `WHERE rn = 1` materializes the head (or tail) per stream from\n * the `(stream, version)` unique index. Parallel `Promise.all` when\n * tail is requested.\n *\n * - **Full-scan path** (`count` or `names` set): one CTE materializes\n * the filtered events, then `GROUP BY stream, name` →\n * `json_group_object(name, n)` for the names map plus `SUM(n)` for\n * count. Heads (and tails when requested) come from the same scan.\n *\n * SQLite specifics:\n * - `data` and `meta` are stored as TEXT (JSON-encoded); the reader\n * JSON-parses them when materializing the {@link Committed} rows.\n * - `blocked` is stored as 0/1 integer; the filter converts.\n * - Array input expands to a placeholder list (`IN (?, ?, ...)`)\n * since SQLite has no native array type.\n */\n async query_stats<E extends Schemas>(\n input: string[] | Pick<StreamFilter, \"stream\" | \"stream_exact\">,\n options?: QueryStatsOptions<E>\n ): Promise<Map<string, StreamStats<E>>> {\n const exclude = options?.exclude ?? [];\n const want_tail = options?.tail ?? false;\n const want_count = options?.count ?? false;\n const want_names = options?.names ?? false;\n const before = options?.before;\n const full_scan = want_count || want_names;\n\n if (Array.isArray(input) && input.length === 0) {\n return new Map<string, StreamStats<E>>();\n }\n\n // Build WHERE clause + positional args. Subscription-level filters\n // (source, blocked) are intentionally not accepted — events live in\n // the events table; subscription state in the streams table. For\n // \"stats for blocked subscriptions\" callers compose with\n // query_streams. So no JOIN here.\n const where: string[] = [];\n const args: unknown[] = [];\n\n if (Array.isArray(input)) {\n const placeholders = input.map(() => \"?\").join(\",\");\n where.push(`e.stream IN (${placeholders})`);\n args.push(...input);\n } else if (input.stream !== undefined) {\n if (input.stream_exact) {\n where.push(`e.stream = ?`);\n args.push(input.stream);\n } else {\n where.push(`e.stream LIKE ?`);\n args.push(streamPatternToLike(input.stream));\n }\n }\n if (exclude.length) {\n const placeholders = exclude.map(() => \"?\").join(\",\");\n where.push(`e.name NOT IN (${placeholders})`);\n args.push(...exclude);\n }\n if (before !== undefined) {\n where.push(`e.id < ?`);\n args.push(before);\n }\n\n const from_clause = `events e`;\n // Always emit a WHERE clause — `WHERE 1=1` short-circuits the\n // empty-filter case without a conditional branch on the generation\n // side. SQLite optimizes the trivial predicate out.\n const where_clause = `WHERE ${where.length ? where.join(\" AND \") : \"1=1\"}`;\n\n return full_scan\n ? this._query_stats_full_scan<E>(\n from_clause,\n where_clause,\n args,\n want_tail,\n want_count,\n want_names\n )\n : this._query_stats_heads_only<E>(\n from_clause,\n where_clause,\n args,\n want_tail\n );\n }\n\n /**\n * Cheap path — head (and optional tail) via ROW_NUMBER() over the\n * `(stream, version)` unique index. Parallel queries when tail set.\n */\n private async _query_stats_heads_only<E extends Schemas>(\n from_clause: string,\n where_clause: string,\n args: unknown[],\n want_tail: boolean\n ): Promise<Map<string, StreamStats<E>>> {\n const cols = `e.id, e.stream, e.version, e.name, e.data, e.created, e.meta, e.pii`;\n const head_sql = `SELECT * FROM (\n SELECT ${cols}, ROW_NUMBER() OVER (PARTITION BY e.stream ORDER BY e.version DESC) AS rn\n FROM ${from_clause}\n ${where_clause}\n ) WHERE rn = 1`;\n const tail_sql = want_tail\n ? `SELECT * FROM (\n SELECT ${cols}, ROW_NUMBER() OVER (PARTITION BY e.stream ORDER BY e.version ASC) AS rn\n FROM ${from_clause}\n ${where_clause}\n ) WHERE rn = 1`\n : null;\n\n const [headRes, tailRes] = await Promise.all([\n this.client.execute({ sql: head_sql, args: args as any[] }),\n tail_sql\n ? this.client.execute({ sql: tail_sql, args: args as any[] })\n : null,\n ]);\n\n const to_committed = (\n row: Record<string, unknown>\n ): Committed<E, keyof E> =>\n ({\n id: Number(row.id),\n stream: row.stream as string,\n version: Number(row.version),\n name: row.name as string,\n data: JSON.parse(row.data as string),\n meta: JSON.parse(row.meta as string),\n created: new Date(row.created as string),\n pii: parse_pii(row.pii),\n }) as Committed<E, keyof E>;\n\n const out = new Map<string, StreamStats<E>>();\n for (const row of headRes.rows) {\n out.set(row.stream as string, {\n head: to_committed(row as Record<string, unknown>),\n });\n }\n if (tailRes) {\n for (const row of tailRes.rows) {\n // Head and tail share the same WHERE, so any stream returning\n // a tail must also have returned a head — no null check needed.\n (\n out.get(row.stream as string) as {\n head: Committed<E, keyof E>;\n tail?: Committed<E, keyof E>;\n }\n ).tail = to_committed(row as Record<string, unknown>);\n }\n }\n return out;\n }\n\n /**\n * Full-scan path — one CTE-based query with per-stream `COUNT(*)` and\n * `json_group_object(name, n)`. Heads (and optional tails) ride free\n * on the same scan.\n */\n private async _query_stats_full_scan<E extends Schemas>(\n from_clause: string,\n where_clause: string,\n args: unknown[],\n want_tail: boolean,\n want_count: boolean,\n want_names: boolean\n ): Promise<Map<string, StreamStats<E>>> {\n const tail_cte = want_tail\n ? `, tails AS (\n SELECT * FROM (\n SELECT *, ROW_NUMBER() OVER (PARTITION BY stream ORDER BY version ASC) AS rn FROM ef\n ) WHERE rn = 1\n )`\n : \"\";\n const tail_join = want_tail\n ? `LEFT JOIN tails t ON t.stream = h.stream`\n : \"\";\n const tail_cols = want_tail\n ? `, t.id AS t_id, t.stream AS t_stream, t.version AS t_version,\n t.name AS t_name, t.data AS t_data, t.created AS t_created, t.meta AS t_meta, t.pii AS t_pii`\n : \"\";\n\n const sql = `\n WITH ef AS (\n SELECT e.id, e.stream, e.version, e.name, e.data, e.created, e.meta, e.pii\n FROM ${from_clause}\n ${where_clause}\n ),\n agg AS (\n SELECT stream,\n SUM(n) AS cnt,\n json_group_object(name, n) AS names\n FROM (\n SELECT stream, name, COUNT(*) AS n\n FROM ef\n GROUP BY stream, name\n )\n GROUP BY stream\n ),\n heads AS (\n SELECT * FROM (\n SELECT *, ROW_NUMBER() OVER (PARTITION BY stream ORDER BY version DESC) AS rn FROM ef\n ) WHERE rn = 1\n )\n ${tail_cte}\n SELECT\n h.id, h.stream, h.version, h.name, h.data, h.created, h.meta, h.pii,\n a.cnt AS agg_count,\n a.names AS agg_names\n ${tail_cols}\n FROM heads h\n LEFT JOIN agg a ON a.stream = h.stream\n ${tail_join}\n `;\n\n const res = await this.client.execute({ sql, args: args as any[] });\n\n const to_committed = (\n id: unknown,\n stream: unknown,\n version: unknown,\n name: unknown,\n data: unknown,\n meta: unknown,\n created: unknown,\n pii: unknown\n ): Committed<E, keyof E> =>\n ({\n id: Number(id),\n stream: stream as string,\n version: Number(version),\n name: name as string,\n data: JSON.parse(data as string),\n meta: JSON.parse(meta as string),\n created: new Date(created as string),\n pii: parse_pii(pii),\n }) as Committed<E, keyof E>;\n\n const out = new Map<string, StreamStats<E>>();\n for (const row of res.rows) {\n const r = row as unknown as Record<string, unknown>;\n const stats: {\n head: Committed<E, keyof E>;\n tail?: Committed<E, keyof E>;\n count?: number;\n names?: Record<string, number>;\n } = {\n head: to_committed(\n r.id,\n r.stream,\n r.version,\n r.name,\n r.data,\n r.meta,\n r.created,\n r.pii\n ),\n };\n if (want_tail && r.t_id !== null && r.t_id !== undefined) {\n stats.tail = to_committed(\n r.t_id,\n r.t_stream,\n r.t_version,\n r.t_name,\n r.t_data,\n r.t_meta,\n r.t_created,\n r.t_pii\n );\n }\n if (want_count) stats.count = Number(r.agg_count);\n // `agg_names` is non-null when this row exists: heads and agg are\n // both built from the same `ef` CTE, so any stream in heads has\n // at least one matching event and `json_group_object` returns a\n // JSON string (never null) for that group.\n if (want_names) stats.names = JSON.parse(r.agg_names as string);\n out.set(r.stream as string, stats as StreamStats<E>);\n }\n return out;\n }\n\n // --- prioritize: bulk priority update with filter (ACT-102) ---\n async prioritize(filter: StreamFilter, priority: number): Promise<number> {\n const { clause, args: filterArgs } = this._filter_clause(filter);\n // libSQL `?` placeholders are positional and NOT reusable, so we\n // bind `priority` twice: once for SET, once for the no-op skip\n // in WHERE.\n const sql = `UPDATE streams SET priority = ?\n WHERE priority <> ? AND ${clause}`;\n const result = await this.client.execute({\n sql,\n args: [priority, priority, ...filterArgs] as any[],\n });\n return result.rowsAffected;\n }\n\n // --- truncate: transactional delete + seed ---\n async truncate(\n targets: Array<{\n stream: string;\n snapshot?: Record<string, unknown>;\n meta?: EventMeta;\n }>\n ) {\n const result = new Map<\n string,\n { deleted: number; committed: Committed<Schemas, keyof Schemas> }\n >();\n\n const tx = await this.client.transaction(\"write\");\n try {\n for (const { stream, snapshot, meta } of targets) {\n const count_row = await tx.execute({\n sql: \"SELECT COUNT(*) as c FROM events WHERE stream = ?\",\n args: [stream],\n });\n const deleted = Number(count_row.rows[0].c);\n await tx.execute({\n sql: \"DELETE FROM events WHERE stream = ?\",\n args: [stream],\n });\n await tx.execute({\n sql: \"DELETE FROM streams WHERE stream = ?\",\n args: [stream],\n });\n\n const event_name =\n snapshot !== undefined ? \"__snapshot__\" : \"__tombstone__\";\n const event_meta = meta ?? { correlation: \"\", causation: {} };\n const now = new Date().toISOString();\n const ins = await tx.execute({\n sql: \"INSERT INTO events (stream, version, name, data, meta, created) VALUES (?, 0, ?, ?, ?, ?)\",\n args: [\n stream,\n event_name,\n JSON.stringify(snapshot ?? {}),\n JSON.stringify(event_meta),\n now,\n ],\n });\n\n result.set(stream, {\n deleted,\n committed: {\n id: Number(ins.lastInsertRowid),\n stream,\n version: 0,\n created: new Date(now),\n name: event_name,\n data: snapshot ?? {},\n meta: event_meta,\n },\n });\n }\n await tx.commit();\n } catch (e) {\n await tx.rollback();\n throw e;\n }\n\n return result;\n }\n\n /**\n * Atomically wipe-and-rebuild the store inside a single libsql\n * `write` transaction.\n *\n * On any throw inside the driver the transaction rolls back and the\n * store is byte-for-byte unchanged. `DELETE FROM events` + `DELETE\n * FROM streams` wipe both tables; `DELETE FROM sqlite_sequence\n * WHERE name = 'events'` resets the autoincrement counter so the\n * new sequence is dense from 1. `created` is preserved verbatim\n * from the source.\n */\n async restore(\n driver: (\n callback: (event: Committed<Schemas, keyof Schemas>) => Promise<number>\n ) => Promise<void>\n ): Promise<void> {\n const tx = await this.client.transaction(\"write\");\n try {\n await tx.execute(\"DELETE FROM events\");\n await tx.execute(\"DELETE FROM streams\");\n // Reset the autoincrement counter so the new sequence is dense\n // from 1. `DELETE FROM sqlite_sequence WHERE name = '?'` is the\n // canonical SQLite reset; safe even if the row doesn't exist.\n await tx.execute(\"DELETE FROM sqlite_sequence WHERE name = 'events'\");\n await driver(async (event) => {\n const ins = await tx.execute({\n sql: \"INSERT INTO events (stream, version, name, data, meta, created, pii) VALUES (?, ?, ?, ?, ?, ?, ?)\",\n args: [\n event.stream,\n event.version,\n event.name,\n JSON.stringify(event.data),\n JSON.stringify(event.meta),\n event.created.toISOString(),\n stringify_pii(event.pii),\n ],\n });\n return Number(ins.lastInsertRowid);\n });\n await tx.commit();\n } catch (error) {\n await tx.rollback();\n throw error;\n }\n }\n\n /**\n * Wipe the sensitive-data payload for every event on the stream — the\n * physical-erasure side of the sensitive-data epic (#566). Sets\n * `events.pii` to `NULL` for the stream's events; `events.data` and\n * the rest of the row are never touched.\n *\n * Single `UPDATE` under SQLite's writer lock, bounded by events-per-\n * stream. Idempotent — the `pii IS NOT NULL` predicate filters out\n * already-wiped rows so a second call returns `0`.\n *\n * SQLite doesn't auto-reclaim space; freed pages stay in the file\n * until an operator-scheduled `PRAGMA incremental_vacuum` or a full\n * `VACUUM`. The production checklist documents the cadence.\n *\n * @param stream Target stream\n * @returns Count of events whose `pii` was set to `NULL`\n */\n async forget_pii(stream: string): Promise<number> {\n const r = await this.client.execute({\n sql: \"UPDATE events SET pii = NULL WHERE stream = ? AND pii IS NOT NULL\",\n args: [stream],\n });\n return r.rowsAffected ?? 0;\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/sqlite-store.ts"],"sourcesContent":["/**\n * @packageDocumentation\n * @module act-sqlite\n * Main entry point for the Act-SQLite adapter. Re-exports all core APIs\n */\nexport * from \"./sqlite-store.js\";\n","import { type Client, createClient } from \"@libsql/client\";\nimport type {\n BlockedLease,\n Committed,\n EventMeta,\n Lease,\n Message,\n Query,\n QueryStatsOptions,\n QueryStreams,\n QueryStreamsResult,\n Schemas,\n Store,\n StreamFilter,\n StreamPosition,\n StreamStats,\n} from \"@rotorsoft/act\";\nimport {\n decrypt,\n type Encryption,\n encrypt,\n makeKeyResolver,\n} from \"@rotorsoft/act-crypto\";\n\n/**\n * SQLite store configuration\n */\nexport interface SqliteConfig {\n /** Path to the SQLite database file (default: \":memory:\") */\n url: string;\n /** Auth token for libSQL server connections (optional) */\n authToken?: string;\n /**\n * Adapter-layer envelope encryption for the `events.pii` column.\n * Optional — when present, every non-null PII payload is encrypted\n * before INSERT and decrypted on every read; when absent, the\n * column is stored and read as plaintext (the framework's default\n * behavior).\n *\n * Cipher and wire format come from `@rotorsoft/act-crypto`:\n * AES-256-GCM with a versioned base64-framed envelope. The TEXT\n * column stores `JSON.stringify(...)`-ed values either way, so\n * encrypted writes land as a JSON-stringified base64 string and\n * plaintext writes as a JSON-stringified object. The read path\n * discriminates by `typeof` after `JSON.parse` — strings get\n * decrypted, objects pass through — which makes mixed-data\n * rollouts transparent.\n *\n * `forget_pii` semantics are unchanged: the column is set to\n * `NULL` regardless of whether the prior value was plaintext or\n * ciphertext.\n *\n * Encryption at rest at the **storage** layer (SQLite SEE, an\n * encrypted volume, OS-level FDE) composes orthogonally with\n * adapter-layer encryption. See\n * `docs/docs/guides/pii-encryption-at-rest.md` for the decision\n * matrix.\n */\n pii_encryption?: Encryption;\n}\n\nconst DEFAULT_CONFIG: SqliteConfig = {\n url: \"file::memory:\",\n};\n\n/** Translate a stream filter (regex-shaped or plain substring) into a\n * SQL LIKE pattern. Honors `^` / `$` anchors and converts `.*` → `%`,\n * `.` → `_`. Unanchored input gets `%` wildcards on both sides.\n *\n * Examples:\n * - `^abc$` → `abc` (exact)\n * - `^abc.*` → `abc%` (starts-with)\n * - `.*abc$` → `%abc` (ends-with)\n * - `abc` → `%abc%` (contains)\n * - `a.c` → `%a_c%` (single-char wildcard, contains)\n *\n * @internal exported for testing\n */\nexport function streamPatternToLike(input: string): string {\n let s = input;\n const start = s.startsWith(\"^\");\n const end = s.endsWith(\"$\");\n if (start) s = s.slice(1);\n if (end) s = s.slice(0, -1);\n s = s.replace(/\\.\\*/g, \"%\").replace(/\\./g, \"_\");\n const out = (start ? \"\" : \"%\") + s + (end ? \"\" : \"%\");\n // Collapse adjacent `%` — e.g. `^a.*` would otherwise yield `a%%`.\n // Same matching semantics, cleaner output.\n return out.replace(/%+/g, \"%\");\n}\n\n/**\n * SQLite event store adapter for [@rotorsoft/act](https://www.npmjs.com/package/@rotorsoft/act).\n *\n * Provides persistent event storage using SQLite via `@libsql/client`.\n * All write operations use transactions for ACID guarantees.\n * Since SQLite serializes writes at the database level, the concurrency\n * model is equivalent to PostgreSQL's `FOR UPDATE SKIP LOCKED` for\n * single-server deployments.\n *\n * **`Store.notify` is intentionally not implemented.** The notify hook is\n * a cross-process wake-up signal that lets a horizontally-scaled Act\n * deployment wake `settle()` immediately on remote commits. SQLite is\n * single-node by design — there is no remote writer to be notified of —\n * so the {@link Act} orchestrator falls back to the existing\n * debounce/poll path, which is correct for this topology.\n *\n * @example\n * ```typescript\n * import { store } from \"@rotorsoft/act\";\n * import { SqliteStore } from \"@rotorsoft/act-sqlite\";\n *\n * store(new SqliteStore({ url: \"file:myapp.db\" }));\n * await store().seed();\n * ```\n */\nexport class SqliteStore implements Store {\n private client: Client;\n /**\n * Memoized key resolver for the optional `pii_encryption` envelope.\n * Initialized in the constructor when encryption is configured;\n * `undefined` otherwise. The resolver caches the operator's key on\n * first use — rotation means restarting the store with a fresh\n * provider.\n */\n private readonly _resolve_pii_key: (() => Promise<Buffer>) | undefined;\n\n constructor(config: Partial<SqliteConfig> = {}) {\n const cfg = { ...DEFAULT_CONFIG, ...config };\n this.client = createClient({\n url: cfg.url,\n authToken: cfg.authToken,\n });\n this._resolve_pii_key = cfg.pii_encryption\n ? makeKeyResolver(cfg.pii_encryption)\n : undefined;\n }\n\n /**\n * Write-side pii encoder: encrypts when `pii_encryption` is configured,\n * stringifies for the TEXT column either way. `null` passes through so\n * `forget_pii` semantics survive intact (the row format skips the\n * column entirely on a `NULL` write).\n *\n * @internal\n */\n private async _stringify_pii_for_write(\n pii: Readonly<Record<string, unknown>> | null | undefined\n ): Promise<string | null> {\n if (pii == null) return null;\n if (this._resolve_pii_key) {\n return JSON.stringify(await encrypt(pii, this._resolve_pii_key));\n }\n return JSON.stringify(pii);\n }\n\n /**\n * Read-side pii decoder: parses the TEXT column, and when\n * `pii_encryption` is configured, transparently decrypts any value\n * that came back as a JSON string. Plaintext writes deserialize to\n * objects and pass through — which is what makes mixed-data\n * rollouts (some pre-encryption rows, some post-) read cleanly.\n *\n * @internal\n */\n private async _parse_pii_from_read(\n raw: unknown\n ): Promise<Record<string, unknown> | null> {\n if (raw == null) return null;\n const parsed = JSON.parse(raw as string);\n if (this._resolve_pii_key && typeof parsed === \"string\") {\n return (await decrypt(parsed, this._resolve_pii_key)) as Record<\n string,\n unknown\n >;\n }\n return parsed;\n }\n\n async seed() {\n await this.client.execute(\"PRAGMA journal_mode=WAL\");\n await this.client.execute(`\n CREATE TABLE IF NOT EXISTS events (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n stream TEXT NOT NULL,\n version INTEGER NOT NULL,\n name TEXT NOT NULL,\n data TEXT NOT NULL,\n meta TEXT NOT NULL,\n created TEXT NOT NULL,\n pii TEXT,\n UNIQUE(stream, version)\n )\n `);\n // Migration for tables created before pii_isolation (#871).\n // libSQL surfaces \"duplicate column\" as an error, hence the\n // try/swallow — mirrors PG's `ADD COLUMN IF NOT EXISTS`. SQLite's\n // row format skips NULL columns, so events without sensitive\n // declarations pay zero extra bytes.\n try {\n await this.client.execute(\"ALTER TABLE events ADD COLUMN pii TEXT\");\n } catch {\n // already present\n }\n await this.client.execute(\n \"CREATE INDEX IF NOT EXISTS idx_events_stream ON events(stream)\"\n );\n await this.client.execute(\n \"CREATE INDEX IF NOT EXISTS idx_events_name ON events(name)\"\n );\n await this.client.execute(`\n CREATE TABLE IF NOT EXISTS streams (\n stream TEXT PRIMARY KEY,\n source TEXT,\n at INTEGER NOT NULL DEFAULT -1,\n retry INTEGER NOT NULL DEFAULT 0,\n blocked INTEGER NOT NULL DEFAULT 0,\n error TEXT NOT NULL DEFAULT '',\n leased_by TEXT,\n leased_until TEXT,\n priority INTEGER NOT NULL DEFAULT 0,\n lane TEXT NOT NULL DEFAULT 'default'\n )\n `);\n // Migration for tables created before priority lanes (ACT-102).\n // libSQL surfaces \"duplicate column\" as an error, hence the\n // try/swallow — this mirrors PG's `ADD COLUMN IF NOT EXISTS`.\n try {\n await this.client.execute(\n \"ALTER TABLE streams ADD COLUMN priority INTEGER NOT NULL DEFAULT 0\"\n );\n } catch {\n // already present\n }\n // Migration for tables created before drain lanes (ACT-1103).\n try {\n await this.client.execute(\n \"ALTER TABLE streams ADD COLUMN lane TEXT NOT NULL DEFAULT 'default'\"\n );\n } catch {\n // already present\n }\n await this.client.execute(\n \"CREATE INDEX IF NOT EXISTS idx_streams_claim ON streams(blocked, priority DESC, at)\"\n );\n // Lane filter index (ACT-1103).\n await this.client.execute(\n \"CREATE INDEX IF NOT EXISTS idx_streams_lane ON streams(lane)\"\n );\n }\n\n async drop() {\n await this.client.execute(\"DROP TABLE IF EXISTS events\");\n await this.client.execute(\"DROP TABLE IF EXISTS streams\");\n }\n\n async dispose() {\n await Promise.resolve();\n this.client.close();\n }\n\n // --- commit: transaction + optimistic concurrency ---\n async commit<E extends Schemas>(\n stream: string,\n msgs: Message<E, keyof E>[],\n meta: EventMeta,\n expectedVersion?: number\n ): Promise<Committed<E, keyof E>[]> {\n const tx = await this.client.transaction(\"write\");\n try {\n const version_row = await tx.execute({\n sql: \"SELECT COALESCE(MAX(version), -1) as v FROM events WHERE stream = ?\",\n args: [stream],\n });\n const current_version = Number(version_row.rows[0].v);\n\n if (\n typeof expectedVersion === \"number\" &&\n current_version !== expectedVersion\n ) {\n const { ConcurrencyError } = await import(\"@rotorsoft/act\");\n throw new ConcurrencyError(\n stream,\n current_version,\n msgs as Message<Schemas, keyof Schemas>[],\n expectedVersion\n );\n }\n\n const now = new Date().toISOString();\n const committed: Committed<E, keyof E>[] = [];\n let version = current_version + 1;\n\n for (const { name, data, pii } of msgs) {\n const pii_for_write = await this._stringify_pii_for_write(pii);\n const result = await tx.execute({\n sql: \"INSERT INTO events (stream, version, name, data, meta, created, pii) VALUES (?, ?, ?, ?, ?, ?, ?)\",\n args: [\n stream,\n version,\n name as string,\n JSON.stringify(data),\n JSON.stringify(meta),\n now,\n pii_for_write,\n ],\n });\n committed.push({\n id: Number(result.lastInsertRowid),\n stream,\n version,\n created: new Date(now),\n name,\n data,\n meta,\n ...(pii == null ? {} : { pii }),\n });\n version++;\n }\n\n await tx.commit();\n return committed;\n } catch (e) {\n await tx.rollback();\n throw e;\n }\n }\n\n // --- query: read-only, no transaction needed ---\n async query<E extends Schemas>(\n callback: (event: Committed<E, keyof E>) => void,\n query?: Query\n ): Promise<number> {\n let sql = \"SELECT * FROM events WHERE 1=1\";\n const args: unknown[] = [];\n\n if (query?.stream) {\n if (query.stream_exact) {\n sql += \" AND stream = ?\";\n args.push(query.stream);\n } else {\n sql += \" AND stream LIKE ?\";\n args.push(streamPatternToLike(query.stream));\n }\n }\n if (query?.names) {\n sql += ` AND name IN (${query.names.map(() => \"?\").join(\",\")})`;\n args.push(...query.names);\n }\n if ((query as any)?.correlation) {\n sql += \" AND json_extract(meta, '$.correlation') = ?\";\n args.push((query as any).correlation);\n }\n if (query?.after !== undefined) {\n sql += \" AND id > ?\";\n args.push(query.after);\n }\n if (query?.before !== undefined) {\n sql += \" AND id < ?\";\n args.push(query.before);\n }\n if (query?.created_after) {\n sql += \" AND created > ?\";\n args.push(query.created_after.toISOString());\n }\n if (query?.created_before) {\n sql += \" AND created < ?\";\n args.push(query.created_before.toISOString());\n }\n if (!query?.with_snaps) {\n sql += \" AND name != '__snapshot__'\";\n }\n\n sql += query?.backward ? \" ORDER BY id DESC\" : \" ORDER BY id ASC\";\n\n if (query?.limit) {\n sql += \" LIMIT ?\";\n args.push(query.limit);\n }\n\n const result = await this.client.execute({ sql, args: args as any[] });\n let count = 0;\n\n for (const row of result.rows) {\n const pii_value = await this._parse_pii_from_read(row.pii);\n await Promise.resolve(\n callback({\n id: Number(row.id),\n stream: row.stream as string,\n version: Number(row.version),\n created: new Date(row.created as string),\n name: row.name as string,\n data: JSON.parse(row.data as string),\n meta: JSON.parse(row.meta as string),\n pii: pii_value,\n })\n );\n count++;\n }\n\n return count;\n }\n\n // --- subscribe: idempotent INSERT OR IGNORE (= PG ON CONFLICT DO NOTHING)\n // plus a UPDATE pass to keep the *max* priority across reactions\n // targeting the same stream (ACT-102). Operator overrides go\n // through `prioritize()` instead.\n async subscribe(\n streams: Array<{\n stream: string;\n source?: string;\n priority?: number;\n lane?: string;\n }>\n ) {\n const tx = await this.client.transaction(\"write\");\n try {\n let subscribed = 0;\n for (const {\n stream,\n source,\n priority = 0,\n lane = \"default\",\n } of streams) {\n const inserted = await tx.execute({\n sql: \"INSERT OR IGNORE INTO streams (stream, source, priority, lane) VALUES (?, ?, ?, ?)\",\n args: [stream, source ?? null, priority, lane],\n });\n if (inserted.rowsAffected > 0) {\n subscribed++;\n } else {\n if (priority > 0) {\n await tx.execute({\n sql: \"UPDATE streams SET priority = ? WHERE stream = ? AND priority < ?\",\n args: [priority, stream, priority],\n });\n }\n // ACT-1103: current subscribe wins on lane.\n await tx.execute({\n sql: \"UPDATE streams SET lane = ? WHERE stream = ? AND lane <> ?\",\n args: [lane, stream, lane],\n });\n }\n }\n const wm = await tx.execute(\n \"SELECT COALESCE(MAX(at), -1) as w FROM streams\"\n );\n await tx.commit();\n return { subscribed, watermark: Number(wm.rows[0].w) };\n } catch (e) {\n await tx.rollback();\n throw e;\n }\n }\n\n // --- claim: write transaction (SQLite serializes writes = equivalent\n // to PG FOR UPDATE SKIP LOCKED for single-server) ---\n async claim(\n lagging: number,\n leading: number,\n by: string,\n millis: number,\n lane?: string\n ) {\n const tx = await this.client.transaction(\"write\");\n try {\n const now = new Date().toISOString();\n\n const lane_clause = lane !== undefined ? \" AND lane = ?\" : \"\";\n const result = await tx.execute({\n sql: `SELECT stream, source, at, priority, lane FROM streams\n WHERE blocked = 0 AND (leased_until IS NULL OR leased_until <= ?)${lane_clause}\n ORDER BY priority DESC, at ASC`,\n args: lane !== undefined ? [now, lane] : [now],\n });\n\n const candidates: {\n stream: string;\n source: string | undefined;\n at: number;\n priority: number;\n lane: string;\n }[] = [];\n for (const row of result.rows) {\n const stream = row.stream as string;\n const source = row.source as string | null;\n const at = Number(row.at);\n\n let has_events: boolean;\n if (source) {\n const check = await tx.execute({\n sql: `SELECT 1 FROM events WHERE id > ? AND name != '__snapshot__' AND stream LIKE ? LIMIT 1`,\n args: [at, streamPatternToLike(source)],\n });\n has_events = check.rows.length > 0;\n } else {\n const check = await tx.execute({\n sql: `SELECT 1 FROM events WHERE id > ? AND name != '__snapshot__' LIMIT 1`,\n args: [at],\n });\n has_events = check.rows.length > 0;\n }\n\n if (has_events) {\n candidates.push({\n stream,\n source: source ?? undefined,\n at,\n priority: Number(row.priority),\n lane: row.lane as string,\n });\n }\n }\n\n // Dual frontier: lagging (priority DESC, watermark ASC — ACT-102)\n // + leading (newest first). The candidates list arrives sorted\n // by `priority DESC, at ASC` from the SELECT above, so the\n // `slice(0, lagging)` already does the right thing.\n const lag = candidates.slice(0, lagging);\n const lead = [...candidates]\n .sort((a, b) => b.at - a.at)\n .slice(0, leading);\n const seen = new Set<string>();\n const combined = [...lag, ...lead].filter((p) => {\n if (seen.has(p.stream)) return false;\n seen.add(p.stream);\n return true;\n });\n\n const leases: Lease[] = [];\n const until = new Date(Date.now() + millis).toISOString();\n for (const row of combined) {\n await tx.execute({\n sql: \"UPDATE streams SET leased_by = ?, leased_until = ?, retry = retry + 1 WHERE stream = ?\",\n args: [by, until, row.stream],\n });\n leases.push({\n stream: row.stream,\n source: row.source,\n at: row.at,\n by,\n retry: 0,\n lagging: row.at < 0,\n lane: row.lane,\n });\n }\n\n await tx.commit();\n return leases;\n } catch (e) {\n await tx.rollback();\n throw e;\n }\n }\n\n // --- ack: transaction + ownership check (= PG WHERE leased_by) ---\n async ack(leases: Lease[]) {\n const tx = await this.client.transaction(\"write\");\n try {\n const result: Lease[] = [];\n for (const l of leases) {\n const r = await tx.execute({\n sql: `UPDATE streams SET at = ?, leased_by = NULL, leased_until = NULL, retry = -1\n WHERE stream = ? AND leased_by = ?`,\n args: [l.at, l.stream, l.by],\n });\n if (r.rowsAffected > 0) result.push(l);\n }\n await tx.commit();\n return result;\n } catch (e) {\n await tx.rollback();\n throw e;\n }\n }\n\n // --- block: transaction + ownership + idempotent (= PG) ---\n async block(leases: BlockedLease[]) {\n const tx = await this.client.transaction(\"write\");\n try {\n const result: BlockedLease[] = [];\n for (const l of leases) {\n const r = await tx.execute({\n sql: `UPDATE streams SET blocked = 1, error = ?\n WHERE stream = ? AND leased_by = ? AND blocked = 0`,\n args: [l.error, l.stream, l.by],\n });\n if (r.rowsAffected > 0) result.push(l);\n }\n await tx.commit();\n return result;\n } catch (e) {\n await tx.rollback();\n throw e;\n }\n }\n\n /**\n * Translate a {@link StreamFilter} to a SQLite `WHERE` clause fragment\n * plus positional args. Returns `\"1\"` (always true) when empty so\n * callers can compose it unconditionally.\n */\n private _filter_clause(filter: StreamFilter): {\n clause: string;\n args: unknown[];\n } {\n const conditions: string[] = [];\n const args: unknown[] = [];\n if (filter.stream !== undefined) {\n if (filter.stream_exact) {\n conditions.push(\"stream = ?\");\n args.push(filter.stream);\n } else {\n conditions.push(\"stream LIKE ?\");\n args.push(streamPatternToLike(filter.stream));\n }\n }\n if (filter.source !== undefined) {\n conditions.push(\"source IS NOT NULL\");\n if (filter.source_exact) {\n conditions.push(\"source = ?\");\n args.push(filter.source);\n } else {\n conditions.push(\"source LIKE ?\");\n args.push(streamPatternToLike(filter.source));\n }\n }\n if (filter.blocked !== undefined) {\n conditions.push(\"blocked = ?\");\n args.push(filter.blocked ? 1 : 0);\n }\n if (filter.lane !== undefined) {\n conditions.push(\"lane = ?\");\n args.push(filter.lane);\n }\n return { clause: conditions.length ? conditions.join(\" AND \") : \"1\", args };\n }\n\n // --- reset: transactional, accepts names or filter ---\n async reset(input: string[] | StreamFilter) {\n const set_clause = `SET at = -1, retry = 0, blocked = 0, error = '',\n leased_by = NULL, leased_until = NULL`;\n const tx = await this.client.transaction(\"write\");\n try {\n let count = 0;\n if (Array.isArray(input)) {\n for (const stream of input) {\n const r = await tx.execute({\n sql: `UPDATE streams ${set_clause} WHERE stream = ?`,\n args: [stream],\n });\n count += r.rowsAffected;\n }\n } else {\n const { clause, args } = this._filter_clause(input);\n const r = await tx.execute({\n sql: `UPDATE streams ${set_clause} WHERE ${clause}`,\n args: args as any[],\n });\n count = r.rowsAffected;\n }\n await tx.commit();\n return count;\n } catch (e) {\n await tx.rollback();\n throw e;\n }\n }\n\n // --- unblock: clear blocked + retry + lease without touching watermark ---\n // `retry = -1` so claim's post-bump returns retry=0 (first attempt),\n // matching the InMemoryStore convention.\n async unblock(input: string[] | StreamFilter) {\n const set_clause = `SET retry = -1, blocked = 0, error = '',\n leased_by = NULL, leased_until = NULL`;\n const tx = await this.client.transaction(\"write\");\n try {\n let count = 0;\n if (Array.isArray(input)) {\n for (const stream of input) {\n const r = await tx.execute({\n sql: `UPDATE streams ${set_clause}\n WHERE stream = ? AND blocked = 1`,\n args: [stream],\n });\n count += r.rowsAffected;\n }\n } else {\n // Filter form: force blocked = true regardless of what the\n // caller passed.\n const { clause, args } = this._filter_clause({\n ...input,\n blocked: true,\n });\n const r = await tx.execute({\n sql: `UPDATE streams ${set_clause} WHERE ${clause}`,\n args: args as any[],\n });\n count = r.rowsAffected;\n }\n await tx.commit();\n return count;\n } catch (e) {\n await tx.rollback();\n throw e;\n }\n }\n\n // --- query_streams: read-only introspection with filters ---\n async query_streams(\n callback: (position: StreamPosition) => void,\n query?: QueryStreams\n ): Promise<QueryStreamsResult> {\n const limit = query?.limit ?? 100;\n let sql =\n \"SELECT stream, source, at, retry, blocked, error, leased_by, leased_until, priority, lane FROM streams WHERE 1=1\";\n const args: unknown[] = [];\n\n if (query?.stream !== undefined) {\n if (query.stream_exact) {\n sql += \" AND stream = ?\";\n args.push(query.stream);\n } else {\n sql += \" AND stream LIKE ?\";\n args.push(streamPatternToLike(query.stream));\n }\n }\n if (query?.source !== undefined) {\n sql += \" AND source IS NOT NULL\";\n if (query.source_exact) {\n sql += \" AND source = ?\";\n args.push(query.source);\n } else {\n sql += \" AND source LIKE ?\";\n args.push(streamPatternToLike(query.source));\n }\n }\n if (query?.blocked !== undefined) {\n sql += \" AND blocked = ?\";\n args.push(query.blocked ? 1 : 0);\n }\n if (query?.lane !== undefined) {\n sql += \" AND lane = ?\";\n args.push(query.lane);\n }\n if (query?.after !== undefined) {\n sql += \" AND stream > ?\";\n args.push(query.after);\n }\n sql += \" ORDER BY stream LIMIT ?\";\n args.push(limit);\n\n const [streamsResult, maxResult] = await Promise.all([\n this.client.execute({ sql, args: args as any[] }),\n this.client.execute(\"SELECT COALESCE(MAX(id), -1) AS m FROM events\"),\n ]);\n\n let count = 0;\n for (const row of streamsResult.rows) {\n const leased_until = row.leased_until as string | null;\n callback({\n stream: row.stream as string,\n source: (row.source as string | null) ?? undefined,\n at: Number(row.at),\n retry: Number(row.retry),\n blocked: Number(row.blocked) === 1,\n error: row.error as string,\n priority: Number(row.priority),\n leased_by: (row.leased_by as string | null) ?? undefined,\n leased_until: leased_until ? new Date(leased_until) : undefined,\n lane: row.lane as string,\n });\n count++;\n }\n\n return { maxEventId: Number(maxResult.rows[0].m), count };\n }\n\n /**\n * Per-stream aggregated stats — see {@link Store.query_stats}.\n *\n * Two code paths (mirrors the PostgresStore strategy):\n *\n * - **Heads-only path** (no `count`, no `names`): one or two queries\n * using `ROW_NUMBER() OVER (PARTITION BY stream ORDER BY version\n * DESC|ASC)` (SQLite lacks PG's `DISTINCT ON`). Window function +\n * `WHERE rn = 1` materializes the head (or tail) per stream from\n * the `(stream, version)` unique index. Parallel `Promise.all` when\n * tail is requested.\n *\n * - **Full-scan path** (`count` or `names` set): one CTE materializes\n * the filtered events, then `GROUP BY stream, name` →\n * `json_group_object(name, n)` for the names map plus `SUM(n)` for\n * count. Heads (and tails when requested) come from the same scan.\n *\n * SQLite specifics:\n * - `data` and `meta` are stored as TEXT (JSON-encoded); the reader\n * JSON-parses them when materializing the {@link Committed} rows.\n * - `blocked` is stored as 0/1 integer; the filter converts.\n * - Array input expands to a placeholder list (`IN (?, ?, ...)`)\n * since SQLite has no native array type.\n */\n async query_stats<E extends Schemas>(\n input: string[] | Pick<StreamFilter, \"stream\" | \"stream_exact\">,\n options?: QueryStatsOptions<E>\n ): Promise<Map<string, StreamStats<E>>> {\n const exclude = options?.exclude ?? [];\n const want_tail = options?.tail ?? false;\n const want_count = options?.count ?? false;\n const want_names = options?.names ?? false;\n const before = options?.before;\n const full_scan = want_count || want_names;\n\n if (Array.isArray(input) && input.length === 0) {\n return new Map<string, StreamStats<E>>();\n }\n\n // Build WHERE clause + positional args. Subscription-level filters\n // (source, blocked) are intentionally not accepted — events live in\n // the events table; subscription state in the streams table. For\n // \"stats for blocked subscriptions\" callers compose with\n // query_streams. So no JOIN here.\n const where: string[] = [];\n const args: unknown[] = [];\n\n if (Array.isArray(input)) {\n const placeholders = input.map(() => \"?\").join(\",\");\n where.push(`e.stream IN (${placeholders})`);\n args.push(...input);\n } else if (input.stream !== undefined) {\n if (input.stream_exact) {\n where.push(`e.stream = ?`);\n args.push(input.stream);\n } else {\n where.push(`e.stream LIKE ?`);\n args.push(streamPatternToLike(input.stream));\n }\n }\n if (exclude.length) {\n const placeholders = exclude.map(() => \"?\").join(\",\");\n where.push(`e.name NOT IN (${placeholders})`);\n args.push(...exclude);\n }\n if (before !== undefined) {\n where.push(`e.id < ?`);\n args.push(before);\n }\n\n const from_clause = `events e`;\n // Always emit a WHERE clause — `WHERE 1=1` short-circuits the\n // empty-filter case without a conditional branch on the generation\n // side. SQLite optimizes the trivial predicate out.\n const where_clause = `WHERE ${where.length ? where.join(\" AND \") : \"1=1\"}`;\n\n return full_scan\n ? this._query_stats_full_scan<E>(\n from_clause,\n where_clause,\n args,\n want_tail,\n want_count,\n want_names\n )\n : this._query_stats_heads_only<E>(\n from_clause,\n where_clause,\n args,\n want_tail\n );\n }\n\n /**\n * Cheap path — head (and optional tail) via ROW_NUMBER() over the\n * `(stream, version)` unique index. Parallel queries when tail set.\n */\n private async _query_stats_heads_only<E extends Schemas>(\n from_clause: string,\n where_clause: string,\n args: unknown[],\n want_tail: boolean\n ): Promise<Map<string, StreamStats<E>>> {\n const cols = `e.id, e.stream, e.version, e.name, e.data, e.created, e.meta, e.pii`;\n const head_sql = `SELECT * FROM (\n SELECT ${cols}, ROW_NUMBER() OVER (PARTITION BY e.stream ORDER BY e.version DESC) AS rn\n FROM ${from_clause}\n ${where_clause}\n ) WHERE rn = 1`;\n const tail_sql = want_tail\n ? `SELECT * FROM (\n SELECT ${cols}, ROW_NUMBER() OVER (PARTITION BY e.stream ORDER BY e.version ASC) AS rn\n FROM ${from_clause}\n ${where_clause}\n ) WHERE rn = 1`\n : null;\n\n const [headRes, tailRes] = await Promise.all([\n this.client.execute({ sql: head_sql, args: args as any[] }),\n tail_sql\n ? this.client.execute({ sql: tail_sql, args: args as any[] })\n : null,\n ]);\n\n const to_committed = async (\n row: Record<string, unknown>\n ): Promise<Committed<E, keyof E>> =>\n ({\n id: Number(row.id),\n stream: row.stream as string,\n version: Number(row.version),\n name: row.name as string,\n data: JSON.parse(row.data as string),\n meta: JSON.parse(row.meta as string),\n created: new Date(row.created as string),\n pii: await this._parse_pii_from_read(row.pii),\n }) as Committed<E, keyof E>;\n\n const out = new Map<string, StreamStats<E>>();\n for (const row of headRes.rows) {\n out.set(row.stream as string, {\n head: await to_committed(row as Record<string, unknown>),\n });\n }\n if (tailRes) {\n for (const row of tailRes.rows) {\n // Head and tail share the same WHERE, so any stream returning\n // a tail must also have returned a head — no null check needed.\n (\n out.get(row.stream as string) as {\n head: Committed<E, keyof E>;\n tail?: Committed<E, keyof E>;\n }\n ).tail = await to_committed(row as Record<string, unknown>);\n }\n }\n return out;\n }\n\n /**\n * Full-scan path — one CTE-based query with per-stream `COUNT(*)` and\n * `json_group_object(name, n)`. Heads (and optional tails) ride free\n * on the same scan.\n */\n private async _query_stats_full_scan<E extends Schemas>(\n from_clause: string,\n where_clause: string,\n args: unknown[],\n want_tail: boolean,\n want_count: boolean,\n want_names: boolean\n ): Promise<Map<string, StreamStats<E>>> {\n const tail_cte = want_tail\n ? `, tails AS (\n SELECT * FROM (\n SELECT *, ROW_NUMBER() OVER (PARTITION BY stream ORDER BY version ASC) AS rn FROM ef\n ) WHERE rn = 1\n )`\n : \"\";\n const tail_join = want_tail\n ? `LEFT JOIN tails t ON t.stream = h.stream`\n : \"\";\n const tail_cols = want_tail\n ? `, t.id AS t_id, t.stream AS t_stream, t.version AS t_version,\n t.name AS t_name, t.data AS t_data, t.created AS t_created, t.meta AS t_meta, t.pii AS t_pii`\n : \"\";\n\n const sql = `\n WITH ef AS (\n SELECT e.id, e.stream, e.version, e.name, e.data, e.created, e.meta, e.pii\n FROM ${from_clause}\n ${where_clause}\n ),\n agg AS (\n SELECT stream,\n SUM(n) AS cnt,\n json_group_object(name, n) AS names\n FROM (\n SELECT stream, name, COUNT(*) AS n\n FROM ef\n GROUP BY stream, name\n )\n GROUP BY stream\n ),\n heads AS (\n SELECT * FROM (\n SELECT *, ROW_NUMBER() OVER (PARTITION BY stream ORDER BY version DESC) AS rn FROM ef\n ) WHERE rn = 1\n )\n ${tail_cte}\n SELECT\n h.id, h.stream, h.version, h.name, h.data, h.created, h.meta, h.pii,\n a.cnt AS agg_count,\n a.names AS agg_names\n ${tail_cols}\n FROM heads h\n LEFT JOIN agg a ON a.stream = h.stream\n ${tail_join}\n `;\n\n const res = await this.client.execute({ sql, args: args as any[] });\n\n const to_committed = async (\n id: unknown,\n stream: unknown,\n version: unknown,\n name: unknown,\n data: unknown,\n meta: unknown,\n created: unknown,\n pii: unknown\n ): Promise<Committed<E, keyof E>> =>\n ({\n id: Number(id),\n stream: stream as string,\n version: Number(version),\n name: name as string,\n data: JSON.parse(data as string),\n meta: JSON.parse(meta as string),\n created: new Date(created as string),\n pii: await this._parse_pii_from_read(pii),\n }) as Committed<E, keyof E>;\n\n const out = new Map<string, StreamStats<E>>();\n for (const row of res.rows) {\n const r = row as unknown as Record<string, unknown>;\n const stats: {\n head: Committed<E, keyof E>;\n tail?: Committed<E, keyof E>;\n count?: number;\n names?: Record<string, number>;\n } = {\n head: await to_committed(\n r.id,\n r.stream,\n r.version,\n r.name,\n r.data,\n r.meta,\n r.created,\n r.pii\n ),\n };\n if (want_tail && r.t_id !== null && r.t_id !== undefined) {\n stats.tail = await to_committed(\n r.t_id,\n r.t_stream,\n r.t_version,\n r.t_name,\n r.t_data,\n r.t_meta,\n r.t_created,\n r.t_pii\n );\n }\n if (want_count) stats.count = Number(r.agg_count);\n // `agg_names` is non-null when this row exists: heads and agg are\n // both built from the same `ef` CTE, so any stream in heads has\n // at least one matching event and `json_group_object` returns a\n // JSON string (never null) for that group.\n if (want_names) stats.names = JSON.parse(r.agg_names as string);\n out.set(r.stream as string, stats as StreamStats<E>);\n }\n return out;\n }\n\n // --- prioritize: bulk priority update with filter (ACT-102) ---\n async prioritize(filter: StreamFilter, priority: number): Promise<number> {\n const { clause, args: filterArgs } = this._filter_clause(filter);\n // libSQL `?` placeholders are positional and NOT reusable, so we\n // bind `priority` twice: once for SET, once for the no-op skip\n // in WHERE.\n const sql = `UPDATE streams SET priority = ?\n WHERE priority <> ? AND ${clause}`;\n const result = await this.client.execute({\n sql,\n args: [priority, priority, ...filterArgs] as any[],\n });\n return result.rowsAffected;\n }\n\n // --- truncate: transactional delete + seed ---\n async truncate(\n targets: Array<{\n stream: string;\n snapshot?: Record<string, unknown>;\n meta?: EventMeta;\n }>\n ) {\n const result = new Map<\n string,\n { deleted: number; committed: Committed<Schemas, keyof Schemas> }\n >();\n\n const tx = await this.client.transaction(\"write\");\n try {\n for (const { stream, snapshot, meta } of targets) {\n const count_row = await tx.execute({\n sql: \"SELECT COUNT(*) as c FROM events WHERE stream = ?\",\n args: [stream],\n });\n const deleted = Number(count_row.rows[0].c);\n await tx.execute({\n sql: \"DELETE FROM events WHERE stream = ?\",\n args: [stream],\n });\n await tx.execute({\n sql: \"DELETE FROM streams WHERE stream = ?\",\n args: [stream],\n });\n\n const event_name =\n snapshot !== undefined ? \"__snapshot__\" : \"__tombstone__\";\n const event_meta = meta ?? { correlation: \"\", causation: {} };\n const now = new Date().toISOString();\n const ins = await tx.execute({\n sql: \"INSERT INTO events (stream, version, name, data, meta, created) VALUES (?, 0, ?, ?, ?, ?)\",\n args: [\n stream,\n event_name,\n JSON.stringify(snapshot ?? {}),\n JSON.stringify(event_meta),\n now,\n ],\n });\n\n result.set(stream, {\n deleted,\n committed: {\n id: Number(ins.lastInsertRowid),\n stream,\n version: 0,\n created: new Date(now),\n name: event_name,\n data: snapshot ?? {},\n meta: event_meta,\n },\n });\n }\n await tx.commit();\n } catch (e) {\n await tx.rollback();\n throw e;\n }\n\n return result;\n }\n\n /**\n * Atomically wipe-and-rebuild the store inside a single libsql\n * `write` transaction.\n *\n * On any throw inside the driver the transaction rolls back and the\n * store is byte-for-byte unchanged. `DELETE FROM events` + `DELETE\n * FROM streams` wipe both tables; `DELETE FROM sqlite_sequence\n * WHERE name = 'events'` resets the autoincrement counter so the\n * new sequence is dense from 1. `created` is preserved verbatim\n * from the source.\n */\n async restore(\n driver: (\n callback: (event: Committed<Schemas, keyof Schemas>) => Promise<number>\n ) => Promise<void>\n ): Promise<void> {\n const tx = await this.client.transaction(\"write\");\n try {\n await tx.execute(\"DELETE FROM events\");\n await tx.execute(\"DELETE FROM streams\");\n // Reset the autoincrement counter so the new sequence is dense\n // from 1. `DELETE FROM sqlite_sequence WHERE name = '?'` is the\n // canonical SQLite reset; safe even if the row doesn't exist.\n await tx.execute(\"DELETE FROM sqlite_sequence WHERE name = 'events'\");\n await driver(async (event) => {\n const pii_for_write = await this._stringify_pii_for_write(event.pii);\n const ins = await tx.execute({\n sql: \"INSERT INTO events (stream, version, name, data, meta, created, pii) VALUES (?, ?, ?, ?, ?, ?, ?)\",\n args: [\n event.stream,\n event.version,\n event.name,\n JSON.stringify(event.data),\n JSON.stringify(event.meta),\n event.created.toISOString(),\n pii_for_write,\n ],\n });\n return Number(ins.lastInsertRowid);\n });\n await tx.commit();\n } catch (error) {\n await tx.rollback();\n throw error;\n }\n }\n\n /**\n * Wipe the sensitive-data payload for every event on the stream — the\n * physical-erasure side of the sensitive-data epic (#566). Sets\n * `events.pii` to `NULL` for the stream's events; `events.data` and\n * the rest of the row are never touched.\n *\n * Single `UPDATE` under SQLite's writer lock, bounded by events-per-\n * stream. Idempotent — the `pii IS NOT NULL` predicate filters out\n * already-wiped rows so a second call returns `0`.\n *\n * SQLite doesn't auto-reclaim space; freed pages stay in the file\n * until an operator-scheduled `PRAGMA incremental_vacuum` or a full\n * `VACUUM`. The production checklist documents the cadence.\n *\n * @param stream Target stream\n * @returns Count of events whose `pii` was set to `NULL`\n */\n async forget_pii(stream: string): Promise<number> {\n const r = await this.client.execute({\n sql: \"UPDATE events SET pii = NULL WHERE stream = ? AND pii IS NOT NULL\",\n args: [stream],\n });\n return r.rowsAffected ?? 0;\n }\n}\n"],"mappings":"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","names":[]}
package/dist/index.js CHANGED
@@ -1,10 +1,13 @@
1
1
  // src/sqlite-store.ts
2
2
  import { createClient } from "@libsql/client";
3
+ import {
4
+ decrypt,
5
+ encrypt,
6
+ makeKeyResolver
7
+ } from "@rotorsoft/act-crypto";
3
8
  var DEFAULT_CONFIG = {
4
9
  url: "file::memory:"
5
10
  };
6
- var parse_pii = (raw) => raw == null ? null : JSON.parse(raw);
7
- var stringify_pii = (pii) => pii == null ? null : JSON.stringify(pii);
8
11
  function streamPatternToLike(input) {
9
12
  let s = input;
10
13
  const start = s.startsWith("^");
@@ -17,12 +20,53 @@ function streamPatternToLike(input) {
17
20
  }
18
21
  var SqliteStore = class {
19
22
  client;
23
+ /**
24
+ * Memoized key resolver for the optional `pii_encryption` envelope.
25
+ * Initialized in the constructor when encryption is configured;
26
+ * `undefined` otherwise. The resolver caches the operator's key on
27
+ * first use — rotation means restarting the store with a fresh
28
+ * provider.
29
+ */
30
+ _resolve_pii_key;
20
31
  constructor(config = {}) {
21
32
  const cfg = { ...DEFAULT_CONFIG, ...config };
22
33
  this.client = createClient({
23
34
  url: cfg.url,
24
35
  authToken: cfg.authToken
25
36
  });
37
+ this._resolve_pii_key = cfg.pii_encryption ? makeKeyResolver(cfg.pii_encryption) : void 0;
38
+ }
39
+ /**
40
+ * Write-side pii encoder: encrypts when `pii_encryption` is configured,
41
+ * stringifies for the TEXT column either way. `null` passes through so
42
+ * `forget_pii` semantics survive intact (the row format skips the
43
+ * column entirely on a `NULL` write).
44
+ *
45
+ * @internal
46
+ */
47
+ async _stringify_pii_for_write(pii) {
48
+ if (pii == null) return null;
49
+ if (this._resolve_pii_key) {
50
+ return JSON.stringify(await encrypt(pii, this._resolve_pii_key));
51
+ }
52
+ return JSON.stringify(pii);
53
+ }
54
+ /**
55
+ * Read-side pii decoder: parses the TEXT column, and when
56
+ * `pii_encryption` is configured, transparently decrypts any value
57
+ * that came back as a JSON string. Plaintext writes deserialize to
58
+ * objects and pass through — which is what makes mixed-data
59
+ * rollouts (some pre-encryption rows, some post-) read cleanly.
60
+ *
61
+ * @internal
62
+ */
63
+ async _parse_pii_from_read(raw) {
64
+ if (raw == null) return null;
65
+ const parsed = JSON.parse(raw);
66
+ if (this._resolve_pii_key && typeof parsed === "string") {
67
+ return await decrypt(parsed, this._resolve_pii_key);
68
+ }
69
+ return parsed;
26
70
  }
27
71
  async seed() {
28
72
  await this.client.execute("PRAGMA journal_mode=WAL");
@@ -112,6 +156,7 @@ var SqliteStore = class {
112
156
  const committed = [];
113
157
  let version = current_version + 1;
114
158
  for (const { name, data, pii } of msgs) {
159
+ const pii_for_write = await this._stringify_pii_for_write(pii);
115
160
  const result = await tx.execute({
116
161
  sql: "INSERT INTO events (stream, version, name, data, meta, created, pii) VALUES (?, ?, ?, ?, ?, ?, ?)",
117
162
  args: [
@@ -121,7 +166,7 @@ var SqliteStore = class {
121
166
  JSON.stringify(data),
122
167
  JSON.stringify(meta),
123
168
  now,
124
- stringify_pii(pii)
169
+ pii_for_write
125
170
  ]
126
171
  });
127
172
  committed.push({
@@ -191,6 +236,7 @@ var SqliteStore = class {
191
236
  const result = await this.client.execute({ sql, args });
192
237
  let count = 0;
193
238
  for (const row of result.rows) {
239
+ const pii_value = await this._parse_pii_from_read(row.pii);
194
240
  await Promise.resolve(
195
241
  callback({
196
242
  id: Number(row.id),
@@ -200,7 +246,7 @@ var SqliteStore = class {
200
246
  name: row.name,
201
247
  data: JSON.parse(row.data),
202
248
  meta: JSON.parse(row.meta),
203
- pii: parse_pii(row.pii)
249
+ pii: pii_value
204
250
  })
205
251
  );
206
252
  count++;
@@ -622,7 +668,7 @@ var SqliteStore = class {
622
668
  this.client.execute({ sql: head_sql, args }),
623
669
  tail_sql ? this.client.execute({ sql: tail_sql, args }) : null
624
670
  ]);
625
- const to_committed = (row) => ({
671
+ const to_committed = async (row) => ({
626
672
  id: Number(row.id),
627
673
  stream: row.stream,
628
674
  version: Number(row.version),
@@ -630,17 +676,17 @@ var SqliteStore = class {
630
676
  data: JSON.parse(row.data),
631
677
  meta: JSON.parse(row.meta),
632
678
  created: new Date(row.created),
633
- pii: parse_pii(row.pii)
679
+ pii: await this._parse_pii_from_read(row.pii)
634
680
  });
635
681
  const out = /* @__PURE__ */ new Map();
636
682
  for (const row of headRes.rows) {
637
683
  out.set(row.stream, {
638
- head: to_committed(row)
684
+ head: await to_committed(row)
639
685
  });
640
686
  }
641
687
  if (tailRes) {
642
688
  for (const row of tailRes.rows) {
643
- out.get(row.stream).tail = to_committed(row);
689
+ out.get(row.stream).tail = await to_committed(row);
644
690
  }
645
691
  }
646
692
  return out;
@@ -692,7 +738,7 @@ var SqliteStore = class {
692
738
  ${tail_join}
693
739
  `;
694
740
  const res = await this.client.execute({ sql, args });
695
- const to_committed = (id, stream, version, name, data, meta, created, pii) => ({
741
+ const to_committed = async (id, stream, version, name, data, meta, created, pii) => ({
696
742
  id: Number(id),
697
743
  stream,
698
744
  version: Number(version),
@@ -700,13 +746,13 @@ var SqliteStore = class {
700
746
  data: JSON.parse(data),
701
747
  meta: JSON.parse(meta),
702
748
  created: new Date(created),
703
- pii: parse_pii(pii)
749
+ pii: await this._parse_pii_from_read(pii)
704
750
  });
705
751
  const out = /* @__PURE__ */ new Map();
706
752
  for (const row of res.rows) {
707
753
  const r = row;
708
754
  const stats = {
709
- head: to_committed(
755
+ head: await to_committed(
710
756
  r.id,
711
757
  r.stream,
712
758
  r.version,
@@ -718,7 +764,7 @@ var SqliteStore = class {
718
764
  )
719
765
  };
720
766
  if (want_tail && r.t_id !== null && r.t_id !== void 0) {
721
- stats.tail = to_committed(
767
+ stats.tail = await to_committed(
722
768
  r.t_id,
723
769
  r.t_stream,
724
770
  r.t_version,
@@ -816,6 +862,7 @@ var SqliteStore = class {
816
862
  await tx.execute("DELETE FROM streams");
817
863
  await tx.execute("DELETE FROM sqlite_sequence WHERE name = 'events'");
818
864
  await driver(async (event) => {
865
+ const pii_for_write = await this._stringify_pii_for_write(event.pii);
819
866
  const ins = await tx.execute({
820
867
  sql: "INSERT INTO events (stream, version, name, data, meta, created, pii) VALUES (?, ?, ?, ?, ?, ?, ?)",
821
868
  args: [
@@ -825,7 +872,7 @@ var SqliteStore = class {
825
872
  JSON.stringify(event.data),
826
873
  JSON.stringify(event.meta),
827
874
  event.created.toISOString(),
828
- stringify_pii(event.pii)
875
+ pii_for_write
829
876
  ]
830
877
  });
831
878
  return Number(ins.lastInsertRowid);