Manifesto
SQL is the source of truth for your database
Schema ownership
Most JavaScript/TypeScript database tools use a schema-first model: define your schema in TypeScript, generate SQL from it. TypeScript becomes the source of truth.
Every schema change requires computing a diff between two TypeScript representations and producing correct SQL. This diff computation is non-trivial. Tools handling it well require significant engineering, and still need manual intervention in edge cases. More fundamentally, you're delegating database decisions to tooling you don't control.
The dev/prod split
Prisma and Drizzle split development and production workflows. Prisma has separate migrate dev (shadow database, auto-generates migrations) and migrate deploy (applies files only) commands. Drizzle's push command (bypasses files, applies diffs directly) is recommended for rapid prototyping but doesn't match production workflows.
The schema-first model requires this split because you need a development mechanism for schema evolution without running the full migration pipeline. This means what you do locally differs structurally from production. Generated migrations reflect TypeScript deltas, not actual intent, potentially dropping and recreating columns where renames were intended.
SQL first
Treat SQL migration files as the source of truth from the start. Same files, same order, same command in development and production.
Write SQL, generate TypeScript types. Migrations are the first artifact. Types are derived.
One workflow, not two. The first migration looks identical to the hundredth. SQL itself doesn't change. The spec is stable, implemented consistently, and readable for decades regardless of what tools replace Damian.
Damian approach
You write migration files in SQL. Damian replays them against an in-memory database and generates TypeScript types. Write queries in SQL using type-safe helpers that bind to derived types.
No push command. No separate dev workflow. The same command runs migrations locally and in production.
For cases where inferred types aren't right, declare custom typings per column. The override is explicit, versioned with code, and survives regeneration.
Workflow:
- Write a migration in SQL
- Run the migration
- Generate TypeScript types
- Write queries in SQL with type-safe helpers
- Seed with populators; reset to clean state anytime
Types follow schema. Schema is SQL. Always in direct contact with what the database does.