Database
Every project's database lives inside its runtime: a database whose shape comes from your committed schema, whose only reader and writer is a function holding the db capability, and whose state — local, per preview, and live — is isolated per namespace. There is no database server to connect to and no client-facing query API to secure.
The queries your functions run are in-process reads against local state — no connection pool, no network hop, no N+1 latency cliff. That is not a toy configuration: you get ordinary SQL and real transactional semantics without the operational surface of a database server, and hosted state is continuously replicated to durable storage rather than living on one disk.
It also keeps the whole backend portable. Your data sits under .snoozestack/ next to your storage and signals — one directory that runs identically on your machine, in a container, and hosted, and one directory to copy when you want your data somewhere else. It is a genuine exit, not an export in someone's bespoke shape.
Reading and writing it#
A function granted the db capability gets one method — a parameterized, single-statement query:
export default async function handler(req: Request, capabilities) { const user = capabilities.auth.user; if (!user) return new Response("unauthorized", { status: 401 }); const { rows, columns } = capabilities.db.query( "select id, body, created_at from notes where user_id = ? order by created_at desc", [user.id], ); return Response.json({ columns, rows });}Writes return { changes, lastInsertRowid } instead of rows. One statement per call is enforced — a second ;-separated statement is an error, not a silent no-op — which is what makes string-building attacks against this surface boring. Always pass values as params, never interpolated into the SQL.
| Surface | Availability |
|---|---|
| Portal UI | Read-only Database → Schema (tables, columns, indexes) and Database → Data (browse rows) — in both the hosted console and the local dashboard |
| CLI | snoozestack schema build/push, snoozestack migration generate — the schema is files; see Schema & migrations |
| SDK / HTTP | No direct database client, by design — apps call a function with functions.invoke() |
Where the data lives#
- Locally:
.snoozestack/dev.db, built from your schema on first run and kept in sync as you edit it.snoozestack dev resetthrows it away and rebuilds (re-running your committedseed.mjs, if any). - Hosted: inside the project's runtime container, on a volume replicated continuously to durable storage — restore and host-failure recovery are platform responsibilities, not yours.
- Per namespace: live (
default) and each named preview hold separate databases, so a preview publish can be exercised against its own data.
Your schema isn't locked in#
The defineTable() vocabulary is deliberately portable: every type has a defined mapping in more than one SQL engine, so the schema describes your data rather than an engine. If your product ever outgrows what the runtime provides, the schema — and the ordinary SQL your functions already write — travels with it.