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
- SQLite
- Postgres
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
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
transaction opens a
server-side session with a short lease rather than a lock the app holds, and
two consequences follow:
Nesting is refused, not flattened
Nesting is refused, not flattened
A flattened rollback would quietly commit work the caller believed was
undone. So the second
begin is an error.The lease expires
The lease expires
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:
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.
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.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.