-
Notifications
You must be signed in to change notification settings - Fork 2.5k
feat(cli): prisma contract convert writes the Prisma 8 contract file a Prisma 7 user needs to leave Prisma 7, keeping the signed marker
#30300
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e296470
d531b24
20dec72
42090aa
ecd7051
ba73d48
7464a61
31711cd
bd2c9aa
8d7066b
2f78c8f
cf298c4
0486cd1
624af75
0eae0d7
1bf9f65
6f16738
e1e3a51
02f6c39
73c31c4
64eaa6c
7187d4d
122ad07
4552716
339b388
73fd5c1
835241e
f5333b6
74fcdc6
def745e
16a95a2
92d5388
7e5cff9
47afbf6
8404437
778ae56
b21b98d
84a8fcb
95f5e84
cf1705d
a8e59db
98e487e
a587642
fc063ac
57b1430
4558f84
165efdd
285157a
5450297
cc10445
4d331b9
b21c012
d552116
ae39c8f
85dac76
d77e4df
095612b
96f165e
8621602
6d70c12
f6655c1
7d1d992
af62b7f
b1f5f55
36f9b42
a328dc9
3b6c209
ebde693
bd3ac97
74e9722
f05c5a4
cca78fa
c2415f6
bbbf787
b961f5f
3e6a7a6
f81e1ad
1245105
94eec7b
646346d
c6edfae
915fd39
5a9cfac
96b6bc1
174e870
8b76490
c39c6f9
7c3c50d
b103af5
f87eb8c
f564c42
f8d682c
1e056e4
9be21cf
19bf85c
5446d89
d0210a2
aa7c357
456be46
36e639a
4a10043
c5782eb
ec0723e
9953679
8d1f1a3
ddbc5c6
3b2c886
6325023
8a81b79
e506638
f970286
8b4aba6
2335e89
014f593
5dadc83
bb333bd
0471116
c5ed162
24840b4
fa3a86c
64bbed9
ecf585b
431b2c9
b6a941b
d64f9b7
140abb7
7077175
b54543a
64b83ed
b897ac3
5afb73d
05ae479
869a4b9
a78619d
cd5ecd0
094a86e
0b6608b
881402e
00e7e19
f94caa5
73493ee
f1c603a
f1e434e
00e776a
9778a35
2c92354
7f415f1
80e1dce
227ddf1
2c1b79c
4f44423
b061904
ff2668c
aebb117
0c7cdcd
b215d07
45ac3ab
bf3a6f5
a89fa1c
d3ed0a3
0d4d77f
84a6e77
c0ad9e6
7558781
160bb35
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| .env | ||
| generated/prisma7/ | ||
| node_modules/ | ||
| .story-*/ |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,106 @@ | ||||||||||||||||||||||
| # Adopting Prisma 8 beside Prisma 7 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| A Prisma 7 project (PostgreSQL) adopts Prisma 8 the way the public guide [Prisma ORM 7 to 8 (PostgreSQL)](https://www.prisma.io/docs/guides/upgrade-prisma-orm/postgresql) describes, with one change: instead of running `prisma contract infer` and hand-editing the inferred contract, Prisma 8 reads `prisma/schema.prisma` directly through `prisma7Schema(...)`. Prisma 7 is installed for real (`@prisma/prisma7`, `@prisma/client`, `@prisma/adapter-pg`, all 7.10.0) and keeps owning the database and its migrations; Prisma 8 reads the schema, signs and verifies the database, and serves the routes that have moved. Nothing is hand-edited. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ## The story in one run | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ```bash | ||||||||||||||||||||||
| cd examples/prisma7-adoption | ||||||||||||||||||||||
| pnpm db:start # terminal 1: in-process Postgres, writes DATABASE_URL to .env | ||||||||||||||||||||||
| pnpm v7:migrate # terminal 2: prisma7 migrate deploy --config prisma7.config.ts | ||||||||||||||||||||||
| pnpm emit # prisma contract emit: Prisma 8 reads prisma/schema.prisma via prisma7Schema | ||||||||||||||||||||||
| pnpm sign # prisma db sign: verifies the database, records the marker | ||||||||||||||||||||||
| pnpm verify # prisma db verify: zero findings | ||||||||||||||||||||||
| pnpm v7:generate # prisma7 generate: the Prisma 7 client | ||||||||||||||||||||||
| pnpm seed # rows written through the Prisma 7 client | ||||||||||||||||||||||
| pnpm start # the same rows read and written through the Prisma 8 ORM | ||||||||||||||||||||||
| pnpm v7:read # the same rows read through Prisma 7 again | ||||||||||||||||||||||
| pnpm convert # prisma contract convert: the cutover file, generated/prisma8/contract.prisma | ||||||||||||||||||||||
| pnpm test # the whole story on a fresh database, including the second migration and the cutover | ||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| `prisma/migrations/` holds two Prisma 7 migrations, the initial one and one adding `Post.viewCount`. On a fresh database `pnpm v7:migrate` applies both at once, so to watch the refresh loop that every later Prisma 7 migration needs, run `pnpm test`: it rolls a scratch copy of this example back to the first migration, runs the commands above, then lands the second migration and runs `pnpm emit`, `pnpm sign`, and `pnpm verify` again. After every `prisma7 migrate deploy` (or `migrate dev`) that is the whole loop: emit, sign, verify. Nothing else changes. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ## Phase by phase | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| The guide's phases, and what this example does in each. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### 1. Prepare Prisma 7 to run side by side | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| `package.json` has `@prisma/prisma7@7.10.0` as a dev dependency instead of `prisma`, so the Prisma 7 CLI is the `prisma7` binary and `prisma` is free for Prisma 8. Prisma 7's config is `prisma7.config.ts`, importing `defineConfig` from `@prisma/prisma7/config`; every Prisma 7 command passes `--config prisma7.config.ts`, because the CLI looks for `prisma.config.ts` by default and that file now belongs to Prisma 8. `@prisma/client@7.10.0` and `@prisma/adapter-pg@7.10.0` stay, and the schema's generator writes the Prisma 7 client to `generated/prisma7/` (gitignored; `pnpm v7:generate` recreates it). | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### 2. Add Prisma 8 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| `prisma.config.ts` is the guide's file with the contract line changed: | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ```ts | ||||||||||||||||||||||
| import 'dotenv/config'; | ||||||||||||||||||||||
| import { definePrismaConfig } from '@prisma/cli-engine'; | ||||||||||||||||||||||
| import { defineConfig as definePostgresConfig, prisma7Schema } from '@prisma/orm-postgres/config'; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| export default definePrismaConfig({ | ||||||||||||||||||||||
| orm: definePostgresConfig({ | ||||||||||||||||||||||
| contract: prisma7Schema('prisma/schema.prisma', { output: 'generated/prisma8/contract.json' }), | ||||||||||||||||||||||
| db: { connection: process.env['DATABASE_URL']! }, | ||||||||||||||||||||||
| }), | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| In your own project the first import is `import { definePrismaConfig } from 'prisma/config'` and the Prisma 8 CLI is the published `prisma@latest` dev dependency, exactly as the guide shows. Inside this repository the published `prisma` package is built elsewhere, so this example aliases the workspace CLI as its `prisma` dev dependency (`"prisma": "workspace:@internal/cli@..."`) and imports `definePrismaConfig` from `@prisma/cli-engine`, which the published package re-exports as `prisma/config`. Everything else is what you would write. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| `prisma7Schema` replaces the guide's `prisma contract infer` step and the two hand edits after it (deleting the `PrismaMigrations` model, adding `@@map` to every model): the source reads the Prisma 7 schema itself, so model names stay as written and `_prisma_migrations` is never part of the contract. `pnpm emit` writes `generated/prisma8/contract.json` and `contract.d.ts`; `pnpm sign` verifies the live schema against that contract and writes Prisma 8's marker; `pnpm verify` reports nothing when they match. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Two rules to know before you start: | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| - A database last migrated on Prisma 5 or earlier must migrate on Prisma 7 first. Since Prisma 6.0.0 the implicit many-to-many junction tables (`_PostToTag` here) carry a primary key on `(A, B)` instead of a unique index, and the source describes that shape; on an older database `db sign` reports the difference. | ||||||||||||||||||||||
| - Every construct the source cannot express is a hard error with the file, line, and the edit that unblocks it, never a silent change. The list is in the `prisma7Schema` section of the [`@prisma/orm-postgres` README](../../packages/3-extensions/postgres/README.md). In this schema nothing needs editing. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### 3. Move routes one at a time | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| `src/db.ts` instantiates both clients over the same `DATABASE_URL`, as the guide's `src/db.ts` does: `prisma` (Prisma 7, through `@prisma/adapter-pg`) and `db` (Prisma 8, `postgres<Contract>({ url, contractJson })`). `scripts/seed.ts` and `src/v7-read.ts` are the routes that have not moved: they use the Prisma 7 client. `src/main.ts` is a route that has: it lists users with their posts and the posts' tags through `db.orm.public.User.include('posts', ...)`, reaching the tags through the `_PostToTag` junction Prisma 7 created, creates a post connected to an existing tag through `db.orm.public.Post.include('tags').create({ ..., tags: (tags) => tags.connect([...]) })`, and renames a user through `db.orm.public.User.where(...).update(...)`, printing the `updatedAt` before and after: Prisma 8's own generator sets it, as Prisma 7's `@updatedAt` did. Run `pnpm start` and then `pnpm v7:read` to see the post Prisma 8 wrote come back through Prisma 7. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### 4. Transfer migration ownership (cutover) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| When the last route has moved, Prisma 8 takes the schema over. The guide's phase 4 is `prisma migration plan --name baseline`, `prisma db sign`, `prisma migration ref set db <timestamp>_baseline`; this example puts one step in front of it, because the contract still reads the Prisma 7 file: | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ```bash | ||||||||||||||||||||||
| pnpm convert # prisma contract convert: writes generated/prisma8/contract.prisma | ||||||||||||||||||||||
| prisma contract emit --config prisma.config.cutover.ts # same contract.json, now from the Prisma 8 file | ||||||||||||||||||||||
| prisma db verify --config prisma.config.cutover.ts # zero findings | ||||||||||||||||||||||
| prisma migration plan --name baseline --config prisma.config.cutover.ts | ||||||||||||||||||||||
| prisma db sign --config prisma.config.cutover.ts | ||||||||||||||||||||||
| prisma migration ref set db <timestamp>_baseline --config prisma.config.cutover.ts | ||||||||||||||||||||||
|
Comment on lines
+68
to
+72
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Make the cutover commands executable from the example directory. The package scripts can use bare
Proposed fix-prisma contract emit --config prisma.config.cutover.ts
-prisma db verify --config prisma.config.cutover.ts
-prisma migration plan --name baseline --config prisma.config.cutover.ts
-prisma db sign --config prisma.config.cutover.ts
-prisma migration ref set db <timestamp>_baseline --config prisma.config.cutover.ts
+pnpm exec prisma contract emit --config prisma.config.cutover.ts
+pnpm exec prisma db verify --config prisma.config.cutover.ts
+pnpm exec prisma migration plan --name baseline --config prisma.config.cutover.ts
+pnpm exec prisma db sign --config prisma.config.cutover.ts
+pnpm exec prisma migration ref set db TIMESTAMP_baseline --config prisma.config.cutover.ts📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||
| ``` | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| `prisma contract convert` prints the contract the Prisma 7 source produced as Prisma 8 PSL: the same storage, execution, and profile hashes and the same domain plane, so the marker `pnpm sign` wrote stays valid. Native enum members come back named after their database values (`USER @map("user")` becomes `user = "user"`), and every relation carries `index: false`, because Prisma 7 created no foreign-key indexes; see the [CLI README](../../packages/1-framework/3-tooling/cli/README.md) for the full list of spellings. `prisma.config.cutover.ts` is `prisma.config.ts` with `contract: 'generated/prisma8/contract.prisma'` in place of `prisma7Schema(...)`; in your own project you edit `prisma.config.ts` in place, and the `--config` flags disappear. `migration plan --name baseline` writes `migrations/app/<timestamp>_baseline/` describing the schema Prisma 8 now owns; `db sign` records it, and the `db` ref names it. `pnpm test` runs this sequence after the second migration. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ### 5. Remove Prisma 7 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| The guide's phase 5: remove `@prisma/prisma7`, `@prisma/client`, and `@prisma/adapter-pg`, delete `prisma7.config.ts`, `prisma/` (schema and Prisma 7 migrations), and `generated/prisma7/`, and drop the `v7:*` scripts. This example keeps them, because showing both side by side is its purpose. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ## What a Prisma 7 user meets along the way | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| - `@prisma/client@7.10.0` declares `prisma` as a peer dependency. With pnpm's default automatic peer installation and no `prisma` dev dependency of your own, the package manager installs Prisma 7's `prisma` to satisfy it, and `prisma contract emit` runs Prisma 7. Keep an explicit `prisma` dev dependency for Prisma 8 (the guide's `prisma@latest`; here the workspace alias) so the `prisma` binary is Prisma 8's. | ||||||||||||||||||||||
| - pnpm's `trustPolicy: no-downgrade` refuses `prisma@7.10.0`, the dependency behind `@prisma/prisma7`, because earlier `prisma` releases carried provenance attestation and this one does not. The workspace exempts that one exact version in `pnpm-workspace.yaml`. | ||||||||||||||||||||||
| - Prisma 7 still ships the schema engine as a native binary, fetched by `@prisma/engines` at install time or on the first `prisma7` run, so one run needs network access; the Prisma 7 client itself has no engine to fetch. | ||||||||||||||||||||||
| - The guide's `prisma7.config.ts` sets `datasource.url` to `process.env["DATABASE_URL"]`, which is `string | undefined`; under `exactOptionalPropertyTypes` that does not type-check, so this example adds the `datasource` block only when the variable is set. `prisma7 generate` runs without a database either way. | ||||||||||||||||||||||
| - Prisma 7 rejects `url` inside the `datasource` block; the URL lives only in `prisma7.config.ts` (Prisma 7) and `prisma.config.ts` (Prisma 8), both reading the same `DATABASE_URL` from `.env`. | ||||||||||||||||||||||
| - Prisma 8 returns `DateTime` columns as `Temporal.PlainDateTime`. Node 24 has no global `Temporal`, so `src/db.ts` imports `temporal-polyfill/full/global` before creating the client. | ||||||||||||||||||||||
| - `pnpm sign` creates `migrations/` (a snapshot of the signed contract and the `db` ref). It is Prisma 8's record of what was signed and is committed here; phase 4 builds on it. | ||||||||||||||||||||||
| - The Prisma 8 CLI prints JSON when stdout is not a terminal (a pipe, a file, or an agent) and prose in a terminal. | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| ## Files | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| | Path | Role | | ||||||||||||||||||||||
| |---|---| | ||||||||||||||||||||||
| | `prisma/schema.prisma`, `prisma/migrations/` | The Prisma 7 schema and its migrations; Prisma 7 owns both. | | ||||||||||||||||||||||
| | `prisma7.config.ts` | Prisma 7's config (`@prisma/prisma7/config`). | | ||||||||||||||||||||||
| | `prisma.config.ts` | Prisma 8's config; `prisma7Schema('prisma/schema.prisma')` is the contract source. | | ||||||||||||||||||||||
| | `prisma.config.cutover.ts` | Prisma 8's config after the cutover; `generated/prisma8/contract.prisma` (written by `pnpm convert`) is the contract source. | | ||||||||||||||||||||||
| | `generated/prisma8/` | `contract.json` and `contract.d.ts` emitted by Prisma 8 (committed). | | ||||||||||||||||||||||
| | `generated/prisma7/` | The Prisma 7 client (`pnpm v7:generate`, gitignored). | | ||||||||||||||||||||||
| | `src/db.ts` | Both clients over one `DATABASE_URL`. | | ||||||||||||||||||||||
| | `src/main.ts` | Routes that moved to Prisma 8. | | ||||||||||||||||||||||
| | `scripts/seed.ts`, `src/v7-read.ts` | Routes still on Prisma 7. | | ||||||||||||||||||||||
| | `scripts/db-start.ts` | In-process Postgres for local runs. | | ||||||||||||||||||||||
| | `test/adoption.test.ts` | The whole story on a fresh database, including the second migration and the cutover. | | ||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| { | ||
| "$schema": "https://biomejs.dev/schemas/2.5.8/schema.json", | ||
| "extends": "//", | ||
| "files": { | ||
| "includes": ["**", "!**/node_modules", "!**/*.d.ts", "!**/contract.json", "!generated/prisma7"] | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Describe the capability owner correctly.
The runtime checks
hasPslContractPrint(this.familyInstance), not the target descriptor. State that the configured family instance or components do not implementPslContractPrintCapable. This matches the emitted error and prevents incorrect diagnosis of the failure.🤖 Prompt for AI Agents