context.db is not a connection. A microVM is replaceable, so the database lives outside it and the app is given no host, no path, and no credential to leak. Statements travel over the VM’s private link to the control plane carrying the deployment’s own workload token, and the control plane resolves that token to exactly one environment’s database. Because the binding is per environment and not per VM, a rollout, a rollback, and a sleep/wake cycle all see the same data — and a superseded deployment’s token stops working the moment the route moves.

Declare one

Always available — no operator configuration at all. One file per environment, held by the control plane, snapshotted with VACUUM INTO.It allows one open write transaction per environment at a time, which is what SQLite is. A second begin is refused with that explanation rather than blocking.Placeholders are ?.
Asking for an engine the control plane does not have is a plan-time refusal, never a silent substitution into the other one.

Query

Four methods: query, get, execute, and transaction. Parameters are always bound, never interpolated, whichever engine is behind the binding. Write the placeholder syntax your engine uses.

Transactions

A failed transaction rolls back automatically. transaction opens a server-side session with a short lease rather than a lock the app holds, and two consequences follow:
A flattened rollback would quietly commit work the caller believed was undone. So the second begin is an error.
A transaction left open past the timeout is rolled back and its session reclaimed, so a crashed guest cannot hold a write lock or a pooled connection indefinitely.Keep transactions short, and never wait on a slow remote call inside one you need to commit — a capability invocation inside a transaction is the classic way to lose the write.

Types over the wire

Rows reach your app as JSON over the private link, so values arrive as the JSON type that can hold them without losing anything. That sometimes means a string where you might expect something richer:
date is the one that bites. new Date('2026-08-22') is UTC midnight and renders as the day before anywhere west of Greenwich. Compare and store a calendar day as text.
Binary goes the other way as itself: pass a Buffer or Uint8Array as a parameter and it is bound as bytes, not as text.

Migrations

migrations names a directory of ordered .sql files. They are applied by the reconciler before the candidate microVM is health-checked.
Once a migration has been applied, changing its contents is a deployment error. Add a new migration instead.
Migrations are read from the uploaded source, not from the build output, so a build.output that excludes them is fine. A migration that drops or rewrites data triggers the DESTRUCTIVE_MIGRATION approval gate in production.

Request bounds

The platform bounds what one request may ask for: statement size, parameter count, result rows, and result bytes.
A query that would return more than the row limit is refused rather than streamed. Add a LIMIT and paginate. Exceeding a bound returns RESOURCE_LIMIT_EXCEEDED, never a truncated answer that looks like a complete one.
Exact values are in Limits.

Snapshots and archives

tiny archive snapshots the database before taking the app offline. SQLite uses VACUUM INTO; Postgres shells out to pg_dump/pg_restore, so postgresql-client must be installed on the control-plane host for Postgres snapshots to be captured. retention on the declaration records how long data is kept. See Lifecycle and Backups.

The HTTP surface underneath

The SDK is a thin client over five routes that authenticate with a workload token alone — no organization is named in the path, because the token is what decides which database the caller may address. See Data routes.