Migrations
Learn what the two committed migrations under prisma/migrations install, how db:migrate, db:deploy and db:reset differ, what prisma/seed.ts creates, and where README.md contradicts the scripts.
Litestore is migration-driven. prisma/migrations/ holds two committed migrations: a baseline that creates the whole schema and installs the money, refund and stock guarantees, and a later one that adds three indexes.
That choice is load-bearing rather than stylistic. The money guarantees exist only because they are in a migration — they are CHECK constraints, triggers and views that no schema-diffing push would ever create.
A prisma db push workflow can reproduce every table and column in this repository and still produce a database that accepts a refund larger than the order it refunds.
What is committed
The baseline
20260726143000_baseline_with_db_guarantees3,121 lines.
It creates the schema from scratch — 19 enum types, the tables and their indexes — and then installs the parts that a schema diff cannot produce:
- 20 CHECK constraints
- four
litestore_guard_*trigger functions, with their triggers - the case-insensitive unique indexes —
user_email_lower_unique,product_variant_sku_upper_unique,product_slug_lower_uniqueand the rest - six views
The six views are:
OrderMoneyTotals
OrderMoneyDrift
CustomerMoneyTotals
CustomerMoneyDrift
InventoryStockTotals
InventoryLedgerDriftThe performance indexes
20260730090000_performance_indexesThree statements, all CREATE INDEX IF NOT EXISTS:
OrderItem_variantId_idx
Customer_createdAt_idx
Media_url_idxThe rest of the directory
prisma/migrations/migration_lock.toml records provider = "postgresql".
prisma/sql/ exists but is empty — Litestore does not use Prisma TypedSQL.
The commands
bun run db:migrateRuns prisma migrate dev. This is the command for a local schema change: it writes a new migration into prisma/migrations/, applies it, and regenerates the client in the same step.
bun run db:deployRuns prisma migrate deploy. This is the production and CI command. It applies pending migrations only — it never generates a migration, never prompts, and never resets.
bun run db:pushAlso runs prisma migrate dev. It does nothing that db:migrate does not already do; the name is a leftover and the script body is identical. db:push does not run prisma db push.
bun run db:resetRuns prisma migrate reset, for rebuilding a local database. It drops the database, replays every migration, then runs the seed.
bun run db:seedRuns tsx prisma/seed.ts, for seeding without dropping anything.
bun run db:generateRuns prisma generate, for regenerating the client after a schema change you did not migrate yourself.
db:reset drops the database
prisma migrate reset deletes every row before it replays the migrations. It is
a local development command. Pointing it at a DATABASE_URL that holds real
orders destroys them.
What keeps the scripts honest
Two harness assertions keep this arrangement from drifting. scripts/check-guarantees-contract.ts requires:
db:migrateto containprisma migrate devdb:deployto containprisma migrate deploy
It also fails the build if the string prisma db push appears anywhere in package.json scripts.
It runs inside bun run verify, so the drift is caught at the gate rather than discovered when a guarantee turns out to be missing in production.
Seeding
prisma/seed.ts is a 2,482-line script that builds its own PrismaClient over PrismaPg from process.env.DATABASE_URL.
It does not use ~/services/db, so it runs without the soft-delete extension.
Every write is an upsert keyed on a natural key, so re-running it does not duplicate rows.
Its own closing summary states what it produces:
- Two rows for each of 41 base models
- 20 additional products with 80+ variants
- 12 email templates
What the seed deliberately skips
Orders are not seeded. The script prints:
Skipping order seeding (orders created through checkout)It skips refund requests, refund request items, returns, coupon usage and shipping for the same reason: those rows are produced by the flows that create them, and are subject to the money constraints. A seeded order written straight to the table would be an order that no checkout produced, sitting under constraints that assume one did.
Signing in
The seed creates an admin account for admin@example.com.
Sign-in is a magic link delivered by Resend, so a working RESEND_API_KEY is what actually gets you into /admin.
Two entry points, one file
There are two seed entry points naming the same file with different runners.
prisma.config.ts sets migrations.seed to bun prisma/seed.ts, which is what prisma migrate reset invokes.
package.json maps db:seed to tsx prisma/seed.ts.
package.json also still carries the legacy block that Prisma 7 no longer reads:
"prisma": { "seed": "bun prisma/seed.ts" }README.md is out of date on this
README.md states, in two places, that the repository is not migration-driven:
Prisma schema is current, but local setup uses
prisma db pushrather than a committed migration history
There is no committed Prisma migration history that should be treated as authoritative local bootstrap state. Do not document this repo as migration-driven unless that changes in the codebase.
Both statements are stale:
- The migration history is committed
- It is the only place the database guarantees are defined
prisma db pushis not in any script- A harness check fails the build if it reappears
Its setup instructions — bun run db:push then bun run db:seed — still work, because db:push runs prisma migrate dev. But they apply the committed migrations rather than pushing a schema diff.
Follow the scripts, not the README prose.
Changing the schema
Edit prisma/schema.prisma, then run:
bun run db:migrateReview the generated SQL before committing it.
If the change touches money, refunds or stock, there are two checks and they answer different questions.
bun run lint:guaranteesChecks the migration SQL, without a database.
bun run db:guarantees:auditChecks the database you actually applied it to.
The first tells you the migration still says the right thing. The second tells you a real database agrees.
Which command to reach for
Changing the schema locally is db:migrate — and that is the only command that writes a migration.
Applying someone else's committed migrations, in CI or production, is db:deploy.
Rebuilding a local database that has drifted or broken is db:reset, and only ever against a database whose contents you are willing to lose.
Pulling a schema change you did not migrate yourself is db:generate.
The rule underneath all four is the one the baseline migration exists to enforce:
A guarantee that is not in a migration is not a guarantee the database is making.
Related
Schema
Learn the core Prisma models in schema.prisma, the conventions every model follows, and how to change the schema safely.
Guarantees
Learn which money, refund, stock, identity, visibility and cache promises Postgres enforces, which ones a harness check enforces, and which ones are explicitly not guaranteed.
Client
Learn how services/db.ts builds the Prisma client on the @prisma/adapter-pg driver adapter, why the connection pool is tuned for a remote pooler, and how the singleton survives dev hot reload.
Client
Learn how services/db.ts builds the Prisma client on the @prisma/adapter-pg driver adapter, why the connection pool is tuned for a remote pooler, and how the singleton survives dev hot reload.
Deletion
Learn which eight read operations the global Prisma extension filters, how deletedAt: undefined opts a read back in, and how check-nested-soft-delete.ts enforces explicit filters on nested to-many reads.