refactor(db): Prisma → Drizzle — the fleet has one ORM - #26
Merged
Conversation
Stack-uniformity migration (recipe-setter for reparaturbonus-zh, solon, aoz-housing). src/lib/db/schema.ts is now the SSOT for domain types; drizzle/0000 recreates byte-identical tables (proven by normalized pg_dump diff against Prisma's init migration on a scratch Postgres). Existing DBs need only a baseline mark — no structural change. - schema.ts: 7 tables, exact Prisma names (tables, columns, indexes, FK constraints incl. cascade rules); cuid ids and updatedAt minted client-side, exactly as Prisma did - client.ts: lazy pg Pool singleton (hermetic build/CI, hot-reload safe) - route rewritten prisma.claim.findUnique -> db.query.claims.findFirst - CI: prisma-generate step deleted (Drizzle has no codegen) - prisma/ dir, @prisma/client + prisma deps, prisma scripts: gone Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WqKqMnHQHSmkGFfc5t7Rxn
github-actions Bot
pushed a commit
to bitbaum/reparaturbonus-zh
that referenced
this pull request
Sep 1, 2026
Stack-uniformity migration (George, 2026-09-01: "Drizzle everywhere"), following the recipe proven in bitbaum/biaslens#26. - src/lib/db/schema.ts: exact-parity schema (Prisma's table/column/enum/ constraint names pinned; cuid2 ids + $onUpdateFn replacing client-side @default(cuid()) / @updatedat) - src/lib/db/index.ts: lazy pg Pool + drizzle() singleton (build-hermetic) - 11 query call sites rewritten (auth, register, admin stats/activity, bonus-codes ×3, shops, test, sitemap, shop metadata layout) - NextAuth PrismaAdapter dropped: unused under credentials + JWT strategy - seed/upsert scripts ported to scripts/db/; prisma/, prisma.config.ts, src/generated/, all 4 prisma deps and CI prisma steps deleted - drizzle/0000 baseline: pg_dump-diff-proven identical to Prisma's 0_init on scratch Postgres 16 (only ORM bookkeeping tables differ) Claude-Session: https://claude.ai/code/session_01WqKqMnHQHSmkGFfc5t7Rxn Co-authored-by: Mao Nakamoto <41178744+maonakamoto@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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). biaslens is the recipe-setter — smallest of the four Prisma repos; reparaturbonus-zh, solon, and aoz-housing follow this pattern.
Call sites (before → after)
Prisma surface was small and is now zero:
src/lib/db/prisma.tsPrismaClientsingletonsrc/lib/db/client.ts(lazypgPool +drizzle(), fleet house pattern from hirnli)src/app/api/claims/[id]/verify/route.tsprisma.claim.findUnique({ select: { evidence: … } })db.query.claims.findFirst({ columns: {}, with: { evidence: { columns: { stance: true } } } })— same shape, same over-fetch disciplinesrc/lib/domain/claim-verification.tsimport type { Evidence } from '@prisma/client'import type { Evidence } from '../db/schema'($inferSelect)route.test.ts@/lib/db/prisma/findUnique@/lib/db/client/db.query.claims.findFirst(missing row =undefined, matching Drizzle)1 query call site rewritten, 1 client module replaced, 1 type import repointed, 1 test mock retargeted. No seed script existed.
grep -rni prismaover the tree now hits only the lockfile (drizzle-orm's own optional peers), historical comments in the parity note, and the README history line.Schema parity — proven, not asserted
The DB was shaped by Prisma's
20260714233936_init; the Drizzle schema matches those tables, it does not reinvent them. Every table, column, index, and FK constraint name is byte-identical ("Outlet","outletId","Article_outletId_idx","Article_outletId_fkey", cascade rules included). Client-side behavior is preserved too: cuid ids minted in the app ($defaultFn(createId)— Prisma's@default(cuid())was also client-side, the columns carry no DB default) andEditorialDna.updatedAtset via$onUpdate, as@updatedAtwas.Evidence: on a scratch Postgres 16 (docker), applied Prisma's original
migration.sqlto db A anddrizzle/0000(vianpm run db:migrate) to db B, then diffed normalizedpg_dump --schema-onlyoutput. The only difference is Drizzle's own bookkeeping (CREATE SCHEMA drizzle;+ its__drizzle_migrationstable vs Prisma's_prisma_migrations); theDEFAULT CURRENT_TIMESTAMPvsDEFAULT now()rendering is the same Postgres function. All 7 application tables identical.Live smoke (same scratch DB,
next buildoutput served):GET /api/claims/c1/verifyover seeded evidence returned{"status":"supported","confidence":0.667,"inputs":{"supports":2,"contradicts":1,"total":3}}; missing id returned the structured 404.Existing-DB baseline
drizzle/0000recreates the schema for fresh databases. A database already shaped by Prisma must not run it — mark it applied instead (Drizzle journals in schemadrizzle, table__drizzle_migrations):biaslens itself is CI-only (no deploy pipeline, CI provisions no DB — tests mock the client,
next buildis hermetic), so no live database needed baselining here. The three follow-up repos with real DBs will need the baseline step above wired into their deploy path.Verification
npm run verify(format:check + lint + typecheck + 17/17 tests) andnpm run buildgreen locally. CI'sprisma generatestep is deleted — Drizzle has no codegen, types flow fromsrc/lib/db/schema.tsat typecheck time. Note:npm auditreports 4 moderate advisories insidedrizzle-kit's bundled dev-only toolchain (@esbuild-kit/*) — devDependency, same version the fleet (hirnli) already runs, no audit gate in CI.The recipe (for reparaturbonus-zh, solon, aoz-housing)
prisma/schema.prisma; grep@prisma/client+PrismaClientusage; list every query call site and the seed script.src/lib/db/schema.tsmatching the existing tables (Prisma's migrations are the ground truth): exact table/column names (Prisma quotes PascalCase/camelCase — keep them),timestamp(N)/doublePrecision/jsonbper the SQL,uniqueIndex/index/foreignKey({name})with Prisma's generated names, cascade rules. Reproduce client-side behaviors:@default(cuid())→$defaultFn(() => createId())(@paralleldrive/cuid2),@updatedAt→$defaultFn+$onUpdate. Addrelations()mirrors and$inferSelecttype exports.src/lib/db/client.ts— lazydrizzle(new Pool(...), { schema })proxy (build/CI-hermetic, hot-reload-safe), the single DB door.drizzle.config.ts(schema →./src/lib/db/schema.ts, out →./drizzle);npx drizzle-kit generatefor the 0000 baseline.drizzle-kit migrate→ db B, diff normalizedpg_dump --schema-only. Only bookkeeping tables may differ.findUnique/findFirst→db.query.<table>.findFirst,select→columns/with, missing row isundefinednotnull;$transaction→db.transaction; port the seed script; retarget test mocks.prisma/dir,@prisma/client+prismadeps, prisma npm scripts,prisma generateCI steps,.env/docs/gitignore mentions. Done only whengrep -rni prismais clean (lockfile optional-peers aside).drizzle.__drizzle_migrations(SQL above) instead of running 0000; drop_prisma_migrations. Wire into the repo's deploy path.🤖 Generated with Claude Code
https://claude.ai/code/session_01WqKqMnHQHSmkGFfc5t7Rxn