Migrations
This page shows you how Litestore creates, applies and resets database migrations, and what the seed script puts in a new database.
Litestore builds and changes its database through committed migrations. Read this page before you change the schema, set up a new database, or deploy to production.
prisma/migrations/ contains two committed migrations. The first is a baseline that creates the whole schema and installs the money, refund and stock guarantees. The second, added later, creates three indexes.
Litestore relies on migrations for a concrete reason. The money guarantees are CHECK constraints, triggers and views, and a push that only compares schemas would never create them. They exist only because a migration installs them.
If you used prisma db push instead, you could reproduce every table and column in this repository and still end up with a database that accepts a refund larger than the order it refunds.
What is committed
The baseline
20260726143000_baseline_with_db_guaranteesThis migration is 3,121 lines long.
It first creates the schema from scratch, including 19 enum types and all tables with their indexes. Then it installs the parts that a schema diff can't produce:
- 20 CHECK constraints
- four
litestore_guard_*trigger functions, with their triggers - the case-insensitive unique indexes, such as
user_email_lower_unique,product_variant_sku_upper_uniqueandproduct_slug_lower_unique - six views
These are the six views:
OrderMoneyTotals
OrderMoneyDrift
CustomerMoneyTotals
CustomerMoneyDrift
InventoryStockTotals
InventoryLedgerDriftThe performance indexes
20260730090000_performance_indexesThis migration has three statements, and each one is a 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".
The prisma/sql/ folder exists but is empty, because Litestore doesn't use Prisma TypedSQL.
The commands
bun run db:migrateThis runs prisma migrate dev. Use it when you change the schema locally. It writes a new migration into prisma/migrations/, applies it, and regenerates the client in the same step.
bun run db:deployThis runs prisma migrate deploy. Use it in production and CI. It only applies pending migrations. It never creates a migration, never asks you a question, and never resets the database.
bun run db:pushThis also runs prisma migrate dev, with a script body identical to db:migrate. The name is left over from an older setup. db:push does not run prisma db push.
bun run db:resetThis runs prisma migrate reset, which you use to rebuild a local database. It drops the database, replays every migration, and then runs the seed.
bun run db:seedThis runs tsx prisma/seed.ts. Use it to seed the database without dropping anything.
bun run db:generateThis runs prisma generate. Use it to regenerate the client after a schema change that you didn't migrate yourself.
db:reset drops the database
prisma migrate reset deletes every row before it replays the migrations. Only use it on a local development database. If you point it at a DATABASE_URL that contains real orders, it destroys them.
What keeps the scripts honest
Two harness assertions stop these scripts from drifting. scripts/check-guarantees-contract.ts requires the following:
db:migratecontainsprisma migrate dev.db:deploycontainsprisma migrate deploy.
The same check also fails the build if the string prisma db push appears anywhere in the package.json scripts.
The check runs inside bun run verify. You find out about drift at that gate, before a production database turns out to be missing a guarantee.
Seeding
prisma/seed.ts is a 2,482-line script. It builds its own PrismaClient over PrismaPg, using process.env.DATABASE_URL.
The script doesn't use ~/services/db, so it runs without the soft-delete extension.
Every write is an upsert keyed on a natural key. You can run the script again without creating duplicate rows.
According to the summary the script prints at the end, it creates the following:
- two rows for each of 41 base models
- 20 additional products with 80+ variants
- 12 email templates
What the seed deliberately skips
The seed doesn't create orders. It prints this message instead:
Skipping order seeding (orders created through checkout)For the same reason, it also skips refund requests, refund request items, returns, coupon usage and shipping. Those rows come from the flows that create them, and the money constraints apply to them. If the seed wrote an order straight into the table, that order would sit under constraints that assume a checkout produced it, when no checkout did.
Signing in
The seed creates an admin account for admin@example.com.
You sign in with a magic link that Resend delivers. To get into /admin, you need a working RESEND_API_KEY.
Two entry points, one file
Two seed entry points run the same file with different runners.
prisma.config.ts sets migrations.seed to bun prisma/seed.ts. This is the command that prisma migrate reset runs.
package.json maps db:seed to tsx prisma/seed.ts.
package.json also still contains the legacy block below, which Prisma 7 no longer reads:
"prisma": { "seed": "bun prisma/seed.ts" }README.md is out of date on this
In two places, README.md says 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 out of date. The migration history is committed, and it is the only place where the database guarantees are defined. No script runs prisma db push, and a harness check fails the build if one starts to.
The README's setup instructions still work. They tell you to run bun run db:push and then bun run db:seed, and since db:push runs prisma migrate dev, you end up applying the committed migrations instead of pushing a schema diff.
When the README and the scripts disagree, follow the scripts.
Changing the schema
-
Edit
prisma/schema.prisma. -
Create the migration. Run this command:
bun run db:migrate -
Review the generated SQL before you commit it.
If your change touches money, refunds or stock, run two more checks. Each one answers a different question.
bun run lint:guaranteesThis checks the migration SQL and doesn't need a database. It tells you the migration still says the right thing.
bun run db:guarantees:auditThis checks the database you applied the migration to. It tells you a real database agrees.
Which command to use
When you change the schema locally, use db:migrate. It is the only command that writes a migration.
When you apply someone else's committed migrations in CI or production, use db:deploy.
When a local database has drifted or broken and you want to rebuild it, use db:reset. Only run it against a database whose contents you are willing to lose.
When you pull a schema change that you didn't migrate yourself, use db:generate.
All four follow from the rule that the baseline migration exists to enforce. If a guarantee isn't in a migration, the database isn't enforcing it.
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
See how Litestore builds its one Prisma client and why its connection pool is tuned the way it is.