Durable Object SQL Tenant Database
One SQLite database per tenant, living inside the Durable Object that serves that tenant, so the compute sits on top of the storage instead of a connection away from it. Serverless could only express this as a WHERE clause on one shared database, where every read pays a round trip and isolation is a code review promise; here a query is a synchronous call into storage that is physically part of the object, joins and aggregates cost microseconds, and there is no pool, no connection ceiling, and no way for one tenant's query to reach another's rows. The tenant file runs its migrations inside blockConcurrencyWhile, which is the only place that holds every inbound event including alarms and RPC, wraps each migration and its version row in transactionSync, and documents the cursor rule that bites everyone once: sql.exec returns a lazy cursor that the next exec invalidates, so toArray comes before the next query and raw() streams an export without materializing the table. It states the platform limits in place rather than in a runbook: 10 GB per object, 100 columns per table, 2 MB per row, 100 KB per statement, 100 bound parameters. The recovery file is the operation a shared Postgres cannot perform at all, restoring a single tenant to a point in time with getBookmarkForTime and onNextSessionRestoreBookmark, including the part everyone misses, that arming a restore does nothing until the object restarts and ctx.abort is what causes that, and the undo bookmark that has to be captured and stored outside the object before the rollback runs.
npx shadcn@latest add https://ui.aryank.space/r/durable-object-sql-tenant-db.jsonInstalls from ui.aryank.space. To add it by hand, copy the files in Files below, or register the @compronents namespace via the docs.
Every tenant lives in one shared table, so isolation is a WHERE clause that every single query must remember. This one forgot. The response ships tenant B's invoices to tenant A, and no type checker or review caught it because the query is syntactically fine.
@cloudflare/[email protected]