# 10. Database — Turso (SQLite) Connection, Migrations, and Seed Data meta: id: kordant-unified-restructure-10 feature: kordant-unified-restructure priority: P0 depends_on: [kordant-unified-restructure-09] tags: [backend, database, drizzle, turso, sqlite] objective: - Set up the database connection layer, migration tooling, and seed data for the unified monolith. Configure Turso (libsql/SQLite) as the database backend, matching the production database used by the legacy `packages/db`. deliverables: - `web/src/server/db/index.ts` — Database connection module: - Creates Turso (libsql) client using `@libsql/client` - Exports `db` instance initialized with Drizzle ORM - Handles connection URL and auth token from environment variables - Graceful shutdown hook to close client - `web/drizzle.config.ts` — Drizzle Kit configuration: - Points to `src/server/db/schema/index.ts` - Specifies `turso` dialect - Reads database URL and auth token from env - `web/src/server/db/migrate.ts` — Migration runner script: - Programmatically runs pending migrations on startup - Can be called from `entry-server.tsx` or a standalone script - `web/src/server/db/seed.ts` — Seed script: - Creates sample users, subscriptions, watchlist items, alerts, blog posts - Idempotent: can be run multiple times without duplicates - Useful for development and demo environments - Environment configuration: - `DATABASE_URL` and `DATABASE_AUTH_TOKEN` in `.env` and `.env.example` steps: 1. Install dependencies in `web/`: - `drizzle-orm`, `drizzle-kit` - `@libsql/client` (Turso/libsql client) 2. Create `web/src/server/db/index.ts`: ```ts import { createClient } from '@libsql/client'; import { drizzle } from 'drizzle-orm/libsql'; import * as schema from './schema'; const client = createClient({ url: process.env.DATABASE_URL, authToken: process.env.DATABASE_AUTH_TOKEN, }); export const db = drizzle(client, { schema }); ``` 3. Update `web/drizzle.config.ts`: ```ts import { defineConfig } from 'drizzle-kit'; export default defineConfig({ schema: './src/server/db/schema/index.ts', out: './drizzle', dialect: 'turso', dbCredentials: { url: process.env.DATABASE_URL!, authToken: process.env.DATABASE_AUTH_TOKEN, }, }); ``` 4. Create `web/src/server/db/migrate.ts`: - Import `migrate` from `drizzle-orm/libsql/migrator` - Run migrations from `./drizzle` folder - Log success or error 5. Create `web/src/server/db/seed.ts`: - Use `db.insert()` to populate tables - Create: 2-3 users, 1 family group, 2 subscriptions (basic + premium), 3-5 watchlist items, 2-3 exposures, 5-10 alerts, 3 blog posts, 2 property watchlist items, 1 removal request - Use deterministic IDs or check for existing data before inserting 6. Add `db:migrate`, `db:generate`, `db:push`, `db:seed` scripts to `web/package.json`. 7. Create `.env.example` in `web/` with `DATABASE_URL=libsql://...` and `DATABASE_AUTH_TOKEN` 8. Test locally: - Create a Turso database (or use local SQLite with `libsql://./dev.db`) - Run `pnpm db:generate` to create migration SQL - Run `pnpm db:push` to apply schema - Run `pnpm db:seed` to populate data - Verify tables exist with correct data using `turso db shell` or a SQLite GUI tool 9. Update `web/src/server/db/schema.ts` from task 09 if any adjustments are needed based on migration output. steps: - Integration: `pnpm db:generate` produces valid SQL - Integration: `pnpm db:push` creates all tables in local Turso/SQLite - Integration: `pnpm db:seed` populates tables without errors - Integration: Application can query seeded data via `db.select()` acceptance_criteria: - [ ] `web/src/server/db/index.ts` exports a working Drizzle `db` instance with Turso client - [ ] `drizzle.config.ts` is correctly configured for Turso (dialect: 'turso') - [ ] `pnpm db:generate` creates migration files in `web/drizzle/` - [ ] `pnpm db:push` applies schema to a Turso database successfully - [ ] `pnpm db:seed` populates all relevant tables with sample data - [ ] The app can perform `db.select().from(users)` and return seeded users - [ ] Environment variables are documented in `.env.example` validation: - `cd web && pnpm db:generate` — check `drizzle/` folder for SQL files - `cd web && pnpm db:push` — verify tables created via `turso db shell "SELECT name FROM sqlite_master WHERE type='table';"` - `cd web && pnpm db:seed` — verify data exists via `turso db shell "SELECT COUNT(*) FROM users;"` - Create a temporary test route that queries `db.select().from(users)` and renders results notes: - We are using Turso (libsql/SQLite) as the database backend with Drizzle ORM. - For local development, you can use a local SQLite file (`libsql://./dev.db`) or create a Turso database. - Turso provides edge-distributed SQLite with fast read replicas — no connection pooling needed. - Migration files should be committed to git so all environments run the same migrations. - The seed script should be idempotent. Use `INSERT OR IGNORE` or check existence before inserting. - SQLite doesn't support `DROP COLUMN` or `ALTER TABLE` extensively — plan schema changes carefully or use Turso's rewrap for migrations.