@rotorsoft/act-sqlite 1.18.0 → 1.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.tsbuildinfo +1 -1
- package/dist/@types/sqlite-store.d.ts +6 -6
- package/dist/@types/sqlite-store.d.ts.map +1 -1
- package/dist/index.cjs +83 -14
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +83 -14
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs.map
CHANGED
|
@@ -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 SubscribeInput,\n} from \"@rotorsoft/act\";\nimport {\n ConcurrencyError,\n dateReviver,\n is_literal_source,\n StoreError,\n ValidationError,\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 /**\n * libSQL connection URL. **Required — there is no default.**\n *\n * Accepted forms:\n *\n * - `file:myapp.db` — a local database file, relative or absolute.\n * This is the shape an embedded deployment wants.\n * - `libsql://…` / `https://…` — a remote libSQL server, paired with\n * {@link SqliteConfig.authToken}.\n * - `:memory:` / `file::memory:` — an in-memory database. Normalized\n * to `file::memory:?cache=shared`, because libSQL hands every\n * *connection* its own private database otherwise and the client\n * does not pin statements to one connection — DDL would land in a\n * database later statements cannot see, so writes would be accepted\n * and then silently lost. Shared cache is the only in-memory mode\n * that round-trips, and it comes with a caveat worth knowing: it is\n * **one database per process**, shared by every store pointed at\n * it, and it outlives {@link SqliteStore.dispose}. For isolated\n * throwaway state prefer `InMemoryStore` from `@rotorsoft/act`, or\n * give each store its own `file:` path.\n *\n * `file::memory:?cache=private` is rejected at construction — it is\n * the failure mode above, spelled out explicitly.\n */\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\n/**\n * The only in-memory URL libSQL serves from a single database. A bare\n * `:memory:` / `file::memory:` gives every connection a *private*\n * database, and the client is free to run consecutive statements on\n * different connections — `seed()`'s DDL then lands somewhere the next\n * `commit` cannot see, which surfaces as a successful commit followed\n * by \"no such table: events\" on readback.\n * @internal\n */\nconst SHARED_MEMORY_URL = \"file::memory:?cache=shared\";\n\n/**\n * Recognize libSQL's in-memory URL forms: the bare `:memory:` alias and\n * the `file::memory:` URI it expands to, with or without a query string.\n * Mirrors `isInMemoryConfig` in `@libsql/core`.\n * @internal\n */\nconst is_in_memory_url = (url: string): boolean =>\n url === \":memory:\" ||\n url === \"file::memory:\" ||\n url.startsWith(\"file::memory:?\");\n\n/**\n * Validate and normalize the configured URL.\n *\n * Two failures are turned into loud construction-time errors rather\n * than silent data loss: a missing URL (there is no safe default — a\n * store has to be told where to write) and an explicitly private\n * in-memory database (unusable, for the reason on\n * {@link SHARED_MEMORY_URL}). Every other in-memory spelling is\n * normalized to the shared-cache form so it actually round-trips.\n * @internal\n */\nconst resolve_url = (url: unknown): string => {\n if (typeof url !== \"string\" || url.trim().length === 0)\n throw new ValidationError(\n 'SqliteStore config — \"url\" is required and has no default. Use a file database (\"file:myapp.db\"), a remote libSQL server (\"libsql://…\" plus authToken), or \":memory:\" for a process-wide shared in-memory database. For isolated throwaway state, prefer InMemoryStore from @rotorsoft/act',\n { url },\n \"missing url\"\n );\n if (url.startsWith(\"file::memory:?\") && url.includes(\"cache=private\"))\n throw new ValidationError(\n 'SqliteStore config — a private in-memory database (\"cache=private\") cannot back a store: libSQL gives each connection its own database, so writes are accepted and then lost on the next statement. Use \":memory:\" for the shared-cache database, a \"file:\" path, or InMemoryStore from @rotorsoft/act',\n { url },\n \"private in-memory url\"\n );\n return is_in_memory_url(url) ? SHARED_MEMORY_URL : url;\n};\n\n/**\n * Parse a JSON-encoded TEXT column, reviving ISO-date strings to `Date`\n * for cross-adapter payload parity (#1198).\n * @internal\n */\nconst parse_json = (raw: string): unknown => JSON.parse(raw, dateReviver);\n\n/**\n * SQLite extended result code for a UNIQUE constraint violation\n * (`SQLITE_CONSTRAINT_UNIQUE`). libsql surfaces it on the error's\n * `rawCode`.\n * @internal\n */\nconst SQLITE_CONSTRAINT_UNIQUE = 2067;\n\n/**\n * Recognize a `(stream, version)` unique-constraint violation from a\n * libsql commit error (#1202). Detects via the extended result code when\n * present, falling back to the message text (`UNIQUE constraint failed`)\n * for drivers that surface only the message.\n * @internal\n */\nconst is_unique_violation = (e: unknown): boolean => {\n const err = e as { rawCode?: number; message?: unknown };\n return (\n err?.rawCode === SQLITE_CONSTRAINT_UNIQUE ||\n (typeof err?.message === \"string\" &&\n err.message.includes(\"UNIQUE constraint failed\"))\n );\n};\n\n/** Portable stream-filter body grammar: literal characters, `.` (any\n * single character), and `.*` (any run). Everything else — escapes\n * (`\\.`), character classes (`[0-9]`), grouping/alternation (`(a|b)`),\n * quantifiers (`+ ? { }`), and mid-pattern anchors — has no faithful\n * `GLOB` translation and must throw instead of silently mis-matching.\n * @internal\n */\nconst PORTABLE_BODY = /^(?:\\.\\*|\\.|[^\\\\^$.()[\\]{}|+?*])*$/;\n\n/** Translate a stream filter (regex-shaped or plain substring) into a\n * SQLite `GLOB` pattern.\n *\n * #1197: `GLOB` — not `LIKE` — because `GLOB` is **case-sensitive**,\n * matching PG's `~` and InMemory's `RegExp`. SQLite's `LIKE` is ASCII\n * case-insensitive, and its per-connection `PRAGMA case_sensitive_like`\n * override is unreliable under libsql's pooled connections (a\n * transaction and a follow-up read can land on different connections).\n * `GLOB` carries its case-sensitivity in the operator itself, so it is\n * correct regardless of which pooled connection runs the statement.\n *\n * Honors `^` / `$` anchors and converts `.*` → `*`, `.` → `?`.\n * Unanchored input gets `*` wildcards on both sides. `GLOB` has no\n * ESCAPE clause and `%` / `_` are ordinary literals under it — so no\n * escaping is needed. The portable grammar rejects `*`, `?`, and `[`\n * as literals (they are metacharacters), so a literal can never collide\n * with a `GLOB` wildcard.\n *\n * Only the portable filter grammar documented on\n * {@link QueryStreams.stream} is convertible: `^` / `$` anchors, `.`,\n * `.*`, and literal characters. Any richer regex syntax throws\n * {@link ValidationError} — a lossy approximation would silently\n * return wrong rows, the worst available failure mode when the filter\n * drives `reset` / `unblock`.\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 * - `^a_c$` → `a_c` (literal underscore — ordinary under GLOB)\n * - `(a|b)` → throws {@link ValidationError}\n *\n * @internal exported for testing\n */\nexport function streamPatternToGlob(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 if (!PORTABLE_BODY.test(s))\n throw new ValidationError(\n `stream filter pattern \"${input}\" — the SQLite adapter supports only the portable subset: \"^\" and \"$\" anchors, \".\" (any single character), \".*\" (any run), and literal characters. Escapes, character classes, grouping, alternation, and quantifiers cannot be converted to GLOB; use stream_exact/source_exact for literal names`,\n input,\n \"non-portable stream filter pattern\"\n );\n const tokens: string[] = [];\n if (!start) tokens.push(\"*\");\n for (let i = 0; i < s.length; i++) {\n const c = s[i];\n if (c === \".\") {\n if (s[i + 1] === \"*\") {\n tokens.push(\"*\");\n i++;\n } else tokens.push(\"?\");\n } else tokens.push(c);\n }\n if (!end) tokens.push(\"*\");\n // Collapse adjacent `*` wildcards — e.g. `^a.*` would otherwise yield\n // `a**`. GLOB treats `**` as `*`, but collapsing keeps the output\n // minimal and matches the pre-#1197 LIKE behavior.\n let out = \"\";\n let prev = \"\";\n for (const t of tokens) {\n if (t === \"*\" && prev === \"*\") continue;\n out += t;\n prev = t;\n }\n return out;\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 *\n * SQLite is a single-writer store: concurrent write transactions raise\n * `SQLITE_BUSY` rather than serializing transparently, so it does not\n * support competing consumers the way PostgreSQL's `FOR UPDATE SKIP LOCKED`\n * does (the TCK leaves `concurrent_claim` off for this reason). The intended\n * model is a single drain worker per database file.\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: SqliteConfig) {\n this.client = createClient({\n url: resolve_url(config?.url),\n authToken: config.authToken,\n });\n this._resolve_pii_key = config.pii_encryption\n ? makeKeyResolver(config.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 // Revive dates like `data`/`meta` do (#1198/#1365). Base64 ciphertext\n // never matches the ISO-8601 pattern, so the encrypted branch below is\n // unaffected by the reviver here — it gets its own on the plaintext.\n const parsed = parse_json(raw as string);\n if (this._resolve_pii_key && typeof parsed === \"string\") {\n return (await decrypt(\n parsed,\n this._resolve_pii_key,\n dateReviver\n )) as Record<string, unknown>;\n }\n return parsed as Record<string, unknown>;\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 // Partial index over snapshot rows only, so the with_snaps \"resume at\n // the latest snapshot\" floor (MAX(id) WHERE stream=? AND\n // name='__snapshot__') is an O(log) lookup and costs nothing for\n // streams with no snapshot (the index has no rows for them).\n await this.client.execute(\n \"CREATE INDEX IF NOT EXISTS idx_events_snapshot ON events(stream, id) WHERE name = '__snapshot__'\"\n );\n // Complement of the snapshot index, and the one claim's has-work probe\n // seeks on: \"does this source stream have a non-snapshot event past the\n // watermark?\" (#1448). SQLite already probes per candidate with a\n // sargable `stream = ? AND id > ?`, so it only lacked the index — the\n // existing idx_events_stream is stream-only and still has to walk every\n // event of that stream. Partial over non-snapshot rows so the two\n // indexes partition the table rather than overlapping.\n await this.client.execute(\n \"CREATE INDEX IF NOT EXISTS idx_events_stream_id ON events(stream, id) WHERE name <> '__snapshot__'\"\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 -1,\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 deferred_at TEXT,\n correlated_at INTEGER\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 deferred reactions (#1090).\n try {\n await this.client.execute(\n \"ALTER TABLE streams ADD COLUMN deferred_at TEXT\"\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 // Migration for tables created before the work set (#1485).\n try {\n await this.client.execute(\n \"ALTER TABLE streams ADD COLUMN correlated_at INTEGER\"\n );\n } catch {\n // already present\n }\n // Rows that predate the mark get one, at the log's head (#1488).\n // `claim` is mark-only now, so a row left at NULL would silently stop\n // being served, and correlate cannot rescue it — its checkpoint is long\n // past those events.\n //\n // Deliberately an over-estimate: it says \"worth one look\", not \"there is\n // work here\". The first drain pass claims each such stream once, fetches\n // its window, handles whatever is really there, and acks — after which\n // the watermark catches the mark and the stream leaves the claimable set\n // with an honest position. One extra cycle per pre-existing subscription,\n // paid once, in exchange for a schema step with no per-row probing.\n await this.client.execute(\n `UPDATE streams\n SET correlated_at = (SELECT COALESCE(MAX(id), -1) FROM events)\n WHERE correlated_at IS NULL`\n );\n // Correlate checkpoint (#1484). Its own single-row table rather than a\n // reserved subscription: a subscription row is counted by every\n // stream-scoped operator surface (prioritize / reset / unblock /\n // query_streams / blocked_streams) and would inflate those counts.\n await this.client.execute(`\n CREATE TABLE IF NOT EXISTS correlated (\n id INTEGER PRIMARY KEY CHECK (id = 0),\n at INTEGER NOT NULL DEFAULT -1\n )\n `);\n await this.client.execute(\n \"INSERT OR IGNORE INTO correlated (id) VALUES (0)\"\n );\n await this.client.execute(\n \"CREATE INDEX IF NOT EXISTS idx_streams_claim ON streams(blocked, priority DESC, at)\"\n );\n // The correlated set IS the index (#1485): it holds only streams with\n // work, so `claim` scans candidates instead of every subscription. A\n // stream leaves when `ack` advances `at` to `correlated_at`, and re-enters\n // when correlate raises the mark.\n await this.client.execute(\n \"CREATE INDEX IF NOT EXISTS idx_streams_correlated_at ON streams(lane, priority DESC, at) WHERE blocked = 0 AND at < correlated_at\"\n );\n // The same set without the lane prefix. SQLite can only use an index for\n // ORDER BY when the leading columns are constrained, so a claim with no\n // lane filter — the shape every app that never calls `.withLane` issues —\n // could not use the index above and fell back to sorting the whole\n // eligible set. With this one the scan stops at `lagging + leading` rows.\n await this.client.execute(\n \"CREATE INDEX IF NOT EXISTS idx_streams_correlated_at_any_lane ON streams(priority DESC, at) WHERE blocked = 0 AND at < correlated_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 await this.client.execute(\"DROP TABLE IF EXISTS correlated\");\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 // Hoisted so the catch can build a ConcurrencyError with the head\n // version on a (stream, version) unique collision (#1202).\n let current_version = -1;\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 current_version = Number(version_row.rows[0].v);\n\n if (\n typeof expectedVersion === \"number\" &&\n current_version !== expectedVersion\n ) {\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 // #1202: map the (stream, version) unique collision to\n // ConcurrencyError — the same signal PG raises from 23505 — so a\n // caller retries on the framework contract, not a raw libsql error.\n // This is the race where the version probe passed but a concurrent\n // INSERT (a second store on the same file, tooling, the act CLI)\n // beat this one to the row. Every other driver error is wrapped in\n // StoreError('commit') for parity with the adapter's other methods.\n if (e instanceof ConcurrencyError) throw e;\n if (is_unique_violation(e))\n throw new ConcurrencyError(\n stream,\n current_version,\n msgs as Message<Schemas, keyof Schemas>[],\n expectedVersion ?? -1\n );\n throw new StoreError(\"commit\", { cause: 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 GLOB ?\";\n args.push(streamPatternToGlob(query.stream));\n }\n }\n if (query?.names !== undefined) {\n // #1199: `names: []` means \"match no event names\". An empty\n // placeholder list yields `name IN ()` which SQLite evaluates as\n // always-false — matching the PG (`= ANY('{}')`) / InMemory\n // semantics. `!== undefined` (not truthiness) keeps that explicit.\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 } else if (query?.with_snaps && query.stream_exact && query.stream) {\n // Resume at the latest snapshot for this stream so pre-snapshot\n // events aren't scanned. No snapshot → MAX is NULL → -1 → full\n // stream. An explicit `after` (above) wins. The orchestrator only sets\n // `with_snaps` for an unbounded current-state load — it suppresses the\n // flag under any `asOf` bound (RFC 1274) — so the floor never needs to\n // re-check `before`/`created_*`/`limit` here.\n sql +=\n \" AND id >= (SELECT COALESCE(MAX(id), -1) FROM events WHERE stream = ? AND name = '__snapshot__')\";\n args.push(query.stream);\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: parse_json(row.data as string) as any,\n meta: parse_json(row.meta as string) as any,\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(streams: SubscribeInput[], correlated_at?: number) {\n // Fail loud at registration for a claim source this adapter cannot\n // match: libsql has no `REGEXP`, and the portable GLOB grammar cannot\n // express alternation/grouping. A pattern source outside the portable\n // subset (e.g. `^(A|B)$`) would otherwise silently never claim its\n // stream, so `streamPatternToGlob` throws `ValidationError` here —\n // pointing operators to InMemory/PG for full regex claim sources —\n // before any row is written. Literal sources skip the check entirely.\n for (const { source } of streams) {\n if (source !== undefined && !is_literal_source(source))\n streamPatternToGlob(source);\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 correlated_at,\n } of streams) {\n const inserted = await tx.execute({\n sql: \"INSERT OR IGNORE INTO streams (stream, source, priority, lane, retry) VALUES (?, ?, ?, ?, -1)\",\n args: [stream, source ?? null, priority, lane],\n });\n if (inserted.rowsAffected > 0) {\n subscribed++;\n } else {\n // `WHERE priority < ?` is the whole max rule, for every value on\n // the number line. An extra `priority > 0` gate around this used to\n // skip the merge for non-positive priorities, which made a stored\n // negative priority unraisable: an operator's `prioritize(-5)`\n // survived every subsequent boot instead of being restored to the\n // declared priority by the restart-driven re-subscribe (#1445).\n await tx.execute({\n sql: \"UPDATE streams SET priority = ? WHERE stream = ? AND priority < ?\",\n args: [priority, stream, priority],\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 // The work mark never regresses (#1485). `WHERE correlated_at IS\n // NULL OR correlated_at < ?` is the whole rule: the NULL arm is first\n // mark (a comparison against NULL is unknown, so the predicate alone\n // would skip it), and it holds for every value including zero and\n // negatives. Written here rather than in the INSERT above so an\n // unmarked subscribe never names the column — a table that predates\n // it, and that `seed()` has not migrated, keeps working.\n if (correlated_at !== undefined)\n await tx.execute({\n sql: \"UPDATE streams SET correlated_at = ? WHERE stream = ? AND (correlated_at IS NULL OR correlated_at < ?)\",\n args: [correlated_at, stream, correlated_at],\n });\n }\n const wm = await tx.execute(\n \"SELECT COALESCE(MAX(at), -1) as w FROM streams\"\n );\n // The correlate checkpoint is written by its own producer, in the call\n // correlate already makes (#1484). MAX keeps it monotonic.\n if (correlated_at !== undefined)\n await tx.execute({\n sql: \"UPDATE correlated SET at = MAX(at, ?) WHERE id = 0\",\n args: [correlated_at],\n });\n // Watermark and checkpoint together — correlate needs both.\n const cp = await tx.execute(\"SELECT at FROM correlated WHERE id = 0\");\n await tx.commit();\n return {\n subscribed,\n watermark: Number(wm.rows[0].w),\n // `seed()` always inserts the singleton row, so this read cannot\n // come back empty — no defensive fallback to leave untested.\n correlated_at: Number(cp.rows[0].at),\n };\n } catch (e) {\n await tx.rollback();\n throw new StoreError(\"subscribe\", { cause: e });\n }\n }\n\n // --- claim: write transaction. Single-writer: concurrent claims from\n // separate connections raise SQLITE_BUSY, so this assumes one drain\n // worker per database file (see class doc; TCK `concurrent_claim` off).\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 // Eligibility is a pure subscription-table predicate (#1488). `claim`\n // does not read the events table at all: correlate records the highest\n // event id that resolves to a target, and `at < correlated_at` is the\n // whole question, answered by the partial index built for it.\n //\n // What this replaced was the worst shape in the adapter — every\n // eligible row selected with no limit, then one `SELECT 1 FROM events`\n // per candidate in a JavaScript loop with no early exit, all inside\n // `transaction(\"write\")`, so a claim's cost was linear in subscribed\n // streams and every concurrent commit queued behind it. There was no\n // index fix available for it, because the probe was in JS rather than\n // in SQL.\n //\n // A row with no mark is not claimable, by definition (#1446): NULL\n // means the row has never been correlated, not that it has no work.\n // An install upgrading from before the column needs one correlate pass\n // from a rewound checkpoint — see the runbook in\n // docs/docs/guides/production-checklist.md.\n // Three bounded reads, unioned, so the scan is O(lease budget)\n // rather than O(streams with work): the priority frontier, the\n // fairness reserve's pure-watermark order, and the leading frontier.\n // Their union is a superset of every row the split below can pick —\n // the fairness reserve needs at most `lagging` rows by `at ASC`, since\n // in the worst case every priority pick is also a lowest-watermark one.\n const elig = `blocked = 0 AND (leased_until IS NULL OR leased_until <= ?)\n AND (deferred_at IS NULL OR deferred_at <= ?)${lane_clause}\n AND at < correlated_at`;\n const cols = \"stream, source, at, priority, lane\";\n const where_args = lane !== undefined ? [now, now, lane] : [now, now];\n const result = await tx.execute({\n sql: `SELECT ${cols} FROM (SELECT ${cols} FROM streams WHERE ${elig}\n ORDER BY priority DESC, at ASC LIMIT ?)\n UNION\n SELECT ${cols} FROM (SELECT ${cols} FROM streams WHERE ${elig}\n ORDER BY at ASC LIMIT ?)\n UNION\n SELECT ${cols} FROM (SELECT ${cols} FROM streams WHERE ${elig}\n ORDER BY at DESC LIMIT ?)`,\n args: [\n ...where_args,\n lagging,\n ...where_args,\n lagging,\n ...where_args,\n leading,\n ],\n });\n\n // UNION does not preserve the operands' order, so re-establish the\n // one the split below reads: priority DESC, then watermark ASC. Over\n // this superset that yields the same head as the unbounded scan did.\n const candidates = result.rows\n .map((row) => ({\n stream: row.stream as string,\n source: (row.source as string | null) ?? undefined,\n at: Number(row.at),\n priority: Number(row.priority),\n lane: row.lane as string,\n }))\n .sort((a, b) => b.priority - a.priority || a.at - b.at);\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. The lagging\n // budget is split (ACT-1223): the priority portion takes the first\n // `lagging - fair` candidates in that order; a fairness reserve then\n // fills `fair` more by pure `at ASC` (priority ignored), excluding\n // the ones already picked, so a default-priority lagging stream is\n // never starved out by sustained higher-priority load. With all\n // priorities equal both portions order by `at`, a no-op split.\n const fair = lagging >= 2 ? Math.max(1, Math.floor(lagging / 4)) : 0;\n const priority_slice = candidates.slice(0, lagging - fair);\n const picked = new Set(priority_slice.map((c) => c.stream));\n const fair_slice = [...candidates]\n .sort((a, b) => a.at - b.at)\n .filter((c) => !picked.has(c.stream))\n .slice(0, fair);\n const lag = [...priority_slice, ...fair_slice].map((c) => ({\n ...c,\n lagging: true,\n }));\n const lead = [...candidates]\n .sort((a, b) => b.at - a.at)\n .slice(0, leading)\n .map((c) => ({ ...c, lagging: false }));\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 const updated = await tx.execute({\n sql: \"UPDATE streams SET leased_by = ?, leased_until = ?, retry = retry + 1 WHERE stream = ? RETURNING retry\",\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: Number(updated.rows[0].retry),\n lagging: row.lagging,\n lane: row.lane,\n });\n }\n\n await tx.commit();\n return leases;\n } catch (e) {\n await tx.rollback();\n throw new StoreError(\"claim\", { cause: 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 // The whole batch finalizes in one transaction, so acks and defer\n // schedules land all-or-nothing per the Store.ack contract. Every\n // entry advances the watermark to its `at` (the last event handled\n // this cycle); an entry without `due` also clears retry + schedule,\n // while an entry with `due` additionally sets the schedule and the\n // entry's own `retry` — advance and defer are independent legs\n // (#1278), so a partial-progress defer keeps the handled prefix. The\n // bound schedule doubles as the ack-vs-defer discriminator — NULL\n // means ack. An explicit defer passes retry -1 (not a failure); a\n // backoff retry passes the climbing counter so the budget keeps\n // accruing across windows (#1262). Only acked entries are returned.\n const result: Lease[] = [];\n for (const l of leases) {\n const due = l.due !== undefined ? new Date(l.due).toISOString() : null;\n const r = await tx.execute({\n // RETURNING the post-ack `retry`/`source`/`lane` from the row so the\n // returned lease reflects the authoritative state, not the caller's\n // pre-ack echo (#1347). A non-due ack sets `retry = -1`; pushing the\n // input lease verbatim leaked the claim-incremented value and\n // diverged from PG (RETURNING s.retry) and InMemory (which return the\n // reset -1). `at`/`by`/`lagging` still come from the input lease,\n // matching both other adapters.\n sql: `UPDATE streams\n SET at = ?,\n deferred_at = ?,\n leased_by = NULL, leased_until = NULL,\n retry = CASE WHEN ? IS NULL THEN -1 ELSE ? END\n WHERE stream = ? AND leased_by = ?\n RETURNING source, retry, lane`,\n args: [l.at, due, due, l.retry, l.stream, l.by],\n });\n if (due === null && r.rows.length > 0) {\n const row = r.rows[0];\n result.push({\n stream: l.stream,\n source: (row.source as string | null) ?? undefined,\n at: l.at,\n by: l.by,\n retry: Number(row.retry),\n lagging: l.lagging,\n lane: row.lane as string,\n });\n }\n }\n await tx.commit();\n return result;\n } catch (e) {\n await tx.rollback();\n throw new StoreError(\"ack\", { cause: 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 // RETURNING the post-block row so the returned lease reflects the\n // authoritative state, not the caller's echo (#1382, the `block`\n // twin of #1347). A block leaves the watermark alone — the failing\n // event must be retried, not skipped — but `run_drain_cycle` hands\n // us a lease whose `at` was fast-forwarded to the fetch ceiling, so\n // pushing the input verbatim reported a position the row never\n // held. `by`/`error`/`lagging` still come from the input lease,\n // matching PG (`RETURNING s.stream, s.source, s.at, i.by, ...`).\n sql: `UPDATE streams SET blocked = 1, error = ?, deferred_at = NULL\n WHERE stream = ? AND leased_by = ? AND blocked = 0\n RETURNING source, at, retry, lane`,\n args: [l.error, l.stream, l.by],\n });\n if (r.rows.length > 0) {\n const row = r.rows[0];\n result.push({\n stream: l.stream,\n source: (row.source as string | null) ?? undefined,\n at: Number(row.at),\n by: l.by,\n retry: Number(row.retry),\n error: l.error,\n lagging: l.lagging,\n lane: row.lane as string,\n });\n }\n }\n await tx.commit();\n return result;\n } catch (e) {\n await tx.rollback();\n throw new StoreError(\"block\", { cause: e });\n }\n }\n\n // --- defer: hold streams out of claim until deferred_at (#1090) ---\n // Persisted as an ISO string (like leased_until) so claim's\n // `deferred_at <= now` comparison is a lexicographic string compare.\n // Cleared by ack/block/reset/unblock. See {@link Store.defer}.\n async defer(input: string[] | StreamFilter, deferred_at: number) {\n const at_iso = new Date(deferred_at).toISOString();\n const tx = await this.client.transaction(\"write\");\n try {\n let count = 0;\n if (Array.isArray(input)) {\n // De-dup so a repeated name counts once, matching PG's set-based\n // `WHERE stream = ANY(...)` (#1360).\n for (const stream of new Set(input)) {\n const r = await tx.execute({\n sql: `UPDATE streams SET deferred_at = ?, retry = -1 WHERE stream = ?`,\n args: [at_iso, 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 deferred_at = ?, retry = -1 WHERE ${clause}`,\n args: [at_iso, ...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 new StoreError(\"defer\", { cause: 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 GLOB ?\");\n args.push(streamPatternToGlob(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 GLOB ?\");\n args.push(streamPatternToGlob(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 = -1, blocked = 0, error = '',\n leased_by = NULL, leased_until = NULL, deferred_at = NULL`;\n const tx = await this.client.transaction(\"write\");\n try {\n let count = 0;\n if (Array.isArray(input)) {\n // De-dup so a repeated name counts once, matching PG's set-based\n // `WHERE stream = ANY(...)` (#1360).\n for (const stream of new Set(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, deferred_at = 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, deferred_at, correlated_at 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 GLOB ?\";\n args.push(streamPatternToGlob(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 GLOB ?\";\n args.push(streamPatternToGlob(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 // Persisted as an ISO string (like leased_until); surface as ms since\n // epoch (#1221) so the cold-start re-seed re-arms at the due-time.\n const deferred_at = row.deferred_at as string | null;\n // NULL means \"no mark yet\" — unknown, not \"no work\" (#1485).\n const correlated_at = row.correlated_at as number | 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 deferred_at: deferred_at ? new Date(deferred_at).getTime() : undefined,\n correlated_at: correlated_at ?? undefined,\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 after = options?.after;\n const limit = options?.limit;\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 GLOB ?`);\n args.push(streamPatternToGlob(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 // Keyset pagination cursor (#1010): exclusive — return only streams\n // whose name sorts strictly after `after`. Applied at the event level\n // so the per-stream head/agg rows downstream only cover the page.\n if (after !== undefined) {\n where.push(`e.stream > ?`);\n args.push(after);\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 limit\n )\n : this._query_stats_heads_only<E>(\n from_clause,\n where_clause,\n args,\n want_tail,\n limit\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 limit?: number\n ): Promise<Map<string, StreamStats<E>>> {\n // `query_stats` is an operator-introspection surface with no actor\n // context and no disclosure gate, so it never carries pii — heads/tails\n // return the event shape without the pii sidecar, matching PostgresStore\n // and InMemoryStore (#1294).\n const cols = `e.id, e.stream, e.version, e.name, e.data, e.created, e.meta`;\n // Keyset pagination (#1010): order heads by stream ascending so the\n // caller can use the last key as the next `after` cursor; cap with\n // LIMIT when set (unbounded otherwise).\n const limit_clause = limit !== undefined ? \" LIMIT ?\" : \"\";\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 ORDER BY stream${limit_clause}`;\n const head_args = limit !== undefined ? [...args, limit] : args;\n // The tail query shares the same WHERE; when paginating it must cover\n // exactly the limited head stream set, so it joins against the same\n // ordered+limited heads subquery rather than re-scanning all streams.\n const tail_sql = want_tail\n ? `SELECT t.* 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 ) t\n WHERE t.rn = 1 AND t.stream IN (\n SELECT stream FROM (\n SELECT e.stream, 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 ORDER BY stream${limit_clause}\n )`\n : null;\n const tail_args =\n limit !== undefined ? [...args, ...args, limit] : [...args, ...args];\n\n const [headRes, tailRes] = await Promise.all([\n this.client.execute({ sql: head_sql, args: head_args as any[] }),\n tail_sql\n ? this.client.execute({ sql: tail_sql, args: tail_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: parse_json(row.data as string) as any,\n meta: parse_json(row.meta as string) as any,\n created: new Date(row.created as string),\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 limit?: number\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`\n : \"\";\n\n // No pii column: `query_stats` heads/tails are pii-free (#1294 — see\n // `_query_stats_heads_only`).\n const sql = `\n WITH ef AS (\n SELECT e.id, e.stream, e.version, e.name, e.data, e.created, e.meta\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 ORDER BY stream${limit !== undefined ? \" LIMIT ?\" : \"\"}\n )\n ${tail_cte}\n SELECT\n h.id, h.stream, h.version, h.name, h.data, h.created, h.meta,\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 ORDER BY h.stream\n `;\n\n // Keyset pagination (#1010): the `heads` CTE is ordered by stream and\n // capped with LIMIT (when set); the final SELECT re-asserts stream\n // order so the returned Map's key order is the next cursor. The LIMIT\n // arg binds last when present (unbounded otherwise).\n const full_args = limit !== undefined ? [...args, limit] : args;\n const res = await this.client.execute({ sql, args: full_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 ): 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: parse_json(data as string) as any,\n meta: parse_json(meta as string) as any,\n created: new Date(created as string),\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 ),\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 );\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 // Windowed targets (`before` set) prune the prefix below the closest safe\n // `__snapshot__` instead — no seed, subscriptions untouched, no-op when no\n // snapshot qualifies.\n async truncate(\n targets: Array<{\n stream: string;\n snapshot?: Record<string, unknown>;\n meta?: EventMeta;\n before?: Date;\n max_id?: number;\n }>\n ) {\n const result = new Map<\n string,\n {\n deleted: number;\n committed: Committed<Schemas, keyof Schemas>;\n before?: Date;\n }\n >();\n\n const tx = await this.client.transaction(\"write\");\n try {\n for (const { stream, before, max_id } of targets) {\n if (before === undefined) continue;\n // Closest safe boundary: latest snapshot older than the cutoff\n // and at/below the consumer watermark cap. ISO-8601 strings\n // compare lexicographically, so `created < ?` works on the text\n // column. No qualifying snapshot → no-op, absent from the result.\n const snap_row = await tx.execute({\n sql: `SELECT id, stream, version, name, data, meta, created\n FROM events\n WHERE stream = ? AND name = '__snapshot__' AND created < ?\n AND (? IS NULL OR id <= ?)\n ORDER BY id DESC LIMIT 1`,\n args: [stream, before.toISOString(), max_id ?? null, max_id ?? null],\n });\n if (!snap_row.rows.length) continue;\n const row = snap_row.rows[0];\n const boundary_id = Number(row.id);\n const del = await tx.execute({\n sql: \"DELETE FROM events WHERE stream = ? AND id < ?\",\n args: [stream, boundary_id],\n });\n result.set(stream, {\n deleted: del.rowsAffected,\n committed: {\n id: boundary_id,\n stream,\n version: Number(row.version),\n created: new Date(row.created as string),\n name: row.name as string,\n data: parse_json(row.data as string) as any,\n meta: parse_json(row.meta as string) as any,\n },\n before,\n });\n }\n for (const { stream, snapshot, meta, before } of targets) {\n if (before !== undefined) continue;\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 // Subscriptions are deliberately untouched, for restart *and* retire\n // targets alike — see the note in truncate's contract. A tombstoned\n // stream's subscription is inert, and reaping it is maintenance that\n // `seed()` performs (#1527).\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":["correlated_at"]}
|
|
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 SubscribeInput,\n SubscribeResult,\n} from \"@rotorsoft/act\";\nimport {\n ConcurrencyError,\n dateReviver,\n is_literal_source,\n StoreError,\n ValidationError,\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 /**\n * libSQL connection URL. **Required — there is no default.**\n *\n * Accepted forms:\n *\n * - `file:myapp.db` — a local database file, relative or absolute.\n * This is the shape an embedded deployment wants.\n * - `libsql://…` / `https://…` — a remote libSQL server, paired with\n * {@link SqliteConfig.authToken}.\n * - `:memory:` / `file::memory:` — an in-memory database. Normalized\n * to `file::memory:?cache=shared`, because libSQL hands every\n * *connection* its own private database otherwise and the client\n * does not pin statements to one connection — DDL would land in a\n * database later statements cannot see, so writes would be accepted\n * and then silently lost. Shared cache is the only in-memory mode\n * that round-trips, and it comes with a caveat worth knowing: it is\n * **one database per process**, shared by every store pointed at\n * it, and it outlives {@link SqliteStore.dispose}. For isolated\n * throwaway state prefer `InMemoryStore` from `@rotorsoft/act`, or\n * give each store its own `file:` path.\n *\n * `file::memory:?cache=private` is rejected at construction — it is\n * the failure mode above, spelled out explicitly.\n */\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\n/**\n * The only in-memory URL libSQL serves from a single database. A bare\n * `:memory:` / `file::memory:` gives every connection a *private*\n * database, and the client is free to run consecutive statements on\n * different connections — `seed()`'s DDL then lands somewhere the next\n * `commit` cannot see, which surfaces as a successful commit followed\n * by \"no such table: events\" on readback.\n * @internal\n */\nconst SHARED_MEMORY_URL = \"file::memory:?cache=shared\";\n\n/**\n * Recognize libSQL's in-memory URL forms: the bare `:memory:` alias and\n * the `file::memory:` URI it expands to, with or without a query string.\n * Mirrors `isInMemoryConfig` in `@libsql/core`.\n * @internal\n */\nconst is_in_memory_url = (url: string): boolean =>\n url === \":memory:\" ||\n url === \"file::memory:\" ||\n url.startsWith(\"file::memory:?\");\n\n/**\n * Validate and normalize the configured URL.\n *\n * Two failures are turned into loud construction-time errors rather\n * than silent data loss: a missing URL (there is no safe default — a\n * store has to be told where to write) and an explicitly private\n * in-memory database (unusable, for the reason on\n * {@link SHARED_MEMORY_URL}). Every other in-memory spelling is\n * normalized to the shared-cache form so it actually round-trips.\n * @internal\n */\nconst resolve_url = (url: unknown): string => {\n if (typeof url !== \"string\" || url.trim().length === 0)\n throw new ValidationError(\n 'SqliteStore config — \"url\" is required and has no default. Use a file database (\"file:myapp.db\"), a remote libSQL server (\"libsql://…\" plus authToken), or \":memory:\" for a process-wide shared in-memory database. For isolated throwaway state, prefer InMemoryStore from @rotorsoft/act',\n { url },\n \"missing url\"\n );\n if (url.startsWith(\"file::memory:?\") && url.includes(\"cache=private\"))\n throw new ValidationError(\n 'SqliteStore config — a private in-memory database (\"cache=private\") cannot back a store: libSQL gives each connection its own database, so writes are accepted and then lost on the next statement. Use \":memory:\" for the shared-cache database, a \"file:\" path, or InMemoryStore from @rotorsoft/act',\n { url },\n \"private in-memory url\"\n );\n return is_in_memory_url(url) ? SHARED_MEMORY_URL : url;\n};\n\n/**\n * Parse a JSON-encoded TEXT column, reviving ISO-date strings to `Date`\n * for cross-adapter payload parity (#1198).\n * @internal\n */\nconst parse_json = (raw: string): unknown => JSON.parse(raw, dateReviver);\n\n/**\n * SQLite extended result code for a UNIQUE constraint violation\n * (`SQLITE_CONSTRAINT_UNIQUE`). libsql surfaces it on the error's\n * `rawCode`.\n * @internal\n */\nconst SQLITE_CONSTRAINT_UNIQUE = 2067;\n\n/**\n * Recognize a `(stream, version)` unique-constraint violation from a\n * libsql commit error (#1202). Detects via the extended result code when\n * present, falling back to the message text (`UNIQUE constraint failed`)\n * for drivers that surface only the message.\n * @internal\n */\nconst is_unique_violation = (e: unknown): boolean => {\n const err = e as { rawCode?: number; message?: unknown };\n return (\n err?.rawCode === SQLITE_CONSTRAINT_UNIQUE ||\n (typeof err?.message === \"string\" &&\n err.message.includes(\"UNIQUE constraint failed\"))\n );\n};\n\n/** Portable stream-filter body grammar: literal characters, `.` (any\n * single character), and `.*` (any run). Everything else — escapes\n * (`\\.`), character classes (`[0-9]`), grouping/alternation (`(a|b)`),\n * quantifiers (`+ ? { }`), and mid-pattern anchors — has no faithful\n * `GLOB` translation and must throw instead of silently mis-matching.\n * @internal\n */\nconst PORTABLE_BODY = /^(?:\\.\\*|\\.|[^\\\\^$.()[\\]{}|+?*])*$/;\n\n/** Translate a stream filter (regex-shaped or plain substring) into a\n * SQLite `GLOB` pattern.\n *\n * #1197: `GLOB` — not `LIKE` — because `GLOB` is **case-sensitive**,\n * matching PG's `~` and InMemory's `RegExp`. SQLite's `LIKE` is ASCII\n * case-insensitive, and its per-connection `PRAGMA case_sensitive_like`\n * override is unreliable under libsql's pooled connections (a\n * transaction and a follow-up read can land on different connections).\n * `GLOB` carries its case-sensitivity in the operator itself, so it is\n * correct regardless of which pooled connection runs the statement.\n *\n * Honors `^` / `$` anchors and converts `.*` → `*`, `.` → `?`.\n * Unanchored input gets `*` wildcards on both sides. `GLOB` has no\n * ESCAPE clause and `%` / `_` are ordinary literals under it — so no\n * escaping is needed. The portable grammar rejects `*`, `?`, and `[`\n * as literals (they are metacharacters), so a literal can never collide\n * with a `GLOB` wildcard.\n *\n * Only the portable filter grammar documented on\n * {@link QueryStreams.stream} is convertible: `^` / `$` anchors, `.`,\n * `.*`, and literal characters. Any richer regex syntax throws\n * {@link ValidationError} — a lossy approximation would silently\n * return wrong rows, the worst available failure mode when the filter\n * drives `reset` / `unblock`.\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 * - `^a_c$` → `a_c` (literal underscore — ordinary under GLOB)\n * - `(a|b)` → throws {@link ValidationError}\n *\n * @internal exported for testing\n */\nexport function streamPatternToGlob(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 if (!PORTABLE_BODY.test(s))\n throw new ValidationError(\n `stream filter pattern \"${input}\" — the SQLite adapter supports only the portable subset: \"^\" and \"$\" anchors, \".\" (any single character), \".*\" (any run), and literal characters. Escapes, character classes, grouping, alternation, and quantifiers cannot be converted to GLOB; use stream_exact/source_exact for literal names`,\n input,\n \"non-portable stream filter pattern\"\n );\n const tokens: string[] = [];\n if (!start) tokens.push(\"*\");\n for (let i = 0; i < s.length; i++) {\n const c = s[i];\n if (c === \".\") {\n if (s[i + 1] === \"*\") {\n tokens.push(\"*\");\n i++;\n } else tokens.push(\"?\");\n } else tokens.push(c);\n }\n if (!end) tokens.push(\"*\");\n // Collapse adjacent `*` wildcards — e.g. `^a.*` would otherwise yield\n // `a**`. GLOB treats `**` as `*`, but collapsing keeps the output\n // minimal and matches the pre-#1197 LIKE behavior.\n let out = \"\";\n let prev = \"\";\n for (const t of tokens) {\n if (t === \"*\" && prev === \"*\") continue;\n out += t;\n prev = t;\n }\n return out;\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 *\n * SQLite is a single-writer store: concurrent write transactions raise\n * `SQLITE_BUSY` rather than serializing transparently, so it does not\n * support competing consumers the way PostgreSQL's `FOR UPDATE SKIP LOCKED`\n * does (the TCK leaves `concurrent_claim` off for this reason). The intended\n * model is a single drain worker per database file.\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: SqliteConfig) {\n this.client = createClient({\n url: resolve_url(config?.url),\n authToken: config.authToken,\n });\n this._resolve_pii_key = config.pii_encryption\n ? makeKeyResolver(config.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 // Revive dates like `data`/`meta` do (#1198/#1365). Base64 ciphertext\n // never matches the ISO-8601 pattern, so the encrypted branch below is\n // unaffected by the reviver here — it gets its own on the plaintext.\n const parsed = parse_json(raw as string);\n if (this._resolve_pii_key && typeof parsed === \"string\") {\n return (await decrypt(\n parsed,\n this._resolve_pii_key,\n dateReviver\n )) as Record<string, unknown>;\n }\n return parsed as Record<string, unknown>;\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 // Partial index over snapshot rows only, so the with_snaps \"resume at\n // the latest snapshot\" floor (MAX(id) WHERE stream=? AND\n // name='__snapshot__') is an O(log) lookup and costs nothing for\n // streams with no snapshot (the index has no rows for them).\n await this.client.execute(\n \"CREATE INDEX IF NOT EXISTS idx_events_snapshot ON events(stream, id) WHERE name = '__snapshot__'\"\n );\n // Complement of the snapshot index, and the one claim's has-work probe\n // seeks on: \"does this source stream have a non-snapshot event past the\n // watermark?\" (#1448). SQLite already probes per candidate with a\n // sargable `stream = ? AND id > ?`, so it only lacked the index — the\n // existing idx_events_stream is stream-only and still has to walk every\n // event of that stream. Partial over non-snapshot rows so the two\n // indexes partition the table rather than overlapping.\n await this.client.execute(\n \"CREATE INDEX IF NOT EXISTS idx_events_stream_id ON events(stream, id) WHERE name <> '__snapshot__'\"\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 -1,\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 deferred_at TEXT,\n correlated_at INTEGER\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 deferred reactions (#1090).\n try {\n await this.client.execute(\n \"ALTER TABLE streams ADD COLUMN deferred_at TEXT\"\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 // Migration for tables created before the work set (#1485).\n try {\n await this.client.execute(\n \"ALTER TABLE streams ADD COLUMN correlated_at INTEGER\"\n );\n } catch {\n // already present\n }\n // Rows that predate the mark get one, at the log's head (#1488).\n // `claim` is mark-only now, so a row left at NULL would silently stop\n // being served, and correlate cannot rescue it — its checkpoint is long\n // past those events.\n //\n // Deliberately an over-estimate: it says \"worth one look\", not \"there is\n // work here\". The first drain pass claims each such stream once, fetches\n // its window, handles whatever is really there, and acks — after which\n // the watermark catches the mark and the stream leaves the claimable set\n // with an honest position. One extra cycle per pre-existing subscription,\n // paid once, in exchange for a schema step with no per-row probing.\n await this.client.execute(\n `UPDATE streams\n SET correlated_at = (SELECT COALESCE(MAX(id), -1) FROM events)\n WHERE correlated_at IS NULL`\n );\n // Correlate checkpoint (#1484), keyed per correlator (#1532).\n //\n // Its own table rather than a reserved subscription: a subscription row is\n // counted by every stream-scoped operator surface (prioritize / reset /\n // unblock / query_streams / blocked_streams) and would inflate those\n // counts.\n //\n // Keyed because correlators that look for different things read the log\n // for different reasons. One row let a worker running partial behaviour\n // inherit another's position, and would let one hold a lease the other\n // needs. The empty key is the shared row a caller with no correlator\n // reads, and the floor a brand-new key inherits.\n //\n // SQLite cannot drop a primary key, so migrating the pre-#1532 shape\n // means rebuilding: copy the single row's checkpoint across as the shared\n // row, then swap the tables. Guarded on the old `id` column, so a seed\n // against either shape converges and the checkpoint survives.\n const correlated_columns = await this.client.execute(\n \"PRAGMA table_info(correlated)\"\n );\n const legacy = correlated_columns.rows.some((r) => r.name === \"id\");\n if (legacy) {\n await this.client.execute(`\n CREATE TABLE IF NOT EXISTS correlated_keyed (\n key TEXT PRIMARY KEY,\n at INTEGER NOT NULL DEFAULT -1,\n leased_by TEXT,\n leased_until INTEGER\n )\n `);\n await this.client.execute(\n \"INSERT OR IGNORE INTO correlated_keyed (key, at) SELECT '', at FROM correlated WHERE id = 0\"\n );\n await this.client.execute(\"DROP TABLE correlated\");\n await this.client.execute(\n \"ALTER TABLE correlated_keyed RENAME TO correlated\"\n );\n } else {\n await this.client.execute(`\n CREATE TABLE IF NOT EXISTS correlated (\n key TEXT PRIMARY KEY,\n at INTEGER NOT NULL DEFAULT -1,\n leased_by TEXT,\n leased_until INTEGER\n )\n `);\n }\n await this.client.execute(\n \"INSERT OR IGNORE INTO correlated (key) VALUES ('')\"\n );\n await this.client.execute(\n \"CREATE INDEX IF NOT EXISTS idx_streams_claim ON streams(blocked, priority DESC, at)\"\n );\n // The correlated set IS the index (#1485): it holds only streams with\n // work, so `claim` scans candidates instead of every subscription. A\n // stream leaves when `ack` advances `at` to `correlated_at`, and re-enters\n // when correlate raises the mark.\n await this.client.execute(\n \"CREATE INDEX IF NOT EXISTS idx_streams_correlated_at ON streams(lane, priority DESC, at) WHERE blocked = 0 AND at < correlated_at\"\n );\n // The same set without the lane prefix. SQLite can only use an index for\n // ORDER BY when the leading columns are constrained, so a claim with no\n // lane filter — the shape every app that never calls `.withLane` issues —\n // could not use the index above and fell back to sorting the whole\n // eligible set. With this one the scan stops at `lagging + leading` rows.\n await this.client.execute(\n \"CREATE INDEX IF NOT EXISTS idx_streams_correlated_at_any_lane ON streams(priority DESC, at) WHERE blocked = 0 AND at < correlated_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 await this.client.execute(\"DROP TABLE IF EXISTS correlated\");\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 // Hoisted so the catch can build a ConcurrencyError with the head\n // version on a (stream, version) unique collision (#1202).\n let current_version = -1;\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 current_version = Number(version_row.rows[0].v);\n\n if (\n typeof expectedVersion === \"number\" &&\n current_version !== expectedVersion\n ) {\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 // #1202: map the (stream, version) unique collision to\n // ConcurrencyError — the same signal PG raises from 23505 — so a\n // caller retries on the framework contract, not a raw libsql error.\n // This is the race where the version probe passed but a concurrent\n // INSERT (a second store on the same file, tooling, the act CLI)\n // beat this one to the row. Every other driver error is wrapped in\n // StoreError('commit') for parity with the adapter's other methods.\n if (e instanceof ConcurrencyError) throw e;\n if (is_unique_violation(e))\n throw new ConcurrencyError(\n stream,\n current_version,\n msgs as Message<Schemas, keyof Schemas>[],\n expectedVersion ?? -1\n );\n throw new StoreError(\"commit\", { cause: 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 GLOB ?\";\n args.push(streamPatternToGlob(query.stream));\n }\n }\n if (query?.names !== undefined) {\n // #1199: `names: []` means \"match no event names\". An empty\n // placeholder list yields `name IN ()` which SQLite evaluates as\n // always-false — matching the PG (`= ANY('{}')`) / InMemory\n // semantics. `!== undefined` (not truthiness) keeps that explicit.\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 } else if (query?.with_snaps && query.stream_exact && query.stream) {\n // Resume at the latest snapshot for this stream so pre-snapshot\n // events aren't scanned. No snapshot → MAX is NULL → -1 → full\n // stream. An explicit `after` (above) wins. The orchestrator only sets\n // `with_snaps` for an unbounded current-state load — it suppresses the\n // flag under any `asOf` bound (RFC 1274) — so the floor never needs to\n // re-check `before`/`created_*`/`limit` here.\n sql +=\n \" AND id >= (SELECT COALESCE(MAX(id), -1) FROM events WHERE stream = ? AND name = '__snapshot__')\";\n args.push(query.stream);\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: parse_json(row.data as string) as any,\n meta: parse_json(row.meta as string) as any,\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: SubscribeInput[],\n correlated_at?: number,\n correlator?: { key: string; by: string; millis: number }\n ): Promise<SubscribeResult> {\n // Fail loud at registration for a claim source this adapter cannot\n // match: libsql has no `REGEXP`, and the portable GLOB grammar cannot\n // express alternation/grouping. A pattern source outside the portable\n // subset (e.g. `^(A|B)$`) would otherwise silently never claim its\n // stream, so `streamPatternToGlob` throws `ValidationError` here —\n // pointing operators to InMemory/PG for full regex claim sources —\n // before any row is written. Literal sources skip the check entirely.\n for (const { source } of streams) {\n if (source !== undefined && !is_literal_source(source))\n streamPatternToGlob(source);\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 correlated_at,\n } of streams) {\n const inserted = await tx.execute({\n sql: \"INSERT OR IGNORE INTO streams (stream, source, priority, lane, retry) VALUES (?, ?, ?, ?, -1)\",\n args: [stream, source ?? null, priority, lane],\n });\n if (inserted.rowsAffected > 0) {\n subscribed++;\n } else {\n // `WHERE priority < ?` is the whole max rule, for every value on\n // the number line. An extra `priority > 0` gate around this used to\n // skip the merge for non-positive priorities, which made a stored\n // negative priority unraisable: an operator's `prioritize(-5)`\n // survived every subsequent boot instead of being restored to the\n // declared priority by the restart-driven re-subscribe (#1445).\n await tx.execute({\n sql: \"UPDATE streams SET priority = ? WHERE stream = ? AND priority < ?\",\n args: [priority, stream, priority],\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 // The work mark never regresses (#1485). `WHERE correlated_at IS\n // NULL OR correlated_at < ?` is the whole rule: the NULL arm is first\n // mark (a comparison against NULL is unknown, so the predicate alone\n // would skip it), and it holds for every value including zero and\n // negatives. Written here rather than in the INSERT above so an\n // unmarked subscribe never names the column — a table that predates\n // it, and that `seed()` has not migrated, keeps working.\n if (correlated_at !== undefined)\n await tx.execute({\n sql: \"UPDATE streams SET correlated_at = ? WHERE stream = ? AND (correlated_at IS NULL OR correlated_at < ?)\",\n args: [correlated_at, stream, correlated_at],\n });\n }\n const wm = await tx.execute(\n \"SELECT COALESCE(MAX(at), -1) as w FROM streams\"\n );\n // The correlate checkpoint is written by its own producer, in the call\n // correlate already makes (#1484). MAX keeps it monotonic.\n // With a correlator the position and the lease are per-key (#1532),\n // both in one conditional upsert: a read then a write would let two\n // workers that both saw an expired lease believe they hold it, which is\n // the duplication the lease removes. A key with no row yet seeds from\n // the shared row, so an upgrade does not re-read history.\n let correlating: boolean | undefined;\n if (correlator) {\n const now = Date.now();\n const taken = await tx.execute({\n sql: `INSERT INTO correlated (key, at, leased_by, leased_until)\n SELECT ?,\n MAX(COALESCE(?, -1),\n COALESCE((SELECT at FROM correlated WHERE key = ''), -1)),\n ?, ?\n ON CONFLICT (key) DO UPDATE\n SET at = MAX(correlated.at, COALESCE(?, -1)),\n leased_by = excluded.leased_by,\n leased_until = excluded.leased_until\n WHERE correlated.leased_until IS NULL\n OR correlated.leased_until < ?\n OR correlated.leased_by = excluded.leased_by`,\n args: [\n correlator.key,\n correlated_at ?? null,\n correlator.by,\n // `millis <= 0` releases outright rather than setting an expiry a\n // hair in the future, which would still refuse a successor asking\n // in the same instant.\n correlator.millis > 0 ? now + correlator.millis : null,\n correlated_at ?? null,\n now,\n ],\n });\n correlating = taken.rowsAffected > 0;\n // Losing the race must still advance the checkpoint this worker\n // already read past, or its marks and its position disagree.\n if (!correlating && correlated_at !== undefined)\n await tx.execute({\n sql: \"UPDATE correlated SET at = MAX(at, ?) WHERE key = ?\",\n args: [correlated_at, correlator.key],\n });\n // The shared row tracks how far *any* correlator has read: the floor\n // a brand-new key inherits, and what a caller reading without a\n // correlator still sees.\n if (correlated_at !== undefined)\n await tx.execute({\n sql: \"UPDATE correlated SET at = MAX(at, ?) WHERE key = ''\",\n args: [correlated_at],\n });\n } else if (correlated_at !== undefined)\n await tx.execute({\n sql: \"UPDATE correlated SET at = MAX(at, ?) WHERE key = ''\",\n args: [correlated_at],\n });\n // Watermark and checkpoint together — correlate needs both.\n const cp = await tx.execute({\n sql: \"SELECT at FROM correlated WHERE key = ?\",\n args: [correlator?.key ?? \"\"],\n });\n await tx.commit();\n return {\n subscribed,\n watermark: Number(wm.rows[0].w),\n // `seed()` always inserts the shared row, and a correlator's row is\n // created by the upsert above — or already existed, which is why the\n // lease was refused. Either way this read cannot come back empty, so\n // there is no defensive fallback to leave untested.\n correlated_at: Number(cp.rows[0].at),\n ...(correlating === undefined ? {} : { correlating }),\n };\n } catch (e) {\n await tx.rollback();\n throw new StoreError(\"subscribe\", { cause: e });\n }\n }\n\n // --- claim: write transaction. Single-writer: concurrent claims from\n // separate connections raise SQLITE_BUSY, so this assumes one drain\n // worker per database file (see class doc; TCK `concurrent_claim` off).\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 // Eligibility is a pure subscription-table predicate (#1488). `claim`\n // does not read the events table at all: correlate records the highest\n // event id that resolves to a target, and `at < correlated_at` is the\n // whole question, answered by the partial index built for it.\n //\n // What this replaced was the worst shape in the adapter — every\n // eligible row selected with no limit, then one `SELECT 1 FROM events`\n // per candidate in a JavaScript loop with no early exit, all inside\n // `transaction(\"write\")`, so a claim's cost was linear in subscribed\n // streams and every concurrent commit queued behind it. There was no\n // index fix available for it, because the probe was in JS rather than\n // in SQL.\n //\n // A row with no mark is not claimable, by definition (#1446): NULL\n // means the row has never been correlated, not that it has no work.\n // An install upgrading from before the column needs one correlate pass\n // from a rewound checkpoint — see the runbook in\n // docs/docs/guides/production-checklist.md.\n // Three bounded reads, unioned, so the scan is O(lease budget)\n // rather than O(streams with work): the priority frontier, the\n // fairness reserve's pure-watermark order, and the leading frontier.\n // Their union is a superset of every row the split below can pick —\n // the fairness reserve needs at most `lagging` rows by `at ASC`, since\n // in the worst case every priority pick is also a lowest-watermark one.\n const elig = `blocked = 0 AND (leased_until IS NULL OR leased_until <= ?)\n AND (deferred_at IS NULL OR deferred_at <= ?)${lane_clause}\n AND at < correlated_at`;\n const cols = \"stream, source, at, priority, lane\";\n const where_args = lane !== undefined ? [now, now, lane] : [now, now];\n const result = await tx.execute({\n sql: `SELECT ${cols} FROM (SELECT ${cols} FROM streams WHERE ${elig}\n ORDER BY priority DESC, at ASC LIMIT ?)\n UNION\n SELECT ${cols} FROM (SELECT ${cols} FROM streams WHERE ${elig}\n ORDER BY at ASC LIMIT ?)\n UNION\n SELECT ${cols} FROM (SELECT ${cols} FROM streams WHERE ${elig}\n ORDER BY at DESC LIMIT ?)`,\n args: [\n ...where_args,\n lagging,\n ...where_args,\n lagging,\n ...where_args,\n leading,\n ],\n });\n\n // UNION does not preserve the operands' order, so re-establish the\n // one the split below reads: priority DESC, then watermark ASC. Over\n // this superset that yields the same head as the unbounded scan did.\n const candidates = result.rows\n .map((row) => ({\n stream: row.stream as string,\n source: (row.source as string | null) ?? undefined,\n at: Number(row.at),\n priority: Number(row.priority),\n lane: row.lane as string,\n }))\n .sort((a, b) => b.priority - a.priority || a.at - b.at);\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. The lagging\n // budget is split (ACT-1223): the priority portion takes the first\n // `lagging - fair` candidates in that order; a fairness reserve then\n // fills `fair` more by pure `at ASC` (priority ignored), excluding\n // the ones already picked, so a default-priority lagging stream is\n // never starved out by sustained higher-priority load. With all\n // priorities equal both portions order by `at`, a no-op split.\n const fair = lagging >= 2 ? Math.max(1, Math.floor(lagging / 4)) : 0;\n const priority_slice = candidates.slice(0, lagging - fair);\n const picked = new Set(priority_slice.map((c) => c.stream));\n const fair_slice = [...candidates]\n .sort((a, b) => a.at - b.at)\n .filter((c) => !picked.has(c.stream))\n .slice(0, fair);\n const lag = [...priority_slice, ...fair_slice].map((c) => ({\n ...c,\n lagging: true,\n }));\n const lead = [...candidates]\n .sort((a, b) => b.at - a.at)\n .slice(0, leading)\n .map((c) => ({ ...c, lagging: false }));\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 const updated = await tx.execute({\n sql: \"UPDATE streams SET leased_by = ?, leased_until = ?, retry = retry + 1 WHERE stream = ? RETURNING retry\",\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: Number(updated.rows[0].retry),\n lagging: row.lagging,\n lane: row.lane,\n });\n }\n\n await tx.commit();\n return leases;\n } catch (e) {\n await tx.rollback();\n throw new StoreError(\"claim\", { cause: 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 // The whole batch finalizes in one transaction, so acks and defer\n // schedules land all-or-nothing per the Store.ack contract. Every\n // entry advances the watermark to its `at` (the last event handled\n // this cycle); an entry without `due` also clears retry + schedule,\n // while an entry with `due` additionally sets the schedule and the\n // entry's own `retry` — advance and defer are independent legs\n // (#1278), so a partial-progress defer keeps the handled prefix. The\n // bound schedule doubles as the ack-vs-defer discriminator — NULL\n // means ack. An explicit defer passes retry -1 (not a failure); a\n // backoff retry passes the climbing counter so the budget keeps\n // accruing across windows (#1262). Only acked entries are returned.\n const result: Lease[] = [];\n for (const l of leases) {\n const due = l.due !== undefined ? new Date(l.due).toISOString() : null;\n const r = await tx.execute({\n // RETURNING the post-ack `retry`/`source`/`lane` from the row so the\n // returned lease reflects the authoritative state, not the caller's\n // pre-ack echo (#1347). A non-due ack sets `retry = -1`; pushing the\n // input lease verbatim leaked the claim-incremented value and\n // diverged from PG (RETURNING s.retry) and InMemory (which return the\n // reset -1). `at`/`by`/`lagging` still come from the input lease,\n // matching both other adapters.\n sql: `UPDATE streams\n SET at = ?,\n deferred_at = ?,\n leased_by = NULL, leased_until = NULL,\n retry = CASE WHEN ? IS NULL THEN -1 ELSE ? END\n WHERE stream = ? AND leased_by = ?\n RETURNING source, retry, lane`,\n args: [l.at, due, due, l.retry, l.stream, l.by],\n });\n if (due === null && r.rows.length > 0) {\n const row = r.rows[0];\n result.push({\n stream: l.stream,\n source: (row.source as string | null) ?? undefined,\n at: l.at,\n by: l.by,\n retry: Number(row.retry),\n lagging: l.lagging,\n lane: row.lane as string,\n });\n }\n }\n await tx.commit();\n return result;\n } catch (e) {\n await tx.rollback();\n throw new StoreError(\"ack\", { cause: 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 // RETURNING the post-block row so the returned lease reflects the\n // authoritative state, not the caller's echo (#1382, the `block`\n // twin of #1347). A block leaves the watermark alone — the failing\n // event must be retried, not skipped — but `run_drain_cycle` hands\n // us a lease whose `at` was fast-forwarded to the fetch ceiling, so\n // pushing the input verbatim reported a position the row never\n // held. `by`/`error`/`lagging` still come from the input lease,\n // matching PG (`RETURNING s.stream, s.source, s.at, i.by, ...`).\n sql: `UPDATE streams SET blocked = 1, error = ?, deferred_at = NULL\n WHERE stream = ? AND leased_by = ? AND blocked = 0\n RETURNING source, at, retry, lane`,\n args: [l.error, l.stream, l.by],\n });\n if (r.rows.length > 0) {\n const row = r.rows[0];\n result.push({\n stream: l.stream,\n source: (row.source as string | null) ?? undefined,\n at: Number(row.at),\n by: l.by,\n retry: Number(row.retry),\n error: l.error,\n lagging: l.lagging,\n lane: row.lane as string,\n });\n }\n }\n await tx.commit();\n return result;\n } catch (e) {\n await tx.rollback();\n throw new StoreError(\"block\", { cause: e });\n }\n }\n\n // --- defer: hold streams out of claim until deferred_at (#1090) ---\n // Persisted as an ISO string (like leased_until) so claim's\n // `deferred_at <= now` comparison is a lexicographic string compare.\n // Cleared by ack/block/reset/unblock. See {@link Store.defer}.\n async defer(input: string[] | StreamFilter, deferred_at: number) {\n const at_iso = new Date(deferred_at).toISOString();\n const tx = await this.client.transaction(\"write\");\n try {\n let count = 0;\n if (Array.isArray(input)) {\n // De-dup so a repeated name counts once, matching PG's set-based\n // `WHERE stream = ANY(...)` (#1360).\n for (const stream of new Set(input)) {\n const r = await tx.execute({\n sql: `UPDATE streams SET deferred_at = ?, retry = -1 WHERE stream = ?`,\n args: [at_iso, 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 deferred_at = ?, retry = -1 WHERE ${clause}`,\n args: [at_iso, ...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 new StoreError(\"defer\", { cause: 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 GLOB ?\");\n args.push(streamPatternToGlob(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 GLOB ?\");\n args.push(streamPatternToGlob(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 = -1, blocked = 0, error = '',\n leased_by = NULL, leased_until = NULL, deferred_at = NULL`;\n const tx = await this.client.transaction(\"write\");\n try {\n let count = 0;\n if (Array.isArray(input)) {\n // De-dup so a repeated name counts once, matching PG's set-based\n // `WHERE stream = ANY(...)` (#1360).\n for (const stream of new Set(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, deferred_at = 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, deferred_at, correlated_at 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 GLOB ?\";\n args.push(streamPatternToGlob(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 GLOB ?\";\n args.push(streamPatternToGlob(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 // Persisted as an ISO string (like leased_until); surface as ms since\n // epoch (#1221) so the cold-start re-seed re-arms at the due-time.\n const deferred_at = row.deferred_at as string | null;\n // NULL means \"no mark yet\" — unknown, not \"no work\" (#1485).\n const correlated_at = row.correlated_at as number | 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 deferred_at: deferred_at ? new Date(deferred_at).getTime() : undefined,\n correlated_at: correlated_at ?? undefined,\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 after = options?.after;\n const limit = options?.limit;\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 GLOB ?`);\n args.push(streamPatternToGlob(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 // Keyset pagination cursor (#1010): exclusive — return only streams\n // whose name sorts strictly after `after`. Applied at the event level\n // so the per-stream head/agg rows downstream only cover the page.\n if (after !== undefined) {\n where.push(`e.stream > ?`);\n args.push(after);\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 limit\n )\n : this._query_stats_heads_only<E>(\n from_clause,\n where_clause,\n args,\n want_tail,\n limit\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 limit?: number\n ): Promise<Map<string, StreamStats<E>>> {\n // `query_stats` is an operator-introspection surface with no actor\n // context and no disclosure gate, so it never carries pii — heads/tails\n // return the event shape without the pii sidecar, matching PostgresStore\n // and InMemoryStore (#1294).\n const cols = `e.id, e.stream, e.version, e.name, e.data, e.created, e.meta`;\n // Keyset pagination (#1010): order heads by stream ascending so the\n // caller can use the last key as the next `after` cursor; cap with\n // LIMIT when set (unbounded otherwise).\n const limit_clause = limit !== undefined ? \" LIMIT ?\" : \"\";\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 ORDER BY stream${limit_clause}`;\n const head_args = limit !== undefined ? [...args, limit] : args;\n // The tail query shares the same WHERE; when paginating it must cover\n // exactly the limited head stream set, so it joins against the same\n // ordered+limited heads subquery rather than re-scanning all streams.\n const tail_sql = want_tail\n ? `SELECT t.* 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 ) t\n WHERE t.rn = 1 AND t.stream IN (\n SELECT stream FROM (\n SELECT e.stream, 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 ORDER BY stream${limit_clause}\n )`\n : null;\n const tail_args =\n limit !== undefined ? [...args, ...args, limit] : [...args, ...args];\n\n const [headRes, tailRes] = await Promise.all([\n this.client.execute({ sql: head_sql, args: head_args as any[] }),\n tail_sql\n ? this.client.execute({ sql: tail_sql, args: tail_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: parse_json(row.data as string) as any,\n meta: parse_json(row.meta as string) as any,\n created: new Date(row.created as string),\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 limit?: number\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`\n : \"\";\n\n // No pii column: `query_stats` heads/tails are pii-free (#1294 — see\n // `_query_stats_heads_only`).\n const sql = `\n WITH ef AS (\n SELECT e.id, e.stream, e.version, e.name, e.data, e.created, e.meta\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 ORDER BY stream${limit !== undefined ? \" LIMIT ?\" : \"\"}\n )\n ${tail_cte}\n SELECT\n h.id, h.stream, h.version, h.name, h.data, h.created, h.meta,\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 ORDER BY h.stream\n `;\n\n // Keyset pagination (#1010): the `heads` CTE is ordered by stream and\n // capped with LIMIT (when set); the final SELECT re-asserts stream\n // order so the returned Map's key order is the next cursor. The LIMIT\n // arg binds last when present (unbounded otherwise).\n const full_args = limit !== undefined ? [...args, limit] : args;\n const res = await this.client.execute({ sql, args: full_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 ): 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: parse_json(data as string) as any,\n meta: parse_json(meta as string) as any,\n created: new Date(created as string),\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 ),\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 );\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 // Windowed targets (`before` set) prune the prefix below the closest safe\n // `__snapshot__` instead — no seed, subscriptions untouched, no-op when no\n // snapshot qualifies.\n async truncate(\n targets: Array<{\n stream: string;\n snapshot?: Record<string, unknown>;\n meta?: EventMeta;\n before?: Date;\n max_id?: number;\n }>\n ) {\n const result = new Map<\n string,\n {\n deleted: number;\n committed: Committed<Schemas, keyof Schemas>;\n before?: Date;\n }\n >();\n\n const tx = await this.client.transaction(\"write\");\n try {\n for (const { stream, before, max_id } of targets) {\n if (before === undefined) continue;\n // Closest safe boundary: latest snapshot older than the cutoff\n // and at/below the consumer watermark cap. ISO-8601 strings\n // compare lexicographically, so `created < ?` works on the text\n // column. No qualifying snapshot → no-op, absent from the result.\n const snap_row = await tx.execute({\n sql: `SELECT id, stream, version, name, data, meta, created\n FROM events\n WHERE stream = ? AND name = '__snapshot__' AND created < ?\n AND (? IS NULL OR id <= ?)\n ORDER BY id DESC LIMIT 1`,\n args: [stream, before.toISOString(), max_id ?? null, max_id ?? null],\n });\n if (!snap_row.rows.length) continue;\n const row = snap_row.rows[0];\n const boundary_id = Number(row.id);\n const del = await tx.execute({\n sql: \"DELETE FROM events WHERE stream = ? AND id < ?\",\n args: [stream, boundary_id],\n });\n result.set(stream, {\n deleted: del.rowsAffected,\n committed: {\n id: boundary_id,\n stream,\n version: Number(row.version),\n created: new Date(row.created as string),\n name: row.name as string,\n data: parse_json(row.data as string) as any,\n meta: parse_json(row.meta as string) as any,\n },\n before,\n });\n }\n for (const { stream, snapshot, meta, before } of targets) {\n if (before !== undefined) continue;\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 // Subscriptions are deliberately untouched, for restart *and* retire\n // targets alike — see the note in truncate's contract. A tombstoned\n // stream's subscription is inert, and reaping it is maintenance that\n // `seed()` performs (#1527).\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":["correlated_at"]}
|
package/dist/index.js
CHANGED
|
@@ -198,14 +198,38 @@ var SqliteStore = class {
|
|
|
198
198
|
SET correlated_at = (SELECT COALESCE(MAX(id), -1) FROM events)
|
|
199
199
|
WHERE correlated_at IS NULL`
|
|
200
200
|
);
|
|
201
|
-
await this.client.execute(
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
201
|
+
const correlated_columns = await this.client.execute(
|
|
202
|
+
"PRAGMA table_info(correlated)"
|
|
203
|
+
);
|
|
204
|
+
const legacy = correlated_columns.rows.some((r) => r.name === "id");
|
|
205
|
+
if (legacy) {
|
|
206
|
+
await this.client.execute(`
|
|
207
|
+
CREATE TABLE IF NOT EXISTS correlated_keyed (
|
|
208
|
+
key TEXT PRIMARY KEY,
|
|
209
|
+
at INTEGER NOT NULL DEFAULT -1,
|
|
210
|
+
leased_by TEXT,
|
|
211
|
+
leased_until INTEGER
|
|
212
|
+
)
|
|
213
|
+
`);
|
|
214
|
+
await this.client.execute(
|
|
215
|
+
"INSERT OR IGNORE INTO correlated_keyed (key, at) SELECT '', at FROM correlated WHERE id = 0"
|
|
216
|
+
);
|
|
217
|
+
await this.client.execute("DROP TABLE correlated");
|
|
218
|
+
await this.client.execute(
|
|
219
|
+
"ALTER TABLE correlated_keyed RENAME TO correlated"
|
|
220
|
+
);
|
|
221
|
+
} else {
|
|
222
|
+
await this.client.execute(`
|
|
223
|
+
CREATE TABLE IF NOT EXISTS correlated (
|
|
224
|
+
key TEXT PRIMARY KEY,
|
|
225
|
+
at INTEGER NOT NULL DEFAULT -1,
|
|
226
|
+
leased_by TEXT,
|
|
227
|
+
leased_until INTEGER
|
|
228
|
+
)
|
|
229
|
+
`);
|
|
230
|
+
}
|
|
207
231
|
await this.client.execute(
|
|
208
|
-
"INSERT OR IGNORE INTO correlated (
|
|
232
|
+
"INSERT OR IGNORE INTO correlated (key) VALUES ('')"
|
|
209
233
|
);
|
|
210
234
|
await this.client.execute(
|
|
211
235
|
"CREATE INDEX IF NOT EXISTS idx_streams_claim ON streams(blocked, priority DESC, at)"
|
|
@@ -363,7 +387,7 @@ var SqliteStore = class {
|
|
|
363
387
|
// plus a UPDATE pass to keep the *max* priority across reactions
|
|
364
388
|
// targeting the same stream (ACT-102). Operator overrides go
|
|
365
389
|
// through `prioritize()` instead.
|
|
366
|
-
async subscribe(streams, correlated_at) {
|
|
390
|
+
async subscribe(streams, correlated_at, correlator) {
|
|
367
391
|
for (const { source } of streams) {
|
|
368
392
|
if (source !== void 0 && !is_literal_source(source))
|
|
369
393
|
streamPatternToGlob(source);
|
|
@@ -403,19 +427,64 @@ var SqliteStore = class {
|
|
|
403
427
|
const wm = await tx.execute(
|
|
404
428
|
"SELECT COALESCE(MAX(at), -1) as w FROM streams"
|
|
405
429
|
);
|
|
406
|
-
|
|
430
|
+
let correlating;
|
|
431
|
+
if (correlator) {
|
|
432
|
+
const now = Date.now();
|
|
433
|
+
const taken = await tx.execute({
|
|
434
|
+
sql: `INSERT INTO correlated (key, at, leased_by, leased_until)
|
|
435
|
+
SELECT ?,
|
|
436
|
+
MAX(COALESCE(?, -1),
|
|
437
|
+
COALESCE((SELECT at FROM correlated WHERE key = ''), -1)),
|
|
438
|
+
?, ?
|
|
439
|
+
ON CONFLICT (key) DO UPDATE
|
|
440
|
+
SET at = MAX(correlated.at, COALESCE(?, -1)),
|
|
441
|
+
leased_by = excluded.leased_by,
|
|
442
|
+
leased_until = excluded.leased_until
|
|
443
|
+
WHERE correlated.leased_until IS NULL
|
|
444
|
+
OR correlated.leased_until < ?
|
|
445
|
+
OR correlated.leased_by = excluded.leased_by`,
|
|
446
|
+
args: [
|
|
447
|
+
correlator.key,
|
|
448
|
+
correlated_at ?? null,
|
|
449
|
+
correlator.by,
|
|
450
|
+
// `millis <= 0` releases outright rather than setting an expiry a
|
|
451
|
+
// hair in the future, which would still refuse a successor asking
|
|
452
|
+
// in the same instant.
|
|
453
|
+
correlator.millis > 0 ? now + correlator.millis : null,
|
|
454
|
+
correlated_at ?? null,
|
|
455
|
+
now
|
|
456
|
+
]
|
|
457
|
+
});
|
|
458
|
+
correlating = taken.rowsAffected > 0;
|
|
459
|
+
if (!correlating && correlated_at !== void 0)
|
|
460
|
+
await tx.execute({
|
|
461
|
+
sql: "UPDATE correlated SET at = MAX(at, ?) WHERE key = ?",
|
|
462
|
+
args: [correlated_at, correlator.key]
|
|
463
|
+
});
|
|
464
|
+
if (correlated_at !== void 0)
|
|
465
|
+
await tx.execute({
|
|
466
|
+
sql: "UPDATE correlated SET at = MAX(at, ?) WHERE key = ''",
|
|
467
|
+
args: [correlated_at]
|
|
468
|
+
});
|
|
469
|
+
} else if (correlated_at !== void 0)
|
|
407
470
|
await tx.execute({
|
|
408
|
-
sql: "UPDATE correlated SET at = MAX(at, ?) WHERE
|
|
471
|
+
sql: "UPDATE correlated SET at = MAX(at, ?) WHERE key = ''",
|
|
409
472
|
args: [correlated_at]
|
|
410
473
|
});
|
|
411
|
-
const cp = await tx.execute(
|
|
474
|
+
const cp = await tx.execute({
|
|
475
|
+
sql: "SELECT at FROM correlated WHERE key = ?",
|
|
476
|
+
args: [correlator?.key ?? ""]
|
|
477
|
+
});
|
|
412
478
|
await tx.commit();
|
|
413
479
|
return {
|
|
414
480
|
subscribed,
|
|
415
481
|
watermark: Number(wm.rows[0].w),
|
|
416
|
-
// `seed()` always inserts the
|
|
417
|
-
//
|
|
418
|
-
|
|
482
|
+
// `seed()` always inserts the shared row, and a correlator's row is
|
|
483
|
+
// created by the upsert above — or already existed, which is why the
|
|
484
|
+
// lease was refused. Either way this read cannot come back empty, so
|
|
485
|
+
// there is no defensive fallback to leave untested.
|
|
486
|
+
correlated_at: Number(cp.rows[0].at),
|
|
487
|
+
...correlating === void 0 ? {} : { correlating }
|
|
419
488
|
};
|
|
420
489
|
} catch (e) {
|
|
421
490
|
await tx.rollback();
|