Database
Turso is hosted SQLite. Local dev uses a plain file — same SQL, zero accounts.
# localTURSO_DATABASE_URL="file:./local.db"# productionTURSO_DATABASE_URL="libsql://your-db.turso.io"TURSO_AUTH_TOKEN="your-token"Schema
Section titled “Schema”migrateSchema() in shared/lib/db.ts creates 8 tables: users, sessions, orgs, members, projects, orders, licenses, share_claims. The schema test runs the real migration against a fresh file DB — any drift fails loudly.
Run it against your own database with:
npm run db:migrateEvery statement is CREATE TABLE IF NOT EXISTS, so it is safe to re-run: it adds what is missing and leaves existing data alone. It reads TURSO_DATABASE_URL and TURSO_AUTH_TOKEN from the environment, falling back to .env. On Cloudflare Pages run it from a machine that holds those same values — Pages Functions do not run migrations, and the same TURSO_DATABASE_URL works on the edge either way (libsql:// is normalized to https:// for the Workers client).
Commerce tables:
orders— one row per successful payment: email, tier, product, amount, status (paid/refunded),coupon_code,cashback_forbidden,waiver.licenses— current entitlement per buyer: license key (unique), tier, seats,updates_until(YYYY-MM-DDorlifetime).
Rules enforced in SQL
Section titled “Rules enforced in SQL”- Tier is CHECK-constrained to
starter/pro/pro_plus— garbage tiers rejected at the DB. Those are the internal ids for Solo, Team & Studio, and Agency. - License keys are UNIQUE.
- Fulfillment is idempotent: redelivered webhooks return the existing license, never a second row.