refactor(db): Prisma → Drizzle — the fleet has one ORM - #136
Merged
Conversation
Stack-uniformity migration (George: "Drizzle everywhere" for fleet-owned
Postgres apps), following the recipe proven in biaslens#26. Schema parity
with the live tables is byte-exact and proven by normalized pg_dump diff
(scratch replay AND against the production database itself); the deploy
ledger on the box is pre-baselined so the cutover is a no-op for prod data.
- src/lib/db/{schema,client,enums}.ts replace prisma/* + @prisma/client
- every call site rewritten (domain, API routes, pages, scripts, tests)
- drizzle/0000_init + 0001_seed_org1 replay the full schema + seed on a
fresh database (CI integration job now runs drizzle-kit migrate)
- prisma deps, prisma.config.ts, generate steps and docs mentions removed
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WqKqMnHQHSmkGFfc5t7Rxn
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stack-uniformity migration (George, 2026-09-01: "Drizzle everywhere" for fleet-owned Postgres apps). Follows the recipe proven in bitbaum/biaslens#26 — solon is the first follow-up repo with a live database (real governance data: 1 org, 2 votes, 1 proposal — nothing here may touch it).
Call sites (before → after)
solon's Prisma surface was ~10× biaslens's: 37 files imported
@prisma/clientor calledprisma.*. All rewritten, now zero:src/lib/db.ts→src/lib/db/client.tsdrizzle(new Pool(...))proxy (hirnli house pattern; build/CI stays hermetic)src/lib/db/schema.ts(new)relations(),$inferSelectrow typessrc/lib/db/enums.ts(new)MemberType.HUMAN === "HUMAN");z.enum(DecisionCategory)keeps working.methods/prisma-enum.tsrenameddb-enum.tsfindUnique/include→db.query.*.findFirst/with,$transaction→db.transaction,upsert→onConflictDoUpdate,count()→select({count()})/$count,Prisma.Decimal→ numeric-as-string (Number()at the same call sites that already did it),Prisma.JsonValue/InputJsonValue/DbNull→unknown/nullwheres becameinArray(col, subquery); repeated org-by-slug lookup extracted toorgBySlug()in domain/org.ts (DRY — 4 routes used it)?? nullwhere Prisma returned null and Drizzle returns undefined$disconnect()→db.$client.end()db.insert(...).returning(); enum imports repointedgrep -rni prismanow hits only the lockfile (drizzle-orm's own optional peers), the schema's provenance comments, and AGENTS.md's history note.Schema parity — proven, not asserted
src/lib/db/schema.tsmatches the tables Prisma's migrations created, byte-for-byte: table/column names,TIMESTAMP(3),NUMERIC(10,2)/(12,2),VARCHAR(90), PascalCase enum type names ("MemberType"), Prisma's index/constraint names (organizations_slug_key,members_organization_id_fkey, …), cascade rules, and client-side uuid minting ($defaultFn(randomUUID)— the columns carry no DB default, exactly like@default(uuid())).Evidence 1 (replay): scratch Postgres 16 — Prisma's
0_init + 1_seed_org1 + 2_voting_methodsinto db A,drizzle-kit migrate(0000_init + 0001_seed_org1) into db B, normalizedpg_dump --schema-onlydiff: 50/50 statements identical. Only spelling differences normalized away:CURRENT_TIMESTAMPvsnow()(same Postgres function) and column order (Prisma's ALTERs appended columns; irrelevant on the live DB, which 0000 never touches). Seed data equal in both (orangecatorg,allocation_policyv1, 2 genesis audit events).Evidence 2 (production):
pg_dump --schema-onlyof the livesolondatabase vs the drizzle-built scratch DB: identical — the only extra statement on prod isCOMMENT ON SCHEMA public IS ''.Evidence 3 (behavior): all 3 integration specs (vote spine, dot allocation, founding seat — 10 tests) green against the drizzle-built scratch DB, plus an HTTP smoke of the built standalone server:
GET /api/orgs/orangecatreturns the seeded org, audit 200, missing org 404.Live-DB cutover mechanism (why the deploy is a no-op)
Deploys run
fleetcrown/scripts/hetzner/apply-schema.sh, which for drizzle-layout apps applies only migrations not yet recorded in the app'spublic._deploy_schema_historyledger — and refuses to auto-baseline a populated database. So before this PR merges, the ledger on bitbaum was seeded by hand (after proving prod schema ≡ drizzle schema, Evidence 2):✅ already applied on the box (
solondatabase). The deploy's schema step therefore finds both tags recorded, applies nothing, and ships code only._prisma_migrationsis intentionally left in place until this deploy is verified live (a rollback deploy of a Prisma-era release would still need it); it gets dropped right after verification.Recipe deltas vs biaslens
drizzle.__drizzle_migrationsinsert because prod is applied byapply-schema.sh, notdrizzle-kit migrate; drizzle's own journal only matters for fresh DBs, i.e. CI).0001_seed_org1so CI's fresh-DB replay stays "baseline + seed", same as before.enums.tsthe pgEnums are built from (one SSOT, no drift possible, client-bundle safe).@mapped — no PascalCase table/column quoting needed (only enum type names keep PascalCase).Number()calls the code already had.prisma.config.ts(Prisma 7 driver-adapter era) deleted alongside the classic artifacts; CI's twoprisma generatesteps and theprisma migrate deployreplay became zero codegen +drizzle-kit migrate;drizzle/metaadded to.prettierignore(generated, drizzle-kit owns it).Verification
npm run verify(prettier + eslint + tsc + design:check + 82 unit tests) andnpm run buildgreen. Integration: 10/10 against a fresh drizzle-migrated Postgres. Live smoke via standalone build. Dependabot PRs #129/#132 (@prisma/*) become obsolete and will be closed on merge.🤖 Generated with Claude Code
https://claude.ai/code/session_01WqKqMnHQHSmkGFfc5t7Rxn