@mcp-b/do-runtime 0.3.6 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/README.md +4 -1
- package/dist/backends/node-sqlite.js +2 -1
- package/dist/backends/node-sqlite.js.map +1 -1
- package/dist/backends/sqlite-wasm.js +2 -1
- package/dist/backends/sqlite-wasm.js.map +1 -1
- package/dist/chunks/{sqlite-DFg92Tgt.js → sqlite-migrations-DsWmLP_B.js} +55 -3
- package/dist/chunks/sqlite-migrations-DsWmLP_B.js.map +1 -0
- package/dist/index.js +276 -45
- package/dist/index.js.map +1 -1
- package/dist/server/alarm-scheduler.js +2 -1
- package/dist/server/alarm-scheduler.js.map +1 -1
- package/dist/src/api/sql.d.ts +15 -3
- package/dist/src/util/sqlite-migrations.d.ts +72 -0
- package/package.json +3 -1
- package/dist/chunks/sqlite-DFg92Tgt.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 053b995: `sql.exec()` and `sql.ingest()` now enforce workerd's pragma allowlist. Every pragma outside `util/sqlite.c++`'s `ALLOWED_PRAGMAS` — `user_version`, `writable_schema`, `journal_mode`, `max_page_count`, and the rest — refuses with workerd's message, `not authorized: SQLITE_AUTH`, and the `pragma_*` table-valued functions follow the same list. Previously every pragma passed straight through, which no code written for Cloudflare could have relied on, and which let application SQL overwrite the runtime's storage version stamp or rewrite `sqlite_master` via `writable_schema`. The allowlist, including argument-signature rules, is pinned by a conformance row that runs against real workerd.
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- 053b995: SQL cursor iterators now match workerd's observable shape, not just its helpers. `sql.exec(...).raw()` and the cursor's own iterator sit on `%IteratorPrototype%` — so `raw().toArray()`, which Drizzle's `durable-sqlite` migrator and driver call, works as it does on Cloudflare — and, like upstream's jsg iterators, they expose `next` and nothing else: no `return`/`throw`, so an early exit (`break`, partial destructuring, `take()`) does not close a retained iterator; results are `{done, value}` in that key order with an own `value: undefined` when done; `Symbol.toStringTag` reads `RawIterator`/`RowIterator`/`Cursor`; and `columnNames` is a prototype accessor, so a cursor JSON-stringifies to `{}`. Pinned by two conformance rows on all three lanes and an end-to-end Drizzle migration test.
|
|
12
|
+
- 053b995: Version runtime storage per database file. Every database the runtime opens — an actor's, the facet tree's, an alarm scheduler's — is stamped with `PRAGMA user_version` and brought forward through forward-only migration steps at open, before any event can enter (the Agents SDK's `_ensureSchema` pattern, one layer down). Pending steps and the stamp commit as one transaction, and a step that issues transaction control of its own is refused by name. A file stamped by a newer release refuses with the database and the remedy named, at open and again at `importSnapshot()`, so the operation that brought a too-new image in is the one that fails. The stamp itself is unreachable from application SQL (see the pragma allowlist change). Note for embedders constructing `AlarmScheduler` directly: the database you pass is now stamped too; host tables sharing that file are untouched — migration steps confine themselves to runtime-owned tables, and a test pins that.
|
|
13
|
+
|
|
3
14
|
## 0.3.6
|
|
4
15
|
|
|
5
16
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -203,6 +203,8 @@ re-enters the owning input gate.
|
|
|
203
203
|
|
|
204
204
|
`SqlDatabaseProvider.open(name)` is the runtime execution seam. The runtime owns database names, tables, transactions, reset behaviour, facet metadata, and streaming `sql.ingest()` statement boundaries; the host chooses the physical provider and prefix. Stored KV values use structured-clone semantics across workerd, Node, and the browser; existing JSON rows remain readable. `_cf_` names are reserved to the runtime.
|
|
205
205
|
|
|
206
|
+
Schema migrations work exactly as on Cloudflare, at both layers. An application migrates its own tables in its constructor — synchronous DDL under boot semantics, or `ctx.blockConcurrencyWhile()` when the path is async; the Agents SDK's versioned `_ensureSchema` and Drizzle's `durable-sqlite` migrator (`drizzle-kit generate` compiled into the bundle, `migrate()` in the constructor) run unchanged, the latter pinned end-to-end by [`src/drizzle-migrations.test.ts`](src/drizzle-migrations.test.ts). The runtime's own `_cf_` tables are versioned separately, per database file, in `PRAGMA user_version`: [`src/util/sqlite-migrations.ts`](src/util/sqlite-migrations.ts) brings an older file forward at open — before any event can enter — a file or imported snapshot stamped by a newer release refuses with the remedy named, and application SQL cannot reach the stamp, because `sql.exec()` enforces workerd's pragma allowlist (decision 19).
|
|
207
|
+
|
|
206
208
|
The browser provider takes an already-installed OPFS SAH pool (`installOpfsSAHPoolVfs`; sync access handles in a dedicated worker — no cross-origin isolation or `SharedArrayBuffer` needed). One pool per worker; the root and each local facet get separate prefixes inside it. `SqliteWasmActorStorage` adds the close, physical delete, and clone operations a local placement host needs around one prefix. The Node provider uses in-memory databases by default and a directory when asked.
|
|
207
209
|
|
|
208
210
|
Both concrete providers also implement `SqlDatabaseSnapshotProvider`. After the host has stopped the actor, `provider.close()` releases every database handle; `exportSnapshot()` then returns the SQLite images for the whole actor storage scope, and `importSnapshot()` replaces an idle scope. The same snapshot can seed a cold local replica because SQLite images are portable between these providers. Node snapshots require a dedicated directory-backed provider. This is backup/restore and replica seeding, not Cloudflare's time-indexed PITR or continuously updated read replication.
|
|
@@ -237,6 +239,7 @@ The browser cannot reproduce every workerd facility. Where it cannot, the runtim
|
|
|
237
239
|
| Stored value wire bytes | Browser-safe versioned structured-clone encoding rather than V8's private format; public value types align and legacy JSON rows remain readable. |
|
|
238
240
|
| SQL row counters | Local `rowsRead`/`rowsWritten`, including `sql.ingest()`, use returned rows and SQLite changes; workerd uses unavailable libsql billing counters. |
|
|
239
241
|
| Reserved SQL names | `_cf_` detected from tokenized SQL text, which can reject more than workerd's authorizer. |
|
|
242
|
+
| PRAGMA allowlist | Workerd's allowlist enforced from tokenized SQL text. A `pragma_*` table-valued function with a string or bound argument is authorized by pragma name only, where workerd's authorizer also sees the resolved argument; the pinned conformance row is the contract. |
|
|
240
243
|
| Node SQLite length limit | Bound and returned strings and blobs are capped at 4 MiB; `node:sqlite` cannot cap an unreturned SQL-computed value. The browser backend uses SQLite's native limit. |
|
|
241
244
|
| Response BYOB readers | Refused; their continuation cannot be re-gated. Use a default reader or `arrayBuffer()`. |
|
|
242
245
|
| Facet `setAlarm()` | Refused synchronously, where workerd breaks the actor asynchronously ([workerd#6810](https://github.com/cloudflare/workerd/issues/6810)). |
|
|
@@ -244,7 +247,7 @@ The browser cannot reproduce every workerd facility. Where it cannot, the runtim
|
|
|
244
247
|
|
|
245
248
|
### Stability
|
|
246
249
|
|
|
247
|
-
This is `0.x`. The public surface is what [`src/index.ts`](src/index.ts) and the subpath exports in [`package.json`](package.json) expose; gates, `IoContext`, storage classes, and facet-manager internals are deliberately not exported and may change without notice. While `0.x`, a breaking change to the public surface is a minor bump with a changelog entry. A feature that is removed goes through the same door as the table above — a named refusal in the API and a conformance row — rather than disappearing, so a caller finds out at the call site and not in production.
|
|
250
|
+
This is `0.x`. The public surface is what [`src/index.ts`](src/index.ts) and the subpath exports in [`package.json`](package.json) expose; gates, `IoContext`, storage classes, and facet-manager internals are deliberately not exported and may change without notice. While `0.x`, a breaking change to the public surface is a minor bump with a changelog entry. A feature that is removed goes through the same door as the table above — a named refusal in the API and a conformance row — rather than disappearing, so a caller finds out at the call site and not in production. Runtime storage is versioned per database file (`PRAGMA user_version`, decision 19): every open brings an older file forward through forward-only migration steps before anything reads it, present storage is then validated against the current shape, and a file stamped by a newer release refuses with the one remedy named.
|
|
248
251
|
|
|
249
252
|
## Package layout
|
|
250
253
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { d as
|
|
1
|
+
import { d as requireSafeDatabaseName, f as requireSqliteLength, i as SQL_WRONG_BINDINGS_MESSAGE, n as requireImportableRuntimeStorage, p as requireValidSqlDatabaseSnapshot } from "../chunks/sqlite-migrations-DsWmLP_B.js";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import { rmSync } from "node:fs";
|
|
4
4
|
import { readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
|
|
@@ -57,6 +57,7 @@ function createNodeSqlProvider(options = {}) {
|
|
|
57
57
|
const path = requireSnapshotDirectory(directory);
|
|
58
58
|
requireClosed(openDatabases);
|
|
59
59
|
requireValidSqlDatabaseSnapshot(snapshot);
|
|
60
|
+
requireImportableRuntimeStorage(snapshot);
|
|
60
61
|
const databases = snapshot.databases.map(({ name, image }) => ({
|
|
61
62
|
name,
|
|
62
63
|
image: new Uint8Array(image)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"node-sqlite.js","names":["#path","#database","#totalChanges","#pragma","#closed","#statement","#layout"],"sources":["../../backends/node-sqlite.ts"],"sourcesContent":["/**\n * ← workerd `NO upstream correspondence (storage-backend adaptation)`\n *\n * `SqlDatabaseProvider` over `node:sqlite`. Promoted out of\n * `host/fixtures/storage-node.ts`, which is already this adapter.\n *\n * Upstream's equivalent is `SqliteDatabase`'s binding to the SQLite C API plus\n * its kj-filesystem VFS — 3,768 lines this package deliberately does not port,\n * because `node:sqlite` and sqlite-wasm play that role underneath us. What has\n * to match is the layer above: the SQL that `sqlite-kv` and `sqlite-metadata`\n * write, and the four operations they need from a database.\n *\n * This is the substrate the unit lane runs on. It is also decision 11's Node\n * conformance lane, and `fixtures/storage-node.ts` already proves the seam\n * across 20 of the extension's 24 Node-lane test files.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport { rmSync } from \"node:fs\";\nimport { readFile, readdir, rename, rm, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { DatabaseSync } from \"node:sqlite\";\nimport type { SQLInputValue, StatementSync } from \"node:sqlite\";\nimport {\n requireSqliteLength,\n requireSafeDatabaseName,\n requireValidSqlDatabaseSnapshot,\n SQL_WRONG_BINDINGS_MESSAGE,\n type SqlDatabase,\n type SqlDatabaseSnapshot,\n type SqlDatabaseSnapshotProvider,\n type SqlDatabaseStatement,\n type SqlResult,\n type SqlValue,\n} from \"../src/util/sqlite\";\n\nexport type NodeSqlProviderOptions = {\n /**\n * Dedicated directory for one actor's database files. Omit for in-memory\n * databases, which cannot be snapshotted and are what the unit lane and\n * upstream's own tests get from `kj::newInMemoryDirectory`.\n */\n directory?: string;\n};\n\nexport function createNodeSqlProvider(\n options: NodeSqlProviderOptions = {},\n): SqlDatabaseSnapshotProvider {\n const directory = options.directory;\n const openDatabases = new Set<NodeSqlDatabase>();\n return {\n async open(name: string): Promise<SqlDatabase> {\n requireSafeDatabaseName(name);\n const path = directory === undefined ? \":memory:\" : join(directory, `${name}.sqlite`);\n let database: NodeSqlDatabase;\n database = new NodeSqlDatabase(path, () => openDatabases.delete(database));\n openDatabases.add(database);\n return database;\n },\n close(): void {\n for (const database of [...openDatabases]) database.close();\n },\n async exportSnapshot(): Promise<SqlDatabaseSnapshot> {\n const path = requireSnapshotDirectory(directory);\n requireClosed(openDatabases);\n const files = await readdir(path);\n requireNoRecoverySidecars(files);\n const names = files\n .filter((file) => file.endsWith(\".sqlite\"))\n .map((file) => file.slice(0, -\".sqlite\".length))\n .sort();\n names.forEach(requireSafeDatabaseName);\n const snapshot: SqlDatabaseSnapshot = {\n version: 1,\n databases: await Promise.all(\n names.map(async (name) => ({\n name,\n image: new Uint8Array(await readFile(join(path, `${name}.sqlite`))),\n })),\n ),\n };\n requireValidSqlDatabaseSnapshot(snapshot);\n return snapshot;\n },\n async importSnapshot(snapshot: SqlDatabaseSnapshot): Promise<void> {\n const path = requireSnapshotDirectory(directory);\n requireClosed(openDatabases);\n requireValidSqlDatabaseSnapshot(snapshot);\n\n const databases = snapshot.databases.map(({ name, image }) => ({\n name,\n image: new Uint8Array(image),\n }));\n const temporary: { name: string; file: string }[] = [];\n try {\n for (const { name, image } of databases) {\n const file = join(path, `.${name}.${randomUUID()}.restore`);\n await writeFile(file, image);\n temporary.push({ name, file });\n }\n const existing = await readdir(path);\n existing\n .filter((file) => file.endsWith(\".sqlite\"))\n .map((file) => file.slice(0, -\".sqlite\".length))\n .forEach(requireSafeDatabaseName);\n for (const file of existing) {\n if (/\\.sqlite-(?:journal|wal|shm)$/.test(file)) await rm(join(path, file), { force: true });\n }\n for (const { name, file } of temporary) {\n await rename(file, join(path, `${name}.sqlite`));\n }\n const restored = new Set(databases.map(({ name }) => `${name}.sqlite`));\n for (const file of existing) {\n if (file.endsWith(\".sqlite\") && !restored.has(file)) {\n await rm(join(path, file), { force: true });\n }\n }\n } finally {\n await Promise.all(temporary.map(({ file }) => rm(file, { force: true })));\n }\n },\n };\n}\n\nexport class NodeSqlDatabase implements SqlDatabase {\n readonly #path: string;\n #database: DatabaseSync;\n\n #closed = false;\n\n constructor(path: string, private readonly onClose: () => void = () => {}) {\n this.#path = path;\n this.#database = new DatabaseSync(path);\n }\n\n prepare(sql: string): SqlDatabaseStatement {\n const statement = this.#database.prepare(sql);\n const source = statement.sourceSQL;\n return new NodeSqlStatement(statement, source, parameterLayout(source), () =>\n this.#totalChanges(),\n );\n }\n\n exec(sql: string, params: readonly SqlValue[]): SqlResult {\n const statement = this.prepare(sql);\n try {\n return statement.execute(params);\n } finally {\n statement.close();\n }\n }\n\n get databaseSize(): number {\n const pageCount = this.#pragma(\"page_count\");\n const pageSize = this.#pragma(\"page_size\");\n return pageCount * pageSize;\n }\n\n /** `node:sqlite`'s own name for `sqlite3_get_autocommit(db) == 0`. */\n get inTransaction(): boolean {\n return this.#database.isTransaction;\n }\n\n reset(): void {\n this.#database.close();\n if (this.#path !== \":memory:\") {\n // The journal and WAL sidecars are part of the database; leaving one\n // behind would have the reopened file replay a transaction from the\n // database that was just deleted.\n for (const suffix of [\"\", \"-journal\", \"-wal\", \"-shm\"]) {\n rmSync(`${this.#path}${suffix}`, { force: true });\n }\n }\n this.#database = new DatabaseSync(this.#path);\n }\n\n close(): void {\n if (this.#closed) return;\n this.#database.close();\n this.#closed = true;\n this.onClose();\n }\n\n #pragma(name: string): number {\n const row = this.#database.prepare(`PRAGMA ${name}`).get();\n const value = row?.[name];\n if (typeof value !== \"number\") {\n throw new Error(`PRAGMA ${name} did not return a number.`);\n }\n return value;\n }\n\n #totalChanges(): number {\n const row = this.#database.prepare(\"SELECT total_changes() AS value\").get();\n const value = row?.value;\n if (typeof value !== \"number\" && typeof value !== \"bigint\") {\n throw new Error(\"total_changes() did not return a number.\");\n }\n return Number(value);\n }\n}\n\ntype ParameterLayout = {\n readonly count: number;\n readonly namesByIndex: ReadonlyMap<number, string>;\n};\n\nclass NodeSqlStatement implements SqlDatabaseStatement {\n readonly #statement: StatementSync;\n readonly #layout: ParameterLayout;\n\n constructor(\n statement: StatementSync,\n readonly sql: string,\n layout: ParameterLayout,\n private readonly totalChanges: () => number,\n ) {\n this.#statement = statement;\n this.#layout = layout;\n }\n\n get parameterCount(): number {\n return this.#layout.count;\n }\n\n execute(params: readonly SqlValue[]): SqlResult {\n if (params.length !== this.parameterCount) throw new Error(SQL_WRONG_BINDINGS_MESSAGE);\n // ponytail: node:sqlite exposes no sqlite3_limit(); guard JS inputs and outputs here,\n // and replace this with the native limit if Node exposes it.\n params.forEach(requireSqliteLength);\n\n const { named, anonymous } = bindParameters(params, this.#layout);\n const statement = this.#statement;\n const columns = statement.columns();\n\n if (columns.length === 0) {\n const { changes } =\n named === undefined ? statement.run(...anonymous) : statement.run(named, ...anonymous);\n return { columnNames: [], rawRows: [], rowsWritten: Number(changes) };\n }\n\n statement.setReadBigInts(true);\n statement.setReturnArrays(true);\n const changesBefore = this.totalChanges();\n const rows: unknown[] =\n named === undefined ? statement.all(...anonymous) : statement.all(named, ...anonymous);\n return {\n columnNames: columns.map((column) => column.name),\n rawRows: rows.map(asRow),\n // `node:sqlite` exposes no sqlite3_stmt_readonly() or per-statement write\n // counter. The total-change delta distinguishes SELECT from DML RETURNING\n // without parsing SQL or executing the statement twice.\n rowsWritten: this.totalChanges() - changesBefore,\n };\n }\n\n close(): void {\n // `StatementSync` exposes no finalize operation. Its native handle follows\n // the lifetime of this short-lived object instead.\n }\n}\n\nfunction bindParameters(\n params: readonly SqlValue[],\n layout: ParameterLayout,\n): {\n named: Record<string, SQLInputValue> | undefined;\n anonymous: SQLInputValue[];\n} {\n let named: Record<string, SQLInputValue> | undefined;\n const anonymous: SQLInputValue[] = [];\n for (let index = 1; index <= layout.count; index += 1) {\n const value = params[index - 1];\n if (value === undefined) throw new Error(SQL_WRONG_BINDINGS_MESSAGE);\n const name = layout.namesByIndex.get(index);\n if (name === undefined) {\n anonymous.push(value);\n } else {\n named ??= {};\n named[name] = value;\n }\n }\n return { named, anonymous };\n}\n\n/**\n * `StatementSync` does not expose `sqlite3_bind_parameter_count()`. This is the\n * one lexical adaptation left in the Node backend: it recognizes only SQLite\n * parameter tokens and lets `DatabaseSync.prepare()` validate every other bit\n * of SQL, including statement boundaries.\n */\nfunction parameterLayout(sql: string): ParameterLayout {\n let nextIndex = 1;\n const indexByName = new Map<string, number>();\n const namesByIndex = new Map<number, string>();\n\n for (let index = 0; index < sql.length; ) {\n const char = sql.charAt(index);\n if (char === \"'\" || char === '\"' || char === \"`\") {\n index = skipQuoted(sql, index, char, true);\n continue;\n }\n if (char === \"[\") {\n index = skipQuoted(sql, index, \"]\", false);\n continue;\n }\n if (char === \"-\" && sql[index + 1] === \"-\") {\n const newline = sql.indexOf(\"\\n\", index + 2);\n index = newline === -1 ? sql.length : newline + 1;\n continue;\n }\n if (char === \"/\" && sql[index + 1] === \"*\") {\n const close = sql.indexOf(\"*/\", index + 2);\n index = close === -1 ? sql.length : close + 2;\n continue;\n }\n if (char === \"?\") {\n let end = index + 1;\n while (isAsciiDigit(sql.charAt(end))) end += 1;\n if (end === index + 1) {\n nextIndex += 1;\n } else {\n const explicit = Number(sql.slice(index + 1, end));\n nextIndex = Math.max(nextIndex, explicit + 1);\n }\n index = end;\n continue;\n }\n if ((char === \":\" || char === \"@\" || char === \"$\") && isParameterChar(sql.charAt(index + 1))) {\n const end = parameterNameEnd(sql, index);\n const name = sql.slice(index, end);\n let parameterIndex = indexByName.get(name);\n if (parameterIndex === undefined) {\n parameterIndex = nextIndex;\n nextIndex += 1;\n indexByName.set(name, parameterIndex);\n namesByIndex.set(parameterIndex, name);\n }\n index = end;\n continue;\n }\n index += 1;\n }\n\n return { count: nextIndex - 1, namesByIndex };\n}\n\nfunction parameterNameEnd(sql: string, start: number): number {\n let index = start + 1;\n while (isParameterChar(sql.charAt(index))) index += 1;\n\n // SQLite's `$name` form also accepts Tcl-style `::suffix` and `(suffix)`.\n if (sql[start] === \"$\") {\n while (sql.slice(index, index + 2) === \"::\" && isParameterChar(sql.charAt(index + 2))) {\n index += 2;\n while (isParameterChar(sql.charAt(index))) index += 1;\n }\n if (sql[index] === \"(\") {\n const close = sql.indexOf(\")\", index + 1);\n if (close !== -1) index = close + 1;\n }\n }\n return index;\n}\n\nfunction isAsciiDigit(char: string): boolean {\n return char >= \"0\" && char <= \"9\";\n}\n\nfunction isParameterChar(char: string): boolean {\n return (\n (char >= \"A\" && char <= \"Z\") ||\n (char >= \"a\" && char <= \"z\") ||\n isAsciiDigit(char) ||\n char === \"_\" ||\n char.charCodeAt(0) >= 0x80\n );\n}\n\nfunction skipQuoted(sql: string, open: number, close: string, doubled: boolean): number {\n let index = open + 1;\n while (index < sql.length) {\n if (sql[index] === close) {\n if (doubled && sql[index + 1] === close) {\n index += 2;\n continue;\n }\n return index + 1;\n }\n index += 1;\n }\n return sql.length;\n}\n\n/**\n * `setReturnArrays(true)` is what makes a row a `rawRow`, and the driver's types\n * describe the object shape either way. Checking rather than asserting keeps a\n * future driver that ignores the flag from handing objects to `getText`.\n */\nfunction asRow(row: unknown): readonly unknown[] {\n if (Array.isArray(row)) {\n row.forEach(requireSqliteLength);\n return row.map(normalizeInteger);\n }\n throw new Error(\"node:sqlite returned a non-array row despite setReturnArrays(true).\");\n}\n\n/** Preserve ordinary numeric rows while keeping the full int64 range until the public API. */\nfunction normalizeInteger(value: unknown): unknown {\n if (typeof value !== \"bigint\") return value;\n const number = Number(value);\n return Number.isSafeInteger(number) && BigInt(number) === value ? number : value;\n}\n\n/**\n * Names come from inside the package (`\"root\"`, `` `facet-${facetId}` ``), so\n * this is defence in depth rather than input validation — but it is the one\n * place a name becomes a path, and a silent traversal here writes an actor's\n * storage somewhere nobody will look for it.\n */\nfunction requireSnapshotDirectory(directory: string | undefined): string {\n if (directory === undefined) {\n throw new Error(\"SQLite snapshots require a directory-backed Node provider.\");\n }\n return directory;\n}\n\nfunction requireClosed(openDatabases: ReadonlySet<NodeSqlDatabase>): void {\n if (openDatabases.size > 0) {\n throw new Error(\"Cannot snapshot or restore while database handles are open.\");\n }\n}\n\nfunction requireNoRecoverySidecars(files: readonly string[]): void {\n const sidecar = files.find((file) => /\\.sqlite-(?:journal|wal|shm)$/.test(file));\n if (sidecar !== undefined) {\n throw new Error(`Cannot export a snapshot with a SQLite recovery sidecar: ${sidecar}`);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"node-sqlite.js","names":["#path","#database","#totalChanges","#pragma","#closed","#statement","#layout"],"sources":["../../backends/node-sqlite.ts"],"sourcesContent":["/**\n * ← workerd `NO upstream correspondence (storage-backend adaptation)`\n *\n * `SqlDatabaseProvider` over `node:sqlite`. Promoted out of\n * `host/fixtures/storage-node.ts`, which is already this adapter.\n *\n * Upstream's equivalent is `SqliteDatabase`'s binding to the SQLite C API plus\n * its kj-filesystem VFS — 3,768 lines this package deliberately does not port,\n * because `node:sqlite` and sqlite-wasm play that role underneath us. What has\n * to match is the layer above: the SQL that `sqlite-kv` and `sqlite-metadata`\n * write, and the four operations they need from a database.\n *\n * This is the substrate the unit lane runs on. It is also decision 11's Node\n * conformance lane, and `fixtures/storage-node.ts` already proves the seam\n * across 20 of the extension's 24 Node-lane test files.\n */\n\nimport { randomUUID } from \"node:crypto\";\nimport { rmSync } from \"node:fs\";\nimport { readFile, readdir, rename, rm, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { DatabaseSync } from \"node:sqlite\";\nimport type { SQLInputValue, StatementSync } from \"node:sqlite\";\nimport {\n requireSqliteLength,\n requireSafeDatabaseName,\n requireValidSqlDatabaseSnapshot,\n SQL_WRONG_BINDINGS_MESSAGE,\n type SqlDatabase,\n type SqlDatabaseSnapshot,\n type SqlDatabaseSnapshotProvider,\n type SqlDatabaseStatement,\n type SqlResult,\n type SqlValue,\n} from \"../src/util/sqlite\";\nimport { requireImportableRuntimeStorage } from \"../src/util/sqlite-migrations\";\n\nexport type NodeSqlProviderOptions = {\n /**\n * Dedicated directory for one actor's database files. Omit for in-memory\n * databases, which cannot be snapshotted and are what the unit lane and\n * upstream's own tests get from `kj::newInMemoryDirectory`.\n */\n directory?: string;\n};\n\nexport function createNodeSqlProvider(\n options: NodeSqlProviderOptions = {},\n): SqlDatabaseSnapshotProvider {\n const directory = options.directory;\n const openDatabases = new Set<NodeSqlDatabase>();\n return {\n async open(name: string): Promise<SqlDatabase> {\n requireSafeDatabaseName(name);\n const path = directory === undefined ? \":memory:\" : join(directory, `${name}.sqlite`);\n let database: NodeSqlDatabase;\n database = new NodeSqlDatabase(path, () => openDatabases.delete(database));\n openDatabases.add(database);\n return database;\n },\n close(): void {\n for (const database of [...openDatabases]) database.close();\n },\n async exportSnapshot(): Promise<SqlDatabaseSnapshot> {\n const path = requireSnapshotDirectory(directory);\n requireClosed(openDatabases);\n const files = await readdir(path);\n requireNoRecoverySidecars(files);\n const names = files\n .filter((file) => file.endsWith(\".sqlite\"))\n .map((file) => file.slice(0, -\".sqlite\".length))\n .sort();\n names.forEach(requireSafeDatabaseName);\n const snapshot: SqlDatabaseSnapshot = {\n version: 1,\n databases: await Promise.all(\n names.map(async (name) => ({\n name,\n image: new Uint8Array(await readFile(join(path, `${name}.sqlite`))),\n })),\n ),\n };\n requireValidSqlDatabaseSnapshot(snapshot);\n return snapshot;\n },\n async importSnapshot(snapshot: SqlDatabaseSnapshot): Promise<void> {\n const path = requireSnapshotDirectory(directory);\n requireClosed(openDatabases);\n requireValidSqlDatabaseSnapshot(snapshot);\n requireImportableRuntimeStorage(snapshot);\n\n const databases = snapshot.databases.map(({ name, image }) => ({\n name,\n image: new Uint8Array(image),\n }));\n const temporary: { name: string; file: string }[] = [];\n try {\n for (const { name, image } of databases) {\n const file = join(path, `.${name}.${randomUUID()}.restore`);\n await writeFile(file, image);\n temporary.push({ name, file });\n }\n const existing = await readdir(path);\n existing\n .filter((file) => file.endsWith(\".sqlite\"))\n .map((file) => file.slice(0, -\".sqlite\".length))\n .forEach(requireSafeDatabaseName);\n for (const file of existing) {\n if (/\\.sqlite-(?:journal|wal|shm)$/.test(file)) await rm(join(path, file), { force: true });\n }\n for (const { name, file } of temporary) {\n await rename(file, join(path, `${name}.sqlite`));\n }\n const restored = new Set(databases.map(({ name }) => `${name}.sqlite`));\n for (const file of existing) {\n if (file.endsWith(\".sqlite\") && !restored.has(file)) {\n await rm(join(path, file), { force: true });\n }\n }\n } finally {\n await Promise.all(temporary.map(({ file }) => rm(file, { force: true })));\n }\n },\n };\n}\n\nexport class NodeSqlDatabase implements SqlDatabase {\n readonly #path: string;\n #database: DatabaseSync;\n\n #closed = false;\n\n constructor(path: string, private readonly onClose: () => void = () => {}) {\n this.#path = path;\n this.#database = new DatabaseSync(path);\n }\n\n prepare(sql: string): SqlDatabaseStatement {\n const statement = this.#database.prepare(sql);\n const source = statement.sourceSQL;\n return new NodeSqlStatement(statement, source, parameterLayout(source), () =>\n this.#totalChanges(),\n );\n }\n\n exec(sql: string, params: readonly SqlValue[]): SqlResult {\n const statement = this.prepare(sql);\n try {\n return statement.execute(params);\n } finally {\n statement.close();\n }\n }\n\n get databaseSize(): number {\n const pageCount = this.#pragma(\"page_count\");\n const pageSize = this.#pragma(\"page_size\");\n return pageCount * pageSize;\n }\n\n /** `node:sqlite`'s own name for `sqlite3_get_autocommit(db) == 0`. */\n get inTransaction(): boolean {\n return this.#database.isTransaction;\n }\n\n reset(): void {\n this.#database.close();\n if (this.#path !== \":memory:\") {\n // The journal and WAL sidecars are part of the database; leaving one\n // behind would have the reopened file replay a transaction from the\n // database that was just deleted.\n for (const suffix of [\"\", \"-journal\", \"-wal\", \"-shm\"]) {\n rmSync(`${this.#path}${suffix}`, { force: true });\n }\n }\n this.#database = new DatabaseSync(this.#path);\n }\n\n close(): void {\n if (this.#closed) return;\n this.#database.close();\n this.#closed = true;\n this.onClose();\n }\n\n #pragma(name: string): number {\n const row = this.#database.prepare(`PRAGMA ${name}`).get();\n const value = row?.[name];\n if (typeof value !== \"number\") {\n throw new Error(`PRAGMA ${name} did not return a number.`);\n }\n return value;\n }\n\n #totalChanges(): number {\n const row = this.#database.prepare(\"SELECT total_changes() AS value\").get();\n const value = row?.value;\n if (typeof value !== \"number\" && typeof value !== \"bigint\") {\n throw new Error(\"total_changes() did not return a number.\");\n }\n return Number(value);\n }\n}\n\ntype ParameterLayout = {\n readonly count: number;\n readonly namesByIndex: ReadonlyMap<number, string>;\n};\n\nclass NodeSqlStatement implements SqlDatabaseStatement {\n readonly #statement: StatementSync;\n readonly #layout: ParameterLayout;\n\n constructor(\n statement: StatementSync,\n readonly sql: string,\n layout: ParameterLayout,\n private readonly totalChanges: () => number,\n ) {\n this.#statement = statement;\n this.#layout = layout;\n }\n\n get parameterCount(): number {\n return this.#layout.count;\n }\n\n execute(params: readonly SqlValue[]): SqlResult {\n if (params.length !== this.parameterCount) throw new Error(SQL_WRONG_BINDINGS_MESSAGE);\n // ponytail: node:sqlite exposes no sqlite3_limit(); guard JS inputs and outputs here,\n // and replace this with the native limit if Node exposes it.\n params.forEach(requireSqliteLength);\n\n const { named, anonymous } = bindParameters(params, this.#layout);\n const statement = this.#statement;\n const columns = statement.columns();\n\n if (columns.length === 0) {\n const { changes } =\n named === undefined ? statement.run(...anonymous) : statement.run(named, ...anonymous);\n return { columnNames: [], rawRows: [], rowsWritten: Number(changes) };\n }\n\n statement.setReadBigInts(true);\n statement.setReturnArrays(true);\n const changesBefore = this.totalChanges();\n const rows: unknown[] =\n named === undefined ? statement.all(...anonymous) : statement.all(named, ...anonymous);\n return {\n columnNames: columns.map((column) => column.name),\n rawRows: rows.map(asRow),\n // `node:sqlite` exposes no sqlite3_stmt_readonly() or per-statement write\n // counter. The total-change delta distinguishes SELECT from DML RETURNING\n // without parsing SQL or executing the statement twice.\n rowsWritten: this.totalChanges() - changesBefore,\n };\n }\n\n close(): void {\n // `StatementSync` exposes no finalize operation. Its native handle follows\n // the lifetime of this short-lived object instead.\n }\n}\n\nfunction bindParameters(\n params: readonly SqlValue[],\n layout: ParameterLayout,\n): {\n named: Record<string, SQLInputValue> | undefined;\n anonymous: SQLInputValue[];\n} {\n let named: Record<string, SQLInputValue> | undefined;\n const anonymous: SQLInputValue[] = [];\n for (let index = 1; index <= layout.count; index += 1) {\n const value = params[index - 1];\n if (value === undefined) throw new Error(SQL_WRONG_BINDINGS_MESSAGE);\n const name = layout.namesByIndex.get(index);\n if (name === undefined) {\n anonymous.push(value);\n } else {\n named ??= {};\n named[name] = value;\n }\n }\n return { named, anonymous };\n}\n\n/**\n * `StatementSync` does not expose `sqlite3_bind_parameter_count()`. This is the\n * one lexical adaptation left in the Node backend: it recognizes only SQLite\n * parameter tokens and lets `DatabaseSync.prepare()` validate every other bit\n * of SQL, including statement boundaries.\n */\nfunction parameterLayout(sql: string): ParameterLayout {\n let nextIndex = 1;\n const indexByName = new Map<string, number>();\n const namesByIndex = new Map<number, string>();\n\n for (let index = 0; index < sql.length; ) {\n const char = sql.charAt(index);\n if (char === \"'\" || char === '\"' || char === \"`\") {\n index = skipQuoted(sql, index, char, true);\n continue;\n }\n if (char === \"[\") {\n index = skipQuoted(sql, index, \"]\", false);\n continue;\n }\n if (char === \"-\" && sql[index + 1] === \"-\") {\n const newline = sql.indexOf(\"\\n\", index + 2);\n index = newline === -1 ? sql.length : newline + 1;\n continue;\n }\n if (char === \"/\" && sql[index + 1] === \"*\") {\n const close = sql.indexOf(\"*/\", index + 2);\n index = close === -1 ? sql.length : close + 2;\n continue;\n }\n if (char === \"?\") {\n let end = index + 1;\n while (isAsciiDigit(sql.charAt(end))) end += 1;\n if (end === index + 1) {\n nextIndex += 1;\n } else {\n const explicit = Number(sql.slice(index + 1, end));\n nextIndex = Math.max(nextIndex, explicit + 1);\n }\n index = end;\n continue;\n }\n if ((char === \":\" || char === \"@\" || char === \"$\") && isParameterChar(sql.charAt(index + 1))) {\n const end = parameterNameEnd(sql, index);\n const name = sql.slice(index, end);\n let parameterIndex = indexByName.get(name);\n if (parameterIndex === undefined) {\n parameterIndex = nextIndex;\n nextIndex += 1;\n indexByName.set(name, parameterIndex);\n namesByIndex.set(parameterIndex, name);\n }\n index = end;\n continue;\n }\n index += 1;\n }\n\n return { count: nextIndex - 1, namesByIndex };\n}\n\nfunction parameterNameEnd(sql: string, start: number): number {\n let index = start + 1;\n while (isParameterChar(sql.charAt(index))) index += 1;\n\n // SQLite's `$name` form also accepts Tcl-style `::suffix` and `(suffix)`.\n if (sql[start] === \"$\") {\n while (sql.slice(index, index + 2) === \"::\" && isParameterChar(sql.charAt(index + 2))) {\n index += 2;\n while (isParameterChar(sql.charAt(index))) index += 1;\n }\n if (sql[index] === \"(\") {\n const close = sql.indexOf(\")\", index + 1);\n if (close !== -1) index = close + 1;\n }\n }\n return index;\n}\n\nfunction isAsciiDigit(char: string): boolean {\n return char >= \"0\" && char <= \"9\";\n}\n\nfunction isParameterChar(char: string): boolean {\n return (\n (char >= \"A\" && char <= \"Z\") ||\n (char >= \"a\" && char <= \"z\") ||\n isAsciiDigit(char) ||\n char === \"_\" ||\n char.charCodeAt(0) >= 0x80\n );\n}\n\nfunction skipQuoted(sql: string, open: number, close: string, doubled: boolean): number {\n let index = open + 1;\n while (index < sql.length) {\n if (sql[index] === close) {\n if (doubled && sql[index + 1] === close) {\n index += 2;\n continue;\n }\n return index + 1;\n }\n index += 1;\n }\n return sql.length;\n}\n\n/**\n * `setReturnArrays(true)` is what makes a row a `rawRow`, and the driver's types\n * describe the object shape either way. Checking rather than asserting keeps a\n * future driver that ignores the flag from handing objects to `getText`.\n */\nfunction asRow(row: unknown): readonly unknown[] {\n if (Array.isArray(row)) {\n row.forEach(requireSqliteLength);\n return row.map(normalizeInteger);\n }\n throw new Error(\"node:sqlite returned a non-array row despite setReturnArrays(true).\");\n}\n\n/** Preserve ordinary numeric rows while keeping the full int64 range until the public API. */\nfunction normalizeInteger(value: unknown): unknown {\n if (typeof value !== \"bigint\") return value;\n const number = Number(value);\n return Number.isSafeInteger(number) && BigInt(number) === value ? number : value;\n}\n\n/**\n * Names come from inside the package (`\"root\"`, `` `facet-${facetId}` ``), so\n * this is defence in depth rather than input validation — but it is the one\n * place a name becomes a path, and a silent traversal here writes an actor's\n * storage somewhere nobody will look for it.\n */\nfunction requireSnapshotDirectory(directory: string | undefined): string {\n if (directory === undefined) {\n throw new Error(\"SQLite snapshots require a directory-backed Node provider.\");\n }\n return directory;\n}\n\nfunction requireClosed(openDatabases: ReadonlySet<NodeSqlDatabase>): void {\n if (openDatabases.size > 0) {\n throw new Error(\"Cannot snapshot or restore while database handles are open.\");\n }\n}\n\nfunction requireNoRecoverySidecars(files: readonly string[]): void {\n const sidecar = files.find((file) => /\\.sqlite-(?:journal|wal|shm)$/.test(file));\n if (sidecar !== undefined) {\n throw new Error(`Cannot export a snapshot with a SQLite recovery sidecar: ${sidecar}`);\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { d as
|
|
1
|
+
import { d as requireSafeDatabaseName, f as requireSqliteLength, i as SQL_WRONG_BINDINGS_MESSAGE, n as requireImportableRuntimeStorage, p as requireValidSqlDatabaseSnapshot, r as SQLITE_LENGTH_LIMIT } from "../chunks/sqlite-migrations-DsWmLP_B.js";
|
|
2
2
|
//#region backends/sqlite-wasm.ts
|
|
3
3
|
/**
|
|
4
4
|
* ← workerd `NO upstream correspondence (storage-backend adaptation)`
|
|
@@ -125,6 +125,7 @@ function createSqliteWasmProvider(host, options) {
|
|
|
125
125
|
async importSnapshot(snapshot) {
|
|
126
126
|
requireClosed(openDatabases);
|
|
127
127
|
requireValidSqlDatabaseSnapshot(snapshot);
|
|
128
|
+
requireImportableRuntimeStorage(snapshot);
|
|
128
129
|
for (const file of ownedFiles()) host.pool.unlink(file);
|
|
129
130
|
for (const { name, image } of snapshot.databases) await host.pool.importDb(`${prefix}.${name}.sqlite`, new Uint8Array(image));
|
|
130
131
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite-wasm.js","names":["#host","#prefix","#provider","#ownedFiles","#filename","#database","#setLengthLimit","#pragma","#closed","#statement"],"sources":["../../backends/sqlite-wasm.ts"],"sourcesContent":["/**\n * ← workerd `NO upstream correspondence (storage-backend adaptation)`\n *\n * `SqlDatabaseProvider` over the browser's OPFS SAH pool.\n *\n * The pool is a parameter, not something this module goes and gets. Installing\n * the VFS is the host's job — `installOpfsSAHPoolVfs` decides the OPFS\n * directory, the pool capacity and whether to clear on init, all of which are\n * layout questions this package deliberately knows nothing about. What arrives\n * here is the already-installed pool, and with it the two things a backend\n * needs that a bare `sqlite3` module cannot give: a database constructor bound\n * to that VFS, plus the pool's file export/import/unlink operations used by\n * snapshots and `reset()`.\n *\n * The pool is structurally typed rather than imported from\n * `@sqlite.org/sqlite-wasm`, so this package takes no dependency on the driver\n * and the caller is free to pass a pool from any build of it. The shape below\n * is the subset of `SAHPoolUtil` and `oo1.DB` that is used, copied from the\n * driver's own `.d.mts`.\n *\n * NOT exercised by the unit lane: it needs OPFS, which means a browser. It is\n * exercised twice in the browser lane — by `sqlite-wasm.smoke.spec.ts`, which\n * drives this file directly, and by the conformance suite, which runs the whole\n * package over it.\n */\n\nimport {\n requireSqliteLength,\n requireSafeDatabaseName,\n requireValidSqlDatabaseSnapshot,\n SQLITE_LENGTH_LIMIT,\n SQL_WRONG_BINDINGS_MESSAGE,\n type SqlDatabase,\n type SqlDatabaseProvider,\n type SqlDatabaseSnapshot,\n type SqlDatabaseSnapshotProvider,\n type SqlDatabaseStatement,\n type SqlResult,\n type SqlValue,\n} from \"../src/util/sqlite\";\n\n/** ← `PreparedStatement`, the members used here. */\nexport interface SqliteWasmStatement {\n readonly columnCount: number;\n readonly parameterCount: number;\n bind(bindings: readonly (string | number | bigint | null | Uint8Array)[]): unknown;\n step(): boolean;\n get(index: number): unknown;\n getColumnNames(target?: string[]): string[];\n finalize(): number | undefined;\n}\n\n/** ← `oo1.DB` / `OpfsSAHPoolDatabase`, the members used here. */\nexport interface SqliteWasmDatabaseHandle {\n /** ← `oo1.DB.pointer`, which is absent once the handle is closed. */\n readonly pointer?: number | undefined;\n prepare(sql: string): SqliteWasmStatement;\n changes(total?: boolean, sixtyFour?: false): number;\n close(): void;\n}\n\n/** ← `SAHPoolUtil`, the members used here. */\nexport interface OpfsSahPool {\n /** Constructs a database inside this pool's VFS. Names are absolute, so they start with \"/\". */\n readonly OpfsSAHPoolDb: new (filename: string) => SqliteWasmDatabaseHandle;\n exportFile(filename: string): Uint8Array | Promise<Uint8Array>;\n importDb(filename: string, image: Uint8Array): number | Promise<number>;\n getFileNames(): string[];\n /** Disassociates a virtual file from the pool. Results are undefined if it is in active use. */\n unlink(filename: string): boolean;\n}\n\n/**\n * ← `Sqlite3Static[\"capi\"]`, restricted to the one C function `oo1.DB` does not\n * wrap.\n *\n * Takes the pointer rather than the handle, even though upstream's `DbPtr`\n * accepts either: a structural subset of `oo1.DB` is not assignable to the\n * `Database` class, so asking for the handle would make the real `capi` fail to\n * satisfy this interface.\n */\nexport interface SqliteWasmCapi {\n readonly SQLITE_LIMIT_LENGTH: number;\n sqlite3_complete(sql: string): 0 | 1;\n sqlite3_get_autocommit(db: number): number;\n sqlite3_limit(db: number, id: number, newValue: number): number;\n}\n\n/**\n * What the host hands over: the pool it installed, and the C-API namespace it\n * already holds. Both come off the same `sqlite3` object the caller used to\n * call `installOpfsSAHPoolVfs`, so this asks for nothing it does not have.\n */\nexport interface SqliteWasmHost {\n readonly pool: OpfsSahPool;\n readonly capi: SqliteWasmCapi;\n}\n\nexport type SqliteWasmProviderOptions = {\n /** Absolute path prefix inside the pool, e.g. `/actor-<id>`. Must start with \"/\". */\n prefix: string;\n};\n\n/**\n * One actor's named databases and their file lifecycle inside an OPFS SAH pool.\n *\n * A root or facet container only needs the `SqlDatabaseProvider` surface. Its\n * host also has to close every connection when that placement dies, remove the\n * prefix on delete, and copy every database on clone. Those file operations\n * belong here because SAH-pool files are virtual and can only be reached through\n * the pool that owns them.\n */\nexport class SqliteWasmActorStorage implements SqlDatabaseProvider {\n readonly #host: SqliteWasmHost;\n readonly #prefix: string;\n readonly #provider: SqlDatabaseSnapshotProvider;\n\n constructor(host: SqliteWasmHost, prefix: string) {\n this.#host = host;\n this.#prefix = prefix;\n this.#provider = createSqliteWasmProvider(host, { prefix });\n }\n\n open(name: string): Promise<SqlDatabase> {\n return this.#provider.open(name);\n }\n\n /**\n * Drop every handle. Leaving one behind per respawn or facet abort would\n * accumulate concurrent writers inside a VFS that expects to own its files.\n */\n close(): void {\n this.#provider.close();\n }\n\n /** Close every handle, then physically remove every database under this prefix. */\n deleteAll(): void {\n this.close();\n for (const file of this.#ownedFiles()) {\n if (!this.#host.pool.unlink(file)) throw new Error(`SAH pool did not unlink ${file}`);\n }\n }\n\n /**\n * Replace this prefix with every database under `source`, including files\n * from an earlier placement that this session never opened.\n *\n * The source may still be running, so this uses the pool's file operations\n * rather than the snapshot API, which correctly refuses open handles. A\n * recovery sidecar means the bytes are not a stable database image and is\n * refused before the destination is touched. Every source image is also\n * exported before replacement starts, so a failed read preserves the target.\n */\n async copyFrom(source: SqliteWasmActorStorage): Promise<void> {\n const files = source.#ownedFiles();\n const sidecar = files.find((file) => !file.endsWith(\".sqlite\"));\n if (sidecar !== undefined) {\n throw new Error(`Cannot clone actor storage with a SQLite recovery sidecar: ${sidecar}`);\n }\n const images: Array<{ name: string; bytes: Uint8Array }> = [];\n for (const file of files) {\n const name = file.slice(source.#prefix.length + 1, -\".sqlite\".length);\n requireSafeDatabaseName(name);\n images.push({\n name,\n bytes: new Uint8Array(await source.#host.pool.exportFile(file)),\n });\n }\n this.deleteAll();\n for (const { name, bytes } of images) {\n await this.#host.pool.importDb(\n `${this.#prefix}.${name}.sqlite`,\n bytes,\n );\n }\n }\n\n #ownedFiles(): string[] {\n return this.#host.pool\n .getFileNames()\n .filter((name) => name.startsWith(`${this.#prefix}.`));\n }\n}\n\nexport function createSqliteWasmProvider(\n host: SqliteWasmHost,\n options: SqliteWasmProviderOptions,\n): SqlDatabaseSnapshotProvider {\n const { prefix } = options;\n if (!prefix.startsWith(\"/\")) {\n throw new Error(`SAH pool names are absolute; prefix must start with \"/\": ${prefix}`);\n }\n const openDatabases = new Set<SqliteWasmDatabase>();\n const ownedFiles = (): string[] =>\n host.pool.getFileNames().filter((name) => name.startsWith(`${prefix}.`));\n return {\n async open(name: string): Promise<SqlDatabase> {\n // Names come from inside the package, so this is defence in depth — but\n // it is the one place a name becomes a pool file name.\n requireSafeDatabaseName(name);\n let database: SqliteWasmDatabase;\n database = new SqliteWasmDatabase(host, `${prefix}.${name}.sqlite`, () =>\n openDatabases.delete(database),\n );\n openDatabases.add(database);\n return database;\n },\n close(): void {\n for (const database of [...openDatabases]) database.close();\n },\n async exportSnapshot(): Promise<SqlDatabaseSnapshot> {\n requireClosed(openDatabases);\n const files = ownedFiles();\n requireNoRecoverySidecars(files);\n const databases = await Promise.all(\n files\n .filter((file) => file.endsWith(\".sqlite\"))\n .sort()\n .map(async (file) => {\n const name = file.slice(prefix.length + 1, -\".sqlite\".length);\n requireSafeDatabaseName(name);\n return { name, image: new Uint8Array(await host.pool.exportFile(file)) };\n }),\n );\n const snapshot: SqlDatabaseSnapshot = { version: 1, databases };\n requireValidSqlDatabaseSnapshot(snapshot);\n return snapshot;\n },\n async importSnapshot(snapshot: SqlDatabaseSnapshot): Promise<void> {\n requireClosed(openDatabases);\n requireValidSqlDatabaseSnapshot(snapshot);\n for (const file of ownedFiles()) host.pool.unlink(file);\n for (const { name, image } of snapshot.databases) {\n await host.pool.importDb(`${prefix}.${name}.sqlite`, new Uint8Array(image));\n }\n },\n };\n}\n\nexport class SqliteWasmDatabase implements SqlDatabase {\n readonly #host: SqliteWasmHost;\n readonly #filename: string;\n #database: SqliteWasmDatabaseHandle;\n #closed = false;\n\n constructor(\n host: SqliteWasmHost,\n filename: string,\n private readonly onClose: () => void = () => {},\n ) {\n this.#host = host;\n this.#filename = filename;\n this.#database = new host.pool.OpfsSAHPoolDb(filename);\n this.#setLengthLimit();\n }\n\n prepare(sql: string): SqlDatabaseStatement {\n const source = firstCompleteStatement(this.#host.capi, sql);\n return new WasmSqlStatement(this.#database, this.#database.prepare(source), source);\n }\n\n exec(sql: string, params: readonly SqlValue[]): SqlResult {\n const statement = this.prepare(sql);\n try {\n return statement.execute(params);\n } finally {\n statement.close();\n }\n }\n\n get databaseSize(): number {\n const pageCount = this.#pragma(\"page_count\");\n const pageSize = this.#pragma(\"page_size\");\n return pageCount * pageSize;\n }\n\n /**\n * `oo1.DB` wraps no equivalent, so this is the one place the backend reaches\n * past it into the C API. `DbPtr` accepts the database object itself.\n */\n get inTransaction(): boolean {\n const pointer = this.#database.pointer;\n if (pointer === undefined) throw new Error(\"The database handle is closed.\");\n return this.#host.capi.sqlite3_get_autocommit(pointer) === 0;\n }\n\n reset(): void {\n // The pool's files are not visible in OPFS under these names, so deleting\n // one goes through the pool rather than through the filesystem. The handle\n // has to be closed first: `unlink`'s results are undefined for a file in\n // active use.\n this.#database.close();\n if (!this.#host.pool.unlink(this.#filename)) {\n throw new Error(`SAH pool did not unlink ${this.#filename}`);\n }\n this.#database = new this.#host.pool.OpfsSAHPoolDb(this.#filename);\n this.#setLengthLimit();\n }\n\n close(): void {\n if (this.#closed) return;\n this.#database.close();\n this.#closed = true;\n this.onClose();\n }\n\n #setLengthLimit(): void {\n const pointer = this.#database.pointer;\n if (pointer === undefined) throw new Error(\"The database handle is closed.\");\n this.#host.capi.sqlite3_limit(\n pointer,\n this.#host.capi.SQLITE_LIMIT_LENGTH,\n SQLITE_LENGTH_LIMIT,\n );\n }\n\n #pragma(name: string): number {\n const row = this.exec(`PRAGMA ${name}`, []).rawRows[0];\n const value = row?.[0];\n if (typeof value !== \"number\") {\n throw new Error(`PRAGMA ${name} did not return a number.`);\n }\n return value;\n }\n}\n\nfunction requireClosed(openDatabases: ReadonlySet<SqliteWasmDatabase>): void {\n if (openDatabases.size > 0) {\n throw new Error(\"Cannot snapshot or restore while database handles are open.\");\n }\n}\n\nfunction requireNoRecoverySidecars(files: readonly string[]): void {\n const sidecar = files.find((file) => !file.endsWith(\".sqlite\"));\n if (sidecar !== undefined) {\n throw new Error(`Cannot export a snapshot with a SQLite recovery sidecar: ${sidecar}`);\n }\n}\n\nclass WasmSqlStatement implements SqlDatabaseStatement {\n readonly #database: SqliteWasmDatabaseHandle;\n readonly #statement: SqliteWasmStatement;\n #closed = false;\n\n constructor(\n database: SqliteWasmDatabaseHandle,\n statement: SqliteWasmStatement,\n readonly sql: string,\n ) {\n this.#database = database;\n this.#statement = statement;\n }\n\n get parameterCount(): number {\n return this.#statement.parameterCount;\n }\n\n execute(params: readonly SqlValue[]): SqlResult {\n if (params.length !== this.parameterCount) throw new Error(SQL_WRONG_BINDINGS_MESSAGE);\n params.forEach(requireSqliteLength);\n if (params.length > 0) this.#statement.bind(params);\n\n const columnCount = this.#statement.columnCount;\n if (columnCount === 0) {\n this.#statement.step();\n return { columnNames: [], rawRows: [], rowsWritten: this.#database.changes(false) };\n }\n\n const changesBefore = this.#database.changes(true);\n const columnNames = this.#statement.getColumnNames();\n const rawRows: unknown[][] = [];\n while (this.#statement.step()) {\n const row: unknown[] = [];\n for (let column = 0; column < columnCount; column += 1) {\n row.push(this.#statement.get(column));\n }\n rawRows.push(row);\n }\n // A SELECT leaves total_changes() untouched; DML RETURNING advances it.\n // The delta avoids a SQL classifier and matches the public cursor contract.\n return {\n columnNames,\n rawRows,\n rowsWritten: this.#database.changes(true) - changesBefore,\n };\n }\n\n close(): void {\n if (this.#closed) return;\n this.#closed = true;\n this.#statement.finalize();\n }\n}\n\n/** Finds the first complete statement without reimplementing SQLite's trigger grammar. */\nfunction firstCompleteStatement(capi: SqliteWasmCapi, sql: string): string {\n let semicolon = sql.indexOf(\";\");\n while (semicolon !== -1) {\n const candidate = sql.slice(0, semicolon + 1);\n if (capi.sqlite3_complete(candidate) === 1) return candidate;\n semicolon = sql.indexOf(\";\", semicolon + 1);\n }\n return sql;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sqlite-wasm.js","names":["#host","#prefix","#provider","#ownedFiles","#filename","#database","#setLengthLimit","#pragma","#closed","#statement"],"sources":["../../backends/sqlite-wasm.ts"],"sourcesContent":["/**\n * ← workerd `NO upstream correspondence (storage-backend adaptation)`\n *\n * `SqlDatabaseProvider` over the browser's OPFS SAH pool.\n *\n * The pool is a parameter, not something this module goes and gets. Installing\n * the VFS is the host's job — `installOpfsSAHPoolVfs` decides the OPFS\n * directory, the pool capacity and whether to clear on init, all of which are\n * layout questions this package deliberately knows nothing about. What arrives\n * here is the already-installed pool, and with it the two things a backend\n * needs that a bare `sqlite3` module cannot give: a database constructor bound\n * to that VFS, plus the pool's file export/import/unlink operations used by\n * snapshots and `reset()`.\n *\n * The pool is structurally typed rather than imported from\n * `@sqlite.org/sqlite-wasm`, so this package takes no dependency on the driver\n * and the caller is free to pass a pool from any build of it. The shape below\n * is the subset of `SAHPoolUtil` and `oo1.DB` that is used, copied from the\n * driver's own `.d.mts`.\n *\n * NOT exercised by the unit lane: it needs OPFS, which means a browser. It is\n * exercised twice in the browser lane — by `sqlite-wasm.smoke.spec.ts`, which\n * drives this file directly, and by the conformance suite, which runs the whole\n * package over it.\n */\n\nimport {\n requireSqliteLength,\n requireSafeDatabaseName,\n requireValidSqlDatabaseSnapshot,\n SQLITE_LENGTH_LIMIT,\n SQL_WRONG_BINDINGS_MESSAGE,\n type SqlDatabase,\n type SqlDatabaseProvider,\n type SqlDatabaseSnapshot,\n type SqlDatabaseSnapshotProvider,\n type SqlDatabaseStatement,\n type SqlResult,\n type SqlValue,\n} from \"../src/util/sqlite\";\nimport { requireImportableRuntimeStorage } from \"../src/util/sqlite-migrations\";\n\n/** ← `PreparedStatement`, the members used here. */\nexport interface SqliteWasmStatement {\n readonly columnCount: number;\n readonly parameterCount: number;\n bind(bindings: readonly (string | number | bigint | null | Uint8Array)[]): unknown;\n step(): boolean;\n get(index: number): unknown;\n getColumnNames(target?: string[]): string[];\n finalize(): number | undefined;\n}\n\n/** ← `oo1.DB` / `OpfsSAHPoolDatabase`, the members used here. */\nexport interface SqliteWasmDatabaseHandle {\n /** ← `oo1.DB.pointer`, which is absent once the handle is closed. */\n readonly pointer?: number | undefined;\n prepare(sql: string): SqliteWasmStatement;\n changes(total?: boolean, sixtyFour?: false): number;\n close(): void;\n}\n\n/** ← `SAHPoolUtil`, the members used here. */\nexport interface OpfsSahPool {\n /** Constructs a database inside this pool's VFS. Names are absolute, so they start with \"/\". */\n readonly OpfsSAHPoolDb: new (filename: string) => SqliteWasmDatabaseHandle;\n exportFile(filename: string): Uint8Array | Promise<Uint8Array>;\n importDb(filename: string, image: Uint8Array): number | Promise<number>;\n getFileNames(): string[];\n /** Disassociates a virtual file from the pool. Results are undefined if it is in active use. */\n unlink(filename: string): boolean;\n}\n\n/**\n * ← `Sqlite3Static[\"capi\"]`, restricted to the one C function `oo1.DB` does not\n * wrap.\n *\n * Takes the pointer rather than the handle, even though upstream's `DbPtr`\n * accepts either: a structural subset of `oo1.DB` is not assignable to the\n * `Database` class, so asking for the handle would make the real `capi` fail to\n * satisfy this interface.\n */\nexport interface SqliteWasmCapi {\n readonly SQLITE_LIMIT_LENGTH: number;\n sqlite3_complete(sql: string): 0 | 1;\n sqlite3_get_autocommit(db: number): number;\n sqlite3_limit(db: number, id: number, newValue: number): number;\n}\n\n/**\n * What the host hands over: the pool it installed, and the C-API namespace it\n * already holds. Both come off the same `sqlite3` object the caller used to\n * call `installOpfsSAHPoolVfs`, so this asks for nothing it does not have.\n */\nexport interface SqliteWasmHost {\n readonly pool: OpfsSahPool;\n readonly capi: SqliteWasmCapi;\n}\n\nexport type SqliteWasmProviderOptions = {\n /** Absolute path prefix inside the pool, e.g. `/actor-<id>`. Must start with \"/\". */\n prefix: string;\n};\n\n/**\n * One actor's named databases and their file lifecycle inside an OPFS SAH pool.\n *\n * A root or facet container only needs the `SqlDatabaseProvider` surface. Its\n * host also has to close every connection when that placement dies, remove the\n * prefix on delete, and copy every database on clone. Those file operations\n * belong here because SAH-pool files are virtual and can only be reached through\n * the pool that owns them.\n */\nexport class SqliteWasmActorStorage implements SqlDatabaseProvider {\n readonly #host: SqliteWasmHost;\n readonly #prefix: string;\n readonly #provider: SqlDatabaseSnapshotProvider;\n\n constructor(host: SqliteWasmHost, prefix: string) {\n this.#host = host;\n this.#prefix = prefix;\n this.#provider = createSqliteWasmProvider(host, { prefix });\n }\n\n open(name: string): Promise<SqlDatabase> {\n return this.#provider.open(name);\n }\n\n /**\n * Drop every handle. Leaving one behind per respawn or facet abort would\n * accumulate concurrent writers inside a VFS that expects to own its files.\n */\n close(): void {\n this.#provider.close();\n }\n\n /** Close every handle, then physically remove every database under this prefix. */\n deleteAll(): void {\n this.close();\n for (const file of this.#ownedFiles()) {\n if (!this.#host.pool.unlink(file)) throw new Error(`SAH pool did not unlink ${file}`);\n }\n }\n\n /**\n * Replace this prefix with every database under `source`, including files\n * from an earlier placement that this session never opened.\n *\n * The source may still be running, so this uses the pool's file operations\n * rather than the snapshot API, which correctly refuses open handles. A\n * recovery sidecar means the bytes are not a stable database image and is\n * refused before the destination is touched. Every source image is also\n * exported before replacement starts, so a failed read preserves the target.\n */\n async copyFrom(source: SqliteWasmActorStorage): Promise<void> {\n const files = source.#ownedFiles();\n const sidecar = files.find((file) => !file.endsWith(\".sqlite\"));\n if (sidecar !== undefined) {\n throw new Error(`Cannot clone actor storage with a SQLite recovery sidecar: ${sidecar}`);\n }\n const images: Array<{ name: string; bytes: Uint8Array }> = [];\n for (const file of files) {\n const name = file.slice(source.#prefix.length + 1, -\".sqlite\".length);\n requireSafeDatabaseName(name);\n images.push({\n name,\n bytes: new Uint8Array(await source.#host.pool.exportFile(file)),\n });\n }\n this.deleteAll();\n for (const { name, bytes } of images) {\n await this.#host.pool.importDb(\n `${this.#prefix}.${name}.sqlite`,\n bytes,\n );\n }\n }\n\n #ownedFiles(): string[] {\n return this.#host.pool\n .getFileNames()\n .filter((name) => name.startsWith(`${this.#prefix}.`));\n }\n}\n\nexport function createSqliteWasmProvider(\n host: SqliteWasmHost,\n options: SqliteWasmProviderOptions,\n): SqlDatabaseSnapshotProvider {\n const { prefix } = options;\n if (!prefix.startsWith(\"/\")) {\n throw new Error(`SAH pool names are absolute; prefix must start with \"/\": ${prefix}`);\n }\n const openDatabases = new Set<SqliteWasmDatabase>();\n const ownedFiles = (): string[] =>\n host.pool.getFileNames().filter((name) => name.startsWith(`${prefix}.`));\n return {\n async open(name: string): Promise<SqlDatabase> {\n // Names come from inside the package, so this is defence in depth — but\n // it is the one place a name becomes a pool file name.\n requireSafeDatabaseName(name);\n let database: SqliteWasmDatabase;\n database = new SqliteWasmDatabase(host, `${prefix}.${name}.sqlite`, () =>\n openDatabases.delete(database),\n );\n openDatabases.add(database);\n return database;\n },\n close(): void {\n for (const database of [...openDatabases]) database.close();\n },\n async exportSnapshot(): Promise<SqlDatabaseSnapshot> {\n requireClosed(openDatabases);\n const files = ownedFiles();\n requireNoRecoverySidecars(files);\n const databases = await Promise.all(\n files\n .filter((file) => file.endsWith(\".sqlite\"))\n .sort()\n .map(async (file) => {\n const name = file.slice(prefix.length + 1, -\".sqlite\".length);\n requireSafeDatabaseName(name);\n return { name, image: new Uint8Array(await host.pool.exportFile(file)) };\n }),\n );\n const snapshot: SqlDatabaseSnapshot = { version: 1, databases };\n requireValidSqlDatabaseSnapshot(snapshot);\n return snapshot;\n },\n async importSnapshot(snapshot: SqlDatabaseSnapshot): Promise<void> {\n requireClosed(openDatabases);\n requireValidSqlDatabaseSnapshot(snapshot);\n requireImportableRuntimeStorage(snapshot);\n for (const file of ownedFiles()) host.pool.unlink(file);\n for (const { name, image } of snapshot.databases) {\n await host.pool.importDb(`${prefix}.${name}.sqlite`, new Uint8Array(image));\n }\n },\n };\n}\n\nexport class SqliteWasmDatabase implements SqlDatabase {\n readonly #host: SqliteWasmHost;\n readonly #filename: string;\n #database: SqliteWasmDatabaseHandle;\n #closed = false;\n\n constructor(\n host: SqliteWasmHost,\n filename: string,\n private readonly onClose: () => void = () => {},\n ) {\n this.#host = host;\n this.#filename = filename;\n this.#database = new host.pool.OpfsSAHPoolDb(filename);\n this.#setLengthLimit();\n }\n\n prepare(sql: string): SqlDatabaseStatement {\n const source = firstCompleteStatement(this.#host.capi, sql);\n return new WasmSqlStatement(this.#database, this.#database.prepare(source), source);\n }\n\n exec(sql: string, params: readonly SqlValue[]): SqlResult {\n const statement = this.prepare(sql);\n try {\n return statement.execute(params);\n } finally {\n statement.close();\n }\n }\n\n get databaseSize(): number {\n const pageCount = this.#pragma(\"page_count\");\n const pageSize = this.#pragma(\"page_size\");\n return pageCount * pageSize;\n }\n\n /**\n * `oo1.DB` wraps no equivalent, so this is the one place the backend reaches\n * past it into the C API. `DbPtr` accepts the database object itself.\n */\n get inTransaction(): boolean {\n const pointer = this.#database.pointer;\n if (pointer === undefined) throw new Error(\"The database handle is closed.\");\n return this.#host.capi.sqlite3_get_autocommit(pointer) === 0;\n }\n\n reset(): void {\n // The pool's files are not visible in OPFS under these names, so deleting\n // one goes through the pool rather than through the filesystem. The handle\n // has to be closed first: `unlink`'s results are undefined for a file in\n // active use.\n this.#database.close();\n if (!this.#host.pool.unlink(this.#filename)) {\n throw new Error(`SAH pool did not unlink ${this.#filename}`);\n }\n this.#database = new this.#host.pool.OpfsSAHPoolDb(this.#filename);\n this.#setLengthLimit();\n }\n\n close(): void {\n if (this.#closed) return;\n this.#database.close();\n this.#closed = true;\n this.onClose();\n }\n\n #setLengthLimit(): void {\n const pointer = this.#database.pointer;\n if (pointer === undefined) throw new Error(\"The database handle is closed.\");\n this.#host.capi.sqlite3_limit(\n pointer,\n this.#host.capi.SQLITE_LIMIT_LENGTH,\n SQLITE_LENGTH_LIMIT,\n );\n }\n\n #pragma(name: string): number {\n const row = this.exec(`PRAGMA ${name}`, []).rawRows[0];\n const value = row?.[0];\n if (typeof value !== \"number\") {\n throw new Error(`PRAGMA ${name} did not return a number.`);\n }\n return value;\n }\n}\n\nfunction requireClosed(openDatabases: ReadonlySet<SqliteWasmDatabase>): void {\n if (openDatabases.size > 0) {\n throw new Error(\"Cannot snapshot or restore while database handles are open.\");\n }\n}\n\nfunction requireNoRecoverySidecars(files: readonly string[]): void {\n const sidecar = files.find((file) => !file.endsWith(\".sqlite\"));\n if (sidecar !== undefined) {\n throw new Error(`Cannot export a snapshot with a SQLite recovery sidecar: ${sidecar}`);\n }\n}\n\nclass WasmSqlStatement implements SqlDatabaseStatement {\n readonly #database: SqliteWasmDatabaseHandle;\n readonly #statement: SqliteWasmStatement;\n #closed = false;\n\n constructor(\n database: SqliteWasmDatabaseHandle,\n statement: SqliteWasmStatement,\n readonly sql: string,\n ) {\n this.#database = database;\n this.#statement = statement;\n }\n\n get parameterCount(): number {\n return this.#statement.parameterCount;\n }\n\n execute(params: readonly SqlValue[]): SqlResult {\n if (params.length !== this.parameterCount) throw new Error(SQL_WRONG_BINDINGS_MESSAGE);\n params.forEach(requireSqliteLength);\n if (params.length > 0) this.#statement.bind(params);\n\n const columnCount = this.#statement.columnCount;\n if (columnCount === 0) {\n this.#statement.step();\n return { columnNames: [], rawRows: [], rowsWritten: this.#database.changes(false) };\n }\n\n const changesBefore = this.#database.changes(true);\n const columnNames = this.#statement.getColumnNames();\n const rawRows: unknown[][] = [];\n while (this.#statement.step()) {\n const row: unknown[] = [];\n for (let column = 0; column < columnCount; column += 1) {\n row.push(this.#statement.get(column));\n }\n rawRows.push(row);\n }\n // A SELECT leaves total_changes() untouched; DML RETURNING advances it.\n // The delta avoids a SQL classifier and matches the public cursor contract.\n return {\n columnNames,\n rawRows,\n rowsWritten: this.#database.changes(true) - changesBefore,\n };\n }\n\n close(): void {\n if (this.#closed) return;\n this.#closed = true;\n this.#statement.finalize();\n }\n}\n\n/** Finds the first complete statement without reimplementing SQLite's trigger grammar. */\nfunction firstCompleteStatement(capi: SqliteWasmCapi, sql: string): string {\n let semicolon = sql.indexOf(\";\");\n while (semicolon !== -1) {\n const candidate = sql.slice(0, semicolon + 1);\n if (capi.sqlite3_complete(candidate) === 1) return candidate;\n semicolon = sql.indexOf(\";\", semicolon + 1);\n }\n return sql;\n}\n"],"mappings":"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"}
|
|
@@ -368,7 +368,7 @@ function hasCurrentSqliteTable(db, name, createSql) {
|
|
|
368
368
|
const rows = "run" in db ? db.run(query, name).rawRows : db.exec(query, [name]).rawRows;
|
|
369
369
|
const row = rows[0];
|
|
370
370
|
if (row === void 0) return false;
|
|
371
|
-
if (rows.length !== 1 || row[0] !== "table" || typeof row[1] !== "string" || normalizeSchemaSql(row[1]) !== normalizeSchemaSql(createSql)) throw new Error(`Incompatible @mcp-b/do-runtime storage schema for table "${name}".
|
|
371
|
+
if (rows.length !== 1 || row[0] !== "table" || typeof row[1] !== "string" || normalizeSchemaSql(row[1]) !== normalizeSchemaSql(createSql)) throw new Error(`Incompatible @mcp-b/do-runtime storage schema for table "${name}". Runtime storage migrations already brought this database to the current version, so this is a shape no release produced — the database is foreign or corrupt. Restore it from a snapshot or delete it to start over.`);
|
|
372
372
|
return true;
|
|
373
373
|
}
|
|
374
374
|
function normalizeSchemaSql(sql) {
|
|
@@ -492,7 +492,59 @@ function stripLeadingTrivia(statement) {
|
|
|
492
492
|
return statement.slice(index);
|
|
493
493
|
}
|
|
494
494
|
}
|
|
495
|
+
/** `MIGRATIONS[i]` takes a database from storage version `i + 1` to `i + 2`. */
|
|
496
|
+
var MIGRATIONS = [];
|
|
497
|
+
/**
|
|
498
|
+
* Bring one just-opened runtime database to `RUNTIME_STORAGE_VERSION`. Called
|
|
499
|
+
* by every seam that opens a runtime database, before anything reads it, with
|
|
500
|
+
* the database's own name so a refusal says which file it is about. The last
|
|
501
|
+
* two parameters exist for the tests in this module's test file; every real
|
|
502
|
+
* caller takes the shipped defaults.
|
|
503
|
+
*
|
|
504
|
+
* A version newer than this release refuses — the analogue of
|
|
505
|
+
* `hasCurrentSqliteTable`'s refusal, with the one remedy named. A version 0
|
|
506
|
+
* database is from before versioning existed (the same shape as version 1) or
|
|
507
|
+
* a fresh file; both enter the chain at 1. Pending steps and the stamp commit
|
|
508
|
+
* as one transaction, so a failed step leaves the file exactly as it was and
|
|
509
|
+
* the container placement fails with the step's error.
|
|
510
|
+
*/
|
|
511
|
+
/**
|
|
512
|
+
* Refuse a snapshot image stamped by a newer release at the import seam, where
|
|
513
|
+
* the operation that brought the file in is the one that fails — instead of at
|
|
514
|
+
* the next placement, far from the cause. `user_version` sits at byte 60 of
|
|
515
|
+
* the SQLite header, big-endian (https://www.sqlite.org/fileformat2.html);
|
|
516
|
+
* callers validate the header shape first (`requireValidSqlDatabaseSnapshot`).
|
|
517
|
+
*/
|
|
518
|
+
function requireImportableRuntimeStorage(snapshot, current = 1) {
|
|
519
|
+
for (const { name, image } of snapshot.databases) {
|
|
520
|
+
const stored = new DataView(image.buffer, image.byteOffset, image.byteLength).getInt32(60);
|
|
521
|
+
if (stored > current) throw new Error(`Snapshot database ${JSON.stringify(name)} was written by a newer @mcp-b/do-runtime (storage version ${stored}; this release supports up to ${current}). Upgrade the package to import it.`);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
function ensureRuntimeStorageVersion(db, name, current = 1, migrations = MIGRATIONS) {
|
|
525
|
+
if (!Number.isSafeInteger(current) || current < 1 || current > 2147483647) throw new Error(`Runtime storage version must be a positive 32-bit integer, got ${current}.`);
|
|
526
|
+
const row = db.exec("PRAGMA user_version", []).rawRows[0];
|
|
527
|
+
if (row === void 0) throw new Error(`PRAGMA user_version returned no row for database ${JSON.stringify(name)}.`);
|
|
528
|
+
const stored = getInt64(row, 0);
|
|
529
|
+
if (stored === current) return;
|
|
530
|
+
if (stored > current) throw new Error(`Runtime database ${JSON.stringify(name)} was written by a newer @mcp-b/do-runtime (storage version ${stored}; this release supports up to ${current}). Upgrade the package to open it.`);
|
|
531
|
+
if (db.inTransaction) throw new Error(`Runtime storage migration for database ${JSON.stringify(name)} began inside an open transaction.`);
|
|
532
|
+
db.exec("BEGIN", []);
|
|
533
|
+
try {
|
|
534
|
+
for (let from = Math.max(stored, 1); from < current; from++) {
|
|
535
|
+
const step = migrations[from - 1];
|
|
536
|
+
if (step === void 0) throw new Error(`Missing runtime storage migration from version ${from} to ${from + 1}.`);
|
|
537
|
+
step(db);
|
|
538
|
+
if (!db.inTransaction) throw new Error(`Runtime storage migration from version ${from} to ${from + 1} closed the migration transaction; a step must not issue BEGIN, COMMIT, ROLLBACK, or SAVEPOINT.`);
|
|
539
|
+
}
|
|
540
|
+
db.exec(`PRAGMA user_version = ${current}`, []);
|
|
541
|
+
db.exec("COMMIT", []);
|
|
542
|
+
} catch (error) {
|
|
543
|
+
if (db.inTransaction) db.exec("ROLLBACK", []);
|
|
544
|
+
throw error;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
495
547
|
//#endregion
|
|
496
|
-
export {
|
|
548
|
+
export { SqliteDatabase as a, getText as c, requireSafeDatabaseName as d, requireSqliteLength as f, SQL_WRONG_BINDINGS_MESSAGE as i, hasCurrentSqliteTable as l, requireImportableRuntimeStorage as n, getBlob as o, requireValidSqlDatabaseSnapshot as p, SQLITE_LENGTH_LIMIT as r, getInt64 as s, ensureRuntimeStorageVersion as t, isNull as u };
|
|
497
549
|
|
|
498
|
-
//# sourceMappingURL=sqlite-
|
|
550
|
+
//# sourceMappingURL=sqlite-migrations-DsWmLP_B.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sqlite-migrations-DsWmLP_B.js","names":["#backend","#resetListeners","#onWriteCallback","#onCriticalErrorCallback","#exec","#execStatement","#checkForAutoRollback","#applyChange","#inTransaction","#savepoints","#rollbackCallbacks","#criticalError","#runRollbackCallbacksDownTo"],"sources":["../../src/util/sqlite.ts","../../src/util/sqlite-migrations.ts"],"sourcesContent":["/**\n * ← workerd `src/workerd/util/sqlite.{h,c++}`\n *\n * The SQL backend port. Upstream's seam is the same one: `server.c++` opens\n * `<actor-id>.<facetId>.sqlite` and hands `ActorSqlite` a `SqliteDatabase`.\n *\n * Almost none of upstream's 3,768 lines are ours. `sqlite.{h,c++}` is workerd's\n * binding to the SQLite C API — statement caching, the VFS, regulators, the\n * authorizer, the memory-metering allocator. Underneath us that role is played\n * by `node:sqlite` and sqlite-wasm, which is the storage-backend adaptation the\n * design record sanctions: `io-gate.h` knows nothing about SQLite, `ActorSqlite`\n * calls into it, and that seam is upstream's rather than ours.\n *\n * So this file is two things stacked:\n *\n * 1. `SqlDatabase` / `SqlDatabaseProvider` — the backend seam. `backends/`\n * implements it, and nothing above `util/` ever sees a driver type.\n * 2. `SqliteDatabase` — the small part of upstream's class that is genuinely\n * ours, because the layers above call into it and a stateless exec interface\n * cannot express it: `onRollback`, the transaction/savepoint stack it needs,\n * `reset()` and its `ResetListener` notification, all three of which\n * `sqlite-kv.c++` and `sqlite-metadata.c++` reach for; plus `onWrite`,\n * `notifyWrite` and `onCriticalError`, which only `io/actor-sqlite.ts`\n * reaches for. That last trio lives here rather than one directory up\n * because it lives here upstream (`sqlite.h:240`, `:248`, `:267`): a\n * callback slot is not actor knowledge, and a reader who finds `onWrite` in\n * `sqlite.h` has to find it in this file too. Every consumer takes a\n * `SqliteDatabase`, exactly as upstream's take a `SqliteDatabase&`.\n *\n * `transactionSync` is NOT here — it lives in `io/actor-sqlite.ts` as\n * SAVEPOINT/RELEASE with a depth counter, exactly as upstream has it. Today\n * both browser and Node adapters duplicate `BEGIN IMMEDIATE`, which is why a\n * nested call is a live SQLite error (§2.4). Moving it inward fixes that.\n *\n * Not ported, because the substrate has no equivalent: the `Regulator` /\n * authorizer machinery (there is no untrusted-SQL path in `util/`, and\n * `api/sql.ts` owns that question); `SqliteObserver` row-count billing, whose\n * counters are libsql `STMTSTATUS` extensions neither backend exposes;\n * `sqlite-metering.{h,c++}`, which swaps SQLite's process-wide allocator to\n * meter per-database memory — a C-API facility with no JS analogue; and the\n * point-in-time-recovery APIs, a named substrate boundary in the package README.\n *\n * Spec: §1.4, §2.4 in docs/decisions.md.\n */\n\n/** The four values SQLite itself accepts after public JSG-style conversion. */\nexport type SqlValue = string | number | null | Uint8Array;\n\nexport type SqlResult = {\n readonly columnNames: readonly string[];\n readonly rawRows: readonly (readonly unknown[])[];\n /** Rows changed by this statement, including DML with `RETURNING`. */\n readonly rowsWritten: number;\n};\n\n/** ← `SqliteDatabase::IngestResult`. */\nexport type SqlIngestResult = {\n readonly remainder: string;\n readonly rowsRead: number;\n readonly rowsWritten: number;\n readonly statementCount: number;\n};\n\n/**\n * One SQLite-compiled statement from the front of a SQL string.\n *\n * `sql` is the exact prefix SQLite consumed, including trigger bodies. Keeping\n * that boundary on the backend is what prevents JavaScript from inventing a\n * second, subtly different SQL grammar.\n */\nexport interface SqlDatabaseStatement {\n readonly sql: string;\n readonly parameterCount: number;\n execute(params: readonly SqlValue[]): SqlResult;\n close(): void;\n}\n\nexport const SQL_WRONG_BINDINGS_MESSAGE = \"Wrong number of parameter bindings for SQL query.\";\n\n/** ← `SQLITE_LIMIT_LENGTH`, raised from 2.2 MB to 4 MiB in workerd 2026-08-20. */\nexport const SQLITE_LENGTH_LIMIT = 4 * 1024 * 1024;\n\nexport const SQLITE_TOOBIG_MESSAGE = \"string or blob too big: SQLITE_TOOBIG\";\n\nconst textEncoder = new TextEncoder();\n\n/** The part of `sqlite3_limit(SQLITE_LIMIT_LENGTH)` visible at the JS binding seam. */\nexport function requireSqliteLength(value: unknown): void {\n const length =\n typeof value === \"string\"\n ? textEncoder.encode(value).byteLength\n : value instanceof Uint8Array\n ? value.byteLength\n : 0;\n if (length > SQLITE_LENGTH_LIMIT) throw new Error(SQLITE_TOOBIG_MESSAGE);\n}\n\nexport const SQL_PRELUDE_BINDINGS_MESSAGE =\n \"When executing multiple SQL statements in a single call, only the last statement can have \" +\n \"parameters.\";\n\n/**\n * One open database. Synchronous exec, matching every substrate we have: in a\n * SQLite-backed Durable Object reads return a value rather than a promise\n * (§1.4), which is what makes the input gate cheap.\n */\nexport interface SqlDatabase {\n /** Compile exactly the first statement, using SQLite's own statement boundary. */\n prepare(sql: string): SqlDatabaseStatement;\n exec(sql: string, params: readonly SqlValue[]): SqlResult;\n readonly databaseSize: number;\n /**\n * ← `sqlite3_get_autocommit(db) == 0`, which is how upstream's\n * `handleCriticalError` learns that SQLite rolled a transaction back on its\n * own (`sqlite.c++:669-691`).\n *\n * SQLite auto-rolls-back on `SQLITE_FULL`, `SQLITE_IOERR`, `SQLITE_NOMEM` and\n * `SQLITE_INTERRUPT`. Nothing announces it, so without this the savepoint\n * stack above would keep believing a transaction is open and the rollback\n * callbacks would never fire — a stale cache with nothing thrown, which is\n * the one failure this layer must never produce.\n */\n readonly inTransaction: boolean;\n /**\n * ← `SqliteDatabase::reset()` — \"delete the underlying database file and\n * create a new one in its place\", which is how upstream implements\n * `deleteAll()`.\n *\n * On the backend rather than above it because only the backend knows how to\n * recreate its own file, and because the alternative — enumerating and\n * dropping every table — is the fragile dance today's `storage.ts` performs,\n * complete with an FTS5 shadow-table ordering hazard its comment documents.\n * The `SqlDatabase` reference stays valid across the call; what changes is\n * the file behind it.\n */\n reset(): void;\n close(): void;\n}\n\n/**\n * Opens databases within ONE actor's storage scope. The package derives the\n * names (`\"root\"`, `` `facet-${facetId}` ``); the host maps them onto files.\n * OPFS layout knowledge stays with the host — this package never reaches for\n * `navigator.storage`.\n */\nexport interface SqlDatabaseProvider {\n open(name: string): Promise<SqlDatabase>;\n}\n\n/** A portable, host-owned image of every SQLite database in one actor storage scope. */\nexport type SqlDatabaseSnapshot = {\n readonly version: 1;\n readonly databases: readonly {\n readonly name: string;\n readonly image: Uint8Array;\n }[];\n};\n\n/** Local backup/restore. This is deliberately not Cloudflare's time-indexed PITR service. */\nexport interface SqlDatabaseSnapshotProvider extends SqlDatabaseProvider {\n /** Close every database opened through this provider before snapshot or placement teardown. */\n close(): void;\n exportSnapshot(): Promise<SqlDatabaseSnapshot>;\n importSnapshot(snapshot: SqlDatabaseSnapshot): Promise<void>;\n}\n\nconst SAFE_DATABASE_NAME = /^[A-Za-z0-9_-]+$/;\nconst SQLITE_HEADER = \"SQLite format 3\";\n\nexport function requireSafeDatabaseName(name: string): void {\n if (!SAFE_DATABASE_NAME.test(name)) {\n throw new Error(`Database name is not a safe file name: ${name}`);\n }\n}\n\n/** Validate the complete snapshot before a backend replaces any files. */\nexport function requireValidSqlDatabaseSnapshot(snapshot: SqlDatabaseSnapshot): void {\n const candidate: unknown = snapshot;\n if (\n candidate === null ||\n typeof candidate !== \"object\" ||\n !(\"version\" in candidate) ||\n candidate.version !== 1 ||\n !(\"databases\" in candidate) ||\n !Array.isArray(candidate.databases)\n ) {\n throw new Error(\"Unsupported SQLite snapshot format.\");\n }\n const names = new Set<string>();\n for (const database of candidate.databases) {\n if (\n database === null ||\n typeof database !== \"object\" ||\n !(\"name\" in database) ||\n typeof database.name !== \"string\" ||\n !(\"image\" in database)\n ) {\n throw new Error(\"SQLite snapshot contains an invalid database entry.\");\n }\n const { name, image } = database;\n requireSafeDatabaseName(name);\n if (names.has(name)) {\n throw new Error(`SQLite snapshot contains duplicate database name: ${name}`);\n }\n names.add(name);\n if (\n !(image instanceof Uint8Array) ||\n image.byteLength < 512 ||\n image.byteLength % 512 !== 0 ||\n [...SQLITE_HEADER].some((character, index) => image[index] !== character.charCodeAt(0))\n ) {\n throw new Error(`Snapshot entry ${name} is not a valid SQLite database image.`);\n }\n }\n}\n\n/**\n * ← `SqliteDatabase::QueryOptions`. The C++ regulator pointer is narrowed to\n * a callback over the exact SQL source SQLite compiled; `api/sql.ts` owns the\n * policy because this backend seam owns no public-API knowledge.\n *\n * `allowUnconfirmed`'s only destination is `onWrite(bool allowUnconfirmed)`,\n * which fires *before* the statement executes so the automatic transaction\n * opens first — see `isWrite` below for how a statement is known to be a write\n * without the compiled plan upstream reads it from.\n */\nexport type QueryOptions = {\n allowUnconfirmed?: boolean;\n /** The public SQL regulator, run once against each SQLite-compiled statement. */\n regulate?: (sql: string) => void;\n};\n\n/**\n * ← the state `SqliteDatabase::onCriticalError` reports and\n * `observedCriticalError()` latches.\n *\n * Raised when SQLite has rolled back an open transaction on its own. Upstream\n * hands this to `ActorSqlite`, which treats it as fatal; §1.6 is why — a\n * storage failure this severe destroys the object rather than being survived.\n * Until `io/actor-sqlite.ts` wires it to `onBroken`, latching it and refusing\n * every subsequent statement is what keeps a caller from reading through a\n * cache that is knowingly wrong.\n */\nexport class SqliteCriticalError extends Error {\n override readonly name = \"SqliteCriticalError\";\n}\n\n/**\n * ← `SqliteDatabase::ResetListener`.\n *\n * Upstream's is a base class whose constructor registers and whose destructor\n * unregisters. JS has neither, so registration is the explicit\n * `db.addResetListener(this)` call — the same translation Section 1 applied to\n * every kj destructor.\n */\nexport interface ResetListener {\n /** Called before the database is actually reset. */\n beforeSqliteReset(): void;\n}\n\n/** ← `SqliteDatabase::Query::isNull(uint column)`. */\nexport function isNull(row: readonly unknown[], column: number): boolean {\n return row[column] === null || row[column] === undefined;\n}\n\n/** ← `SqliteDatabase::Query::getBlob(uint column)`. Fails closed on any other column type. */\nexport function getBlob(row: readonly unknown[], column: number): Uint8Array {\n const value = row[column];\n if (value instanceof Uint8Array) return value;\n throw new Error(`Expected a BLOB in column ${column}, got ${describe(value)}.`);\n}\n\n/** ← `SqliteDatabase::Query::getText(uint column)`. Fails closed on any other column type. */\nexport function getText(row: readonly unknown[], column: number): string {\n const value = row[column];\n if (typeof value === \"string\") return value;\n throw new Error(`Expected TEXT in column ${column}, got ${describe(value)}.`);\n}\n\n/**\n * ← `SqliteDatabase::Query::getInt64(uint column)`.\n *\n * Narrowed to a safe integer rather than upstream's `int64_t`: a JS number\n * cannot carry the full range, and a silently-rounded row id or alarm time is\n * exactly the kind of corruption this layer must not produce.\n */\nexport function getInt64(row: readonly unknown[], column: number): number {\n const value = row[column];\n if (typeof value === \"number\" && Number.isSafeInteger(value)) return value;\n if (typeof value === \"bigint\") {\n const narrowed = Number(value);\n if (Number.isSafeInteger(narrowed) && BigInt(narrowed) === value) return narrowed;\n }\n throw new Error(`Expected a safe integer in column ${column}, got ${describe(value)}.`);\n}\n\nfunction describe(value: unknown): string {\n if (value === null) return \"NULL\";\n if (value instanceof Uint8Array) return \"a BLOB\";\n return `${typeof value} ${String(value)}`;\n}\n\ntype Savepoint = {\n name: string;\n /** Size of `rollbackCallbacks` when this savepoint was created. */\n rollbackCallbackIndex: number;\n};\n\n/**\n * ← `SqliteDatabase`, restricted to the members `sqlite-kv` and\n * `sqlite-metadata` actually call.\n *\n * The one piece of real machinery here is the transaction/savepoint stack that\n * `onRollback()` needs. Upstream learns of a `BEGIN` / `SAVEPOINT` / `COMMIT` /\n * `RELEASE` / `ROLLBACK` from the SQLite authorizer while the statement is\n * being compiled (`prepareSql` fills a `ParseContext::stateChange`); we have no\n * authorizer, so the statement text is the only source. `applyChange` below is\n * a line-for-line port of upstream's; only where the `StateChange` comes from\n * differs.\n */\nexport class SqliteDatabase {\n readonly #backend: SqlDatabase;\n readonly #resetListeners = new Set<ResetListener>();\n\n /** Callbacks registered with onRollback that haven't been committed nor rolled back yet. */\n #rollbackCallbacks: (() => void)[] = [];\n /** Savepoints that haven't been committed nor rolled back yet. */\n #savepoints: Savepoint[] = [];\n /** True if in a BEGIN TRANSACTION transaction. */\n #inTransaction = false;\n /** ← `criticalErrorOccurred`, holding the exception rather than a bool. */\n #criticalError: SqliteCriticalError | undefined;\n /** ← `onWriteCallback`. */\n #onWriteCallback: ((allowUnconfirmed: boolean) => void) | undefined;\n /** ← `onCriticalErrorCallback`. */\n #onCriticalErrorCallback: ((exception: SqliteCriticalError) => void) | undefined;\n\n constructor(backend: SqlDatabase) {\n this.#backend = backend;\n }\n\n /**\n * Invokes the given callback whenever a query begins which may write to the\n * database. The callback is called just before executing the query.\n *\n * Durable Objects uses this to automatically begin a transaction and close the\n * output gate.\n *\n * Note that the write callback is NOT called before (or at any point during) a\n * `reset()`. Use the `ResetListener` mechanism for that case.\n */\n onWrite(callback: (allowUnconfirmed: boolean) => void): void {\n this.#onWriteCallback = callback;\n }\n\n /**\n * Invokes the given callback when a \"critical error\" causes an automatic\n * rollback during a transaction.\n *\n * See: https://www.sqlite.org/lang_transaction.html#response_to_errors_within_a_transaction\n *\n * Upstream passes `(errorMessage, maybeException)` and lets the caller build\n * the exception; `#checkForAutoRollback` has already built one by the time it\n * can tell a rollback happened, so the callback receives that.\n */\n onCriticalError(callback: (exception: SqliteCriticalError) => void): void {\n this.#onCriticalErrorCallback = callback;\n }\n\n /**\n * Invoke the onWrite() callback.\n *\n * \"This is useful when the caller is about to execute a statement which SQLite\n * considers read-only, but needs to be considered a write for our purposes. In\n * particular, we use the onWrite callback to start automatic transactions, and\n * we use the SAVEPOINT statement to implement explicit transactions. For\n * synchronous transactions, the explicit transaction needs to be nested inside\n * the automatic transaction, so we need to force an auto-transaction to start\n * before the SAVEPOINT.\"\n */\n notifyWrite(allowUnconfirmed = false): void {\n this.#onWriteCallback?.(allowUnconfirmed);\n }\n\n /** ← `SqliteDatabase::run`, in both its bare and its `QueryOptions` form. */\n run(sql: string, ...bindings: SqlValue[]): SqlResult;\n run(options: QueryOptions, sql: string, ...bindings: SqlValue[]): SqlResult;\n run(first: string | QueryOptions, ...rest: SqlValue[]): SqlResult {\n if (typeof first === \"string\") return this.#exec(first, rest, false);\n\n const [sql, ...bindings] = rest;\n if (typeof sql !== \"string\") throw new Error(\"run(options, sql, ...) takes a SQL string.\");\n return this.#exec(sql, bindings, first.allowUnconfirmed ?? false, first.regulate);\n }\n\n /** ← `SqliteDatabase::ingestSql`: execute complete statements, retain the partial tail. */\n ingest(sql: string, regulate?: (sql: string) => void): SqlIngestResult {\n this.assertUsable();\n let remainder = sql;\n let rowsRead = 0;\n let rowsWritten = 0;\n let statementCount = 0;\n\n while (hasSqlStatement(remainder)) {\n let statement: SqlDatabaseStatement;\n try {\n statement = this.#backend.prepare(remainder);\n } catch (error) {\n if (error instanceof Error && /incomplete input/i.test(error.message)) break;\n throw error;\n }\n try {\n // sqlite3_complete_length(), which upstream uses, requires the terminating semicolon.\n if (!statement.sql.trimEnd().endsWith(\";\")) break;\n regulate?.(statement.sql);\n if (statement.parameterCount !== 0) throw new Error(SQL_WRONG_BINDINGS_MESSAGE);\n const result = this.#execStatement(statement, [], false);\n rowsRead += result.rawRows.length;\n rowsWritten += result.rowsWritten;\n statementCount += 1;\n remainder = remainder.slice(statement.sql.length);\n } finally {\n statement.close();\n }\n }\n\n return { remainder, rowsRead, rowsWritten, statementCount };\n }\n\n #exec(\n sql: string,\n bindings: readonly SqlValue[],\n allowUnconfirmed: boolean,\n regulate?: (sql: string) => void,\n ): SqlResult {\n this.assertUsable();\n\n let remaining = sql;\n let result: SqlResult | undefined;\n while (hasSqlStatement(remaining)) {\n const statement = this.#backend.prepare(remaining);\n const tail = remaining.slice(statement.sql.length);\n const isFinal = !hasSqlStatement(tail);\n try {\n regulate?.(statement.sql);\n if (!isFinal && statement.parameterCount !== 0) {\n throw new Error(SQL_PRELUDE_BINDINGS_MESSAGE);\n }\n if (isFinal && statement.parameterCount !== bindings.length) {\n throw new Error(SQL_WRONG_BINDINGS_MESSAGE);\n }\n result = this.#execStatement(\n statement,\n isFinal ? bindings : [],\n isFinal ? allowUnconfirmed : false,\n );\n } finally {\n statement.close();\n }\n remaining = tail;\n }\n if (result === undefined) throw new Error(\"Expected at least one SQL statement.\");\n return result;\n }\n\n /** Execute one statement from `run()`'s batch; bindings and results belong to the last one. */\n #execStatement(\n statement: SqlDatabaseStatement,\n bindings: readonly SqlValue[],\n allowUnconfirmed: boolean,\n ): SqlResult {\n const { sql } = statement;\n const change = classify(sql);\n\n // Before the statement runs, as upstream's `Query::checkRequirements` does: the callback opens\n // the transaction this statement is about to write into, and it is also allowed to refuse the\n // statement outright when whatever owns the transaction is already broken.\n if (isWrite(sql)) this.notifyWrite(allowUnconfirmed);\n\n let result: SqlResult;\n try {\n result = statement.execute(bindings);\n } catch (error) {\n this.#checkForAutoRollback(error);\n throw error;\n }\n // Upstream applies the effect on the statement's first step, i.e. after it\n // has actually run, so a statement that throws changes nothing.\n this.#applyChange(change);\n return result;\n }\n\n /**\n * ← `SqliteDatabase::handleCriticalError`, which reaches the same conclusion\n * from the error code plus `sqlite3_get_autocommit`. We do not see the error\n * code — the backend has already turned it into a JS exception — so the\n * disagreement between our stack and the backend's is the whole signal, and\n * it is enough: we only ask after a statement has failed, and the only thing\n * that closes a transaction without going through `run()` is SQLite itself.\n *\n * The callbacks are DISCARDED rather than invoked, which looks wrong for two\n * lines and is not. Invoking them is only correct when a rollback actually\n * happened, and the branch above is the sole case where that is knowable; in\n * the ordinary case — a constraint violation, which does not roll anything\n * back — firing them would restore a cache the database has moved past, which\n * is corruption in the other direction. Upstream does not fire them here\n * either. It makes the actor fatal instead, and so does this: the caches are\n * knowingly stale, so the database is finished rather than repaired.\n */\n #checkForAutoRollback(cause: unknown): void {\n if (!this.#inTransaction && this.#savepoints.length === 0) return;\n if (this.#backend.inTransaction) return;\n\n this.#inTransaction = false;\n this.#savepoints = [];\n this.#rollbackCallbacks = [];\n const critical = new SqliteCriticalError(\n \"SQLite rolled back the open transaction in response to a critical error, so every \" +\n \"in-memory view of this database is now stale and it can no longer be used.\",\n { cause },\n );\n this.#criticalError = critical;\n this.#onCriticalErrorCallback?.(critical);\n throw critical;\n }\n\n /**\n * ← `SqliteDatabase::observedCriticalError()`. The named state\n * `io/actor-sqlite.ts` wires to `onBroken`, so it does not have to re-derive\n * the condition from an exception it caught.\n */\n observedCriticalError(): SqliteCriticalError | undefined {\n return this.#criticalError;\n }\n\n /**\n * The guard for any read this package serves from a cache rather than from a\n * statement. Those are the only paths a latched critical error would not\n * already stop, and they are exactly the paths whose answer is wrong once\n * SQLite has rolled back underneath them.\n */\n assertUsable(): void {\n if (this.#criticalError !== undefined) throw this.#criticalError;\n }\n\n get databaseSize(): number {\n return this.#backend.databaseSize;\n }\n\n /**\n * ← `SqliteDatabase::onRollback`.\n *\n * \"Register a callback which shall be called if the current transaction is\n * rolled back. If the current transaction commits, then the callback is\n * discarded without invoking it. [...] When a rollback occurs, callbacks are\n * invoked in the reverse of the order in which they were registered.\"\n *\n * With nothing open there is nothing that can roll back, so the callback is\n * dropped — upstream's `if (inTransaction || !savepoints.empty())`.\n */\n onRollback(callback: () => void): void {\n if (this.#inTransaction || this.#savepoints.length > 0) {\n this.#rollbackCallbacks.push(callback);\n }\n }\n\n addResetListener(listener: ResetListener): void {\n this.#resetListeners.add(listener);\n }\n\n removeResetListener(listener: ResetListener): void {\n this.#resetListeners.delete(listener);\n }\n\n /** ← `SqliteDatabase::reset()`. */\n reset(): void {\n // Refused for the same reason `run()` is: the listeners below read their own\n // state on the way out, and after a critical error that state is stale.\n this.assertUsable();\n // \"If transactions are open during reset(), whatever had the transaction\n // open is going to get confused at best, or lose data at worst.\"\n if (this.#inTransaction || this.#savepoints.length > 0) {\n throw new Error(\"can't reset() a database during a transaction\");\n }\n for (const listener of this.#resetListeners) {\n listener.beforeSqliteReset();\n }\n this.#backend.reset();\n }\n\n close(): void {\n this.#backend.close();\n }\n\n /** ← `SqliteDatabase::applyChange`, ported statement for statement. */\n #applyChange(change: StateChange): void {\n switch (change.kind) {\n case \"none\":\n break;\n\n case \"begin\":\n if (change.savepointName !== null) {\n this.#savepoints.push({\n name: change.savepointName,\n rollbackCallbackIndex: this.#rollbackCallbacks.length,\n });\n } else {\n assert(\n this.#savepoints.length === 0,\n \"BEGIN TRANSACTION should have failed when savepoints are present?\",\n );\n assert(\n !this.#inTransaction,\n \"BEGIN TRANSACTION should have failed when already in a transaction?\",\n );\n assert(\n this.#rollbackCallbacks.length === 0,\n \"we shouldn't have been keeping rollback callbacks with no transaction open!\",\n );\n this.#inTransaction = true;\n }\n break;\n\n case \"commit\":\n if (change.savepointName !== null) {\n // Per https://www.sqlite.org/lang_savepoint.html, releasing a savepoint also releases\n // all later savepoints.\n for (;;) {\n const savepoint = this.#savepoints.pop();\n assert(savepoint !== undefined, \"released a savepoint that didn't exist?\");\n if (savepoint.name === change.savepointName) break;\n }\n } else {\n assert(this.#inTransaction, \"COMMIT TRANSACTION without BEGIN TRANSACTION?\");\n // Since BEGIN TRANSACTION cannot be nested within a savepoint, this must have released\n // all savepoints implicitly.\n this.#savepoints = [];\n this.#inTransaction = false;\n }\n if (this.#savepoints.length === 0 && !this.#inTransaction) {\n this.#rollbackCallbacks = [];\n }\n break;\n\n case \"rollback\":\n if (change.savepointName !== null) {\n for (;;) {\n const savepoint = this.#savepoints[this.#savepoints.length - 1];\n assert(savepoint !== undefined, \"released a savepoint that didn't exist?\");\n if (savepoint.name === change.savepointName) {\n this.#runRollbackCallbacksDownTo(savepoint.rollbackCallbackIndex);\n // Rolling back to a savepoint does not release it, so it stays on the stack and\n // must be released separately.\n break;\n }\n this.#savepoints.pop();\n }\n } else {\n assert(this.#inTransaction, \"ROLLBACK TRANSACTION without BEGIN TRANSACTION?\");\n this.#savepoints = [];\n this.#inTransaction = false;\n this.#runRollbackCallbacksDownTo(0);\n }\n break;\n }\n }\n\n #runRollbackCallbacksDownTo(index: number): void {\n assert(this.#rollbackCallbacks.length >= index, \"rollback callback stack shrank?\");\n while (this.#rollbackCallbacks.length > index) {\n // Upstream pops first and then invokes, so a callback that throws does not leave itself on\n // the stack to be invoked a second time by the next rollback.\n const callback = this.#rollbackCallbacks.pop();\n assert(callback !== undefined, \"rollback callback stack shrank?\");\n callback();\n }\n }\n}\n\ntype SqliteSchemaDatabase = Pick<SqlDatabase, \"exec\"> | Pick<SqliteDatabase, \"run\">;\n\n/**\n * Returns whether a runtime-owned table exists, and refuses any present shape\n * other than the one this release writes.\n */\nexport function hasCurrentSqliteTable(\n db: SqliteSchemaDatabase,\n name: string,\n createSql: string,\n): boolean {\n // SQLite identifiers are ASCII case-insensitive, so schema validation must\n // find the same object that CREATE TABLE IF NOT EXISTS would collide with.\n const query = \"SELECT type, sql FROM sqlite_master WHERE name = ? COLLATE NOCASE\";\n const rows = \"run\" in db ? db.run(query, name).rawRows : db.exec(query, [name]).rawRows;\n const row = rows[0];\n if (row === undefined) return false;\n if (\n rows.length !== 1 ||\n row[0] !== \"table\" ||\n typeof row[1] !== \"string\" ||\n normalizeSchemaSql(row[1]) !== normalizeSchemaSql(createSql)\n ) {\n throw new Error(\n `Incompatible @mcp-b/do-runtime storage schema for table \"${name}\". ` +\n \"Runtime storage migrations already brought this database to the \" +\n \"current version, so this is a shape no release produced — the \" +\n \"database is foreign or corrupt. Restore it from a snapshot or \" +\n \"delete it to start over.\",\n );\n }\n return true;\n}\n\nfunction normalizeSchemaSql(sql: string): string {\n return sql\n .replace(/\\bIF\\s+NOT\\s+EXISTS\\b/giu, \"\")\n .replace(/\\s+/gu, \" \")\n .trim()\n .replace(/;$/u, \"\")\n .toLowerCase();\n}\n\nfunction assert(condition: boolean, message: string): asserts condition {\n if (!condition) throw new Error(message);\n}\n\n/** ← `SqliteDatabase::StateChange`. */\ntype StateChange =\n | { kind: \"none\" }\n | { kind: \"begin\"; savepointName: string | null }\n | { kind: \"commit\"; savepointName: string | null }\n | { kind: \"rollback\"; savepointName: string | null };\n\nconst NO_CHANGE: StateChange = { kind: \"none\" };\n\n/** SQLite savepoint names compare case-insensitively, so the stack stores them folded. */\nfunction savepointName(raw: string): string {\n const unquoted =\n (raw.startsWith('\"') && raw.endsWith('\"')) ||\n (raw.startsWith(\"'\") && raw.endsWith(\"'\")) ||\n (raw.startsWith(\"`\") && raw.endsWith(\"`\"))\n ? raw.slice(1, -1)\n : raw.startsWith(\"[\") && raw.endsWith(\"]\")\n ? raw.slice(1, -1)\n : raw;\n return unquoted.toLowerCase();\n}\n\nconst NAME = String.raw`(\"[^\"]*\"|'[^']*'|\\`[^\\`]*\\`|\\[[^\\]]*\\]|[A-Za-z_][A-Za-z0-9_$]*)`;\nconst BEGIN = new RegExp(\n String.raw`^BEGIN(\\s+(DEFERRED|IMMEDIATE|EXCLUSIVE))?(\\s+TRANSACTION)?$`,\n \"i\",\n);\nconst SAVEPOINT = new RegExp(String.raw`^SAVEPOINT\\s+${NAME}$`, \"i\");\nconst COMMIT = new RegExp(String.raw`^(COMMIT|END)(\\s+TRANSACTION)?$`, \"i\");\nconst RELEASE = new RegExp(String.raw`^RELEASE(\\s+SAVEPOINT)?\\s+${NAME}$`, \"i\");\nconst ROLLBACK = new RegExp(String.raw`^ROLLBACK(\\s+TRANSACTION)?$`, \"i\");\nconst ROLLBACK_TO = new RegExp(\n String.raw`^ROLLBACK(\\s+TRANSACTION)?\\s+TO(\\s+SAVEPOINT)?\\s+${NAME}$`,\n \"i\",\n);\nconst TRANSACTION_KEYWORD = /^(BEGIN|COMMIT|END|ROLLBACK|SAVEPOINT|RELEASE)\\b/i;\n\n/** Every group referenced below is mandatory in its pattern, so an absent one is a broken pattern. */\nfunction group(match: RegExpExecArray, index: number): string {\n const value = match[index];\n if (value === undefined) throw new Error(`SQL pattern group ${index} did not match: ${match[0]}`);\n return value;\n}\n\n/**\n * Derives upstream's `StateChange` from the statement text.\n *\n * A statement that opens with a transaction keyword but does not match one of\n * the forms below throws rather than being classified as `NoChange`, since\n * guessing in that direction is what loses a rollback callback. `run()` asks\n * the backend to compile one native statement at a time and applies this state\n * change after each executes, matching workerd's `prepareMulti()` prelude.\n */\nfunction classify(sql: string): StateChange {\n const statement = stripLeadingTrivia(sql).trim().replace(/;$/, \"\").trimEnd();\n\n const rollbackTo = ROLLBACK_TO.exec(statement);\n if (rollbackTo !== null) {\n return { kind: \"rollback\", savepointName: savepointName(group(rollbackTo, 3)) };\n }\n if (ROLLBACK.test(statement)) return { kind: \"rollback\", savepointName: null };\n\n const savepoint = SAVEPOINT.exec(statement);\n if (savepoint !== null) {\n return { kind: \"begin\", savepointName: savepointName(group(savepoint, 1)) };\n }\n if (BEGIN.test(statement)) return { kind: \"begin\", savepointName: null };\n\n const release = RELEASE.exec(statement);\n if (release !== null) {\n return { kind: \"commit\", savepointName: savepointName(group(release, 2)) };\n }\n if (COMMIT.test(statement)) return { kind: \"commit\", savepointName: null };\n\n if (TRANSACTION_KEYWORD.test(statement)) {\n throw new Error(`Unrecognized transaction-control statement: ${statement}`);\n }\n return NO_CHANGE;\n}\n\n/**\n * ← `!sqlite3_stmt_readonly(statement)`, the test upstream's `onWrite` gate is\n * written against (`sqlite.c++:1562-1568`).\n *\n * It is NOT the authorizer — the authorizer never sees this question, and the\n * distinction is load bearing: `sqlite3_stmt_readonly()` reports BEGIN, COMMIT,\n * ROLLBACK, SAVEPOINT and RELEASE as read-only, which is why `notifyWrite()`\n * exists at all and why an automatic transaction's own `BEGIN` does not recurse\n * into the callback that issued it.\n *\n * Neither backend exposes the compiled statement, so the text is the only\n * source, and it fails closed the way `classify` does: **a statement is a write\n * unless it provably is not.** The complete read set is `SELECT` and `EXPLAIN`\n * plus the five transaction-control forms. `WITH`, `PRAGMA` and anything\n * unrecognised are writes, which costs a read-only CTE a transaction and an\n * output-gate lock it does not need, and cannot cost atomicity — which is the\n * only error this classification is allowed to make.\n */\nfunction isWrite(sql: string): boolean {\n return !NON_WRITE_KEYWORDS.has(leadingKeyword(sql));\n}\n\nconst NON_WRITE_KEYWORDS = new Set([\n \"SELECT\",\n \"EXPLAIN\",\n \"BEGIN\",\n \"COMMIT\",\n \"END\",\n \"ROLLBACK\",\n \"SAVEPOINT\",\n \"RELEASE\",\n]);\n\n/** The first bare word, skipping whitespace and both comment forms. */\nfunction leadingKeyword(sql: string): string {\n return (/^[A-Za-z]+/.exec(stripLeadingTrivia(sql))?.[0] ?? \"\").toUpperCase();\n}\n\nfunction hasSqlStatement(sql: string): boolean {\n return stripLeadingTrivia(sql).trim().length > 0;\n}\n\nfunction stripLeadingTrivia(statement: string): string {\n let index = 0;\n for (;;) {\n while (index < statement.length && /\\s/.test(statement.charAt(index))) index += 1;\n if (statement[index] === \"-\" && statement[index + 1] === \"-\") {\n const newline = statement.indexOf(\"\\n\", index);\n index = newline === -1 ? statement.length : newline + 1;\n continue;\n }\n if (statement[index] === \"/\" && statement[index + 1] === \"*\") {\n const close = statement.indexOf(\"*/\", index + 2);\n index = close === -1 ? statement.length : close + 2;\n continue;\n }\n return statement.slice(index);\n }\n}\n","/**\n * Runtime storage versioning. Package-original — workerd has no counterpart,\n * because Cloudflare upgrades workerd and its storage together. This package is\n * an npm dependency over data it does not control: OPFS files in a browser and\n * SQLite files on disk outlive any release, so the release that changes a\n * `_cf_` shape has to bring existing files forward itself.\n *\n * The mechanism is the Cloudflare Agents SDK's `_ensureSchema` pattern moved\n * one layer down: a stored schema version, a cumulative list of forward-only\n * idempotent migration steps, and a fast path that skips everything when the\n * stored version is current. Where an Agent runs this in its constructor\n * (before any event is delivered), the runtime runs it at database open —\n * `createActorContainer()` and `AlarmScheduler`'s constructor — which precedes\n * every event by construction, so no `blockConcurrencyWhile` is involved.\n *\n * The version lives in `PRAGMA user_version`: per database file, carried by\n * snapshots, transactional. Application SQL cannot reach it — `SqlStorage`\n * ports workerd's pragma allowlist (`util/sqlite.c++:539-563`), which does not\n * include `user_version` — so the value is runtime-owned by the same rule that\n * reserves `_cf_` names.\n *\n * Rules for a step, when the first real one is written:\n *\n * - **Forward-only.** Never edit or reorder a shipped step; add the next one\n * and bump `RUNTIME_STORAGE_VERSION`.\n * - **Runtime tables only, guarded on existence.** Every database file holds\n * a different subset of runtime tables (`_cf_KV`/`_cf_METADATA` in an actor\n * database, the facet index and deletion receipts in the root's facet\n * database, `_cf_ALARM` in a scheduler's), creation is lazy, and a database\n * handed to `AlarmScheduler` is host-opened and may hold host tables beside\n * the runtime's — a step must no-op where its table is absent and must\n * never touch a table the runtime does not own.\n * - **Idempotent against the current shape too** (`IF NOT EXISTS`, tolerate\n * \"duplicate column\"): `deleteAll()` resets a file to version 0 while this\n * release recreates its tables at the current shape, so a later chain run\n * can meet already-current tables.\n * - **No transaction control.** The chain and the stamp are one transaction;\n * a step that issues `BEGIN`/`COMMIT`/`ROLLBACK`/`SAVEPOINT` is refused by\n * name below rather than silently splitting it.\n *\n * Spec: decision 19 in docs/decisions.md.\n */\n\nimport { getInt64, type SqlDatabase, type SqlDatabaseSnapshot } from \"./sqlite\";\n\n/**\n * The shape of every runtime-owned table in this release. Bump together with\n * the step that upgrades the previous shape.\n */\nexport const RUNTIME_STORAGE_VERSION = 1;\n\nexport type RuntimeMigration = (db: SqlDatabase) => void;\n\n/** `MIGRATIONS[i]` takes a database from storage version `i + 1` to `i + 2`. */\nconst MIGRATIONS: readonly RuntimeMigration[] = [];\n\n/**\n * Bring one just-opened runtime database to `RUNTIME_STORAGE_VERSION`. Called\n * by every seam that opens a runtime database, before anything reads it, with\n * the database's own name so a refusal says which file it is about. The last\n * two parameters exist for the tests in this module's test file; every real\n * caller takes the shipped defaults.\n *\n * A version newer than this release refuses — the analogue of\n * `hasCurrentSqliteTable`'s refusal, with the one remedy named. A version 0\n * database is from before versioning existed (the same shape as version 1) or\n * a fresh file; both enter the chain at 1. Pending steps and the stamp commit\n * as one transaction, so a failed step leaves the file exactly as it was and\n * the container placement fails with the step's error.\n */\n/**\n * Refuse a snapshot image stamped by a newer release at the import seam, where\n * the operation that brought the file in is the one that fails — instead of at\n * the next placement, far from the cause. `user_version` sits at byte 60 of\n * the SQLite header, big-endian (https://www.sqlite.org/fileformat2.html);\n * callers validate the header shape first (`requireValidSqlDatabaseSnapshot`).\n */\nexport function requireImportableRuntimeStorage(\n snapshot: SqlDatabaseSnapshot,\n current: number = RUNTIME_STORAGE_VERSION,\n): void {\n for (const { name, image } of snapshot.databases) {\n const stored = new DataView(image.buffer, image.byteOffset, image.byteLength).getInt32(60);\n if (stored > current) {\n throw new Error(\n `Snapshot database ${JSON.stringify(name)} was written by a newer @mcp-b/do-runtime ` +\n `(storage version ${stored}; this release supports up to ${current}). ` +\n `Upgrade the package to import it.`,\n );\n }\n }\n}\n\nexport function ensureRuntimeStorageVersion(\n db: SqlDatabase,\n name: string,\n current: number = RUNTIME_STORAGE_VERSION,\n migrations: readonly RuntimeMigration[] = MIGRATIONS,\n): void {\n // `user_version` is a signed 32-bit field in the database header; a target\n // outside it would silently truncate on write.\n if (!Number.isSafeInteger(current) || current < 1 || current > 0x7fff_ffff) {\n throw new Error(`Runtime storage version must be a positive 32-bit integer, got ${current}.`);\n }\n const row = db.exec(\"PRAGMA user_version\", []).rawRows[0];\n if (row === undefined) {\n throw new Error(`PRAGMA user_version returned no row for database ${JSON.stringify(name)}.`);\n }\n const stored = getInt64(row, 0);\n if (stored === current) return;\n if (stored > current) {\n throw new Error(\n `Runtime database ${JSON.stringify(name)} was written by a newer @mcp-b/do-runtime ` +\n `(storage version ${stored}; this release supports up to ${current}). ` +\n `Upgrade the package to open it.`,\n );\n }\n\n if (db.inTransaction) {\n throw new Error(\n `Runtime storage migration for database ${JSON.stringify(name)} began inside an open transaction.`,\n );\n }\n db.exec(\"BEGIN\", []);\n try {\n // A negative stamp can only be a foreign file; it enters at 1 like a fresh\n // one, and `hasCurrentSqliteTable` afterwards refuses any foreign shape.\n for (let from = Math.max(stored, 1); from < current; from++) {\n const step = migrations[from - 1];\n if (step === undefined) {\n throw new Error(`Missing runtime storage migration from version ${from} to ${from + 1}.`);\n }\n step(db);\n if (!db.inTransaction) {\n // The step's own stray COMMIT already persisted its work; failing here\n // keeps the file unstamped so the next run retries the whole chain.\n throw new Error(\n `Runtime storage migration from version ${from} to ${from + 1} closed the migration ` +\n `transaction; a step must not issue BEGIN, COMMIT, ROLLBACK, or SAVEPOINT.`,\n );\n }\n }\n db.exec(`PRAGMA user_version = ${current}`, []);\n db.exec(\"COMMIT\", []);\n } catch (error) {\n // SQLite may have rolled back on its own; roll back only what is still\n // open so nothing masks the step's error (§ SqlDatabase.inTransaction).\n if (db.inTransaction) db.exec(\"ROLLBACK\", []);\n throw error;\n }\n}\n"],"mappings":"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"}
|