From ff50116eba05168285996b1f1342ebc20ca89766 Mon Sep 17 00:00:00 2001 From: willbot Date: Mon, 14 Sep 2026 08:55:09 +0200 Subject: [PATCH 01/14] docs(orm8): C21 conventions for the orm/contract-authoring section Co-Authored-By: Claude Fable 5.1 Signed-off-by: willbot Signed-off-by: Will Madden --- .../c21-contract-authoring/conventions.md | 24 +++++++++++++++++++ .../page-conventions.md | 14 +++++++++++ 2 files changed, 38 insertions(+) create mode 100644 docs/orm-docs-audit/c21-contract-authoring/conventions.md create mode 100644 docs/orm-docs-audit/c21-contract-authoring/page-conventions.md diff --git a/docs/orm-docs-audit/c21-contract-authoring/conventions.md b/docs/orm-docs-audit/c21-contract-authoring/conventions.md new file mode 100644 index 00000000000..75c20b9c746 --- /dev/null +++ b/docs/orm-docs-audit/c21-contract-authoring/conventions.md @@ -0,0 +1,24 @@ +# Shared conventions for the C21 plain-language pass (carried from the fundamentals and reference sections) + +Verified facts (rc.10 source at wip/prisma-src/, commit cfccb09be2). Use these words on every page; do not restate them differently. + +- "contract": on first use on a page, say: "your contract, the `contract.prisma` file that replaced `schema.prisma`". After that, "contract" alone. +- `db`: "`db` is the client you create once in `src/prisma/db.ts`; `prisma orm init` writes that file." Examples import it as `import { db } from "./prisma/db"` from a file directly inside `src/`. +- `db.orm.public.User`: "`db.orm` holds your models by model name; `public` is the PostgreSQL schema (the namespace your tables live in, `public` unless you set one)." On MongoDB there is no schema segment and the accessor is the collection name: `db.orm.users`. +- `db.sql.public.user`: the SQL query builder, keyed by table name. The table name is the model name with a lowercase first letter unless the model sets `@@map`. Column names are the field names unless `@map`. Say this once where `db.sql` first appears. +- `db.raw.sql`: raw SQL as a template string. `db.runtime()` is the connection that runs a built query: `db.runtime().query(plan)` for rows, `db.runtime().execute(plan)` for a count. On PostgreSQL `db.runtime()` is synchronous; on MongoDB it must be awaited: `(await db.runtime()).query(plan)`. State this where `db.runtime()` first appears on a page. +- "plan": avoid the word. Say "the built query" or show the code. Every raw or builder query ends with `.build()` and is then passed to `query()` or `execute()`. +- `.all()`, `.first()`, `.create(...)`: "you `await` the whole chain; the last call says what you want back". Do not use "terminal". +- `.first()` returns `null` when nothing matches. `.all().firstOrThrow()` throws an error with code `RUNTIME.NO_ROWS`. +- `emit`: "`prisma contract emit`, which you run after every change to the contract". Never a bare "emit". +- Scaffold command: `npm create prisma@latest -- my-app` (never `npx create-prisma@latest` or the bare package name). Bun: `bun create prisma@latest my-app`. +- Skills note at the bottom of pages: keep, but say "Projects created with `npm create prisma@latest` include the Prisma ORM skills for your coding agent". +- `conflictOn: { email: "..." }` takes a unique column with its value; if a row with that value exists, `update` is applied to it. Leave it out to use the primary key. +- Nested writes that work: `create`, `connect`, `disconnect` (`posts: (p) => p.create([...])`). `connectOrCreate`, nested update/upsert/delete, and `set` do not exist; link /orm/coming-from-prisma-orm-7#not-available-yet. +- Transaction options (`isolationLevel`, `timeout`, `maxWait`) and nested transactions do not exist. Prisma ORM 7's interactive `$transaction(async (tx) => ...)` maps directly to `db.transaction(async (tx) => ...)`. +- `create-prisma` mentions of "skills": "Prisma ORM skills, instruction files for coding agents". +- Type names in raw SQL (`pg/int4@1`): "the PostgreSQL type plus a version, always `@1` today"; prefer taking the type from the table: `db.sql.public.user.columns.id`. There is no published list; the raw-queries page shows the ids a bare JavaScript value gets. +- Errors: "an error with code `X`" (it is `error.code`). +- Do not use: terminal, codec, envelope, facade, lane, junction model/table (say "join table" / "model for the join table"), surface, plan, "rides", "fan out", "drop one level", "field proxy" (say "the `p` argument"), "lambda" (say "callback"). +- `npx prisma skills sync` exists in the published CLI (8.0.0-rc.13, newer than the rc.9 source tree): "in an existing project, run `npx prisma skills sync`". Do not drop it because the rc.9 tree lacks it. +- The serverless client (`postgresServerless(...)` from `@prisma/orm-postgres/serverless`) has no `db.orm`, `runtime()`, or `transaction()`; it runs SQL builder and raw queries only, reading rows 100 at a time. Never show `db.orm` on it. diff --git a/docs/orm-docs-audit/c21-contract-authoring/page-conventions.md b/docs/orm-docs-audit/c21-contract-authoring/page-conventions.md new file mode 100644 index 00000000000..f542f5c9032 --- /dev/null +++ b/docs/orm-docs-audit/c21-contract-authoring/page-conventions.md @@ -0,0 +1,14 @@ +# Conventions for the orm/contract-authoring pages (in addition to conventions.md) + +- These are explanation and how-to pages, not reference pages: The data contract (what it is), Author in PSL, Author in TypeScript, The emitted artifacts, Capabilities. Each one answers "what is this and what do I write". Keep the heading order. Do not renumber, rename, or reorder headings; if a heading must change because it contains a banned term, pin the old anchor with `[#old-anchor]`. +- No commentary about the docs inside the docs. Cut sentences such as "This page explains", "This page covers the basics briefly", "the examples throughout these docs", "to give a sense of the granularity". Write the thing itself. +- Never interleave PostgreSQL and MongoDB sentence by sentence. Where both databases matter, give PostgreSQL its own sentences or block and then MongoDB its own. Tabs already on a page stay. +- The word "contract" is the subject of this section. On first use on a page: "your contract, the `contract.prisma` file that replaced `schema.prisma`" (or `contract.ts` when the page is about TypeScript authoring). After that, "contract" alone. "Data contract" is allowed in the page title and once in the first sentence of the-data-contract; elsewhere say "contract". +- `prisma contract emit` "writes `contract.json` and `contract.d.ts` beside the source". Say this once per page where the command first appears. Never a bare "emit" as a noun ("the emit", "emission", "emitted artifacts"). "The artifacts" is allowed after the page has named the two files. +- "Hashes" and the marker: "when you run `prisma db sign`, Prisma ORM records which contract the database matches in a small table it creates in the database; the client checks that record before its first query" (verify against source; use the wording the check confirms). Do not write "signature" or "marker" without that definition on the same page. +- Banned on these pages in addition to the list: gate / gated / gates (write "checks", "requires", "is only available when"); capability in prose (write "a database feature the contract needs", "what your database supports"; the code identifier `capabilities` for the section of `contract.json`, and the key names such as `sql.lateral`, are code and stay); codec (write "how the value is stored and read", or name the type id such as `pg/text@1`); canonical / canonicalizes (write "in a fixed key order"); deterministic (write "the same source always produces the same files"); pure (as in "the contract file must stay pure": write "must not read the environment, the clock, or random values"); artifact when the two files can be named instead; "source of truth" (write "the one file the config points at"); "escape hatch"; "purpose-built"; "first-class"; "wiring" / "wired"; "hangs off"; "consult"; "authoritative"; "physical name" (write "column name in the database"); "value object" only with its definition on the same page ("a `type` block: a structured value stored inside its parent row, with no table of its own"). +- Type ids such as `pg/text@1`: "the PostgreSQL type plus a version, always `@1` today". Do not explain the format twice on one page. +- "Prompt your coding agent" sections: keep, but the first sentence reads "Projects created with `npm create prisma@latest` include the Prisma ORM skills for your coding agent; in an existing project, run `npx prisma skills sync`." Keep the example prompts. +- Scaffold command: `npm create prisma@latest -- my-app`. Emit command in prose and code: `npx prisma contract emit` (not `prisma@latest`, once the page has shown installation; the pages assume an existing project). +- The source of truth is prisma/orm at 8.0.0-rc.10 (`cfccb09be2`), checked out at `wip/prisma-src/`. Where a page names an rc.9 behaviour that rc.10 changed, rc.10 wins. +- The CLI pages under `/cli/` (`contract-emit`, `contract-infer`, `db-sign`, `db-verify`, `db-init`, `migration-plan`, `configuration`) are out of scope; link to them, do not restate their flags. From 9b2985c6e0f701696aaaffce7ade2cfd3a813970 Mon Sep 17 00:00:00 2001 From: willbot Date: Mon, 14 Sep 2026 09:05:12 +0200 Subject: [PATCH 02/14] docs: link the 7 to 8 and 6 to 8 upgrade guides from the v7 guides nav The v7 guides tree had no Upgrade Prisma ORM section, so a reader on the Prisma ORM 7 docs could not reach either guide from the sidebar. Co-Authored-By: Claude Fable 5.1 Signed-off-by: willbot Signed-off-by: Will Madden --- apps/docs/content/docs/guides/v7/meta.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/docs/content/docs/guides/v7/meta.json b/apps/docs/content/docs/guides/v7/meta.json index be47531cbec..d7bb19db405 100644 --- a/apps/docs/content/docs/guides/v7/meta.json +++ b/apps/docs/content/docs/guides/v7/meta.json @@ -7,6 +7,9 @@ "deployment", "database", "switch-to-prisma-orm", - "integrations" + "integrations", + "---Upgrade Prisma ORM---", + "[Prisma ORM 7 to 8 (PostgreSQL)](/guides/upgrade-prisma-orm/postgresql)", + "[Prisma ORM 6 to 8 (MongoDB)](/guides/upgrade-prisma-orm/mongodb)" ] } From 63a5ae31123ee2919bd585c83314935c627ca03c Mon Sep 17 00:00:00 2001 From: willbot Date: Mon, 14 Sep 2026 09:06:36 +0200 Subject: [PATCH 03/14] docs(orm8): contract-authoring pages, banned terms replaced and facts checked against rc.10 (part one) The data contract, the artifacts page, and the PSL page. The client only logs a warning when the database marker does not match the contract; prisma db verify is what fails. storageHash covers the storage layout only. Contract files live in src/prisma/. An enum without @@type gets its storage type from its member values. A type from a pack not listed in the config fails with CONFIG.MISSING_EXTENSION_PACKS. The artifacts page is now titled by the two files it describes. The plain-language checker now skips link targets, pinned heading anchors, and href attributes, which are not prose. Co-Authored-By: Claude Fable 5.1 Signed-off-by: willbot Signed-off-by: Will Madden --- .../docs-reader-review/scripts/check-plain.sh | 4 +- .../orm/contract-authoring/psl-syntax.mdx | 62 ++++++------- .../the-contract-artifact.mdx | 90 +++++++++---------- .../contract-authoring/the-data-contract.mdx | 46 +++++----- apps/docs/content/docs/orm/core-concepts.mdx | 2 +- 5 files changed, 101 insertions(+), 103 deletions(-) diff --git a/.claude/skills/docs-reader-review/scripts/check-plain.sh b/.claude/skills/docs-reader-review/scripts/check-plain.sh index d4d2a448d4a..abc7402a94c 100755 --- a/.claude/skills/docs-reader-review/scripts/check-plain.sh +++ b/.claude/skills/docs-reader-review/scripts/check-plain.sh @@ -6,7 +6,7 @@ # "(unexplained)" there (emit, signature, marker, ref, plan) are allowed when the sentence # defines them, which a regex cannot judge; the reader review covers those. # What is skipped: fenced code (``` or ~~~, indented or not), indented code blocks, -# inline code, and any line carrying the marker {/* plain-language:defined */}, which +# inline code, link targets, pinned heading anchors, href attributes, and any line carrying the marker {/* plain-language:defined */}, which # an author adds only when the sentence itself defines the term in plain words. set -u pat='\bterminal (calls?|methods?|verbs?)\b|\b(read|write|query) terminals?\b|\bcodecs?\b|\benvelopes?\b|\btype position\b|\brelease (line|cycle)\b|\bruns ahead\b|\b(sql|raw|orm) lanes?\b|\blane object\b|\bfacades?\b|\bjunction (model|record|table)\b|\bjoin model\b|\bquery plans?\b|\blower(ed|ing)\b|\bnamespace-qualified\b|\bnamespace coordinate\b|\breducers?\b|\brefinement callback\b|\bdotted codes?\b|\b(CLI|config) envelope\b|\bdev loop\b|\bbrownfield\b|\bgreenfield\b|\bdeterministic\b|\bidempotent\b|\btopology\b|\bcapabilit(y|ies)\b|\bbuffer(s|ed)? (the|every|all)\b|\bmaterializ(e|es|ed)\b|\bunblocks?\b|\bAPI surface\b|\bsurfaces? (the|a|an|as)\b|\bwires? up\b|\blives at\b|\bhands back\b|\brides on\b|\broutes on\b|\bkeys on\b' @@ -18,7 +18,7 @@ for f in "$@"; do /^( |\t)/ { next } /plain-language:defined/ { next } { print NR": "$0 } - ' "$f" | sed 's/`[^`]*`//g' | grep -E -i "$pat" || true) + ' "$f" | sed -E 's/`[^`]*`//g; s/\]\([^)]*\)/]/g; s/\[#[^]]*\]//g; s/href="[^"]*"//g' | grep -E -i "$pat" || true) if [ -n "$hits" ]; then status=1; echo "== $f"; echo "$hits" | cut -c1-160; fi done if [ $status -eq 0 ]; then echo "plain-language check: clean"; else echo "plain-language check: banned terms found"; fi diff --git a/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx b/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx index 37fd5fd2019..b70d67f89bd 100644 --- a/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx @@ -6,13 +6,13 @@ metaTitle: Author the Prisma ORM contract in PSL metaDescription: 'Learn how to write a Prisma ORM contract in the Prisma schema language, including named types, enums, value objects, relations, and extension types.' --- -PSL is the preferred way to author the Prisma ORM [data contract](/orm/contract-authoring/the-data-contract). You write a Prisma schema file, usually `prisma/contract.prisma`, and [`contract emit`](/cli/contract-emit) turns it into `contract.json` and `contract.d.ts`. +PSL is the preferred way to author [your contract](/orm/contract-authoring/the-data-contract), the `contract.prisma` file that replaced `schema.prisma`. You write a Prisma schema file, usually `src/prisma/contract.prisma`, and [`prisma contract emit`](/cli/contract-emit) writes `contract.json` and `contract.d.ts` beside it. -If you know the Prisma schema language, most of a contract file reads exactly as you expect. This page covers the shared basics briefly and the Prisma ORM 8 additions in detail: named types, typed enums, value objects, base models with variants, and extension types. +If you know the Prisma schema language, most of a contract file reads exactly as you expect. The Prisma ORM 8 additions are named types, typed enums, value objects, base models with variants, and extension types. ## A complete contract -```prisma title="prisma/contract.prisma" tab="PostgreSQL" +```prisma title="src/prisma/contract.prisma" tab="PostgreSQL" // use prisma-8 types { @@ -56,7 +56,7 @@ model Post { } ``` -```prisma title="prisma/contract.prisma" tab="MongoDB" +```prisma title="src/prisma/contract.prisma" tab="MongoDB" // use prisma-8 type Address { @@ -99,11 +99,11 @@ model Post { } ``` -Run `npx prisma@latest contract emit` after any change to refresh the artifacts. +Run `npx prisma contract emit` after any change to refresh the two files. ## Point the config at the schema -The config's `contract` path names the source of truth. A `.prisma` extension selects PSL authoring: +The config's `contract` path names the one file Prisma ORM reads. A `.prisma` extension selects PSL authoring: ```typescript title="prisma.config.ts" tab="PostgreSQL" import { definePrismaConfig } from "prisma/config"; @@ -111,7 +111,7 @@ import { defineConfig as ormConfig } from "@prisma/orm-postgres/config"; export default definePrismaConfig({ orm: ormConfig({ - contract: "./prisma/contract.prisma", + contract: "./src/prisma/contract.prisma", }), }); ``` @@ -122,21 +122,21 @@ import { defineConfig as ormConfig } from "@prisma/orm-mongo/config"; export default definePrismaConfig({ orm: ormConfig({ - contract: "./prisma/contract.prisma", + contract: "./src/prisma/contract.prisma", }), }); ``` -Scaffolded projects (`npm create prisma@latest`) already contain this wiring and a starter schema. +Projects created with `npm create prisma@latest -- my-app` already have this config and a starter contract file. ## Models and fields Models declare fields with a type, an optional `?` marker, and attributes: -- `@id` marks the primary key; `@@id([a, b])` declares a composite key. -- `@unique` adds a unique constraint; `@@index([...])` declares a secondary index. -- `@default(...)` sets a default. Database function defaults such as `@default(now())` become column defaults in the database. Generated defaults such as `@default(uuid())` are applied by Prisma ORM before each write instead, so they work the same on every database. They appear in the contract's `execution` section rather than as DDL. -- `@map("column_name")` sets a field's physical name; `@@map("table_name")` sets the table or collection name when it differs from the model name. +- `@id` marks the primary key. `@@id([a, b])` declares a composite key. +- `@unique` adds a unique constraint. `@@index([...])` declares a secondary index. +- `@default(...)` sets a default. Database function defaults such as `@default(now())` become column defaults in the database. Generated defaults such as `@default(uuid())` are applied by Prisma ORM before each write. They appear in the contract's `execution` section rather than as a column default. +- `@map("column_name")` sets a field's column name in the database. `@@map("table_name")` sets the table or collection name when it differs from the model name. How IDs map differs by database: @@ -150,7 +150,7 @@ model User { } ``` -The primary key is an ordinary column; pick its type and default yourself. +The primary key is an ordinary column. Pick its type and default yourself. @@ -162,7 +162,7 @@ model User { } ``` -The primary key is MongoDB's `_id`: type it `ObjectId` and map it to the physical `_id` key. +The primary key is MongoDB's `_id`: type it `ObjectId` and map it to `_id`. @@ -178,11 +178,11 @@ types { } ``` -Fields then use `ShortName` like any built-in type. The alias keeps the storage decision (a `varchar(35)` column rather than `text`) in one place. Native PostgreSQL types are written in type position like this, so a field can also use `VarChar(35)`, `Uuid`, or `Timestamptz` directly without an alias. +Fields then use `ShortName` like any built-in type. The alias keeps the storage decision (a `varchar(35)` column rather than `text`) in one place. Native PostgreSQL types are written as the type, so a field can also use `VarChar(35)`, `Uuid`, or `Timestamptz` directly without an alias. ## Enums -An enum in Prisma ORM declares its storage codec with `@@type` and, optionally, the stored value for each member: +An enum declares how its values are stored with `@@type`, and optionally the stored value for each member: ```prisma enum Priority { @@ -193,11 +193,11 @@ enum Priority { } ``` -`@@type("pg/text@1")` stores the values as `text` on PostgreSQL. When a member has no explicit value, the member name itself is stored. In the database, Prisma ORM enforces the allowed values with a `CHECK` constraint on each column that uses the enum. +`@@type("pg/text@1")` stores the values as `text` on PostgreSQL. That name is the PostgreSQL type plus a version, always `@1` today. When a member has no explicit value, the member name itself is stored. Leave `@@type` out and Prisma ORM infers it from the member values. In the database, Prisma ORM enforces the allowed values with a `CHECK` constraint on each column that uses the enum. ## Value objects -A `type` block declares a value object: a structured value that belongs to its parent record and has no identity or table of its own. +A `type` block declares a value object: a structured value stored inside its parent row, with no table of its own. ```prisma type Address { @@ -213,11 +213,11 @@ model User { } ``` -Storage differs by database: on PostgreSQL a value object field lives in a single `jsonb` column, and on MongoDB it is an embedded document. Either way, `contract.d.ts` types it as a structured object rather than untyped JSON. On MongoDB, whether to embed or reference is the central modeling decision; [MongoDB data modeling](/orm/data-modeling/mongodb#embed-or-reference) covers it. +Storage differs by database. On PostgreSQL a value object field is stored in a single `jsonb` column. On MongoDB it is an embedded document. Either way, `contract.d.ts` types it as a structured object rather than untyped JSON. On MongoDB, whether to embed or reference is the central modeling decision. [MongoDB data modeling](/orm/data-modeling/mongodb#embed-or-reference) covers it. ## Relations -Relations use the `@relation` syntax you know from Prisma ORM. The side that holds the foreign key declares the scalar field and the mapping; the other side declares a list: +Relations use the `@relation` syntax you know from Prisma ORM. The side that holds the foreign key declares the scalar field and the mapping. The other side declares a list: ```prisma model Post { @@ -230,7 +230,7 @@ model User { } ``` -Many-to-many relations go through an explicit join model with a composite primary key. The list fields on both sides then resolve through it: +Many-to-many relations go through an explicit model for the join table. Its primary key must cover both foreign keys. The list fields on both sides then resolve through it: ```prisma model Post { @@ -253,7 +253,9 @@ model PostTag { } ``` -The emitted contract records the relation as `N:M` with the join table's columns, so queries can traverse `post.tags` directly. +`contract.json` records the relation as `N:M` with the join table's columns, so queries can traverse `post.tags` directly. + +For which shape to choose and which side owns the foreign key, see [relational data modeling](/orm/data-modeling/relational-databases) and [MongoDB data modeling](/orm/data-modeling/mongodb). ## Base models and variants @@ -282,7 +284,7 @@ Rows with `type = "bug"` are `Bug` records. On PostgreSQL, the variant's `@@map` ## Extension types -Extension packs contribute types through constructor expressions in the `types` block. Compose the pack in the config, then use its types: +Extension packs contribute types through constructor calls in the `types` block. Add the pack to the config, then use its types: ```typescript title="prisma.config.ts" import { definePrismaConfig } from "prisma/config"; @@ -291,13 +293,13 @@ import { defineConfig as ormConfig } from "@prisma/orm-postgres/config"; export default definePrismaConfig({ orm: ormConfig({ - contract: "./prisma/contract.prisma", + contract: "./src/prisma/contract.prisma", extensions: [pgvector], }), }); ``` -```prisma title="prisma/contract.prisma" +```prisma title="src/prisma/contract.prisma" types { Embedding1536 = pgvector.Vector(1536) } @@ -308,17 +310,15 @@ model Post { } ``` -`contract emit` validates extension types against the packs composed in the config: a type from a pack that is not listed fails the emit with a diagnostic. Re-run `contract emit` after changing the extension list. +`prisma contract emit` checks extension types against the packs listed in the config. A type from a pack that is not listed fails with an error whose `code` is `CONFIG.MISSING_EXTENSION_PACKS`. Run `npx prisma contract emit` again after changing the extension list. ## Starting from an existing database If the database already exists, don't write the schema by hand. [`contract infer`](/cli/contract-infer) reads the live schema and writes a starter `contract.prisma` for you to review and edit. -The syntax above declares relations. For which shape to choose and which side owns the foreign key, see [relational data modeling](/orm/data-modeling/relational-databases) and [MongoDB data modeling](/orm/data-modeling/mongodb). - ## Prompt your coding agent -Projects scaffolded with `create-prisma@latest` install [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent. The `prisma-8` skill covers this page. Ask your agent to: +Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent; in an existing project, run `npx prisma skills sync`. The `prisma-8` skill covers PSL authoring. Ask your agent to: - "Using the prisma-8 skill, add a Status enum stored as text and use it on the Order model." - "Add a one-to-many between User and Post with the foreign key on Post." @@ -326,6 +326,6 @@ Projects scaffolded with `create-prisma@latest` install [Prisma ORM skills](/ai/ ## Next steps -- Emit and inspect [the artifacts](/orm/contract-authoring/the-contract-artifact) the schema produces. +- Run `npx prisma contract emit` and inspect [`contract.json` and `contract.d.ts`](/orm/contract-authoring/the-contract-artifact). - If you prefer defining models in code, see [authoring in TypeScript](/orm/contract-authoring/typescript-schema-builder). - Apply the contract to a database with [`db init`](/cli/db-init) or plan changes with [`migration plan`](/cli/migration-plan). diff --git a/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx b/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx index 85b9c675fab..0a1122b4469 100644 --- a/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx @@ -1,45 +1,43 @@ --- -title: The emitted artifacts -description: contract.json and contract.d.ts are the deterministic artifacts every other part of Prisma ORM consumes. Here is what is inside them. +title: contract.json and contract.d.ts +description: 'contract.json and contract.d.ts are the two files every other part of Prisma ORM reads. Here is what is inside them.' url: /orm/contract-authoring/the-contract-artifact metaTitle: The Prisma ORM contract artifacts -metaDescription: 'What contract.json and contract.d.ts contain, how deterministic emission and content hashes work, and how the runtime and tooling consume the artifacts.' +metaDescription: 'What contract.json and contract.d.ts contain, why the same source always produces the same two files, how the hashes work, and how your app and the CLI read them.' --- -Emitting your contract produces two generated files. This page explains what is in each, why they are deterministic, and how their hashes keep your code and your database in agreement. +[`prisma contract emit`](/cli/contract-emit) writes `contract.json` and `contract.d.ts` beside the source by default: -[`contract emit`](/cli/contract-emit) writes both next to the source by default: - -| File | Contents | Consumed by | +| File | Contents | Read by | | --- | --- | --- | -| `contract.json` | The canonical, machine-readable contract: models, storage, capabilities, and content hashes | The runtime, migration tooling, verification, and anything else that needs to read your schema | +| `contract.json` | The machine-readable contract: models, how they are stored, which database features they need, and the hashes | `db`, the migration tooling, the database checks, and anything else that needs to read your schema | | `contract.d.ts` | TypeScript declarations derived from the contract | The query APIs and your application code, for typed models and results | -Both files are generated. Do not edit them; change the [PSL](/orm/contract-authoring/psl-syntax) or [TypeScript](/orm/contract-authoring/typescript-schema-builder) source and re-run `contract emit`. The files carry a generated-file notice that says exactly this. +Both files are generated. Do not edit them. Change the [PSL](/orm/contract-authoring/psl-syntax) or [TypeScript](/orm/contract-authoring/typescript-schema-builder) source and run `prisma contract emit` again. Each file starts with a notice that says exactly this. -## Deterministic emission +## The same source always produces the same two files [#deterministic-emission] -Emission is deterministic: the same source produces byte-identical artifacts on every machine and every run. Keys are written in canonical order and values in normalized form, which is what makes the artifacts diffable in code review and hashable for verification. +Run `prisma contract emit` on the same source on any machine and you get byte-identical files. Keys are written in a fixed key order and values in a normalized form, which is what lets you read the files in a code review and hash them. -Determinism is also why the contract source must stay pure. A schema that read the clock or the environment would emit differently each time, and every hash-based guarantee below would collapse. The [TypeScript authoring page](/orm/contract-authoring/typescript-schema-builder#keep-the-contract-file-pure) lists the concrete rules. +That is also why the contract source must not read the environment, the clock, or random values. A source that did would produce different files each run, and every hash below would stop meaning anything. The [TypeScript authoring page](/orm/contract-authoring/typescript-schema-builder#keep-the-contract-file-pure) lists the rules. ## The content hashes -Emission computes hashes over distinct parts of the contract. Each answers a different question: +`prisma contract emit` computes a hash over each of these parts of the contract: | Hash | Covers | Changes when | | --- | --- | --- | -| `storageHash` | Models, fields, relations, and the full storage layout: tables, columns, keys, indexes | Any schema change | -| `executionHash` | Defaults Prisma ORM applies before writes, such as `uuid()` generators | Generated defaults change | -| `profileHash` | The profile the contract is built for: the target database and its family | The contract targets a different database | +| `storageHash` | The storage layout: tables, columns, primary keys, uniques, indexes, foreign keys, and the value sets behind enums | A table, column, key, constraint, or index changes | +| `executionHash` | The defaults Prisma ORM applies before a write, such as `@default(uuid())` | A generated default changes | +| `profileHash` | The database this contract targets, and its family | The contract targets a different database | -The hashes are how Prisma ORM connects the contract to a live database. [`db sign`](/cli/db-sign) checks that the database satisfies the contract and records the hashes in a marker inside the database. From then on, [`db verify`](/cli/db-verify) and the runtime compare the contract they hold against the marker, so a stale deploy or an unmigrated database is caught by verification rather than by a failing query. +The hashes are how Prisma ORM connects the contract to a live database. [`prisma db sign`](/cli/db-sign) checks that the database satisfies the contract, then records `storageHash` and `profileHash` in a small table it creates in the database. [`prisma db verify`](/cli/db-verify) compares the contract you hold against that record and against the live schema, and fails when they disagree. `db` makes the same comparison before its first query, but it logs a warning and carries on. ## Inside `contract.json` -The contract separates what your application models from how it is stored. The `domain` section describes models, fields, and relations; the `storage` section describes tables, columns, keys, and indexes (or collections and indexes for a MongoDB contract); each model's `storage` block bridges the two. Everything is grouped by namespace (on PostgreSQL, the schema, typically `public`). +The contract separates what your application models from how it is stored. The `domain` section describes models, fields, and relations; the `storage` section describes tables, columns, keys, and indexes. On MongoDB it describes collections and indexes instead. Each model's own `storage` block joins the two. Everything is grouped by namespace. On PostgreSQL the namespace is the schema, `public` unless you set one. -An abridged emit of a `User`/`Post` schema: +An abridged `contract.json` for a `User` and `Post` schema: ```json title="prisma/contract.json (abridged)" { @@ -48,8 +46,8 @@ An abridged emit of a `User`/`Post` schema: "target": "postgres", "profileHash": "…", "roots": { - "user": { "namespace": "public", "model": "User" }, - "post": { "namespace": "public", "model": "Post" } + "post": { "model": "Post", "namespace": "public" }, + "user": { "model": "User", "namespace": "public" } }, "domain": { "namespaces": { @@ -57,20 +55,20 @@ An abridged emit of a `User`/`Post` schema: "models": { "User": { "fields": { - "id": { "nullable": false, "type": { "kind": "scalar", "codecId": "pg/uuid@1" } }, - "email": { "nullable": false, "type": { "kind": "scalar", "codecId": "pg/text@1" } } + "email": { "nullable": false, "type": { "codecId": "pg/text@1", "kind": "scalar" } }, + "id": { "nullable": false, "type": { "codecId": "pg/uuid@1", "kind": "scalar" } } }, "relations": { "posts": { "cardinality": "1:N", - "to": { "namespace": "public", "model": "Post" }, - "on": { "localFields": ["id"], "targetFields": ["userId"] } + "on": { "localFields": ["id"], "targetFields": ["userId"] }, + "to": { "model": "Post", "namespace": "public" } } }, "storage": { + "fields": { "email": { "column": "email" }, "id": { "column": "id" } }, "namespaceId": "public", - "table": "user", - "fields": { "id": { "column": "id" }, "email": { "column": "email" } } + "table": "user" } } } @@ -78,22 +76,22 @@ An abridged emit of a `User`/`Post` schema: } }, "storage": { - "storageHash": "…", "namespaces": { "public": { "entries": { "table": { "user": { "columns": { - "id": { "nativeType": "uuid", "codecId": "pg/uuid@1", "nullable": false }, - "email": { "nativeType": "text", "codecId": "pg/text@1", "nullable": false } + "email": { "codecId": "pg/text@1", "nativeType": "text", "nullable": false }, + "id": { "codecId": "pg/uuid@1", "nativeType": "uuid", "nullable": false } }, "primaryKey": { "columns": ["id"] } } } } } - } + }, + "storageHash": "…" }, "execution": { "executionHash": "…" }, "capabilities": { "postgres": { "returning": true } }, @@ -104,11 +102,11 @@ An abridged emit of a `User`/`Post` schema: The sections, top to bottom: - **`schemaVersion`, `targetFamily`, `target`**: the contract format version and the database this contract targets. -- **`roots`**: the accessor names your queries start from, each mapping to a model. The SQL builder's `db.sql.public.user` exists because `roots.user` points at `User`, and the model it names is what the ORM accessor `db.orm.public.User` hangs off. -- **`domain`**: the application's view. Each field carries `nullable` and a `codecId` such as `pg/text@1`, which names the codec that encodes and decodes values of that type. Relations record cardinality and the fields they join on. The model's `storage` block maps fields to columns. -- **`storage`**: the database's view: tables with columns (native type plus codec), primary keys, uniques, indexes, foreign keys, and value sets backing enums. This is the section migration tooling diffs and `db verify` checks the live schema against. -- **`execution`**: defaults Prisma ORM applies before writes, such as UUID generation, kept out of the database's DDL. -- **`capabilities`** and **`extensions`**: the database and extension features available to this contract, merged from the target, adapter, and extension packs at emit time. Query APIs consult these before using gated features; see [Capabilities](/orm/contract-authoring/capabilities). +- **`roots`**: each table name mapped to the model stored in it. The SQL query builder is keyed by those table names, as in `db.sql.public.user`, and the model name is what `db.orm.public.User` uses. +- **`domain`**: the application's view. Each scalar field records whether it is nullable and its type id, such as `pg/text@1`, the PostgreSQL type plus a version, always `@1` today. Relations record how many records are on each side and the fields they join on. The model's `storage` block maps fields to columns. +- **`storage`**: the database's view. Tables with their columns, each with a native type and a type id, plus primary keys, uniques, indexes, foreign keys, check constraints, and the value sets behind enums. This is the section the migration tooling compares between versions and `prisma db verify` checks the live schema against. +- **`execution`**: the defaults Prisma ORM applies before a write, such as `@default(uuid())`. These are not column defaults in the database. +- **`capabilities`** and **`extensions`**: which database features this contract can use, collected from the adapter for your database and any extension packs when you run `prisma contract emit`. The query APIs check these before building a query that needs one. See [which database features the contract needs](/orm/contract-authoring/capabilities). ## Inside `contract.d.ts` @@ -128,11 +126,11 @@ export type AddressOutput = { }; ``` -Because the hashes are literal types, a client built against one contract version is type-incompatible with another version's artifacts. Type checking catches a mismatched contract before verification has to. +Because the hashes are literal types, a client built against one version of the contract will not type-check against another version's files. TypeScript catches a mismatched contract before `prisma db verify` has to. ## How the application consumes the artifacts -The runtime client is constructed from both files: `contract.json` as the value, `contract.d.ts` as the type. +`db` is built from both files: `contract.json` as the value, `contract.d.ts` as the type. `prisma orm init` writes `src/prisma/db.ts` for you. ```typescript title="src/prisma/db.ts" import postgres from "@prisma/orm-postgres/runtime"; @@ -144,22 +142,22 @@ export const db = postgres({ }); ``` -At startup, the client carries the contract's hashes and verifies them against the database marker before executing queries. +Before its first query, `db` reads the record `prisma db sign` wrote in the database and compares it with the contract's hashes. If they do not match, it logs a warning and runs the query anyway. Run `prisma db verify` when you need the check to fail instead. ## Version control -Commit the artifacts alongside the source. They contain structure only, no data and no credentials, and committed artifacts let teammates, CI, and deploys consume the contract without re-running emission. Re-run `contract emit` after every source change so the committed artifacts never trail the source; a CI job can enforce this by running the emit and failing if the working tree changes. +Commit `contract.json` and `contract.d.ts` alongside the source. They hold structure only, no data and no credentials. Committing them lets teammates, CI, and deploys read the contract without running `prisma contract emit` first. Run `prisma contract emit` after every source change so the two files never trail the source. A CI job can enforce this by running the command and failing if the working tree changes. ## Prompt your coding agent -Projects scaffolded with `create-prisma@latest` install [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent; the `prisma-8` skill covers this page. Ask your agent to: +Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent; in an existing project, run `npx prisma skills sync`. The `prisma-8` skill covers the contract. Ask your agent to: - "Explain the difference between contract.json and contract.d.ts in this project." -- "Re-emit the contract and show me what changed in the artifact." +- "Run prisma contract emit and show me what changed in contract.json." ## Next steps -- Learn how the contract's [capabilities](/orm/contract-authoring/capabilities) gate database features. -- Apply the contract to a fresh database with [`db init`](/cli/db-init). -- Check a live database against the contract with [`db verify`](/cli/db-verify). -- Plan schema changes between contract versions with [`migration plan`](/cli/migration-plan). +- Learn [which database features](/orm/contract-authoring/capabilities) the contract needs, and how Prisma ORM checks that your database has them. +- Apply the contract to a fresh database with [`prisma db init`](/cli/db-init). +- Check a live database against the contract with [`prisma db verify`](/cli/db-verify). +- Plan schema changes between contract versions with [`prisma migration plan`](/cli/migration-plan). diff --git a/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx b/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx index 570dd9d7fb7..3736ca09cc8 100644 --- a/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx @@ -1,12 +1,12 @@ --- title: The data contract -description: 'The data contract is the single description of your data model and its storage layout. Everything in Prisma ORM is typed, planned, and verified against it.' +description: 'The data contract is the one description of your data model and how it is stored. Prisma ORM types your queries, plans your migrations, and checks your database against it.' url: /orm/contract-authoring/the-data-contract metaTitle: The Prisma ORM data contract -metaDescription: 'What the Prisma ORM data contract is, how PSL and TypeScript authoring produce the same artifacts, and how the contract drives typing, migrations, and verification.' +metaDescription: 'What the Prisma ORM data contract is, how PSL and TypeScript authoring produce the same two files, and how the contract drives typing, migrations, and checking your database.' --- -Every Prisma ORM project has one description of its data: the models, their fields, and how they map to database tables. That description is the data contract. +Every Prisma ORM project has one description of its data: the models, their fields, and how they map to database tables. That description is the data contract, the `contract.prisma` file that replaced `schema.prisma`. For example, a blog's contract declares a `User` and a `Post`, the fields each carries, and how they relate. You author it in PSL, the Prisma schema language: @@ -28,9 +28,9 @@ model Post { } ``` -The first line of every `.prisma` contract is `// use prisma-8`. It marks the file as a Prisma ORM 8 contract rather than a Prisma ORM 7 schema: the Prisma language server only offers diagnostics, completion, and formatting on files that carry it, and excludes unmarked files from its schema composition. Completion covers field, model, and block attribute names and their argument keys, taken from the installed target and extensions. Contracts written before `8.0.0-rc.10` start with `// use prisma-next`; the language server still accepts that line and replaces it when you format the file. +The first line of every `.prisma` contract is `// use prisma-8`. It marks the file as a Prisma ORM 8 contract rather than a Prisma ORM 7 schema. The Prisma language server only offers diagnostics, completion, and formatting on files that carry it. Completion covers field, model, and block attribute names and their argument keys, taken from the installed target and extensions. Contracts written before `8.0.0-rc.10` start with `// use prisma-next`. The language server still accepts that line and replaces it when you format the file. -Prisma ORM compiles this into a machine-readable artifact, and the rest of the toolchain works against it: queries are type-checked against the contract, migrations are planned as changes to it, and the database is verified against it before your code runs. +`prisma contract emit` turns this source into two machine-readable files, and the rest of the toolchain works against them. Your queries are type-checked against the contract, migrations are planned as changes to it, and `prisma db verify` checks a live database against it. Think of it as a `package-lock.json` for your data: an exact, versioned record of what your application expects from its database. @@ -42,11 +42,11 @@ In Prisma ORM, the **contract** is what you write in your code, and the **schema ## Why a contract -Prisma ORM's current architecture compiles your schema into generated client code. The schema knowledge exists, but it is buried in code that only the client itself can interpret. +Prisma ORM 7 compiles your schema into generated client code. The description of your data is in there, but only the generated client can read it. -Prisma ORM keeps the schema knowledge in the open. Your contract source compiles to two plain files: `contract.json`, a canonical JSON description of models, storage, and capabilities, and `contract.d.ts`, the TypeScript types derived from it. Both are deterministic: the same source always produces byte-identical output, so the artifacts can be diffed in code review, hashed for verification, and read directly by tools and AI agents. +Prisma ORM keeps that description in the open. Your contract source compiles to two plain files. `contract.json` describes your models, how they are stored, and which database features the contract needs. `contract.d.ts` holds the TypeScript types derived from it. The same source always produces the same two files, so you can read them in a code review, hash them, and hand them to tools and coding agents. -The contract also identifies the exact schema version it describes. When you emit the contract, Prisma ORM computes hashes over its content, and [`db sign`](/cli/db-sign) records them in a small marker inside the database. Before executing queries, Prisma ORM compares the contract it was built against with the marker in the database it is talking to. If they do not match, for example after a deploy against an unmigrated database, verification fails before any query runs. +The contract also identifies the exact version of the schema it describes. `prisma contract emit` computes hashes over its contents. When you run [`prisma db sign`](/cli/db-sign), Prisma ORM records which contract the database matches in a small table it creates in the database. The client reads that record before its first query and logs a warning if the record does not match the contract the client holds. To fail instead of warn, for example in a deploy check, run [`prisma db verify`](/cli/db-verify). ## How it works @@ -65,25 +65,25 @@ export default definePrismaConfig({ The source is either a Prisma schema file (`contract.prisma`) or a TypeScript file (`contract.ts`). The file extension selects the authoring mode. Both modes describe the same things: models with fields and relations, the tables and columns they map to, named types, enums, and any extension-provided types. -Emitting turns the source into the artifacts: +Run `prisma contract emit` after every change to the contract. It writes `contract.json` and `contract.d.ts` beside the source: ```npm -npx prisma@latest contract emit +npx prisma contract emit ``` -This writes `contract.json` and `contract.d.ts` next to the source. From there, the rest of the toolchain takes over: +From there, the rest of the toolchain takes over: - The query APIs load `contract.d.ts` to give you typed models, fields, and results. -- The runtime receives `contract.json` and verifies its hashes against the database marker. -- The migration tooling diffs two contracts to plan schema changes, and [`db verify`](/cli/db-verify) checks a live database against the current contract. +- `db` reads `contract.json` and compares its hashes with the record `prisma db sign` wrote in the database. +- The migration tooling compares two contracts to plan schema changes, and [`prisma db verify`](/cli/db-verify) checks a live database against the current contract. -## Two authoring modes, one artifact +## Two authoring modes, one `contract.json` [#two-authoring-modes-one-artifact] -**[PSL](/orm/contract-authoring/psl-syntax)** is the preferred way to author the contract: a compact language purpose-built for describing data. It is what `create-prisma` scaffolds, what [`contract infer`](/cli/contract-infer) writes when you start from an existing database, and what the examples throughout these docs use. +**[PSL](/orm/contract-authoring/psl-syntax)** is the usual way to write the contract. It is a compact language for describing data. It is what `npm create prisma@latest` scaffolds, and what [`prisma contract infer`](/cli/contract-infer) writes when you start from an existing database. For the cases PSL does not cover, define models with the **[TypeScript builder](/orm/contract-authoring/typescript-schema-builder)** instead. Reach for it when model definitions must be composed, generated, or shared as ordinary TypeScript modules. -Both modes produce the same contract. For an equivalent schema they emit the same `contract.json`, so migrations, verification, and the query APIs behave the same no matter which mode you write. You give up nothing by staying with PSL. A project declares exactly one source of truth: the file named by `contract` in the config. Keep the other form out of the project, or treat it as a generated reference, so the two can never disagree. +Both modes produce the same contract. For an equivalent schema they write the same `contract.json`, so migrations, database checks, and the query APIs behave the same no matter which mode you write. You give up nothing by staying with PSL. A project has exactly one contract file, the one named by `contract` in the config. Keep the other form out of the project, or treat it as a generated reference, so the two can never disagree. ## What the contract contains @@ -91,18 +91,18 @@ The contract describes structure, not data: - models, fields, and relations, plus how they map to tables and columns - storage details: primary keys, unique constraints, indexes, and foreign keys -- named types, enums, and value objects -- types and capabilities contributed by extension packs, such as pgvector's `Vector` -- content hashes that identify this exact version of the schema +- named types, enums, and `type` blocks, each a structured value stored inside its parent row with no table of its own +- the types and database features that extension packs add, such as pgvector's `Vector` +- hashes that identify this exact version of the schema -It contains no rows, no credentials, and no connection details, so committing the source and the emitted artifacts to version control is safe and expected. +It contains no rows, no credentials, and no connection details, so committing the source, `contract.json`, and `contract.d.ts` to version control is safe and expected. ## Prompt your coding agent -Projects scaffolded with `create-prisma@latest` install [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent. The `prisma-8` skill covers this page. Ask your agent to: +Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent; in an existing project, run `npx prisma skills sync`. The `prisma-8` skill covers the contract. Ask your agent to: - "Using the prisma-8 skill, explain what our contract.json currently declares." -- "Add an Invoice model to the contract and emit it." +- "Add an Invoice model to the contract and run prisma contract emit." - "Check whether our database still satisfies the contract." ## Next steps @@ -127,7 +127,7 @@ Projects scaffolded with `create-prisma@latest` install [Prisma ORM skills](/ai/ } > What is inside contract.json and contract.d.ts, and how the hashes work. diff --git a/apps/docs/content/docs/orm/core-concepts.mdx b/apps/docs/content/docs/orm/core-concepts.mdx index 499c3724c07..a8640b5440e 100644 --- a/apps/docs/content/docs/orm/core-concepts.mdx +++ b/apps/docs/content/docs/orm/core-concepts.mdx @@ -58,7 +58,7 @@ Every other part of the toolchain reads these artifacts, not your source file. T Emission is deterministic. The same source always produces byte-identical artifacts, so both files are committed to version control and diff cleanly in code review. Think of the pair like `package.json` and `package-lock.json`: the source is what you ask for, the artifacts are the exact resolved result. -See [The emitted artifacts](/orm/contract-authoring/the-contract-artifact) for what is inside each file. +See [contract.json and contract.d.ts](/orm/contract-authoring/the-contract-artifact) for what is inside each file. ## Hashes and the database signature From 0d4ac989320479bb6142e01db95dcaba54844234 Mon Sep 17 00:00:00 2001 From: willbot Date: Mon, 14 Sep 2026 09:08:28 +0200 Subject: [PATCH 04/14] docs(orm8): contract-authoring pages, banned terms replaced and facts checked against rc.10 (part two) The TypeScript builder and the capabilities pages, plus the three checker reports. The capabilities page is now titled Supported database features, with its old heading anchors pinned. A relation named by a string is caught when prisma contract emit builds the contract, not at compile time. SQLite reports lateral and enums as false rather than leaving them out. The query-time error code ORM.CAPABILITY_MISSING is named. The CONFIG.MISSING_EXTENSION_PACKS claim on the PSL page is gone, since nothing in rc.10 raises it. Co-Authored-By: Claude Fable 5.1 Signed-off-by: willbot Signed-off-by: Will Madden --- .../docs-reader-review/scripts/check-plain.sh | 3 +- .../orm/contract-authoring/capabilities.mdx | 61 ++++---- .../orm/contract-authoring/psl-syntax.mdx | 2 +- .../contract-authoring/the-data-contract.mdx | 2 +- .../typescript-schema-builder.mdx | 50 +++--- apps/docs/content/docs/orm/core-concepts.mdx | 2 +- .../factcheck1.psl-syntax.md | 114 ++++++++++++++ ...ctcheck1.the-data-contract-and-artifact.md | 104 +++++++++++++ .../factcheck1.typescript-and-capabilities.md | 145 ++++++++++++++++++ 9 files changed, 424 insertions(+), 59 deletions(-) create mode 100644 docs/orm-docs-audit/c21-contract-authoring/factcheck1.psl-syntax.md create mode 100644 docs/orm-docs-audit/c21-contract-authoring/factcheck1.the-data-contract-and-artifact.md create mode 100644 docs/orm-docs-audit/c21-contract-authoring/factcheck1.typescript-and-capabilities.md diff --git a/.claude/skills/docs-reader-review/scripts/check-plain.sh b/.claude/skills/docs-reader-review/scripts/check-plain.sh index abc7402a94c..8c8cdef3310 100755 --- a/.claude/skills/docs-reader-review/scripts/check-plain.sh +++ b/.claude/skills/docs-reader-review/scripts/check-plain.sh @@ -6,7 +6,7 @@ # "(unexplained)" there (emit, signature, marker, ref, plan) are allowed when the sentence # defines them, which a regex cannot judge; the reader review covers those. # What is skipped: fenced code (``` or ~~~, indented or not), indented code blocks, -# inline code, link targets, pinned heading anchors, href attributes, and any line carrying the marker {/* plain-language:defined */}, which +# inline code, link targets, pinned heading anchors, href attributes, the front-matter url line, and any line carrying the marker {/* plain-language:defined */}, which # an author adds only when the sentence itself defines the term in plain words. set -u pat='\bterminal (calls?|methods?|verbs?)\b|\b(read|write|query) terminals?\b|\bcodecs?\b|\benvelopes?\b|\btype position\b|\brelease (line|cycle)\b|\bruns ahead\b|\b(sql|raw|orm) lanes?\b|\blane object\b|\bfacades?\b|\bjunction (model|record|table)\b|\bjoin model\b|\bquery plans?\b|\blower(ed|ing)\b|\bnamespace-qualified\b|\bnamespace coordinate\b|\breducers?\b|\brefinement callback\b|\bdotted codes?\b|\b(CLI|config) envelope\b|\bdev loop\b|\bbrownfield\b|\bgreenfield\b|\bdeterministic\b|\bidempotent\b|\btopology\b|\bcapabilit(y|ies)\b|\bbuffer(s|ed)? (the|every|all)\b|\bmaterializ(e|es|ed)\b|\bunblocks?\b|\bAPI surface\b|\bsurfaces? (the|a|an|as)\b|\bwires? up\b|\blives at\b|\bhands back\b|\brides on\b|\broutes on\b|\bkeys on\b' @@ -17,6 +17,7 @@ for f in "$@"; do infence { next } /^( |\t)/ { next } /plain-language:defined/ { next } + /^url: / { next } { print NR": "$0 } ' "$f" | sed -E 's/`[^`]*`//g; s/\]\([^)]*\)/]/g; s/\[#[^]]*\]//g; s/href="[^"]*"//g' | grep -E -i "$pat" || true) if [ -n "$hits" ]; then status=1; echo "== $f"; echo "$hits" | cut -c1-160; fi diff --git a/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx b/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx index 61e8d74dca3..f6164838e06 100644 --- a/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx @@ -1,18 +1,18 @@ --- -title: Capabilities -description: 'Capabilities record what your database stack supports, so Prisma ORM can reject unsupported features early with a clear error.' +title: Supported database features +description: 'The contract records which database features your setup supports, so Prisma ORM can reject an unsupported one early with a clear error.' url: /orm/contract-authoring/capabilities -metaTitle: Capabilities in Prisma ORM -metaDescription: 'How Prisma ORM capabilities are derived from the target, adapter, and extension packs, what they gate, and what happens when a required capability is missing.' +metaTitle: Supported database features in Prisma ORM +metaDescription: 'How a Prisma ORM contract records which database features your setup supports, where those declarations come from, what each key controls, and what happens when one is missing.' --- Not every database stack supports every feature. One setup can run lateral joins, `RETURNING` clauses, and vector distance operations; another cannot. -Capabilities are how the [data contract](/orm/contract-authoring/the-data-contract) records what yours supports. Prisma ORM checks them before using a gated feature. An unsupported feature fails early with an error that names the missing capability, instead of surfacing as a database error mid-query. +The `capabilities` section of your contract, the `contract.prisma` file that replaced `schema.prisma`, records which features yours supports. Prisma ORM checks it before it uses one of them. A feature your stack lacks fails early with an error that names the missing key, instead of reaching the database and failing there. -## Where capabilities come from +## Where the `capabilities` section comes from [#where-capabilities-come-from] -You do not write capabilities yourself. When [`contract emit`](/cli/contract-emit) runs, it merges the capability declarations of every component composed in your project: the target (PostgreSQL, SQLite, MongoDB), its adapter and driver, and any extension packs from the config. The merged result lands in the contract's `capabilities` section, grouped by namespace: +You do not write this section yourself. [`npx prisma contract emit`](/cli/contract-emit) writes `contract.json` and `contract.d.ts` beside the source, and while it does so it merges the declarations of every component composed in your project: the target (PostgreSQL, SQLite, MongoDB), its adapter, and any extension packs named in the config. The merged result lands in the contract's `capabilities` section, grouped by namespace: ```json title="prisma/contract.json (excerpt)" { @@ -27,6 +27,7 @@ You do not write capabilities yourself. When [`contract emit`](/cli/contract-emi "returning": true }, "sql": { + "checkConstraint": true, "defaultInInsert": true, "enums": true, "lateral": true, @@ -37,61 +38,61 @@ You do not write capabilities yourself. When [`contract emit`](/cli/contract-emi } ``` -The `sql` namespace holds keys shared across SQL databases; the `postgres` namespace holds PostgreSQL-specific keys. Extension packs contribute keys of their own: composing the pgvector pack is what adds `pgvector.cosine` above. Because the packs declare the keys, adding or removing an extension in `prisma.config.ts` and re-running `contract emit` changes the capability set with no further work. +The `sql` namespace holds keys shared across SQL databases. The `postgres` namespace holds PostgreSQL-specific keys. Extension packs contribute keys of their own: composing the pgvector pack is what adds `pgvector.cosine` above. Because the packs declare the keys, adding or removing an extension in `prisma.config.ts` and running `npx prisma contract emit` again changes the set with no further work. -## What capabilities gate +## What the `capabilities` section controls [#what-capabilities-gate] -Capabilities are checked at two points, both before any SQL reaches the database. +The keys are checked at two points, both before any SQL reaches the database. -**When the contract is built.** If the schema uses a feature the target cannot store, `contract emit` fails with a diagnostic. For example, SQLite does not report the `scalarList` capability, so a scalar list field in a contract targeting SQLite fails to emit: +**When the contract is built.** If the contract uses a feature the target cannot store, `npx prisma contract emit` fails with a diagnostic. SQLite does not report `scalarList`, so a scalar list field in a contract targeting SQLite fails: ```text Field "User.tags" is a scalar list, but target "sqlite" does not support scalar lists (the adapter does not report the "scalarList" capability). ``` -**When a query is built.** Query-builder methods that depend on a capability check the contract's `capabilities` section and throw if the key is missing, naming the method and the capability: +**When a query is built.** Query-builder methods that need a key read the contract's `capabilities` section and throw an error whose `code` is `ORM.CAPABILITY_MISSING`, naming the method and the key: ```text distinctOn() requires capability postgres.distinctOn lateralJoin() requires capability sql.lateral ``` -The error fires when your code constructs the query, so a test that builds the query catches it: a query that uses a feature your stack lacks cannot be built at all. +The error is raised when your code constructs the query, so a test that builds the query catches it. A query that uses a feature your stack lacks cannot be built at all. -## Example capabilities +## Example keys [#example-capabilities] -A few of the keys the built-in components declare, to give a sense of the granularity: +A few of the keys the built-in components declare: -| Capability | Gates | +| Key | What it covers | | --- | --- | -| `sql.lateral` | Lateral joins (`lateralJoin()`); PostgreSQL declares it, SQLite does not | +| `sql.lateral` | Lateral joins (`lateralJoin()`); PostgreSQL reports `true`, SQLite reports `false` | | `sql.returning` | `RETURNING` clauses on writes | -| `sql.enums` | Enum value sets enforced in the database; SQLite does not declare it | +| `sql.enums` | Enum value sets enforced in the database; SQLite reports `false` | | `sql.defaultInInsert` | Using `DEFAULT` as a value in multi-row inserts | -| `sql.scalarList` | Scalar list fields in the schema | +| `sql.scalarList` | Scalar list fields in the contract | | `postgres.distinctOn` | `DISTINCT ON` queries (`distinctOn()`) | | `postgres.jsonAgg` | JSON aggregation for nested reads | -| `postgres.pgvector.cosine` | Cosine distance operations, contributed by the pgvector pack | +| `postgres.pgvector.cosine` | Contributed by the pgvector pack, alongside its cosine operations | -This table is a sample, not the full registry. Each target, adapter, and pack ships its own declarations. Your project's emitted `contract.json` is the authoritative list of what your stack supports. +This table is a sample, not the full list. Each target, adapter, and pack ships its own declarations. Your project's emitted `contract.json` is the full list of what your stack supports. -MongoDB currently declares no capability keys: the capability system mostly differentiates SQL targets and their extensions, and the MongoDB pipeline does not yet gate features this way. +MongoDB currently declares no keys: this mechanism mostly separates SQL targets and their extensions, and the MongoDB pipeline does not yet check features this way. -## Capabilities and verification +## The `capabilities` section and `prisma db verify` [#capabilities-and-verification] -Capabilities describe what the composed software stack supports, as declared by the target, adapter, and packs. They are recorded in the contract at emit time rather than probed from the live database, so the same contract behaves identically in every environment. Database-side verification is separate: [`db verify`](/cli/db-verify) checks that the database matches the contract's schema and profile, using the hashes described in [the contract artifact](/orm/contract-authoring/the-contract-artifact#the-content-hashes). +The keys describe what the composed software supports, as declared by the target, the adapter, and the packs. They are recorded in the contract when you run `npx prisma contract emit` rather than read from the live database, so the same contract behaves identically in every environment. Checking the database itself is separate: [`db verify`](/cli/db-verify) compares the database against the contract's schema and profile, using the hashes described in [the contract artifact](/orm/contract-authoring/the-contract-artifact#the-content-hashes). -Extensions are the main source of capabilities beyond the core. [Using extensions](/orm/extensions/using-extensions) shows the full flow from install to first query. +Extensions are the main source of keys beyond the core set. [Using extensions](/orm/extensions/using-extensions) shows the full flow from install to first query. ## Prompt your coding agent -Projects scaffolded with `create-prisma@latest` install [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent. The `prisma-8` skill covers this page. Ask your agent to: +Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent; in an existing project, run `npx prisma skills sync`. The `prisma-8` skill covers this material. Ask your agent to: -- "Which capabilities does our contract currently require, and which package provides each?" -- "Add pgvector to the project and confirm the capability shows up in the emitted contract." +- "Which keys does the `capabilities` section of our contract require, and which package provides each?" +- "Add pgvector to the project and confirm its key shows up in the emitted contract." ## Next steps -- See where the `capabilities` block sits in [the contract artifact](/orm/contract-authoring/the-contract-artifact). -- Compose extension packs, and the capabilities they bring, in the [CLI configuration](/cli/configuration). +- See where the `capabilities` block is in [contract.json and contract.d.ts](/orm/contract-authoring/the-contract-artifact). +- Compose extension packs, and the keys they bring, in the [CLI configuration](/cli/configuration). diff --git a/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx b/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx index b70d67f89bd..1f36935df6a 100644 --- a/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx @@ -310,7 +310,7 @@ model Post { } ``` -`prisma contract emit` checks extension types against the packs listed in the config. A type from a pack that is not listed fails with an error whose `code` is `CONFIG.MISSING_EXTENSION_PACKS`. Run `npx prisma contract emit` again after changing the extension list. +`prisma contract emit` resolves extension types through the packs listed in the config, so list the pack before using its types. Run `npx prisma contract emit` again after changing the extension list. ## Starting from an existing database diff --git a/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx b/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx index 3736ca09cc8..622beaa33c5 100644 --- a/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx @@ -134,7 +134,7 @@ Projects created with `npm create prisma@latest` include the [Prisma ORM skills] } > How Prisma ORM checks that your database supports what the contract diff --git a/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx b/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx index 0fd5cc18bef..c24289eaaff 100644 --- a/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx @@ -1,25 +1,25 @@ --- title: Author in TypeScript -description: 'Define the Prisma ORM contract with a typed builder API instead of a schema file. Same models, same artifacts, no separate language.' +description: 'Define the Prisma ORM contract with a typed builder in TypeScript instead of a schema file. Same models, same emitted files, no separate language.' url: /orm/contract-authoring/typescript-schema-builder metaTitle: Author the Prisma ORM contract in TypeScript -metaDescription: 'Learn how to define a Prisma ORM contract in TypeScript with defineContract, including fields, relations, enums, storage mapping, and extension types.' +metaDescription: 'How to define a Prisma ORM contract in TypeScript with defineContract, including fields, relations, enums, storage mapping, and extension types.' --- -With TypeScript authoring, you define the Prisma ORM [data contract](/orm/contract-authoring/the-data-contract) in code. Instead of a `.prisma` file, you write `prisma/contract.ts` with the `defineContract` builder, and [`contract emit`](/cli/contract-emit) produces exactly the same `contract.json` and `contract.d.ts` a PSL schema would. +With TypeScript authoring you write your contract, the `contract.ts` file that replaced `schema.prisma`, in code rather than in a [`.prisma` file](/orm/contract-authoring/the-data-contract). You write `prisma/contract.ts` with the `defineContract` builder. [`npx prisma contract emit`](/cli/contract-emit) then writes `contract.json` and `contract.d.ts` beside the source, the same two files a PSL contract produces. ## When to choose TypeScript over PSL -[PSL](/orm/contract-authoring/psl-syntax) is the preferred way to author the contract. Both modes emit identical artifacts, so you give up nothing by staying with PSL. The TypeScript builder is an escape hatch for the cases PSL does not cover. Reach for it when: +[PSL](/orm/contract-authoring/psl-syntax) is the preferred way to write the contract. Both forms produce the same two files, so you give up nothing by staying with PSL. Use the TypeScript builder for the cases PSL does not cover. Reach for it when: - model definitions must be split, composed, or reused across ordinary TypeScript modules or packages -- parts of the schema are assembled programmatically from other static definitions (the [purity rules](#keep-the-contract-file-pure) still apply) +- parts of the contract are assembled programmatically from other static definitions -If neither applies, write PSL: it is more compact, and it is what `create-prisma` scaffolds and [`contract infer`](/cli/contract-infer) writes. +If neither applies, write PSL. It is more compact, and it is what [`contract infer`](/cli/contract-infer) writes. ## Point the config at the contract file -The config's `contract` path names the source of truth. A `.ts` extension selects TypeScript authoring: +The config's `contract` path names the one file Prisma ORM reads. A `.ts` extension selects TypeScript authoring: ```typescript title="prisma.config.ts" tab="PostgreSQL" import { definePrismaConfig } from "prisma/config"; @@ -154,19 +154,19 @@ export const contract = defineContract({ }); ``` -Run `npx prisma@latest contract emit` after any change to refresh the artifacts. +Run `npx prisma contract emit` after any change to refresh the two files. The two builders share the same shape but differ where the databases do. On PostgreSQL, fields pick column types and models chain `.sql({ table })` to map storage. On MongoDB, the ID is a `field.objectId()` named `_id`, and the scalar helpers are `field.string()`, `field.int32()`, `field.double()`, `field.bool()`, and `field.date()`. The collection is an inline `collection` option on the model rather than a chained call. ## How `defineContract` works -`defineContract` takes two arguments: an options object and a factory function. +`defineContract` takes an options object, and optionally a factory function after it. -The options object declares what the contract is built from, most importantly `extensions`. The target and database family are already bound by the import: `@prisma/orm-postgres/contract-builder` produces PostgreSQL contracts, so you never name them yourself. +The options object declares what the contract is built from, most importantly `extensions`. The database and its family are already fixed by the import: `@prisma/orm-postgres/contract-builder` produces PostgreSQL contracts, so you never name them yourself. -The factory receives authoring helpers composed from the target and every extension pack you declared: `field` for field definitions, `model` for models, and `type` for pack-provided types. `type.pgvector` in the example exists only because `pgvector` is listed in `extensions`. The factory returns the contract's content: `models`, plus optional `enums` and `types`. +The factory receives authoring helpers composed from your database and every extension pack you declared: `field` for field definitions, `model` for models, `rel` for relations, and `type` for pack-provided types. `type.pgvector` in the example exists only because `pgvector` is listed in `extensions`. The factory returns the contract's content: `models`, plus optional `enums` and `types`. -The MongoDB builder is simpler: it exports `field`, `model`, and `rel` directly, and its `defineContract` takes a single definition object with the `models`, as in the MongoDB tab above. The sections below use the PostgreSQL builder. +The MongoDB builder exports `field`, `model`, and `rel` directly, and its `defineContract` accepts a single definition object holding the `models`, as in the MongoDB tab above. The sections below use the PostgreSQL builder. ## Fields @@ -177,16 +177,16 @@ The MongoDB builder is simpler: it exports `field`, `model`, and `rel` directly, - `field.temporal.createdAt()` and `field.temporal.updatedAt()` for managed timestamps - `field.namedType(x)` for enums and declared named types -The exact helper set comes from the target and the composed extension packs. Every field builder supports chained modifiers: +The exact helper set comes from your database and the extension packs you compose. Every field builder supports chained modifiers: - `.optional()` makes the field nullable. -- `.default(value)` sets a literal default; `.defaultSql(expression)` sets a database function default. -- `.unique()` adds a unique constraint; `.id()` marks the primary key. -- `.column("column_name")` sets the physical column name when it differs from the field name. +- `.default(value)` sets a literal default. `.defaultSql(expression)` sets a database function default. +- `.unique()` adds a unique constraint. `.id()` marks the primary key. +- `.column("column_name")` sets the column name in the database when it differs from the field name. ## Enums -`enumType` declares an enum with an explicit storage codec and members: +`enumType` declares an enum, the type its values are stored as, and its members: ```typescript const Priority = enumType( @@ -213,7 +213,7 @@ Post.relations({ }); ``` -`rel.hasMany(Model, { by })` names the foreign key field on the other model. `rel.belongsTo(Model, { from, to })` maps the local foreign key field to the referenced field. `rel.hasOne` and `rel.manyToMany` cover the remaining shapes. A typo in a relation target is a compile error either way: the PostgreSQL builder takes model objects, and the MongoDB builder takes model names that are type-checked against the declared models. +`rel.hasMany(Model, { by })` names the foreign key field on the other model. `rel.belongsTo(Model, { from, to })` maps the local foreign key field to the referenced field. `rel.hasOne` and `rel.manyToMany` cover the remaining shapes. Both builders also accept the model name as a string. Pass the model object, as above, and a typo is a compile error. Pass a string and the typo is reported when `prisma contract emit` builds the contract. ## Storage mapping @@ -253,30 +253,30 @@ export const contract = defineContract( ); ``` -The same pack must also be composed in `prisma.config.ts` (as `extensions: [pgvector]`, using the pack's `/control` export) so the CLI and runtime agree with the contract. +Add the same pack to `prisma.config.ts` as `extensions: [pgvector]`, importing the pack's `/control` export. The CLI and your app then compose the same set the contract was built with. -## Keep the contract file pure +## Keep the contract file free of changing values [#keep-the-contract-file-pure] -The contract file describes structure. When you emit it, Prisma ORM canonicalizes it to JSON and hashes it, and the same source must always produce the same bytes. That works only if the file is pure data: +The contract file describes structure. `prisma contract emit` writes it to JSON in a fixed key order and hashes it, so the same source always produces the same files. That works only if the file holds plain data: - Do not read `process.env`, the current time, or random values into the contract. A contract that changes per machine or per run breaks hashing and verification. - Keep field values plain: strings, numbers, booleans, and the builder's own objects. Functions, class instances, and `Date` objects do not serialize. -- Keep the file free of side effects. Emission evaluates it to obtain the contract object and nothing else. +- Keep the file free of side effects. `prisma contract emit` imports the file and reads its `default` or `contract` export, and nothing else. Configuration that legitimately varies per environment, such as the database URL, belongs in `prisma.config.ts`, not in the contract. ## Parity with PSL -TypeScript and [PSL](/orm/contract-authoring/psl-syntax) authoring emit the same canonical artifact for an equivalent schema. You can move between the modes without any downstream change. A project still declares exactly one source of truth: the file named in the config. Keep the other form out of the project so the two can never disagree. +TypeScript and [PSL](/orm/contract-authoring/psl-syntax) authoring produce the same `contract.json` and `contract.d.ts` for an equivalent contract. You can move between the two forms without changing anything downstream. A project names exactly one contract file in its config. Keep the other form out of the project so the two can never disagree. ## Prompt your coding agent -Projects scaffolded with `create-prisma@latest` install [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent. The `prisma-8` skill covers this page. Ask your agent to: +Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent; in an existing project, run `npx prisma skills sync`. The `prisma-8` skill covers TypeScript authoring. Ask your agent to: - "Convert this contract.prisma to the TypeScript schema builder." - "Using the prisma-8 skill, add a unique index to the email field in our TypeScript schema." ## Next steps -- Emit and inspect [the artifacts](/orm/contract-authoring/the-contract-artifact) the contract produces. +- Inspect [the two files](/orm/contract-authoring/the-contract-artifact) the contract produces. - Apply the contract to a database with [`db init`](/cli/db-init) or plan changes with [`migration plan`](/cli/migration-plan). diff --git a/apps/docs/content/docs/orm/core-concepts.mdx b/apps/docs/content/docs/orm/core-concepts.mdx index a8640b5440e..09a055a4607 100644 --- a/apps/docs/content/docs/orm/core-concepts.mdx +++ b/apps/docs/content/docs/orm/core-concepts.mdx @@ -119,7 +119,7 @@ Prisma ORM's layering exists for extensibility. Our core remains small, because ## Capabilities -A **capability** is a specific feature that a database may or may not support, such as `RETURNING` clauses or vector indexes. Your contract declares the capabilities it needs; the adapter reports what the connected database provides. Prisma ORM compares the two at startup, so a missing feature surfaces as one clear error when the app boots, instead of as a failed query later. See [Capabilities](/orm/contract-authoring/capabilities) for more information. +A **capability** is a specific feature that a database may or may not support, such as `RETURNING` clauses or vector indexes. Your contract declares the capabilities it needs; the adapter reports what the connected database provides. Prisma ORM compares the two at startup, so a missing feature surfaces as one clear error when the app boots, instead of as a failed query later. See [Supported database features](/orm/contract-authoring/capabilities) for more information. ## Codecs diff --git a/docs/orm-docs-audit/c21-contract-authoring/factcheck1.psl-syntax.md b/docs/orm-docs-audit/c21-contract-authoring/factcheck1.psl-syntax.md new file mode 100644 index 00000000000..c6cd83011ce --- /dev/null +++ b/docs/orm-docs-audit/c21-contract-authoring/factcheck1.psl-syntax.md @@ -0,0 +1,114 @@ +Done. Report below. + +## 1. Banned-term and wording replacements + +| Line | Old | New | +| --- | --- | --- | +| 9 | "author the Prisma ORM [data contract]"; "usually `prisma/contract.prisma`"; "[`contract emit`] turns it into" | "author [your contract], the `contract.prisma` file that replaced `schema.prisma`"; "usually `src/prisma/contract.prisma`"; "[`prisma contract emit`] writes `contract.json` and `contract.d.ts` beside it" | +| 11 | "This page covers the shared basics briefly and the Prisma ORM 8 additions in detail: …" | "The Prisma ORM 8 additions are …" (docs-about-docs commentary cut) | +| 102 | "Run `npx prisma@latest contract emit` … to refresh the artifacts." | "Run `npx prisma contract emit` … to refresh the two files." | +| 106 | "names the source of truth" | "names the one file Prisma ORM reads" | +| 130 | "Scaffolded projects … already contain this wiring and a starter schema." | "Projects created with `npm create prisma@latest -- my-app` already have this config and a starter contract file." | +| 136–137 | semicolons joining clauses | split into separate sentences | +| 139 | "sets a field's physical name" | "sets a field's column name in the database" (and split the semicolon) | +| 153 | "an ordinary column; pick its type" | "an ordinary column. Pick its type" | +| 165 | "map it to the physical `_id` key" | "map it to `_id`" | +| 181 | "written in type position like this" | "written as the type" | +| 185 | "declares its storage codec with `@@type`" | "declares how its values are stored with `@@type`" | +| 200 | "a structured value that belongs to its parent record and has no identity or table of its own" | "a structured value stored inside its parent row, with no table of its own" (convention wording) | +| 216 | one sentence interleaving PostgreSQL and MongoDB; semicolon | separate sentences per database | +| 220 | semicolon joining clauses | split | +| 233 | "an explicit join model" | "an explicit model for the join table" | +| 256 | "The emitted contract records" | "`contract.json` records" | +| 285 | "constructor expressions"; "Compose the pack in the config" | "constructor calls"; "Add the pack to the config" | +| 311 | "fails the emit with a diagnostic" | "fails with an error whose `code` is `CONFIG.MISSING_EXTENSION_PACKS`" | +| 321 | "Projects scaffolded with `create-prisma@latest` install …" | convention sentence: "Projects created with `npm create prisma@latest` include the [Prisma ORM skills] …; in an existing project, run `npx prisma skills sync`." Also "covers this page" → "covers PSL authoring". | +| 329 | "Emit and inspect [the artifacts] the schema produces." | "Run `npx prisma contract emit` and inspect [the two files it writes]." | + +No heading was renamed, so no anchors were pinned. One misplaced paragraph (the "For which shape to choose…" sentence, previously stranded under "Starting from an existing database") was moved to the end of the Relations section. + +`check-plain.sh` prints **clean**. The extra page-convention words are gone (the only remaining "artifact" is inside a URL). + +## 2. Claim-by-claim check + +All line numbers are for the edited file. Source paths are relative to `wip/prisma-src/` (prisma/orm at `cfccb09be2`). + +| Line | Claim | Deciding source | Verdict | +| --- | --- | --- | --- | +| 9 | Contract file is `contract.prisma`; default location | `packages/1-framework/1-core/config/src/config-types.ts:45` (`DEFAULT_CONTRACT_SOURCE_DIR = 'src/prisma'`), `cli/src/commands/init/templates/code-templates.ts:63-68` | Corrected → `src/prisma/contract.prisma` (also lines 15, 59, 114, 125, 296, 302) | +| 9 | `contract emit` writes `contract.json` and `contract.d.ts` | `packages/1-framework/3-tooling/emitter/src/generate-contract-dts.ts`, `packages/3-extensions/postgres/src/config/define-config.ts:28-41` (output derived beside the source) | Confirmed | +| 16, 60 | `// use prisma-8` header line | `packages/1-framework/3-tooling/language-server/src/schema-directive.ts:1`; scaffold `code-templates.ts:165,197` | Confirmed | +| 18-20 | `types { ShortName = VarChar(35) }` | `contract-psl/test/interpreter.types.test.ts:33-70` (`Slug = VarChar(191)` → `sql/varchar@1`) | Confirmed | +| 22-27, 62-67 | `type Address { … }` value-object block | `contract-psl/test/interpreter.value-objects.test.ts:46-70`; mongo `2-mongo-family/2-authoring/contract-psl/test/interpreter.attribute-specs.test.ts:140` | Confirmed | +| 29-34 | `enum Priority { @@type("pg/text@1") Low = "low" … }` | `contract-psl/test/interpreter.enum.test.ts:125-150` (same schema verbatim) | Confirmed | +| 37, 47 | `Uuid @id @default(uuid())` | `contract-psl/test/interpreter.defaults.test.ts:152-200`; `3-targets/6-adapters/postgres/src/core/control-mutation-defaults.ts:276` | Confirmed | +| 38, 48 | `String` scalar | `control-mutation-defaults.ts:164` | Confirmed | +| 39, 51 | `DateTime @default(now())` | `control-mutation-defaults.ts:188`; `interpreter.defaults.test.ts:136-142` (storage default `now()`) | Confirmed | +| 40, 82 | `address Address?` (optional value object) | `interpreter.value-objects.test.ts:112-155` | Confirmed | +| 41, 83 | `posts Post[]` navigation list | `contract-psl/README.md` ("Relation navigation lists are supported") | Confirmed | +| 43, 55, 85, 98 | `@@map("user")` / `@@map("posts")` | `contract-psl/README.md`; `interpreter.polymorphism.test.ts:161-190` | Confirmed | +| 48 | `title ShortName` (alias in field position) | `interpreter.types.test.ts:40-60` | Confirmed | +| 50 | `priority Priority @default(Low)` | `interpreter.enum.test.ts:863-885` (`@default(Low)` → stored `"low"`) | Confirmed | +| 53, 95 | `@relation(fields: [...], references: [...])` | `interpreter.relations.test.ts`; mongo `interpreter.test.ts:355-385` | Confirmed | +| 70 | `@@type("mongo/string@1")` | `3-mongo-target/2-mongo-adapter/src/core/codec-ids.ts:2` | Confirmed | +| 77, 91 | `ObjectId @id @map("_id")` | mongo `interpreter.test.ts:146,358`; `2-mongo-family/2-authoring/contract-psl/src/interpreter.ts:1320-1331` | Confirmed | +| 93 | `DateTime` on MongoDB | mongo `interpreter.test.ts:45` (`mongo/date@1`) | Confirmed | +| 97 | `@@index([authorId])` on MongoDB | mongo `interpreter.attribute-specs.test.ts:169`, `interpreter.polymorphism.test.ts:476` | Confirmed | +| 102 | Re-run emit after every change | `page-conventions.md` + emit pipeline | Confirmed | +| 106 | A `.prisma` extension selects PSL authoring | `3-extensions/postgres/src/config/define-config.ts:42-50` (`.ts` → TypeScript, otherwise PSL) | Confirmed | +| 110 | `defineConfig as ormConfig` from `@prisma/orm-postgres/config` | `packages/9-public/@prisma/orm-postgres/package.json:69`; `postgres/src/config/define-config.ts:35` | Confirmed | +| 121 | `@prisma/orm-mongo/config` | `packages/9-public/@prisma/orm-mongo/package.json:60`; `mongo/src/config/define-config.ts` | Confirmed | +| 109, 120, 290 | `definePrismaConfig` from `"prisma/config"` | see Q1 | **Q** | +| 112-116 | `definePrismaConfig({ orm: ormConfig({ contract }) })` envelope | `code-templates.ts:302-313`; `skills/prisma-8/references/contract.md:47` | Confirmed | +| 130 | Scaffolded projects already have the config and a starter contract | `code-templates.ts:69-78` (`starterSchema`), `:299-313` (`configFile`) | Confirmed | +| 136 | `@id` / `@@id([a, b])` | `contract-psl/README.md`; `interpreter.relations.many-to-many.test.ts:52` | Confirmed | +| 137 | `@unique` / `@@index([...])` | `contract-psl/README.md` (`@@index` parameter surface); `interpreter.relations.one-to-one.test.ts:37` | Confirmed | +| 138 | `@default(now())` becomes a column default; `@default(uuid())` is applied by Prisma ORM before each write and appears in `execution` | `interpreter.defaults.test.ts:79-148`; `contract-psl/README.md` ("Execution defaults: `uuid()` …", "This is application-side") | Corrected (dropped the unsupported clause "so they work the same on every database"; "rather than as DDL" → "rather than as a column default") | +| 139 | `@map` sets the column name, `@@map` the table or collection name | `contract-psl/README.md` responsibilities list | Confirmed | +| 147-153 | PostgreSQL ID tab: `id Uuid @id @default(uuid())`, ordinary column | `interpreter.defaults.test.ts:152-200` | Confirmed | +| 159-165 | MongoDB ID tab: `id ObjectId @id @map("_id")`, is `_id` | `2-mongo-family/.../interpreter.ts:1318-1332` | Confirmed | +| 175-179 | `types` block declares reusable aliases | `interpreter.types.test.ts:33-80` | Confirmed | +| 181 | Alias binds `varchar(35)` rather than `text`; `VarChar(35)`, `Uuid`, `Timestamptz` usable directly | `control-mutation-defaults.ts:205-276` (`postgresNativeAuthoringTypes`) | Confirmed | +| 185 | `@@type` declares storage; member values optional | `interpreter.enum.test.ts:624-800` | Corrected → added "Leave `@@type` out and Prisma ORM infers it from the member values", since rc.10 infers the codec from bare, string, or integer members (`interpreter.enum.test.ts:624,650,676`) | +| 196 | `pg/text@1` stores values as `text` | `control-mutation-defaults.ts:164-167`; `interpreter.enum.test.ts:125` | Confirmed (added the convention gloss "the PostgreSQL type plus a version, always `@1` today") | +| 196 | A bare member stores the member name | `interpreter.enum.test.ts:624-647` (`values: ['Admin', 'User']`) | Confirmed | +| 196 | Allowed values enforced with a `CHECK` constraint on each column | `interpreter.enum.test.ts:803-825` (`"priority" IN (1, 10)`); `3-targets/3-targets/postgres/src/core/migrations/operations/constraints.ts:157` | Confirmed | +| 200 | `type` block = value object, no table of its own | `interpreter.value-objects.test.ts:46-78` | Confirmed | +| 216 | PostgreSQL: single `jsonb` column | `interpreter.value-objects.test.ts:112-165` (`pg/jsonb@1`) | Confirmed | +| 216 | MongoDB: embedded document | `2-mongo-family/2-authoring/contract-psl/src/derive-json-schema.ts:56-87` (`bsonType: 'object'`) | Confirmed | +| 216 | `contract.d.ts` types it as a structured object | `emitter/src/domain-type-generation.ts:607-640`; `generate-contract-dts.ts:100-110` | Confirmed | +| 220-231 | Relation shape: FK side declares the scalar field, other side the list | `contract-psl/README.md`; `interpreter.relations.test.ts` | Confirmed | +| 233 | Many-to-many needs an explicit join-table model whose primary key covers both foreign keys | `interpreter.relations.many-to-many.test.ts:35-60` and `:602-636` (`PSL_JUNCTION_ID_NOT_FK_COVERING`) | Confirmed (wording made precise: "Its primary key must cover both foreign keys") | +| 244-253 | `@@id([postId, tagId])` on the join model | same test, line 52 | Confirmed | +| 256 | Relation recorded as `N:M` with the join table's columns | `interpreter.relations.many-to-many.test.ts:63,77` (`cardinality: 'N:M'`, `through: {...}`) | Confirmed | +| 260-279 | `@@discriminator(type)` on the base, `@@base(Task, "bug")` on the variant | `interpreter.polymorphism.test.ts:68-130` | Confirmed | +| 281 | PostgreSQL: variant with its own `@@map` gets its own table sharing the base primary key | `interpreter.polymorphism.test.ts:161-232` (variant table has the base PK column, `primaryKey: ['id']`, FK to the base) | Confirmed | +| 281 | PostgreSQL: without `@@map`, variant fields are nullable columns in the base table | `interpreter.polymorphism.test.ts:131-160, 404-455` | Confirmed | +| 281 | MongoDB: variants stay in the base collection, so no `@@map` of their own | `2-mongo-family/.../interpreter.ts:460-483` (`PSL_MONGO_VARIANT_SEPARATE_COLLECTION`) | Confirmed | +| 285 | Extension packs contribute types via constructor calls in `types {}` | `contract-psl/README.md`; `psl-parser/test/parse-leaf.test.ts:432` | Confirmed | +| 289 | `import pgvector from "@prisma/orm-extension-pgvector/control"` | `packages/9-public/@prisma/orm-extension-pgvector/package.json` exports `"./control"`; `skills/prisma-8/SKILL.md:36` maps `@internal/` → `@prisma/orm-` | Confirmed | +| 295 | `extensions: [pgvector]` | `postgres/src/config/define-config.ts:21,36` | Confirmed | +| 302-303 | `Embedding1536 = pgvector.Vector(1536)` | `3-extensions/pgvector/src/core/authoring.ts:4-19`; `pgvector/src/contract.ts:34` | Confirmed | +| 313 | A type from an unlisted pack fails emit | `1-framework/1-core/errors/src/control.ts:350-370` | Corrected → now names the code `CONFIG.MISSING_EXTENSION_PACKS` | +| 317 | `contract infer` reads the live schema and writes a starter `contract.prisma` | `cli/src/orm/contract/paths.ts:4-24`; `cli/src/orm/contract/infer.ts:87-89` | Confirmed | +| 321 | Skills note | `page-conventions.md`; `skills/prisma-8/SKILL.md` | Corrected to the mandated sentence | +| 324 | "composite unique constraint on userId and title" is expressible | `interpreter.relations.one-to-one.test.ts:37` (`@@unique([a, b])`) | Confirmed | +| 329-331 | Next-steps link targets exist | `apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx`, `typescript-schema-builder.mdx`, `cli/db-init.mdx`, `cli/migration-plan.mdx`, `cli/contract-emit.mdx`, `cli/contract-infer.mdx` all present | Confirmed | +| 216, 281 | Anchors `#embed-or-reference`, `#polymorphic-relations`, `#available-skills-for-prisma-8` | headings in `orm/data-modeling/mongodb.mdx:28`, `relational-databases.mdx:166`, `ai/tools/skills.mdx:100` | Confirmed | + +## 3. Q list + +**Q1 — `import { definePrismaConfig } from "prisma/config"` (lines 109, 120, 290).** Every occurrence in the rc.10 tree imports it from `@prisma/cli-engine`, never from `prisma/config`: `cli/src/commands/init/templates/code-templates.ts:304`, `templates/quick-reference-postgres.md:43`, `quick-reference-mongo.md:45`, `skills/prisma-8/references/contract.md:47,52`, `docs/releases/v8.0.0-rc.4.md:25`. The tree contains no package named `prisma`, so it cannot confirm or rule out a `prisma/config` re-export in the published CLI. I left the page alone because this is a section-wide decision, not a page-level one: `prisma/config` appears **116 times** across `apps/docs/content/docs/orm/` and only 7 times as a target-specific config import. Changing one page would make the docs inconsistent. The operator should decide for the whole section. + +Searches run: `grep -rn "definePrismaConfig" packages skills docs`; `grep -rn '"name": "prisma"' packages --include=package.json`; `grep -rn "cli-engine" packages --include='*.json' -l`; `grep -rn "prisma/config" packages --include='*.ts'` (no hits); `grep -rho 'from "[^"]*config"' apps/docs/content/docs/orm/ | sort | uniq -c`. + +No other claims were left unchecked. + +## 4. Line count + +`apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx`: **331 before, 331 after.** + +## Notes for the operator + +- The sibling contract-authoring page `the-data-contract.mdx` still uses `prisma/contract.prisma` (lines 13 and 61). It is another agent's page, so I did not touch it, but it needs the same `src/prisma/` correction. +- Two short PSL snippets on the page (the Relations and many-to-many examples) omit the `id` fields their `references: [id]` clauses point at. They are deliberate excerpts, consistent with the page's style, so I left them. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/factcheck1.the-data-contract-and-artifact.md b/docs/orm-docs-audit/c21-contract-authoring/factcheck1.the-data-contract-and-artifact.md new file mode 100644 index 00000000000..73a2a51e8d0 --- /dev/null +++ b/docs/orm-docs-audit/c21-contract-authoring/factcheck1.the-data-contract-and-artifact.md @@ -0,0 +1,104 @@ +Both pages are edited and the plain-language checker prints clean on each. + +## 1. Banned-term and wording replacements + +**the-data-contract.mdx** + +| Line | Old | New | +| --- | --- | --- | +| 3, 6 | front matter: "single description", "typed, planned, and verified", "the same artifacts" | plain rewording, no "artifacts" | +| 9 | "That description is the data contract." | "…the data contract, the `contract.prisma` file that replaced `schema.prisma`." (section convention, first use) | +| 33 | "compiles this into a machine-readable artifact" | "`prisma contract emit` turns this source into two machine-readable files" | +| 45 | "Prisma ORM's current architecture" | "Prisma ORM 7" (now reads as a contrast) | +| 47 | "a canonical JSON description of models, storage, and capabilities"; "Both are deterministic … byte-identical output"; "the artifacts can be diffed" | "describes your models, how they are stored, and which database features the contract needs"; "The same source always produces the same two files, so you can read them in a code review…" | +| 49 | "records them in a small marker inside the database" | the section's mandated marker wording: "records which contract the database matches in a small table it creates in the database" | +| 68–72 | "Emitting turns the source into the artifacts"; `npx prisma@latest contract emit` | "Run `prisma contract emit` after every change… It writes `contract.json` and `contract.d.ts` beside the source"; `npx prisma contract emit` | +| 77 | "The runtime receives `contract.json`" | "`db` reads `contract.json`" ("runtime" as a noun is banned) | +| 80 | heading "## Two authoring modes, one artifact" | "## Two authoring modes, one \`contract.json\` [#two-authoring-modes-one-artifact]" (old anchor pinned) | +| 82 | "the preferred way … purpose-built for describing data"; "what `create-prisma` scaffolds"; "what the examples throughout these docs use" | "the usual way … a compact language for describing data"; "what `npm create prisma@latest` scaffolds"; docs-commentary clause cut | +| 86 | "emit the same `contract.json`"; "verification"; "exactly one source of truth" | "write the same `contract.json`"; "database checks"; "exactly one contract file, the one named by `contract` in the config" | +| 94–98 | "value objects"; "types and capabilities contributed by extension packs"; "committing … the emitted artifacts" | "`type` blocks, each a structured value stored inside its parent row with no table of its own"; "the types and database features that extension packs add"; "committing the source, `contract.json`, and `contract.d.ts`" | +| 102 | "Projects scaffolded with `create-prisma@latest` install…" | the mandated sentence, including `npx prisma skills sync` | +| 105 | "and emit it" | "and run prisma contract emit" | + +**the-contract-artifact.mdx** + +| Line | Old | New | +| --- | --- | --- | +| 3, 6 | "deterministic artifacts", "deterministic emission" | plain rewording | +| 9 | "This page explains what is in each, why they are deterministic…" | deleted (docs commentary) | +| 13 | "canonical, machine-readable contract … capabilities"; "The runtime, … verification" | "machine-readable contract: … which database features they need"; "`db`, the migration tooling, the database checks" | +| 18 | heading "## Deterministic emission" | "## The same source always produces the same two files [#deterministic-emission]" (old anchor pinned) | +| 20–22 | "Emission is deterministic"; "canonical order"; "the contract source must stay pure" | "Run `prisma contract emit` … byte-identical files"; "a fixed key order"; "must not read the environment, the clock, or random values" | +| 34 | "records the hashes in a marker inside the database"; "caught by verification rather than by a failing query" | mandated marker wording, plus the corrected behaviour (see below) | +| 105 | "the model it names is what `db.orm.public.User` hangs off" | "the model name is what `db.orm.public.User` uses" | +| 106–107 | "a `codecId` … which names the codec that encodes and decodes values"; "native type plus codec" | "its type id, such as `pg/text@1`, the PostgreSQL type plus a version, always `@1` today"; "a native type and a type id" | +| 109 | "Query APIs consult these before using gated features" | "The query APIs check these before building a query that needs one" | +| 129 | "type-incompatible with another version's artifacts"; "before verification has to" | "will not type-check against another version's files"; "before `prisma db verify` has to" | +| 151 | "without re-running emission"; "running the emit" | "without running `prisma contract emit` first"; "running the command" | +| 155, 158 | scaffold/skills sentence; "Re-emit the contract … in the artifact" | mandated sentence; "Run prisma contract emit and show me what changed in contract.json" | +| 162 | "how the contract's capabilities gate database features" | "which database features the contract needs, and how Prisma ORM checks that your database has them" | + +Two lines carry `{/* plain-language:defined */}`: the pinned anchor `[#deterministic-emission]` and the Next-steps link whose **URL path** is `/orm/contract-authoring/capabilities`. Both trip the checker on a code-like string, not on prose jargon. I removed a second link to that URL rather than add a third marker. + +## 2. Claims checked + +| Line | Claim | Deciding source | Verdict | +| --- | --- | --- | --- | +| dc 31 | `// use prisma-8` is the first line; marks a Prisma 8 contract | `packages/1-framework/3-tooling/language-server/src/schema-directive.ts:1`; `CHANGELOG.md:33` | Confirmed | +| dc 31 | language server only diagnoses/completes/formats files carrying it | `document-diagnostics.ts:27`; `server.ts:384`; completion needs `project.artifacts.document(uri)` (`server.ts:452`) | Confirmed | +| dc 31 | "excludes unmarked files from its schema composition" | `schema-inputs.ts` — the input set comes only from `contract.source.inputs` in the config; no composition exclusion in rc.10 code | **Corrected**: clause deleted (it was an rc.9 release-note line) | +| dc 31 | completion covers field/model/block attribute names and argument keys from target and extensions | `completion-provider.ts:41-47`; `server.ts:464-479` | Confirmed | +| dc 31 | pre-rc.10 contracts use `// use prisma-next`; still accepted, replaced on format | `docs/releases/v8.0.0-rc.10.md:25`; `schema-directive.ts:9`; `server.ts:395` | Confirmed | +| dc 33 | "the database is verified against it before your code runs" | `packages/2-sql/5-runtime/src/sql-runtime.ts:897-928` — mismatch only logs `CONTRACT.MARKER_MISMATCH` | **Corrected**: now "`prisma db verify` checks a live database against it" | +| dc 35 | `package-lock.json` comparison | wording only | Confirmed (unchanged) | +| dc 45 | Prisma ORM 7 compiles the schema into generated client code | contrast claim about v7, not in this tree | **Q1** | +| dc 47 | same source produces the same two files | `packages/1-framework/0-foundation/contract/src/canonicalization.ts:179-209`, `emit.ts:31-48` | Confirmed | +| dc 49 | `db sign` records hashes in a table it creates | `packages/2-sql/9-family/src/core/control-instance.ts:825-895`; table is `prisma_contract.marker` (`3-targets/6-adapters/postgres/src/core/control-adapter.ts:98`) | Confirmed | +| dc 49 | client checks before its first query; **fails on mismatch** | `sql-runtime.ts:368-371, 897-928`; `docs/releases/v0.12.0.md:87` ("no longer throws … emits one warn-level log line") | **Corrected**: "logs a warning … To fail instead of warn … run `prisma db verify`" | +| dc 56 | `import { definePrismaConfig } from "prisma/config"` | `cli/src/commands/init/templates/code-templates.ts:304` | **Corrected**: `from "@prisma/cli-engine"` | +| dc 57 | `defineConfig as ormConfig` from `@prisma/orm-postgres/config` | same file + every `examples/*/prisma.config.ts` | Confirmed | +| dc 66 | file extension selects PSL vs TypeScript mode | `packages/3-extensions/postgres/src/config/define-config.ts:43-52` | Confirmed | +| dc 71 | `npx prisma@latest contract emit` | page conventions (existing project) | **Corrected**: `npx prisma contract emit` | +| dc 74 | emit writes both files beside the source | `cli/src/orm/contract/emit.ts:55,84`; `define-config.ts:25-33` | Confirmed | +| dc 76–78 | who reads which file | `emit.ts`, `sql-runtime.ts`, `cli/src/orm/db/verify.ts:81-155` | Confirmed, wording corrected on the middle bullet | +| dc 82 | PSL is the usual mode; `contract infer` writes PSL | `cli/src/orm/contract/infer.ts:132-201`; `templates/code-templates.ts:67` (`src/prisma/contract.prisma`) | Confirmed | +| dc 82 | `npm create prisma@latest` scaffolds PSL | `create-prisma` is not in this tree | **Q2** | +| dc 86 | identical `contract.json` for an equivalent schema | `contract-psl/test/ts-psl-parity.test.ts:342-870`; `psl-ts-namespace-parity.test.ts` | Confirmed | +| dc 92–96 | what the contract contains | real `contract.json` at `examples/prisma-8-demo/src/prisma/contract.json` | Confirmed | +| ar 11–14 | the table | `emit.ts`, `cli/src/orm/contract/emit.ts:73-85` | Confirmed after rewording | +| ar 16 | generated-file notice in both files | `emitter/src/emit.ts:39-44`; `examples/prisma-8-demo/src/prisma/contract.d.ts:1-3` | Confirmed | +| ar 20 | fixed key order | `canonicalization.ts:66 TOP_LEVEL_ORDER`, `:179-209 sortObjectKeys` | Confirmed | +| ar 30 | `storageHash` covers "models, fields, relations, and the full storage layout" | `contract/src/hashing.ts:72-83` hashes only `storage`; `build-contract.ts:1540-1547` | **Corrected**: now "the storage layout: tables, columns, primary keys, uniques, indexes, foreign keys, and the value sets behind enums" | +| ar 31 | `executionHash` covers pre-write defaults | `hashing.ts:85-92`; `build-contract.ts:1596-1602`; real `execution.mutations.defaults` | Confirmed | +| ar 32 | `profileHash` covers the target database and its family | `hashing.ts:94-101` plus `build-contract.ts:1591-1595`, which passes `capabilities: {}` in rc.10 | Confirmed | +| ar 34 | `db sign` checks then records; `db verify` and the client compare | `control-instance.ts:825-895`; `cli/src/orm/db/verify.ts:81-155` (marker + schema, exit code 4) | Confirmed | +| ar 34 | "caught by verification rather than by a failing query" | `sql-runtime.ts:897-928` | **Corrected**: `db verify` fails; `db` warns and carries on | +| ar 38 | domain/storage split, MongoDB collections, namespace = PostgreSQL schema | `examples/prisma-8-demo/src/prisma/contract.json`; `examples/mongo-demo/src/contract.json` (`entries.collection`) | Confirmed, sentence split | +| ar 44–99 | the abridged `contract.json` (section names, `roots`, `domain` field shape, `storage`, `execution`, `capabilities`, `extensions`) | `examples/prisma-8-demo/src/prisma/contract.json`; `contract-ts/src/data-contract-json-schema.ts` | Confirmed; **Corrected** key order inside objects to the emitted alphabetical order (`codecId` before `kind`, `model` before `namespace`, `namespaces` before `storageHash`, etc.) | +| ar 105 | `roots` shape and what it keys | `build-contract.ts:1421-1437` (keyed by bare storage table name) | Confirmed, reworded | +| ar 109 | capabilities "merged from the target, adapter, and extension packs" | `build-contract.ts:1577-1590`; values declared in `3-targets/6-adapters/postgres/src/core/descriptor-meta.ts:172-194` | **Corrected**: "collected from the adapter for your database and any extension packs" | +| ar 109 | query APIs check capabilities before use | `2-sql/4-lanes/sql-builder/src/runtime/builder-base.ts:62, 278-290` (`ORM.CAPABILITY_MISSING`) | Confirmed | +| ar 116–126 | `contract.d.ts` excerpt: `StorageHashBase`, `ProfileHashBase`, `CodecTypes[...]["output"]`, `AddressOutput` | `examples/prisma-8-demo/src/prisma/contract.d.ts:41-46, 264-269`; `emitter/src/generate-contract-dts.ts` | Confirmed, verbatim match | +| ar 134–143 | `src/prisma/db.ts` example | `examples/prisma-8-demo/src/prisma/db.ts:1-11`; `templates/code-templates.ts:318-331` | Confirmed | +| ar 131 | `prisma orm init` writes `src/prisma/db.ts` (added) | `cli/src/orm/init-scaffold.ts:188`; `1-core/config/src/config-types.ts:45` | Confirmed | +| ar 151 | version-control advice | no rows/credentials in the emitted files; `--output-path` flag exists | Confirmed | +| ar 160–163 | Next-steps CLI links | `apps/docs/content/docs/cli/` contains `contract-emit`, `contract-infer`, `db-sign`, `db-verify`, `db-init`, `migration-plan` | Confirmed | + +No test covers the runtime warning path directly that I found; the behaviour above comes from `sql-runtime.ts` and the v0.12.0 release note. + +## 3. Q list + +- **Q1** (dc 45, "Prisma ORM 7 compiles your schema into generated client code"): nothing about Prisma ORM 7 exists in the rc.10 tree. Searches: `grep -rn "prisma-client-js\|generated client" wip/prisma-src/docs wip/prisma-src/skills`, `grep -rn "Prisma ORM 7" wip/prisma-src`. Left as written. +- **Q2** (dc 82, "`npm create prisma@latest` scaffolds PSL"): `create-prisma` is not in this checkout. Searches: `find wip/prisma-src -type d -name 'create-prisma*'`, `grep -rn "create-prisma" wip/prisma-src/packages`. I used the wording `conventions.md` mandates. The related fact that `prisma orm init` defaults to `src/prisma/contract.prisma` is confirmed. + +## 4. Line counts + +| Page | Before | After | +| --- | --- | --- | +| the-data-contract.mdx | 143 | 143 | +| the-contract-artifact.mdx | 165 | 163 | + +## One thing for you to decide + +The artifact page's front-matter `title` is still **"The emitted artifacts"**, which `page-conventions.md` bans as a phrase. I left it because three pages I must not touch link to it by that exact text (`orm/core-concepts.mdx:61`, `psl-syntax.mdx:329`, `typescript-schema-builder.mdx:281`) and `meta.json` orders the section by slug. Renaming it needs a coordinated pass across those pages. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/factcheck1.typescript-and-capabilities.md b/docs/orm-docs-audit/c21-contract-authoring/factcheck1.typescript-and-capabilities.md new file mode 100644 index 00000000000..d0ae7b667be --- /dev/null +++ b/docs/orm-docs-audit/c21-contract-authoring/factcheck1.typescript-and-capabilities.md @@ -0,0 +1,145 @@ +Both pages are done. Here is the report. + +## 1. Banned-term and wording replacements + +**typescript-schema-builder.mdx** + +| Line | Old | New | +| --- | --- | --- | +| 3, 6 | "Same models, same artifacts" / "Learn how to define…" | "Same models, same emitted files" / "How to define…" | +| 9 | "you define the Prisma ORM data contract in code… produces exactly the same `contract.json` and `contract.d.ts`" | first-use convention: "your contract, the `contract.ts` file that replaced `schema.prisma`"; "`npx prisma contract emit` then writes `contract.json` and `contract.d.ts` beside the source" | +| 13 | "Both modes emit identical artifacts… The TypeScript builder is an **escape hatch**" | "Both forms produce the same two files… Use the TypeScript builder for the cases PSL does not cover" | +| 16 | "(the **purity rules** still apply)" parenthetical caveat | cut | +| 22 | "names the **source of truth**" | "names the one file Prisma ORM reads" | +| 157 | "refresh the **artifacts**" | "refresh the two files" | +| 163 | — | "takes two arguments" → "takes an options object, and optionally a factory function after it" (both overloads exist) | +| 183–184 | semicolons joining clauses | split into sentences | +| 185 | "sets the **physical** column name" | "sets the column name in the database" | +| 189 | "with an explicit storage **codec**" | "an enum, the type its values are stored as, and its members" | +| 256 | "the CLI and **runtime** agree" + mid-sentence parenthetical | "Add the same pack to `prisma.config.ts` as `extensions: [pgvector]`, importing the pack's `/control` export. The CLI and your app then compose the same set the contract was built with." | +| 258 | heading "Keep the contract file **pure**" | "## Keep the contract file free of changing values `[#keep-the-contract-file-pure]`" — anchor pinned; `the-contract-artifact.mdx:22` links to it and still resolves | +| 260 | "Prisma ORM **canonicalizes** it to JSON… the file is **pure** data" | "writes it to JSON in a fixed key order and hashes it, so the same source always produces the same files… only if the file holds plain data" | +| 264 | "**Emission** evaluates it" | "`prisma contract emit` imports the file and reads its `default` or `contract` export" | +| 270 | "the same **canonical artifact**… exactly one **source of truth**" | "the same `contract.json` and `contract.d.ts`… A project names exactly one contract file in its config" | +| 274 | "Projects scaffolded with `create-prisma@latest` install…" | the required sentence, including `npx prisma skills sync` | +| 281 | "**Emit** and inspect the **artifacts**" | "Inspect the two files" | + +**capabilities.mdx** + +Prose use of "capability/capabilities" replaced throughout with "keys", "the `capabilities` section", "which database features your setup supports". Other replacements: + +| Line | Old | New | +| --- | --- | --- | +| 11 | "instead of **surfacing** as a database error mid-query" | "instead of reaching the database and failing there"; first-use "contract" convention added | +| 15 | "You do not write capabilities yourself. When `contract emit` runs…" | "You do not write this section yourself. `npx prisma contract emit` writes `contract.json` and `contract.d.ts` beside the source, and while it does so it merges…" | +| 43 | heading "What capabilities **gate**" | "## What capabilities control `[#what-capabilities-gate]`" — anchor pinned | +| 64 | "to give a sense of the granularity" (docs commentary) | cut | +| 67 | table column "Gates" | "What it covers" | +| 77 | "is the **authoritative** list" | "is the full list" | +| 79 | "does not yet **gate** features this way" | "does not yet check features this way" | +| 90 | scaffold sentence | required wording | + +The four headings containing "Capabilities", plus `title:`, `url:` and `metaTitle:`, were left alone as instructed. + +## 2. Claims checked + +**typescript-schema-builder.mdx** + +| Line | Claim | Deciding source | Verdict | +| --- | --- | --- | --- | +| 9 | emit writes `contract.json` + `.d.ts` beside the source | `contract-ts/src/config-types.ts:90-125` (`defaultOutputFromContractPath`) | Confirmed | +| 13 | PSL is the preferred form | `skills/prisma-8/references/contract.md:6,46` (secondary prose) | Confirmed | +| 15–16 | TS is for composed/programmatic definitions | same, line 46 | Confirmed | +| 18 | "what `create-prisma` scaffolds and `contract infer` writes" | `cli/src/orm/init-inputs.ts:98-103`; `cli/src/orm/contract/paths.ts:5` | **Corrected** — `prisma orm init` prompts for PSL or TypeScript and writes either, so the scaffold half was dropped. Now: "It is more compact, and it is what `contract infer` writes." | +| 22 | `.ts` extension selects TypeScript authoring | `3-extensions/postgres/src/config/define-config.ts:44-49` | Confirmed | +| 25, 36 | `import { definePrismaConfig } from "prisma/config"` | `cli/src/commands/init/templates/code-templates.ts:304`; `docs/releases/v8.0.0-rc.4.md:25` | **Corrected** — it is `@prisma/cli-engine`. No `prisma/config` specifier exists anywhere in the tree. | +| 26, 37 | `defineConfig as ormConfig` from `@prisma/orm-postgres/config` / `@prisma/orm-mongo/config` | `9-public/@prisma/orm-postgres/package.json` exports, `…/orm-mongo/package.json` | Confirmed | +| 48 | builders at `/contract-builder` on both packages | both package.json exports maps | Confirmed | +| 51 | `@prisma/orm-extension-pgvector/pack` | `…/orm-extension-pgvector/package.json` exports | Confirmed | +| 52 | `defineContract, enumType, member, rel` exported | `3-extensions/postgres/src/exports/contract-builder.ts` | Confirmed | +| 54, 194 | `{ codecId: "pg/text@1", nativeType: "text" }` | `3-targets/3-targets/postgres/src/core/authoring.ts:677` | Confirmed | +| 56–62, 192 | `enumType(name, codec, member(...))` | `1-framework/2-authoring/contract/src/enum-type.ts:153-183` | Confirmed | +| 64–67 | `defineContract(options, factory)` with `extensions` | `3-extensions/postgres/src/contract/define-contract.ts:89-102` | Confirmed | +| 68 | factory receives `{ field, model, type }` | `contract-ts/src/composed-authoring-helpers.ts:133-152` | Confirmed (`rel` also present) | +| 70, 249 | `type.pgvector.Vector(1536)` | `3-extensions/pgvector/src/core/authoring.ts:4-19` | Confirmed | +| 75, 85 | `field.id.uuidv4String()` | `2-sql/9-family/src/core/authoring-field-presets.ts:99` | Confirmed | +| 76, 86 | `field.text()` | `postgres/src/core/authoring.ts:678` | Confirmed | +| 77–78 | `field.temporal.createdAt()` / `.updatedAt()` | `2-sql/9-family/src/core/timestamp-now-generator.ts:61,69` | Confirmed | +| 79 | `field.json().optional()` | `authoring.ts:719`; `contract-dsl.ts:183` | Confirmed | +| 87 | `field.uuidString()` | `authoring-field-presets.ts:42` | Confirmed | +| 88 | `field.namedType(Priority).default(Priority.members.Low)` | `contract-dsl.ts:2105,313`; `enum-type.ts:219,233` (`members` maps name → stored value) | Confirmed | +| 100, 208 | `rel.hasMany(Post, { by })` | `contract-dsl.ts:2001-2021` | Confirmed | +| 105, 212 | `rel.belongsTo(User, { from, to })` | `contract-dsl.ts:1948-1999` | Confirmed | +| 101, 223 | `.sql({ table })` | `contract-dsl.ts:1574` | Confirmed | +| 106, 229 | `.sql(({ cols, constraints }) => …)` | `contract-dsl.ts:1624-1632`, `SqlContext` at 1246-1253 | Confirmed | +| 109, 232 | `constraints.foreignKey(cols.x, Model.refs.y, { name })` | `contract-dsl.ts:1121-1155` | Confirmed | +| 95–115 | returned `{ enums, types, models }` | `define-contract.ts:96-101` | Confirmed | +| 121–154 | whole MongoDB example | `cli/src/commands/init/templates/code-templates.ts:258-292` writes the same shapes; `field.objectId/string/date` at `2-mongo-family/…/contract-builder.ts:1008-1025`; `collection` at 1362; `rel.hasMany(target,{from,to})` at 1277-1296; `rel.belongsTo` at 1299-1322; `User.ref("_id")` at 1443; `defineContract({models})` at `3-extensions/mongo/src/contract/define-contract.ts` overload 1 | Confirmed | +| 157 | `npx prisma@latest contract emit` | page-conventions | **Corrected** to `npx prisma contract emit` | +| 159 | Mongo scalars `int32`, `double`, `bool` | `2-mongo-family/…/contract-builder.ts:1014-1025` | Confirmed | +| 163 | "takes two arguments" | `define-contract.ts` two overloads | **Corrected** — "takes an options object, and optionally a factory function after it" | +| 165 | target and family bound by the import | `define-contract.ts:113-121` (`buildBoundContract`) | Confirmed | +| 167 | helpers "composed from the target and every extension pack" | `composed-authoring-helpers.ts:270-277` composes family + target + extensions | **Corrected** — "composed from your database and every extension pack"; `rel` added to the list | +| 169 | Mongo `defineContract` "takes a single definition object" | `mongo/src/contract/define-contract.ts` also has a factory overload | **Corrected** — "accepts a single definition object holding the `models`" | +| 175–178 | field helper bullets | as above | Confirmed | +| 180 | helper set comes from target + packs | `composed-authoring-helpers.ts:270` | **Corrected** — "from your database and the extension packs you compose" (`uuidString` comes from the SQL family, not the target) | +| 182–185 | `.optional()`, `.default()`, `.defaultSql()`, `.unique()`, `.id()`, `.column()` | `contract-dsl.ts:183, 313, 320, 379, 327, 217` | Confirmed | +| 200 | enum must be in the returned `enums` map | `build-contract.ts:1463` registers only `definition.enums` | Confirmed | +| 216 | `rel.hasOne` and `rel.manyToMany` | `contract-dsl.ts:2023, 2045` | Confirmed | +| 216 | "a typo is a compile error either way" | `contract-dsl.ts:1948-2012` string overloads accept any string; `contract-lowering.ts:344` reports "references unknown model" at build time; `contract-warnings.ts:127-131` warns on string fallbacks | **Corrected** — now: model object gives a compile error, a string is caught when `prisma contract emit` builds the contract | +| 237 | `Model.refs` typed against the model | `contract-dsl.ts:1456, 1478, 1279-1302` | Confirmed | +| 256 | pack must also be in `prisma.config.ts` via `/control` | `postgres/src/config/define-config.ts:22` (`ControlExtensionDescriptor`); pgvector exports `./control` | Confirmed | +| 260 | fixed key order + hashing | `1-framework/0-foundation/contract/src/hashing.ts:1-60` (`canonicalizeContract` + sha256) | Confirmed | +| 264 | emit evaluates the file and nothing else | `contract-ts/src/config-types.ts:105-113` (dynamic import, reads `default ?? contract`) | Confirmed | +| 270 | PSL and TS produce the same output | `contract-psl/test/ts-psl-parity.test.ts`, `psl-ts-namespace-parity.test.ts:141` | Confirmed | +| 281–282 | links `/cli/db-init`, `/cli/migration-plan`, artifact page | pages exist in the repo | Confirmed | + +**capabilities.mdx** + +| Line | Claim | Deciding source | Verdict | +| --- | --- | --- | --- | +| 11 | error names the missing key before the database is reached | `sql-builder/src/runtime/builder-base.ts:278-292` | Confirmed | +| 15 | "you do not write them yourself" | `cli/src/control-api/operations/contract-emit.ts:252-268`; `contract-enrichment.ts:66` | Confirmed | +| 15 | merged from "target, its adapter **and driver**, and extension packs" | `contract-emit.ts:252` merges `[target, adapter, ...extensions]`; `control-stack.ts:662` the same | **Corrected** — "the target …, its adapter, and any extension packs named in the config" | +| 20–28 | `postgres.*` keys and their values | `3-targets/6-adapters/postgres/src/core/descriptor-meta.ts:179-186` | Confirmed | +| 26 | `pgvector.cosine` sits under `postgres` | `3-extensions/pgvector/src/core/descriptor-meta.ts:66-69` | Confirmed | +| 29–35 | `sql.*` keys | `postgres/…/descriptor-meta.ts:187-194` | Confirmed; added the real `checkConstraint: true` so the excerpt matches an emitted file | +| 41 | packs contribute keys; re-emitting changes the set | `capabilities.ts:70-87`; `contract-emit.ts:252` | Confirmed | +| 47–51 | SQLite scalar-list diagnostic text | `contract-psl/src/psl-field-resolution.ts:497-503` | Confirmed (quote is the first sentence of the message verbatim; the message continues "Remove the list or author it against a target that supports scalar lists.") | +| 54 | error code | `builder-base.ts:287`; `sql-orm-client/src/collection-contract.ts:583,599` | **Corrected** by addition — the page now names `ORM.CAPABILITY_MISSING` | +| 56–58 | both quoted messages | `builder-base.ts:288`; `collection-contract.ts:600` | Confirmed verbatim | +| 61 | error raised while the query is constructed | `builder-base.ts:56-64` (`_gate` wraps the method call) | Confirmed | +| 69 | `sql.lateral`; "SQLite does not declare it" | `6-adapters/sqlite/src/core/descriptor-meta.ts:23` declares `lateral: false` | **Corrected** — "PostgreSQL reports `true`, SQLite reports `false`" | +| 70 | `sql.returning` | `collection-contract.ts:578-585` | Confirmed | +| 71 | `sql.enums`; "SQLite does not declare it" | `sqlite/…/descriptor-meta.ts:27` declares `enums: false` | **Corrected** — "SQLite reports `false`" | +| 72 | `sql.defaultInInsert` for multi-row inserts | `sql-orm-client/src/collection.ts:1483` | Confirmed | +| 73 | `sql.scalarList` | `psl-field-resolution.ts:497` | Confirmed | +| 74 | `postgres.distinctOn` | `collection-contract.ts:587-602` | Confirmed | +| 75 | `postgres.jsonAgg` "JSON aggregation for nested reads" | declared only | **Q** (see below) | +| 76 | `postgres.pgvector.cosine` "cosine distance operations" | declared only; the operations are `cosineDistance` / `cosineSimilarity` | **Corrected** — "Contributed by the pgvector pack, alongside its cosine operations" | +| 80 | MongoDB declares no keys | `3-mongo-target/1-mongo-target/src/core/descriptor-meta.ts:15` (`capabilities: {}`); driver control at `3-mongo-driver/src/exports/control.ts:27` | Confirmed | +| 84 | recorded at emit time, not probed | static declarations on the descriptors; merged in `contract-enrichment.ts:66` | Confirmed | +| 84 | `db verify` compares schema and profile using the hashes | `cli/src/control-api/operations/db-verify.ts:287-305`; `cli/src/orm/db/verify.ts:149` | Confirmed | +| 86, 97, 98 | links to `/orm/extensions/using-extensions`, the artifact page, `/cli/configuration` | pages exist | Confirmed | + +## 3. Q list (unverifiable, page text left alone) + +1. **`postgres.jsonAgg` "JSON aggregation for nested reads"** — the key is declared by the PostgreSQL and SQLite adapters but nothing in `src` reads it in rc.10. Searches: `grep -rn "jsonAgg" --include='*.ts' packages` (only `descriptor-meta.ts`, `adapter.ts`, generated `contract.d.ts` files, and tests); `grep -rn "capabilities\['sql'\]\|capabilities\['postgres'\]"` across `packages`. +2. **`sql.enums` "Enum value sets enforced in the database"** — same situation: declared by both adapters, read nowhere. Searches: `grep -rn "'enums'" --include='*.ts' packages/2-sql packages/3-targets | grep -i capab` (no hits); the `capabilities['sql']` grep above returned only `checkConstraint` and `scalarList`. +3. **Purity bullets 2 and 3 on the TypeScript page** ("Functions, class instances, and `Date` objects do not serialize") — no code in the tree rejects such values or states the rule. Searches: `grep -rn "process.env\|Date\b\|non-serializable\|NOT_SERIALIZABLE" --include='*.ts' packages/2-sql/2-authoring/contract-ts/src/` (no hits); `grep -rn "throw\|error" .../is-plain-record.ts` (no hits). Bullet 1 is a fair reading of `hashing.ts`, which hashes the built contract, but the source does not state the rule either. + +Also worth flagging, not on the page: `CONFIG.MISSING_EXTENSION_PACKS` exists as an error factory in `1-framework/1-core/errors/src/control.ts:356` but has **no caller** in rc.10 outside its own unit test. I therefore did not add the "emit fails with…" sentence that would have made the extension-pack paragraph sharper. + +## 4. Line counts + +| Page | Before | After | +| --- | --- | --- | +| `typescript-schema-builder.mdx` | 282 | 282 | +| `capabilities.mdx` | 97 | 98 | + +The one added line on the capabilities page is the real `"checkConstraint": true` entry in the `contract.json` excerpt. + +## 5. Checker status + +- `typescript-schema-builder.mdx` — `check-plain.sh` prints **clean**; the extra page-conventions grep is clean (only `codecId` inside code, and the pinned `[#keep-the-contract-file-pure]` anchor). +- `capabilities.mdx` — the extra page-conventions grep is clean. `check-plain.sh` still reports seven lines, all of which you told me to keep: `title: Capabilities`, `url: /orm/contract-authoring/capabilities`, `metaTitle: Capabilities in Prisma ORM`, and the four headings. Every prose sentence is clean. Tell me if you want the headings and title reworded with pinned anchors instead, and I will do that in one pass. \ No newline at end of file From c468006e5f38b6e3c57336b00ebfecb1cdd228dc Mon Sep 17 00:00:00 2001 From: willbot Date: Mon, 14 Sep 2026 09:18:50 +0200 Subject: [PATCH 05/14] docs(orm8): contract-authoring pages, reader-review round one applied Five readers coming from Prisma ORM 7, one per page, and a fixer per page. The pages now say where datasource and generator went, that prisma generate is gone, which table prisma db sign writes and when to run it, that @db.VarChar became VarChar(...), how the many-to-many join model is matched, what a variant inherits, the field helpers for common column types, how to add indexes and composite keys in the TypeScript builder, that rel.belongsTo alone creates no foreign key, and what to do on ORM.CAPABILITY_MISSING. contract.d.ts has Output and Input types per type block, not per model. Co-Authored-By: Claude Fable 5.1 Signed-off-by: willbot Signed-off-by: Will Madden --- .../orm/contract-authoring/capabilities.mdx | 69 +++++---- .../orm/contract-authoring/psl-syntax.mdx | 60 ++++++-- .../the-contract-artifact.mdx | 59 +++++--- .../contract-authoring/the-data-contract.mdx | 45 +++--- .../typescript-schema-builder.mdx | 143 +++++++++++------- apps/docs/cspell.json | 1 + .../capabilities.round1.md | 71 +++++++++ .../psl-syntax.round1.md | 95 ++++++++++++ .../the-contract-artifact.round1.md | 95 ++++++++++++ .../the-data-contract.round1.md | 70 +++++++++ .../typescript-schema-builder.round1.md | 91 +++++++++++ 11 files changed, 652 insertions(+), 147 deletions(-) create mode 100644 docs/orm-docs-audit/c21-contract-authoring/capabilities.round1.md create mode 100644 docs/orm-docs-audit/c21-contract-authoring/psl-syntax.round1.md create mode 100644 docs/orm-docs-audit/c21-contract-authoring/the-contract-artifact.round1.md create mode 100644 docs/orm-docs-audit/c21-contract-authoring/the-data-contract.round1.md create mode 100644 docs/orm-docs-audit/c21-contract-authoring/typescript-schema-builder.round1.md diff --git a/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx b/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx index f6164838e06..2a369fb2ac7 100644 --- a/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx @@ -3,87 +3,86 @@ title: Supported database features description: 'The contract records which database features your setup supports, so Prisma ORM can reject an unsupported one early with a clear error.' url: /orm/contract-authoring/capabilities metaTitle: Supported database features in Prisma ORM -metaDescription: 'How a Prisma ORM contract records which database features your setup supports, where those declarations come from, what each key controls, and what happens when one is missing.' +metaDescription: 'How a Prisma ORM contract records which database features your setup supports, where the list comes from, what each key controls, and what happens when one is missing.' --- -Not every database stack supports every feature. One setup can run lateral joins, `RETURNING` clauses, and vector distance operations; another cannot. - -The `capabilities` section of your contract, the `contract.prisma` file that replaced `schema.prisma`, records which features yours supports. Prisma ORM checks it before it uses one of them. A feature your stack lacks fails early with an error that names the missing key, instead of reaching the database and failing there. +Not every database supports every feature. One setup can run lateral joins, `RETURNING` clauses, and vector distance operations. Another cannot. [`npx prisma contract emit`](/cli/contract-emit) writes `contract.json` and `contract.d.ts` beside your [contract](/orm/contract-authoring/the-data-contract) source file. The `capabilities` section of `contract.json` lists the database features your setup supports, and you never edit it. Prisma ORM reads that list before it uses one of those features. A feature your setup lacks fails right away, with an error that names the key for the missing feature, instead of reaching the database and failing there. ## Where the `capabilities` section comes from [#where-capabilities-come-from] -You do not write this section yourself. [`npx prisma contract emit`](/cli/contract-emit) writes `contract.json` and `contract.d.ts` beside the source, and while it does so it merges the declarations of every component composed in your project: the target (PostgreSQL, SQLite, MongoDB), its adapter, and any extension packs named in the config. The merged result lands in the contract's `capabilities` section, grouped by namespace: +Three things each say which features they support: your database (PostgreSQL, SQLite, or MongoDB), its driver package, and any extension packages listed in `prisma.config.ts`. `npx prisma contract emit` collects all of those answers into `contract.json`, grouped by prefix: -```json title="prisma/contract.json (excerpt)" +```json title="src/prisma/contract.json (excerpt)" { "capabilities": { "postgres": { "distinctOn": true, - "jsonAgg": true, - "lateral": true, - "limit": true, - "orderBy": true, - "pgvector.cosine": true, - "returning": true + "pgvector.cosine": true }, "sql": { - "checkConstraint": true, - "defaultInInsert": true, - "enums": true, "lateral": true, - "returning": true, "scalarList": true } } } ``` -The `sql` namespace holds keys shared across SQL databases. The `postgres` namespace holds PostgreSQL-specific keys. Extension packs contribute keys of their own: composing the pgvector pack is what adds `pgvector.cosine` above. Because the packs declare the keys, adding or removing an extension in `prisma.config.ts` and running `npx prisma contract emit` again changes the set with no further work. +Keys under `sql` are shared across SQL databases. Keys under `postgres` belong to PostgreSQL only. Extension packages add keys of their own, and `pgvector.cosine` above comes from the pgvector package. Install it with `npm install @prisma/orm-extension-pgvector`, then list it in `prisma.config.ts` and run `npx prisma contract emit` again: + +```typescript title="prisma.config.ts" +import { definePrismaConfig } from "prisma/config"; +import pgvector from "@prisma/orm-extension-pgvector/control"; +import { defineConfig as ormConfig } from "@prisma/orm-postgres/config"; + +export default definePrismaConfig({ + orm: ormConfig({ contract: "./src/prisma/contract.prisma", extensions: [pgvector] }), +}); +``` ## What the `capabilities` section controls [#what-capabilities-gate] -The keys are checked at two points, both before any SQL reaches the database. +Prisma ORM checks the keys twice, both times before any SQL reaches the database. -**When the contract is built.** If the contract uses a feature the target cannot store, `npx prisma contract emit` fails with a diagnostic. SQLite does not report `scalarList`, so a scalar list field in a contract targeting SQLite fails: +**When the contract is built.** If your contract uses a feature your database cannot store, `npx prisma contract emit` fails. SQLite has no `scalarList` key, so a list field such as `tags String[]` fails on SQLite: ```text Field "User.tags" is a scalar list, but target "sqlite" does not support scalar lists (the adapter does not report the "scalarList" capability). +Remove the list or author it against a target that supports scalar lists. ``` -**When a query is built.** Query-builder methods that need a key read the contract's `capabilities` section and throw an error whose `code` is `ORM.CAPABILITY_MISSING`, naming the method and the key: +Do one of the two: remove the list field, or point the contract at a database that supports list fields, such as PostgreSQL. + +**When a query is built.** A query-builder method that needs a feature throws an error with code `ORM.CAPABILITY_MISSING`. The message names the method and the key: ```text distinctOn() requires capability postgres.distinctOn lateralJoin() requires capability sql.lateral ``` -The error is raised when your code constructs the query, so a test that builds the query catches it. A query that uses a feature your stack lacks cannot be built at all. +That message means the method is not available on the database you are connected to. Rewrite the query without it, or move to a database that supports the feature. If the key comes from an extension package, install that package, list it in `prisma.config.ts`, and run `npx prisma contract emit` again. The error is raised when your code builds the query, so a test that builds the query catches it. + +Your own code can read the same list: the client exposes it as `db.contract.capabilities`, so `db.contract.capabilities.sql?.lateral` is `true` when lateral joins are available. ## Example keys [#example-capabilities] -A few of the keys the built-in components declare: +A few of the keys your database, its driver package, and the built-in extension packages supply: | Key | What it covers | | --- | --- | -| `sql.lateral` | Lateral joins (`lateralJoin()`); PostgreSQL reports `true`, SQLite reports `false` | +| `sql.lateral` | Lateral joins (`lateralJoin()`) | | `sql.returning` | `RETURNING` clauses on writes | -| `sql.enums` | Enum value sets enforced in the database; SQLite reports `false` | -| `sql.defaultInInsert` | Using `DEFAULT` as a value in multi-row inserts | -| `sql.scalarList` | Scalar list fields in the contract | +| `sql.scalarList` | List fields such as `tags String[]` | | `postgres.distinctOn` | `DISTINCT ON` queries (`distinctOn()`) | -| `postgres.jsonAgg` | JSON aggregation for nested reads | -| `postgres.pgvector.cosine` | Contributed by the pgvector pack, alongside its cosine operations | +| `postgres.pgvector.cosine` | Cosine distance from the pgvector package | -This table is a sample, not the full list. Each target, adapter, and pack ships its own declarations. Your project's emitted `contract.json` is the full list of what your stack supports. +PostgreSQL has all three `sql` keys above, and SQLite has none of them. This table is a sample, not the full list. Open your project's `contract.json` to see every key your setup supports. -MongoDB currently declares no keys: this mechanism mostly separates SQL targets and their extensions, and the MongoDB pipeline does not yet check features this way. +MongoDB has no keys at all, so none of these checks apply to it. ## The `capabilities` section and `prisma db verify` [#capabilities-and-verification] -The keys describe what the composed software supports, as declared by the target, the adapter, and the packs. They are recorded in the contract when you run `npx prisma contract emit` rather than read from the live database, so the same contract behaves identically in every environment. Checking the database itself is separate: [`db verify`](/cli/db-verify) compares the database against the contract's schema and profile, using the hashes described in [the contract artifact](/orm/contract-authoring/the-contract-artifact#the-content-hashes). - -Extensions are the main source of keys beyond the core set. [Using extensions](/orm/extensions/using-extensions) shows the full flow from install to first query. +The keys say what your installed software supports. They are recorded when you run `npx prisma contract emit`, not read from the live database, so the same contract behaves the same way in every environment. Checking the live database is a separate job: [`db verify`](/cli/db-verify) compares the database against your contract, using the recorded hashes described in [contract.json and contract.d.ts](/orm/contract-authoring/the-contract-artifact#the-content-hashes). Extension packages are the main source of keys beyond the built-in ones, and [using extensions](/orm/extensions/using-extensions) shows the full path from install to first query. ## Prompt your coding agent @@ -94,5 +93,5 @@ Projects created with `npm create prisma@latest` include the [Prisma ORM skills] ## Next steps -- See where the `capabilities` block is in [contract.json and contract.d.ts](/orm/contract-authoring/the-contract-artifact). -- Compose extension packs, and the keys they bring, in the [CLI configuration](/cli/configuration). +- See where the `capabilities` block is, and what `contract.d.ts` holds, in [contract.json and contract.d.ts](/orm/contract-authoring/the-contract-artifact). +- Add extension packages, and the keys they bring, in the [CLI configuration](/cli/configuration). diff --git a/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx b/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx index 1f36935df6a..bcc7e8d3c70 100644 --- a/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx @@ -6,9 +6,17 @@ metaTitle: Author the Prisma ORM contract in PSL metaDescription: 'Learn how to write a Prisma ORM contract in the Prisma schema language, including named types, enums, value objects, relations, and extension types.' --- -PSL is the preferred way to author [your contract](/orm/contract-authoring/the-data-contract), the `contract.prisma` file that replaced `schema.prisma`. You write a Prisma schema file, usually `src/prisma/contract.prisma`, and [`prisma contract emit`](/cli/contract-emit) writes `contract.json` and `contract.d.ts` beside it. +PSL, the Prisma Schema Language, is the preferred way to author [your contract](/orm/contract-authoring/the-data-contract), the `contract.prisma` file that replaced `schema.prisma`. You write a Prisma schema file, usually `src/prisma/contract.prisma`, and [`npx prisma contract emit`](/cli/contract-emit) writes `contract.json` and `contract.d.ts` beside it. -If you know the Prisma schema language, most of a contract file reads exactly as you expect. The Prisma ORM 8 additions are named types, typed enums, value objects, base models with variants, and extension types. +If you know the Prisma schema language, most of a contract file reads exactly as you expect. Prisma ORM 8 adds five things: + +- named types: reusable aliases for a type and its storage details. +- enums: unchanged, except that an enum can now say how its values are stored. +- value objects: a structured value stored inside its parent row, with no table of its own. +- base models and variants: one model reusing another model's fields and rows. +- extension types: field types that come from an npm package, such as vectors. + +The `datasource` and `generator` blocks are gone. The connection URL and the file paths are set in `prisma.config.ts` instead. [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7#schema) lists every change to the schema file, including what each old `@db.` attribute becomes. ## A complete contract @@ -99,11 +107,13 @@ model Post { } ``` +The first line, `// use prisma-8`, tells the Prisma editor extension that the file is a Prisma ORM 8 contract. It is not required: the contract works without it. `prisma orm init` writes it, so keep it. + Run `npx prisma contract emit` after any change to refresh the two files. ## Point the config at the schema -The config's `contract` path names the one file Prisma ORM reads. A `.prisma` extension selects PSL authoring: +The config's `contract` path names the one file Prisma ORM reads. It takes a single path, so there is no folder of schema files. A `.prisma` extension selects PSL authoring: ```typescript title="prisma.config.ts" tab="PostgreSQL" import { definePrismaConfig } from "prisma/config"; @@ -127,6 +137,8 @@ export default definePrismaConfig({ }); ``` +The import chooses the database. `@prisma/orm-postgres/config` makes it a PostgreSQL project, `@prisma/orm-mongo/config` a MongoDB one. The connection URL goes in this file too, in a `db` key beside `contract`. [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7#schema) shows a config with both and lists the other keys. + Projects created with `npm create prisma@latest -- my-app` already have this config and a starter contract file. ## Models and fields @@ -134,9 +146,11 @@ Projects created with `npm create prisma@latest -- my-app` already have this con Models declare fields with a type, an optional `?` marker, and attributes: - `@id` marks the primary key. `@@id([a, b])` declares a composite key. -- `@unique` adds a unique constraint. `@@index([...])` declares a secondary index. -- `@default(...)` sets a default. Database function defaults such as `@default(now())` become column defaults in the database. Generated defaults such as `@default(uuid())` are applied by Prisma ORM before each write. They appear in the contract's `execution` section rather than as a column default. +- `@unique` adds a unique constraint on one field. `@@unique([userId, title])` adds one across several fields. +- `@@index([...])` declares a secondary index. +- `@default(...)` sets a default. Database function defaults such as `@default(now())` become column defaults in the database. Generated defaults such as `@default(uuid())` are produced by Prisma ORM before each write, not by the database. - `@map("column_name")` sets a field's column name in the database. `@@map("table_name")` sets the table or collection name when it differs from the model name. +- `@updatedAt` no longer exists. Write `updatedAt temporal.updatedAt()`, with the call where the type would go. How IDs map differs by database: @@ -178,11 +192,13 @@ types { } ``` -Fields then use `ShortName` like any built-in type. The alias keeps the storage decision (a `varchar(35)` column rather than `text`) in one place. Native PostgreSQL types are written as the type, so a field can also use `VarChar(35)`, `Uuid`, or `Timestamptz` directly without an alias. +Fields then use `ShortName` like any built-in type. The alias keeps the storage decision in one place: a `varchar(35)` column rather than `text`. + +An alias is optional. A field can name the native PostgreSQL type directly, such as `VarChar(35)`, `Uuid`, or `Timestamptz`. In Prisma ORM 8 the native type is the field's type, so `String @db.VarChar(35)` from Prisma ORM 7 becomes `VarChar(35)`. The `@db.` attributes are gone. ## Enums -An enum declares how its values are stored with `@@type`, and optionally the stored value for each member: +An enum lists its members. It can also say how their values are stored, with `@@type`, and what each member stores: ```prisma enum Priority { @@ -193,7 +209,11 @@ enum Priority { } ``` -`@@type("pg/text@1")` stores the values as `text` on PostgreSQL. That name is the PostgreSQL type plus a version, always `@1` today. When a member has no explicit value, the member name itself is stored. Leave `@@type` out and Prisma ORM infers it from the member values. In the database, Prisma ORM enforces the allowed values with a `CHECK` constraint on each column that uses the enum. +`@@type("pg/text@1")` stores the values as `text` on PostgreSQL. That name is the PostgreSQL type plus a version, always `@1` today. Write `@@type("pg/int4@1")` to store them as integers instead. When a member has no explicit value, the member name itself is stored. + +`@@type` is optional. Leave it out and Prisma ORM picks the type from the member values. Bare member names and string values give the database's text type. Integer values give its integer type. + +An `enum` block is not a PostgreSQL `enum` type: the column is text or an integer. If your database already has a PostgreSQL `enum` type, declare it in a `native_enum` block and type the field `pg.enum(Role)`. [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7#schema) shows both. ## Value objects @@ -230,7 +250,9 @@ model User { } ``` -Many-to-many relations go through an explicit model for the join table. Its primary key must cover both foreign keys. The list fields on both sides then resolve through it: +Add `onDelete` and `onUpdate` to the same `@relation`. They belong on the side that holds the foreign key, not on the list side. For a one-to-one, make the other side singular instead of a list, and put `@unique` on the foreign-key field. + +Many-to-many relations need a model for the join table. You write that model yourself: there is no implicit many-to-many. Prisma ORM matches the two list fields to the model that holds a foreign key to each side and whose primary key is exactly those two foreign keys: ```prisma model Post { @@ -253,7 +275,7 @@ model PostTag { } ``` -`contract.json` records the relation as `N:M` with the join table's columns, so queries can traverse `post.tags` directly. +You then read `post.tags` as a list of `Tag`, without mentioning `PostTag` in the query. For which shape to choose and which side owns the foreign key, see [relational data modeling](/orm/data-modeling/relational-databases) and [MongoDB data modeling](/orm/data-modeling/mongodb). @@ -280,11 +302,15 @@ model Bug { } ``` -Rows with `type = "bug"` are `Bug` records. On PostgreSQL, the variant's `@@map` picks its storage layout: with its own `@@map`, as here, the variant's fields live in their own table sharing the base model's primary key. Without one, they live in the base table as nullable columns. [Relational data modeling](/orm/data-modeling/relational-databases#polymorphic-relations) covers choosing between the two. On MongoDB, variants add their fields to documents in the base model's collection, so a variant declares `@@base` but no `@@map` of its own. +Rows with `type = "bug"` are `Bug` records. A variant reuses its base model's fields, so a `Bug` has `id`, `title`, and `type` as well as `severity` and `stepsToRepro`. You query it through the base model: `db.orm.public.Task.variant('Bug')` returns the `Bug` rows only. See [`variant()`](/orm/reference/orm-client#variant). + +On PostgreSQL, the variant's `@@map` picks where the variant's own fields are stored. With its own `@@map`, as `Bug` has here, they are in a table of their own that shares the base model's primary key. Without one, they are nullable columns in the base table. [Relational data modeling](/orm/data-modeling/relational-databases#polymorphic-relations) covers choosing between the two. + +On MongoDB, a variant adds its fields to documents in the base model's collection, so it declares `@@base` but no `@@map` of its own. ## Extension types -Extension packs contribute types through constructor calls in the `types` block. Add the pack to the config, then use its types: +An extension pack is an npm package that adds field types Prisma ORM does not ship, such as vectors. Add the pack to the config, then call its types in the `types` block: ```typescript title="prisma.config.ts" import { definePrismaConfig } from "prisma/config"; @@ -300,6 +326,8 @@ export default definePrismaConfig({ ``` ```prisma title="src/prisma/contract.prisma" +// use prisma-8 + types { Embedding1536 = pgvector.Vector(1536) } @@ -310,7 +338,9 @@ model Post { } ``` -`prisma contract emit` resolves extension types through the packs listed in the config, so list the pack before using its types. Run `npx prisma contract emit` again after changing the extension list. +The `pgvector` part of `pgvector.Vector(1536)` is a fixed name the pack declares. It is not the name you gave the import in `prisma.config.ts`, so renaming that import does not change what you write here. + +`npx prisma contract emit` resolves extension types through the packs listed in the config, so list the pack before using its types. Run it again after changing the extension list. [Using extensions](/orm/extensions/using-extensions) covers installing a pack. ## Starting from an existing database @@ -318,7 +348,7 @@ If the database already exists, don't write the schema by hand. [`contract infer ## Prompt your coding agent -Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent; in an existing project, run `npx prisma skills sync`. The `prisma-8` skill covers PSL authoring. Ask your agent to: +Projects created with `npm create prisma@latest -- my-app` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent. Skills are instruction files the agent reads. In an existing project, run `npx prisma skills sync`. The `prisma-8` skill covers PSL authoring. Ask your agent to: - "Using the prisma-8 skill, add a Status enum stored as text and use it on the Order model." - "Add a one-to-many between User and Post with the foreign key on Post." @@ -326,6 +356,6 @@ Projects created with `npm create prisma@latest` include the [Prisma ORM skills] ## Next steps -- Run `npx prisma contract emit` and inspect [`contract.json` and `contract.d.ts`](/orm/contract-authoring/the-contract-artifact). +- Run `npx prisma contract emit` and inspect [`contract.json` and `contract.d.ts`](/orm/contract-authoring/the-contract-artifact). You do not import them yourself: `db`, the client you create in `src/prisma/db.ts`, reads both. - If you prefer defining models in code, see [authoring in TypeScript](/orm/contract-authoring/typescript-schema-builder). - Apply the contract to a database with [`db init`](/cli/db-init) or plan changes with [`migration plan`](/cli/migration-plan). diff --git a/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx b/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx index 0a1122b4469..d5426342c9b 100644 --- a/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx @@ -6,40 +6,42 @@ metaTitle: The Prisma ORM contract artifacts metaDescription: 'What contract.json and contract.d.ts contain, why the same source always produces the same two files, how the hashes work, and how your app and the CLI read them.' --- -[`prisma contract emit`](/cli/contract-emit) writes `contract.json` and `contract.d.ts` beside the source by default: +You write [your contract](/orm/contract-authoring/the-data-contract) in one source file: `src/prisma/contract.prisma` in PSL, or `src/prisma/contract.ts` in TypeScript. [`npx prisma contract emit`](/cli/contract-emit) reads that file and writes two files beside it: | File | Contents | Read by | | --- | --- | --- | -| `contract.json` | The machine-readable contract: models, how they are stored, which database features they need, and the hashes | `db`, the migration tooling, the database checks, and anything else that needs to read your schema | +| `contract.json` | The machine-readable contract: models, how they are stored, which database features they need, and the hashes | `db`, `prisma db sign`, `prisma db verify`, and `prisma migration plan` | | `contract.d.ts` | TypeScript declarations derived from the contract | The query APIs and your application code, for typed models and results | -Both files are generated. Do not edit them. Change the [PSL](/orm/contract-authoring/psl-syntax) or [TypeScript](/orm/contract-authoring/typescript-schema-builder) source and run `prisma contract emit` again. Each file starts with a notice that says exactly this. +`db` is the client you create once in `src/prisma/db.ts`. `prisma orm init` writes that file for you. + +Both files are generated. Do not edit them. Change the [PSL](/orm/contract-authoring/psl-syntax) or [TypeScript](/orm/contract-authoring/typescript-schema-builder) source and run `npx prisma contract emit` again. Each file starts with a notice that says exactly this. To write the two files to another directory, see [`prisma contract emit`](/cli/contract-emit). ## The same source always produces the same two files [#deterministic-emission] -Run `prisma contract emit` on the same source on any machine and you get byte-identical files. Keys are written in a fixed key order and values in a normalized form, which is what lets you read the files in a code review and hash them. +Run `npx prisma contract emit` on the same source on any machine and you get the same two files, byte for byte. You can read the diff of either file in a code review. -That is also why the contract source must not read the environment, the clock, or random values. A source that did would produce different files each run, and every hash below would stop meaning anything. The [TypeScript authoring page](/orm/contract-authoring/typescript-schema-builder#keep-the-contract-file-pure) lists the rules. +That only holds if the contract source does not read the environment, the clock, or random values. A source that did would produce different files each run, and every hash below would stop meaning anything. The [TypeScript authoring page](/orm/contract-authoring/typescript-schema-builder#keep-the-contract-file-pure) lists the rules. ## The content hashes -`prisma contract emit` computes a hash over each of these parts of the contract: +`npx prisma contract emit` computes a hash over each of these parts of the contract. You never compute or compare them yourself. | Hash | Covers | Changes when | | --- | --- | --- | -| `storageHash` | The storage layout: tables, columns, primary keys, uniques, indexes, foreign keys, and the value sets behind enums | A table, column, key, constraint, or index changes | +| `storageHash` | The storage layout: tables, columns, primary keys, uniques, indexes, foreign keys, and the allowed values of each enum | A table, column, key, constraint, or index changes | | `executionHash` | The defaults Prisma ORM applies before a write, such as `@default(uuid())` | A generated default changes | -| `profileHash` | The database this contract targets, and its family | The contract targets a different database | +| `profileHash` | The database this contract targets, and whether it is SQL or MongoDB | The contract targets a different database | -The hashes are how Prisma ORM connects the contract to a live database. [`prisma db sign`](/cli/db-sign) checks that the database satisfies the contract, then records `storageHash` and `profileHash` in a small table it creates in the database. [`prisma db verify`](/cli/db-verify) compares the contract you hold against that record and against the live schema, and fails when they disagree. `db` makes the same comparison before its first query, but it logs a warning and carries on. +The hashes are how Prisma ORM connects the contract to a live database. [`npx prisma db sign`](/cli/db-sign) checks that the database satisfies the contract, then records `storageHash` and `profileHash` in a table it creates called `prisma_contract.marker`. You never write to that table yourself. Run `db sign` when the live database already matches the contract, for example after [`prisma contract infer`](/cli/contract-infer) on an existing database, or after a deployment applied the changes. [`npx prisma db verify`](/cli/db-verify) compares the contract you hold against that record and against the live schema, and fails when they disagree. ## Inside `contract.json` -The contract separates what your application models from how it is stored. The `domain` section describes models, fields, and relations; the `storage` section describes tables, columns, keys, and indexes. On MongoDB it describes collections and indexes instead. Each model's own `storage` block joins the two. Everything is grouped by namespace. On PostgreSQL the namespace is the schema, `public` unless you set one. +The contract keeps your application's view of the data separate from the way the database stores it. The `domain` section describes models, fields, and relations. The `storage` section describes tables, columns, keys, and indexes. On MongoDB it describes collections and indexes instead. Each model's own `storage` block joins the two. Everything is grouped by namespace. On PostgreSQL the namespace is the schema. It is `public` unless you put the models in a `namespace` block, such as `namespace auth { }`, in your [PSL source](/orm/contract-authoring/psl-syntax). An abridged `contract.json` for a `User` and `Post` schema: -```json title="prisma/contract.json (abridged)" +```json title="src/prisma/contract.json (abridged)" { "schemaVersion": "1", "targetFamily": "sql", @@ -101,18 +103,24 @@ An abridged `contract.json` for a `User` and `Post` schema: The sections, top to bottom: -- **`schemaVersion`, `targetFamily`, `target`**: the contract format version and the database this contract targets. -- **`roots`**: each table name mapped to the model stored in it. The SQL query builder is keyed by those table names, as in `db.sql.public.user`, and the model name is what `db.orm.public.User` uses. -- **`domain`**: the application's view. Each scalar field records whether it is nullable and its type id, such as `pg/text@1`, the PostgreSQL type plus a version, always `@1` today. Relations record how many records are on each side and the fields they join on. The model's `storage` block maps fields to columns. -- **`storage`**: the database's view. Tables with their columns, each with a native type and a type id, plus primary keys, uniques, indexes, foreign keys, check constraints, and the value sets behind enums. This is the section the migration tooling compares between versions and `prisma db verify` checks the live schema against. -- **`execution`**: the defaults Prisma ORM applies before a write, such as `@default(uuid())`. These are not column defaults in the database. -- **`capabilities`** and **`extensions`**: which database features this contract can use, collected from the adapter for your database and any extension packs when you run `prisma contract emit`. The query APIs check these before building a query that needs one. See [which database features the contract needs](/orm/contract-authoring/capabilities). +- **`schemaVersion`, `targetFamily`, `target`**: the contract format version, whether the database is SQL or MongoDB, and which database this contract targets. +- **`roots`**: the table name of every model, so the query APIs know what to call each one. The SQL query builder uses the table name, as in `db.sql.public.user`. The model API uses the model name, as in `db.orm.public.User`. Both are described in the [ORM reference](/orm/reference). +- **`domain`**: the application's view. Each scalar field records whether it is nullable and its `codecId`, such as `pg/text@1`, which is the PostgreSQL type plus a version. Every id ends in `@1` today. Relations record how many records are on each side and the fields they join on. The model's `storage` block maps fields to columns. +- **`storage`**: the database's view. Tables with their columns, each with a native type and a `codecId`, plus primary keys, uniques, indexes, foreign keys, check constraints, and the allowed values of each enum. This is the section the migration tooling compares between versions and `prisma db verify` checks the live schema against. +- **`execution`**: the defaults Prisma ORM applies before a write, such as `@default(uuid())`. Prisma ORM fills these in as it sends the write. They are not column defaults in the database, so a write that does not go through Prisma ORM gets whatever the column itself defaults to. +- **`capabilities`**: which database features this contract can use. `npx prisma contract emit` collects them from the adapter for your database and from any extension packs named in your config. The query APIs check them before building a query that needs one. See [supported database features](/orm/contract-authoring/capabilities). +- **`extensions`**: one entry for each extension pack composed in your project, with the types that pack adds. It is `{}` when you compose none. ## Inside `contract.d.ts` -The declarations file gives the type system the same information. It exports the contract type, branded hash types matching `contract.json`, and input/output types for every model: +The declarations file gives the type system the same information. It exports: + +- `Contract`, the type the client is built from. `db.ts` imports it. +- One type per hash, such as `StorageHash` and `ProfileHash`. The hash string is part of the type. +- `Output` and `Input` for each `type` block in your contract, such as `AddressOutput` and `AddressInput` below. +- `FieldOutputTypes` and `FieldInputTypes`, which hold the field types of every model, keyed by namespace and model name. -```typescript title="prisma/contract.d.ts (excerpt)" +```typescript title="src/prisma/contract.d.ts (excerpt)" export type StorageHash = StorageHashBase<"9f49f8f9e51a9cc016f1ec2098ebae9406521a3cc2cf00207adc795078333d8b">; export type ProfileHash = @@ -126,27 +134,34 @@ export type AddressOutput = { }; ``` -Because the hashes are literal types, a client built against one version of the contract will not type-check against another version's files. TypeScript catches a mismatched contract before `prisma db verify` has to. +`CodecTypes`, `StorageHashBase`, and `ProfileHashBase` are helper types the file declares for itself. You never write them. Because the hash string is part of the type, files from two different runs of `npx prisma contract emit` do not type-check against each other. If you regenerate `contract.json` and not `contract.d.ts`, TypeScript catches it before `prisma db verify` has to. ## How the application consumes the artifacts `db` is built from both files: `contract.json` as the value, `contract.d.ts` as the type. `prisma orm init` writes `src/prisma/db.ts` for you. ```typescript title="src/prisma/db.ts" +import "dotenv/config"; import postgres from "@prisma/orm-postgres/runtime"; import type { Contract } from "./contract.d"; import contractJson from "./contract.json" with { type: "json" }; export const db = postgres({ contractJson, + url: process.env['DATABASE_URL']!, }); ``` -Before its first query, `db` reads the record `prisma db sign` wrote in the database and compares it with the contract's hashes. If they do not match, it logs a warning and runs the query anyway. Run `prisma db verify` when you need the check to fail instead. +The connection string comes from the `DATABASE_URL` environment variable. The import path `./contract.d` is written that way on purpose. It is not a typo, and TypeScript resolves it to `contract.d.ts`. Before its first query, `db` reads the record `prisma db sign` wrote in the database and compares it with the contract's hashes. If they do not match, it logs a warning and runs the query anyway. Run `prisma db verify` when you need the check to fail instead. ## Version control -Commit `contract.json` and `contract.d.ts` alongside the source. They hold structure only, no data and no credentials. Committing them lets teammates, CI, and deploys read the contract without running `prisma contract emit` first. Run `prisma contract emit` after every source change so the two files never trail the source. A CI job can enforce this by running the command and failing if the working tree changes. +Commit `contract.json` and `contract.d.ts` alongside the source. They hold structure only, no data and no credentials. Committing them lets teammates, CI, and deploys read the contract without running `npx prisma contract emit` first. Run `npx prisma contract emit` after every source change so the two files never trail the source. A CI job can check this in two lines: + +```bash +npx prisma contract emit +git diff --exit-code src/prisma +``` ## Prompt your coding agent diff --git a/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx b/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx index 622beaa33c5..0d79d12eabc 100644 --- a/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx @@ -8,9 +8,9 @@ metaDescription: 'What the Prisma ORM data contract is, how PSL and TypeScript a Every Prisma ORM project has one description of its data: the models, their fields, and how they map to database tables. That description is the data contract, the `contract.prisma` file that replaced `schema.prisma`. -For example, a blog's contract declares a `User` and a `Post`, the fields each carries, and how they relate. You author it in PSL, the Prisma schema language: +For example, a blog's contract declares a `User` and a `Post`, the fields each one has, and how they relate. You author it in PSL, the Prisma schema language: -```prisma title="prisma/contract.prisma" +```prisma title="src/prisma/contract.prisma" // use prisma-8 model User { @@ -28,11 +28,11 @@ model Post { } ``` -The first line of every `.prisma` contract is `// use prisma-8`. It marks the file as a Prisma ORM 8 contract rather than a Prisma ORM 7 schema. The Prisma language server only offers diagnostics, completion, and formatting on files that carry it. Completion covers field, model, and block attribute names and their argument keys, taken from the installed target and extensions. Contracts written before `8.0.0-rc.10` start with `// use prisma-next`. The language server still accepts that line and replaces it when you format the file. +The first line of every `.prisma` contract is `// use prisma-8`. It marks the file as a Prisma ORM 8 contract rather than a Prisma ORM 7 schema file. Editor support depends on that line: the Prisma language server only reports problems, autocompletes, and formats files that start with it. Contracts written before `8.0.0-rc.10` start with `// use prisma-next` instead. The language server still accepts that older line and replaces it when you format the file. -`prisma contract emit` turns this source into two machine-readable files, and the rest of the toolchain works against them. Your queries are type-checked against the contract, migrations are planned as changes to it, and `prisma db verify` checks a live database against it. +`prisma contract emit` turns this file into two files that the rest of the toolchain reads: `contract.json` and `contract.d.ts`. Your queries are type-checked against the contract, migrations are planned as changes to it, and `prisma db verify` checks a live database against it. Think of it as a `package-lock.json` for your data: an exact, versioned record of what your application expects from its database. -Think of it as a `package-lock.json` for your data: an exact, versioned record of what your application expects from its database. +If you already have a Prisma ORM 7 `schema.prisma`, [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7) lists what changed and the [PostgreSQL upgrade guide](/guides/upgrade-prisma-orm/postgresql) walks through converting the file. :::note[Contract vs. schema] @@ -42,15 +42,13 @@ In Prisma ORM, the **contract** is what you write in your code, and the **schema ## Why a contract -Prisma ORM 7 compiles your schema into generated client code. The description of your data is in there, but only the generated client can read it. +Prisma ORM 7 compiles your `schema.prisma` into generated client code. The description of your data is in there, but only the generated client can read it. Prisma ORM keeps that description in the open. Your contract file compiles to two plain files. `contract.json` describes your models, how they are stored, and [which database features they need](/orm/contract-authoring/capabilities), such as pgvector. `contract.d.ts` holds the TypeScript types derived from it. The same source always produces the same two files, so you can read them in a code review, hash them, and hand them to tools and coding agents. -Prisma ORM keeps that description in the open. Your contract source compiles to two plain files. `contract.json` describes your models, how they are stored, and which database features the contract needs. `contract.d.ts` holds the TypeScript types derived from it. The same source always produces the same two files, so you can read them in a code review, hash them, and hand them to tools and coding agents. - -The contract also identifies the exact version of the schema it describes. `prisma contract emit` computes hashes over its contents. When you run [`prisma db sign`](/cli/db-sign), Prisma ORM records which contract the database matches in a small table it creates in the database. The client reads that record before its first query and logs a warning if the record does not match the contract the client holds. To fail instead of warn, for example in a deploy check, run [`prisma db verify`](/cli/db-verify). +The contract also identifies its own exact version. `prisma contract emit` computes hashes from the contract and writes them into `contract.json`. [`prisma db sign`](/cli/db-sign) checks that your database satisfies the contract, then records those hashes in the `prisma_contract.marker` table, which it creates in your database. Run it once the database matches the contract, after you have applied your migrations. Your app then logs a warning when the database stops matching. To fail instead of warn, for example in a deploy check, run [`prisma db verify`](/cli/db-verify). ## How it works -A Prisma ORM project declares one contract source in `prisma.config.ts`: +A Prisma ORM project declares one contract file in `prisma.config.ts`: ```typescript title="prisma.config.ts" import { definePrismaConfig } from "prisma/config"; @@ -58,42 +56,43 @@ import { defineConfig as ormConfig } from "@prisma/orm-postgres/config"; export default definePrismaConfig({ orm: ormConfig({ - contract: "./prisma/contract.prisma", + contract: "./src/prisma/contract.prisma", }), }); ``` -The source is either a Prisma schema file (`contract.prisma`) or a TypeScript file (`contract.ts`). The file extension selects the authoring mode. Both modes describe the same things: models with fields and relations, the tables and columns they map to, named types, enums, and any extension-provided types. +`prisma orm init` writes that config for you and installs the database package it imports, `@prisma/orm-postgres` for PostgreSQL. It also writes the contract file and `src/prisma/db.ts`. The contract file is either a Prisma schema file (`contract.prisma`) or a TypeScript file (`contract.ts`). The file extension selects the authoring mode, so to author in TypeScript you point `contract` at `./src/prisma/contract.ts`. Both modes describe the same things: models with fields and relations, the tables and columns they map to, named types, enums, and any types that [extension packages](/orm/contract-authoring/psl-syntax#extension-types) add. -Run `prisma contract emit` after every change to the contract. It writes `contract.json` and `contract.d.ts` beside the source: +Run `prisma contract emit` after every change to the contract. It replaces `prisma generate`, which Prisma ORM 8 does not have. It writes `contract.json` and `contract.d.ts` beside the source: ```npm npx prisma contract emit ``` -From there, the rest of the toolchain takes over: +From there, the rest of the toolchain takes over. `db` is the client you create once in `src/prisma/db.ts`, and `prisma orm init` writes that file for you: -- The query APIs load `contract.d.ts` to give you typed models, fields, and results. +- `db` loads `contract.d.ts` to give you typed models, fields, and results. - `db` reads `contract.json` and compares its hashes with the record `prisma db sign` wrote in the database. -- The migration tooling compares two contracts to plan schema changes, and [`prisma db verify`](/cli/db-verify) checks a live database against the current contract. +- The migration tooling compares two contracts to plan database changes, and [`prisma db verify`](/cli/db-verify) checks a live database against the current contract. ## Two authoring modes, one `contract.json` [#two-authoring-modes-one-artifact] -**[PSL](/orm/contract-authoring/psl-syntax)** is the usual way to write the contract. It is a compact language for describing data. It is what `npm create prisma@latest` scaffolds, and what [`prisma contract infer`](/cli/contract-infer) writes when you start from an existing database. +**[PSL](/orm/contract-authoring/psl-syntax)** is the usual way to write the contract. It is a compact language for describing data. It is what `npm create prisma@latest` scaffolds, and what [`prisma contract infer`](/cli/contract-infer) writes when you start from an existing database. That command reads the live database and writes a `contract.prisma` from what it finds. -For the cases PSL does not cover, define models with the **[TypeScript builder](/orm/contract-authoring/typescript-schema-builder)** instead. Reach for it when model definitions must be composed, generated, or shared as ordinary TypeScript modules. +Define models with the **[TypeScript builder](/orm/contract-authoring/typescript-schema-builder)** instead when you want to build them with code. Reach for it when model definitions must be composed, generated, or shared as ordinary TypeScript modules. -Both modes produce the same contract. For an equivalent schema they write the same `contract.json`, so migrations, database checks, and the query APIs behave the same no matter which mode you write. You give up nothing by staying with PSL. A project has exactly one contract file, the one named by `contract` in the config. Keep the other form out of the project, or treat it as a generated reference, so the two can never disagree. +Both modes produce the same contract. For the same models they write the same `contract.json`, so migrations, database checks, and `db` behave the same no matter which mode you write. A project has exactly one contract file, the one named by `contract` in the config. Do not keep a `contract.prisma` and a `contract.ts` in the same project, so the two can never disagree. ## What the contract contains The contract describes structure, not data: - models, fields, and relations, plus how they map to tables and columns -- storage details: primary keys, unique constraints, indexes, and foreign keys -- named types, enums, and `type` blocks, each a structured value stored inside its parent row with no table of its own -- the types and database features that extension packs add, such as pgvector's `Vector` -- hashes that identify this exact version of the schema +- how the data is stored: primary keys, unique constraints, indexes, and foreign keys +- [named types](/orm/contract-authoring/psl-syntax#named-types) and enums +- [`type` blocks](/orm/contract-authoring/psl-syntax#value-objects), each a structured value stored inside its parent row with no table of its own +- the types and database features that extension packages add, such as pgvector's `Vector` +- hashes that identify this exact version of the contract It contains no rows, no credentials, and no connection details, so committing the source, `contract.json`, and `contract.d.ts` to version control is safe and expected. diff --git a/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx b/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx index c24289eaaff..0b21f133403 100644 --- a/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx @@ -6,14 +6,14 @@ metaTitle: Author the Prisma ORM contract in TypeScript metaDescription: 'How to define a Prisma ORM contract in TypeScript with defineContract, including fields, relations, enums, storage mapping, and extension types.' --- -With TypeScript authoring you write your contract, the `contract.ts` file that replaced `schema.prisma`, in code rather than in a [`.prisma` file](/orm/contract-authoring/the-data-contract). You write `prisma/contract.ts` with the `defineContract` builder. [`npx prisma contract emit`](/cli/contract-emit) then writes `contract.json` and `contract.d.ts` beside the source, the same two files a PSL contract produces. +Prisma ORM 8 replaces `schema.prisma` with [a contract](/orm/contract-authoring/the-data-contract). You can write your contract in two forms. One is a `.prisma` file written in PSL, short for Prisma Schema Language. The other is TypeScript, in `src/prisma/contract.ts`, built with the `defineContract` builder. [`npx prisma contract emit`](/cli/contract-emit) writes `contract.json` and `contract.d.ts` beside the source, and both forms produce those same two files. ## When to choose TypeScript over PSL [PSL](/orm/contract-authoring/psl-syntax) is the preferred way to write the contract. Both forms produce the same two files, so you give up nothing by staying with PSL. Use the TypeScript builder for the cases PSL does not cover. Reach for it when: - model definitions must be split, composed, or reused across ordinary TypeScript modules or packages -- parts of the contract are assembled programmatically from other static definitions +- you want to build models in a loop from data you already keep in TypeScript, such as one model per entry in a list of table names If neither applies, write PSL. It is more compact, and it is what [`contract infer`](/cli/contract-infer) writes. @@ -27,7 +27,7 @@ import { defineConfig as ormConfig } from "@prisma/orm-postgres/config"; export default definePrismaConfig({ orm: ormConfig({ - contract: "./prisma/contract.ts", + contract: "./src/prisma/contract.ts", }), }); ``` @@ -38,16 +38,18 @@ import { defineConfig as ormConfig } from "@prisma/orm-mongo/config"; export default definePrismaConfig({ orm: ormConfig({ - contract: "./prisma/contract.ts", + contract: "./src/prisma/contract.ts", }), }); ``` ## A complete contract -The builder comes from your database's target package: `@prisma/orm-postgres/contract-builder` for PostgreSQL, `@prisma/orm-mongo/contract-builder` for MongoDB. +The builder comes from your database's package: `@prisma/orm-postgres/contract-builder` for PostgreSQL, `@prisma/orm-mongo/contract-builder` for MongoDB. -```typescript title="prisma/contract.ts" tab="PostgreSQL" +Export the contract under the name `contract`, as below, or as the file's default export. `prisma contract emit` looks for those two names and nothing else. + +```typescript title="src/prisma/contract.ts" tab="PostgreSQL" import pgvector from "@prisma/orm-extension-pgvector/pack"; import { defineContract, enumType, member, rel } from "@prisma/orm-postgres/contract-builder"; @@ -117,7 +119,7 @@ export const contract = defineContract( ); ``` -```typescript title="prisma/contract.ts" tab="MongoDB" +```typescript title="src/prisma/contract.ts" tab="MongoDB" import { defineContract, field, model, rel } from "@prisma/orm-mongo/contract-builder"; const User = model("User", { @@ -156,31 +158,46 @@ export const contract = defineContract({ Run `npx prisma contract emit` after any change to refresh the two files. -The two builders share the same shape but differ where the databases do. On PostgreSQL, fields pick column types and models chain `.sql({ table })` to map storage. On MongoDB, the ID is a `field.objectId()` named `_id`, and the scalar helpers are `field.string()`, `field.int32()`, `field.double()`, `field.bool()`, and `field.date()`. The collection is an inline `collection` option on the model rather than a chained call. - ## How `defineContract` works -`defineContract` takes an options object, and optionally a factory function after it. +You never pass the name of your database. It comes from the package you import. -The options object declares what the contract is built from, most importantly `extensions`. The database and its family are already fixed by the import: `@prisma/orm-postgres/contract-builder` produces PostgreSQL contracts, so you never name them yourself. +On PostgreSQL, `defineContract` takes an options object and then a function. The options object lists the extension packs you use. The function returns the contract's content: `models`, plus `enums` and `types` if you have them. Its one argument gives you `field`, `model`, `rel`, and `type`. Those four come from the argument, not from imports. `type.pgvector` exists in the example only because `pgvector` is listed in `extensions`. -The factory receives authoring helpers composed from your database and every extension pack you declared: `field` for field definitions, `model` for models, `rel` for relations, and `type` for pack-provided types. `type.pgvector` in the example exists only because `pgvector` is listed in `extensions`. The factory returns the contract's content: `models`, plus optional `enums` and `types`. +On MongoDB, `defineContract` takes a single object holding `models`, and you import `field`, `model`, and `rel` from the package instead. The MongoDB builder differs from the PostgreSQL one in four more ways. Every model declares an `_id` field built with `field.objectId()`, and that field is the primary key, so you never mark it with `.id()`. The scalar helpers are `field.objectId()`, `field.string()`, `field.int32()`, `field.double()`, `field.bool()`, and `field.date()`. The collection name and the relations are inline options on the model rather than chained calls. Relations name the field on each side, as in `rel.hasMany("Post", { from: "_id", to: "authorId" })`, and a field on another model is written `User.ref("_id")`. -The MongoDB builder exports `field`, `model`, and `rel` directly, and its `defineContract` accepts a single definition object holding the `models`, as in the MongoDB tab above. The sections below use the PostgreSQL builder. +The rest of this page uses the PostgreSQL builder. ## Fields -`field` provides typed constructors for common field shapes: +`field` has a helper for each column type. On PostgreSQL: + +| Column | Helper | +| --- | --- | +| text | `field.text()` | +| integer | `field.int()` | +| big integer | `field.bigint()` | +| float | `field.float()` | +| decimal | `field.decimal()` | +| boolean | `field.boolean()` | +| date and time | `field.dateTime()` | +| bytes | `field.bytes()` | +| JSON | `field.json()` | +| UUID stored as text | `field.uuidString()` | +| UUID stored in a `uuid` column | `field.uuidNative()` | + +There is no helper for a date without a time, or a time without a date. Extension packs add more helpers of their own. + +For a primary key whose value your app generates, use `field.id.uuidv4String()`, `field.id.uuidv7String()`, `field.id.ulid()`, `field.id.nanoid()`, `field.id.cuid2()`, or `field.id.ksuid()`. Each one marks the field as the primary key for you. `field.id.uuidv4Native()` and `field.id.uuidv7Native()` do the same in a `uuid` column. -- `field.text()`, `field.uuidString()`, `field.json()` for scalar fields -- `field.id.uuidv4String()` for a UUID primary key with a client-generated default -- `field.temporal.createdAt()` and `field.temporal.updatedAt()` for managed timestamps -- `field.namedType(x)` for enums and declared named types +`field.temporal.createdAt()` fills the column with `now()` when the row is created. `field.temporal.updatedAt()` fills it on every create and every update. -The exact helper set comes from your database and the extension packs you compose. Every field builder supports chained modifiers: +`field.namedType(x)` takes an enum or a type from an extension pack. + +Every field builder supports chained modifiers: - `.optional()` makes the field nullable. -- `.default(value)` sets a literal default. `.defaultSql(expression)` sets a database function default. +- `.default(value)` sets a literal default. `.defaultSql(expression)` sets a default the database computes, and the argument is a SQL expression as a string: `.defaultSql("now()")`. - `.unique()` adds a unique constraint. `.id()` marks the primary key. - `.column("column_name")` sets the column name in the database when it differs from the field name. @@ -197,33 +214,37 @@ const Priority = enumType( ); ``` -Each `member(name, storedValue)` pairs the TypeScript-visible name with the value stored in the column. Fields reference the enum with `field.namedType(Priority)`, and defaults reference a member as `Priority.members.Low`. Include the enum in the factory's returned `enums` map so it is emitted. +The second argument says how each member is stored. `codecId` is the type id, which is the PostgreSQL type plus a version, always `@1` today. [Raw queries](/orm/reference/raw-queries#binding-a-bare-value-with-param) shows more type ids. `nativeType` is the column type in the database. `as const` keeps both strings as literal types so the builder can check them. + +Each `member(name, storedValue)` pairs the TypeScript-visible name with the value stored in the column. Fields reference the enum with `field.namedType(Priority)`, and defaults reference a member as `Priority.members.Low`. Include the enum in the returned `enums` map so it reaches the two files. + +The TypeScript builder has no helper for a real PostgreSQL `enum` type. Members are stored in whatever column type you name here, `text` above. To get a PostgreSQL `enum` type, write the contract in PSL and use a `native_enum` block. ## Relations -Relations are declared on the model builder with `.relations(...)` and the `rel` helpers: +Relations are declared on the model builder with `.relations(...)` and the `rel` helpers, as the full example above shows. + +`rel.hasMany(Model, { by })` names the foreign key field on the other model. `rel.hasOne(Model, { by })` is the same with at most one row on the other side. `rel.belongsTo(Model, { from, to })` maps the local foreign key field to the field it points at. + +`rel.manyToMany(Model, { through, from, to })` goes through a join table. You declare the model for the join table yourself and pass it as `through`. `from` and `to` name the two foreign key fields on that model: ```typescript User.relations({ - posts: rel.hasMany(Post, { by: "userId" }), -}); - -Post.relations({ - user: rel.belongsTo(User, { from: "userId", to: "id" }), + teams: rel.manyToMany(Team, { through: Membership, from: "userId", to: "teamId" }), }); ``` -`rel.hasMany(Model, { by })` names the foreign key field on the other model. `rel.belongsTo(Model, { from, to })` maps the local foreign key field to the referenced field. `rel.hasOne` and `rel.manyToMany` cover the remaining shapes. Both builders also accept the model name as a string. Pass the model object, as above, and a typo is a compile error. Pass a string and the typo is reported when `prisma contract emit` builds the contract. +`rel.belongsTo` on its own does not create a foreign key constraint in the database. Ask for one in either of two places. Chain `.sql({ fk: { name: "post_userId_fkey" } })` on the relation, or list `constraints.foreignKey(...)` in the model's `.sql({ foreignKeys: [...] })`, as the full example above does. Both forms take `name`, `onDelete`, and `onUpdate`. + +`rel.hasMany`, `rel.hasOne`, `rel.belongsTo`, and `rel.manyToMany` also accept the model name as a string. Pass the model object, as above, and a typo is a compile error. Pass a string and the typo is reported when `prisma contract emit` builds the contract. ## Storage mapping -`.sql(...)` maps a model to its table. The object form covers the common case: +`.sql(...)` maps a model to its table. The object form covers the common case: `User.sql({ table: "user" })`. -```typescript -User.relations({ ... }).sql({ table: "user" }); -``` +`.relations(...)`, `.attributes(...)`, and `.sql(...)` are independent. A model with no relations calls `.sql({ table })` straight after `model(...)`. -The callback form additionally exposes the model's columns and constraint builders, for foreign keys with explicit names: +The callback form gives you the model's columns as `cols` and the constraint builders as `constraints`. Use it for indexes and for foreign keys with explicit names: ```typescript Post.relations({ ... }).sql(({ cols, constraints }) => ({ @@ -231,52 +252,70 @@ Post.relations({ ... }).sql(({ cols, constraints }) => ({ foreignKeys: [ constraints.foreignKey(cols.userId, User.refs.id, { name: "post_userId_fkey" }), ], + indexes: [ + constraints.index([cols.userId]), + constraints.index([cols.userId, cols.createdAt], { name: "post_user_created_idx" }), + ], })); ``` +`constraints.index` takes one column for a plain index and a list for a multi-column index. Add `{ unique: true }` to make it a unique index. + `Model.refs` provides typed references to another model's fields, so `User.refs.id` is checked against the actual `User` definition. +A primary key made of two fields is a model attribute rather than storage mapping. Chain `.attributes(...)` and build it from the field references: + +```typescript +Membership.attributes(({ fields, constraints }) => ({ + id: constraints.id([fields.userId, fields.teamId]), +})); +``` + +`constraints.unique([fields.userId, fields.teamId])` in the same block makes a unique constraint across two fields. For a key made of one field, `.id()` on the field is enough, and the `field.id.*` helpers already do it. + ## Extension types -Declare packs in `defineContract`'s options and the factory's `type` helper exposes their constructors: +Declare packs in `defineContract`'s options and the `type` helper exposes their constructors. The full example above imports `pgvector` from `@prisma/orm-extension-pgvector/pack`, lists it in `extensions`, builds `type.pgvector.Vector(1536)` in the returned `types` map, and uses it on a field with `field.namedType(types.Embedding1536)`. -```typescript -import pgvector from "@prisma/orm-extension-pgvector/pack"; +Add the same pack to `prisma.config.ts`. It goes inside `ormConfig({ ... })`, beside `contract`, and the config imports the pack's `/control` export while the contract file imports its `/pack` export: -export const contract = defineContract( - { extensions: { pgvector } }, - ({ field, model, type }) => { - const types = { Embedding1536: type.pgvector.Vector(1536) } as const; - // ... use field.namedType(types.Embedding1536) in a model - return { types, models: { /* ... */ } }; - }, -); -``` +```typescript title="prisma.config.ts" +import { definePrismaConfig } from "prisma/config"; +import pgvector from "@prisma/orm-extension-pgvector/control"; +import { defineConfig as ormConfig } from "@prisma/orm-postgres/config"; -Add the same pack to `prisma.config.ts` as `extensions: [pgvector]`, importing the pack's `/control` export. The CLI and your app then compose the same set the contract was built with. +export default definePrismaConfig({ + orm: ormConfig({ + contract: "./src/prisma/contract.ts", + extensions: [pgvector], + }), +}); +``` ## Keep the contract file free of changing values [#keep-the-contract-file-pure] -The contract file describes structure. `prisma contract emit` writes it to JSON in a fixed key order and hashes it, so the same source always produces the same files. That works only if the file holds plain data: +The contract file describes structure. Three rules keep it usable: -- Do not read `process.env`, the current time, or random values into the contract. A contract that changes per machine or per run breaks hashing and verification. +- Do not read `process.env`, the current time, or random values into the contract. `prisma contract emit` writes the contract to JSON in a fixed key order and hashes it. A contract that changes per machine or per run breaks that hash and the checks that rely on it. - Keep field values plain: strings, numbers, booleans, and the builder's own objects. Functions, class instances, and `Date` objects do not serialize. -- Keep the file free of side effects. `prisma contract emit` imports the file and reads its `default` or `contract` export, and nothing else. +- Keep the file free of side effects. `prisma contract emit` imports the file and reads its `default` or `contract` export, and nothing else. It runs the file, but it does not type-check it. Type errors show in your editor and when you run `tsc`. Configuration that legitimately varies per environment, such as the database URL, belongs in `prisma.config.ts`, not in the contract. ## Parity with PSL -TypeScript and [PSL](/orm/contract-authoring/psl-syntax) authoring produce the same `contract.json` and `contract.d.ts` for an equivalent contract. You can move between the two forms without changing anything downstream. A project names exactly one contract file in its config. Keep the other form out of the project so the two can never disagree. +TypeScript and [PSL](/orm/contract-authoring/psl-syntax) authoring produce the same `contract.json` and `contract.d.ts` for an equivalent contract. You can move between the two forms without changing anything downstream. A project names exactly one contract file in its config. + +No command converts a `.prisma` contract into TypeScript. To move an existing project, write the TypeScript file by hand, point `contract` in `prisma.config.ts` at it, and delete the `.prisma` file so the two can never disagree. ## Prompt your coding agent Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent; in an existing project, run `npx prisma skills sync`. The `prisma-8` skill covers TypeScript authoring. Ask your agent to: - "Convert this contract.prisma to the TypeScript schema builder." -- "Using the prisma-8 skill, add a unique index to the email field in our TypeScript schema." +- "Using the prisma-8 skill, add a unique constraint to the email field in our TypeScript schema." ## Next steps -- Inspect [the two files](/orm/contract-authoring/the-contract-artifact) the contract produces. +- Inspect [`contract.json` and `contract.d.ts`](/orm/contract-authoring/the-contract-artifact), the two files the contract produces. - Apply the contract to a database with [`db init`](/cli/db-init) or plan changes with [`migration plan`](/cli/migration-plan). diff --git a/apps/docs/cspell.json b/apps/docs/cspell.json index e1eb0c6d4d7..7ec95e47683 100644 --- a/apps/docs/cspell.json +++ b/apps/docs/cspell.json @@ -471,6 +471,7 @@ "dbsql", "txexecute", "txsql", + "ksuid", "ltrim", "rtrim" ], diff --git a/docs/orm-docs-audit/c21-contract-authoring/capabilities.round1.md b/docs/orm-docs-audit/c21-contract-authoring/capabilities.round1.md new file mode 100644 index 00000000000..cc1b5be6338 --- /dev/null +++ b/docs/orm-docs-audit/c21-contract-authoring/capabilities.round1.md @@ -0,0 +1,71 @@ +Read once, as a reader coming from Prisma 6 who has never seen this version. + +## Sentences I could not restate after one reading + +1. "The `capabilities` section of your contract, the `contract.prisma` file that replaced `schema.prisma`, records which features yours supports." + Stopped me: the page then never shows `contract.prisma` again. Every example is `contract.json`. So is the `capabilities` section in the file I write, or the file the tool generates? Two paragraphs later the page says "You do not write this section yourself", which makes the `contract.prisma` mention look wrong. Plainer: "Prisma records the supported features in `contract.json`, a file the CLI generates." + +2. "…while it does so it merges the declarations of every component composed in your project: the target (PostgreSQL, SQLite, MongoDB), its adapter, and any extension packs named in the config." + Stopped me: four new nouns in one sentence — "declarations", "component", "composed", "target", "adapter", "extension packs" — plus "the config", which is not named until the following paragraph (`prisma.config.ts`). I cannot tell what a "component" is versus a "pack". Plainer: "Your database type, its driver, and any add-on packages each state which features they support. `prisma contract emit` collects all of those statements into `contract.json`." + +3. "Because the packs declare the keys, adding or removing an extension in `prisma.config.ts` and running `npx prisma contract emit` again changes the set with no further work." + Stopped me: "the set" of what — keys? packs? And "no further work" compared with what earlier workflow? I have no baseline. + +4. "Query-builder methods that need a key read the contract's `capabilities` section and throw an error whose `code` is `ORM.CAPABILITY_MISSING`, naming the method and the key." + Stopped me: one sentence carrying a mechanism, an error code, and the error's contents. Also "need a key" is odd phrasing — methods need a *feature*, not a key. + +5. "MongoDB currently declares no keys: this mechanism mostly separates SQL targets and their extensions, and the MongoDB pipeline does not yet check features this way." + Stopped me: "this mechanism mostly separates SQL targets and their extensions" — I cannot work out what "separates" means here. And "the MongoDB pipeline" is a piece of internal machinery I have never heard of. Plainer: "MongoDB declares no keys, so none of these checks apply to it." + +6. "[`db verify`](/cli/db-verify) compares the database against the contract's schema and profile, using the hashes described in [the contract artifact](…#the-content-hashes)." + Stopped me: "profile" appears here for the first and only time and is never explained. "the hashes" likewise. Plainer: "`db verify` checks the live database against your contract; capabilities are not part of that check." + +7. "Projects created with `npm create prisma@latest` include the [Prisma ORM skills](…) for your coding agent; in an existing project, run `npx prisma skills sync`." + Stopped me: three unexplained things (what a "skill" is, what it does, why my agent needs one) in a sentence appended to a page about database features. + +## Words and phrases I had to guess + +- **"contract"** — guessed: the new name for the schema file, or maybe the whole set of generated files. Never actually defined on this page. +- **"target"** — guessed: the database engine. The parenthetical "(PostgreSQL, SQLite, MongoDB)" is the only reason I got there. +- **"adapter"** — guessed: the driver package, like `@prisma/adapter-pg` in v6. +- **"extension packs"** / "packs" / "extensions" — guessed: optional add-on npm packages. The page uses all three names for what I assume is one thing. +- **"composed" / "composing the pgvector pack"** — guessed: listed in config so it gets included. +- **"declarations" / "declares" / "reports"** — guessed: all mean "states that it supports". Three verbs, one idea. +- **"namespace"** — guessed: the top-level grouping key, `sql` or `postgres`. +- **"scalar list"** — guessed: a `String[]` field. +- **"profile"** — could not guess at all. +- **"the MongoDB pipeline"** — could not guess. +- **"diagnostic"** — guessed: a build error message. Why not say "error"? +- **"key"** — guessed: the name of one capability. But the page's title says "features", the section says "capabilities", and the table column says "Key". Three words for one thing, and I had to work that out myself. + +## Where I asked "so what do I actually type?" and got nothing + +1. **I hit `ORM.CAPABILITY_MISSING`. Now what?** The page shows the error and stops. Do I change the adapter, install a pack, rewrite the query, or edit something? This is the single most likely reason a reader lands here and the page does not answer it. +2. **I hit the `scalarList` build error on SQLite.** Same problem — the error text is shown, the fix is not. Remove the field? Switch targets? +3. **Can my own code check a capability before calling a method?** Nothing is shown. I do not know whether there is an API, or whether I am meant to read `contract.json` myself. +4. **How do I add pgvector?** The page says composing the pack "adds `pgvector.cosine`" and tells my *agent* to "Add pgvector to the project", but never shows me a line of `prisma.config.ts`, an install command, or a config key. +5. **Where does `contract.json` live?** "beside the source" — beside which source? The code block is captioned `prisma/contract.json`, but I had to infer that. +6. **`contract.d.ts` is emitted too.** Never mentioned again. What do I do with it? +7. **How do I see my full key list?** "Your project's emitted `contract.json` is the full list" — so, open the file? There is no `prisma contract` subcommand shown for printing it. + +## Where the page explains the inside when I only wanted the outside + +- "the keys are checked at two points" plus "Query-builder methods that need a key read the contract's `capabilities` section" — I wanted to know what breaks and how to fix it, not where the lookup happens. +- "They are recorded in the contract when you run `npx prisma contract emit` rather than read from the live database, so the same contract behaves identically in every environment." The last clause is useful. The mechanism before it is not what I asked. +- "using the hashes described in [the contract artifact]" — pure internals in a paragraph whose point is "verification is a different thing". +- "this mechanism mostly separates SQL targets and their extensions" — architecture, not instruction. +- The whole `sql` versus `postgres` namespace explanation is background. What I needed was: how do I find the key a failing method wants? + +## Could I do what the page is for, after one reading? + +Partly. I understand that Prisma now records supported features and fails early. That is the page's stated purpose, and it lands. + +What I still could not do: + +- Fix either of the two errors the page shows me. It tells me what the error means and not one word about what to change. +- Add or remove an extension pack, despite the page claiming that is easy. I have never seen the config syntax. +- Explain what a "contract" is, or reconcile `contract.prisma` with `contract.json`. The one sentence defining the contract contradicts the section right after it. +- Say what "profile" means, or what `db verify` actually compares. +- Tell whether `capabilities` is something I ever edit. I believe not, but the opening sentence says it is "of your contract, the `contract.prisma` file", which reads like it is mine. + +Concrete gap, ranked: the page is a description of a mechanism, not a page a stuck reader can act on. Two error messages are quoted and neither has a remedy. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/psl-syntax.round1.md b/docs/orm-docs-audit/c21-contract-authoring/psl-syntax.round1.md new file mode 100644 index 00000000000..42620c39f87 --- /dev/null +++ b/docs/orm-docs-audit/c21-contract-authoring/psl-syntax.round1.md @@ -0,0 +1,95 @@ +I read the page once, start to finish, as a Prisma 6 user. + +## Sentences I could not restate after one reading + +1. Line 9 — "PSL is the preferred way to author [your contract](...), the `contract.prisma` file that replaced `schema.prisma`." + Stopped by: "PSL" is never expanded anywhere in the body, and "contract" is never defined. Line 11 says "the Prisma schema language" but never joins the two. I guessed PSL = Prisma Schema Language from the meta description in the frontmatter, which readers do not see. + Plainer: "PSL (Prisma Schema Language) is the schema language you already use. In Prisma 8 the schema file is called `contract.prisma` instead of `schema.prisma`." + +2. Line 11 — "The Prisma ORM 8 additions are named types, typed enums, value objects, base models with variants, and extension types." + Stopped by: five invented-sounding terms in one sentence, none defined yet. "typed enums" is worse than the rest, because I already have enums in v6 and the page never says what the "typed" part changes. The section heading later is just "Enums", so I could not match the promise to the section. + +3. Line 138 — "Generated defaults such as `@default(uuid())` are applied by Prisma ORM before each write. They appear in the contract's `execution` section rather than as a column default." + Stopped by: "the contract's `execution` section" — a thing I have never seen and the page never shows. I only wanted to know whether `@default(uuid())` still works. + Plainer: drop the second sentence. + +4. Line 181 — "Native PostgreSQL types are written as the type, so a field can also use `VarChar(35)`, `Uuid`, or `Timestamptz` directly without an alias." + Stopped by: "written as the type" is not English I can parse. More importantly, this is the page's only hint that `@db.VarChar(35)` — the syntax I have typed for two years — is gone. It never says so. + Plainer: "In Prisma 8 you write the native type as the field's type. `String @db.VarChar(35)` in Prisma 6 becomes `VarChar(35)`." + +5. Line 196 — "That name is the PostgreSQL type plus a version, always `@1` today." + Stopped by: I do not know what the version is for, why it exists if it is always `@1`, or what changes when it is not. + +6. Line 196 — "Leave `@@type` out and Prisma ORM infers it from the member values." + Stopped by: this contradicts line 185, "An enum declares how its values are stored with `@@type`." Is `@@type` required or not? And infers it to what — `text`? I do not know. + +7. Line 233 — "Many-to-many relations go through an explicit model for the join table. Its primary key must cover both foreign keys. The list fields on both sides then resolve through it." + Stopped by: "resolve through it". In the example, `Post.tags` and `Tag.posts` carry no `@relation` and never name `PostTag`. I cannot tell how Prisma connects them. Is it because `PostTag` is the only model holding both foreign keys? The page does not say. + +8. Line 283 — "On PostgreSQL, the variant's `@@map` picks its storage layout: with its own `@@map`, as here, the variant's fields live in their own table sharing the base model's primary key. Without one, they live in the base table as nullable columns." + Stopped by: too many ideas in one sentence, and a surprise. `@@map` has always been a naming attribute. Here its presence or absence silently switches the whole storage strategy. I read it three times. + +9. Line 287 — "Extension packs contribute types through constructor calls in the `types` block." + Stopped by: "extension packs" is undefined, and "constructor calls" is TypeScript vocabulary used to describe something in a `.prisma` file. I could not picture it until I read the code block. + +## Words and phrases I had to guess + +- **"PSL"** — guessed Prisma Schema Language. +- **"contract"** — guessed it is just the new word for the schema. Still not sure whether it means more than that. +- **"emit"** in `prisma contract emit` — guessed "generate". +- **"named types"** — guessed "type aliases", confirmed two sections later. +- **"value object"** — guessed "embedded/inline struct". The page confirms it, but only after using the term in the intro. +- **"base models with variants"** — guessed single-table inheritance, from other ORMs. "variant" is never defined; it is only used. +- **"extension types"** and **"extension packs"** — guessed npm packages that add custom column types. +- **"the `prisma-8` skill"** (line 321) — guessed some agent instruction file. I do not know what a "skill" is here. +- **`// use prisma-8`** (lines 16, 60) — guessed a required pragma, not a comment. See below. + +## Places I asked "so what do I actually type?" and got no answer + +1. **`// use prisma-8` at the top of every contract file.** It appears in both complete examples and is never mentioned in prose. It is spelled like a comment but reads like a directive. Is it required? What happens without it? And the extension-types example at line 302 omits it, so I now think it is optional, or I think the example is wrong. I cannot tell which. + +2. **Where do `datasource db` and `generator client` go?** Every schema I have written for two years starts with both. Neither appears in either complete example, and the page never says they are gone. So: where does my connection URL live now? How do I choose the client output path? The config file at line 108 sets only `contract`. I do not know how Prisma reaches my database. + +3. **How the provider is chosen.** I inferred it from `@prisma/orm-postgres/config` versus `@prisma/orm-mongo/config`. The page never states that the import picks the database. + +4. **`@@unique`.** The attribute list at lines 134–139 covers `@id`, `@@id`, `@unique`, `@@index`, `@default`, `@map`, `@@map`. It omits `@@unique`. Then line 325 tells me to ask my agent for "a composite unique constraint on userId and title" — a thing the page never shows me how to write. + +5. **Valid `@@type` names.** I have `"pg/text@1"` and `"mongo/string@1"`. What do I write for an enum stored as an integer, or as a real PostgreSQL `enum` type? Is the native PG `CREATE TYPE` enum still available at all? No list, no link. + +6. **Where the name `pgvector` in `pgvector.Vector(1536)` comes from.** Is it the default import identifier I chose in `prisma.config.ts`, or a fixed name the pack declares? If I write `import pgv from ...`, do I then write `pgv.Vector(1536)`? The page does not say, and this is the exact thing I would get wrong. + +7. **What a variant model inherits.** `model Bug` has no `id` and no `title`. Does `Bug` have them? Do I query `prisma.bug.findMany()` or `prisma.task.findMany()` and narrow? Neither is stated. + +8. **`@updatedAt`, `onDelete`, `onUpdate`, one-to-one relations.** All routine in v6, none mentioned. I do not know whether they survived. + +9. **Multi-file schemas.** Line 106 says the config "names the one file Prisma ORM reads". I use `prismaSchemaFolder` today. That reads like a removal, but the page never confirms it. + +10. **How I use `contract.d.ts`.** It is produced twice in the page and never used. Do I import from it? Does the client pick it up automatically? + +## Places explaining the internals when I only wanted to know what to do + +- Line 138 — "They appear in the contract's `execution` section rather than as a column default." +- Line 196 — "In the database, Prisma ORM enforces the allowed values with a `CHECK` constraint on each column that uses the enum." +- Line 256 — "`contract.json` records the relation as `N:M` with the join table's columns, so queries can traverse `post.tags` directly." +- Line 216 — "On PostgreSQL a value object field is stored in a single `jsonb` column." This one earns its place; it changes how I would query. The other three do not. + +## Two smaller inconsistencies + +- Line 130 says `npm create prisma@latest -- my-app`; line 321 says `npm create prisma@latest`. Same command, two spellings. +- Line 102 and 313 say `npx prisma contract emit`; line 313's first sentence and line 9 say `prisma contract emit`. + +## Could I do what the page is for, after one reading? + +Partly. I could write a model with fields, ids, defaults, maps, a one-to-many relation, an enum, and a value object, by copying the complete contract at the top and editing it. That part works. + +I could not ship a working project, because after one reading I still do not know: + +- where my database connection URL goes, now that `datasource` has vanished from the examples; +- whether `// use prisma-8` is required or decorative; +- whether `@db.VarChar(35)` still works, or how to convert my existing native-type annotations; +- how to write `@@unique`; +- which `@@type` strings are legal beyond the two shown; +- whether implicit many-to-many (`Post[]`/`Tag[]` with no join model) still exists, or whether I must now write the join model by hand for every one of them; +- what a variant model inherits, or how to query one. + +The biggest single gap is the silent disappearance of `datasource` and `generator`. For a reader upgrading from v6, that is the first thing they will notice and the page acts as if it never existed. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/the-contract-artifact.round1.md b/docs/orm-docs-audit/c21-contract-authoring/the-contract-artifact.round1.md new file mode 100644 index 00000000000..971ec47194b --- /dev/null +++ b/docs/orm-docs-audit/c21-contract-authoring/the-contract-artifact.round1.md @@ -0,0 +1,95 @@ +Reader review of `/Users/wmadden/Projects/prisma/web/.claude/worktrees/error-reference-follow-ups-81a8e7/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx` + +## Sentences I could not restate after one reading + +**Line 9** — "writes `contract.json` and `contract.d.ts` beside the source by default" +Stopped by "the source." Which file is the source? A `.prisma` file? A `.ts` file? Where does it live? The whole page hangs off a noun it never names. Plainer: "writes both files next to your schema file (`prisma/schema.ts` or `prisma/schema.prisma`)." + +**Line 13** — "Read by `db`, the migration tooling, the database checks, and anything else that needs to read your schema" +`db` appears here as if I already know it. I guessed it was the client object, and only got confirmation 120 lines later at line 133. "The database checks" is also vague — which checks? Name the commands. + +**Line 30** — "and the value sets behind enums" +"Value sets behind enums" made me stop. I guessed it means the list of allowed values for each enum. Say that. + +**Line 32** — "`profileHash` | The database this contract targets, and its family" +I do not know what "family" means. I guessed from `targetFamily: "sql"` further down that "family" means SQL vs MongoDB, but the table asks me to understand it 6 lines before the example shows it. + +**Line 34** — "[`prisma db sign`](/cli/db-sign) checks that the database satisfies the contract, then records `storageHash` and `profileHash` in a small table it creates in the database." +Two problems. "Sign" made me expect cryptographic signing; it is actually "stamp the hashes into a table." And "a small table it creates" — what is it called? Will it show up in my migrations? Does it need permissions? Nothing. + +**Line 34** — "`db` makes the same comparison before its first query, but it logs a warning and carries on." +I could restate it, but it is said again in full at line 145. One of the two is redundant. + +**Line 38** — "The contract separates what your application models from how it is stored." +"What your application models" reads as a noun phrase on first pass ("the things your application models") before I realised "models" is the verb. Rewrite: "The contract keeps your application's view of the data separate from the way the database stores it." + +**Line 38** — "On PostgreSQL the namespace is the schema, `public` unless you set one." +Set one where? In PSL? In TypeScript? In the connection string? The page does not say, and does not link anywhere that does. + +**Line 105** — "**`roots`**: each table name mapped to the model stored in it. The SQL query builder is keyed by those table names, as in `db.sql.public.user`, and the model name is what `db.orm.public.User` uses." +Too many new things in one bullet: `roots`, "SQL query builder", `db.sql.*`, `db.orm.*`. None of `db.sql` or `db.orm` has been introduced anywhere on this page, and neither is linked. I could not restate what `roots` is *for* — only what it contains. + +**Line 106** — "its type id, such as `pg/text@1`, the PostgreSQL type plus a version, always `@1` today" +The prose calls it a "type id" but the JSON key is `codecId`. I had to work out these are the same thing. And "always `@1` today" left me wondering whether I ever need to care. + +**Line 109** — "collected from the adapter for your database and any extension packs when you run `prisma contract emit`" +"Extension packs" is new and undefined and unlinked. I guessed it means optional plugin packages. Also, the bullet covers both `capabilities` and `extensions` but only explains `capabilities`; `extensions: {}` is never described. + +**Line 113** — "It exports the contract type, branded hash types matching `contract.json`, and input/output types for every model" +"Branded hash types" stopped me cold. I guessed, from the code block, that the hash string is baked into the type so two different contracts cannot be mixed up. Line 129 then explains it properly — so the jargon at 113 buys nothing. Also "the contract type" is unnamed here; I only learn it is called `Contract` from the import on line 137. + +**Line 129** — "TypeScript catches a mismatched contract before `prisma db verify` has to." +Restatable, but I do not know what "mismatched" means in practice — I regenerated one file but not the other? I upgraded the CLI? What actually triggers this in a real day? + +**Line 149** — "A CI job can enforce this by running the command and failing if the working tree changes." +I know what it means, but not what to write. See below. + +## Words and phrases I had to guess + +- **"contract"** — the central word of the page and of the URL, never defined here. I guessed it means the whole schema definition, compiled. If there is a page that defines it, this page should link to it in the first sentence. +- **`db`** — guessed: the client instance, the Prisma 6 `PrismaClient` equivalent. +- **"sign" / "verify"** — guessed: record the hashes, and check the hashes. +- **"family"** — guessed: SQL vs MongoDB. +- **"codecId"** — guessed: an identifier for how a value is converted between the database and TypeScript. +- **"branded"** — guessed: a type that carries a literal string so mismatches fail to compile. +- **"extension packs"** — guessed: optional add-on packages. +- **"roots"** — guessed: the entry points you address models through. +- **`CodecTypes`, `StorageHashBase`, `ProfileHashBase`** — appear in the excerpt with no explanation. Guessed they are internal helper types I never write myself. +- **"the database checks"** (line 13) — guessed: `prisma db verify`. + +## Places I asked "so what do I actually type?" + +1. **Line 9.** What is the command's argument, and where do I run it from? The page says "beside the source by default" — implying a flag exists to change it — and never shows the flag or the default path. +2. **Line 38, namespaces.** "`public` unless you set one." How do I set one? No syntax, no link. +3. **Lines 135–143, `db.ts`.** This is the one code block I would actually copy, and it is incomplete as a mental model: `postgres({ contractJson })` takes no connection string and no URL. Where does the database URL come from? Environment variable? A default name? I would get stuck here. +4. **Line 137.** `import type { Contract } from "./contract.d";` — importing a path ending in `.d` is unusual enough that I would assume it was a typo and change it. If it is deliberate, say so. +5. **Line 113, input/output types.** I am told input and output types exist for every model. What are they named? I can infer `AddressOutput` from the example, so presumably `AddressInput`, but I am guessing at the API surface I have to type every day. +6. **Line 149, the CI job.** "Running the command and failing if the working tree changes" — show it. Two lines would do: `prisma contract emit` then `git diff --exit-code prisma/`. +7. **Line 34.** When do I run `db sign`? Once at setup? After every migration? The page tells me what it does, never when I do it. + +## Places that explain internals when I wanted to know what to do + +- **The whole "The same source always produces the same two files" section (lines 18–22).** Byte-identical output, fixed key order, normalized values — that is how the tool works. What I need to act on is one sentence: do not read `process.env`, `Date.now()`, or random values in your schema source. The section links elsewhere for the actual rules, so the explanation here is background I cannot use. +- **"The content hashes" (lines 24–34).** Three hashes, what each covers, what makes each change. As a reader I do not compute or compare these; the tool does. The actionable part is the last paragraph. The table is the tool's internals. +- **Line 108** — "These are not column defaults in the database." A true internal detail, but it raises a practical question it does not answer: if I insert with raw SQL, do I get no default at all? +- **Line 20** — "Keys are written in a fixed key order and values in a normalized form, which is what lets you read the files in a code review and hash them." The justification chain (fixed order → hashable) is engine reasoning. "You can read these diffs in a code review" is the part that matters to me. + +## Inconsistencies that cost me a re-read + +- The JSON block is titled `prisma/contract.json`, but `db.ts` imports `./contract.json` from `src/prisma/db.ts`, so the file is at `src/prisma/contract.json`. Two different locations for the same file in the same page. +- The `contract.json` example is `User` and `Post`. The `contract.d.ts` example is `Address`, with fields that appear nowhere else. I spent a moment looking for the `Address` model before concluding it is an unrelated excerpt. +- Line 105 refers to `db.sql.public.user` and `db.orm.public.User` — two APIs introduced in a parenthetical, in a bullet about a different topic. + +## Could I do what the page is for? + +The page's job is to tell me what is in these two files and what reads them. Partly, yes: I could open a `contract.json` in a code review and roughly follow `domain` versus `storage`, and I would know not to hand-edit either file and to commit both. + +What I would still not know after one reading: + +- What "contract" means as a concept, which is the page's own subject. +- Where the source file lives, what it is called, and what `prisma contract emit` actually takes as arguments. +- How to finish `db.ts` — specifically how the database connection is configured. This is the block I would copy, and it would not work. +- When to run `db sign` versus `db verify` in a real workflow, and what the table `db sign` creates is called. +- What `db.sql` and `db.orm` are, though the page uses both. +- How to set a namespace other than `public`. +- What the input types are called, when the page tells me they exist. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/the-data-contract.round1.md b/docs/orm-docs-audit/c21-contract-authoring/the-data-contract.round1.md new file mode 100644 index 00000000000..176478b87e7 --- /dev/null +++ b/docs/orm-docs-audit/c21-contract-authoring/the-data-contract.round1.md @@ -0,0 +1,70 @@ +I read the page once, as a Prisma 7 user would. Findings below. + +File: `/Users/wmadden/Projects/prisma/web/.claude/worktrees/error-reference-follow-ups-81a8e7/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx` + +## Sentences I could not restate after one reading + +1. Line 31: "Completion covers field, model, and block attribute names and their argument keys, taken from the installed target and extensions." + - Stopped by: "the installed target" (a target of what? I have no idea what a target is — the word appears here and nowhere else on the page) and "extensions" (first use, undefined). "block attribute names and their argument keys" is three nested nouns. + - Plainer: "Autocomplete suggests attribute names and their arguments, based on the database package and extension packages you have installed." + +2. Line 49: "The contract also identifies the exact version of the schema it describes." + - Stopped by: the note at line 39 just told me schema means the database's actual structure. So this says the contract identifies a version of my live database, which cannot be right. Same clash at line 96 ("hashes that identify this exact version of the schema") and line 78 ("plan schema changes"). After one reading I cannot tell which meaning of "schema" is in force in any given sentence. + - Plainer: "The contract also carries a hash that identifies this exact version of the contract." + +3. Line 49: "`prisma contract emit` computes hashes over its contents." + - Stopped by: "its" — the source file's contents, or the emitted files' contents? And what do I do with a hash? + +4. Line 49: "When you run [`prisma db sign`](/cli/db-sign), Prisma ORM records which contract the database matches in a small table it creates in the database." + - Stopped by: too much at once, and no answer to when I run this. After a migration? Before deploy? Once ever? Also "a small table" — named what? It will show up in my database and I will want to know. + +5. Line 86: "Keep the other form out of the project, or treat it as a generated reference, so the two can never disagree." + - Stopped by: "the other form", "a generated reference". Generated by what? I do not know what action I am being told to take or avoid. + +6. Line 94: "named types, enums, and `type` blocks, each a structured value stored inside its parent row with no table of its own" + - Stopped by: "each" — does it apply to enums too? An enum is not a structured value. And I do not know the difference between "named types" and "`type` blocks"; they read as the same thing listed twice. + +7. Line 77: "`db` reads `contract.json` and compares its hashes with the record `prisma db sign` wrote in the database." + - Stopped by: bare `db`. Is that a CLI command group, a runtime object, a package? The other two bullets name real things (`contract.d.ts`, "the migration tooling"); this one names a two-letter thing I have never seen. + +## Words and phrases I had to guess + +- "the installed target" (line 31) — guessed: the database-specific package, maybe `@prisma/orm-postgres`. +- "extensions" (line 31) vs "extension packs" (line 95) vs "extension-provided types" (line 66) — guessed these are all the same thing. The page uses three names for it and defines none. +- "machine-readable files" (line 33) — guessed `contract.json` and `contract.d.ts`, confirmed 14 lines later. Name them where you first mention them. +- "which database features the contract needs" (line 47) — guessed things like JSON columns or pgvector. +- "The query APIs" (line 76) — guessed this is the new name for what I call PrismaClient. Plural, so possibly several. +- "contract source" (line 47) vs "contract source" declared in config (line 53) — guessed both mean the `.prisma` or `.ts` file I write. +- "`prisma contract infer`" (line 82) — guessed it reads an existing database and writes a contract for it. The page never says. +- "storage details" (line 93) — guessed: the database-level objects. + +## Places I asked "so what do I actually type?" and got no answer + +1. **I have a `schema.prisma` today.** Line 9 says `contract.prisma` "replaced `schema.prisma`". The page never tells me how to move from one to the other. Is there a command? Do I rename the file and add `// use prisma-8`? This is the single biggest gap for anyone coming from 7. +2. **Installing.** `@prisma/orm-postgres` appears in the config snippet with no install command. No `npm install` anywhere on the page. +3. **`prisma generate` is gone.** I ran it after every schema change for two years. Line 68 says run `prisma contract emit` instead, but never says `generate` is retired, so I am left wondering whether I now run both. +4. **Does anything run `emit` for me?** Line 68 says "after every change". Does `prisma migrate dev` do it? Is there a watch mode? Not said. +5. **When to run `prisma db sign`.** Mentioned twice, never placed in a workflow. +6. **Where `contract.d.ts` gets consumed.** Line 76 says the query APIs load it. No import line, no client construction, nowhere on the page. +7. **"For the cases PSL does not cover" (line 84).** Which cases? I cannot decide between the two authoring modes without knowing. And line 86 then says "You give up nothing by staying with PSL", which directly contradicts line 84. +8. **TypeScript mode config.** Line 66 says the extension selects the mode. Do I just point `contract:` at `./prisma/contract.ts`? Probably, but the page only shows the `.prisma` case. + +## Places that explain the insides when I wanted instructions + +1. Line 31, the whole language-server paragraph. I asked "what goes at the top of my file". I got diagnostics, completion internals, argument keys, and the `// use prisma-next` history from a release candidate I never used. One sentence would do: put `// use prisma-8` on the first line; editor support depends on it. +2. Line 49: "The client reads that record before its first query and logs a warning if the record does not match the contract the client holds." That is the client's startup sequence. What I need is: my app warns when the database is out of date; use `prisma db verify` in CI to fail instead. +3. Line 47: "The same source always produces the same two files, so you can read them in a code review, hash them, and hand them to tools and coding agents." This is a design rationale. Fine in "Why a contract", but it sits where I am still trying to learn what the files are. + +## Could I do what the page is for, after one reading? + +Partly. If I were starting a brand-new project I could write a `contract.prisma`, put `// use prisma-8` at the top, point `prisma.config.ts` at it, and run `npx prisma contract emit`. That much is clear. + +What I would still not know: + +- How to convert my existing `schema.prisma` to a contract. The page announces the replacement and then never addresses the upgrade. +- What to install, or what `@prisma/orm-postgres` is. +- How to query. I never see a client imported or constructed, and "the query APIs" is not a thing I can type. +- Whether `prisma generate` still exists. +- When to run `prisma db sign`, and what table it creates. +- Which situations require the TypeScript builder, given the page both names such cases and says I give up nothing by avoiding it. +- Whether "schema" means my database or my contract, in any sentence after the note at line 39. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/typescript-schema-builder.round1.md b/docs/orm-docs-audit/c21-contract-authoring/typescript-schema-builder.round1.md new file mode 100644 index 00000000000..69647ab9c46 --- /dev/null +++ b/docs/orm-docs-audit/c21-contract-authoring/typescript-schema-builder.round1.md @@ -0,0 +1,91 @@ +I read the page once, top to bottom, as someone coming from Prisma 6 with `schema.prisma`. + +## Sentences I could not restate after one reading + +1. "With TypeScript authoring you write your contract, the `contract.ts` file that replaced `schema.prisma`, in code rather than in a [`.prisma` file]." + Stopped by: "your contract" is a brand-new term, and the definition is crammed in as an aside between the subject and the verb. I had to read it three times to see that "write ... in code" was the main clause. Plainer: "Prisma 8 replaces `schema.prisma` with a *contract*. You can write the contract either as a `.prisma` file or as TypeScript in `prisma/contract.ts`." + +2. "the same two files a PSL contract produces." + Stopped by: "PSL" is never spelled out anywhere on the page. It is only a link. Plainer: spell it out once — "PSL (Prisma Schema Language, the `.prisma` file format)". + +3. "parts of the contract are assembled programmatically from other static definitions" + Stopped by: "other static definitions" of what? Static as opposed to what? I cannot picture a case. Plainer: give one concrete example, e.g. "you generate models from a list of table names you already have in TypeScript". + +4. "The database and its family are already fixed by the import: `@prisma/orm-postgres/contract-builder` produces PostgreSQL contracts, so you never name them yourself." + Stopped by: "its family". I do not know what a database *family* is, and "them" has no clear antecedent. Plainer: "You never pass the database name. It comes from which package you import." + +5. "The factory receives authoring helpers composed from your database and every extension pack you declared" + Stopped by: "authoring helpers composed from" is three abstractions stacked. Plainer: "Which helpers you get depends on your database and the extensions you listed." + +6. "The two builders share the same shape but differ where the databases do." + Stopped by: this claims the two examples are the same shape, but they are not. The PostgreSQL tab calls `defineContract(options, factory)`; the MongoDB tab calls `defineContract({ models })` with no factory at all. They look like two different APIs. The sentence told me not to notice a difference I had already noticed. + +7. "`prisma contract emit` writes it to JSON in a fixed key order and hashes it, so the same source always produces the same files." + Stopped by: nothing here tells me what to do. See "internals" below. + +8. "Both builders also accept the model name as a string." + Stopped by: "both builders." Five paragraphs earlier "the two builders" meant PostgreSQL and MongoDB. Here it means `rel.hasMany` and `rel.belongsTo`. Plainer: "`rel.hasMany` and `rel.belongsTo` also accept the model name as a string." + +## Words and phrases I had to guess + +- **"contract"** — I guessed: the new name for the schema. +- **"PSL"** — I guessed: Prisma Schema Language. +- **"codecId: "pg/text@1""** — I guessed: some identifier for how a value is encoded to and from the column. I have no idea where the list of valid values lives. It appears twice in code and is never explained in prose. +- **"nativeType: "text""** — I guessed: the raw PostgreSQL type name. +- **"as const"** — I guessed it is required, not stylistic, but the page never says why. +- **"your database's target package"** — I guessed: the `@prisma/orm-*` package for your database. +- **"extension pack" / "pack"** — I guessed: a plugin. The contract imports `.../pack` and the config imports `/control`, with no explanation of why the same extension has two entry points. +- **"declared named types"** — I guessed: things like the pgvector `Vector(1536)` you put in the `types` map. +- **"emitted"** — I guessed: written into `contract.json`. +- **"contract infer"** — I guessed: reads an existing database and writes a `.prisma` file. +- **"the two files"** — used in Next steps without repeating which two. + +## Places I asked "so what do I actually type?" and got no answer + +1. **The full list of field helpers.** The page shows `field.text()`, `field.uuidString()`, `field.json()`, `field.id.uuidv4String()`, `field.temporal.*`, `field.namedType()`. I have integers, booleans, decimals, plain dates, and byte columns in my app. "The exact helper set comes from your database and the extension packs you compose" is not an answer and there is no link to the list. This is the single biggest gap. +2. **`.defaultSql(expression)`** — is the argument a string? `defaultSql("now()")`? No example. +3. **`rel.hasOne` and `rel.manyToMany`** — named and then dropped. Many-to-many was the hardest relation in v6. What are the options? Is there a join table, and do I name it? +4. **Indexes.** `.unique()` is covered. There is no way shown to add a plain, non-unique index, or a multi-column index. Meanwhile the agent-prompt section says "add a unique index to the email field", which mixes up index and constraint. +5. **Composite primary keys.** Both `field.id.uuidv4String()` and a chained `.id()` exist. Which do I use, and how do I mark two fields as the key together? +6. **Adding the extension to `prisma.config.ts`.** "Add the same pack to `prisma.config.ts` as `extensions: [pgvector]`, importing the pack's `/control` export." No code block. I do not know the import line, and I do not know whether `extensions` goes at the top level of `definePrismaConfig` or inside `ormConfig({ ... })`. The config example earlier shows only `contract`, so I cannot infer it. +7. **Do I have to declare foreign keys myself?** The PostgreSQL example has both `rel.belongsTo` *and* `constraints.foreignKey(...)` for the same relation. The prose says the callback form is "for foreign keys with explicit names", which suggests the constraint already exists and I am only naming it — but that is my guess, and the example reads as if both are required. +8. **Can I call `.sql()` without `.relations()`?** Every example is `Model.relations({...}).sql({...})`. A model with no relations is common. Is `Model.sql({ table })` legal? +9. **Postgres enums.** The enum is stored as `text`. In v6 I got a real Postgres `enum` type. Can I still have one? If so, what `codecId` and `nativeType`? +10. **What the emit step needs from my TypeScript setup.** Does `prisma contract emit` type-check the file? Does the file have to be inside my `tsconfig.json`? If I import a helper from another package, does emit resolve it? +11. **Converting an existing project.** "Keep the other form out of the project so the two can never disagree." Do I delete `schema.prisma`? Is there a command? The only conversion path offered is asking a coding agent. +12. **MongoDB primary keys.** `_id: field.objectId()` is never marked with `.id()`. I assume `_id` is special, but the page does not say so. + +## Places that explain how the tool works when I only wanted to know what to do + +- **The whole "How `defineContract` works" section.** The useful part is one sentence: MongoDB takes one object, PostgreSQL takes options plus a factory. The rest describes argument plumbing I would have learned from the example. +- **"`prisma contract emit` writes it to JSON in a fixed key order and hashes it, so the same source always produces the same files. That works only if the file holds plain data."** I do not need the key ordering or the hash. The rule I need is the three bullets that follow. Lead with the rule. +- **"`prisma contract emit` imports the file and reads its `default` or `contract` export, and nothing else."** Half internals, half the only place the page tells me my export must be named `contract` or be the default. That is a required instruction buried in a section about purity, six screens below the example that relies on it. +- **"The factory receives authoring helpers composed from ..."** — describes the mechanism. What I need is: "`field`, `model`, `rel` and `type` come from the factory's argument, not from imports." + +## Contradictions between the two tabs that no prose resolves + +Reading top to bottom, the MongoDB tab looks like a different product: + +- PostgreSQL destructures `{ field, model, type }` from a factory; MongoDB imports `field, model, rel` at the top. +- PostgreSQL: `rel.hasMany(Post, { by: "userId" })`. MongoDB: `rel.hasMany("Post", { from: "_id", to: "authorId" })`. Different option names for the same idea, with no note that `by` and `from`/`to` differ per database. +- PostgreSQL: `User.refs.id` (property, plural). MongoDB: `User.ref("_id")` (method, singular). Never mentioned. +- PostgreSQL puts relations in a chained `.relations()`; MongoDB puts them inline in the model options. The prose mentions the `collection` difference but not this one. +- The MongoDB `rel` import is used, but the MongoDB list of helpers in the prose ("`field.string()`, `field.int32()` ...") omits `field.objectId()`, which the example actually uses for the key. + +## Could I do what the page is for after one reading? + +Partly. I could copy the PostgreSQL example, change the model and field names, point `prisma.config.ts` at it, and run `npx prisma contract emit`. That is real, and it is the page's main job. + +I could not write a contract for my actual application. I would still not know: + +- what to type for an integer, boolean, decimal, plain date, or binary field — the majority of my columns; +- how to write a many-to-many relation, which every app of mine has; +- how to add a non-unique or multi-column index; +- how to write a composite primary key; +- where `extensions` goes in `prisma.config.ts`, or what the `/control` import line is; +- whether I must declare foreign key constraints by hand or only when I want to name them; +- what a `codecId` is, or where to find valid values, even though I must type one for every enum; +- whether `.sql()` works on a model with no relations; +- how to move my existing `schema.prisma` over, other than asking an agent. + +The page reads as a tour of the builder's design. It is not yet something I can write a real contract from without a second, longer reference next to it — and it does not link to one. \ No newline at end of file From ce3f9a3fa0a6eadf7ba82025a520eef70df7faa2 Mon Sep 17 00:00:00 2001 From: willbot Date: Mon, 14 Sep 2026 09:28:23 +0200 Subject: [PATCH 06/14] docs(orm8): contract-authoring pages, reader-review round two applied The pages now carry the everyday loop (edit, contract emit, migration plan, db migrate) and say that applying a migration records the marker itself, so db sign is only for a database that already matches. The TypeScript page's prose matches its examples on where rel comes from and on constraints.index taking a list, and it shows nativeEnum with pg.enum instead of claiming the builder has no native enum helper. The PSL page shows temporal.updatedAt(), a native_enum block, the db key in the config, and links the scalar type table. The capabilities page names the database package once and explains MongoDB without contradicting the rule. Co-Authored-By: Claude Fable 5.1 Signed-off-by: willbot Signed-off-by: Will Madden --- .../orm/contract-authoring/capabilities.mdx | 39 +++--- .../orm/contract-authoring/psl-syntax.mdx | 126 +++++++++--------- .../the-contract-artifact.mdx | 46 +++---- .../contract-authoring/the-data-contract.mdx | 46 +++---- .../typescript-schema-builder.mdx | 75 ++++++----- .../capabilities.round2.md | 112 ++++++++++++++++ .../page-conventions.md | 2 +- .../psl-syntax.round2.md | 96 +++++++++++++ .../the-contract-artifact.round2.md | 88 ++++++++++++ .../the-data-contract.round2.md | 76 +++++++++++ .../typescript-schema-builder.round2.md | 83 ++++++++++++ 11 files changed, 625 insertions(+), 164 deletions(-) create mode 100644 docs/orm-docs-audit/c21-contract-authoring/capabilities.round2.md create mode 100644 docs/orm-docs-audit/c21-contract-authoring/psl-syntax.round2.md create mode 100644 docs/orm-docs-audit/c21-contract-authoring/the-contract-artifact.round2.md create mode 100644 docs/orm-docs-audit/c21-contract-authoring/the-data-contract.round2.md create mode 100644 docs/orm-docs-audit/c21-contract-authoring/typescript-schema-builder.round2.md diff --git a/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx b/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx index 2a369fb2ac7..65ce206c168 100644 --- a/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx @@ -6,11 +6,11 @@ metaTitle: Supported database features in Prisma ORM metaDescription: 'How a Prisma ORM contract records which database features your setup supports, where the list comes from, what each key controls, and what happens when one is missing.' --- -Not every database supports every feature. One setup can run lateral joins, `RETURNING` clauses, and vector distance operations. Another cannot. [`npx prisma contract emit`](/cli/contract-emit) writes `contract.json` and `contract.d.ts` beside your [contract](/orm/contract-authoring/the-data-contract) source file. The `capabilities` section of `contract.json` lists the database features your setup supports, and you never edit it. Prisma ORM reads that list before it uses one of those features. A feature your setup lacks fails right away, with an error that names the key for the missing feature, instead of reaching the database and failing there. +Your [contract](/orm/contract-authoring/the-data-contract), the `contract.prisma` file that replaced `schema.prisma`, describes your data. Not every database supports every feature: one setup can run lateral joins and `RETURNING` clauses, and another cannot. [`npx prisma contract emit`](/cli/contract-emit) writes `contract.json` and `contract.d.ts` beside your contract. The `capabilities` section of `contract.json` lists the database features your setup supports, and you never edit it. Prisma ORM reads that list before it uses one of those features. A feature your setup lacks fails right away, with an error that names the key for the missing feature, instead of reaching the database and failing there. ## Where the `capabilities` section comes from [#where-capabilities-come-from] -Three things each say which features they support: your database (PostgreSQL, SQLite, or MongoDB), its driver package, and any extension packages listed in `prisma.config.ts`. `npx prisma contract emit` collects all of those answers into `contract.json`, grouped by prefix: +Three things each say which features they support: your database, your database package, and any extension packages listed in `prisma.config.ts`. Your database package is `@prisma/orm-postgres`, `@prisma/orm-sqlite`, or `@prisma/orm-mongo`, and you choose it by the import in `prisma.config.ts`. `npx prisma contract emit` collects all of those answers into `contract.json`, in groups: ```json title="src/prisma/contract.json (excerpt)" { @@ -27,21 +27,20 @@ Three things each say which features they support: your database (PostgreSQL, SQ } ``` -Keys under `sql` are shared across SQL databases. Keys under `postgres` belong to PostgreSQL only. Extension packages add keys of their own, and `pgvector.cosine` above comes from the pgvector package. Install it with `npm install @prisma/orm-extension-pgvector`, then list it in `prisma.config.ts` and run `npx prisma contract emit` again: +A key is written group first. The `pgvector.cosine` key under `postgres` is written `postgres.pgvector.cosine`. Keys under `sql` are shared across SQL databases, and keys under `postgres` belong to PostgreSQL only. Extension packages add keys of their own, and `pgvector.cosine` above comes from the pgvector package. Install it with `npm install @prisma/orm-extension-pgvector`, add these two lines to `prisma.config.ts`, and run `npx prisma contract emit` again: -```typescript title="prisma.config.ts" -import { definePrismaConfig } from "prisma/config"; -import pgvector from "@prisma/orm-extension-pgvector/control"; -import { defineConfig as ormConfig } from "@prisma/orm-postgres/config"; +```diff title="prisma.config.ts" ++ import pgvector from "@prisma/orm-extension-pgvector/control"; -export default definePrismaConfig({ - orm: ormConfig({ contract: "./src/prisma/contract.prisma", extensions: [pgvector] }), -}); + orm: ormConfig({ + contract: "./src/prisma/contract.prisma", ++ extensions: [pgvector], + }), ``` ## What the `capabilities` section controls [#what-capabilities-gate] -Prisma ORM checks the keys twice, both times before any SQL reaches the database. +Two errors tell you that a feature is missing. Both arrive before any SQL reaches the database. **When the contract is built.** If your contract uses a feature your database cannot store, `npx prisma contract emit` fails. SQLite has no `scalarList` key, so a list field such as `tags String[]` fails on SQLite: @@ -51,22 +50,22 @@ scalar lists (the adapter does not report the "scalarList" capability). Remove the list or author it against a target that supports scalar lists. ``` -Do one of the two: remove the list field, or point the contract at a database that supports list fields, such as PostgreSQL. +In that message, "target" and "adapter" both mean your database package, which is `@prisma/orm-sqlite` here. Do one of the two: remove the list field, or switch to a database that supports list fields. To switch to PostgreSQL, change the import in `prisma.config.ts` from `@prisma/orm-sqlite/config` to `@prisma/orm-postgres/config`. -**When a query is built.** A query-builder method that needs a feature throws an error with code `ORM.CAPABILITY_MISSING`. The message names the method and the key: +**When you call a query-builder method.** A method that needs a feature throws an error with code `ORM.CAPABILITY_MISSING`. The message names the method and the key: ```text distinctOn() requires capability postgres.distinctOn lateralJoin() requires capability sql.lateral ``` -That message means the method is not available on the database you are connected to. Rewrite the query without it, or move to a database that supports the feature. If the key comes from an extension package, install that package, list it in `prisma.config.ts`, and run `npx prisma contract emit` again. The error is raised when your code builds the query, so a test that builds the query catches it. +That message means the method is not available on the database you are connected to. Rewrite the query without it, or move to a database that supports the feature. If the key comes from an extension package, install that package, list it in `prisma.config.ts`, and run `npx prisma contract emit` again. The call throws as soon as you make it, so a test that calls it catches it. -Your own code can read the same list: the client exposes it as `db.contract.capabilities`, so `db.contract.capabilities.sql?.lateral` is `true` when lateral joins are available. +Your own code can read the same list. `db` is the client you create once in `src/prisma/db.ts`, and it exposes the list as `db.contract.capabilities`. A whole group can be absent, so write `db.contract.capabilities.sql?.lateral`, which is `true` when lateral joins are available. ## Example keys [#example-capabilities] -A few of the keys your database, its driver package, and the built-in extension packages supply: +A few of the keys your database, your database package, and the built-in extension packages supply: | Key | What it covers | | --- | --- | @@ -76,17 +75,17 @@ A few of the keys your database, its driver package, and the built-in extension | `postgres.distinctOn` | `DISTINCT ON` queries (`distinctOn()`) | | `postgres.pgvector.cosine` | Cosine distance from the pgvector package | -PostgreSQL has all three `sql` keys above, and SQLite has none of them. This table is a sample, not the full list. Open your project's `contract.json` to see every key your setup supports. +PostgreSQL has all three `sql` keys above, and SQLite has none of them. This table is a sample, not the full list. Open `src/prisma/contract.json` in your project to see every key your setup supports. -MongoDB has no keys at all, so none of these checks apply to it. +The methods that need these keys exist only on the SQL query builder, and MongoDB does not have one, so nothing on MongoDB reads the `capabilities` section. The [ORM reference](/orm/reference) lists the methods each database has. ## The `capabilities` section and `prisma db verify` [#capabilities-and-verification] -The keys say what your installed software supports. They are recorded when you run `npx prisma contract emit`, not read from the live database, so the same contract behaves the same way in every environment. Checking the live database is a separate job: [`db verify`](/cli/db-verify) compares the database against your contract, using the recorded hashes described in [contract.json and contract.d.ts](/orm/contract-authoring/the-contract-artifact#the-content-hashes). Extension packages are the main source of keys beyond the built-in ones, and [using extensions](/orm/extensions/using-extensions) shows the full path from install to first query. +The keys say what your installed software supports. They are recorded when you run `npx prisma contract emit`, not read from the live database, so the same contract behaves the same way in every environment. Checking the live database is a separate job. [`db verify`](/cli/db-verify) compares the database against your contract. Extension packages are the main source of keys beyond the built-in ones, and [using extensions](/orm/extensions/using-extensions) shows the full path from install to first query. ## Prompt your coding agent -Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent; in an existing project, run `npx prisma skills sync`. The `prisma-8` skill covers this material. Ask your agent to: +Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent. In an existing project, run `npx prisma skills sync`. The `prisma-8` skill covers this material. Ask your agent to: - "Which keys does the `capabilities` section of our contract require, and which package provides each?" - "Add pgvector to the project and confirm its key shows up in the emitted contract." diff --git a/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx b/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx index bcc7e8d3c70..2b8a4474fd0 100644 --- a/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx @@ -10,10 +10,10 @@ PSL, the Prisma Schema Language, is the preferred way to author [your contract]( If you know the Prisma schema language, most of a contract file reads exactly as you expect. Prisma ORM 8 adds five things: -- named types: reusable aliases for a type and its storage details. +- named types: one name for a type and the database column type it uses. - enums: unchanged, except that an enum can now say how its values are stored. - value objects: a structured value stored inside its parent row, with no table of its own. -- base models and variants: one model reusing another model's fields and rows. +- base models and variants: a variant model adds fields to a base model, and one field's value says which variant a row is. - extension types: field types that come from an npm package, such as vectors. The `datasource` and `generator` blocks are gone. The connection URL and the file paths are set in `prisma.config.ts` instead. [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7#schema) lists every change to the schema file, including what each old `@db.` attribute becomes. @@ -83,9 +83,7 @@ enum UserRole { model User { id ObjectId @id @map("_id") - name String email String - bio String? role UserRole address Address? posts Post[] @@ -94,11 +92,9 @@ model User { } model Post { - id ObjectId @id @map("_id") - title String - content String - authorId ObjectId - createdAt DateTime + id ObjectId @id @map("_id") + title String + authorId ObjectId author User @relation(fields: [authorId], references: [id]) @@ -107,84 +103,70 @@ model Post { } ``` -The first line, `// use prisma-8`, tells the Prisma editor extension that the file is a Prisma ORM 8 contract. It is not required: the contract works without it. `prisma orm init` writes it, so keep it. - -Run `npx prisma contract emit` after any change to refresh the two files. +Keep the first line, `// use prisma-8`. The Prisma editor extension reads it to tell that the file is a Prisma ORM 8 contract. Run `npx prisma contract emit` after any change to refresh the two files. ## Point the config at the schema -The config's `contract` path names the one file Prisma ORM reads. It takes a single path, so there is no folder of schema files. A `.prisma` extension selects PSL authoring: +The config's `contract` path names the one file Prisma ORM reads. It takes a single path, so there is no folder of schema files. A `.prisma` extension selects PSL authoring. The `db` key holds the connection URL: -```typescript title="prisma.config.ts" tab="PostgreSQL" +```typescript title="prisma.config.ts" +import 'dotenv/config'; import { definePrismaConfig } from "prisma/config"; import { defineConfig as ormConfig } from "@prisma/orm-postgres/config"; export default definePrismaConfig({ orm: ormConfig({ contract: "./src/prisma/contract.prisma", + db: { + connection: process.env['DATABASE_URL']!, + }, }), }); ``` -```typescript title="prisma.config.ts" tab="MongoDB" -import { definePrismaConfig } from "prisma/config"; -import { defineConfig as ormConfig } from "@prisma/orm-mongo/config"; - -export default definePrismaConfig({ - orm: ormConfig({ - contract: "./src/prisma/contract.prisma", - }), -}); -``` - -The import chooses the database. `@prisma/orm-postgres/config` makes it a PostgreSQL project, `@prisma/orm-mongo/config` a MongoDB one. The connection URL goes in this file too, in a `db` key beside `contract`. [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7#schema) shows a config with both and lists the other keys. - -Projects created with `npm create prisma@latest -- my-app` already have this config and a starter contract file. +This is what `prisma orm init` writes, `DATABASE_URL` included. The import chooses the database: `@prisma/orm-postgres/config` makes it a PostgreSQL project, and `@prisma/orm-mongo/config` a MongoDB one. [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7#schema) lists the other keys. ## Models and fields -Models declare fields with a type, an optional `?` marker, and attributes: +Models declare fields with a type, an optional `?` marker, and attributes. [Scalar fields](/orm/data-modeling#scalar-fields) lists the types a field can hold, among them `String`, `Int`, `Boolean`, `Decimal`, `DateTime`, `Json`, and `Bytes`. A native PostgreSQL type such as `VarChar(35)` or `Timestamptz` is written in the same position as one of those. - `@id` marks the primary key. `@@id([a, b])` declares a composite key. - `@unique` adds a unique constraint on one field. `@@unique([userId, title])` adds one across several fields. - `@@index([...])` declares a secondary index. - `@default(...)` sets a default. Database function defaults such as `@default(now())` become column defaults in the database. Generated defaults such as `@default(uuid())` are produced by Prisma ORM before each write, not by the database. - `@map("column_name")` sets a field's column name in the database. `@@map("table_name")` sets the table or collection name when it differs from the model name. -- `@updatedAt` no longer exists. Write `updatedAt temporal.updatedAt()`, with the call where the type would go. -How IDs map differs by database: - - - - +`@updatedAt` no longer exists. Write the call where the type would go: ```prisma -model User { - id Uuid @id @default(uuid()) +model Post { + updatedAt temporal.updatedAt() } ``` -The primary key is an ordinary column. Pick its type and default yourself. +`temporal` is built in. You do not import it or declare it. - +How IDs map differs by database: - +```prisma tab="PostgreSQL" +model User { + id Uuid @id @default(uuid()) +} +``` -```prisma +```prisma tab="MongoDB" model User { id ObjectId @id @map("_id") } ``` -The primary key is MongoDB's `_id`: type it `ObjectId` and map it to `_id`. - - +On PostgreSQL the primary key is an ordinary column, so pick its type and default yourself. - +On MongoDB the primary key is the document's `_id`. Type it `ObjectId` and map it to `_id`. ## Named types -The `types` block declares reusable type aliases. An alias binds a base type and its storage details under one name: +The `types` block declares reusable type aliases. An alias binds a type and its database column type under one name: ```prisma types { @@ -192,9 +174,7 @@ types { } ``` -Fields then use `ShortName` like any built-in type. The alias keeps the storage decision in one place: a `varchar(35)` column rather than `text`. - -An alias is optional. A field can name the native PostgreSQL type directly, such as `VarChar(35)`, `Uuid`, or `Timestamptz`. In Prisma ORM 8 the native type is the field's type, so `String @db.VarChar(35)` from Prisma ORM 7 becomes `VarChar(35)`. The `@db.` attributes are gone. +Fields then use `ShortName` like any built-in type. The alias keeps the column decision in one place: a `varchar(35)` column rather than `text`. An alias is optional, and a field can name the native PostgreSQL type directly, such as `VarChar(35)`, `Uuid`, or `Timestamptz`. In Prisma ORM 8 the native type is the field's type, so `String @db.VarChar(35)` from Prisma ORM 7 becomes `VarChar(35)`. The `@db.` attributes are gone. ## Enums @@ -209,11 +189,26 @@ enum Priority { } ``` -`@@type("pg/text@1")` stores the values as `text` on PostgreSQL. That name is the PostgreSQL type plus a version, always `@1` today. Write `@@type("pg/int4@1")` to store them as integers instead. When a member has no explicit value, the member name itself is stored. +In `pg/text@1`, `pg` is PostgreSQL and `text` is the column type, so the values are stored as `text`. Write the `@1` as shown, because there is no other version yet. Write `@@type("pg/int4@1")` to store the values as integers instead. When a member has no explicit value, the member name itself is stored. + +`@@type` is optional. Leave it out and Prisma ORM picks the type from the member values: bare member names and string values give the database's text type, and integer values give its integer type. + +A `@default` names the member, as in `priority Priority @default(Low)`, even where the member stores a different value. + +An `enum` block is not a PostgreSQL `enum` type: the column is text or an integer. For a PostgreSQL `enum` type, declare it in a `native_enum` block and type the field `pg.enum(Role)`: -`@@type` is optional. Leave it out and Prisma ORM picks the type from the member values. Bare member names and string values give the database's text type. Integer values give its integer type. +```prisma +native_enum Role { + admin = "admin" + member = "member" +} + +model User { + role pg.enum(Role) +} +``` -An `enum` block is not a PostgreSQL `enum` type: the column is text or an integer. If your database already has a PostgreSQL `enum` type, declare it in a `native_enum` block and type the field `pg.enum(Role)`. [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7#schema) shows both. +Each member needs a value. `pg` comes with `@prisma/orm-postgres`, so there is nothing to import. ## Value objects @@ -228,11 +223,14 @@ type Address { } model User { - id Uuid @id @default(uuid()) - address Address? + id Uuid @id @default(uuid()) + address Address? + addresses Address[] } ``` +A value object field can be optional or a list, and a `type` block can hold a field of another `type`. + Storage differs by database. On PostgreSQL a value object field is stored in a single `jsonb` column. On MongoDB it is an embedded document. Either way, `contract.d.ts` types it as a structured object rather than untyped JSON. On MongoDB, whether to embed or reference is the central modeling decision. [MongoDB data modeling](/orm/data-modeling/mongodb#embed-or-reference) covers it. ## Relations @@ -252,7 +250,7 @@ model User { Add `onDelete` and `onUpdate` to the same `@relation`. They belong on the side that holds the foreign key, not on the list side. For a one-to-one, make the other side singular instead of a list, and put `@unique` on the foreign-key field. -Many-to-many relations need a model for the join table. You write that model yourself: there is no implicit many-to-many. Prisma ORM matches the two list fields to the model that holds a foreign key to each side and whose primary key is exactly those two foreign keys: +Many-to-many relations need a model for the join table. You write that model yourself: there is no implicit many-to-many. That model must follow two rules. It holds a foreign key to each side, and each foreign key references that side's full `@id`. Its own `@@id([...])` lists exactly those two foreign-key fields. ```prisma model Post { @@ -277,6 +275,8 @@ model PostTag { You then read `post.tags` as a list of `Tag`, without mentioning `PostTag` in the query. +Break either rule and `npx prisma contract emit` throws an error whose `code` is `PSL_JUNCTION_ID_NOT_FK_COVERING` when the `@@id` lists anything other than the two foreign-key fields, or `PSL_JUNCTION_TARGET_FK_NOT_ID` when a foreign key does not reference the other side's `@id`. The message names the list field it could not match. + For which shape to choose and which side owns the foreign key, see [relational data modeling](/orm/data-modeling/relational-databases) and [MongoDB data modeling](/orm/data-modeling/mongodb). ## Base models and variants @@ -302,9 +302,13 @@ model Bug { } ``` -Rows with `type = "bug"` are `Bug` records. A variant reuses its base model's fields, so a `Bug` has `id`, `title`, and `type` as well as `severity` and `stepsToRepro`. You query it through the base model: `db.orm.public.Task.variant('Bug')` returns the `Bug` rows only. See [`variant()`](/orm/reference/orm-client#variant). +A field name is written bare, as `type` is in `@@discriminator(type)`, and a database name is quoted, as in `@@map("task")`. -On PostgreSQL, the variant's `@@map` picks where the variant's own fields are stored. With its own `@@map`, as `Bug` has here, they are in a table of their own that shares the base model's primary key. Without one, they are nullable columns in the base table. [Relational data modeling](/orm/data-modeling/relational-databases#polymorphic-relations) covers choosing between the two. +Rows with `type = "bug"` are `Bug` records. A variant reuses its base model's fields, so a `Bug` has `id`, `title`, and `type` as well as `severity` and `stepsToRepro`. A variant does not declare an `@id` of its own: it takes the base model's primary key. + +You query a variant through the base model: `db.orm.public.Task.variant('Bug')` returns the `Bug` rows only. Here `public` is the PostgreSQL schema. See [`variant()`](/orm/reference/orm-client#variant). + +On PostgreSQL you choose where a variant's own fields are stored, and the variant's `@@map` is the choice. Give the variant its own `@@map`, as `Bug` has here, and its fields are in a table of their own that shares the base model's primary key. Leave `@@map` out and they are nullable columns in the base table. [Relational data modeling](/orm/data-modeling/relational-databases#polymorphic-relations) covers choosing between the two. On MongoDB, a variant adds its fields to documents in the base model's collection, so it declares `@@base` but no `@@map` of its own. @@ -326,8 +330,6 @@ export default definePrismaConfig({ ``` ```prisma title="src/prisma/contract.prisma" -// use prisma-8 - types { Embedding1536 = pgvector.Vector(1536) } @@ -338,9 +340,7 @@ model Post { } ``` -The `pgvector` part of `pgvector.Vector(1536)` is a fixed name the pack declares. It is not the name you gave the import in `prisma.config.ts`, so renaming that import does not change what you write here. - -`npx prisma contract emit` resolves extension types through the packs listed in the config, so list the pack before using its types. Run it again after changing the extension list. [Using extensions](/orm/extensions/using-extensions) covers installing a pack. +The `pgvector` part of `pgvector.Vector(1536)` is a fixed name the pack declares, not the name you gave the import. `npx prisma contract emit` resolves extension types through the packs listed in the config, so list the pack before using its types. Run it again after changing the extension list. [Using extensions](/orm/extensions/using-extensions) covers installing a pack and names the packs you can add. ## Starting from an existing database @@ -356,6 +356,6 @@ Projects created with `npm create prisma@latest -- my-app` include the [Prisma O ## Next steps -- Run `npx prisma contract emit` and inspect [`contract.json` and `contract.d.ts`](/orm/contract-authoring/the-contract-artifact). You do not import them yourself: `db`, the client you create in `src/prisma/db.ts`, reads both. +- Run `npx prisma contract emit` and inspect [`contract.json` and `contract.d.ts`](/orm/contract-authoring/the-contract-artifact). You do not import them yourself. `db`, the client that reads both, is in `src/prisma/db.ts`, and `prisma orm init` writes that file. See [transactions and runtime](/orm/reference/transactions-and-runtime). - If you prefer defining models in code, see [authoring in TypeScript](/orm/contract-authoring/typescript-schema-builder). - Apply the contract to a database with [`db init`](/cli/db-init) or plan changes with [`migration plan`](/cli/migration-plan). diff --git a/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx b/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx index d5426342c9b..cd4f23efd7f 100644 --- a/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx @@ -6,17 +6,24 @@ metaTitle: The Prisma ORM contract artifacts metaDescription: 'What contract.json and contract.d.ts contain, why the same source always produces the same two files, how the hashes work, and how your app and the CLI read them.' --- -You write [your contract](/orm/contract-authoring/the-data-contract) in one source file: `src/prisma/contract.prisma` in PSL, or `src/prisma/contract.ts` in TypeScript. [`npx prisma contract emit`](/cli/contract-emit) reads that file and writes two files beside it: +Your contract, [the one description of your data and how it is stored](/orm/contract-authoring/the-data-contract), is one source file: `src/prisma/contract.prisma` in PSL, or `src/prisma/contract.ts` in TypeScript. [`npx prisma contract emit`](/cli/contract-emit) reads that file and writes two files beside it. In the table below, `db` is your client, the object you run queries on. `prisma orm init` writes it for you in `src/prisma/db.ts`. | File | Contents | Read by | | --- | --- | --- | | `contract.json` | The machine-readable contract: models, how they are stored, which database features they need, and the hashes | `db`, `prisma db sign`, `prisma db verify`, and `prisma migration plan` | | `contract.d.ts` | TypeScript declarations derived from the contract | The query APIs and your application code, for typed models and results | -`db` is the client you create once in `src/prisma/db.ts`. `prisma orm init` writes that file for you. - Both files are generated. Do not edit them. Change the [PSL](/orm/contract-authoring/psl-syntax) or [TypeScript](/orm/contract-authoring/typescript-schema-builder) source and run `npx prisma contract emit` again. Each file starts with a notice that says exactly this. To write the two files to another directory, see [`prisma contract emit`](/cli/contract-emit). +The everyday loop on a project: + +1. Edit `contract.prisma` or `contract.ts`. +2. Run `npx prisma contract emit`. +3. Create the migration with [`npx prisma migration plan`](/cli/migration-plan), read it, then apply it with [`npx prisma db migrate`](/cli/db-migrate). +4. Check the database against the contract with [`npx prisma db verify`](/cli/db-verify) in CI or in your deploy step. + +Applying a migration records which contract the database now matches, so you do not sign it yourself afterwards. [`npx prisma db init`](/cli/db-init) creates the structures in an empty database and records the same thing. Run [`npx prisma db sign`](/cli/db-sign) only when the database already matches the contract and nothing recorded it, such as after [`npx prisma contract infer`](/cli/contract-infer) on an existing database. + ## The same source always produces the same two files [#deterministic-emission] Run `npx prisma contract emit` on the same source on any machine and you get the same two files, byte for byte. You can read the diff of either file in a code review. @@ -25,19 +32,13 @@ That only holds if the contract source does not read the environment, the clock, ## The content hashes -`npx prisma contract emit` computes a hash over each of these parts of the contract. You never compute or compare them yourself. - -| Hash | Covers | Changes when | -| --- | --- | --- | -| `storageHash` | The storage layout: tables, columns, primary keys, uniques, indexes, foreign keys, and the allowed values of each enum | A table, column, key, constraint, or index changes | -| `executionHash` | The defaults Prisma ORM applies before a write, such as `@default(uuid())` | A generated default changes | -| `profileHash` | The database this contract targets, and whether it is SQL or MongoDB | The contract targets a different database | +`npx prisma contract emit` computes a hash over each of three parts of the contract. You never compute or compare them yourself. `storageHash` covers the storage layout: tables, columns, primary keys, uniques, indexes, foreign keys, and the allowed values of each enum. `executionHash` covers the defaults Prisma ORM applies before a write. `profileHash` covers which database the contract targets. -The hashes are how Prisma ORM connects the contract to a live database. [`npx prisma db sign`](/cli/db-sign) checks that the database satisfies the contract, then records `storageHash` and `profileHash` in a table it creates called `prisma_contract.marker`. You never write to that table yourself. Run `db sign` when the live database already matches the contract, for example after [`prisma contract infer`](/cli/contract-infer) on an existing database, or after a deployment applied the changes. [`npx prisma db verify`](/cli/db-verify) compares the contract you hold against that record and against the live schema, and fails when they disagree. +The hashes are how Prisma ORM connects the contract to a live database. `npx prisma db sign` checks that the database satisfies the contract, then records `storageHash` and `profileHash` in a table it creates called `prisma_contract.marker`. You never write to that table yourself. `npx prisma db verify` compares the contract you hold against that record and against the live schema, and fails when they disagree. To fix a mismatch, apply the missing migration, or run `npx prisma db sign` when the database already matches the contract. ## Inside `contract.json` -The contract keeps your application's view of the data separate from the way the database stores it. The `domain` section describes models, fields, and relations. The `storage` section describes tables, columns, keys, and indexes. On MongoDB it describes collections and indexes instead. Each model's own `storage` block joins the two. Everything is grouped by namespace. On PostgreSQL the namespace is the schema. It is `public` unless you put the models in a `namespace` block, such as `namespace auth { }`, in your [PSL source](/orm/contract-authoring/psl-syntax). +The contract keeps your application's view of the data separate from the way the database stores it. The `domain` section describes models, fields, and relations. The `storage` section describes tables, columns, keys, and indexes. On MongoDB it describes collections and indexes instead. Each model's own `storage` block maps each field to its column. Everything is grouped by namespace. On PostgreSQL the namespace is the schema. It is `public` unless you put the models in a `namespace` block, such as `namespace auth { }`, in your [PSL source](/orm/contract-authoring/psl-syntax). An abridged `contract.json` for a `User` and `Post` schema: @@ -104,12 +105,12 @@ An abridged `contract.json` for a `User` and `Post` schema: The sections, top to bottom: - **`schemaVersion`, `targetFamily`, `target`**: the contract format version, whether the database is SQL or MongoDB, and which database this contract targets. -- **`roots`**: the table name of every model, so the query APIs know what to call each one. The SQL query builder uses the table name, as in `db.sql.public.user`. The model API uses the model name, as in `db.orm.public.User`. Both are described in the [ORM reference](/orm/reference). -- **`domain`**: the application's view. Each scalar field records whether it is nullable and its `codecId`, such as `pg/text@1`, which is the PostgreSQL type plus a version. Every id ends in `@1` today. Relations record how many records are on each side and the fields they join on. The model's `storage` block maps fields to columns. -- **`storage`**: the database's view. Tables with their columns, each with a native type and a `codecId`, plus primary keys, uniques, indexes, foreign keys, check constraints, and the allowed values of each enum. This is the section the migration tooling compares between versions and `prisma db verify` checks the live schema against. -- **`execution`**: the defaults Prisma ORM applies before a write, such as `@default(uuid())`. Prisma ORM fills these in as it sends the write. They are not column defaults in the database, so a write that does not go through Prisma ORM gets whatever the column itself defaults to. -- **`capabilities`**: which database features this contract can use. `npx prisma contract emit` collects them from the adapter for your database and from any extension packs named in your config. The query APIs check them before building a query that needs one. See [supported database features](/orm/contract-authoring/capabilities). -- **`extensions`**: one entry for each extension pack composed in your project, with the types that pack adds. It is `{}` when you compose none. +- **`roots`**: one entry per model. Each key is the table name, and its value names the model that table stores. The SQL query builder uses the table name, as in `db.sql.public.user`. The model API uses the model name, as in `db.orm.public.User`. Both are described in the [ORM reference](/orm/reference). +- **`domain`**: the application's view. Each scalar field records whether it is nullable and its `codecId`, such as `pg/text@1`, which is the PostgreSQL type plus a version. Relations record how many records are on each side and the fields they join on. The model's `storage` block maps each field to its column. +- **`storage`**: the database's view. Tables with their columns, plus primary keys, uniques, indexes, foreign keys, check constraints, and the allowed values of each enum. This is the section the migration tooling compares between versions and `prisma db verify` checks the live schema against. +- **`execution`**: the defaults Prisma ORM applies before a write, such as `@default(uuid())`. Prisma ORM fills these in as it sends the write. They are not column defaults in the database. A write that does not go through Prisma ORM gets whatever the column itself defaults to. +- **`capabilities`**: which database features this contract can use. `npx prisma contract emit` collects them from the adapter for your database and from the extension packs listed in `extensions: [...]` in `prisma.config.ts`. The query APIs check them before building a query that needs one. See [supported database features](/orm/contract-authoring/capabilities) and [extension types](/orm/contract-authoring/psl-syntax#extension-types). +- **`extensions`**: one entry for each extension pack in that `extensions: [...]` list, with the types that pack adds. It is `{}` when you list none. ## Inside `contract.d.ts` @@ -117,7 +118,7 @@ The declarations file gives the type system the same information. It exports: - `Contract`, the type the client is built from. `db.ts` imports it. - One type per hash, such as `StorageHash` and `ProfileHash`. The hash string is part of the type. -- `Output` and `Input` for each `type` block in your contract, such as `AddressOutput` and `AddressInput` below. +- `Output` and `Input` for each [`type` block](/orm/contract-authoring/psl-syntax#value-objects) in your contract. A `type` block declares a structured value stored inside its parent row, with no table of its own. A `type Address` gives you the `AddressOutput` and `AddressInput` below. - `FieldOutputTypes` and `FieldInputTypes`, which hold the field types of every model, keyed by namespace and model name. ```typescript title="src/prisma/contract.d.ts (excerpt)" @@ -130,11 +131,10 @@ export type AddressOutput = { readonly street: CodecTypes["pg/text@1"]["output"]; readonly city: CodecTypes["pg/text@1"]["output"]; readonly zip: CodecTypes["pg/text@1"]["output"] | null; - readonly country: CodecTypes["pg/text@1"]["output"]; }; ``` -`CodecTypes`, `StorageHashBase`, and `ProfileHashBase` are helper types the file declares for itself. You never write them. Because the hash string is part of the type, files from two different runs of `npx prisma contract emit` do not type-check against each other. If you regenerate `contract.json` and not `contract.d.ts`, TypeScript catches it before `prisma db verify` has to. +The hash string is part of the type. If you regenerate `contract.json` and not `contract.d.ts`, TypeScript catches it before `prisma db verify` has to. ## How the application consumes the artifacts @@ -152,7 +152,7 @@ export const db = postgres({ }); ``` -The connection string comes from the `DATABASE_URL` environment variable. The import path `./contract.d` is written that way on purpose. It is not a typo, and TypeScript resolves it to `contract.d.ts`. Before its first query, `db` reads the record `prisma db sign` wrote in the database and compares it with the contract's hashes. If they do not match, it logs a warning and runs the query anyway. Run `prisma db verify` when you need the check to fail instead. +The connection string comes from the `DATABASE_URL` environment variable. The import path `./contract.d` is written that way on purpose. It is not a typo, and TypeScript resolves it to `contract.d.ts`. Before its first query, `db` reads the record in the database of which contract it matches and compares it with the contract's hashes. If they do not match, it logs a warning whose `code` is `CONTRACT.MARKER_MISMATCH` and runs the query anyway. If there is no record at all, because nothing has signed the database yet, it logs a warning whose `code` is `CONTRACT.MARKER_MISSING` and again runs the query. Run `prisma db verify` when you need the check to fail instead. ## Version control @@ -165,7 +165,7 @@ git diff --exit-code src/prisma ## Prompt your coding agent -Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent; in an existing project, run `npx prisma skills sync`. The `prisma-8` skill covers the contract. Ask your agent to: +Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent. In an existing project, run `npx prisma skills sync`. The `prisma-8` skill covers the contract. Ask your agent to: - "Explain the difference between contract.json and contract.d.ts in this project." - "Run prisma contract emit and show me what changed in contract.json." diff --git a/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx b/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx index 0d79d12eabc..edabec0c404 100644 --- a/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx @@ -8,7 +8,7 @@ metaDescription: 'What the Prisma ORM data contract is, how PSL and TypeScript a Every Prisma ORM project has one description of its data: the models, their fields, and how they map to database tables. That description is the data contract, the `contract.prisma` file that replaced `schema.prisma`. -For example, a blog's contract declares a `User` and a `Post`, the fields each one has, and how they relate. You author it in PSL, the Prisma schema language: +For example, a blog's contract declares a `User` and a `Post`, the fields each one has, and how they relate. You author it in PSL, the Prisma Schema Language, the same language as `schema.prisma`: ```prisma title="src/prisma/contract.prisma" // use prisma-8 @@ -23,12 +23,11 @@ model Post { id Int @id @default(autoincrement()) title String userId Int - - user User @relation(fields: [userId], references: [id]) + user User @relation(fields: [userId], references: [id]) } ``` -The first line of every `.prisma` contract is `// use prisma-8`. It marks the file as a Prisma ORM 8 contract rather than a Prisma ORM 7 schema file. Editor support depends on that line: the Prisma language server only reports problems, autocompletes, and formats files that start with it. Contracts written before `8.0.0-rc.10` start with `// use prisma-next` instead. The language server still accepts that older line and replaces it when you format the file. +Start every `.prisma` contract with `// use prisma-8`. Your editor's Prisma support only works on files that have that line. A `contract.ts` file needs no header line. `prisma contract emit` turns this file into two files that the rest of the toolchain reads: `contract.json` and `contract.d.ts`. Your queries are type-checked against the contract, migrations are planned as changes to it, and `prisma db verify` checks a live database against it. Think of it as a `package-lock.json` for your data: an exact, versioned record of what your application expects from its database. @@ -36,15 +35,15 @@ If you already have a Prisma ORM 7 `schema.prisma`, [Coming from Prisma ORM 7](/ :::note[Contract vs. schema] -In Prisma ORM, the **contract** is what you write in your code, and the **schema** is your database's actual structure. Some tools use these words the other way around, so keep the distinction in mind: you author a contract, and Prisma ORM checks that the database's schema satisfies it. +In Prisma ORM, the **contract** is what you write in your code, and the **schema** is your database's actual structure. Some tools use these words the other way around. The language you write the contract in keeps the older name: it is PSL, the Prisma Schema Language, the same language as `schema.prisma`. ::: ## Why a contract -Prisma ORM 7 compiles your `schema.prisma` into generated client code. The description of your data is in there, but only the generated client can read it. Prisma ORM keeps that description in the open. Your contract file compiles to two plain files. `contract.json` describes your models, how they are stored, and [which database features they need](/orm/contract-authoring/capabilities), such as pgvector. `contract.d.ts` holds the TypeScript types derived from it. The same source always produces the same two files, so you can read them in a code review, hash them, and hand them to tools and coding agents. +Your contract compiles to two plain files you can open and read. `contract.json` describes your models, how they are stored, and [which database features they need](/orm/contract-authoring/capabilities). `contract.d.ts` holds the TypeScript types derived from it. The same source always produces the same two files, so you can read them in a code review and hand them to tools and coding agents. In Prisma ORM 7 the same description existed, but only the generated client could read it. -The contract also identifies its own exact version. `prisma contract emit` computes hashes from the contract and writes them into `contract.json`. [`prisma db sign`](/cli/db-sign) checks that your database satisfies the contract, then records those hashes in the `prisma_contract.marker` table, which it creates in your database. Run it once the database matches the contract, after you have applied your migrations. Your app then logs a warning when the database stops matching. To fail instead of warn, for example in a deploy check, run [`prisma db verify`](/cli/db-verify). +`prisma contract emit` also writes a hash of the contract into `contract.json`. When you apply a contract change to the database, the command that applies it records that hash in a small table it creates in your database. Before its first query, `db` compares the two and logs a warning if they differ. To fail instead of warn, for example in a deploy check, run [`prisma db verify`](/cli/db-verify). ## How it works @@ -61,27 +60,29 @@ export default definePrismaConfig({ }); ``` -`prisma orm init` writes that config for you and installs the database package it imports, `@prisma/orm-postgres` for PostgreSQL. It also writes the contract file and `src/prisma/db.ts`. The contract file is either a Prisma schema file (`contract.prisma`) or a TypeScript file (`contract.ts`). The file extension selects the authoring mode, so to author in TypeScript you point `contract` at `./src/prisma/contract.ts`. Both modes describe the same things: models with fields and relations, the tables and columns they map to, named types, enums, and any types that [extension packages](/orm/contract-authoring/psl-syntax#extension-types) add. +`definePrismaConfig` holds the whole config. `ormConfig` comes from your database package and holds the settings for that database: `@prisma/orm-postgres` for PostgreSQL, `@prisma/orm-sqlite` for SQLite, and `@prisma/orm-mongo` for MongoDB. The examples here use PostgreSQL. To start a new project, run `npm create prisma@latest -- my-app`. To add Prisma ORM to a project you already have, run `prisma orm init`. Either one writes `prisma.config.ts`, the contract file, and `src/prisma/db.ts`, and installs the database package. + +The contract file is either a PSL file (`contract.prisma`) or a TypeScript file (`contract.ts`). The file extension selects the authoring mode, so to author in TypeScript you point `contract` at `./src/prisma/contract.ts`. Both modes describe the same things: models with fields and relations, the tables and columns they map to, named types, enums, and any types that [extension packages](/orm/contract-authoring/psl-syntax#extension-types) add. Extension packages are npm packages that add field types and database features, such as pgvector, a PostgreSQL extension for vector columns. -Run `prisma contract emit` after every change to the contract. It replaces `prisma generate`, which Prisma ORM 8 does not have. It writes `contract.json` and `contract.d.ts` beside the source: +After every change to the contract, run these three commands: ```npm npx prisma contract emit +npx prisma migration plan +npx prisma db migrate ``` -From there, the rest of the toolchain takes over. `db` is the client you create once in `src/prisma/db.ts`, and `prisma orm init` writes that file for you: +`prisma contract emit` writes `contract.json` and `contract.d.ts` beside the source. [`prisma migration plan`](/orm/migrations/generating-a-migration) works out what the database needs and writes a migration. [`prisma db migrate`](/orm/migrations/applying-a-migration) applies it and records the new hash for you, so there is no separate signing step. Run [`prisma db sign`](/cli/db-sign) only when the database already matches the contract and nothing needs applying, for example after `prisma contract infer`. -- `db` loads `contract.d.ts` to give you typed models, fields, and results. -- `db` reads `contract.json` and compares its hashes with the record `prisma db sign` wrote in the database. -- The migration tooling compares two contracts to plan database changes, and [`prisma db verify`](/cli/db-verify) checks a live database against the current contract. +`db` is the client you create once in `src/prisma/db.ts`, and `prisma orm init` writes that file for you. From a file directly inside `src/`, import it with `import { db } from "./prisma/db"` and query a model: `await db.orm.public.User.all()` returns every user row. `db.orm` holds your models by model name. `public` is the PostgreSQL schema, the namespace your tables are in, which is `public` unless you set one. [Reading data](/orm/fundamentals/reading-data) covers queries. ## Two authoring modes, one `contract.json` [#two-authoring-modes-one-artifact] **[PSL](/orm/contract-authoring/psl-syntax)** is the usual way to write the contract. It is a compact language for describing data. It is what `npm create prisma@latest` scaffolds, and what [`prisma contract infer`](/cli/contract-infer) writes when you start from an existing database. That command reads the live database and writes a `contract.prisma` from what it finds. -Define models with the **[TypeScript builder](/orm/contract-authoring/typescript-schema-builder)** instead when you want to build them with code. Reach for it when model definitions must be composed, generated, or shared as ordinary TypeScript modules. +Define models with the **[TypeScript builder](/orm/contract-authoring/typescript-schema-builder)** instead when you want to build them with code. Reach for it when model definitions must be composed, generated, or shared as ordinary TypeScript modules. That page shows what a `contract.ts` file looks like. -Both modes produce the same contract. For the same models they write the same `contract.json`, so migrations, database checks, and `db` behave the same no matter which mode you write. A project has exactly one contract file, the one named by `contract` in the config. Do not keep a `contract.prisma` and a `contract.ts` in the same project, so the two can never disagree. +Both modes produce the same contract. For the same models they write the same `contract.json`, so migrations, database checks, and `db` behave the same no matter which mode you write. A project has exactly one contract file, the one named by `contract` in the config. Do not keep both a `contract.prisma` and a `contract.ts`. If you had both, they could describe different things. ## What the contract contains @@ -89,16 +90,16 @@ The contract describes structure, not data: - models, fields, and relations, plus how they map to tables and columns - how the data is stored: primary keys, unique constraints, indexes, and foreign keys -- [named types](/orm/contract-authoring/psl-syntax#named-types) and enums -- [`type` blocks](/orm/contract-authoring/psl-syntax#value-objects), each a structured value stored inside its parent row with no table of its own -- the types and database features that extension packages add, such as pgvector's `Vector` -- hashes that identify this exact version of the contract +- [named types](/orm/contract-authoring/psl-syntax#named-types), which are reusable aliases for a type and its storage details, and enums +- [`type` blocks](/orm/contract-authoring/psl-syntax#value-objects), each a structured value stored inside its parent row with no table of its own. On PostgreSQL each one is stored in a single `jsonb` column. +- the types and database features that extension packages add, such as pgvector's `Vector` type +- a hash of the contract, which the database check compares against It contains no rows, no credentials, and no connection details, so committing the source, `contract.json`, and `contract.d.ts` to version control is safe and expected. ## Prompt your coding agent -Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent; in an existing project, run `npx prisma skills sync`. The `prisma-8` skill covers the contract. Ask your agent to: +Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent. In an existing project, run `npx prisma skills sync`. The `prisma-8` skill covers the contract. Ask your agent to: - "Using the prisma-8 skill, explain what our contract.json currently declares." - "Add an Invoice model to the contract and run prisma contract emit." @@ -107,7 +108,6 @@ Projects created with `npm create prisma@latest` include the [Prisma ORM skills] ## Next steps - [Model your data](/orm/data-modeling) before writing the contract: models, keys, and relations. -- [Query against the contract](/orm/fundamentals/reading-data): every result is typed by what you authored here. } > - Write the contract as a Prisma schema file. + Write the contract as a PSL file. } > - Define the same models with the typed contract builder. + Define the same models with the TypeScript builder. @1`. `nativeType` is the column type in the database. Write `as const` after the object. To store the members as integers, pass `{ codecId: "pg/int4@1", nativeType: "int4" } as const`. [Raw queries](/orm/reference/raw-queries#binding-a-bare-value-with-param) lists more type ids. Each `member(name, storedValue)` pairs the TypeScript-visible name with the value stored in the column. Fields reference the enum with `field.namedType(Priority)`, and defaults reference a member as `Priority.members.Low`. Include the enum in the returned `enums` map so it reaches the two files. -The TypeScript builder has no helper for a real PostgreSQL `enum` type. Members are stored in whatever column type you name here, `text` above. To get a PostgreSQL `enum` type, write the contract in PSL and use a `native_enum` block. +Members are stored in whatever column type you name here, `text` above. For a real PostgreSQL `enum` type, declare it with `nativeEnum` and type the field with `pg.enum`, both exported by `@prisma/orm-postgres/contract-builder`. The type is created in the database under the name you give it: + +```typescript +const Role = nativeEnum("Role", "user", "admin"); + +const Account = model("Account", { + fields: { + role: field.column(pg.enum(Role)), + }, +}); +``` ## Relations @@ -234,7 +231,17 @@ User.relations({ }); ``` -`rel.belongsTo` on its own does not create a foreign key constraint in the database. Ask for one in either of two places. Chain `.sql({ fk: { name: "post_userId_fkey" } })` on the relation, or list `constraints.foreignKey(...)` in the model's `.sql({ foreignKeys: [...] })`, as the full example above does. Both forms take `name`, `onDelete`, and `onUpdate`. +`rel.belongsTo` on its own does not create a foreign key constraint in the database. Ask for one in either of two places. Chain `.sql(...)` on the relation itself, inside the `.relations({ ... })` object: + +```typescript +Post.relations({ + user: rel.belongsTo(User, { from: "userId", to: "id" }).sql({ fk: { name: "post_userId_fkey" } }), +}); +``` + +Or list `constraints.foreignKey(...)` in the model's `.sql({ foreignKeys: [...] })`, as the full example above does. Both forms take `name`, `onDelete`, and `onUpdate`. + +Prisma ORM checks that the two sides of a relation name the same number of fields. It does not check that their column types match, so choose a field helper that produces the same column type as the key you point at. `rel.hasMany`, `rel.hasOne`, `rel.belongsTo`, and `rel.manyToMany` also accept the model name as a string. Pass the model object, as above, and a typo is a compile error. Pass a string and the typo is reported when `prisma contract emit` builds the contract. @@ -242,7 +249,7 @@ User.relations({ `.sql(...)` maps a model to its table. The object form covers the common case: `User.sql({ table: "user" })`. -`.relations(...)`, `.attributes(...)`, and `.sql(...)` are independent. A model with no relations calls `.sql({ table })` straight after `model(...)`. +You can chain `.relations(...)`, `.attributes(...)`, and `.sql(...)` in any order, and you can skip any of them. Each returns a new model builder and leaves the one you called it on unchanged. `rel.belongsTo(User, ...)` may point at either the bare `User` or the chained one, because both carry the same model name. A model with no relations calls `.sql({ table })` straight after `model(...)`. The callback form gives you the model's columns as `cols` and the constraint builders as `constraints`. Use it for indexes and for foreign keys with explicit names: @@ -259,11 +266,11 @@ Post.relations({ ... }).sql(({ cols, constraints }) => ({ })); ``` -`constraints.index` takes one column for a plain index and a list for a multi-column index. Add `{ unique: true }` to make it a unique index. +`constraints.index` always takes a list of columns, even when the list has one entry. Add `{ unique: true }` to make it a unique index. `Model.refs` provides typed references to another model's fields, so `User.refs.id` is checked against the actual `User` definition. -A primary key made of two fields is a model attribute rather than storage mapping. Chain `.attributes(...)` and build it from the field references: +For a primary key made of two fields, use `.attributes(...)` instead of `.sql(...)`, and build it from the field references: ```typescript Membership.attributes(({ fields, constraints }) => ({ @@ -271,11 +278,11 @@ Membership.attributes(({ fields, constraints }) => ({ })); ``` -`constraints.unique([fields.userId, fields.teamId])` in the same block makes a unique constraint across two fields. For a key made of one field, `.id()` on the field is enough, and the `field.id.*` helpers already do it. +The keys in that object are fixed. `id` takes one constraint, and `uniques` takes a list, so `uniques: [constraints.unique([fields.userId, fields.teamId])]` makes a unique constraint across two fields. `cols` in `.sql(...)` and `fields` in `.attributes(...)` are the same model fields under two names. For a key made of one field, `.id()` on the field is enough, and the `field.id.*` helpers already do it. ## Extension types -Declare packs in `defineContract`'s options and the `type` helper exposes their constructors. The full example above imports `pgvector` from `@prisma/orm-extension-pgvector/pack`, lists it in `extensions`, builds `type.pgvector.Vector(1536)` in the returned `types` map, and uses it on a field with `field.namedType(types.Embedding1536)`. +An extension pack is an npm package that adds column types to the builder. Declare packs in `defineContract`'s options and the `type` helper exposes their constructors. The full example above imports `pgvector` from `@prisma/orm-extension-pgvector/pack`, lists it in `extensions`, builds `type.pgvector.Vector(1536)` in the returned `types` map, and uses it on a field with `field.namedType(types.Embedding1536)`. Add the same pack to `prisma.config.ts`. It goes inside `ormConfig({ ... })`, beside `contract`, and the config imports the pack's `/control` export while the contract file imports its `/pack` export: @@ -296,9 +303,9 @@ export default definePrismaConfig({ The contract file describes structure. Three rules keep it usable: -- Do not read `process.env`, the current time, or random values into the contract. `prisma contract emit` writes the contract to JSON in a fixed key order and hashes it. A contract that changes per machine or per run breaks that hash and the checks that rely on it. +- Do not read `process.env`, the current time, or random values into the contract. Prisma ORM compares a hash of the contract across runs, and a contract that changes every run fails that comparison. - Keep field values plain: strings, numbers, booleans, and the builder's own objects. Functions, class instances, and `Date` objects do not serialize. -- Keep the file free of side effects. `prisma contract emit` imports the file and reads its `default` or `contract` export, and nothing else. It runs the file, but it does not type-check it. Type errors show in your editor and when you run `tsc`. +- Keep the file free of side effects. `prisma contract emit` runs the file, but it does not type-check it. Type errors show in your editor and when you run `tsc`. Configuration that legitimately varies per environment, such as the database URL, belongs in `prisma.config.ts`, not in the contract. @@ -310,7 +317,7 @@ No command converts a `.prisma` contract into TypeScript. To move an existing pr ## Prompt your coding agent -Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent; in an existing project, run `npx prisma skills sync`. The `prisma-8` skill covers TypeScript authoring. Ask your agent to: +Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent. In an existing project, run `npx prisma skills sync`. The `prisma-8` skill covers TypeScript authoring. Ask your agent to: - "Convert this contract.prisma to the TypeScript schema builder." - "Using the prisma-8 skill, add a unique constraint to the email field in our TypeScript schema." diff --git a/docs/orm-docs-audit/c21-contract-authoring/capabilities.round2.md b/docs/orm-docs-audit/c21-contract-authoring/capabilities.round2.md new file mode 100644 index 00000000000..d91a259cebc --- /dev/null +++ b/docs/orm-docs-audit/c21-contract-authoring/capabilities.round2.md @@ -0,0 +1,112 @@ +I read the page once, top to bottom, as someone who used Prisma 6 for two years and has never seen this version. + +## Sentences I could not restate after one reading + +**1. Line 9 (the opening paragraph).** Six ideas in one block, and it names four things I have never heard of before it explains any of them: + +> "[`npx prisma contract emit`](/cli/contract-emit) writes `contract.json` and `contract.d.ts` beside your [contract](/orm/contract-authoring/the-data-contract) source file." + +What stopped me: "contract" — I do not know what that is. In the version I know, I write `schema.prisma` and run `prisma generate`. Is the contract the schema renamed? Is it something extra? The link tells me to go read another page, which means this page does not stand on its own. Plainest wording I would have understood: one sentence saying "A contract is the new name for your schema file" or "A contract is X, and it replaces Y." + +**2. Line 13.** + +> "Three things each say which features they support: your database (PostgreSQL, SQLite, or MongoDB), its driver package, and any extension packages listed in `prisma.config.ts`." + +What stopped me: "its driver package." I do not know what package that is or where I chose it. The config example below imports `@prisma/orm-postgres/config` — is that the driver package? The page never says. Plainest wording: "your driver package (for example `@prisma/orm-postgres`)". + +**3. Line 13, second half.** + +> "collects all of those answers into `contract.json`, grouped by prefix" + +What stopped me: "grouped by prefix" does not match what I then see. The JSON shows a key `"pgvector.cosine"` nested inside `"postgres"`. The table later calls the same thing `postgres.pgvector.cosine`. Nobody tells me that the written form of a key joins the group and the key with a dot. I had to work that out by comparing two code blocks. Plainest wording: "Keys are written group-first: the `cosine` key inside `postgres` is written `postgres.pgvector.cosine`." + +**4. Lines 49-51 (the error text).** + +> "target "sqlite" does not support scalar lists (the adapter does not report the "scalarList" capability)." + +Two words stopped me. "target" appears nowhere else on the page — everywhere else the page says "database." Is a target a thing I set somewhere? "adapter" also appears nowhere else. Is an adapter the driver package from line 13, or a third thing? I now have three possible names for what might be one thing: driver package, adapter, target. + +**5. Line 63.** + +> "The error is raised when your code builds the query, so a test that builds the query catches it." + +What stopped me: I do not know what "builds the query" means as code I write. In the version I know, `db.user.findMany()` returns a promise and nothing happens until I await it. Does "builds" mean the call itself throws before I await? Does it mean there is a separate build step? The sentence tells me a test catches it but not what the test looks like. + +**6. Line 85.** + +> "[`db verify`](/cli/db-verify) compares the database against your contract, using the recorded hashes described in [contract.json and contract.d.ts](/orm/contract-authoring/the-contract-artifact#the-content-hashes)." + +What stopped me: "recorded hashes." I did not ask how the comparison is implemented, and this clause does not change anything I would do. Cut it. + +**7. Line 81.** + +> "MongoDB has no keys at all, so none of these checks apply to it." + +This contradicts the rule the page just taught me. The rule was: a missing key means the feature fails early. If MongoDB has no keys, then by that rule every feature should fail on MongoDB. Instead the page says the checks are skipped. I cannot tell from one reading what actually happens if I call `lateralJoin()` on MongoDB. + +## Words and phrases I had to guess at + +- **"contract"** — guessed: the new name for `schema.prisma`. Not confirmed anywhere on this page. +- **"vector distance operations"** (line 9) — guessed: similarity search for embeddings. Probably right, but it is the third item in a list where the first two are plain SQL, so it reads like I should already know it. +- **"driver package"** (line 13) — guessed: `@prisma/orm-postgres`. +- **"adapter"** (line 50) — guessed: the same thing as the driver package. +- **"target"** (line 49) — guessed: the database engine, set somewhere in the contract file. +- **`/control`** in `import pgvector from "@prisma/orm-extension-pgvector/control"` (line 34) — guessed: a required subpath for config-time imports. I would have typed the bare package name and got it wrong. +- **`defineConfig as ormConfig`** (line 35) — guessed: a rename to avoid clashing with the outer `definePrismaConfig`. The rename is never mentioned, so it looks like `ormConfig` is a real exported name. +- **`db`** (line 65) — guessed: my instantiated client. Never introduced on this page. +- **`?.`** in `db.contract.capabilities.sql?.lateral` — guessed: the whole `sql` group can be absent, not just the key. The page never says a group may be missing entirely. + +## "So what do I actually type?" + +**1. Line 54.** + +> "remove the list field, or point the contract at a database that supports list fields, such as PostgreSQL." + +How do I point the contract at a different database? Which file, which line, which field? The page shows me `contract: "./src/prisma/contract.prisma"` in the config but never shows the place where the database is chosen. This is the one action the page asks me to take and it does not show the edit. + +**2. Line 30.** + +> "then list it in `prisma.config.ts`" + +The example below shows a complete config with `extensions: [pgvector]`. But my existing config almost certainly does not have an `extensions` key yet, and I have to reverse-engineer the diff from a full file. Show the two added lines, not a whole file. + +**3. Line 63.** + +> "Rewrite the query without it" + +Rewrite it as what? For `lateralJoin()` and `distinctOn()` there is presumably a fallback shape. The page does not show one, so the advice is "do something else" with no second option. + +**4. Line 63.** + +> "a test that builds the query catches it" + +No test is shown. I would want three lines of code. + +**5. Line 79.** + +> "Open your project's `contract.json` to see every key your setup supports." + +No path given here. It is on line 15 in a code block title (`src/prisma/contract.json`), thirty lines earlier, and line 9 says "beside your contract source file," which is a different answer. Say the path once, here. + +## Places the page explains internals when I wanted instructions + +- **Line 44:** "Prisma ORM checks the keys twice, both times before any SQL reaches the database." The "twice" framing is about the tool's internal sequencing. What I need is: here are two errors you may see, here is what each means. The page does then give exactly that, so the framing sentence is the only part that is about the machine rather than about me. +- **Line 85, the whole section.** "The keys say what your installed software supports. They are recorded when you run `npx prisma contract emit`, not read from the live database, so the same contract behaves the same way in every environment." This is an explanation of a design decision. The only part I can act on is: if you want to check the real database, run `db verify`. The "recorded hashes" clause is pure internals. +- **Line 30:** "Keys under `sql` are shared across SQL databases. Keys under `postgres` belong to PostgreSQL only." This is taxonomy, not action. It is short enough to survive, but it is the answer to "how is the file organised," which I did not ask. + +## Could I do what the page is for, after one reading? + +No, because I cannot tell what the page is for. + +Line 9 tells me "you never edit it." So the page is not a task. It appears to be a reference for two error messages. If that is the purpose, it is buried: the errors are in the middle, under a heading called "What the `capabilities` section controls," which does not say "errors." + +What I would still not know after one reading: + +1. **What a contract is.** Everything on this page hangs off that word and the page does not define it. I would have to leave and read another page before this one makes sense. +2. **How to change the database my contract targets** — the fix the page recommends on line 54. +3. **Whether "driver package," "adapter," and "target" are one thing or three.** +4. **What happens on MongoDB.** The page says checks do not apply, but the stated rule says they should all fail. +5. **What a query "building" is in code**, and therefore what the test on line 63 looks like. +6. **Where to find the full key list** for a database I am considering but have not installed. The page says to open my own `contract.json`, which only tells me about the setup I already have. If I am deciding whether to switch from SQLite to PostgreSQL, that does not help. + +One thing that did work on first reading: the two error blocks on lines 48-52 and 58-61 are literal text I can paste into a search box, and the sentence "That message means the method is not available on the database you are connected to" is a plain restatement I understood immediately. That sentence is the model for how the rest of the page should read. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/page-conventions.md b/docs/orm-docs-audit/c21-contract-authoring/page-conventions.md index f542f5c9032..5f1ae1caa82 100644 --- a/docs/orm-docs-audit/c21-contract-authoring/page-conventions.md +++ b/docs/orm-docs-audit/c21-contract-authoring/page-conventions.md @@ -8,7 +8,7 @@ - "Hashes" and the marker: "when you run `prisma db sign`, Prisma ORM records which contract the database matches in a small table it creates in the database; the client checks that record before its first query" (verify against source; use the wording the check confirms). Do not write "signature" or "marker" without that definition on the same page. - Banned on these pages in addition to the list: gate / gated / gates (write "checks", "requires", "is only available when"); capability in prose (write "a database feature the contract needs", "what your database supports"; the code identifier `capabilities` for the section of `contract.json`, and the key names such as `sql.lateral`, are code and stay); codec (write "how the value is stored and read", or name the type id such as `pg/text@1`); canonical / canonicalizes (write "in a fixed key order"); deterministic (write "the same source always produces the same files"); pure (as in "the contract file must stay pure": write "must not read the environment, the clock, or random values"); artifact when the two files can be named instead; "source of truth" (write "the one file the config points at"); "escape hatch"; "purpose-built"; "first-class"; "wiring" / "wired"; "hangs off"; "consult"; "authoritative"; "physical name" (write "column name in the database"); "value object" only with its definition on the same page ("a `type` block: a structured value stored inside its parent row, with no table of its own"). - Type ids such as `pg/text@1`: "the PostgreSQL type plus a version, always `@1` today". Do not explain the format twice on one page. -- "Prompt your coding agent" sections: keep, but the first sentence reads "Projects created with `npm create prisma@latest` include the Prisma ORM skills for your coding agent; in an existing project, run `npx prisma skills sync`." Keep the example prompts. +- "Prompt your coding agent" sections: keep, but the first sentence reads "Projects created with `npm create prisma@latest` include the Prisma ORM skills for your coding agent. In an existing project, run `npx prisma skills sync`." Keep the example prompts. - Scaffold command: `npm create prisma@latest -- my-app`. Emit command in prose and code: `npx prisma contract emit` (not `prisma@latest`, once the page has shown installation; the pages assume an existing project). - The source of truth is prisma/orm at 8.0.0-rc.10 (`cfccb09be2`), checked out at `wip/prisma-src/`. Where a page names an rc.9 behaviour that rc.10 changed, rc.10 wins. - The CLI pages under `/cli/` (`contract-emit`, `contract-infer`, `db-sign`, `db-verify`, `db-init`, `migration-plan`, `configuration`) are out of scope; link to them, do not restate their flags. diff --git a/docs/orm-docs-audit/c21-contract-authoring/psl-syntax.round2.md b/docs/orm-docs-audit/c21-contract-authoring/psl-syntax.round2.md new file mode 100644 index 00000000000..f8618cc2f7f --- /dev/null +++ b/docs/orm-docs-audit/c21-contract-authoring/psl-syntax.round2.md @@ -0,0 +1,96 @@ +# Reader report: `orm/contract-authoring/psl-syntax.mdx` + +Background I brought: two years of Prisma 7. I have never seen Prisma 8. + +## Sentences I could not restate after one reading + +**1.** "base models and variants: one model reusing another model's fields and rows." (line 16) + +What stopped me: "and rows." Fields I understand. A model reusing another model's *rows* means nothing to me at this point — models don't have rows, tables do. I only half-understood it four sections later. + +Plainer: "base models and variants: several models that share one table, told apart by the value of one field." + +**2.** "`@updatedAt` no longer exists. Write `updatedAt temporal.updatedAt()`, with the call where the type would go." (line 153) + +What stopped me: `temporal`. I don't know what it is, where it comes from, or whether I have to declare or import it. And "with the call where the type would go" is a clue that the syntax is strange, but it's a description of a code sample instead of a code sample. Every other feature on this page gets a code block; this one gets a sentence. + +Plainer: show it. +```prisma +model Post { + updatedAt temporal.updatedAt() +} +``` +And say in one clause what `temporal` is (built in? a namespace I get for free?). + +**3.** "In Prisma ORM 8 the native type is the field's type, so `String @db.VarChar(35)` from Prisma ORM 7 becomes `VarChar(35)`. The `@db.` attributes are gone." (line 197) + +What stopped me: the examples above this use `String`, `DateTime`, and `Uuid`. So sometimes the type is a native PostgreSQL type and sometimes it is a Prisma scalar. The page never says both sets exist side by side, never says which is which, and never links to a list of either. After one reading I do not know whether `String` is still legal, or whether it is a preferred spelling of `text`. + +**4.** "Prisma ORM matches the two list fields to the model that holds a foreign key to each side and whose primary key is exactly those two foreign keys" (line 255) + +What stopped me: three conditions welded into one sentence, and it's written from the tool's point of view ("Prisma ORM matches"), not mine. I had to read it twice to work out that it's telling me the rules my join model must satisfy. + +Plainer: "For Prisma ORM to connect `Post.tags` and `Tag.posts`, the join model must (a) hold a foreign key to each side, and (b) use exactly those two foreign keys as its primary key." + +And then: what happens if I get it wrong? Silent? An error at `emit`? The page doesn't say. + +**5.** "`@@type("pg/text@1")` stores the values as `text` on PostgreSQL. That name is the PostgreSQL type plus a version, always `@1` today." (line 212) + +What stopped me: "always `@1` today" tells me a version exists but gives me no reason to care and no rule for what to write when it isn't 1. If it is always `@1`, why am I typing it? I could not restate the purpose of the version because the page doesn't give one. + +**6.** "If your database already has a PostgreSQL `enum` type, declare it in a `native_enum` block and type the field `pg.enum(Role)`." (line 216) + +What stopped me: two new pieces of syntax (`native_enum`, `pg.enum(...)`) described in prose only, and `pg.` appears here for the first and only time with no explanation. I cannot write this after reading it. + +**7.** "With its own `@@map`, as `Bug` has here, they are in a table of their own that shares the base model's primary key. Without one, they are nullable columns in the base table." (line 307) + +What stopped me: it's not hard, but it's backwards for a reader. It tells me the consequence of a choice before telling me a choice exists. I had to reconstruct: "omitting `@@map` on a variant is a real design decision, not a default." + +## Words and phrases I had to guess + +- **"storage details"** (lines 13, 187). Guessed: the database column type and its size, e.g. `varchar(35)`. Confirmed later by example, but I guessed first. +- **`Uuid`** (line 45). Guessed: the PostgreSQL `uuid` column type, now spelled as a Prisma type. Not stated until line 197. +- **"named types"** vs **"extension types"** vs **"value objects"** (lines 13-17). Guessed all three from the one-line glosses; the value-object gloss ("a structured value stored inside its parent row, with no table of its own") is the only one that actually landed. +- **`pg/` and `mongo/` prefixes** in `@@type` (lines 38, 78). Guessed: the database family. Never stated. +- **`@1`** (line 38). Guessed: a format version for how values are encoded. Never stated. +- **`/control`** in `import pgvector from "@prisma/orm-extension-pgvector/control"` (line 317). Guessed: a subpath export meant for config files, as opposed to a runtime one. Never mentioned. +- **`public`** in `db.orm.public.Task.variant('Bug')` (line 305). Guessed: the PostgreSQL schema name. Nothing on this page introduces schemas, so it reads like a typo the first time. +- **`@@discriminator(type)`** (line 292). Guessed: `type` is the unquoted field name. Jarring, because `@@map("task")` two lines below quotes its argument and `@@base(Task, "bug")` mixes both styles in one call. I guessed the rule is "identifiers bare, strings quoted" and moved on. +- **`temporal`** (line 153). Guessed: a built-in namespace of time-related function types. Pure guess. +- **"emit"** (line 9). Guessed: generate. Fine, but it is a new verb replacing `generate`, and the page doesn't say that. + +## Places I asked "so what do I actually type?" and got no answer + +1. **The connection URL.** "The connection URL goes in this file too, in a `db` key beside `contract`." (line 140) The config example directly above it is complete except for the one thing every project needs. Adding two lines to the sample would cost nothing; instead I'm sent to the migration page. +2. **`updatedAt`.** Line 153, above. Described, never shown. +3. **`native_enum` and `pg.enum(Role)`.** Line 216. Described, never shown. +4. **The type list.** I have `String`, `DateTime`, `Uuid`, `VarChar(n)`, `ObjectId`, `Timestamptz`. What about integers, booleans, decimals, JSON, arrays of scalars, `Bytes`? No list, no link to one. For a page whose job is "here is how you write the schema", this is the biggest hole. +5. **Value objects, beyond the one shape.** Can I write `addresses Address[]`? Can a `type` contain another `type`? Can a `type` field have `@map` or a default? Not answered. +6. **Extension type names.** "The `pgvector` part … is a fixed name the pack declares." (line 341) Good to know it isn't the import name — but for any pack other than pgvector, how do I find out what its fixed name is? Its README? `contract emit` output? Not said. +7. **Enum defaults.** `priority Priority @default(Low)` (line 58) uses the member name while the member stores `"low"`. I guessed that's always right, but the Enums section never mentions defaults. +8. **The variant's missing `@id`.** `model Bug` (line 296) has no `@id` and no `id` field, unlike every other model on the page. I assume `@@base` supplies it. The page doesn't confirm it, so I don't know whether I may add one, must not add one, or should. +9. **Creating `db`.** "`db`, the client you create in `src/prisma/db.ts`, reads both." (line 359) I create it — how? That's the only sentence on the page that tells me to write a file and it has no link and no snippet. +10. **`Json`.** Value objects are stored "in a single `jsonb` column" (line 236). So is there still a plain `Json` field type for data I don't want to give a shape to? Unanswered. + +## Places the page explains internals when I only wanted to know what to do + +- **Line 236:** "On PostgreSQL a value object field is stored in a single `jsonb` column. On MongoDB it is an embedded document." I can live with this — it does affect what I can query — but it arrives before I've been told anything about *using* value objects. +- **Line 255:** the join-model matching rule, written as what the tool does rather than what my schema must look like. See above. +- **Line 341:** "It is not the name you gave the import in `prisma.config.ts`, so renaming that import does not change what you write here." This is a paragraph about a mistake I hadn't made yet, explaining a resolution mechanism, in place of telling me where to find the name I need. +- **Line 110:** "The first line, `// use prisma-8`, tells the Prisma editor extension that the file is a Prisma ORM 8 contract. It is not required: the contract works without it. `prisma orm init` writes it, so keep it." Three sentences that cancel each other out — it does nothing, it isn't required, keep it anyway. One sentence would do: "Leave the `// use prisma-8` comment in place; the editor extension uses it." +- **Line 151:** the split between database defaults and Prisma-generated defaults is genuinely useful — that one earns its place. Noting it so the list above isn't read as "cut all mechanism." + +## Could I do what the page is for, after one reading? + +Partly. I could write a plausible PostgreSQL contract with models, fields, `@id`, `@unique`, `@@map`, relations, an enum, and a value object, and I could point `prisma.config.ts` at it. The complete example at the top is the most useful thing on the page and carries most of that. + +What I would still not know: + +- **Which field types exist.** This is the failure that matters. I can copy `String`, `Uuid`, `DateTime`, `ObjectId` from the example and I can write `VarChar(35)`. For an integer, a boolean, a decimal, a date-only column, or a string array, I would be guessing, and the page gives me nowhere to look. +- **How to connect to my database**, because the `db` key is named but never shown. +- **How to write `updatedAt`**, `native_enum`, or `pg.enum(...)` — three features the page tells me about but does not show. +- **How to create `db`**, the client I'm told in the last section that I create. +- **Whether my many-to-many join model is correct**, or what I'll see if it isn't. +- **What `public` is** in `db.orm.public.Task`, and therefore whether schemas are something I need to declare in the contract. + +The page reads like it was written for someone who already knows Prisma 8 and needs the deltas listed. As someone arriving from 7, I got a good tour of the five new things and lost the ordinary ground under them — the type list, the connection string, and the client file. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/the-contract-artifact.round2.md b/docs/orm-docs-audit/c21-contract-authoring/the-contract-artifact.round2.md new file mode 100644 index 00000000000..44282190921 --- /dev/null +++ b/docs/orm-docs-audit/c21-contract-authoring/the-contract-artifact.round2.md @@ -0,0 +1,88 @@ +Read once, top to bottom, as someone coming from Prisma 6. + +## Sentences I could not restate after one reading + +**1. Line 13, the `contract.json` row** + +> "The machine-readable contract: models, how they are stored, which database features they need, and the hashes" +> Read by: "`db`, `prisma db sign`, `prisma db verify`, and `prisma migration plan`" + +Three of those four are commands I type. `db` is not. I read the whole row assuming `db` was a fourth command before line 16 told me it is a client object. Put line 16 before the table, or write "your `db` client". + +**2. Line 36 — one paragraph, six separate facts** + +> "The hashes are how Prisma ORM connects the contract to a live database. `npx prisma db sign` checks that the database satisfies the contract, then records `storageHash` and `profileHash` in a table it creates called `prisma_contract.marker`. You never write to that table yourself. Run `db sign` when the live database already matches the contract, for example after `prisma contract infer` on an existing database, or after a deployment applied the changes. `npx prisma db verify` compares the contract you hold against that record and against the live schema, and fails when they disagree." + +What stopped me: two new commands, a new table, a third command (`contract infer`) I have never heard of, and a conditional rule about when to run `db sign`, all in one block. I also could not work out whether `db sign` is something I run routinely or only in the two listed situations. Plainer: split it. "`prisma db sign` records the hashes in the database. Run it after X or after Y. `prisma db verify` compares them later and fails if they differ." + +**3. Line 40** + +> "Each model's own `storage` block joins the two." + +"Joins" how? I had to scroll to the JSON and work out from `"storage": { "fields": { "email": { "column": "email" } } }` that it means "maps each field to its column". Say that. + +**4. Line 107** + +> "**`roots`**: the table name of every model, so the query APIs know what to call each one." + +This contradicts the JSON directly above it. In the JSON, `roots.user` has value `{ "model": "User", "namespace": "public" }` — the value is the *model* name, and `user` is a key. Is `user` the table name or a lowercased alias? The `storage` section separately says `"table": "user"`. After one reading I do not know whether `roots` holds table names, lowercased model names, or something else. + +**5. Line 110** + +> "They are not column defaults in the database, so a write that does not go through Prisma ORM gets whatever the column itself defaults to." + +I can restate this, but it is the single most surprising behaviour change from Prisma 6 on the page and it is the last clause of a bullet. In Prisma 6 I thought of `@default(uuid())` as belonging to the schema. "If something else writes to this table, it will not get a UUID" deserves its own sentence, or a warning. + +**6. Line 137** + +> "Because the hash string is part of the type, files from two different runs of `npx prisma contract emit` do not type-check against each other." + +Two files from two runs — which two? I eventually decided it means "a `contract.json` from one run and a `contract.d.ts` from another". The next sentence says that, so the first one is a harder restatement of the second. Cut it. + +**7. Line 155** + +> "Before its first query, `db` reads the record `prisma db sign` wrote in the database and compares it with the contract's hashes. If they do not match, it logs a warning and runs the query anyway." + +Not the wording, the gap: what if `db sign` was never run and there is no record? Does it warn on every app start? I do not know after one reading. + +## Words and phrases I had to guess + +- **"contract"** — the whole page rests on it and never defines it. I guessed: the new name for `schema.prisma`, plus type information. The link on line 9 goes elsewhere. +- **"artifact"** (`metaTitle`, the URL, the heading "How the application consumes the artifacts") — guessed: "generated file". The word never appears in the body text as a defined thing. +- **"codecId"**, `"pg/text@1"` — line 108 says it is "the PostgreSQL type plus a version". So what is a codec? I guessed: the thing that converts a database value to a JavaScript value. Never stated. +- **"namespace"** — I guessed it means "PostgreSQL schema", which line 40 half-confirms, but then "On MongoDB it describes collections" leaves what a namespace is there unsaid. +- **"extension pack"** (lines 111, 112) — guessed: an npm package adding types and database features. "composed in your project" — I guessed "installed and listed in config". +- **"`type` block in your contract"** (line 120) — guessed: a reusable struct in PSL, like `type Address { ... }`. Never shown on this page, yet `AddressOutput` is the example. +- **`"returning": true`** (line 99) — guessed: PostgreSQL `RETURNING`. +- **"skills"** (line 168) — guessed: prompt files for a coding agent. +- **"`profileHash`: The database this contract targets, and whether it is SQL or MongoDB"** — I guessed "profile" is just a name for that pair. Fine, but the word does nothing. +- **"`prisma_contract.marker`"** — guessed: a table called `marker` in a schema called `prisma_contract`. + +## "So what do I actually type?" and the page does not say + +- **Where does `capabilities` come from?** Line 111: "from any extension packs named in your config". Which config file? What does the entry look like? No file name, no example, no link. +- **Do I run `prisma db sign` on a brand-new project?** Line 36 lists two situations (after `contract infer`, after a deploy). Line 176 says to use `prisma db init` for a fresh database. Whether `db init` signs for me is not stated. +- **Where do I put `prisma db verify`?** Line 155 says run it "when you need the check to fail instead" — instead of a startup warning. In CI? A start script? The Version Control section gives a CI snippet for `contract emit` but not for `verify`. +- **What do I do with `StorageHash`, `ProfileHash`, `FieldOutputTypes`, `FieldInputTypes`?** They are listed as exports with no example of importing or using one. `FieldOutputTypes` gets "keyed by namespace and model name" and nothing else. +- **How do I fix a mismatch?** If `db` warns at startup that hashes differ, or `db verify` fails, the page never says which command repairs it. + +## Places explaining how it works inside when I wanted to know what to do + +- **"Inside `contract.json`", lines 38–112.** Sixty lines of JSON plus seven bullets explaining every section of a file I was told on line 18 never to edit. Nothing in this section tells me to do anything. The parts I would actually use — that `db.sql.public.user` is keyed off `roots` and `db.orm.public.User` off the model name — are buried in the `roots` bullet. +- **"The content hashes", lines 26–36.** The section opens with "You never compute or compare them yourself" and then gives me a three-row table of hash names and what each covers. If I never touch them, the table is internals. The useful part is the one paragraph about `db sign` and `db verify`. +- **Line 137:** "`CodecTypes`, `StorageHashBase`, and `ProfileHashBase` are helper types the file declares for itself. You never write them." Telling me the names of three things I must never use is internals. +- **Line 155:** "The import path `./contract.d` is written that way on purpose. It is not a typo, and TypeScript resolves it to `contract.d.ts`." This one earns its place — I would have "fixed" it. + +## Could I do what the page is for after one reading? + +The page is explaining two generated files, not walking me through a task, so partly yes. I came away knowing: I edit the source, not these two files; I run `npx prisma contract emit` after every source change; I commit both files; and I can add the two-line CI check on lines 162–163. That is real and I could act on it today. + +What I still would not know: + +- What a "contract" is, in one sentence. The page assumes it. +- The order of operations on a real project. `contract emit`, `db init`, `db sign`, `db verify`, `contract infer`, `migration plan` — six commands are named and the page never places them on a timeline. +- Whether my Prisma 6 habits still apply. `PrismaClient` is now `db`; `@default(uuid())` is now applied by the client, not the column. Both changes are mentioned in passing. Nothing else is mapped. +- What a codec, an extension pack, or a `type` block is, or where the config file that names extension packs lives. +- What to do when the hash check fails. + +Biggest single fix: define "contract" in the first paragraph, and move the six commands into one short ordered list, before the tour of the JSON. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/the-data-contract.round2.md b/docs/orm-docs-audit/c21-contract-authoring/the-data-contract.round2.md new file mode 100644 index 00000000000..e16b558f6a1 --- /dev/null +++ b/docs/orm-docs-audit/c21-contract-authoring/the-data-contract.round2.md @@ -0,0 +1,76 @@ +Read once, as a Prisma 7 user. Report below. + +## Sentences I could not restate after one reading + +**Line 47** — "The contract also identifies its own exact version. `prisma contract emit` computes hashes from the contract and writes them into `contract.json`. [`prisma db sign`](/cli/db-sign) checks that your database satisfies the contract, then records those hashes in the `prisma_contract.marker` table, which it creates in your database. Run it once the database matches the contract, after you have applied your migrations." + +Four separate ideas in a row, and two things stopped me. "identifies its own exact version" — version of what? There is no version number here, only hashes, so the word "version" points at something I cannot find. And "Run it once the database matches the contract" is ambiguous: "once" could mean "one single time" or "as soon as". I read it both ways and could not settle it. Plainest wording I would have understood: "`prisma contract emit` writes a hash of your contract into `contract.json`. After you apply your migrations, run `prisma db sign`. It checks the database, then stores that hash in a table it creates called `marker` in the `prisma_contract` schema. Run it again every time the contract changes." + +**Line 47** — "Your app then logs a warning when the database stops matching." + +Logs where? At startup, on the first query, on every query? I cannot picture when I would see this, so I cannot tell whether it is useful to me or noise. + +**Line 45** — "Prisma ORM keeps that description in the open." + +"in the open" is a metaphor doing the work of the real claim. I think it means "in plain files you can read", which the next sentence says anyway. Plainest wording: delete the sentence. + +**Line 45** — "so you can read them in a code review, hash them, and hand them to tools and coding agents." + +"hash them" stopped me. Why would I hash a file, and what would I do with the hash? The page already says Prisma computes hashes itself (line 47), so this reads like a second, unrelated hashing that is never explained. + +**Line 84** — "Do not keep a `contract.prisma` and a `contract.ts` in the same project, so the two can never disagree." + +The "so" clause makes it sound like keeping both causes the guarantee. I had to re-read to work out the reason is "otherwise they could disagree". Plainest wording: "Keep only one. If you had both, they could describe different things." + +**Line 93** — "[`type` blocks](/…), each a structured value stored inside its parent row with no table of its own" + +I do not know what this actually becomes in my Postgres database. A `jsonb` column? A composite type? "Structured value stored inside its parent row" tells me the idea but not the thing. + +**Line 39** — "In Prisma ORM, the **contract** is what you write in your code, and the **schema** is your database's actual structure." + +I could restate it, but it contradicts the rest of the page as I read it. The file is called `contract.prisma`, the language is "PSL, the Prisma schema language" (line 11), and the card on line 118 says "Write the contract as a Prisma schema file." So "schema" is used for my authored file three times after the note tells me it means the database. That left me unsure which meaning applies where. + +## Words and phrases I had to guess + +- **"the data contract"** (line 9) — guessed: just the new name for `schema.prisma`. The page confirms this later, but the title made me expect a new concept. +- **"extension packages"** (lines 64, 94) — guessed: npm packages that add extra field types, since pgvector is the example. Never defined, and the only link is to a PSL anchor. +- **"pgvector"** and **"`Vector`"** (lines 45, 94) — guessed: a Postgres extension for vector columns. Fine if you know it; nothing on the page says what it is. +- **"`prisma_contract.marker` table"** (line 47) — guessed: a table named `marker` in a schema named `prisma_contract`. The dotted name is never explained, and it matters because it is a table appearing in my database. +- **"the migration tooling"** (line 76) — guessed: some `prisma migrate` command. The page never names one. +- **"named types"** (line 92) — guessed: aliases for scalar types. No idea, really. +- **"the typed contract builder"** (line 125) vs **"TypeScript builder"** (line 82) — guessed these are the same thing. +- **"PSL"** — expanded once on line 11 as "the Prisma schema language", then used as an unexpanded acronym. I coped, but it is the only expansion. + +## "So what do I actually type?" — unanswered + +1. **Migrations.** The page tells me three times that migrations are planned from the contract (lines 33, 47, 76) and tells me to run `db sign` "after you have applied your migrations". It never gives the migration command. This is the single biggest gap: the page describes the workflow but omits its middle step. +2. **The everyday loop.** I wanted a numbered list: edit contract → `prisma contract emit` → migrate (?) → `prisma db sign`. I had to assemble that from lines 47, 66, and 76, and I am still not sure `db sign` is needed after every change or only the first time. +3. **Starting a project.** Line 64 says `prisma orm init` writes the config, the contract, and `db.ts`. Line 80 says `npm create prisma@latest` scaffolds PSL, and line 101 says projects created that way get the skills. Two different starting commands, never compared. I do not know which one to run. +4. **Using `db`.** Line 72 says "`db` is the client you create once in `src/prisma/db.ts`" and then three bullets about what `db` does internally. It never shows the import line or a single query. After reading a page about the thing that types my queries, I cannot type one. +5. **Anything but PostgreSQL.** The only database package named is `@prisma/orm-postgres` (lines 55, 64). I use MySQL. The page does not say what I import, and does not say Postgres is just the example. +6. **TypeScript authoring.** Line 64 says point `contract` at `./src/prisma/contract.ts`, and line 82 says use the builder. There is no fragment of `contract.ts` anywhere, so I cannot tell what the mode even looks like before clicking away. +7. **`// use prisma-8` on the TypeScript side.** Line 31 says "The first line of every `.prisma` contract" must be that comment. Does `contract.ts` need something equivalent? Unanswered. +8. **The two config functions.** Line 54-55 imports both `definePrismaConfig` and `ormConfig` from two packages. I would not have guessed that shape, and nothing says why there are two or when the inner one changes. +9. **`prisma db sign` cadence.** After I change the contract and migrate, do I re-sign? Line 47's phrasing does not settle it. + +## Where it explains the internals when I wanted instructions + +- **Line 72-76**, the three bullets under "From there, the rest of the toolchain takes over." All three describe what `db` and the tooling do to the files: "`db` loads `contract.d.ts`", "`db` reads `contract.json` and compares its hashes with the record `prisma db sign` wrote". None of it changes anything I type. This is the section where I expected to learn how to use `db`. +- **Line 45**, the whole "Why a contract" opening. "Prisma ORM 7 compiles your `schema.prisma` into generated client code. The description of your data is in there, but only the generated client can read it." This is a design rationale. I came to find out how to author the file. +- **Line 31**, the language-server detail: "the Prisma language server only reports problems, autocompletes, and formats files that start with it". The instruction is "put `// use prisma-8` on line one." The mechanism behind editor support is more than I needed, and the `prisma-next` history sentence is about a release candidate I never used. +- **Line 47**, hash computation and the marker table. What I need is "run `prisma db sign` after migrating". The hashing mechanism is the answer to a question I did not ask. + +## Could I do what the page is for, after one reading? + +Partly. I could copy the `contract.prisma` example, copy `prisma.config.ts`, and run `npx prisma contract emit`. That is real. + +What I would still not know: + +- How to change my database after I change the contract. No migration command appears on the page. +- Whether to start with `prisma orm init` or `npm create prisma@latest`. +- What to import or query with. I never see `db` used. +- What to do on MySQL or SQLite. +- Whether `prisma db sign` is a one-time setup step or part of every change. +- What `contract.ts` looks like, so I cannot decide between the two authoring modes the page spends a section comparing. + +The page succeeds at telling me what the contract *is*. It does not get me through one full change to my data model. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/typescript-schema-builder.round2.md b/docs/orm-docs-audit/c21-contract-authoring/typescript-schema-builder.round2.md new file mode 100644 index 00000000000..e898d7759bb --- /dev/null +++ b/docs/orm-docs-audit/c21-contract-authoring/typescript-schema-builder.round2.md @@ -0,0 +1,83 @@ +Read once, top to bottom, as someone coming from Prisma 6. + +## Sentences I could not restate after one reading + +**1.** "Prisma ORM 8 replaces `schema.prisma` with [a contract](/orm/contract-authoring/the-data-contract)." +Stopped by: "a contract" is a new word and the page never says what it is. I only learn it is a file that emits two other files. Plainer: "Instead of `schema.prisma`, Prisma 8 reads a single file called the contract, which describes your models." + +**2.** "[`npx prisma contract emit`](/cli/contract-emit) writes `contract.json` and `contract.d.ts` beside the source, and both forms produce those same two files." +Stopped by: "beside the source" — beside which source? I guessed `src/prisma/`. Also three ideas in one sentence. Plainer: "Run `npx prisma contract emit`. It writes `contract.json` and `contract.d.ts` into the same folder as your contract file." + +**3.** "Its one argument gives you `field`, `model`, `rel`, and `type`. Those four come from the argument, not from imports." +This contradicts the example directly above it. The example destructures only three — `({ field, model, type })` — and imports `rel` from `@prisma/orm-postgres/contract-builder`. So which is right for `rel`? I could not tell, and this is the kind of thing that makes the file not compile. Same for `enumType` and `member`, which are imported and never mentioned in this list. + +**4.** "`constraints.index` takes one column for a plain index and a list for a multi-column index." +The example right above passes a list in both cases: `constraints.index([cols.userId])`. So do I type `cols.userId` or `[cols.userId]` for one column? The sentence and the code disagree. + +**5.** "`codecId` is the type id, which is the PostgreSQL type plus a version, always `@1` today." +"pg/text@1" is not "the PostgreSQL type plus a version" — there is a `pg/` prefix the sentence does not account for. And "codec" is never defined. Plainer: "`codecId` is a string of the form `pg/@1`." + +**6.** "A primary key made of two fields is a model attribute rather than storage mapping." +Stopped by: this is a category distinction internal to Prisma. As a reader I do not know what "model attribute" means or why I should care. Plainer: "For a primary key made of two fields, use `.attributes(...)` instead of `.sql(...)`." + +**7.** "`.relations(...)`, `.attributes(...)`, and `.sql(...)` are independent." +"Independent" of what? Can I call them in any order? Can I call `.sql(...)` twice? Can I chain all three? The example only ever shows `.relations().sql()`. Plainer: "You can chain any of these three, in any order, and you can skip any of them." + +**8.** "`prisma contract emit` writes the contract to JSON in a fixed key order and hashes it. A contract that changes per machine or per run breaks that hash and the checks that rely on it." +Two problems. "the checks that rely on it" — which checks? Never named, so I cannot judge how bad this is. And the hashing detail is machinery I did not ask about. Plainer: "Do not read `process.env`, the clock, or random values in the contract file. Prisma compares a hash of the contract across runs, and a contract that changes every run fails that comparison." + +## Words and phrases I had to guess + +- **"contract"** — guessed: the new name for the schema. +- **"PSL"** — expanded on first use, good, but I guessed it is basically the old `schema.prisma` language. +- **"codec"** in `codecId` — guessed: a rule for converting between the database value and the JavaScript value. +- **"extension pack"** / **"pack"** — guessed: an npm package that adds column types, like pgvector. +- **`/pack` vs `/control` exports** — guessed: two entry points of the same package, one for the contract and one for the config. I do not know why, but the page tells me which goes where, which is enough. +- **`ulid`, `nanoid`, `cuid2`, `ksuid`** — guessed: ID string formats. I do not know how to choose between them and the page does not link anywhere. +- **`field.uuidNative()` vs `field.uuidString()`** — guessed from the table: one makes a `uuid` column, one a text column. +- **"emitted files"** (description) / "the two files" — guessed: `contract.json` and `contract.d.ts`. +- **`Model.refs` vs `Model.ref("_id")`** — the page uses `User.refs.id` for PostgreSQL and `User.ref("_id")` for MongoDB. I guessed these are two different APIs for the two databases. Never stated. +- **`cols` vs `fields`** — `.sql(({ cols, constraints }) => ...)` and `.attributes(({ fields, constraints }) => ...)`. I guessed they are the same thing under two names. Never stated. + +## "So what do I actually type?" — not answered + +1. **How do I create the contract file in the first place?** The page shows a finished file and a config pointing at it, but never says whether `npx prisma init` makes it or whether I create `src/prisma/contract.ts` by hand. The only creation advice is in "Parity with PSL," and only for converting an existing project. + +2. **A date without a time.** "There is no helper for a date without a time, or a time without a date." So what do I type? A `field.dateTime()` with `.column()`? A codec? The page names the gap and then stops. + +3. **Where does `.sql({ fk: ... })` go on a relation?** "Chain `.sql({ fk: { name: "post_userId_fkey" } })` on the relation." Chain it onto what expression — `rel.belongsTo(User, {...}).sql({...})`, inside the `.relations({ ... })` object? No code sample for this form, while the other form gets a full one. + +4. **A non-text enum.** "Members are stored in whatever column type you name here." If I want integers, what `codecId` and `nativeType` do I type? I am sent to the raw-queries page to hunt for it. + +5. **The `native_enum` block in PSL.** "To get a PostgreSQL `enum` type, write the contract in PSL and use a `native_enum` block." No syntax, no link to that block. + +6. **The key in `.attributes(...)`.** `id: constraints.id([fields.userId, fields.teamId])` — is `id` a required key name, or any label I pick? If I also add `constraints.unique(...)` "in the same block," what key do I give it? + +7. **Does the foreign-key field type have to match the key it points at?** `id: field.id.uuidv4String()` and `userId: field.uuidString()` in the example. I noticed the pairing but the page never states the rule, so I do not know if `field.text()` would also work. + +8. **Does `.relations()` change `User`, or return a new object?** In the example, `Post`'s `belongsTo(User, ...)` references the bare `User` from `model(...)`, while `models.User` is `User.relations(...).sql(...)`. If these are different objects, which one do relations have to point at? + +9. **MongoDB's limits.** The page says the MongoDB builder differs in five ways, then: "The rest of this page uses the PostgreSQL builder." So do enums, `enumType`, extension packs, `.attributes(...)`, and indexes work on MongoDB at all? I am left with a full page of material and no idea how much of it applies to me. + +## Places the page explains the machinery when I only wanted instructions + +- **The whole "How `defineContract` works" section.** The title promises internals, and some of it is internals ("You never pass the name of your database. It comes from the package you import."). But it is also the only place the argument list and the MongoDB differences are written down, so I cannot skip it. The useful parts are buried under a how-it-works heading. +- "`prisma contract emit` writes the contract to JSON in a fixed key order and hashes it." — I want the rule, not the hashing scheme. +- "`prisma contract emit` imports the file and reads its `default` or `contract` export, and nothing else. It runs the file, but it does not type-check it." — the second half is useful (type errors will not stop emit). The first half repeats what the "A complete contract" section already told me. +- "`as const` keeps both strings as literal types so the builder can check them." — borderline. I would have accepted "write `as const` after the object." + +## Could I do this after one reading? + +For the narrow case the example covers — PostgreSQL, a few models with text, UUID, JSON and timestamp fields, one-to-many and belongs-to relations, table renames, one named foreign key — yes. The full example is good and I could copy it. + +Outside that, no. After one reading I would still not know: + +- whether to import `rel` or take it from the callback argument (the page and its own example disagree) +- whether `constraints.index` wants a bare column or a list for one column (same problem) +- what to type for a date-only column +- what `codecId` to use for anything other than text +- where `.sql({ fk: ... })` attaches on a relation +- how much of the page applies if I use MongoDB +- how to create the contract file for a brand-new project + +The two contradictions between the prose and the code samples are the worst of it. Both are in areas where I would be typing code straight from the page, and both would fail to compile with no hint from the page about which version is right. \ No newline at end of file From 9ced890fd6607438bea81ee7c2ce3940dd5a632b Mon Sep 17 00:00:00 2001 From: willbot Date: Mon, 14 Sep 2026 09:39:01 +0200 Subject: [PATCH 07/14] docs(orm8): contract-authoring pages, reader-review round three applied Prefer-cutting round. The artifacts page lost its executionHash row and half its contract.json tour. The TypeScript page's complete example has no extension pack, uses {} for the options, and a date-only column is field.column with pg/date-temporal@1. The PSL page shows one-to-one, names the plain scalar column types, and defines discriminator at first use. The capabilities page says contract emit never connects to the database and separates the emit-time check from the query-time keys. The data-contract page carries the connection string in its config example. Co-Authored-By: Claude Fable 5.1 Signed-off-by: willbot Signed-off-by: Will Madden --- .../orm/contract-authoring/capabilities.mdx | 60 +++--- .../orm/contract-authoring/psl-syntax.mdx | 73 ++++--- .../the-contract-artifact.mdx | 51 ++--- .../contract-authoring/the-data-contract.mdx | 36 ++-- .../typescript-schema-builder.mdx | 184 +++++++++--------- .../capabilities.round3.md | 56 ++++++ .../psl-syntax.round3.md | 98 ++++++++++ .../the-contract-artifact.round3.md | 74 +++++++ .../the-data-contract.round3.md | 90 +++++++++ .../typescript-schema-builder.round3.md | 100 ++++++++++ 10 files changed, 605 insertions(+), 217 deletions(-) create mode 100644 docs/orm-docs-audit/c21-contract-authoring/capabilities.round3.md create mode 100644 docs/orm-docs-audit/c21-contract-authoring/psl-syntax.round3.md create mode 100644 docs/orm-docs-audit/c21-contract-authoring/the-contract-artifact.round3.md create mode 100644 docs/orm-docs-audit/c21-contract-authoring/the-data-contract.round3.md create mode 100644 docs/orm-docs-audit/c21-contract-authoring/typescript-schema-builder.round3.md diff --git a/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx b/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx index 65ce206c168..ac01931e330 100644 --- a/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx @@ -1,46 +1,45 @@ --- title: Supported database features -description: 'The contract records which database features your setup supports, so Prisma ORM can reject an unsupported one early with a clear error.' -url: /orm/contract-authoring/capabilities +description: 'The contract records which database features your packages support, so Prisma ORM can reject an unsupported one early with a clear error.' metaTitle: Supported database features in Prisma ORM -metaDescription: 'How a Prisma ORM contract records which database features your setup supports, where the list comes from, what each key controls, and what happens when one is missing.' +metaDescription: 'How a Prisma ORM contract records which database features you can use, where the list comes from, what each key controls, and what happens when one is missing.' +url: /orm/contract-authoring/capabilities --- -Your [contract](/orm/contract-authoring/the-data-contract), the `contract.prisma` file that replaced `schema.prisma`, describes your data. Not every database supports every feature: one setup can run lateral joins and `RETURNING` clauses, and another cannot. [`npx prisma contract emit`](/cli/contract-emit) writes `contract.json` and `contract.d.ts` beside your contract. The `capabilities` section of `contract.json` lists the database features your setup supports, and you never edit it. Prisma ORM reads that list before it uses one of those features. A feature your setup lacks fails right away, with an error that names the key for the missing feature, instead of reaching the database and failing there. +Your [contract](/orm/contract-authoring/the-data-contract), the `contract.prisma` file that replaced `schema.prisma`, describes your data. [`npx prisma contract emit`](/cli/contract-emit) writes `contract.json` and `contract.d.ts` beside it. The `capabilities` section of `contract.json` lists the database features you can use, under keys such as `sql.lateral`. Prisma ORM writes that section, and you never edit it. A feature that is not listed fails right away, with an error that names its key, instead of reaching the database and failing there. ## Where the `capabilities` section comes from [#where-capabilities-come-from] -Three things each say which features they support: your database, your database package, and any extension packages listed in `prisma.config.ts`. Your database package is `@prisma/orm-postgres`, `@prisma/orm-sqlite`, or `@prisma/orm-mongo`, and you choose it by the import in `prisma.config.ts`. `npx prisma contract emit` collects all of those answers into `contract.json`, in groups: +The keys come from your database package (`@prisma/orm-postgres`, `@prisma/orm-sqlite`, or `@prisma/orm-mongo`) and any extension packages listed in `prisma.config.ts`. `npx prisma contract emit` never connects to your database. It reads those packages and writes what they report, in groups: ```json title="src/prisma/contract.json (excerpt)" { "capabilities": { - "postgres": { - "distinctOn": true, - "pgvector.cosine": true - }, - "sql": { - "lateral": true, - "scalarList": true - } + "postgres": { "distinctOn": true, "pgvector.cosine": true }, + "sql": { "lateral": true, "scalarList": true } } } ``` -A key is written group first. The `pgvector.cosine` key under `postgres` is written `postgres.pgvector.cosine`. Keys under `sql` are shared across SQL databases, and keys under `postgres` belong to PostgreSQL only. Extension packages add keys of their own, and `pgvector.cosine` above comes from the pgvector package. Install it with `npm install @prisma/orm-extension-pgvector`, add these two lines to `prisma.config.ts`, and run `npx prisma contract emit` again: +A key is written group first, so `pgvector.cosine` under `postgres` is `postgres.pgvector.cosine`. The `sql` group holds keys that more than one SQL database may support. SQLite reports `false` for most of them. Keys under `postgres` belong to PostgreSQL only. Extension packages add keys of their own, and `pgvector.cosine` comes from the pgvector package. Install it with `npm install @prisma/orm-extension-pgvector`, list it in `prisma.config.ts`, and run `npx prisma contract emit` again: -```diff title="prisma.config.ts" -+ import pgvector from "@prisma/orm-extension-pgvector/control"; +```typescript title="prisma.config.ts" +import { definePrismaConfig } from "prisma/config"; +import pgvector from "@prisma/orm-extension-pgvector/control"; +import { defineConfig as ormConfig } from "@prisma/orm-postgres/config"; +export default definePrismaConfig({ orm: ormConfig({ contract: "./src/prisma/contract.prisma", -+ extensions: [pgvector], + extensions: [pgvector], + db: { connection: process.env['DATABASE_URL']! }, }), +}); ``` -## What the `capabilities` section controls [#what-capabilities-gate] +pgvector is also a PostgreSQL server extension, and the first migration the package ships installs it in PostgreSQL for you. -Two errors tell you that a feature is missing. Both arrive before any SQL reaches the database. +## What the `capabilities` section controls [#what-capabilities-gate] **When the contract is built.** If your contract uses a feature your database cannot store, `npx prisma contract emit` fails. SQLite has no `scalarList` key, so a list field such as `tags String[]` fails on SQLite: @@ -50,22 +49,25 @@ scalar lists (the adapter does not report the "scalarList" capability). Remove the list or author it against a target that supports scalar lists. ``` -In that message, "target" and "adapter" both mean your database package, which is `@prisma/orm-sqlite` here. Do one of the two: remove the list field, or switch to a database that supports list fields. To switch to PostgreSQL, change the import in `prisma.config.ts` from `@prisma/orm-sqlite/config` to `@prisma/orm-postgres/config`. +In that message, "target" and "adapter" both mean your database package, which is `@prisma/orm-sqlite` here. Do one of the two: remove the list field, or move to a database that supports list fields. To move to PostgreSQL, change the import and the connection string in `prisma.config.ts`, then set up the new database. -**When you call a query-builder method.** A method that needs a feature throws an error with code `ORM.CAPABILITY_MISSING`. The message names the method and the key: +**When you call a method on the [SQL query builder](/orm/reference/sql-query-builder).** A method that needs a feature throws an error whose `code` is `ORM.CAPABILITY_MISSING`. The message names the method and the key: ```text distinctOn() requires capability postgres.distinctOn -lateralJoin() requires capability sql.lateral ``` -That message means the method is not available on the database you are connected to. Rewrite the query without it, or move to a database that supports the feature. If the key comes from an extension package, install that package, list it in `prisma.config.ts`, and run `npx prisma contract emit` again. The call throws as soon as you make it, so a test that calls it catches it. +That message means the method is not available on the database you are connected to. Rewrite the query without it, or move to a database that supports the feature. If the key comes from an extension package, install that package, list it in `prisma.config.ts`, and run `npx prisma contract emit` again. The error arrives when you call the method, not when you `await` the query, so a test that calls it catches it. Your own code can read the same list through `db`, the client you create once in `src/prisma/db.ts`. A value such as `db.contract.capabilities.sql?.lateral` is `true`, `false`, or `undefined` when the whole group is absent: -Your own code can read the same list. `db` is the client you create once in `src/prisma/db.ts`, and it exposes the list as `db.contract.capabilities`. A whole group can be absent, so write `db.contract.capabilities.sql?.lateral`, which is `true` when lateral joins are available. +```typescript +if (db.contract.capabilities.sql?.lateral) { + // run the lateral join here, or fall back to a second query +} +``` ## Example keys [#example-capabilities] -A few of the keys your database, your database package, and the built-in extension packages supply: +A few of the keys your database package and extension packages supply: | Key | What it covers | | --- | --- | @@ -75,19 +77,17 @@ A few of the keys your database, your database package, and the built-in extensi | `postgres.distinctOn` | `DISTINCT ON` queries (`distinctOn()`) | | `postgres.pgvector.cosine` | Cosine distance from the pgvector package | -PostgreSQL has all three `sql` keys above, and SQLite has none of them. This table is a sample, not the full list. Open `src/prisma/contract.json` in your project to see every key your setup supports. - -The methods that need these keys exist only on the SQL query builder, and MongoDB does not have one, so nothing on MongoDB reads the `capabilities` section. The [ORM reference](/orm/reference) lists the methods each database has. +PostgreSQL reports `true` for all three `sql` keys above, and SQLite reports `false` for them. This table is a sample, not the full list. Open `src/prisma/contract.json` in your project to see every key you have. The check that runs when the contract is built applies on every database, MongoDB included. The keys that methods check at query time exist only on the SQL query builder, which MongoDB does not have. The [ORM reference](/orm/reference) lists the methods each database has. ## The `capabilities` section and `prisma db verify` [#capabilities-and-verification] -The keys say what your installed software supports. They are recorded when you run `npx prisma contract emit`, not read from the live database, so the same contract behaves the same way in every environment. Checking the live database is a separate job. [`db verify`](/cli/db-verify) compares the database against your contract. Extension packages are the main source of keys beyond the built-in ones, and [using extensions](/orm/extensions/using-extensions) shows the full path from install to first query. +The keys say what your packages support, recorded when you run `npx prisma contract emit`, so the same contract behaves the same way in every environment. Checking the live database is a separate job, and [`db verify`](/cli/db-verify) compares the database against your contract. Extension packages are the main source of keys beyond the ones your database package brings, and [using extensions](/orm/extensions/using-extensions) shows the full path from install to first query. ## Prompt your coding agent Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent. In an existing project, run `npx prisma skills sync`. The `prisma-8` skill covers this material. Ask your agent to: -- "Which keys does the `capabilities` section of our contract require, and which package provides each?" +- "Which keys in the `capabilities` section does our code rely on, and which package provides each?" - "Add pgvector to the project and confirm its key shows up in the emitted contract." ## Next steps diff --git a/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx b/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx index 2b8a4474fd0..01f4d0811e4 100644 --- a/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx @@ -1,19 +1,17 @@ --- title: Author in PSL -description: 'Write the Prisma ORM contract as a Prisma schema file, using the schema language you already know plus the Prisma ORM 8 additions.' +description: 'Write the Prisma ORM contract in the Prisma schema language you already know, plus the Prisma ORM 8 additions.' url: /orm/contract-authoring/psl-syntax metaTitle: Author the Prisma ORM contract in PSL metaDescription: 'Learn how to write a Prisma ORM contract in the Prisma schema language, including named types, enums, value objects, relations, and extension types.' --- -PSL, the Prisma Schema Language, is the preferred way to author [your contract](/orm/contract-authoring/the-data-contract), the `contract.prisma` file that replaced `schema.prisma`. You write a Prisma schema file, usually `src/prisma/contract.prisma`, and [`npx prisma contract emit`](/cli/contract-emit) writes `contract.json` and `contract.d.ts` beside it. +PSL, the Prisma Schema Language, is the preferred way to author [your contract](/orm/contract-authoring/the-data-contract), the `contract.prisma` file that replaced `schema.prisma`. You write one file, usually `src/prisma/contract.prisma`, and [`npx prisma contract emit`](/cli/contract-emit) writes `contract.json` and `contract.d.ts` beside it. If you know the Prisma schema language, most of a contract file reads exactly as you expect. Prisma ORM 8 adds five things: -If you know the Prisma schema language, most of a contract file reads exactly as you expect. Prisma ORM 8 adds five things: - -- named types: one name for a type and the database column type it uses. +- named types: give a database column type a name you can reuse on many fields. - enums: unchanged, except that an enum can now say how its values are stored. - value objects: a structured value stored inside its parent row, with no table of its own. -- base models and variants: a variant model adds fields to a base model, and one field's value says which variant a row is. +- base models and variants: store several kinds of record in one table. A base model holds the shared fields, each variant adds its own, and one column says which kind each row is. - extension types: field types that come from an npm package, such as vectors. The `datasource` and `generator` blocks are gone. The connection URL and the file paths are set in `prisma.config.ts` instead. [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7#schema) lists every change to the schema file, including what each old `@db.` attribute becomes. @@ -103,11 +101,11 @@ model Post { } ``` -Keep the first line, `// use prisma-8`. The Prisma editor extension reads it to tell that the file is a Prisma ORM 8 contract. Run `npx prisma contract emit` after any change to refresh the two files. +Line 1 is `// use prisma-8`. The Prisma editor extension reads it to tell that the file is a Prisma ORM 8 contract. Run `npx prisma contract emit` after any change to refresh the two files. ## Point the config at the schema -The config's `contract` path names the one file Prisma ORM reads. It takes a single path, so there is no folder of schema files. A `.prisma` extension selects PSL authoring. The `db` key holds the connection URL: +The config's `contract` path names the one file Prisma ORM reads. It takes a single path, so there is no folder of contract files. If the path ends in `.prisma`, Prisma ORM reads it as PSL. If it ends in `.ts`, as TypeScript. The `db` key holds the connection URL: ```typescript title="prisma.config.ts" import 'dotenv/config'; @@ -128,15 +126,15 @@ This is what `prisma orm init` writes, `DATABASE_URL` included. The import choos ## Models and fields -Models declare fields with a type, an optional `?` marker, and attributes. [Scalar fields](/orm/data-modeling#scalar-fields) lists the types a field can hold, among them `String`, `Int`, `Boolean`, `Decimal`, `DateTime`, `Json`, and `Bytes`. A native PostgreSQL type such as `VarChar(35)` or `Timestamptz` is written in the same position as one of those. +Models declare fields with a type, an optional `?` marker, and attributes. [Scalar fields](/orm/data-modeling#scalar-fields) lists the types a field can hold, among them `String`, `Int`, `Boolean`, `Decimal`, `DateTime`, `Json`, and `Bytes`. You can write a PostgreSQL type such as `VarChar(35)` or `Timestamptz` wherever you would write `String` or `DateTime`. On PostgreSQL, plain `String` is a `text` column, `Int` is `int4`, and `DateTime` is `timestamptz`. - `@id` marks the primary key. `@@id([a, b])` declares a composite key. - `@unique` adds a unique constraint on one field. `@@unique([userId, title])` adds one across several fields. - `@@index([...])` declares a secondary index. -- `@default(...)` sets a default. Database function defaults such as `@default(now())` become column defaults in the database. Generated defaults such as `@default(uuid())` are produced by Prisma ORM before each write, not by the database. +- `@default(...)` sets a default. Database function defaults such as `@default(now())` become column defaults in the database. Generated defaults such as `@default(uuid())` are produced by Prisma ORM before each write, so a row written by raw SQL or another application gets no value. - `@map("column_name")` sets a field's column name in the database. `@@map("table_name")` sets the table or collection name when it differs from the model name. -`@updatedAt` no longer exists. Write the call where the type would go: +`@updatedAt` is gone. Instead of a type, write `temporal.updatedAt()` as the field's type: ```prisma model Post { @@ -144,7 +142,7 @@ model Post { } ``` -`temporal` is built in. You do not import it or declare it. +`temporal` is built in. You do not import it or declare it. On PostgreSQL the column is `timestamptz`, and you can still set the field yourself on a write. The other member is `temporal.createdAt()`. How IDs map differs by database: @@ -160,13 +158,11 @@ model User { } ``` -On PostgreSQL the primary key is an ordinary column, so pick its type and default yourself. - -On MongoDB the primary key is the document's `_id`. Type it `ObjectId` and map it to `_id`. +On PostgreSQL the primary key is an ordinary column, so pick its type and default yourself. On MongoDB the primary key is the document's `_id`. Type it `ObjectId` and map it to `_id`. ## Named types -The `types` block declares reusable type aliases. An alias binds a type and its database column type under one name: +The `types` block gives a database column type a name you can reuse on many fields: ```prisma types { @@ -174,7 +170,7 @@ types { } ``` -Fields then use `ShortName` like any built-in type. The alias keeps the column decision in one place: a `varchar(35)` column rather than `text`. An alias is optional, and a field can name the native PostgreSQL type directly, such as `VarChar(35)`, `Uuid`, or `Timestamptz`. In Prisma ORM 8 the native type is the field's type, so `String @db.VarChar(35)` from Prisma ORM 7 becomes `VarChar(35)`. The `@db.` attributes are gone. +Fields then use `ShortName` like any built-in type. The name keeps the column decision in one place: a `varchar(35)` column rather than `text`. A name is optional, and a field can use the native PostgreSQL type directly, such as `VarChar(35)`, `Uuid`, or `Timestamptz`. In Prisma ORM 8 the native type is the field's type, so `String @db.VarChar(35)` from Prisma ORM 7 becomes `VarChar(35)`. The `@db.` attributes are gone. ## Enums @@ -189,11 +185,9 @@ enum Priority { } ``` -In `pg/text@1`, `pg` is PostgreSQL and `text` is the column type, so the values are stored as `text`. Write the `@1` as shown, because there is no other version yet. Write `@@type("pg/int4@1")` to store the values as integers instead. When a member has no explicit value, the member name itself is stored. +In `pg/text@1`, `pg` is PostgreSQL, `text` is the column type, and `@1` is the version, always `@1` today. Write `@@type("pg/int4@1")` to store the values as integers instead. When a member has no explicit value, the member name itself is stored. -`@@type` is optional. Leave it out and Prisma ORM picks the type from the member values: bare member names and string values give the database's text type, and integer values give its integer type. - -A `@default` names the member, as in `priority Priority @default(Low)`, even where the member stores a different value. +`@@type` is optional. Leave it out and Prisma ORM picks the type from the member values: bare member names and string values give the database's text type, and integer values give its integer type. A `@default` names the member, as in `priority Priority @default(Low)`, even where the member stores a different value. An `enum` block is not a PostgreSQL `enum` type: the column is text or an integer. For a PostgreSQL `enum` type, declare it in a `native_enum` block and type the field `pg.enum(Role)`: @@ -229,9 +223,7 @@ model User { } ``` -A value object field can be optional or a list, and a `type` block can hold a field of another `type`. - -Storage differs by database. On PostgreSQL a value object field is stored in a single `jsonb` column. On MongoDB it is an embedded document. Either way, `contract.d.ts` types it as a structured object rather than untyped JSON. On MongoDB, whether to embed or reference is the central modeling decision. [MongoDB data modeling](/orm/data-modeling/mongodb#embed-or-reference) covers it. +A value object field can be optional or a list, and a `type` block can hold a field of another `type`. Watch the two spellings: `types { ... }` declares named types, and `type X { ... }` declares a value object. Storage differs by database. On PostgreSQL a value object field is stored in a single `jsonb` column, and a list such as `addresses Address[]` is one `jsonb` column holding an array. On MongoDB it is an embedded document. Either way, `contract.d.ts` types it as a structured object rather than untyped JSON. On MongoDB, whether to embed or reference is the central modeling decision. [MongoDB data modeling](/orm/data-modeling/mongodb#embed-or-reference) covers it. ## Relations @@ -248,9 +240,16 @@ model User { } ``` -Add `onDelete` and `onUpdate` to the same `@relation`. They belong on the side that holds the foreign key, not on the list side. For a one-to-one, make the other side singular instead of a list, and put `@unique` on the foreign-key field. +Add `onDelete` and `onUpdate` to the same `@relation`. They belong on the side that holds the foreign key, not on the list side. For a one-to-one, make the other side singular instead of a list, so `User` declares `profile Profile?`, and put `@unique` on the foreign-key field: -Many-to-many relations need a model for the join table. You write that model yourself: there is no implicit many-to-many. That model must follow two rules. It holds a foreign key to each side, and each foreign key references that side's full `@id`. Its own `@@id([...])` lists exactly those two foreign-key fields. +```prisma +model Profile { + userId Uuid @unique + user User @relation(fields: [userId], references: [id]) +} +``` + +Many-to-many relations need a model for the join table. You write that model yourself: there is no implicit many-to-many. Prisma ORM matches a list field such as `tags Tag[]` to the one model that holds a foreign key to each side. That model must follow two rules. Each foreign key references every field of that side's `@id`, and its own `@@id([...])` lists exactly those two foreign-key fields. ```prisma model Post { @@ -273,15 +272,13 @@ model PostTag { } ``` -You then read `post.tags` as a list of `Tag`, without mentioning `PostTag` in the query. - -Break either rule and `npx prisma contract emit` throws an error whose `code` is `PSL_JUNCTION_ID_NOT_FK_COVERING` when the `@@id` lists anything other than the two foreign-key fields, or `PSL_JUNCTION_TARGET_FK_NOT_ID` when a foreign key does not reference the other side's `@id`. The message names the list field it could not match. +You then read `post.tags` as a list of `Tag`, without mentioning `PostTag` in the query. Break either rule and `npx prisma contract emit` reports which list field it could not match. If two models qualify, `npx prisma contract emit` throws an error whose `code` is `PSL_AMBIGUOUS_BACKRELATION`. Put the same `@relation("name")` on the list field and on `PostTag`'s field that points back at it. For which shape to choose and which side owns the foreign key, see [relational data modeling](/orm/data-modeling/relational-databases) and [MongoDB data modeling](/orm/data-modeling/mongodb). ## Base models and variants -A base model declares a discriminator field, and each variant names its base and its discriminator value: +A base model declares a discriminator field, the field whose value says which variant a row is. Each variant names its base and its discriminator value: ```prisma model Task { @@ -302,19 +299,21 @@ model Bug { } ``` -A field name is written bare, as `type` is in `@@discriminator(type)`, and a database name is quoted, as in `@@map("task")`. - -Rows with `type = "bug"` are `Bug` records. A variant reuses its base model's fields, so a `Bug` has `id`, `title`, and `type` as well as `severity` and `stepsToRepro`. A variant does not declare an `@id` of its own: it takes the base model's primary key. +A field name is written bare, as `type` is in `@@discriminator(type)`, and a database name is quoted, as in `@@map("task")`. Rows whose `type` column holds `"bug"` are `Bug` records. A variant reuses its base model's fields, so a `Bug` has `id`, `title`, and `type` as well as `severity` and `stepsToRepro`. A variant does not declare an `@id` of its own: it takes the base model's primary key. You query a variant through the base model: `db.orm.public.Task.variant('Bug')` returns the `Bug` rows only. Here `public` is the PostgreSQL schema. See [`variant()`](/orm/reference/orm-client#variant). -On PostgreSQL you choose where a variant's own fields are stored, and the variant's `@@map` is the choice. Give the variant its own `@@map`, as `Bug` has here, and its fields are in a table of their own that shares the base model's primary key. Leave `@@map` out and they are nullable columns in the base table. [Relational data modeling](/orm/data-modeling/relational-databases#polymorphic-relations) covers choosing between the two. +On PostgreSQL, whether you write `@@map` on the variant decides where its fields are stored. Give the variant its own `@@map`, as `Bug` has here, and its fields are in a table of their own that shares the base model's primary key. Leave `@@map` out and they are nullable columns in the base table. [Relational data modeling](/orm/data-modeling/relational-databases#polymorphic-relations) covers choosing between the two. On MongoDB, a variant adds its fields to documents in the base model's collection, so it declares `@@base` but no `@@map` of its own. ## Extension types -An extension pack is an npm package that adds field types Prisma ORM does not ship, such as vectors. Add the pack to the config, then call its types in the `types` block: +An extension pack is an npm package that adds field types Prisma ORM does not ship, such as vectors. Install the pack, add it to the config, then call its types in the `types` block: + +```bash +npm install @prisma/orm-extension-pgvector +``` ```typescript title="prisma.config.ts" import { definePrismaConfig } from "prisma/config"; @@ -340,11 +339,11 @@ model Post { } ``` -The `pgvector` part of `pgvector.Vector(1536)` is a fixed name the pack declares, not the name you gave the import. `npx prisma contract emit` resolves extension types through the packs listed in the config, so list the pack before using its types. Run it again after changing the extension list. [Using extensions](/orm/extensions/using-extensions) covers installing a pack and names the packs you can add. +The `pgvector` part of `pgvector.Vector(1536)` is a fixed name the pack declares, not the name you gave the import. List the pack before using its types. Run `npx prisma contract emit` again after changing the extension list. [Using extensions](/orm/extensions/using-extensions) covers installing a pack and names the packs you can add. ## Starting from an existing database -If the database already exists, don't write the schema by hand. [`contract infer`](/cli/contract-infer) reads the live schema and writes a starter `contract.prisma` for you to review and edit. +If the database already exists, don't write the contract by hand. [`contract infer`](/cli/contract-infer) reads the live schema and writes a starter `contract.prisma` for you to review and edit. ## Prompt your coding agent diff --git a/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx b/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx index cd4f23efd7f..aa55fd83d78 100644 --- a/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx @@ -6,14 +6,14 @@ metaTitle: The Prisma ORM contract artifacts metaDescription: 'What contract.json and contract.d.ts contain, why the same source always produces the same two files, how the hashes work, and how your app and the CLI read them.' --- -Your contract, [the one description of your data and how it is stored](/orm/contract-authoring/the-data-contract), is one source file: `src/prisma/contract.prisma` in PSL, or `src/prisma/contract.ts` in TypeScript. [`npx prisma contract emit`](/cli/contract-emit) reads that file and writes two files beside it. In the table below, `db` is your client, the object you run queries on. `prisma orm init` writes it for you in `src/prisma/db.ts`. +Your contract is [a single file that describes your data and how it is stored](/orm/contract-authoring/the-data-contract). It replaces the `schema.prisma` of Prisma ORM 7, and it is `src/prisma/contract.prisma` in PSL or `src/prisma/contract.ts` in TypeScript. [`npx prisma contract emit`](/cli/contract-emit) reads that file and writes two files beside it. In the table below, `db` is your client, the object you run queries on. [`npx prisma orm init`](/cli/orm-init) creates the contract source, `src/prisma/db.ts`, and `.env.example` for you. | File | Contents | Read by | | --- | --- | --- | | `contract.json` | The machine-readable contract: models, how they are stored, which database features they need, and the hashes | `db`, `prisma db sign`, `prisma db verify`, and `prisma migration plan` | | `contract.d.ts` | TypeScript declarations derived from the contract | The query APIs and your application code, for typed models and results | -Both files are generated. Do not edit them. Change the [PSL](/orm/contract-authoring/psl-syntax) or [TypeScript](/orm/contract-authoring/typescript-schema-builder) source and run `npx prisma contract emit` again. Each file starts with a notice that says exactly this. To write the two files to another directory, see [`prisma contract emit`](/cli/contract-emit). +`prisma db sign` records in the database which contract it matches. Both files are generated. Do not edit them. Change the [PSL](/orm/contract-authoring/psl-syntax) or [TypeScript](/orm/contract-authoring/typescript-schema-builder) source and run `npx prisma contract emit` again. Each file starts with a notice that says exactly this. To write the two files to another directory, see [`prisma contract emit`](/cli/contract-emit). The everyday loop on a project: @@ -22,23 +22,21 @@ The everyday loop on a project: 3. Create the migration with [`npx prisma migration plan`](/cli/migration-plan), read it, then apply it with [`npx prisma db migrate`](/cli/db-migrate). 4. Check the database against the contract with [`npx prisma db verify`](/cli/db-verify) in CI or in your deploy step. -Applying a migration records which contract the database now matches, so you do not sign it yourself afterwards. [`npx prisma db init`](/cli/db-init) creates the structures in an empty database and records the same thing. Run [`npx prisma db sign`](/cli/db-sign) only when the database already matches the contract and nothing recorded it, such as after [`npx prisma contract infer`](/cli/contract-infer) on an existing database. +Applying a migration records which contract the database now matches, so you do not sign it yourself afterwards. [`npx prisma db init`](/cli/db-init) creates the structures in an empty database and records the same thing. `npx prisma db verify` tells you whether anything recorded it. Run [`npx prisma db sign`](/cli/db-sign) only when the database already matches the contract and nothing recorded it, such as after [`npx prisma contract infer`](/cli/contract-infer) has written a starter contract from an existing database. ## The same source always produces the same two files [#deterministic-emission] -Run `npx prisma contract emit` on the same source on any machine and you get the same two files, byte for byte. You can read the diff of either file in a code review. - -That only holds if the contract source does not read the environment, the clock, or random values. A source that did would produce different files each run, and every hash below would stop meaning anything. The [TypeScript authoring page](/orm/contract-authoring/typescript-schema-builder#keep-the-contract-file-pure) lists the rules. +Run `npx prisma contract emit` on the same source on any machine and you get the same two files, byte for byte. That holds only if your contract source does not read the environment, the clock, or random values, and the [TypeScript authoring page](/orm/contract-authoring/typescript-schema-builder#keep-the-contract-file-pure) lists the rules. ## The content hashes -`npx prisma contract emit` computes a hash over each of three parts of the contract. You never compute or compare them yourself. `storageHash` covers the storage layout: tables, columns, primary keys, uniques, indexes, foreign keys, and the allowed values of each enum. `executionHash` covers the defaults Prisma ORM applies before a write. `profileHash` covers which database the contract targets. +`npx prisma contract emit` computes a hash over each part of the contract. You never compute or compare them yourself. `storageHash` covers the storage layout: tables, columns, primary keys, uniques, indexes, foreign keys, and the allowed values of each enum. `profileHash` covers which database the contract targets. -The hashes are how Prisma ORM connects the contract to a live database. `npx prisma db sign` checks that the database satisfies the contract, then records `storageHash` and `profileHash` in a table it creates called `prisma_contract.marker`. You never write to that table yourself. `npx prisma db verify` compares the contract you hold against that record and against the live schema, and fails when they disagree. To fix a mismatch, apply the missing migration, or run `npx prisma db sign` when the database already matches the contract. +The hashes are how Prisma ORM connects the contract to a live database. `npx prisma db sign` checks that the database satisfies the contract, then records `storageHash` and `profileHash` in a table it creates called `prisma_contract.marker`. You never write to that table yourself. `npx prisma db verify` compares the contract you hold against that record and against the live schema, and fails when they disagree. To fix a mismatch, apply the pending migration, or run `npx prisma db sign` when the database already matches the contract. ## Inside `contract.json` -The contract keeps your application's view of the data separate from the way the database stores it. The `domain` section describes models, fields, and relations. The `storage` section describes tables, columns, keys, and indexes. On MongoDB it describes collections and indexes instead. Each model's own `storage` block maps each field to its column. Everything is grouped by namespace. On PostgreSQL the namespace is the schema. It is `public` unless you put the models in a `namespace` block, such as `namespace auth { }`, in your [PSL source](/orm/contract-authoring/psl-syntax). +The contract keeps your application's view of the data separate from the way the database stores it. The `domain` section describes models, fields, and relations. The `storage` section describes tables, columns, keys, and indexes. On MongoDB it describes collections and indexes instead. Each model's own `storage` block maps each field to its column. Everything is grouped by namespace. On PostgreSQL the namespace is the schema. It is `public` unless you put the models in a `namespace` block in your [PSL source](/orm/contract-authoring/psl-syntax). An abridged `contract.json` for a `User` and `Post` schema: @@ -61,13 +59,6 @@ An abridged `contract.json` for a `User` and `Post` schema: "email": { "nullable": false, "type": { "codecId": "pg/text@1", "kind": "scalar" } }, "id": { "nullable": false, "type": { "codecId": "pg/uuid@1", "kind": "scalar" } } }, - "relations": { - "posts": { - "cardinality": "1:N", - "on": { "localFields": ["id"], "targetFields": ["userId"] }, - "to": { "model": "Post", "namespace": "public" } - } - }, "storage": { "fields": { "email": { "column": "email" }, "id": { "column": "id" } }, "namespaceId": "public", @@ -96,21 +87,16 @@ An abridged `contract.json` for a `User` and `Post` schema: }, "storageHash": "…" }, - "execution": { "executionHash": "…" }, - "capabilities": { "postgres": { "returning": true } }, - "extensions": {} + "capabilities": { "postgres": { "returning": true } } } ``` The sections, top to bottom: - **`schemaVersion`, `targetFamily`, `target`**: the contract format version, whether the database is SQL or MongoDB, and which database this contract targets. -- **`roots`**: one entry per model. Each key is the table name, and its value names the model that table stores. The SQL query builder uses the table name, as in `db.sql.public.user`. The model API uses the model name, as in `db.orm.public.User`. Both are described in the [ORM reference](/orm/reference). -- **`domain`**: the application's view. Each scalar field records whether it is nullable and its `codecId`, such as `pg/text@1`, which is the PostgreSQL type plus a version. Relations record how many records are on each side and the fields they join on. The model's `storage` block maps each field to its column. -- **`storage`**: the database's view. Tables with their columns, plus primary keys, uniques, indexes, foreign keys, check constraints, and the allowed values of each enum. This is the section the migration tooling compares between versions and `prisma db verify` checks the live schema against. -- **`execution`**: the defaults Prisma ORM applies before a write, such as `@default(uuid())`. Prisma ORM fills these in as it sends the write. They are not column defaults in the database. A write that does not go through Prisma ORM gets whatever the column itself defaults to. -- **`capabilities`**: which database features this contract can use. `npx prisma contract emit` collects them from the adapter for your database and from the extension packs listed in `extensions: [...]` in `prisma.config.ts`. The query APIs check them before building a query that needs one. See [supported database features](/orm/contract-authoring/capabilities) and [extension types](/orm/contract-authoring/psl-syntax#extension-types). -- **`extensions`**: one entry for each extension pack in that `extensions: [...]` list, with the types that pack adds. It is `{}` when you list none. +- **`roots`**: one entry per model. The key is the table name, and its value names the model that table stores. The [ORM reference](/orm/reference) covers both ways of reaching it, `db.sql.public.user` by table name and `db.orm.public.User` by model name. +- **`domain` and `storage`**: your application's view, then the database's view. Each scalar field in `domain` records whether it is nullable and its type id, such as `pg/text@1`, which is the PostgreSQL type plus a version. `storage` holds the tables with their columns, keys, indexes, constraints, and enum values. Migration tooling compares `storage` between two contract versions. `prisma db verify` compares it to the live database. +- **`capabilities`**: which database features this contract can use. `npx prisma contract emit` collects them from the adapter for your database and from the packages listed in `extensions: [...]` in `prisma.config.ts`. The query APIs check them before building a query that needs one. See [supported database features](/orm/contract-authoring/capabilities) and [extension types](/orm/contract-authoring/psl-syntax#extension-types). ## Inside `contract.d.ts` @@ -118,23 +104,16 @@ The declarations file gives the type system the same information. It exports: - `Contract`, the type the client is built from. `db.ts` imports it. - One type per hash, such as `StorageHash` and `ProfileHash`. The hash string is part of the type. -- `Output` and `Input` for each [`type` block](/orm/contract-authoring/psl-syntax#value-objects) in your contract. A `type` block declares a structured value stored inside its parent row, with no table of its own. A `type Address` gives you the `AddressOutput` and `AddressInput` below. -- `FieldOutputTypes` and `FieldInputTypes`, which hold the field types of every model, keyed by namespace and model name. +- Input and output types for each [`type` block](/orm/contract-authoring/psl-syntax#value-objects) in your contract, plus `FieldOutputTypes` and `FieldInputTypes`, which hold the field types of every model. ```typescript title="src/prisma/contract.d.ts (excerpt)" export type StorageHash = StorageHashBase<"9f49f8f9e51a9cc016f1ec2098ebae9406521a3cc2cf00207adc795078333d8b">; export type ProfileHash = ProfileHashBase<"9c8aa3114e84ed3b7ea2bd57526d9c2e1bf7c5292be694e9d3801f566fda7ccb">; - -export type AddressOutput = { - readonly street: CodecTypes["pg/text@1"]["output"]; - readonly city: CodecTypes["pg/text@1"]["output"]; - readonly zip: CodecTypes["pg/text@1"]["output"] | null; -}; ``` -The hash string is part of the type. If you regenerate `contract.json` and not `contract.d.ts`, TypeScript catches it before `prisma db verify` has to. +You never write these types yourself. Both files come from the one `npx prisma contract emit` run, so they only drift when someone commits one and not the other. When that happens, TypeScript catches it before `prisma db verify` has to. ## How the application consumes the artifacts @@ -152,7 +131,9 @@ export const db = postgres({ }); ``` -The connection string comes from the `DATABASE_URL` environment variable. The import path `./contract.d` is written that way on purpose. It is not a typo, and TypeScript resolves it to `contract.d.ts`. Before its first query, `db` reads the record in the database of which contract it matches and compares it with the contract's hashes. If they do not match, it logs a warning whose `code` is `CONTRACT.MARKER_MISMATCH` and runs the query anyway. If there is no record at all, because nothing has signed the database yet, it logs a warning whose `code` is `CONTRACT.MARKER_MISSING` and again runs the query. Run `prisma db verify` when you need the check to fail instead. +The connection string comes from the `DATABASE_URL` environment variable. `prisma orm init` writes `.env.example`. Put your `DATABASE_URL` in `.env`. Keep the `.d` in the import path. Before its first query, `db` reads the record in the database of which contract it matches and compares it with the contract's hashes. If they do not match, it logs a warning whose `code` is `CONTRACT.MARKER_MISMATCH` and runs the query anyway. If there is no record at all, because nothing has signed the database yet, it logs a warning whose `code` is `CONTRACT.MARKER_MISSING` and again runs the query. + +When `CONTRACT.MARKER_MISMATCH` shows up in production, apply the pending migration, or run `npx prisma db sign` when the database already matches the contract. The client can only warn or skip this check. See [`verifyMarker`](/orm/reference/transactions-and-runtime#postgresoptions) on the transactions and runtime page. Run `prisma db verify` when you need a check that fails. ## Version control diff --git a/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx b/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx index edabec0c404..df260581fdd 100644 --- a/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx @@ -6,9 +6,7 @@ metaTitle: The Prisma ORM data contract metaDescription: 'What the Prisma ORM data contract is, how PSL and TypeScript authoring produce the same two files, and how the contract drives typing, migrations, and checking your database.' --- -Every Prisma ORM project has one description of its data: the models, their fields, and how they map to database tables. That description is the data contract, the `contract.prisma` file that replaced `schema.prisma`. - -For example, a blog's contract declares a `User` and a `Post`, the fields each one has, and how they relate. You author it in PSL, the Prisma Schema Language, the same language as `schema.prisma`: +Every Prisma ORM project has one description of its data: the models, their fields, and how they map to database tables. That description is the data contract, the `contract.prisma` file that replaced `schema.prisma`. For example, a blog's contract declares a `User` and a `Post`, the fields each one has, and how they relate. You author it in PSL, the Prisma Schema Language, the same language as `schema.prisma`: ```prisma title="src/prisma/contract.prisma" // use prisma-8 @@ -27,11 +25,9 @@ model Post { } ``` -Start every `.prisma` contract with `// use prisma-8`. Your editor's Prisma support only works on files that have that line. A `contract.ts` file needs no header line. - -`prisma contract emit` turns this file into two files that the rest of the toolchain reads: `contract.json` and `contract.d.ts`. Your queries are type-checked against the contract, migrations are planned as changes to it, and `prisma db verify` checks a live database against it. Think of it as a `package-lock.json` for your data: an exact, versioned record of what your application expects from its database. +`npx prisma orm init` writes the first line, `// use prisma-8`, into every `.prisma` contract. The CLI works without that line, and the Prisma VS Code extension needs it to recognize the file. A `contract.ts` file needs no header line. -If you already have a Prisma ORM 7 `schema.prisma`, [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7) lists what changed and the [PostgreSQL upgrade guide](/guides/upgrade-prisma-orm/postgresql) walks through converting the file. +`npx prisma contract emit` turns this file into two files that the CLI and your application read: `contract.json` and `contract.d.ts`. Your queries are type-checked against the contract, migrations are planned as changes to it, and `npx prisma db verify` checks a live database against it. Think of it as a `package-lock.json` for your data: a file in version control that records exactly what your application expects from its database. :::note[Contract vs. schema] @@ -41,26 +37,32 @@ In Prisma ORM, the **contract** is what you write in your code, and the **schema ## Why a contract -Your contract compiles to two plain files you can open and read. `contract.json` describes your models, how they are stored, and [which database features they need](/orm/contract-authoring/capabilities). `contract.d.ts` holds the TypeScript types derived from it. The same source always produces the same two files, so you can read them in a code review and hand them to tools and coding agents. In Prisma ORM 7 the same description existed, but only the generated client could read it. +Your contract compiles to two plain files you can open and read. `contract.json` describes your models, how they are stored, and the [database features they need](/orm/contract-authoring/capabilities). `contract.d.ts` holds the TypeScript types derived from it. The same source always produces the same two files, so you can read them in a code review and hand them to tools and coding agents. -`prisma contract emit` also writes a hash of the contract into `contract.json`. When you apply a contract change to the database, the command that applies it records that hash in a small table it creates in your database. Before its first query, `db` compares the two and logs a warning if they differ. To fail instead of warn, for example in a deploy check, run [`prisma db verify`](/cli/db-verify). +`npx prisma contract emit` also writes a [hash](/orm/contract-authoring/the-contract-artifact) of the contract into `contract.json`, and `npx prisma db migrate` stores the same hash in your database. The first time your client `db` runs a query, it compares the two hashes and logs a warning if they differ. To fail instead of warn, for example in a deploy check, run [`prisma db verify`](/cli/db-verify). ## How it works A Prisma ORM project declares one contract file in `prisma.config.ts`: ```typescript title="prisma.config.ts" +import "dotenv/config"; import { definePrismaConfig } from "prisma/config"; import { defineConfig as ormConfig } from "@prisma/orm-postgres/config"; export default definePrismaConfig({ orm: ormConfig({ contract: "./src/prisma/contract.prisma", + db: { + connection: process.env["DATABASE_URL"]!, + }, }), }); ``` -`definePrismaConfig` holds the whole config. `ormConfig` comes from your database package and holds the settings for that database: `@prisma/orm-postgres` for PostgreSQL, `@prisma/orm-sqlite` for SQLite, and `@prisma/orm-mongo` for MongoDB. The examples here use PostgreSQL. To start a new project, run `npm create prisma@latest -- my-app`. To add Prisma ORM to a project you already have, run `prisma orm init`. Either one writes `prisma.config.ts`, the contract file, and `src/prisma/db.ts`, and installs the database package. +`definePrismaConfig` holds the whole config: which contract file to use and how to reach your database. Your connection string is in `prisma.config.ts` now, and the contract has no `datasource` block. The database settings come from the `defineConfig` export from your database package, imported here as `ormConfig`. The packages are `@prisma/orm-postgres` for PostgreSQL, `@prisma/orm-sqlite` for SQLite, and `@prisma/orm-mongo` for MongoDB. The examples here use PostgreSQL. + +To start a new project, run `npm create prisma@latest -- my-app`. To add Prisma ORM 8 to a project that has no Prisma ORM at all, run `npx prisma orm init`. If you already have a Prisma ORM 7 `schema.prisma`, follow the [PostgreSQL upgrade guide](/guides/upgrade-prisma-orm/postgresql) instead, and [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7) lists what changed. Both `npm create prisma@latest` and `npx prisma orm init` write `prisma.config.ts`, the contract file, and `src/prisma/db.ts`, and install the database package. The contract file is either a PSL file (`contract.prisma`) or a TypeScript file (`contract.ts`). The file extension selects the authoring mode, so to author in TypeScript you point `contract` at `./src/prisma/contract.ts`. Both modes describe the same things: models with fields and relations, the tables and columns they map to, named types, enums, and any types that [extension packages](/orm/contract-authoring/psl-syntax#extension-types) add. Extension packages are npm packages that add field types and database features, such as pgvector, a PostgreSQL extension for vector columns. @@ -72,17 +74,17 @@ npx prisma migration plan npx prisma db migrate ``` -`prisma contract emit` writes `contract.json` and `contract.d.ts` beside the source. [`prisma migration plan`](/orm/migrations/generating-a-migration) works out what the database needs and writes a migration. [`prisma db migrate`](/orm/migrations/applying-a-migration) applies it and records the new hash for you, so there is no separate signing step. Run [`prisma db sign`](/cli/db-sign) only when the database already matches the contract and nothing needs applying, for example after `prisma contract infer`. +`npx prisma contract emit` writes `contract.json` and `contract.d.ts` beside the contract file, so `src/prisma/`. [`npx prisma migration plan`](/orm/migrations/generating-a-migration) works out what the database needs and writes a migration. [`npx prisma db migrate`](/orm/migrations/applying-a-migration) applies it and records the new hash for you. Run [`npx prisma db sign`](/cli/db-sign) only to record the hash without applying anything, for example after `npx prisma contract infer`, when the database already matches the contract. -`db` is the client you create once in `src/prisma/db.ts`, and `prisma orm init` writes that file for you. From a file directly inside `src/`, import it with `import { db } from "./prisma/db"` and query a model: `await db.orm.public.User.all()` returns every user row. `db.orm` holds your models by model name. `public` is the PostgreSQL schema, the namespace your tables are in, which is `public` unless you set one. [Reading data](/orm/fundamentals/reading-data) covers queries. +`db` is the client you create once in `src/prisma/db.ts`, and `npx prisma orm init` writes that file for you. From a file directly inside `src/`, import it with `import { db } from "./prisma/db"` and query a model: `await db.orm.public.User.all()` returns every user row. `db.orm` holds your models by model name. `public` is the PostgreSQL schema, the namespace your tables are in, unless you configured a different one. [Reading data](/orm/fundamentals/reading-data) covers queries. ## Two authoring modes, one `contract.json` [#two-authoring-modes-one-artifact] -**[PSL](/orm/contract-authoring/psl-syntax)** is the usual way to write the contract. It is a compact language for describing data. It is what `npm create prisma@latest` scaffolds, and what [`prisma contract infer`](/cli/contract-infer) writes when you start from an existing database. That command reads the live database and writes a `contract.prisma` from what it finds. +**[PSL](/orm/contract-authoring/psl-syntax)** is the usual way to write the contract. It is a compact language for describing data. It is what `npm create prisma@latest` scaffolds, and what [`npx prisma contract infer`](/cli/contract-infer) writes when you start from an existing database. That command reads the live database and writes a `contract.prisma` from what it finds. Define models with the **[TypeScript builder](/orm/contract-authoring/typescript-schema-builder)** instead when you want to build them with code. Reach for it when model definitions must be composed, generated, or shared as ordinary TypeScript modules. That page shows what a `contract.ts` file looks like. -Both modes produce the same contract. For the same models they write the same `contract.json`, so migrations, database checks, and `db` behave the same no matter which mode you write. A project has exactly one contract file, the one named by `contract` in the config. Do not keep both a `contract.prisma` and a `contract.ts`. If you had both, they could describe different things. +Both modes produce the same contract, so migrations, database checks, and `db` behave the same no matter which mode you write. A project has exactly one contract file. Do not keep both a `contract.prisma` and a `contract.ts`: the config names one file and Prisma ORM reads only that one, so edits to the other file do nothing. ## What the contract contains @@ -91,9 +93,8 @@ The contract describes structure, not data: - models, fields, and relations, plus how they map to tables and columns - how the data is stored: primary keys, unique constraints, indexes, and foreign keys - [named types](/orm/contract-authoring/psl-syntax#named-types), which are reusable aliases for a type and its storage details, and enums -- [`type` blocks](/orm/contract-authoring/psl-syntax#value-objects), each a structured value stored inside its parent row with no table of its own. On PostgreSQL each one is stored in a single `jsonb` column. +- [`type` blocks](/orm/contract-authoring/psl-syntax#value-objects), each a reusable group of fields such as an address, stored inside its parent row with no table of its own. On PostgreSQL each one is stored in a single `jsonb` column. - the types and database features that extension packages add, such as pgvector's `Vector` type -- a hash of the contract, which the database check compares against It contains no rows, no credentials, and no connection details, so committing the source, `contract.json`, and `contract.d.ts` to version control is safe and expected. @@ -136,7 +137,6 @@ Projects created with `npm create prisma@latest` include the [Prisma ORM skills] title="Supported database features" icon={} > - How Prisma ORM checks that your database supports what the contract - needs. + How Prisma ORM checks that your database supports what the contract needs. diff --git a/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx b/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx index 08c4f83a1ca..9754fd68b80 100644 --- a/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx @@ -49,76 +49,46 @@ export default definePrismaConfig({ The builder comes from your database's package: `@prisma/orm-postgres/contract-builder` for PostgreSQL, `@prisma/orm-mongo/contract-builder` for MongoDB. -Export the contract under the name `contract`, as below, or as the file's default export. `prisma contract emit` looks for those two names and nothing else. +Export the contract under the name `contract`, as below, or as the file's default export. Those are the only two names `prisma contract emit` looks for. Run `npx prisma contract emit` again after every edit to the contract, and commit the contract file together with [`contract.json` and `contract.d.ts`](/orm/contract-authoring/the-contract-artifact). ```typescript title="src/prisma/contract.ts" tab="PostgreSQL" -import pgvector from "@prisma/orm-extension-pgvector/pack"; -import { defineContract, enumType, member, rel } from "@prisma/orm-postgres/contract-builder"; +import { defineContract, enumType, member } from "@prisma/orm-postgres/contract-builder"; const pgText = { codecId: "pg/text@1", nativeType: "text" } as const; -const Priority = enumType( - "Priority", - pgText, - member("Low", "low"), - member("High", "high"), - member("Urgent", "urgent"), -); - -export const contract = defineContract( - { - extensions: { pgvector }, - }, - ({ field, model, type }) => { - const types = { - Embedding1536: type.pgvector.Vector(1536), - } as const; - - const User = model("User", { - fields: { - id: field.id.uuidv4String(), - email: field.text(), - createdAt: field.temporal.createdAt(), - updatedAt: field.temporal.updatedAt(), - address: field.json().optional(), - }, - }); - - const Post = model("Post", { - fields: { - id: field.id.uuidv4String(), - title: field.text(), - userId: field.uuidString(), - priority: field.namedType(Priority).default(Priority.members.Low), - createdAt: field.temporal.createdAt(), - updatedAt: field.temporal.updatedAt(), - embedding: field.namedType(types.Embedding1536).optional(), - }, - }); - - return { - enums: { Priority }, - types, - models: { - User: User.relations({ - posts: rel.hasMany(Post, { by: "userId" }), - }).sql({ - table: "user", - }), - Post: Post.relations({ - user: rel.belongsTo(User, { from: "userId", to: "id" }), - }).sql(({ cols, constraints }) => ({ - table: "post", - foreignKeys: [ - constraints.foreignKey(cols.userId, User.refs.id, { - name: "post_userId_fkey", - }), - ], - })), - }, - }; - }, -); +const Priority = enumType("Priority", pgText, member("Low", "low"), member("High", "high")); + +export const contract = defineContract({}, ({ field, model, rel }) => { + const User = model("User", { + fields: { + id: field.id.uuidv4String(), + email: field.text(), + createdAt: field.temporal.createdAt(), + address: field.json().optional(), + }, + }); + + const Post = model("Post", { + fields: { + id: field.id.uuidv4String(), + title: field.text(), + userId: field.uuidString(), + priority: field.namedType(Priority).default(Priority.members.Low), + createdAt: field.temporal.createdAt(), + updatedAt: field.temporal.updatedAt(), + }, + }); + + return { + enums: { Priority }, + models: { + User: User.relations({ posts: rel.hasMany(Post, { by: "userId" }) }).sql({ table: "user" }), + Post: Post.relations({ + user: rel.belongsTo(User, { from: "userId", to: "id" }).sql({ fk: { name: "post_userId_fkey" } }), + }).sql({ table: "post" }), + }, + }; +}); ``` ```typescript title="src/prisma/contract.ts" tab="MongoDB" @@ -128,9 +98,7 @@ const User = model("User", { collection: "users", fields: { _id: field.objectId(), - name: field.string(), email: field.string(), - bio: field.string().optional(), }, relations: { posts: rel.hasMany("Post", { from: "_id", to: "authorId" }), @@ -150,19 +118,24 @@ const Post = model("Post", { }, }); -export const contract = defineContract({ - models: { - User, - Post, - }, -}); +export const contract = defineContract({ models: { User, Post } }); ``` ## How `defineContract` works -On PostgreSQL, `defineContract` takes an options object and then a function. You never name your database, because the package you import carries it. The options object lists the extension packs you use. The function returns the contract's content: `models`, plus `enums` and `types` if you have them. Its one argument holds `field`, `model`, `rel`, and `type`. The package exports `field`, `model`, `rel`, `member`, and `enumType`. So `field`, `model`, and `rel` have two sources and either one works. The example takes `field`, `model`, and `type` from the argument, and imports `defineContract`, `enumType`, `member`, and `rel`. `enumType` and `member` must be imported, because enums are declared outside the function. `type.pgvector` exists in the example only because `pgvector` is listed in `extensions`. +On PostgreSQL, `defineContract` takes an options object and then a function. You never name your database, because the package you import carries it. The options object lists the extension packs you use, and you write `{}` when you use none. The function returns the contract's content: `models`, plus `enums` and `types` if you have them. + +Take `field`, `model`, `rel`, and `type` from the function's one argument. Import `defineContract`, `enumType`, and `member` from the package, because you use them outside the function. + +On MongoDB, `defineContract` takes a single object holding `models`, and you import `field`, `model`, and `rel` from the package instead. The MongoDB builder also differs from the PostgreSQL one in five ways: + +- Every model declares an `_id` field built with `field.objectId()`. That field is always the primary key, so you never mark it with `.id()` and there is no `.attributes(...)` on MongoDB. +- The scalar helpers are `field.objectId()`, `field.string()`, `field.int32()`, `field.double()`, `field.bool()`, and `field.date()`. +- The collection name and the relations are inline options on the model rather than chained calls. +- Relations name the field on each side, as in `rel.hasMany("Post", { from: "_id", to: "authorId" })`, and a field on another model is written `User.ref("_id")` where PostgreSQL writes `User.refs.id`. +- Indexes are an `indexes` option on the model, built with the `index` helper the same package exports, as in `indexes: [index({ email: 1 }, { unique: true })]`. -On MongoDB, `defineContract` takes a single object holding `models`, and you import `field`, `model`, and `rel` from the package instead. The MongoDB builder differs from the PostgreSQL one in four more ways. Every model declares an `_id` field built with `field.objectId()`, and that field is the primary key, so you never mark it with `.id()`. The scalar helpers are `field.objectId()`, `field.string()`, `field.int32()`, `field.double()`, `field.bool()`, and `field.date()`. The collection name and the relations are inline options on the model rather than chained calls. Relations name the field on each side, as in `rel.hasMany("Post", { from: "_id", to: "authorId" })`, and a field on another model is written `User.ref("_id")` where PostgreSQL writes `User.refs.id`. Enums, extension packs, and indexes all work on MongoDB. `enumType` and `member` come from `@prisma/orm-mongo/contract-builder`, extension packs go in the same `extensions` option, and indexes are an `indexes` option on the model. There is no `.attributes(...)` on MongoDB, because `_id` is always the primary key. +Enums and extension packs work on MongoDB too. `enumType` and `member` come from `@prisma/orm-mongo/contract-builder`, and extension packs go in the same `extensions` option. The rest of this page uses the PostgreSQL builder. @@ -184,9 +157,9 @@ The rest of this page uses the PostgreSQL builder. | UUID stored as text | `field.uuidString()` | | UUID stored in a `uuid` column | `field.uuidNative()` | -There is no helper for a date without a time, or a time without a date. To store a date with no time, write that model in [PSL](/orm/contract-authoring/psl-syntax) and give the field the `Date` type. Extension packs add more helpers of their own. +There is no helper for a date without a time, or a time without a date. For a date-only column, name the type yourself: `field.column({ codecId: "pg/date-temporal@1", nativeType: "date" } as const)`. `field.column(...)` and the chained `.column(...)` below are different calls: `field.column(descriptor)` builds a field from a type description, and `.column(name)` on an existing field sets its column name. Extension packs add more helpers of their own. -For a primary key whose value your app generates, use `field.id.uuidv4String()`, `field.id.uuidv7String()`, `field.id.ulid()`, `field.id.nanoid()`, `field.id.cuid2()`, or `field.id.ksuid()`. Each names a different format for the generated ID string, and each marks the field as the primary key for you. `field.id.uuidv4Native()` and `field.id.uuidv7Native()` do the same in a `uuid` column. +For a primary key whose value your app generates, use `field.id.uuidv4String()`, `field.id.uuidv7String()`, `field.id.ulid()`, `field.id.nanoid()`, `field.id.cuid2()`, or `field.id.ksuid()`. They all generate the ID in your app, and `uuidv4String` is the common choice. Each marks the field as the primary key for you. `field.id.uuidv4Native()` and `field.id.uuidv7Native()` do the same in a `uuid` column. `field.temporal.createdAt()` fills the column with `now()` when the row is created. `field.temporal.updatedAt()` fills it on every create and every update. `field.namedType(x)` takes an enum or a type from an extension pack. @@ -194,20 +167,22 @@ Every field builder supports chained modifiers: - `.optional()` makes the field nullable. - `.default(value)` sets a literal default. `.defaultSql(expression)` sets a default the database computes, and the argument is a SQL expression as a string: `.defaultSql("now()")`. -- `.unique()` adds a unique constraint. `.id()` marks the primary key. +- `.unique()` adds a unique constraint. `.id()` marks the primary key, for a key that is not generated, such as an integer you set yourself. - `.column("column_name")` sets the column name in the database when it differs from the field name. ## Enums `enumType` declares an enum, the type its values are stored as, and its members, as `Priority` does in the full example above. -The second argument says how each member is stored. `codecId` is a string of the form `pg/@1`. `nativeType` is the column type in the database. Write `as const` after the object. To store the members as integers, pass `{ codecId: "pg/int4@1", nativeType: "int4" } as const`. [Raw queries](/orm/reference/raw-queries#binding-a-bare-value-with-param) lists more type ids. +The second argument says how each member is stored. `codecId` names the PostgreSQL type plus a version, always `@1` today. `nativeType` is the column type in the database. Write `as const` after the object. To store the members as integers, pass `{ codecId: "pg/int4@1", nativeType: "int4" } as const`. [Raw queries](/orm/reference/raw-queries#binding-a-bare-value-with-param) lists more type ids. Each `member(name, storedValue)` pairs the TypeScript-visible name with the value stored in the column. Fields reference the enum with `field.namedType(Priority)`, and defaults reference a member as `Priority.members.Low`. Include the enum in the returned `enums` map so it reaches the two files. Members are stored in whatever column type you name here, `text` above. For a real PostgreSQL `enum` type, declare it with `nativeEnum` and type the field with `pg.enum`, both exported by `@prisma/orm-postgres/contract-builder`. The type is created in the database under the name you give it: ```typescript +import { field, model, nativeEnum, pg } from "@prisma/orm-postgres/contract-builder"; + const Role = nativeEnum("Role", "user", "admin"); const Account = model("Account", { @@ -217,21 +192,23 @@ const Account = model("Account", { }); ``` +`Role` does not go in the returned `enums` map. Using it on a field is enough to create the type in the database. + ## Relations Relations are declared on the model builder with `.relations(...)` and the `rel` helpers, as the full example above shows. `rel.hasMany(Model, { by })` names the foreign key field on the other model. `rel.hasOne(Model, { by })` is the same with at most one row on the other side. `rel.belongsTo(Model, { from, to })` maps the local foreign key field to the field it points at. -`rel.manyToMany(Model, { through, from, to })` goes through a join table. You declare the model for the join table yourself and pass it as `through`. `from` and `to` name the two foreign key fields on that model: +`rel.manyToMany(Model, { through, from, to })` goes through a join table. You declare the model for the join table yourself and pass it as `through`. Below, `PostTag` is that model, and it holds the two foreign key fields `postId` and `tagId`: ```typescript -User.relations({ - teams: rel.manyToMany(Team, { through: Membership, from: "userId", to: "teamId" }), +Post.relations({ + tags: rel.manyToMany(Tag, { through: PostTag, from: "postId", to: "tagId" }), }); ``` -`rel.belongsTo` on its own does not create a foreign key constraint in the database. Ask for one in either of two places. Chain `.sql(...)` on the relation itself, inside the `.relations({ ... })` object: +`rel.belongsTo` on its own does not create a foreign key constraint in the database. Ask for one by chaining `.sql(...)` on the relation itself, inside the `.relations({ ... })` object, as the full example above does: ```typescript Post.relations({ @@ -239,9 +216,9 @@ Post.relations({ }); ``` -Or list `constraints.foreignKey(...)` in the model's `.sql({ foreignKeys: [...] })`, as the full example above does. Both forms take `name`, `onDelete`, and `onUpdate`. +`fk` takes `name`, `onDelete`, and `onUpdate`. -Prisma ORM checks that the two sides of a relation name the same number of fields. It does not check that their column types match, so choose a field helper that produces the same column type as the key you point at. +Prisma ORM does not check that the column types on the two sides of a relation match, so choose a field helper that produces the same column type as the key you point at. `rel.hasMany`, `rel.hasOne`, `rel.belongsTo`, and `rel.manyToMany` also accept the model name as a string. Pass the model object, as above, and a typo is a compile error. Pass a string and the typo is reported when `prisma contract emit` builds the contract. @@ -249,16 +226,13 @@ Prisma ORM checks that the two sides of a relation name the same number of field `.sql(...)` maps a model to its table. The object form covers the common case: `User.sql({ table: "user" })`. -You can chain `.relations(...)`, `.attributes(...)`, and `.sql(...)` in any order, and you can skip any of them. Each returns a new model builder and leaves the one you called it on unchanged. `rel.belongsTo(User, ...)` may point at either the bare `User` or the chained one, because both carry the same model name. A model with no relations calls `.sql({ table })` straight after `model(...)`. +You can chain `.relations(...)`, `.attributes(...)`, and `.sql(...)` in any order, and you can skip any of them. You can pass `User` to `rel.belongsTo(User, ...)` before chaining `.sql()` onto it. A model with no relations calls `.sql({ table })` straight after `model(...)`. -The callback form gives you the model's columns as `cols` and the constraint builders as `constraints`. Use it for indexes and for foreign keys with explicit names: +The callback form gives you the model's columns as `cols` and the constraint builders as `constraints`. Use it for indexes: ```typescript Post.relations({ ... }).sql(({ cols, constraints }) => ({ table: "post", - foreignKeys: [ - constraints.foreignKey(cols.userId, User.refs.id, { name: "post_userId_fkey" }), - ], indexes: [ constraints.index([cols.userId]), constraints.index([cols.userId, cols.createdAt], { name: "post_user_created_idx" }), @@ -273,16 +247,31 @@ Post.relations({ ... }).sql(({ cols, constraints }) => ({ For a primary key made of two fields, use `.attributes(...)` instead of `.sql(...)`, and build it from the field references: ```typescript -Membership.attributes(({ fields, constraints }) => ({ - id: constraints.id([fields.userId, fields.teamId]), +PostTag.attributes(({ fields, constraints }) => ({ + id: constraints.id([fields.postId, fields.tagId]), })); ``` -The keys in that object are fixed. `id` takes one constraint, and `uniques` takes a list, so `uniques: [constraints.unique([fields.userId, fields.teamId])]` makes a unique constraint across two fields. `cols` in `.sql(...)` and `fields` in `.attributes(...)` are the same model fields under two names. For a key made of one field, `.id()` on the field is enough, and the `field.id.*` helpers already do it. +That object accepts exactly two keys, `id` and `uniques`. `id` takes one constraint, and `uniques` takes a list, so `uniques: [constraints.unique([fields.postId, fields.tagId])]` makes a unique constraint across two fields. `cols` in `.sql(...)` and `fields` in `.attributes(...)` are the same model fields under two names. For a key made of one field, `.id()` on the field is enough, and the `field.id.*` helpers already do it. ## Extension types -An extension pack is an npm package that adds column types to the builder. Declare packs in `defineContract`'s options and the `type` helper exposes their constructors. The full example above imports `pgvector` from `@prisma/orm-extension-pgvector/pack`, lists it in `extensions`, builds `type.pgvector.Vector(1536)` in the returned `types` map, and uses it on a field with `field.namedType(types.Embedding1536)`. +An extension pack is an npm package that adds column types to the builder. List packs in `defineContract`'s options object, and the `type` helper exposes their constructors: + +```typescript title="src/prisma/contract.ts" +import pgvector from "@prisma/orm-extension-pgvector/pack"; +import { defineContract } from "@prisma/orm-postgres/contract-builder"; + +export const contract = defineContract({ extensions: { pgvector } }, ({ field, model, type }) => { + const types = { Embedding1536: type.pgvector.Vector(1536) } as const; + const Post = model("Post", { + fields: { id: field.id.uuidv4String(), embedding: field.namedType(types.Embedding1536) }, + }); + return { types, models: { Post: Post.sql({ table: "post" }) } }; +}); +``` + +The key on `type.pgvector` is the pack's own name, not the name you gave the import. `Embedding1536` is a name you choose, and it appears under that name in `contract.d.ts`. Return the `types` map so the name reaches the two files. Add the same pack to `prisma.config.ts`. It goes inside `ormConfig({ ... })`, beside `contract`, and the config imports the pack's `/control` export while the contract file imports its `/pack` export: @@ -301,11 +290,12 @@ export default definePrismaConfig({ ## Keep the contract file free of changing values [#keep-the-contract-file-pure] -The contract file describes structure. Three rules keep it usable: +The contract file describes structure. These rules keep it usable: -- Do not read `process.env`, the current time, or random values into the contract. Prisma ORM compares a hash of the contract across runs, and a contract that changes every run fails that comparison. +- Do not read `process.env`, the current time, or random values into the contract. A contract built from those values makes `npx prisma contract emit` produce a different `contract.json` on each run. - Keep field values plain: strings, numbers, booleans, and the builder's own objects. Functions, class instances, and `Date` objects do not serialize. -- Keep the file free of side effects. `prisma contract emit` runs the file, but it does not type-check it. Type errors show in your editor and when you run `tsc`. +- Keep the file free of side effects. `npx prisma contract emit` runs the file, so anything it does while loading, such as writing a file or calling a service, happens every time you run the command. +- `npx prisma contract emit` runs the file but does not type-check it. Type errors show in your editor and when you run `tsc`. Configuration that legitimately varies per environment, such as the database URL, belongs in `prisma.config.ts`, not in the contract. diff --git a/docs/orm-docs-audit/c21-contract-authoring/capabilities.round3.md b/docs/orm-docs-audit/c21-contract-authoring/capabilities.round3.md new file mode 100644 index 00000000000..7a8c1a7cd8e --- /dev/null +++ b/docs/orm-docs-audit/c21-contract-authoring/capabilities.round3.md @@ -0,0 +1,56 @@ +Read once, top to bottom, as a Prisma 6 user who has never seen this version. + +## Sentences I could not restate after one reading + +1. Line 9 (the whole opening paragraph). It carries six separate ideas in one block: contracts replaced `schema.prisma`; databases differ; `contract emit` writes two files; there is a `capabilities` section; you must not edit it; errors name a "key". By the third sentence I had lost the thread. Plainest version: split it. "Your contract file `contract.prisma` replaces `schema.prisma`. Running `npx prisma contract emit` produces `contract.json`. One part of that file, `capabilities`, lists the database features you can use. Prisma writes it; you never edit it." + +2. Line 13: "Three things each say which features they support: your database, your database package, and any extension packages listed in `prisma.config.ts`." I read this as "emit connects to my database and asks it". Line 84 then says the opposite: "not read from the live database". I still do not know which is true. If the database's contribution is really "which database you named in config", say that. + +3. Line 62: "The call throws as soon as you make it, so a test that calls it catches it." I cannot restate this. Throws as soon as you make it *instead of when?* I assume you mean it throws at the call and not when you await the query, but the page never says what the alternative was, so the sentence has no content for me. + +4. Line 78: "PostgreSQL has all three `sql` keys above, and SQLite has none of them." Line 30 told me "Keys under `sql` are shared across SQL databases." SQLite is a SQL database. So either the group name does not mean what line 30 said, or this sentence is wrong. Plainest version: "The `sql` group holds keys that more than one SQL database may support. It does not mean every SQL database supports them." + +5. Line 80: "The methods that need these keys exist only on the SQL query builder, and MongoDB does not have one, so nothing on MongoDB reads the `capabilities` section." This contradicts line 45, which says `contract emit` itself fails on a missing capability. Does emit skip that check on MongoDB? I cannot tell. + +6. Line 84, first sentence: "The keys say what your installed software supports." "Installed software" is a new term for the same three things named in line 13. Third name for one concept. + +## Words and phrases I had to guess + +- **"your setup"** (lines 3, 9, 13, 78). Guessed: database + database package + extensions. Never defined, and it is the subject of the page's main claim. +- **"key"** (line 9, before any example). Guessed: a JSON property name. Confirmed only at line 15. +- **"target"** and **"adapter"** (error text, line 48). The page tells me both mean the database package, which is the fourth and fifth name for the same thing. I guessed right only because line 53 told me. +- **"query-builder method"** (line 55) and **"the SQL query builder"** (line 80). Guessed: some new API that is not `prisma.user.findMany()`. In Prisma 6 there was no query builder. The page assumes I know what this is and never links to it until the very end. +- **"/control"** in `@prisma/orm-extension-pgvector/control` (line 33). Guessed: a subpath export for config-time code. No idea why it is not the package root. +- **"built-in extension packages"** (line 68). Guessed: first-party packages that you nonetheless have to install. The phrase reads as self-contradictory. +- **"Checking the live database is a separate job"** (line 84). Guessed: "a different command does that". +- **"db"** (line 64). Guessed: the replacement for `PrismaClient`. + +## "So what do I actually type?" + +- **Line 13, "you choose it by the import in `prisma.config.ts`".** I am never shown `prisma.config.ts`. Not once, in full. The only two glimpses are a four-line fragment (lines 35–38) that starts mid-object with `orm: ormConfig({`, and a bare import path at line 53. I do not know what encloses `orm:`, so I do not know where to paste the pgvector lines. +- **Line 53, "switch to a database that supports list fields... change the import".** Changing one import does not move me from SQLite to PostgreSQL. I need a connection string, a running Postgres, and my data moved. The page implies a one-line fix. I would have tried it and been stuck. +- **Line 30, installing pgvector.** `npm install`, add two lines, re-emit. Nothing about the database side. pgvector is a PostgreSQL server extension. Do I need it installed in Postgres? Does `contract emit` fail or silently omit the key if it is not? This is the exact "so what do I actually type" gap that would cost me an afternoon. +- **Line 64, reading capabilities in my own code.** One expression, `db.contract.capabilities.sql?.lateral`, and no surrounding code. I wanted a small working example: check the key, run the fast query, otherwise fall back. Also, `?.` returns `undefined` when the group is missing, not `false`. The page says it "is `true` when lateral joins are available" and stops there, so I would have written `=== false` and got it wrong. +- **Line 55, the runtime error.** I am told `distinctOn()` throws. I am never shown how to call `distinctOn()` or where it lives. I cannot reproduce, and I cannot fix by example. +- **Line 88, the agent prompt** "Which keys does the `capabilities` section of our contract require". The `capabilities` section does not require keys; my code requires keys that the section may or may not list. The prompt as written asks a question that does not parse. + +## Places that explain internals when I wanted instructions + +- **The whole "Where the `capabilities` section comes from" section (lines 11–39).** The first half explains how the tool assembles a file I was just told I must never edit. As a reader my questions were "how do I know if I have a key" and "how do I get one I lack". The provenance story answers neither. The only actionable content is the pgvector install, which is buried at the end of a paragraph about key-naming. +- **Line 84, "so the same contract behaves the same way in every environment".** This is a design rationale. It does not tell me to do anything, and it arrives in the same paragraph as `db verify` and an extensions link, so three unrelated things share one paragraph. +- **Line 30, "A key is written group first."** A naming rule presented before I have any reason to write a key myself. I do not write keys; I read them in errors. + +## Could I do what the page is for, after one reading? + +Partly. If one of the two errors in the page landed in my terminal verbatim, I could act: remove the list field, or install the extension and re-emit. That is a real win and it is the page's strongest part. + +What I still would not know: + +- Whether `contract emit` talks to my live database. The page says both. +- What the SQL query builder is, or how to call `distinctOn()` / `lateralJoin()` at all. I cannot use, or avoid, a feature I do not know how to invoke. +- What a complete `prisma.config.ts` looks like, so I cannot apply the pgvector diff with confidence. +- Whether adding a pgvector key needs anything done inside PostgreSQL itself. +- What `db verify` actually checks, or whether it would catch a capability problem. The page says it is "a separate job" and links away. +- Whether any of this applies to MongoDB. Lines 45 and 80 disagree. + +One more thing, blunt: the same concept is named five ways across this page — "your setup", "your database package", "target", "adapter", "installed software". Two of those come from error messages you do not control, but the prose adds three more on top. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/psl-syntax.round3.md b/docs/orm-docs-audit/c21-contract-authoring/psl-syntax.round3.md new file mode 100644 index 00000000000..729832d6a87 --- /dev/null +++ b/docs/orm-docs-audit/c21-contract-authoring/psl-syntax.round3.md @@ -0,0 +1,98 @@ +## Sentences I could not restate after one reading + +**1.** "named types: one name for a type and the database column type it uses." +Stopped me: this promises two things bound together, but the only example, `ShortName = VarChar(35)`, shows one thing. I could not tell which half of `VarChar(35)` is "the type" and which is "the database column type." Plainer: "named types: give a database column type a name you can reuse on many fields." + +**2.** "base models and variants: a variant model adds fields to a base model, and one field's value says which variant a row is." +Stopped me: three ideas in one sentence, and I did not yet know the word "variant" in this product. Plainer: "base models and variants: you can store several kinds of record in one table. A base model holds the shared fields; each variant adds its own. One column says which kind each row is." + +**3.** "A `.prisma` extension selects PSL authoring." +Stopped me: I could not work out what "selects" means here — does the file extension switch Prisma into a mode? What is the other mode, and what extension picks it? Plainer: "If the path ends in `.prisma`, Prisma reads it as PSL. If it ends in `.ts`, Prisma reads it as a TypeScript schema." + +**4.** "A native PostgreSQL type such as `VarChar(35)` or `Timestamptz` is written in the same position as one of those." +Stopped me: "in the same position as one of those" made me re-read to find the antecedent. Plainer: "You can write a PostgreSQL type such as `VarChar(35)` or `Timestamptz` wherever you would write `String` or `DateTime`." + +**5.** "`@updatedAt` no longer exists. Write the call where the type would go:" +Stopped me: "the call" — a call to what? And the example field has no type at all, which contradicts every other field on the page. Plainer: "`@updatedAt` is gone. Instead of a type, write `temporal.updatedAt()` as the field's type." + +**6.** "An alias binds a type and its database column type under one name." +Same problem as (1). The example does not show two things being bound. + +**7.** "Write the `@1` as shown, because there is no other version yet." +Stopped me: a version of *what*? The encoding? The PostgreSQL type? The Prisma package? The sentence tells me to copy it without telling me what it is. Plainer: "`@1` is the version of the storage format. Only version 1 exists, so always write `@1`." + +**8.** "That model must follow two rules. It holds a foreign key to each side, and each foreign key references that side's full `@id`." +Stopped me: "full `@id`" — I guessed this means "every field of a composite primary key, not just one of them," but the page never says so, and the example only shows single-field keys. + +**9.** "Break either rule and `npx prisma contract emit` throws an error whose `code` is `PSL_JUNCTION_ID_NOT_FK_COVERING` when the `@@id` lists anything other than the two foreign-key fields, or `PSL_JUNCTION_TARGET_FK_NOT_ID` when a foreign key does not reference the other side's `@id`. The message names the list field it could not match." +Stopped me: one sentence, two error codes, two conditions, and a nested clause. I had to read it three times. Split it into two sentences or a two-row table. + +**10.** "On PostgreSQL you choose where a variant's own fields are stored, and the variant's `@@map` is the choice." +Stopped me: "the `@@map` is the choice" is not a statement I can act on. The real rule is hidden in the next sentence. Plainer: "On PostgreSQL, whether you write `@@map` on the variant decides where its fields live." + +**11.** "Rows with `type = "bug"` are `Bug` records." +Minor, but `type` here is a field name I just read as a PSL keyword three sections earlier (`type Address`). I stalled on which one it meant. + +## Words and phrases I had to guess + +- **"contract"** — I guessed it is what I used to call the schema. The page also calls it "the schema" at the end ("don't write the schema by hand"), which made me doubt my guess. +- **"discriminator"** — I guessed: the column whose value says which kind of row this is. The page never defines it; it only uses it in `@@discriminator(type)`. +- **"variant"** — I guessed: a subclass. +- **"value object"** — I guessed: a nested object with no table of its own. The page does define it, which helped. +- **"extension pack"** — I guessed: a plugin npm package. +- **"`pg/text@1`"** — I guessed `pg` = PostgreSQL, `text` = column type. The page confirmed both. `@1` I still cannot explain. +- **"`db.orm.public.Task`"** — I guessed `db.orm` is a namespace and `public` is a schema. The page confirms `public` only. +- **"full `@id`"** — guessed: all fields of a composite key. +- **"`temporal`"** — guessed: a built-in namespace of date/time helpers. The page says it is built in but not what else is in it. + +## Places I asked "so what do I actually type?" and got no answer + +**1. The many-to-many example does not connect.** You show: +```prisma +model Post { tags Tag[] } +model Tag { posts Post[] } +model PostTag { ... } +``` +Nothing in `Post` or `Tag` mentions `PostTag`. How does Prisma know `tags Tag[]` goes through `PostTag` and not some other join model? If two join models exist between the same pair, what do I type? Also, neither `Post` nor `Tag` has an `id` in this snippet, yet `PostTag` references `[id]` on both. + +**2. `temporal.updatedAt()` — what is the field's TypeScript type?** Is it a `DateTime`? Can it be `?`. Can I set it by hand on a write? Are there other `temporal.*` calls? Nothing. + +**3. Plain `String` — what column do I get?** The page explains `VarChar(35)` and says "The `@db.` attributes are gone," but never says what plain `String`, `Int`, or `DateTime` map to on PostgreSQL. That is the first thing I need, because most of my fields are plain `String`. + +**4. One-to-one has no code.** "make the other side singular instead of a list, and put `@unique` on the foreign-key field" — every other relation shape gets a snippet; this one is the easiest to get wrong and gets prose only. + +**5. `native_enum` has loose ends.** Can I write `@default(...)` on a `pg.enum(Role)` field? Do I write the member name or the quoted value? Is `Role` usable as a named type in `types`? Is there a `mongo.enum`? + +**6. Lists of value objects on PostgreSQL.** You say "a value object field is stored in a single `jsonb` column." Is `addresses Address[]` also one `jsonb` column holding an array? You show the field in the example and then do not cover it. + +**7. Variants on MongoDB and the `variant()` call.** `db.orm.public.Task.variant('Bug')` — `public` is the PostgreSQL schema. On MongoDB, what goes in that slot? I am left unable to type the MongoDB version of the line you just showed me. + +**8. Where does `// use prisma-8` have to be?** "Keep the first line" — must it be line 1 exactly? What if I want a licence comment above it? And this is explained 80 lines *after* the first time it appears in code. + +**9. Adding an extension pack.** The config snippet shows `extensions: [pgvector]`, but not the `npm install` line. The page says "Using extensions covers installing a pack" — but I am already here, mid-task, and cannot finish without leaving. + +**10. `types` versus `type`.** `types { ... }` declares aliases; `type X { ... }` declares a value object. Those are one character apart and mean entirely different things, and the page never points that out. I will type the wrong one. + +## Places that explain the internals when I only wanted to know what to do + +- "Generated defaults such as `@default(uuid())` are produced by Prisma ORM before each write, not by the database." Tell me the consequence I care about: a row inserted by raw SQL or another application will not get a value. +- "`npx prisma contract emit` resolves extension types through the packs listed in the config, so list the pack before using its types." The first clause is machinery. Only "list the pack before using its types" is an instruction. +- The two `PSL_JUNCTION_*` error codes. Useful if I am already broken, but they sit in the middle of the how-to, ahead of the sentence that tells me which relation shape to pick. +- "In `pg/text@1`, `pg` is PostgreSQL and `text` is the column type, so the values are stored as `text`." Fine, but it is the decoding of a string I was going to copy anyway. + +## Could I do what the page is for, after one reading? + +Partly. I could write a plain model with fields, IDs, `@map`, `@@map`, `@unique`, `@@index`, and a one-to-many relation, and I could write the config file. That is the majority of what I need, and I got it. + +I could **not** do these without a second pass or a guess: + +- A many-to-many relation. The example does not show how the two list fields find the join model, so I would type it and hope. +- A one-to-one relation. No code shown. +- `temporal.updatedAt()`. I do not know the resulting type or whether I can write to the field. +- Any base model and variant on MongoDB, or the `variant()` query on MongoDB. +- Choosing a column type for ordinary fields, because the page never says what plain `String` becomes. +- A `native_enum` with a default. + +I also finished unsure of two words the page leans on the whole way through: what exactly a "contract" is as opposed to a schema, and what a "discriminator" is. Both are used before they are explained, and "discriminator" is never explained at all. + +Page read: `/Users/wmadden/Projects/prisma/web/.claude/worktrees/error-reference-follow-ups-81a8e7/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx` \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/the-contract-artifact.round3.md b/docs/orm-docs-audit/c21-contract-authoring/the-contract-artifact.round3.md new file mode 100644 index 00000000000..ca29fd3dc00 --- /dev/null +++ b/docs/orm-docs-audit/c21-contract-authoring/the-contract-artifact.round3.md @@ -0,0 +1,74 @@ +Read once, top to bottom, as a Prisma 6 user who has never seen this version. + +## Sentences I could not restate after one reading + +**Line 9:** "Your contract, [the one description of your data and how it is stored](/orm/contract-authoring/the-data-contract), is one source file" + +"the one description" stopped me. I could not tell if "one" means "single" or is the start of a phrase like "the one that...". Plainest version: "Your contract is a single file that describes your data and how it is stored." + +**Line 25:** "Applying a migration records which contract the database now matches, so you do not sign it yourself afterwards." + +Two problems. "Records" where? Nothing has told me yet that there is a table anywhere. And "sign" has not been defined at this point — it first appears as a bare command name in the table on line 13. I only learned what signing is twelve lines later, on line 37. Plainest version, if it moved after the hashes section: "Migrations write the marker row for you. Only run `prisma db sign` when the database already matches the contract but no marker row exists." + +**Line 35:** "`executionHash` covers the defaults Prisma ORM applies before a write." + +I can restate the words but not the point. Line 37 then says signing records `storageHash` and `profileHash`. So `executionHash` is described and never used. I finished the page not knowing what reads it or what breaks when it changes. + +**Line 110:** "This is the section the migration tooling compares between versions and `prisma db verify` checks the live schema against." + +Two subjects, two verbs, and a dangling "against" at the end. I had to re-read to work out that `db verify` compares this section to the live schema. Plainest version: "Migration tooling compares this section between two contract versions. `prisma db verify` compares it to the live database." + +**Line 120 and 137:** "One type per hash, such as `StorageHash` and `ProfileHash`. The hash string is part of the type." / "If you regenerate `contract.json` and not `contract.d.ts`, TypeScript catches it before `prisma db verify` has to." + +I cannot picture how this works. Both files come out of one command. How do I ever end up with one regenerated and not the other? And what does TypeScript actually compare the hash type against, given the `.json` is plain data? The page states the outcome and skips the only part I would need to believe it. + +**Line 155:** "The import path `./contract.d` is written that way on purpose. It is not a typo, and TypeScript resolves it to `contract.d.ts`." + +I believe you, but "on purpose" for what purpose? The sentence defends the spelling without saying why the ordinary `./contract` does not work. If I tidy it up later, I will not know what I broke. + +## Words and phrases I had to guess + +- **"contract"** — guessed: the new name for `schema.prisma`. The page never says so plainly, and I am a reader coming from the old version who needs that one line. +- **"profile"** in `profileHash` — guessed: the database target, from "covers which database the contract targets". But "profile" appears nowhere else, so I do not know what the noun refers to. +- **"codecId"**, `pg/text@1` — guessed: an identifier for the type-conversion code, with a version number. Line 109 says "the PostgreSQL type plus a version", which does not explain the word "codec". +- **"marker"**, `prisma_contract.marker` — guessed: a one-row bookkeeping table recording which contract the database matches. +- **"sign"** — guessed: write that marker row. Confirmed only at line 37, long after first use. +- **"extension pack"** (line 112, 113) — guessed: an npm package that adds database types. Never defined here, and the link is labelled "extension types", which is a different phrase. +- **"roots"** — guessed: the entry points of the client API. The key/value direction ("Each key is the table name, and its value names the model") took two readings against the JSON. +- **`StorageHashBase`, `ProfileHashBase`, `CodecTypes`** (lines 126–131) — appear in the example code and are not in the list of exports above them. I guessed they are internal types I never touch. +- **`prisma contract infer`** (line 25) — guessed: the replacement for `prisma db pull`. +- **"targetFamily": "sql"** — guessed from the bullet, fine. + +## Places I asked "so what do I actually type?" + +1. **How do I get a `contract.prisma` at all?** Line 9 assumes it exists. Line 9 says `prisma orm init` writes `db.ts`. Line 168 says `npm create prisma@latest`. Line 168 also says `npx prisma skills sync`. Three commands, no statement of which one starts a project. +2. **`DATABASE_URL`** (line 155): "The connection string comes from the `DATABASE_URL` environment variable." The code imports `dotenv/config`, so presumably a `.env` file. The page never says to create one or what goes in it. +3. **The JSON import** (line 147): `import contractJson from "./contract.json" with { type: "json" }`. That syntax needs specific `tsconfig.json` and Node settings. If it fails to compile, this page gives me nothing. It is generated code, but the page is the one showing it to me. +4. **`namespace auth { }`** (line 41): mentioned in a subordinate clause with no example and no link to a fuller example. +5. **`extensions: [...]` in `prisma.config.ts`** (line 112): named twice, never shown. +6. **"Run `npx prisma db sign` only when the database already matches the contract and nothing recorded it"** (line 25): how do I check whether anything recorded it? `db verify`, presumably, but the page does not say to run it first. +7. **Making the startup check fail instead of warn** (line 155): "Run `prisma db verify` when you need the check to fail instead." That is a separate CLI command, not something my app can do at boot. Is there an option on `postgres({...})` for this? Not said either way. +8. **`AddressInput` / `AddressOutput` and `FieldInputTypes` / `FieldOutputTypes`** (lines 121–122): I am told they exist and what they hold. Not one line of code using them. + +## Places that explain the internals when I only wanted to know what to do + +- **The whole "Inside `contract.json`" section** (lines 39–113), about 75 lines, for a file that line 16 tells me never to edit. I read a field-by-field walkthrough of a generated file before I was told anything about writing one. +- **"The same source always produces the same two files"** (lines 27–31). Byte-for-byte reproducibility is a property of the tool. The one thing here I can act on — do not read env vars, the clock, or randomness in your contract file — is a single sentence buried in the middle, and it is punted to another page. +- **"The content hashes"** (lines 33–37) opens with "You never compute or compare them yourself" and then spends three sentences describing what each one covers. If I never touch them, tell me the one fact I need: a mismatch means the database and the contract disagree, and here is how to fix it. +- **`schemaVersion`, `codecId`, `roots`, the `domain`/`storage` split** — all file-format internals. +- **Line 108** drops two entire query APIs (`db.sql.public.user` and `db.orm.public.User`) inside a bullet about a JSON key. That is a major thing to learn about, introduced as a footnote to something I do not need. + +## Could I do what the page is for after one reading? + +Partly. + +I can do the loop on lines 18–23: edit the source, run `npx prisma contract emit`, run `migration plan`, read it, `db migrate`, and `db verify` in CI. I can commit both files and add the two-line CI check on lines 162–163. That part is clear and I would get it right. + +What I would still not know: + +- What a contract actually is compared to the `schema.prisma` I have used for two years. The page assumes I already know. +- When I need `prisma db sign` in real life. The one example given is "after `prisma contract infer`", and I do not know what that command does either. +- What `executionHash` is for. +- How to write anything in the source files. This page is entirely about the output. +- What to do when `CONTRACT.MARKER_MISMATCH` shows up in my production logs. The page tells me it warns and keeps going, which sounds like something I should act on, and then sends me to a CLI command I cannot run from inside my app. +- Whether `contract.d.ts` can ever actually get out of sync with `contract.json`, given one command writes both. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/the-data-contract.round3.md b/docs/orm-docs-audit/c21-contract-authoring/the-data-contract.round3.md new file mode 100644 index 00000000000..ab1f076d988 --- /dev/null +++ b/docs/orm-docs-audit/c21-contract-authoring/the-data-contract.round3.md @@ -0,0 +1,90 @@ +I read the page once, top to bottom, as a Prisma 7 user who has never seen version 8. + +## Sentences I could not restate after one reading + +**1. Line 30 — the header comment** + +> "Start every `.prisma` contract with `// use prisma-8`. Your editor's Prisma support only works on files that have that line." + +Two ideas that fight each other. "Start every" reads as a hard requirement. The reason given is only about my editor. So: does the CLI fail without it, or do I just lose syntax highlighting? What is "your editor's Prisma support" — the VS Code extension? Something else? Plainer: "Add `// use prisma-8` as the first line. Without it, the Prisma VS Code extension ignores the file. The CLI works either way." (If the CLI does *not* work either way, say that instead — that is the version I needed and did not get.) + +**2. Line 46 — the whole hash paragraph** + +> "`prisma contract emit` also writes a hash of the contract into `contract.json`. When you apply a contract change to the database, the command that applies it records that hash in a small table it creates in your database. Before its first query, `db` compares the two and logs a warning if they differ." + +Four moving parts in three sentences, and every noun is vague. "the command that applies it" — which command? (I only learn it is `prisma db migrate` thirty lines later.) "a small table it creates in your database" — named what? Do I need to know it exists when I review a migration? "Before its first query" — the first query of what, the process? per connection? "compares the two" — the two hashes, I assume, but "the two" has no clear antecedent because three things were just named. And `db` is used here as if I know what it is; the page does not tell me until line 77. + +Plainer: "`prisma contract emit` writes a hash of your contract into `contract.json`. `prisma db migrate` stores the same hash in a table in your database. The first time your app runs a query, Prisma compares the two hashes and logs a warning if they do not match." + +**3. Line 63 — the config paragraph** + +> "`definePrismaConfig` holds the whole config. `ormConfig` comes from your database package and holds the settings for that database" + +`ormConfig` does not exist. The code block imports `defineConfig as ormConfig`. So the prose names a thing that is only a local rename I made up in my own file. I stopped and re-read the import three times to confirm. Say "the `defineConfig` export from your database package, imported here as `ormConfig`." + +The same paragraph then packs in: what the two functions do, the three database packages, which one the examples use, two different init commands, and the four things those commands write. That is six unrelated facts in one paragraph. I could not hold them. + +**4. Line 75 — signing** + +> "so there is no separate signing step. Run [`prisma db sign`](/cli/db-sign) only when the database already matches the contract and nothing needs applying, for example after `prisma contract infer`." + +"Signing" is never defined. The page has never used the word before this sentence. I had to guess it means "write the hash into the database table." Also `prisma contract infer` is referenced here but not explained until line 81. Plainer: "`prisma db migrate` records the new hash for you. Run `prisma db sign` only to record the hash without applying anything — for example after `prisma contract infer`, when the database already matches." + +**5. Line 77 — `public`** + +> "`public` is the PostgreSQL schema, the namespace your tables are in, which is `public` unless you set one." + +The callout at line 38 just told me, in bold, that "the **schema** is your database's actual structure." Now "schema" means a PostgreSQL namespace. That is a third meaning for a word the page went out of its way to define. I re-read the callout to check whether I had misunderstood it. The sentence also defines `public` using the word `public` twice. Plainer: "`public` is the PostgreSQL namespace your tables live in. It is `public` unless you configured a different one." + +**6. Line 85 — the two-files warning** + +> "Do not keep both a `contract.prisma` and a `contract.ts`. If you had both, they could describe different things." + +"Could describe different things" tells me a risk, not a consequence. Does the CLI error? Silently use the one in the config and ignore the other? I could not restate what actually happens to me. + +**7. Line 94 — `type` blocks** + +> "[`type` blocks](/orm/contract-authoring/psl-syntax#value-objects), each a structured value stored inside its parent row with no table of its own." + +I understood "no table of its own" and "stored in a `jsonb` column." I did not understand what a `type` block *is for* or how it differs from a model, and the link text says "value objects," a term the sentence never uses. + +## Words and phrases I had to guess + +- **"the rest of the toolchain"** (line 32) — guessed: the CLI, the client, and my editor. Not stated. +- **"an exact, versioned record"** (line 32) — guessed: versioned as in checked into git, not as in "has a version number." The `package-lock.json` comparison is what let me guess. +- **"named types"** (line 93) — guessed: a reusable alias, from the appositive. Fine, but "reusable aliases for a type and its storage details" is itself guesswork about what "storage details" covers. +- **"capabilities"** — the link at line 44 says "which database features they need" and the card at line 135 says "Supported database features," but the URL is `/capabilities`. Guessed they are the same thing. +- **"extension packages"** (line 65) — the pgvector example saved me. Without it I had nothing. +- **"artifact"** — the section heading anchor and the card at line 128 use "artifact" for `contract.json`. The prose never does. Mild, but I noticed the mismatch. +- **"signing" / `db sign`** — guessed: recording the hash. See above. +- **"logs a warning"** (line 46) — guessed: to stdout, at app startup. Where do I see it? + +## "So what do I actually type?" + +1. **How does any of this reach my database?** The page tells me to run `prisma db migrate` but never mentions a connection string, `DATABASE_URL`, `.env`, or a `datasource` block. In Prisma 7 the connection lived in `schema.prisma`. The example contract has no datasource block at all. So where does the URL go now — `prisma.config.ts`? An env file? This is the single biggest hole. I could not run the three commands after reading this page. +2. **`npx` or not?** The code block says `npx prisma contract emit`. The prose says "run `prisma orm init`" and "run `npx prisma skills sync`." Inconsistent, so I do not know whether `prisma` is expected on my PATH. +3. **`npm create prisma@latest -- my-app`** — the bare `--` is unexplained and easy to mistype. And I am not told what it produces on disk beyond a list of four files with no directory layout. +4. **Where does `contract.json` go?** "beside the source" (line 75). So `src/prisma/contract.json`. I had to infer that from the config path. +5. **Converting my existing Prisma 7 project.** Line 34 sends me to two other pages. Fine for an overview page, but then line 63 tells me `prisma orm init` "adds Prisma ORM to a project you already have" — which is it for a Prisma 7 project? I would have run the wrong one. +6. **Is the `// use prisma-8` line something the generator writes, or something I type?** Not said. + +## Places the page explains internals when I wanted instructions + +- **Line 46, the whole paragraph.** I wanted "run these commands and your database stays in sync." I got a mechanism: a hash, a table the tool creates, a comparison before the first query. The one actionable sentence — "To fail instead of warn... run `prisma db verify`" — is buried at the end. +- **Line 44, "In Prisma ORM 7 the same description existed, but only the generated client could read it."** Interesting, changes nothing about what I type. +- **Line 85, "For the same models they write the same `contract.json`."** The useful part is "pick either, it does not matter." The identical-output detail is implementation reassurance. + +## Could I do what the page is for? + +The page is an orientation page, and on that narrow bar it mostly works. After one reading I can say what the contract is, that it replaced `schema.prisma`, that it compiles to `contract.json` and `contract.d.ts`, that I can write it in PSL or TypeScript but not both, and that the three-command loop is emit, plan, migrate. + +What I still would not know: + +- **How to point Prisma at my actual database.** No connection string anywhere. This is disqualifying for anyone trying to follow along. +- **Whether `// use prisma-8` is required or cosmetic.** +- **What "signing" is**, so I cannot tell when `prisma db sign` applies to me. +- **What happens if the hashes do not match** beyond "logs a warning" — can I still query? Is my app broken? +- **Which init command my Prisma 7 project needs.** +- **What a `type` block is for**, only where it is stored. + +The three-command block at line 69 is the best part of the page. Everything I needed to act on is either in that block or scattered around it in prose; the paragraphs between the blocks are where I got lost. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/typescript-schema-builder.round3.md b/docs/orm-docs-audit/c21-contract-authoring/typescript-schema-builder.round3.md new file mode 100644 index 00000000000..406deb79634 --- /dev/null +++ b/docs/orm-docs-audit/c21-contract-authoring/typescript-schema-builder.round3.md @@ -0,0 +1,100 @@ +# Reader review: `orm/contract-authoring/typescript-schema-builder.mdx` + +I have used Prisma 6 for two years. I have never seen "contract", `defineContract`, or any of this vocabulary before. + +## Sentences I could not restate after one reading + +**1. Line 163, the whole `defineContract` paragraph.** The worst part of the page. Eight facts in one block, several of them about the library's internal wiring rather than what I type: + +> "So `field`, `model`, and `rel` have two sources and either one works. The example takes `field`, `model`, and `type` from the argument, and imports `defineContract`, `enumType`, `member`, and `rel`." + +What stopped me: I now know the same three names exist in two places and either works, which is exactly the fact that makes me unable to decide what to write. Then the page tells me what its own example chose, which does not tell me what I should choose. Plainer: "Take `field`, `model`, `rel`, and `type` from the callback argument. Import `defineContract`, `enumType`, and `member` from the package, because you use them outside the callback." + +**2. Line 165, the MongoDB paragraph.** One paragraph, about fourteen separate facts, announced as "four more ways" and then listing more than four. I cannot hold it. This should be a bulleted list or its own page. I gave up halfway and skimmed. + +**3. Line 187.** + +> "To store a date with no time, write that model in [PSL](...) and give the field the `Date` type." + +This contradicts line 314: "A project names exactly one contract file in its config." If I can only have one contract file and it is TypeScript, I cannot "write that model in PSL". So what do I actually do if one model needs a date-only column — abandon TypeScript authoring entirely for the whole project? The page does not say, and it reads like the two sentences were written by different people. + +**4. Line 244.** + +> "Prisma ORM checks that the two sides of a relation name the same number of fields." + +I cannot resolve this. Every relation helper on this page names exactly one field per side (`by`, `from`, `to` are all single strings). Nothing on the page shows a relation over more than one field, so I do not know what this check is checking or how I would ever trip it. + +**5. Line 252.** + +> "Each returns a new model builder and leaves the one you called it on unchanged. `rel.belongsTo(User, ...)` may point at either the bare `User` or the chained one, because both carry the same model name." + +This is a description of how the builder is implemented. What I wanted to know is "can I pass `User` before I have chained `.sql()` onto it?" Answer: yes. Say that. + +**6. Line 281.** + +> "The keys in that object are fixed." + +Fixed to what? The paragraph mentions `id` and `uniques`. Are those the only two? Is there a third? "The object accepts exactly two keys, `id` and `uniques`" would have been readable. + +**7. Line 306.** + +> "Prisma ORM compares a hash of the contract across runs, and a contract that changes every run fails that comparison." + +Internal mechanism, and the consequence is vague. What fails, and when? Does `prisma contract emit` error out? Does a migration get regenerated? Plainer: "If the contract file produces different output on each run, `prisma ` reports ." + +**8. Line 308.** + +> "Keep the file free of side effects. `prisma contract emit` runs the file, but it does not type-check it. Type errors show in your editor and when you run `tsc`." + +The second and third sentences have nothing to do with side effects. Three unrelated ideas under one bullet. Also, the bullet tells me to avoid side effects and never says what counts as one, or what breaks if I have one. The other two bullets in this list both say what goes wrong; this one does not. + +## Words and phrases I had to guess + +- **"contract"** — I guessed this is the new name for what `schema.prisma` was. The page half-confirms it in the first sentence but then line 22 says the init command "asks how you want to write your **schema**", and the page title/URL says "schema builder". Three words for one thing. +- **`contract.json` and `contract.d.ts`** — named five times, never described. I guessed `.d.ts` is the types my client code uses. I do not know what `contract.json` is for or whether I commit either file. +- **`codecId: "pg/text@1"`** — I guessed "codec" means the rule for converting between the database value and the JavaScript value. I have no idea what the `@1` means — a version of what? The explanation on line 204 only restates the shape, and then sends me to a **raw queries** page to find valid values, which is a strange place to keep a list the enum section depends on. +- **`nativeType`** — guessed: the literal SQL column type. +- **"extension pack"** — line 285 defines it, 230 lines after line 55 uses it. On first read I guessed it was a plugin. +- **`ulid`, `nanoid`, `cuid2`, `ksuid`** (line 189) — I know `cuid` from Prisma 6. The page says "Each names a different format" and stops. I would be picking at random. +- **`pack` vs `control` exports** (line 287) — guessed that one is for the build tooling and one for the contract file. The page says which import goes where and never says why, so I cannot reason about it if I get it wrong. +- **`refs` vs `ref`** — `User.refs.id` on PostgreSQL, `User.ref("_id")` on MongoDB. I guessed they do the same thing. +- **`cols` vs `fields`** — line 281 says they are "the same model fields under two names", which confirms my guess but leaves me wondering why I have to remember two names. + +## Places I asked "so what do I actually type?" + +1. **`field.column(pg.enum(Role))`, line 215.** Line 198 told me `.column("column_name")` takes a string and sets the column name. Now the same method takes a type. Same name, two unrelated jobs, no acknowledgement. On top of that the snippet has no `import` line, so I do not know where `pg` comes from as a value (the prose says the package exports it, the code does not show it), and it never says whether `Role` has to go in the returned `enums` map the way `Priority` does. +2. **Does `defineContract` on PostgreSQL require the options object?** Every PostgreSQL example passes `{ extensions: { pgvector } }`. I have no extensions. Do I write `defineContract({}, ({ field, model }) => ...)` or `defineContract(({ field, model }) => ...)`? The page never shows the common case — a plain contract with no extensions — which is exactly the case I have. +3. **The two reasons to use TypeScript at all (line 15-16) are never demonstrated.** "build models in a loop from data you already keep in TypeScript, such as one model per entry in a list of table names" is the reason I would come to this page. There is no example of a loop, and no example of splitting models across modules. The page sells a capability and then never shows it. +4. **`.sql({ fk: { name } })` vs `.sql({ foreignKeys: [...] })`, lines 238 and 242.** Two spellings for one thing, `fk` singular object versus `foreignKeys` array. When do I use which? "Ask for one in either of two places" does not help me choose. +5. **Where does the `pgvector` key on `type.pgvector` come from?** I assume it is the key I used in `extensions: { pgvector }`, so renaming the import renames the accessor. Never stated. +6. **`type.pgvector.Vector(1536)` is stored under the key `Embedding1536`.** Is that name arbitrary, or does it appear in the database or in `contract.d.ts`? I do not know whether I am naming something or just holding a local variable. +7. **MongoDB indexes.** "indexes are an `indexes` option on the model" — no example, and the PostgreSQL index syntax uses `constraints.index([cols.x])` which clearly does not apply, since MongoDB has no `.sql(...)`. +8. **`.id()` on a field.** Line 197 lists it. Line 281 says the `field.id.*` helpers already do it. So when do I ever call `.id()` myself — an integer primary key? An existing column? Not shown. +9. **`rel.manyToMany`, line 229.** The example references `Membership` and `Team` out of nowhere. It says "You declare the model for the join table yourself" but does not show what that model has to contain (does it need its own `id`? just the two foreign keys?). The composite-key example on line 276 uses `Membership` again, still never defined. +10. **MongoDB relation arguments are inconsistent with PostgreSQL and with themselves.** `rel.hasMany("Post", { from: "_id", to: "authorId" })` uses a string model name; `rel.belongsTo(User, { from: "authorId", to: User.ref("_id") })` uses the object, and mixes a plain string `from` with a ref-object `to`. PostgreSQL's `hasMany` uses `{ by }` instead of `{ from, to }`. Four shapes across two examples. I would be copying and pasting and hoping. +11. **Do I commit `contract.json` and `contract.d.ts`, and when do I re-run `emit`?** Only after editing the contract? As part of a build script? Never said. + +## Places explaining how the tool works when I only wanted to know what to do + +- Line 163, the "two sources and either one works" explanation. +- Line 252, "Each returns a new model builder and leaves the one you called it on unchanged." +- Line 281, "`cols` in `.sql(...)` and `fields` in `.attributes(...)` are the same model fields under two names." +- Line 306, "Prisma ORM compares a hash of the contract across runs." +- Line 52, "`prisma contract emit` looks for those two names and nothing else" — the useful half is "export it as `contract` or as the default export"; the rest is about the tool's lookup. +- Line 246, "Pass a string and the typo is reported when `prisma contract emit` builds the contract" — this one earns its place, because it changes what I should type. Most of the others do not. + +## Could I do what the page is for, after one reading? + +Partly. I could copy the PostgreSQL example, rename models, change field helpers, and get a contract with a one-to-many relation and a table mapping. That is real. + +I could not do any of the following without rereading or guessing: + +- Write a contract with **no** extension packs, because the shape of `defineContract`'s first argument in that case is never shown. +- Use TypeScript for the reason the page told me to use TypeScript — generating models in a loop or splitting them across modules. No example exists. +- Add a real PostgreSQL `enum` type. The only snippet for it has no imports, reuses `field.column` for an unrelated purpose, and never says where `Role` goes in the returned object. +- Handle a date-only column, because the page's advice contradicts its own rule about having one contract file. +- Choose between `.sql({ fk })` and `.sql({ foreignKeys })`, or between importing `field`/`model`/`rel` and destructuring them. +- Pick an ID format, because the page lists six and describes none. +- Explain to a colleague what `contract.json` is, what `codecId`'s `@1` means, or why a pack has both a `/pack` and a `/control` export. + +The page is roughly two pages: a reference for someone who already knows the builder, and a tutorial that never quite commits to showing the plain case. The single biggest fix would be a short, complete, extension-free PostgreSQL example near the top — two models, one relation, no `pgvector`, no `codecId`, no callback destructuring puzzle — before any of the advanced material. \ No newline at end of file From dda080edbab4271170741b06f160575a2e90295f Mon Sep 17 00:00:00 2001 From: willbot Date: Mon, 14 Sep 2026 09:46:25 +0200 Subject: [PATCH 08/14] docs(orm8): contract-authoring pages, reader-review round four applied Last wording round, no page longer than before. The TypeScript page's native enum snippet takes field and model from the callback like the rest of the page, names the foreign-key action values, and says there is no auto-increment helper in rc.10. The PSL page names the native PostgreSQL types, says a mixed enum throws PSL_ENUM_CANNOT_INFER_TYPE, and explains // use prisma-8 at its first appearance. The artifacts page opens with what changed since schema.prisma and lists when a database is signed by hand. Co-Authored-By: Claude Fable 5.1 Signed-off-by: willbot Signed-off-by: Will Madden --- .../orm/contract-authoring/capabilities.mdx | 16 +-- .../orm/contract-authoring/psl-syntax.mdx | 46 ++++----- .../the-contract-artifact.mdx | 33 +++---- .../contract-authoring/the-data-contract.mdx | 24 ++--- .../typescript-schema-builder.mdx | 40 ++++---- .../capabilities.round4.md | 82 ++++++++++++++++ .../psl-syntax.round4.md | 78 +++++++++++++++ .../the-contract-artifact.round4.md | 77 +++++++++++++++ .../the-data-contract.round4.md | 94 ++++++++++++++++++ .../typescript-schema-builder.round4.md | 98 +++++++++++++++++++ 10 files changed, 506 insertions(+), 82 deletions(-) create mode 100644 docs/orm-docs-audit/c21-contract-authoring/capabilities.round4.md create mode 100644 docs/orm-docs-audit/c21-contract-authoring/psl-syntax.round4.md create mode 100644 docs/orm-docs-audit/c21-contract-authoring/the-contract-artifact.round4.md create mode 100644 docs/orm-docs-audit/c21-contract-authoring/the-data-contract.round4.md create mode 100644 docs/orm-docs-audit/c21-contract-authoring/typescript-schema-builder.round4.md diff --git a/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx b/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx index ac01931e330..2d3af44c340 100644 --- a/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx @@ -6,7 +6,7 @@ metaDescription: 'How a Prisma ORM contract records which database features you url: /orm/contract-authoring/capabilities --- -Your [contract](/orm/contract-authoring/the-data-contract), the `contract.prisma` file that replaced `schema.prisma`, describes your data. [`npx prisma contract emit`](/cli/contract-emit) writes `contract.json` and `contract.d.ts` beside it. The `capabilities` section of `contract.json` lists the database features you can use, under keys such as `sql.lateral`. Prisma ORM writes that section, and you never edit it. A feature that is not listed fails right away, with an error that names its key, instead of reaching the database and failing there. +Your [contract](/orm/contract-authoring/the-data-contract), the `contract.prisma` file that replaced `schema.prisma`, describes your data. [`npx prisma contract emit`](/cli/contract-emit) writes `contract.json` and `contract.d.ts` beside it. The `capabilities` section of `contract.json` lists the database features you can use, under keys such as `sql.lateral`. Prisma ORM writes that section, and you never edit it. If you use a feature that is not listed, Prisma ORM raises an error instead of sending the query to the database. ## Where the `capabilities` section comes from [#where-capabilities-come-from] @@ -21,7 +21,7 @@ The keys come from your database package (`@prisma/orm-postgres`, `@prisma/orm-s } ``` -A key is written group first, so `pgvector.cosine` under `postgres` is `postgres.pgvector.cosine`. The `sql` group holds keys that more than one SQL database may support. SQLite reports `false` for most of them. Keys under `postgres` belong to PostgreSQL only. Extension packages add keys of their own, and `pgvector.cosine` comes from the pgvector package. Install it with `npm install @prisma/orm-extension-pgvector`, list it in `prisma.config.ts`, and run `npx prisma contract emit` again: +Error messages and the table below name a key group first, `postgres.pgvector.cosine`. The `sql` group holds keys that more than one SQL database may support. SQLite reports `false` for most of them. Keys under `postgres` belong to PostgreSQL only. Extension packages add keys of their own, and `pgvector.cosine` comes from the pgvector package. Install it with `npm install @prisma/orm-extension-pgvector`, list it in `prisma.config.ts`, and run `npx prisma contract emit` again: ```typescript title="prisma.config.ts" import { definePrismaConfig } from "prisma/config"; @@ -37,11 +37,11 @@ export default definePrismaConfig({ }); ``` -pgvector is also a PostgreSQL server extension, and the first migration the package ships installs it in PostgreSQL for you. +pgvector is also a PostgreSQL server extension, and the package ships a migration that installs it. Apply it with `npx prisma db migrate`, see [Using extensions](/orm/extensions/using-extensions). Run `npx prisma contract emit` after every change to the contract or to the extension list. ## What the `capabilities` section controls [#what-capabilities-gate] -**When the contract is built.** If your contract uses a feature your database cannot store, `npx prisma contract emit` fails. SQLite has no `scalarList` key, so a list field such as `tags String[]` fails on SQLite: +**When the contract is built.** If your contract uses a feature your database package does not support, `npx prisma contract emit` fails. SQLite has no `scalarList` key, so a list field such as `tags String[]` fails on SQLite: ```text Field "User.tags" is a scalar list, but target "sqlite" does not support @@ -49,7 +49,7 @@ scalar lists (the adapter does not report the "scalarList" capability). Remove the list or author it against a target that supports scalar lists. ``` -In that message, "target" and "adapter" both mean your database package, which is `@prisma/orm-sqlite` here. Do one of the two: remove the list field, or move to a database that supports list fields. To move to PostgreSQL, change the import and the connection string in `prisma.config.ts`, then set up the new database. +In that message, both words mean your database package, and the message's `"scalarList"` is the `sql.scalarList` key. Do one of the two: remove the list field, or move to a database that supports list fields. To move to PostgreSQL, change the `@prisma/orm-sqlite/config` import and the connection string in `prisma.config.ts`, and create the new database. **When you call a method on the [SQL query builder](/orm/reference/sql-query-builder).** A method that needs a feature throws an error whose `code` is `ORM.CAPABILITY_MISSING`. The message names the method and the key: @@ -57,7 +57,7 @@ In that message, "target" and "adapter" both mean your database package, which i distinctOn() requires capability postgres.distinctOn ``` -That message means the method is not available on the database you are connected to. Rewrite the query without it, or move to a database that supports the feature. If the key comes from an extension package, install that package, list it in `prisma.config.ts`, and run `npx prisma contract emit` again. The error arrives when you call the method, not when you `await` the query, so a test that calls it catches it. Your own code can read the same list through `db`, the client you create once in `src/prisma/db.ts`. A value such as `db.contract.capabilities.sql?.lateral` is `true`, `false`, or `undefined` when the whole group is absent: +That message means the method is not available on the database package you chose. Rewrite the query without it, or move to a database that supports the feature. If the key comes from an extension package, install that package and list it in `prisma.config.ts`. You do not have to run the query to get the error, so a test that only builds the query still fails. Your own code can read the same list through `db`, the client you create once in `src/prisma/db.ts`, which `npx prisma orm init` writes. A value such as `db.contract.capabilities.sql?.lateral` is `true` or `false`, or `undefined` if the whole group is missing. For a key with a dot in it, write `db.contract.capabilities.postgres?.["pgvector.cosine"]`: ```typescript if (db.contract.capabilities.sql?.lateral) { @@ -77,11 +77,11 @@ A few of the keys your database package and extension packages supply: | `postgres.distinctOn` | `DISTINCT ON` queries (`distinctOn()`) | | `postgres.pgvector.cosine` | Cosine distance from the pgvector package | -PostgreSQL reports `true` for all three `sql` keys above, and SQLite reports `false` for them. This table is a sample, not the full list. Open `src/prisma/contract.json` in your project to see every key you have. The check that runs when the contract is built applies on every database, MongoDB included. The keys that methods check at query time exist only on the SQL query builder, which MongoDB does not have. The [ORM reference](/orm/reference) lists the methods each database has. +PostgreSQL reports `true` for all three `sql` keys above, and SQLite reports `false` for them. This table is a sample, not the full list. Open `src/prisma/contract.json` in your project to see every key you have. The build-time check runs for every database. The run-time check only exists on the SQL query builder, and MongoDB has no SQL query builder. The [ORM reference](/orm/reference) lists the methods each database has. ## The `capabilities` section and `prisma db verify` [#capabilities-and-verification] -The keys say what your packages support, recorded when you run `npx prisma contract emit`, so the same contract behaves the same way in every environment. Checking the live database is a separate job, and [`db verify`](/cli/db-verify) compares the database against your contract. Extension packages are the main source of keys beyond the ones your database package brings, and [using extensions](/orm/extensions/using-extensions) shows the full path from install to first query. +[`npx prisma db verify`](/cli/db-verify) checks the live database against the contract's tables and columns, not against this section. The keys say what your packages support, recorded when you run `npx prisma contract emit`. ## Prompt your coding agent diff --git a/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx b/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx index 01f4d0811e4..296397071dd 100644 --- a/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx @@ -6,18 +6,20 @@ metaTitle: Author the Prisma ORM contract in PSL metaDescription: 'Learn how to write a Prisma ORM contract in the Prisma schema language, including named types, enums, value objects, relations, and extension types.' --- -PSL, the Prisma Schema Language, is the preferred way to author [your contract](/orm/contract-authoring/the-data-contract), the `contract.prisma` file that replaced `schema.prisma`. You write one file, usually `src/prisma/contract.prisma`, and [`npx prisma contract emit`](/cli/contract-emit) writes `contract.json` and `contract.d.ts` beside it. If you know the Prisma schema language, most of a contract file reads exactly as you expect. Prisma ORM 8 adds five things: +PSL, the Prisma Schema Language, is the preferred way to author [your contract](/orm/contract-authoring/the-data-contract), the `contract.prisma` file that replaced `schema.prisma`. You write one file, usually `src/prisma/contract.prisma`, and [`npx prisma contract emit`](/cli/contract-emit) writes `contract.json` and `contract.d.ts` beside it. If you know the Prisma schema language, most of a contract file reads exactly as you expect. Prisma ORM 8 differs in five places: - named types: give a database column type a name you can reuse on many fields. - enums: unchanged, except that an enum can now say how its values are stored. - value objects: a structured value stored inside its parent row, with no table of its own. -- base models and variants: store several kinds of record in one table. A base model holds the shared fields, each variant adds its own, and one column says which kind each row is. +- base models and variants: one table can hold more than one kind of record. Put the shared fields in a base model, put the differences in each variant, and Prisma ORM uses one column to tell them apart. - extension types: field types that come from an npm package, such as vectors. The `datasource` and `generator` blocks are gone. The connection URL and the file paths are set in `prisma.config.ts` instead. [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7#schema) lists every change to the schema file, including what each old `@db.` attribute becomes. ## A complete contract +Every contract starts with `// use prisma-8`. Keep that line at the top. + ```prisma title="src/prisma/contract.prisma" tab="PostgreSQL" // use prisma-8 @@ -101,7 +103,7 @@ model Post { } ``` -Line 1 is `// use prisma-8`. The Prisma editor extension reads it to tell that the file is a Prisma ORM 8 contract. Run `npx prisma contract emit` after any change to refresh the two files. +`Uuid` is PostgreSQL's `uuid` type written as a field type. Run `npx prisma contract emit` after any change to refresh `contract.json` and `contract.d.ts`. Then [`npx prisma db init`](/cli/db-init) creates the tables. ## Point the config at the schema @@ -126,15 +128,15 @@ This is what `prisma orm init` writes, `DATABASE_URL` included. The import choos ## Models and fields -Models declare fields with a type, an optional `?` marker, and attributes. [Scalar fields](/orm/data-modeling#scalar-fields) lists the types a field can hold, among them `String`, `Int`, `Boolean`, `Decimal`, `DateTime`, `Json`, and `Bytes`. You can write a PostgreSQL type such as `VarChar(35)` or `Timestamptz` wherever you would write `String` or `DateTime`. On PostgreSQL, plain `String` is a `text` column, `Int` is `int4`, and `DateTime` is `timestamptz`. +Models declare fields with a type, an optional `?` marker, and attributes. [Scalar fields](/orm/data-modeling#scalar-fields) lists the types a field can hold, among them `String`, `Int`, `Boolean`, `Decimal`, `DateTime`, `Json`, and `Bytes`. You can write a PostgreSQL type wherever you would write `String` or `DateTime`. The PostgreSQL types you can write are `VarChar`, `Char`, `Numeric`, `Timestamp`, `Timestamptz`, `Time`, `Timetz`, `Date`, `Uuid`, `Inet`, `SmallInt`, and `Real`, plus `DateString`, `TimestampString`, `TimestamptzString`, and `TimeString`, which store the same columns but read back as text. On PostgreSQL, plain `String` is a `text` column, `Int` is `int4`, and `DateTime` is `timestamptz`. - `@id` marks the primary key. `@@id([a, b])` declares a composite key. - `@unique` adds a unique constraint on one field. `@@unique([userId, title])` adds one across several fields. - `@@index([...])` declares a secondary index. -- `@default(...)` sets a default. Database function defaults such as `@default(now())` become column defaults in the database. Generated defaults such as `@default(uuid())` are produced by Prisma ORM before each write, so a row written by raw SQL or another application gets no value. +- `@default(...)` sets a default. Database function defaults such as `@default(now())` become column defaults in the database. Generated defaults such as `@default(uuid())` come from Prisma ORM, and the database will not fill them in for you, so a row written by raw SQL or another application gets no value. - `@map("column_name")` sets a field's column name in the database. `@@map("table_name")` sets the table or collection name when it differs from the model name. -`@updatedAt` is gone. Instead of a type, write `temporal.updatedAt()` as the field's type: +`@updatedAt` is gone. Write `temporal.updatedAt()` where the field's type would go: ```prisma model Post { @@ -142,7 +144,7 @@ model Post { } ``` -`temporal` is built in. You do not import it or declare it. On PostgreSQL the column is `timestamptz`, and you can still set the field yourself on a write. The other member is `temporal.createdAt()`. +Prisma ORM sets the field to the current time on every create and update. `temporal` is built in. You do not import it or declare it. On PostgreSQL the column is `timestamptz`, and you can still set the field yourself on a write. `temporal.createdAt()` sets the field once, when the row is created. How IDs map differs by database: @@ -170,7 +172,7 @@ types { } ``` -Fields then use `ShortName` like any built-in type. The name keeps the column decision in one place: a `varchar(35)` column rather than `text`. A name is optional, and a field can use the native PostgreSQL type directly, such as `VarChar(35)`, `Uuid`, or `Timestamptz`. In Prisma ORM 8 the native type is the field's type, so `String @db.VarChar(35)` from Prisma ORM 7 becomes `VarChar(35)`. The `@db.` attributes are gone. +Fields then use `ShortName` like any built-in type. The name keeps the column decision in one place: a `varchar(35)` column rather than `text`. A name is optional, and a field can use the native PostgreSQL type directly, such as `VarChar(35)`, `Uuid`, or `Timestamptz`. In Prisma ORM 8 the native type is the field's type, so `String @db.VarChar(35)` from Prisma ORM 7 becomes `VarChar(35)`. The `@db.` attributes are gone. The `types` block is PostgreSQL only. ## Enums @@ -185,9 +187,9 @@ enum Priority { } ``` -In `pg/text@1`, `pg` is PostgreSQL, `text` is the column type, and `@1` is the version, always `@1` today. Write `@@type("pg/int4@1")` to store the values as integers instead. When a member has no explicit value, the member name itself is stored. +In `pg/text@1`, `pg` is PostgreSQL, `text` is the column type, and `@1` is the version of how the value is stored and read. Write `@@type("pg/int4@1")` to store the values as integers instead. When a member has no explicit value, the member name itself is stored. -`@@type` is optional. Leave it out and Prisma ORM picks the type from the member values: bare member names and string values give the database's text type, and integer values give its integer type. A `@default` names the member, as in `priority Priority @default(Low)`, even where the member stores a different value. +`@@type` is optional. Leave it out and Prisma ORM picks the type from the member values: bare member names and string values give the database's text type, and integer values give its integer type. Give every member the same kind of value, because a mix of string and integer values throws an error whose `code` is `PSL_ENUM_CANNOT_INFER_TYPE`. A `@default` names the member, as in `priority Priority @default(Low)`, even where the member stores a different value. An `enum` block is not a PostgreSQL `enum` type: the column is text or an integer. For a PostgreSQL `enum` type, declare it in a `native_enum` block and type the field `pg.enum(Role)`: @@ -202,7 +204,7 @@ model User { } ``` -Each member needs a value. `pg` comes with `@prisma/orm-postgres`, so there is nothing to import. +Each member needs a value. `pg` comes with `@prisma/orm-postgres`, so there is nothing to import. You do not create the PostgreSQL type yourself: `npx prisma migration plan` includes the `CREATE TYPE`. ## Value objects @@ -223,7 +225,7 @@ model User { } ``` -A value object field can be optional or a list, and a `type` block can hold a field of another `type`. Watch the two spellings: `types { ... }` declares named types, and `type X { ... }` declares a value object. Storage differs by database. On PostgreSQL a value object field is stored in a single `jsonb` column, and a list such as `addresses Address[]` is one `jsonb` column holding an array. On MongoDB it is an embedded document. Either way, `contract.d.ts` types it as a structured object rather than untyped JSON. On MongoDB, whether to embed or reference is the central modeling decision. [MongoDB data modeling](/orm/data-modeling/mongodb#embed-or-reference) covers it. +A value object field can be optional or a list, and a `type` block can hold a field of another `type`. Watch the two spellings: `types { ... }` declares named types, and `type X { ... }` declares a value object. Storage differs by database. On PostgreSQL a value object field is stored in a single `jsonb` column. On MongoDB it is an embedded document. Either way, `contract.d.ts` types it as a structured object rather than untyped JSON. On MongoDB, whether to embed or reference is the central modeling decision. [MongoDB data modeling](/orm/data-modeling/mongodb#embed-or-reference) covers it. ## Relations @@ -249,7 +251,7 @@ model Profile { } ``` -Many-to-many relations need a model for the join table. You write that model yourself: there is no implicit many-to-many. Prisma ORM matches a list field such as `tags Tag[]` to the one model that holds a foreign key to each side. That model must follow two rules. Each foreign key references every field of that side's `@id`, and its own `@@id([...])` lists exactly those two foreign-key fields. +Many-to-many relations need a model for the join table. You write that model yourself: there is no implicit many-to-many. That model must follow two rules. If a side has a composite primary key, that model needs one foreign-key field for each part of it. Its `@@id([...])` must list exactly the foreign-key fields, and nothing else. ```prisma model Post { @@ -272,7 +274,7 @@ model PostTag { } ``` -You then read `post.tags` as a list of `Tag`, without mentioning `PostTag` in the query. Break either rule and `npx prisma contract emit` reports which list field it could not match. If two models qualify, `npx prisma contract emit` throws an error whose `code` is `PSL_AMBIGUOUS_BACKRELATION`. Put the same `@relation("name")` on the list field and on `PostTag`'s field that points back at it. +You then read `post.tags` as a list of `Tag`, without mentioning `PostTag` in the query. Break either rule and `npx prisma contract emit` reports which list field it could not match to a model. If two models qualify, `npx prisma contract emit` throws an error whose `code` is `PSL_AMBIGUOUS_BACKRELATION`. Put the same `@relation("name")` on both ends of one pair, so on `PostTag.post` for `Post.tags`. For which shape to choose and which side owns the foreign key, see [relational data modeling](/orm/data-modeling/relational-databases) and [MongoDB data modeling](/orm/data-modeling/mongodb). @@ -301,31 +303,25 @@ model Bug { A field name is written bare, as `type` is in `@@discriminator(type)`, and a database name is quoted, as in `@@map("task")`. Rows whose `type` column holds `"bug"` are `Bug` records. A variant reuses its base model's fields, so a `Bug` has `id`, `title`, and `type` as well as `severity` and `stepsToRepro`. A variant does not declare an `@id` of its own: it takes the base model's primary key. -You query a variant through the base model: `db.orm.public.Task.variant('Bug')` returns the `Bug` rows only. Here `public` is the PostgreSQL schema. See [`variant()`](/orm/reference/orm-client#variant). +You query a variant through the base model: `db.orm.public.Task.variant('Bug')` returns the `Bug` rows only. Here `public` is the PostgreSQL schema. Writing a row of a variant starts with the same `variant('Bug')` call. See [`variant()`](/orm/reference/orm-client#variant). -On PostgreSQL, whether you write `@@map` on the variant decides where its fields are stored. Give the variant its own `@@map`, as `Bug` has here, and its fields are in a table of their own that shares the base model's primary key. Leave `@@map` out and they are nullable columns in the base table. [Relational data modeling](/orm/data-modeling/relational-databases#polymorphic-relations) covers choosing between the two. +On a variant, `@@map` does more than rename. On PostgreSQL it chooses between two storage layouts. Give the variant its own `@@map`, as `Bug` has here, and its fields are in a table of their own that shares the base model's primary key. Leave `@@map` out and they are nullable columns in the base table. [Relational data modeling](/orm/data-modeling/relational-databases#polymorphic-relations) covers choosing between the two. On MongoDB, a variant adds its fields to documents in the base model's collection, so it declares `@@base` but no `@@map` of its own. ## Extension types -An extension pack is an npm package that adds field types Prisma ORM does not ship, such as vectors. Install the pack, add it to the config, then call its types in the `types` block: +An extension pack is an npm package that adds field types Prisma ORM does not ship, such as vectors. Install the pack, add the import and the `extensions` line to the config shown above, then call its types in the `types` block: ```bash npm install @prisma/orm-extension-pgvector ``` ```typescript title="prisma.config.ts" -import { definePrismaConfig } from "prisma/config"; import pgvector from "@prisma/orm-extension-pgvector/control"; -import { defineConfig as ormConfig } from "@prisma/orm-postgres/config"; -export default definePrismaConfig({ - orm: ormConfig({ - contract: "./src/prisma/contract.prisma", +// inside ormConfig({ ... }), beside contract and db: extensions: [pgvector], - }), -}); ``` ```prisma title="src/prisma/contract.prisma" @@ -357,4 +353,4 @@ Projects created with `npm create prisma@latest -- my-app` include the [Prisma O - Run `npx prisma contract emit` and inspect [`contract.json` and `contract.d.ts`](/orm/contract-authoring/the-contract-artifact). You do not import them yourself. `db`, the client that reads both, is in `src/prisma/db.ts`, and `prisma orm init` writes that file. See [transactions and runtime](/orm/reference/transactions-and-runtime). - If you prefer defining models in code, see [authoring in TypeScript](/orm/contract-authoring/typescript-schema-builder). -- Apply the contract to a database with [`db init`](/cli/db-init) or plan changes with [`migration plan`](/cli/migration-plan). +- Plan changes to a database you have already created with [`migration plan`](/cli/migration-plan). diff --git a/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx b/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx index aa55fd83d78..22c3eebd0d7 100644 --- a/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx @@ -2,18 +2,18 @@ title: contract.json and contract.d.ts description: 'contract.json and contract.d.ts are the two files every other part of Prisma ORM reads. Here is what is inside them.' url: /orm/contract-authoring/the-contract-artifact -metaTitle: The Prisma ORM contract artifacts +metaTitle: The Prisma ORM contract files metaDescription: 'What contract.json and contract.d.ts contain, why the same source always produces the same two files, how the hashes work, and how your app and the CLI read them.' --- -Your contract is [a single file that describes your data and how it is stored](/orm/contract-authoring/the-data-contract). It replaces the `schema.prisma` of Prisma ORM 7, and it is `src/prisma/contract.prisma` in PSL or `src/prisma/contract.ts` in TypeScript. [`npx prisma contract emit`](/cli/contract-emit) reads that file and writes two files beside it. In the table below, `db` is your client, the object you run queries on. [`npx prisma orm init`](/cli/orm-init) creates the contract source, `src/prisma/db.ts`, and `.env.example` for you. +Your contract is [a single file that describes your data and how it is stored](/orm/contract-authoring/the-data-contract). It replaces the `schema.prisma` of Prisma ORM 7, and it is `src/prisma/contract.prisma` in Prisma Schema Language (PSL) or `src/prisma/contract.ts` in TypeScript. [`npx prisma contract emit`](/cli/contract-emit) reads that file and writes two files beside it. In the table below, `db` is your client, the object you run queries on. [`npx prisma orm init`](/cli/orm-init) creates the contract source, `src/prisma/db.ts`, and `.env.example` for you. In Prisma ORM 7 you edited `schema.prisma` and the tooling read the same file. Now you edit the contract and the tooling reads the two files it produces. | File | Contents | Read by | | --- | --- | --- | | `contract.json` | The machine-readable contract: models, how they are stored, which database features they need, and the hashes | `db`, `prisma db sign`, `prisma db verify`, and `prisma migration plan` | | `contract.d.ts` | TypeScript declarations derived from the contract | The query APIs and your application code, for typed models and results | -`prisma db sign` records in the database which contract it matches. Both files are generated. Do not edit them. Change the [PSL](/orm/contract-authoring/psl-syntax) or [TypeScript](/orm/contract-authoring/typescript-schema-builder) source and run `npx prisma contract emit` again. Each file starts with a notice that says exactly this. To write the two files to another directory, see [`prisma contract emit`](/cli/contract-emit). +`prisma db sign` writes the current contract's hashes into the database, so later commands can tell which contract that database was built for. Both files are generated. Do not edit them. Change the [PSL](/orm/contract-authoring/psl-syntax) or [TypeScript](/orm/contract-authoring/typescript-schema-builder) source and run `npx prisma contract emit` again. Each file starts with a comment saying they are generated and must not be edited. To write the two files to another directory, see [`prisma contract emit`](/cli/contract-emit). The everyday loop on a project: @@ -22,7 +22,10 @@ The everyday loop on a project: 3. Create the migration with [`npx prisma migration plan`](/cli/migration-plan), read it, then apply it with [`npx prisma db migrate`](/cli/db-migrate). 4. Check the database against the contract with [`npx prisma db verify`](/cli/db-verify) in CI or in your deploy step. -Applying a migration records which contract the database now matches, so you do not sign it yourself afterwards. [`npx prisma db init`](/cli/db-init) creates the structures in an empty database and records the same thing. `npx prisma db verify` tells you whether anything recorded it. Run [`npx prisma db sign`](/cli/db-sign) only when the database already matches the contract and nothing recorded it, such as after [`npx prisma contract infer`](/cli/contract-infer) has written a starter contract from an existing database. +`npx prisma db verify` reports whether the database has a record and whether it matches. You almost never sign a database yourself: + +- Applying a migration records the match for you, and so does [`npx prisma db init`](/cli/db-init) when it creates the structures in an empty database. +- Run [`npx prisma db sign`](/cli/db-sign) by hand only after [`npx prisma contract infer`](/cli/contract-infer) has written a starter contract from a database that already matches it. ## The same source always produces the same two files [#deterministic-emission] @@ -30,9 +33,7 @@ Run `npx prisma contract emit` on the same source on any machine and you get the ## The content hashes -`npx prisma contract emit` computes a hash over each part of the contract. You never compute or compare them yourself. `storageHash` covers the storage layout: tables, columns, primary keys, uniques, indexes, foreign keys, and the allowed values of each enum. `profileHash` covers which database the contract targets. - -The hashes are how Prisma ORM connects the contract to a live database. `npx prisma db sign` checks that the database satisfies the contract, then records `storageHash` and `profileHash` in a table it creates called `prisma_contract.marker`. You never write to that table yourself. `npx prisma db verify` compares the contract you hold against that record and against the live schema, and fails when they disagree. To fix a mismatch, apply the pending migration, or run `npx prisma db sign` when the database already matches the contract. +`npx prisma contract emit` computes a hash over each part of the contract. You never compute or compare them yourself. `storageHash` covers the storage layout: tables, columns, primary keys, uniques, indexes, foreign keys, and the allowed values of each enum. `profileHash` covers which database the contract targets. The hashes are how Prisma ORM connects the contract to a live database. `npx prisma db sign` checks that the database satisfies the contract, then records `storageHash` and `profileHash` in the database. `npx prisma db verify` compares the contract you hold against that record and against the live schema, and fails when they disagree. To fix a mismatch, apply the pending migration, or run `npx prisma db sign` when the database already matches the contract. ## Inside `contract.json` @@ -94,26 +95,24 @@ An abridged `contract.json` for a `User` and `Post` schema: The sections, top to bottom: - **`schemaVersion`, `targetFamily`, `target`**: the contract format version, whether the database is SQL or MongoDB, and which database this contract targets. -- **`roots`**: one entry per model. The key is the table name, and its value names the model that table stores. The [ORM reference](/orm/reference) covers both ways of reaching it, `db.sql.public.user` by table name and `db.orm.public.User` by model name. -- **`domain` and `storage`**: your application's view, then the database's view. Each scalar field in `domain` records whether it is nullable and its type id, such as `pg/text@1`, which is the PostgreSQL type plus a version. `storage` holds the tables with their columns, keys, indexes, constraints, and enum values. Migration tooling compares `storage` between two contract versions. `prisma db verify` compares it to the live database. -- **`capabilities`**: which database features this contract can use. `npx prisma contract emit` collects them from the adapter for your database and from the packages listed in `extensions: [...]` in `prisma.config.ts`. The query APIs check them before building a query that needs one. See [supported database features](/orm/contract-authoring/capabilities) and [extension types](/orm/contract-authoring/psl-syntax#extension-types). +- **`roots`**: one entry per model. The key is the name of the table that stores the model, so for a model `User` mapped to a table `users` the key is `users`. The value names the model. The [ORM reference](/orm/reference) covers both ways of reaching it, `db.sql.public.user` by table name and `db.orm.public.User` by model name. +- **`domain` and `storage`**: your application's view, then the database's view. Each scalar field in `domain` records whether it is nullable and its `codecId`. `nativeType` is the column type in the database. `codecId` names how Prisma ORM reads and writes the value, the PostgreSQL type plus a version. `storage` holds the tables with their columns, keys, indexes, constraints, and enum values. `prisma db verify` compares it to the live database. +- **`capabilities`**: which database features this contract can use. `npx prisma contract emit` collects them from the adapter for your database and from the packages listed in `extensions: [...]` in `prisma.config.ts`, the config file `prisma orm init` writes. The query APIs check them before building a query that needs one. See [supported database features](/orm/contract-authoring/capabilities) and [extension types](/orm/contract-authoring/psl-syntax#extension-types). ## Inside `contract.d.ts` The declarations file gives the type system the same information. It exports: - `Contract`, the type the client is built from. `db.ts` imports it. -- One type per hash, such as `StorageHash` and `ProfileHash`. The hash string is part of the type. -- Input and output types for each [`type` block](/orm/contract-authoring/psl-syntax#value-objects) in your contract, plus `FieldOutputTypes` and `FieldInputTypes`, which hold the field types of every model. +- One type per hash, such as `StorageHash` and `ProfileHash`. The hash is part of the type name, so a `contract.d.ts` from one run does not type-check against a `contract.json` from another. +- Input and output types for each [`type` block](/orm/contract-authoring/psl-syntax#value-objects) in your contract. ```typescript title="src/prisma/contract.d.ts (excerpt)" export type StorageHash = StorageHashBase<"9f49f8f9e51a9cc016f1ec2098ebae9406521a3cc2cf00207adc795078333d8b">; -export type ProfileHash = - ProfileHashBase<"9c8aa3114e84ed3b7ea2bd57526d9c2e1bf7c5292be694e9d3801f566fda7ccb">; ``` -You never write these types yourself. Both files come from the one `npx prisma contract emit` run, so they only drift when someone commits one and not the other. When that happens, TypeScript catches it before `prisma db verify` has to. +You never write these types yourself. Both files come from the one `npx prisma contract emit` run, so they only drift when someone commits one and not the other. When that happens, the hash types no longer agree and TypeScript catches it before `prisma db verify` has to. ## How the application consumes the artifacts @@ -131,9 +130,9 @@ export const db = postgres({ }); ``` -The connection string comes from the `DATABASE_URL` environment variable. `prisma orm init` writes `.env.example`. Put your `DATABASE_URL` in `.env`. Keep the `.d` in the import path. Before its first query, `db` reads the record in the database of which contract it matches and compares it with the contract's hashes. If they do not match, it logs a warning whose `code` is `CONTRACT.MARKER_MISMATCH` and runs the query anyway. If there is no record at all, because nothing has signed the database yet, it logs a warning whose `code` is `CONTRACT.MARKER_MISSING` and again runs the query. +The connection string comes from the `DATABASE_URL` environment variable. `prisma orm init` writes `.env.example`. Put your `DATABASE_URL` in `.env`. Keep the `.d` in the import path. Delete the `.d` and the import stops resolving. The `tsconfig.json` settings the JSON import needs are on the [transactions and runtime page](/orm/reference/transactions-and-runtime). Before its first query, `db` reads the record in the database of which contract it matches and compares it with the contract's hashes. If they do not match, it logs a warning whose `code` is `CONTRACT.MARKER_MISMATCH` and runs the query anyway. If there is no record at all, because nothing has signed the database yet, it logs a warning whose `code` is `CONTRACT.MARKER_MISSING` and again runs the query. On MongoDB the client is `mongo(...)` from `@prisma/orm-mongo/runtime`, described under [`mongo(options)`](/orm/reference/transactions-and-runtime#mongooptions). -When `CONTRACT.MARKER_MISMATCH` shows up in production, apply the pending migration, or run `npx prisma db sign` when the database already matches the contract. The client can only warn or skip this check. See [`verifyMarker`](/orm/reference/transactions-and-runtime#postgresoptions) on the transactions and runtime page. Run `prisma db verify` when you need a check that fails. +When `CONTRACT.MARKER_MISMATCH` shows up in production, apply the pending migration, or run `npx prisma db sign` when the database already matches the contract. The client warns by default. Set [`verifyMarker`](/orm/reference/transactions-and-runtime#postgresoptions) to `false` to skip the check. It cannot be made to fail. Run `prisma db verify` when you need a check that fails. ## Version control diff --git a/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx b/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx index df260581fdd..6facd64d3ae 100644 --- a/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx @@ -25,21 +25,21 @@ model Post { } ``` -`npx prisma orm init` writes the first line, `// use prisma-8`, into every `.prisma` contract. The CLI works without that line, and the Prisma VS Code extension needs it to recognize the file. A `contract.ts` file needs no header line. +`npx prisma orm init` writes the first line, `// use prisma-8`, into every `.prisma` contract. The CLI does not need this line. The VS Code extension does, so keep it. A `contract.ts` file needs no header line. -`npx prisma contract emit` turns this file into two files that the CLI and your application read: `contract.json` and `contract.d.ts`. Your queries are type-checked against the contract, migrations are planned as changes to it, and `npx prisma db verify` checks a live database against it. Think of it as a `package-lock.json` for your data: a file in version control that records exactly what your application expects from its database. +`npx prisma contract emit` turns this file into two files that the CLI and your application read: `contract.json` and `contract.d.ts`. Your queries are type-checked against the contract, migrations are planned as changes to it, and `npx prisma db verify` checks a live database against it. :::note[Contract vs. schema] -In Prisma ORM, the **contract** is what you write in your code, and the **schema** is your database's actual structure. Some tools use these words the other way around. The language you write the contract in keeps the older name: it is PSL, the Prisma Schema Language, the same language as `schema.prisma`. +In Prisma ORM, the **contract** is what you write in your code. The **schema** is your database's actual structure. ::: ## Why a contract -Your contract compiles to two plain files you can open and read. `contract.json` describes your models, how they are stored, and the [database features they need](/orm/contract-authoring/capabilities). `contract.d.ts` holds the TypeScript types derived from it. The same source always produces the same two files, so you can read them in a code review and hand them to tools and coding agents. +Your contract compiles to two plain files you can open and read. `contract.json` describes your models, how they are stored, and the [database features they need](/orm/contract-authoring/capabilities). `contract.d.ts` holds the TypeScript types derived from it. You can read both files in a code review and hand them to tools and coding agents. -`npx prisma contract emit` also writes a [hash](/orm/contract-authoring/the-contract-artifact) of the contract into `contract.json`, and `npx prisma db migrate` stores the same hash in your database. The first time your client `db` runs a query, it compares the two hashes and logs a warning if they differ. To fail instead of warn, for example in a deploy check, run [`prisma db verify`](/cli/db-verify). +`npx prisma db migrate` records in your database which contract it applied. The first time `db` runs a query it checks that record and logs a warning on a mismatch. To fail instead, run [`npx prisma db verify`](/cli/db-verify) in a deploy check. ## How it works @@ -60,7 +60,7 @@ export default definePrismaConfig({ }); ``` -`definePrismaConfig` holds the whole config: which contract file to use and how to reach your database. Your connection string is in `prisma.config.ts` now, and the contract has no `datasource` block. The database settings come from the `defineConfig` export from your database package, imported here as `ormConfig`. The packages are `@prisma/orm-postgres` for PostgreSQL, `@prisma/orm-sqlite` for SQLite, and `@prisma/orm-mongo` for MongoDB. The examples here use PostgreSQL. +`definePrismaConfig` holds the whole config: which contract file to use and how to reach your database. Your connection string is in `prisma.config.ts` now, and the contract has no `datasource` block. `npx prisma orm init` writes `.env.example`, and your `DATABASE_URL` goes in `.env`. `@prisma/orm-postgres/config` exports `defineConfig`. The example renames it to `ormConfig` on import. The packages are `@prisma/orm-postgres` for PostgreSQL, `@prisma/orm-sqlite` for SQLite, and `@prisma/orm-mongo` for MongoDB. The examples here use PostgreSQL. To start a new project, run `npm create prisma@latest -- my-app`. To add Prisma ORM 8 to a project that has no Prisma ORM at all, run `npx prisma orm init`. If you already have a Prisma ORM 7 `schema.prisma`, follow the [PostgreSQL upgrade guide](/guides/upgrade-prisma-orm/postgresql) instead, and [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7) lists what changed. Both `npm create prisma@latest` and `npx prisma orm init` write `prisma.config.ts`, the contract file, and `src/prisma/db.ts`, and install the database package. @@ -74,17 +74,17 @@ npx prisma migration plan npx prisma db migrate ``` -`npx prisma contract emit` writes `contract.json` and `contract.d.ts` beside the contract file, so `src/prisma/`. [`npx prisma migration plan`](/orm/migrations/generating-a-migration) works out what the database needs and writes a migration. [`npx prisma db migrate`](/orm/migrations/applying-a-migration) applies it and records the new hash for you. Run [`npx prisma db sign`](/cli/db-sign) only to record the hash without applying anything, for example after `npx prisma contract infer`, when the database already matches the contract. +`npx prisma contract emit` writes `contract.json` and `contract.d.ts` beside the contract file, so `src/prisma/`. [`npx prisma migration plan`](/orm/migrations/generating-a-migration) works out what the database needs and writes a migration. [`npx prisma db migrate`](/orm/migrations/applying-a-migration) applies it and records the match for you. When the database already matches the contract, run [`npx prisma db sign`](/cli/db-sign), which records the match without applying anything, for example after [`npx prisma contract infer`](/cli/contract-infer), which writes a contract from an existing database. -`db` is the client you create once in `src/prisma/db.ts`, and `npx prisma orm init` writes that file for you. From a file directly inside `src/`, import it with `import { db } from "./prisma/db"` and query a model: `await db.orm.public.User.all()` returns every user row. `db.orm` holds your models by model name. `public` is the PostgreSQL schema, the namespace your tables are in, unless you configured a different one. [Reading data](/orm/fundamentals/reading-data) covers queries. +`db` is the client you create once in `src/prisma/db.ts`, and `npx prisma orm init` writes that file for you. From a file directly inside `src/`, import it with `import { db } from "./prisma/db"` and query a model: `await db.orm.public.User.all()` returns every user row. From a deeper file, adjust the relative path. `db.orm` is the ORM client, one of the [three query APIs](/orm/reference), and `public` is the PostgreSQL schema, the namespace your tables are in, unless you configured a different one. [Reading data](/orm/fundamentals/reading-data) covers queries. ## Two authoring modes, one `contract.json` [#two-authoring-modes-one-artifact] -**[PSL](/orm/contract-authoring/psl-syntax)** is the usual way to write the contract. It is a compact language for describing data. It is what `npm create prisma@latest` scaffolds, and what [`npx prisma contract infer`](/cli/contract-infer) writes when you start from an existing database. That command reads the live database and writes a `contract.prisma` from what it finds. +**[PSL](/orm/contract-authoring/psl-syntax)** is the usual way to write the contract. It is a compact language for describing data. It is what `npm create prisma@latest` scaffolds, and what [`npx prisma contract infer`](/cli/contract-infer) writes when you start from an existing database. Define models with the **[TypeScript builder](/orm/contract-authoring/typescript-schema-builder)** instead when you want to build them with code. Reach for it when model definitions must be composed, generated, or shared as ordinary TypeScript modules. That page shows what a `contract.ts` file looks like. -Both modes produce the same contract, so migrations, database checks, and `db` behave the same no matter which mode you write. A project has exactly one contract file. Do not keep both a `contract.prisma` and a `contract.ts`: the config names one file and Prisma ORM reads only that one, so edits to the other file do nothing. +Both modes produce the same contract, so migrations, database checks, and `db` behave the same no matter which mode you write. A project has exactly one contract file. Do not keep both a `contract.prisma` and a `contract.ts`: the config names one file and Prisma ORM reads only that one, so edits to the other file do nothing. To switch modes, change the path in `contract` and delete the old file. ## What the contract contains @@ -92,8 +92,8 @@ The contract describes structure, not data: - models, fields, and relations, plus how they map to tables and columns - how the data is stored: primary keys, unique constraints, indexes, and foreign keys -- [named types](/orm/contract-authoring/psl-syntax#named-types), which are reusable aliases for a type and its storage details, and enums -- [`type` blocks](/orm/contract-authoring/psl-syntax#value-objects), each a reusable group of fields such as an address, stored inside its parent row with no table of its own. On PostgreSQL each one is stored in a single `jsonb` column. +- [named types](/orm/contract-authoring/psl-syntax#named-types), which are reusable aliases for a database column type, and enums +- [`type` blocks](/orm/contract-authoring/psl-syntax#value-objects), also called value objects, each a reusable group of fields such as an address, stored inside its parent row with no table of its own - the types and database features that extension packages add, such as pgvector's `Vector` type It contains no rows, no credentials, and no connection details, so committing the source, `contract.json`, and `contract.d.ts` to version control is safe and expected. diff --git a/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx b/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx index 9754fd68b80..054d599eee6 100644 --- a/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx @@ -6,7 +6,7 @@ metaTitle: Author the Prisma ORM contract in TypeScript metaDescription: 'How to define a Prisma ORM contract in TypeScript with defineContract, including fields, relations, enums, storage mapping, and extension types.' --- -Instead of `schema.prisma`, Prisma ORM 8 reads a single file called [the contract](/orm/contract-authoring/the-data-contract), which describes your models. You can write it in two forms. One is a `.prisma` file written in PSL, short for Prisma Schema Language. The other is TypeScript, in `src/prisma/contract.ts`, built with the `defineContract` builder. Both forms produce the same two files. [`npx prisma contract emit`](/cli/contract-emit) writes `contract.json` and `contract.d.ts` into the folder that holds your contract file. +In Prisma ORM 8, `schema.prisma` is replaced by a file called [the contract](/orm/contract-authoring/the-data-contract). It holds the model definitions you used to write in `schema.prisma`. You can write it in two forms. One is a `.prisma` file written in PSL, short for Prisma Schema Language. The other is TypeScript, in `src/prisma/contract.ts`, built with the `defineContract` builder. Both forms produce the same two files. [`npx prisma contract emit`](/cli/contract-emit) writes `contract.json` and `contract.d.ts` into the folder that holds your contract file. ## When to choose TypeScript over PSL @@ -54,6 +54,7 @@ Export the contract under the name `contract`, as below, or as the file's defaul ```typescript title="src/prisma/contract.ts" tab="PostgreSQL" import { defineContract, enumType, member } from "@prisma/orm-postgres/contract-builder"; +// nativeType is the column type the database gets. codecId picks how Prisma ORM converts the value between TypeScript and that column, and its @1 is the version of that conversion. const pgText = { codecId: "pg/text@1", nativeType: "text" } as const; const Priority = enumType("Priority", pgText, member("Low", "low"), member("High", "high")); @@ -123,19 +124,19 @@ export const contract = defineContract({ models: { User, Post } }); ## How `defineContract` works -On PostgreSQL, `defineContract` takes an options object and then a function. You never name your database, because the package you import carries it. The options object lists the extension packs you use, and you write `{}` when you use none. The function returns the contract's content: `models`, plus `enums` and `types` if you have them. +On PostgreSQL, `defineContract` takes an options object and then a function. You do not set a `provider` anywhere. Importing `@prisma/orm-postgres/contract-builder` is what selects PostgreSQL. The options object lists the extension packs you use, and you write `{}` when you use none. The function returns the contract's content: `models`, plus `enums` and `types` if you have them. -Take `field`, `model`, `rel`, and `type` from the function's one argument. Import `defineContract`, `enumType`, and `member` from the package, because you use them outside the function. +Take `field`, `model`, `rel`, and `type` from the function's one argument. Import `defineContract`, `enumType`, and `member` from the package. The package also exports `field`, `model`, and `rel` for use outside the function. On MongoDB, `defineContract` takes a single object holding `models`, and you import `field`, `model`, and `rel` from the package instead. The MongoDB builder also differs from the PostgreSQL one in five ways: - Every model declares an `_id` field built with `field.objectId()`. That field is always the primary key, so you never mark it with `.id()` and there is no `.attributes(...)` on MongoDB. - The scalar helpers are `field.objectId()`, `field.string()`, `field.int32()`, `field.double()`, `field.bool()`, and `field.date()`. - The collection name and the relations are inline options on the model rather than chained calls. -- Relations name the field on each side, as in `rel.hasMany("Post", { from: "_id", to: "authorId" })`, and a field on another model is written `User.ref("_id")` where PostgreSQL writes `User.refs.id`. -- Indexes are an `indexes` option on the model, built with the `index` helper the same package exports, as in `indexes: [index({ email: 1 }, { unique: true })]`. +- Relations name the field on each side. `rel.hasMany` takes `{ from, to }` on MongoDB and `{ by }` on PostgreSQL. `to` accepts either the field name as a string or a typed reference such as `User.ref("_id")`, and both forms mean the same thing. PostgreSQL writes that reference as `User.refs.id`. +- Indexes are an `indexes` option on the model, built with the `index` helper the same package exports, as in `indexes: [index({ email: 1 }, { unique: true })]`. The `1` is ascending, MongoDB's own index syntax. -Enums and extension packs work on MongoDB too. `enumType` and `member` come from `@prisma/orm-mongo/contract-builder`, and extension packs go in the same `extensions` option. +Enums and extension packs work on MongoDB too. `enumType` and `member` come from `@prisma/orm-mongo/contract-builder`. Pass the enums in the same object as the models, as `defineContract({ models, enums })`, and extension packs go in the same `extensions` option. The rest of this page uses the PostgreSQL builder. @@ -159,9 +160,9 @@ The rest of this page uses the PostgreSQL builder. There is no helper for a date without a time, or a time without a date. For a date-only column, name the type yourself: `field.column({ codecId: "pg/date-temporal@1", nativeType: "date" } as const)`. `field.column(...)` and the chained `.column(...)` below are different calls: `field.column(descriptor)` builds a field from a type description, and `.column(name)` on an existing field sets its column name. Extension packs add more helpers of their own. -For a primary key whose value your app generates, use `field.id.uuidv4String()`, `field.id.uuidv7String()`, `field.id.ulid()`, `field.id.nanoid()`, `field.id.cuid2()`, or `field.id.ksuid()`. They all generate the ID in your app, and `uuidv4String` is the common choice. Each marks the field as the primary key for you. `field.id.uuidv4Native()` and `field.id.uuidv7Native()` do the same in a `uuid` column. +For a primary key whose value your app generates, use `field.id.uuidv4String()`, `field.id.uuidv7String()`, `field.id.ulid()`, `field.id.nanoid()`, `field.id.cuid2()`, or `field.id.ksuid()`. They all generate the ID in your app, and `uuidv4String` is the common choice. Each marks the field as the primary key for you. `field.id.uuidv4Native()` and `field.id.uuidv7Native()` do the same in a `uuid` column. There is no auto-increment helper, so use one of those `field.id.*` helpers. For a key the database generates instead, write `field.uuidNative().defaultSql("gen_random_uuid()").id()`. -`field.temporal.createdAt()` fills the column with `now()` when the row is created. `field.temporal.updatedAt()` fills it on every create and every update. `field.namedType(x)` takes an enum or a type from an extension pack. +`field.temporal.createdAt()` fills the column with `now()` when the row is created. `field.temporal.updatedAt()` fills it on every create and every update, and Prisma ORM sets it in the client, not with a database trigger. `field.namedType(x)` takes an enum or a type from an extension pack. Every field builder supports chained modifiers: @@ -174,21 +175,20 @@ Every field builder supports chained modifiers: `enumType` declares an enum, the type its values are stored as, and its members, as `Priority` does in the full example above. -The second argument says how each member is stored. `codecId` names the PostgreSQL type plus a version, always `@1` today. `nativeType` is the column type in the database. Write `as const` after the object. To store the members as integers, pass `{ codecId: "pg/int4@1", nativeType: "int4" } as const`. [Raw queries](/orm/reference/raw-queries#binding-a-bare-value-with-param) lists more type ids. +The second argument says how each member is stored, as the same `codecId` and `nativeType` pair the full example above explains. Write `as const` after the object, so TypeScript keeps the exact strings. To store the members as integers, pass `{ codecId: "pg/int4@1", nativeType: "int4" } as const`. More type ids are listed with the [raw query `param` helper](/orm/reference/raw-queries#binding-a-bare-value-with-param), which names types the same way. Each `member(name, storedValue)` pairs the TypeScript-visible name with the value stored in the column. Fields reference the enum with `field.namedType(Priority)`, and defaults reference a member as `Priority.members.Low`. Include the enum in the returned `enums` map so it reaches the two files. Members are stored in whatever column type you name here, `text` above. For a real PostgreSQL `enum` type, declare it with `nativeEnum` and type the field with `pg.enum`, both exported by `@prisma/orm-postgres/contract-builder`. The type is created in the database under the name you give it: ```typescript -import { field, model, nativeEnum, pg } from "@prisma/orm-postgres/contract-builder"; +import { defineContract, nativeEnum, pg } from "@prisma/orm-postgres/contract-builder"; const Role = nativeEnum("Role", "user", "admin"); -const Account = model("Account", { - fields: { - role: field.column(pg.enum(Role)), - }, +export const contract = defineContract({}, ({ field, model }) => { + const Account = model("Account", { fields: { role: field.column(pg.enum(Role)) } }); + return { models: { Account: Account.sql({ table: "account" }) } }; }); ``` @@ -216,7 +216,7 @@ Post.relations({ }); ``` -`fk` takes `name`, `onDelete`, and `onUpdate`. +`fk` takes `name`, `onDelete`, and `onUpdate`. `onDelete` and `onUpdate` each take `'noAction'`, `'restrict'`, `'cascade'`, `'setNull'`, or `'setDefault'`. Prisma ORM does not check that the column types on the two sides of a relation match, so choose a field helper that produces the same column type as the key you point at. @@ -226,7 +226,7 @@ Prisma ORM does not check that the column types on the two sides of a relation m `.sql(...)` maps a model to its table. The object form covers the common case: `User.sql({ table: "user" })`. -You can chain `.relations(...)`, `.attributes(...)`, and `.sql(...)` in any order, and you can skip any of them. You can pass `User` to `rel.belongsTo(User, ...)` before chaining `.sql()` onto it. A model with no relations calls `.sql({ table })` straight after `model(...)`. +You can chain `.relations(...)`, `.attributes(...)`, and `.sql(...)` in any order, and you can skip any of them. A model with no relations calls `.sql({ table })` straight after `model(...)`. The callback form gives you the model's columns as `cols` and the constraint builders as `constraints`. Use it for indexes: @@ -242,7 +242,7 @@ Post.relations({ ... }).sql(({ cols, constraints }) => ({ `constraints.index` always takes a list of columns, even when the list has one entry. Add `{ unique: true }` to make it a unique index. -`Model.refs` provides typed references to another model's fields, so `User.refs.id` is checked against the actual `User` definition. +`Model.refs` provides typed references to another model's fields, for the constraint builders inside `.sql(...)`. Write `constraints.foreignKey(cols.userId, User.refs.id)` and TypeScript checks `id` against the actual `User` definition. For a primary key made of two fields, use `.attributes(...)` instead of `.sql(...)`, and build it from the field references: @@ -252,7 +252,7 @@ PostTag.attributes(({ fields, constraints }) => ({ })); ``` -That object accepts exactly two keys, `id` and `uniques`. `id` takes one constraint, and `uniques` takes a list, so `uniques: [constraints.unique([fields.postId, fields.tagId])]` makes a unique constraint across two fields. `cols` in `.sql(...)` and `fields` in `.attributes(...)` are the same model fields under two names. For a key made of one field, `.id()` on the field is enough, and the `field.id.*` helpers already do it. +That object accepts exactly two keys, `id` and `uniques`. `id` takes one constraint, and `uniques` takes a list, so `uniques: [constraints.unique([fields.postId, fields.tagId])]` makes a unique constraint across two fields. For a key made of one field, `.id()` on the field is enough, and the `field.id.*` helpers already do it. ## Extension types @@ -271,7 +271,7 @@ export const contract = defineContract({ extensions: { pgvector } }, ({ field, m }); ``` -The key on `type.pgvector` is the pack's own name, not the name you gave the import. `Embedding1536` is a name you choose, and it appears under that name in `contract.d.ts`. Return the `types` map so the name reaches the two files. +The key on `type.pgvector` is the pack's own name, not the name you gave the import. The name is in the pack's documentation, and pgvector's is `pgvector`. `Embedding1536` is a name you choose, and it appears under that name in `contract.d.ts`. Return the `types` map so the name reaches the two files. Add the same pack to `prisma.config.ts`. It goes inside `ormConfig({ ... })`, beside `contract`, and the config imports the pack's `/control` export while the contract file imports its `/pack` export: @@ -294,7 +294,7 @@ The contract file describes structure. These rules keep it usable: - Do not read `process.env`, the current time, or random values into the contract. A contract built from those values makes `npx prisma contract emit` produce a different `contract.json` on each run. - Keep field values plain: strings, numbers, booleans, and the builder's own objects. Functions, class instances, and `Date` objects do not serialize. -- Keep the file free of side effects. `npx prisma contract emit` runs the file, so anything it does while loading, such as writing a file or calling a service, happens every time you run the command. +- Keep the file free of side effects. `npx prisma contract emit` loads the file with Node.js, so anything it does while loading, such as writing a file or calling a service, happens every time you run the command. The file is ordinary TypeScript, so `import` model definitions from other files as usual. - `npx prisma contract emit` runs the file but does not type-check it. Type errors show in your editor and when you run `tsc`. Configuration that legitimately varies per environment, such as the database URL, belongs in `prisma.config.ts`, not in the contract. diff --git a/docs/orm-docs-audit/c21-contract-authoring/capabilities.round4.md b/docs/orm-docs-audit/c21-contract-authoring/capabilities.round4.md new file mode 100644 index 00000000000..7f36911fa59 --- /dev/null +++ b/docs/orm-docs-audit/c21-contract-authoring/capabilities.round4.md @@ -0,0 +1,82 @@ +Read once, top to bottom, as someone coming from Prisma 6. + +## Sentences I could not restate after one reading + +**Line 9** — "A feature that is not listed fails right away, with an error that names its key, instead of reaching the database and failing there." +A feature doesn't fail; my code does. And "right away" — right away *when*? At build? At startup? At the call? I only learned the answer two sections later. Plainer: "If you use a feature that is not listed, Prisma ORM raises an error instead of sending the query to the database." + +**Line 24** — "A key is written group first, so `pgvector.cosine` under `postgres` is `postgres.pgvector.cosine`." +Written *where*? In the JSON it is not written that way — the JSON shows `"pgvector.cosine"` nested inside `"postgres"`. I eventually guessed you mean "in error messages and in this page's table". Plainer: "Error messages name a key group-first." + +**Line 44** — "If your contract uses a feature your database cannot store, `npx prisma contract emit` fails." +This contradicts line 13, which says emit "never connects to your database." So it is not my database deciding, it is my database *package*. I stopped and re-read both lines. + +**Line 60** — "That message means the method is not available on the database you are connected to." +Same contradiction, sharper. Nothing so far involved a connection. The capability list came from a package on disk. + +**Line 60** — "The error arrives when you call the method, not when you `await` the query, so a test that calls it catches it." +Three ideas in one sentence, and the "so" does not follow on first reading. I think you mean: "You do not have to run the query to get the error, so a test that only builds the query still fails." + +**Line 60** — "A value such as `db.contract.capabilities.sql?.lateral` is `true`, `false`, or `undefined` when the whole group is absent." +The grammar breaks. It reads as if all three cases happen when the group is absent. Plainer: "…is `true` or `false`, or `undefined` if the whole group is missing." + +**Line 60** — "Your own code can read the same list through `db`, the client you create once in `src/prisma/db.ts`." +`db` and that file arrive as things I am assumed to already have. I have never made one. + +**Line 80** — "The check that runs when the contract is built applies on every database, MongoDB included. The keys that methods check at query time exist only on the SQL query builder, which MongoDB does not have." +"The keys that methods check at query time" is hard to parse — keys don't check, methods check keys. Plainer: "The build-time check runs for every database. The run-time check only exists on the SQL query builder, and MongoDB has no SQL query builder." + +**Line 84** — "The keys say what your packages support, recorded when you run `npx prisma contract emit`, so the same contract behaves the same way in every environment." +The "so" hides the reason (the list is frozen at emit time and never asks the live database). I had to reconstruct it. + +**Line 84** — the whole paragraph. The heading promises "The `capabilities` section and `prisma db verify`", then one sentence mentions `db verify`, then it changes subject to extension packages. It never answers the question the heading raised: does `db verify` tell me my real database is missing something the capabilities claim? + +## Words and phrases I had to guess + +- **"contract emit"** — guessed: a codegen step like `prisma generate` was in v6. +- **"database package"** — guessed: the v8 replacement for `provider = "postgresql"`. +- **"extension packages"** — guessed: optional npm packages that add features. +- **`@prisma/orm-extension-pgvector/control`** — guessed: the subpath you import for config registration. `/control` is never explained. +- **"target" and "adapter"** — the page tells me both mean the database package. So one thing has three names and I have to carry all three. +- **"group"** — guessed: the top-level object in `capabilities`. +- **"scalar list"** — guessed from `tags String[]` in the same sentence. Lucky. +- **`sql.lateral` / `lateralJoin()`** — guessed: SQL `LATERAL` joins. The table entry "Lateral joins (`lateralJoin()`)" defines the term with the term. +- **"SQL query builder"** — new in v8 as far as I know; guessed it is a raw-ish query API. +- **"the first migration the package ships"** — guessed: installing the npm package puts a migration file in my project. +- **`db.contract`** — guessed the emitted contract is readable at run time off the client. +- **`db verify`** — guessed: compares live schema to contract. +- **"skills sync", "the `prisma-8` skill"** — guessed: files for a coding agent. + +## "So what do I actually type?" — unanswered + +1. **`src/prisma/db.ts`.** Line 60 tells me to read capabilities "through `db`", and never shows me how `db` comes to exist or what to import. The one code sample assumes it. +2. **Reading a three-segment key.** `capabilities.sql?.lateral` is shown. What do I write for `postgres.pgvector.cosine`? `capabilities.postgres?.["pgvector.cosine"]`? The dot inside the key name makes this a real question and the page created it in line 24 and then dropped it. +3. **The pgvector migration.** "the first migration the package ships installs it in PostgreSQL for you" — installs it *when*? Do I run `prisma migrate dev`? Something else? The install instructions stop at `npx prisma contract emit`. +4. **Switching to PostgreSQL.** "change the import and the connection string in `prisma.config.ts`, then set up the new database." There are three imports in the sample above; I don't know which one. "Set up the new database" is an entire unnamed job. +5. **When must I re-run `contract emit`?** The page says "run it again" twice, both times inside a specific recipe. It never states the general rule. +6. **The key in the error vs the key in the table.** The error says `the "scalarList" capability`; the table says `sql.scalarList`. I had to assume those are the same thing. + +## Places that explain the machinery when I wanted instructions + +- Line 13: "`npx prisma contract emit` never connects to your database. It reads those packages and writes what they report." That is how it works. What I needed was: what do I do when a key is missing. +- Line 24: the whole "written group first" naming rule — internal naming mechanics, given before I have any reason to care. +- Line 52: "In that message, 'target' and 'adapter' both mean your database package." You are explaining the tool's inconsistent internal vocabulary to me rather than the tool using one word. +- Line 60: "The error arrives when you call the method, not when you `await` the query." Implementation timing. +- Line 84: "recorded when you run `npx prisma contract emit`, so the same contract behaves the same way in every environment." Design rationale for a decision I cannot change. + +## Could I do what the page is for, after one reading? + +Partly. + +I could handle both error messages it shows me. If I saw `distinctOn() requires capability postgres.distinctOn`, I would know to rewrite the query or install a package. If emit failed on `tags String[]` under SQLite, I would know to drop the field. That is the page's core job and it does it. + +What I would still not know: + +- How to write the capability check in my own code, because I do not have `db` and the page did not tell me where it comes from. +- How to check a nested key like `postgres.pgvector.cosine` in TypeScript. +- Whether adding pgvector actually worked — the migration step is asserted, not instructed. +- Whether the list can go stale, and what re-syncs it. +- What `db verify` has to do with any of this, despite a section heading promising to tell me. +- Whether `capabilities` itself can be absent (the sample uses `?.` on `sql` but not on `capabilities`, and I don't know if that is deliberate). + +One thing outside the reader's view: the heading anchors are `#what-capabilities-gate` and `#where-capabilities-come-from`. The first uses "gate", which the heading text itself avoided. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/psl-syntax.round4.md b/docs/orm-docs-audit/c21-contract-authoring/psl-syntax.round4.md new file mode 100644 index 00000000000..1e97a96ec33 --- /dev/null +++ b/docs/orm-docs-audit/c21-contract-authoring/psl-syntax.round4.md @@ -0,0 +1,78 @@ +I read the page once, top to bottom, as a Prisma 7 user who has never seen version 8. + +## Sentences I could not restate after one reading + +**Line 14** — "base models and variants: store several kinds of record in one table. A base model holds the shared fields, each variant adds its own, and one column says which kind each row is." +Three new ideas in one bullet, and "variant" is a word this page invents without warning. Plainer: "Base models and variants: one table can hold more than one kind of record. Put the shared fields in a base model, put the differences in each variant, and Prisma uses one column to tell them apart." + +**Line 137** — "`@updatedAt` is gone. Instead of a type, write `temporal.updatedAt()` as the field's type." +This contradicts itself in one sentence: "instead of a type" and "as the field's type." I had to read it three times. Plainer: "`@updatedAt` is gone. Write `temporal.updatedAt()` where the field's type would go." + +**Line 145** — "The other member is `temporal.createdAt()`." +"Member" of what? I guessed `temporal` is a namespace with exactly two entries. The sentence also never tells me what `temporal.createdAt()` does or how it differs from `createdAt DateTime @default(now())`, which the page's own example on line 45 uses. + +**Line 188** — "In `pg/text@1`, `pg` is PostgreSQL, `text` is the column type, and `@1` is the version, always `@1` today." +Version of *what*? The encoding? The type? The package? I still do not know, and "always `@1` today" tells me it will change without telling me what changes. + +**Line 190** — "Leave it out and Prisma ORM picks the type from the member values: bare member names and string values give the database's text type, and integer values give its integer type." +Two rules compressed into one clause, and it does not say what happens if I mix string and integer values, which the sentence structure invites me to try. + +**Line 252** — "Each foreign key references every field of that side's `@id`, and its own `@@id([...])` lists exactly those two foreign-key fields." +"Every field of that side's `@id`" made me stop. I think it means composite primary keys, but "each foreign key" is singular-plural in the same breath. Plainer: "If a side has a composite primary key, the join model must have one foreign-key field for each part of it. The join model's `@@id([...])` must list exactly the foreign-key fields, and nothing else." + +**Line 275** — "Put the same `@relation("name")` on the list field and on `PostTag`'s field that points back at it." +`PostTag` has two relation fields, `post` and `tag`. "The field that points back at it" does not tell me which one, and "it" could be the list field or the model. No example is shown for the fix, only for the thing that breaks. + +**Line 306** — "On PostgreSQL, whether you write `@@map` on the variant decides where its fields are stored." +I could restate the words, but I did not believe them. `@@map` is a naming attribute everywhere else on this page. Here its presence or absence silently changes the physical storage layout. That is a large consequence hidden behind a rename attribute, and the page states it as if it were obvious. + +## Words and phrases I had to guess + +- **"named types"** (line 11) — guessed: a type alias. Confirmed later at line 165, but the `types { ... }` block appears in the example at line 24 before anything explains it. +- **"value object"** (line 13) — guessed: an embedded struct. The page explains it, but the term itself is not one I brought with me. +- **"variant"** — guessed: subclass. +- **"discriminator"** (line 281) — I know this term from ORMs, but the page defines it inline, which was fine. +- **"extension pack"** (line 312) — guessed: a plugin. The page says "npm package that adds field types," which helped. +- **`// use prisma-8`** (line 22) — on first sight I guessed this was a required directive that the parser reads. The explanation is 80 lines later at line 104, and says it is only for the editor extension. By then I had already assumed it was mandatory. +- **`Uuid`** (line 43) — guessed a built-in type. It is never introduced. It just appears in the first example and is used everywhere after. Line 173 mentions it in passing as a "native PostgreSQL type." +- **`db.orm.public.Task`** (line 304) — guessed `db.orm` is the generated client and `public` is a schema segment. The page explains `public` and nothing else. +- **`/control`** in `@prisma/orm-extension-pgvector/control` (line 320) — no guess. I do not know why it is not the package root. +- **"the two files"** (line 104) — guessed `contract.json` and `contract.d.ts`. + +## Places I asked "so what do I actually type?" and got no answer + +1. **How does `temporal.updatedAt()` actually behave?** The page never says the field updates automatically on every write. It only says "you can still set the field yourself on a write." It assumes I already know what `@updatedAt` did. Can I write `temporal.updatedAt()?`? Can I add `@map`? No example shows either. +2. **Which native types can I write?** Line 129 says "a PostgreSQL type such as `VarChar(35)` or `Timestamptz`." Such as which others? There is no list and no link to one. I do not know if `Numeric(10,2)` or `Inet` work. +3. **How do I insert a variant row?** Line 304 shows how to read `Bug` rows. It never shows how to create one. That is the first thing I would try. +4. **What does a variant with no `@@map` look like?** Line 306 describes it in prose; the only example given has `@@map`. +5. **The ambiguous-relation fix.** As above: no code for the `@relation("name")` resolution, only for the failure. +6. **The second `prisma.config.ts`** (line 318) has `extensions` but no `db` key, and no `import 'dotenv/config'`. Am I replacing my config or adding a line to it? I would have guessed wrong. +7. **Does `native_enum` create the PostgreSQL type for me?** Line 192 to 205 tells me how to declare it and how to type the field. It never says whether `db init` or `migration plan` will run `CREATE TYPE`, or whether I must make it myself first. +8. **Is `types { }` available on MongoDB?** The PostgreSQL tab has one, the MongoDB tab does not, and nothing says whether that is a limitation or just a shorter example. +9. **Can `types { }` appear more than once, and does declaration order matter?** Not addressed. +10. **What replaces `datasource`'s other settings?** Line 17 says the block is gone and points at a migration page. Fine as a pointer, but `provider`, `shadowDatabaseUrl`, and `relationMode` all vanish with no local hint. +11. **How do I get any of this into a database?** The answer is the last bullet on the page, line 360. After writing a contract I ran `contract emit` and had nothing in Postgres. + +## Places the page explains internals when I wanted instructions + +- **Line 134** — "Generated defaults such as `@default(uuid())` are produced by Prisma ORM before each write, so a row written by raw SQL or another application gets no value." The consequence is worth knowing. "Produced by Prisma ORM before each write" is how it works inside; "the database will not fill this in for you" is what I needed. +- **Line 226** — "On PostgreSQL a value object field is stored in a single `jsonb` column, and a list such as `addresses Address[]` is one `jsonb` column holding an array. On MongoDB it is an embedded document." Three storage details when my question was "can I put a struct on a model, yes or no." +- **Line 252** — "Prisma ORM matches a list field such as `tags Tag[]` to the one model that holds a foreign key to each side." This is the matching algorithm. What I wanted was: "write the join model like this, and here are the two rules it must follow." The rules do follow, but the algorithm comes first. +- **Line 104** — "The Prisma editor extension reads it to tell that the file is a Prisma ORM 8 contract." Tooling internals. "Keep this line at the top" would have done, and would have answered it at line 22 where I first asked. + +## Could I do what the page is for, after one reading? + +Partly. I could write a contract file with models, fields, IDs, an enum, a value object, a one-to-many, and a one-to-one, and I could point `prisma.config.ts` at it. That much the page teaches cleanly, and the two-tab PostgreSQL/MongoDB examples are the best part of it. + +What I would still not know: + +- Whether `temporal.updatedAt()` updates the field by itself. This is the single worst gap, because the page treats the behavior as already known and only documents the syntax change. +- Which native column types I am allowed to write. I have two examples and no list. +- How to create or write a variant record, only how to read one. +- Whether `native_enum` produces the PostgreSQL type or expects me to. +- How to fix `PSL_AMBIGUOUS_BACKRELATION`, because the instruction names a field I cannot identify. +- What the `@1` in `pg/text@1` versions, and when it would become `@2`. +- Whether the extension config example replaces my config or adds to it. +- That I need `db init` to see a table, until the last line of the page. + +Two smaller things. Line 9 says "Prisma ORM 8 adds five things," then the second bullet is "enums: unchanged, except that an enum can now say how its values are stored" — an unchanged feature is not an addition, and it made me wonder if I had misread the list. And the first code block uses `types { }`, `@@type("pg/text@1")`, `Uuid`, and `// use prisma-8` before any of the four is explained, so my first read of the page's centerpiece example was mostly guessing. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/the-contract-artifact.round4.md b/docs/orm-docs-audit/c21-contract-authoring/the-contract-artifact.round4.md new file mode 100644 index 00000000000..a6366c9bc7b --- /dev/null +++ b/docs/orm-docs-audit/c21-contract-authoring/the-contract-artifact.round4.md @@ -0,0 +1,77 @@ +Read once, top to bottom, as someone coming from `schema.prisma` in Prisma 7. + +## Sentences I could not restate after one reading + +1. **"`prisma db sign` records in the database which contract it matches."** (line 16) + What stopped me: I cannot tell what "it" is — the database or the contract. Plainest wording I'd have understood: "`prisma db sign` writes the current contract's hashes into the database, so later commands can tell which contract that database was built for." + +2. **"Each file starts with a notice that says exactly this."** (line 16) + "This" points back at three sentences ("Both files are generated. Do not edit them. Change the source and re-run emit."). I had to guess which one is in the file header. Plainer: "Both files begin with a comment saying they are generated and must not be edited." + +3. **"Applying a migration records which contract the database now matches, so you do not sign it yourself afterwards. … `npx prisma db verify` tells you whether anything recorded it."** (line 25) + Two problems. "which contract the database now matches" is the same tangled phrasing as above. Then "whether anything recorded it" — "it" has no clear referent at all, and "anything" is so vague I had to invent a meaning ("whether any command has ever written the marker row"). Plainer: "`prisma db verify` reports whether any command has written that record yet." + +4. **The whole of line 25 as one paragraph.** It names five commands (`db migrate`, `db init`, `db verify`, `db sign`, `contract infer`) and three separate rules in four sentences. I could not hold it after one pass. It needs to be a short list: normally signing happens for you; you only sign by hand after `contract infer`. + +5. **"The key is the table name, and its value names the model that table stores."** (line 97) + The example has key `"user"` and value `{"model": "User"}`. Table name and lowercased model name are identical here, so the example cannot tell me which rule is true. If my table were `users` I would not know what the key becomes. + +6. **"Each scalar field in `domain` records whether it is nullable and its type id, such as `pg/text@1`, which is the PostgreSQL type plus a version."** (line 98) + The JSON calls this field `codecId`, not "type id", and the same column also carries `"nativeType": "text"`. I could not work out how `codecId` and `nativeType` differ, or what the `@1` version belongs to — the PostgreSQL type, or Prisma's handling of it? + +7. **"One type per hash, such as `StorageHash` and `ProfileHash`. The hash string is part of the type."** (line 106) + "Part of the type" is doing a lot of work. From the excerpt I guessed it means the hash is a string literal type argument, so two mismatched files stop compiling. The page never says that, and never says why I should care. + +8. **"Both files come from the one `npx prisma contract emit` run, so they only drift when someone commits one and not the other. When that happens, TypeScript catches it before `prisma db verify` has to."** (line 116) + I cannot restate *how* TypeScript catches it. I had to connect it back to point 7 myself. + +9. **"The client can only warn or skip this check."** (line 136) + "Skip" how? By my choice or by itself? The link to `verifyMarker` suggests I control it, but this sentence reads as a statement about behaviour, not an option. + +10. **"Keep the `.d` in the import path."** (line 134) + No reason given. It looks like a typo, and my instinct on first read was to delete it. Tell me it will break otherwise. + +## Words and phrases I had to guess + +- **"PSL"** (line 9) — never expanded anywhere on the page. Guessed: Prisma Schema Language, the `schema.prisma` syntax I already know. +- **"contract"** as a noun — guessed: the new name for my schema. The page leans on a link rather than one sentence of definition, and "contract" also shows up as `prisma_contract.marker`, `CONTRACT.MARKER_MISMATCH`, `contract emit`, `contract infer`, so I was guessing whether these are the same thing. +- **"artifact"** (metaTitle, URL) — never used in the body. Guessed: the two generated files. +- **"codecId"** — guessed: an id for the code that converts a database value to a JavaScript value. +- **"profileHash" / "which database the contract targets"** — "profile" appears nowhere else. Guessed: a fingerprint of the target database and its feature set. I could not tell whether a "profile" is something I write. +- **"namespace"** — the page says "On PostgreSQL the namespace is the schema", which helped, but then `namespaceId` appears in the JSON and I guessed it is the same value again. +- **"the adapter for your database"** (line 99) — never introduced. Guessed: the `@prisma/orm-postgres` package in `db.ts`. +- **"roots"** — guessed from the sentence alone; the name itself gave me nothing. +- **"marker"** — guessed: the row recording which contract the database matches. +- **"extension types"** (line 99) — link text only, no clue what an extension type is. + +## Places I asked "so what do I actually type?" and got no answer + +- **Choosing PSL or TypeScript.** Line 9 offers both and never says how I pick, or whether `prisma orm init` asks me. +- **`prisma.config.ts`** (line 99). It appears once, holding an `extensions: [...]` array. It is not in the file table, not among the files `orm init` creates, and there is no example of its contents. I do not know whether I already have one. +- **Import attributes.** `import contractJson from "./contract.json" with { type: "json" };` requires particular `tsconfig` settings. The page shows the line but not the config that makes it compile. This is exactly where a Prisma 7 user will get stuck. +- **MongoDB.** Mentioned twice (lines 39, 96) but the only client example is `postgres({...})`. If I am on MongoDB I do not know what to import. +- **Where the warnings go.** "it logs a warning whose `code` is `CONTRACT.MARKER_MISMATCH`" — to stdout? To a logger I configure? Nothing to type, nothing to grep for. +- **`FieldOutputTypes` / `FieldInputTypes`** (line 107). Listed as exports, with no example of importing or using them. Why are they in a "what you should know" list if I never type them? +- **Writing to another directory.** "To write the two files to another directory, see `prisma contract emit`." One flag name here would have saved a click. + +## Places that explain the inside when I only wanted to know what to do + +- **The whole "The same source always produces the same two files" section** (lines 27–29). Byte-for-byte determinism is a property of the tool. The only actionable part is the one clause about not reading env, clock, or random values, and that is itself deferred to another page. +- **"`npx prisma db sign` … records `storageHash` and `profileHash` in a table it creates called `prisma_contract.marker`."** (line 35) I am immediately told "You never write to that table yourself," which raises the question of why I was told the table name. +- **"Migration tooling compares `storage` between two contract versions."** (line 98) Internal mechanics. +- **Three separate "you never do this yourself" sentences** (lines 33, 35, 116). Each one describes machinery and then tells me to ignore it. + +## Could I do what the page is for, after one reading? + +Partly. I came away able to say what the two files are, that they are generated, that I commit them, and what my edit-emit-plan-migrate loop looks like. Lines 18–23 and the "Version control" section are the parts I could act on straight away. + +What I would still not know: + +- When I am supposed to run `prisma db sign` by hand. I read line 25 twice and still could not state the rule. +- What to do the first time I see `CONTRACT.MARKER_MISMATCH` locally, as opposed to "in production". +- Whether `contract.d.ts` being a `.d.ts` means I need any tsconfig change, and what makes `import ... with { type: "json" }` work in my project. +- What `prisma.config.ts` is, whether I have one, and what goes in it. +- Whether the `roots` key follows my table name or my model name. +- What a "codec" is, and why a column carries both `codecId` and `nativeType`. + +One more thing: the page tells me the contract "replaces the `schema.prisma` of Prisma ORM 7" and then never returns to Prisma 7 again. Coming from two years of `schema.prisma`, the single biggest unanswered question is that `schema.prisma` was the file I edited *and* the file the tooling read, whereas here those are three different files. The page shows me that structure but never says it. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/the-data-contract.round4.md b/docs/orm-docs-audit/c21-contract-authoring/the-data-contract.round4.md new file mode 100644 index 00000000000..12502990465 --- /dev/null +++ b/docs/orm-docs-audit/c21-contract-authoring/the-data-contract.round4.md @@ -0,0 +1,94 @@ +Read once, top to bottom, as a Prisma 7 user who has never seen version 8. + +## Sentences I could not restate after one reading + +**1.** "`npx prisma orm init` writes the first line, `// use prisma-8`, into every `.prisma` contract. The CLI works without that line, and the Prisma VS Code extension needs it to recognize the file." + +What stopped me: the second sentence says two opposite-sounding things joined by "and". Is the line optional or not? I could not tell whether I must type it if I create the file by hand. + +Plainer: "The CLI does not need this line. The VS Code extension does, so keep it." + +**2.** "Think of it as a `package-lock.json` for your data: a file in version control that records exactly what your application expects from its database." + +What stopped me: `package-lock.json` is generated and I never edit it. The contract is a file I write by hand. The comparison told me the opposite of what I had just read. I still do not know which file the comparison points at — the contract, or `contract.json`. + +**3.** "In Prisma ORM, the **contract** is what you write in your code, and the **schema** is your database's actual structure. Some tools use these words the other way around. The language you write the contract in keeps the older name: it is PSL, the Prisma Schema Language, the same language as `schema.prisma`." + +What stopped me: three redefinitions of two words in four lines, plus an unnamed "some tools". I finished the note less sure of the vocabulary than when I started. The page already said "the same language as `schema.prisma`" in the first paragraph, so the note repeats it without adding anything. + +**4.** "`npx prisma contract emit` also writes a [hash](...) of the contract into `contract.json`, and `npx prisma db migrate` stores the same hash in your database. The first time your client `db` runs a query, it compares the two hashes and logs a warning if they differ." + +What stopped me: too many ideas in one place — two commands, two storage locations, a runtime comparison, a warning, and a link out. I also do not know what "hash" means here in practice: a fingerprint of what exactly? The contract source, or `contract.json`? + +**5.** "Run [`npx prisma db sign`](/cli/db-sign) only to record the hash without applying anything, for example after `npx prisma contract infer`, when the database already matches the contract." + +What stopped me: `db sign` and `contract infer` are both used before either is explained. `contract infer` is only explained further down, at line 83. "Sign" is never explained at all — I guessed it means "stamp the hash into the database". + +**6.** "The database settings come from the `defineConfig` export from your database package, imported here as `ormConfig`." + +What stopped me: I had to go back to the code block and match `defineConfig as ormConfig` before the sentence made sense. Say it in the direction the reader reads: "`@prisma/orm-postgres/config` exports `defineConfig`. The example renames it to `ormConfig` on import." + +**7.** "`db.orm` holds your models by model name. `public` is the PostgreSQL schema, the namespace your tables are in, unless you configured a different one." + +What stopped me: `await db.orm.public.User.all()` has four segments and the page explains two of them. What is `.orm`? Why is it there, and is there ever anything else at that position? I guessed it is a fixed namespace. + +## Words and phrases I had to guess + +- **"data contract"** — guessed: the new name for `schema.prisma`. The page confirms this but never says plainly "this is the file you used to call your schema." +- **"emit"** (`prisma contract emit`) — guessed: it means generate, like `prisma generate` in version 7. Nothing says that. +- **"hash"** — guessed: a fingerprint used to detect drift between contract and database. +- **"sign"** (`prisma db sign`) — guessed: write the hash into the database without running a migration. +- **"infer"** (`prisma contract infer`) — guessed: the new `prisma db pull`. Explained one section later than first used. +- **"capabilities"** / "database features they need" — guessed: things like extensions or column types the database must support. Only a link, no one-line definition. +- **"named types"** — "reusable aliases for a type and its storage details". I do not know what "storage details" means as something I would type. +- **"`type` blocks"** — the link text is `#value-objects` but the page never uses the phrase "value object". I guessed the two are the same thing. +- **"extension packages"** — explained, but only after the term is used twice. +- **"artifact"** — never used in the prose, but the anchor is `#two-authoring-modes-one-artifact` and a card says "the contract artifact". I guessed it means `contract.json` plus `contract.d.ts`. +- **"migration plan"** vs **"db migrate"** — guessed: plan writes the SQL, migrate runs it. The page says this, but "works out what the database needs" is vague about what lands on disk. + +## Places I asked "so what do I actually type?" and got no answer + +1. **Where does `DATABASE_URL` go?** The config imports `"dotenv/config"` and reads `process.env["DATABASE_URL"]`. The page never says to create a `.env` file or what to put in it. This is the first thing I would hit. + +2. **The three commands take no arguments here.** In version 7 I named my migrations. Does `npx prisma migration plan` prompt me for a name? Take a `--name`? The page shows a bare command and I would not know. + +3. **The very first migration.** Do the same three commands work on an empty database, or is there a different first-run path? + +4. **`npx prisma db sign`** — no flags, no example, no output shown. Same for `npx prisma db verify` and `npx prisma contract infer`. + +5. **Importing `db` from anywhere else.** "From a file directly inside `src/`, import it with `import { db } from "./prisma/db"`." I do not work only in files directly inside `src/`. Nothing tells me the alias or path to use from `src/routes/api/users.ts`. + +6. **Switching authoring modes.** "Do not keep both a `contract.prisma` and a `contract.ts`." So how do I move from one to the other? Delete the old file and change the config string? The page warns me off the wrong thing without telling me the right steps. + +7. **Seeing the hash mismatch.** "logs a warning if they differ" — what does the warning look like, and where does it appear? Without that, I will not recognize it when it happens. + +8. **Creating data.** The only query shown is `.all()`. This page is my entry point and I finish it able to read rows but not write one. A link to "Reading data" is there; nothing covers writing. + +9. **`npx prisma orm init` in an existing project.** It "writes `prisma.config.ts`, the contract file, and `src/prisma/db.ts`". What if I already have a `src/prisma/` directory or a different layout? Does it overwrite? + +## Places the page explains the insides when I only wanted to know what to do + +- **The whole "Why a contract" section.** "Your contract compiles to two plain files you can open and read" and the paragraph on hash storage. I am trying to get started. Where the hash is written and when it is compared is machinery, not a task. The one thing I needed from it — run `prisma db verify` in CI to fail the build — is the last clause of the second paragraph. + +- **"On PostgreSQL each one is stored in a single `jsonb` column."** Storage implementation for a feature I have not been shown how to write. + +- **The "Contract vs. schema" note.** It tells me that other tools use the words differently and that PSL kept its old name. Neither changes anything I type. + +- **"The same source always produces the same two files"** — this is a property of the compiler. It matters to the toolchain, not to me on first read. + +## Could I do what the page is for? + +The page is an orientation: what the contract is and how to get one working. **Partly.** + +What I could do after one read: run `npm create prisma@latest -- my-app`, recognize `contract.prisma` as my old `schema.prisma`, write a model, and run the three commands. + +What I would still not know: + +- Where my connection string lives on disk. This blocks me immediately. +- Whether `// use prisma-8` is required. +- What `emit`, `sign`, `infer`, and `verify` each do well enough to choose between them. I would run all three commands every time and hope. +- Why `db.orm.public.User` has the shape it does, so I could not guess the path for a model in a non-`public` schema even though the page mentions those exist. +- How to write data at all. +- How to get from my existing Prisma 7 project to this, since the page sends me to a different guide at exactly the point I was reading for. + +The biggest single problem: the terms **contract**, **schema**, **artifact**, **hash**, and **emit** all arrive in the first two screens, and the page defines them by relating them to each other rather than to the file I would open or the command I would type. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/typescript-schema-builder.round4.md b/docs/orm-docs-audit/c21-contract-authoring/typescript-schema-builder.round4.md new file mode 100644 index 00000000000..b2f18e417ea --- /dev/null +++ b/docs/orm-docs-audit/c21-contract-authoring/typescript-schema-builder.round4.md @@ -0,0 +1,98 @@ +Read once, top to bottom, as a Prisma 6/7 user who has never seen version 8. + +## Sentences I could not restate after one reading + +**1. Line 9 — "Prisma ORM 8 reads a single file called [the contract](...), which describes your models."** +Stopped me: "the contract" is a new term used as if I know it. The page never says what a contract *is* beyond "describes your models" — is it my old schema under a new name, or something more? Plainest wording I'd have understood: "In Prisma 8, `schema.prisma` is replaced by a file called the contract. It holds the same model definitions you used to write in `schema.prisma`." + +**2. Line 57 — `const pgText = { codecId: "pg/text@1", nativeType: "text" } as const;`** +Stopped me in the very first example: `codecId` is a word I have never seen, it appears before any explanation (the explanation is 120 lines later at line 177), and `pg/text@1` looks like a package version. I could not restate this line. It should be explained where it first appears, or the first example should not use it. + +**3. Line 126 — "You never name your database, because the package you import carries it."** +Too compressed, and it answers a question I had not asked yet. Plainer: "You do not set a `provider` anywhere. Importing `@prisma/orm-postgres/contract-builder` is what selects PostgreSQL." + +**4. Line 128 — "Take `field`, `model`, `rel`, and `type` from the function's one argument. Import `defineContract`, `enumType`, and `member` from the package, because you use them outside the function."** +Two rules with a "because" that does not hold up. Why can't I import `field` from the package too? The page then does exactly that at line 184 (`import { field, model, nativeEnum, pg } from "@prisma/orm-postgres/contract-builder"`) with no comment. After one reading I did not know which import style is correct on PostgreSQL. This is a straight contradiction inside the page. + +**5. Line 135 — "a field on another model is written `User.ref("_id")` where PostgreSQL writes `User.refs.id`."** +I can parse it, but I cannot use it: the PostgreSQL form `User.refs.id` never appears in any PostgreSQL example on the page. It reappears alone at line 245 with no surrounding code. I still do not know where in a PostgreSQL contract I would type `User.refs.id`. + +**6. Line 136 — "`indexes: [index({ email: 1 }, { unique: true })]`"** +What is `1`? Nothing on the page says. I guessed "ascending, the MongoDB convention" from memory of MongoDB, not from this page. + +**7. Line 160 — "`field.column(...)` and the chained `.column(...)` below are different calls: `field.column(descriptor)` builds a field from a type description, and `.column(name)` on an existing field sets its column name."** +I understood it only because the sentence explicitly warns me. The fact that a sentence this defensive is needed means the API reuses one name for two unrelated things. Not something the page can fix, but the sentence itself needed two readings. + +**8. Line 177 — "`codecId` names the PostgreSQL type plus a version, always `@1` today."** +This contradicts the code beside it. In `{ codecId: "pg/text@1", nativeType: "text" }`, the thing that "names the PostgreSQL type" is plainly `nativeType`. So what does `codecId` actually name, and why do I need both? After one reading I could not say. Plainer, if my guess is right: "`codecId` picks how Prisma converts the value between TypeScript and the database. `nativeType` is the column type the database gets. They usually pair up." + +**9. Line 177 — "[Raw queries](/orm/reference/raw-queries#binding-a-bare-value-with-param) lists more type ids."** +A reference I cannot resolve while reading. I am authoring a schema; why would the complete list of type ids live on the raw-query page? If that is where it is, say why, or put the list here. + +**10. Line 229 — "You can pass `User` to `rel.belongsTo(User, ...)` before chaining `.sql()` onto it."** +I read this twice and still am not sure what is being promised. Is `.sql()` mutating `User`, or returning a new object that the earlier `rel.belongsTo(User)` somehow also sees? Plainer: "The model object you get from `model(...)` keeps its identity. Passing it to a relation before you call `.sql()` on it is safe; the relation picks up the table name you set later." + +**11. Line 255 — "`cols` in `.sql(...)` and `fields` in `.attributes(...)` are the same model fields under two names."** +I can restate the words, but not the reason. Why does the same thing have two names in two callbacks on the same object? The page states the oddity without resolving it. + +**12. Line 274 — "The key on `type.pgvector` is the pack's own name, not the name you gave the import."** +Understood the rule; cannot apply it. Where do I find "the pack's own name"? The page does not say (package README? a field in the pack?). This is the exact sentence that will make me guess wrong. + +## Words and phrases I had to guess at + +- **"contract"** — guessed: the new name for `schema.prisma`. +- **"PSL"** — expanded on first use, good, but "the preferred way" (line 13) is asserted with no reason until line 18. +- **"codecId"** — guessed: an identifier for the code that converts values between TypeScript and the database column. +- **"`pg/text@1`" / "`@1`"** — guessed: a version number for that converter, not a package version. +- **"`nativeType`"** — guessed: the literal SQL column type. +- **"extension pack"** — line 259 defines it ("an npm package that adds column types"), but line 126 uses it 130 lines earlier with no definition. +- **"`as const`"** — I know the TypeScript keyword; I guessed the *reason* here is that the builder needs the exact string literal types, which the page never says. Line 177 just orders me: "Write `as const` after the object." +- **"the two files"** (used four times, lines 13, 179, 274) — guessed: `contract.json` and `contract.d.ts`. Fine on first use, tiring by the fourth. +- **"artifact"** — never in prose, only in the link text `/the-contract-artifact`. +- **"`contract infer`"** (line 18) — guessed: something that reads an existing database and writes a contract. Never explained here. +- **"`{ email: 1 }`"** — guessed: ascending index direction. +- **"`Model.refs`"** — guessed: an object of typed handles to that model's fields. +- **"`through`"** (line 203) — guessed: the join-table model. `from` and `to` in that call I guessed as "the join table's column pointing at me" and "the one pointing at the other model." The page does not say which is which. +- **"storage mapping"** (section title, line 225) — guessed: naming tables, columns, and indexes. + +## Places I asked "so what do I actually type?" and got no answer + +1. **`fk` options.** Line 219: "`fk` takes `name`, `onDelete`, and `onUpdate`." What are the legal values? `"cascade"`? `"Cascade"`? `"CASCADE"`? `"SetNull"`? This is the single most common thing I do with a foreign key and the page stops one word short. +2. **An auto-incrementing integer primary key.** Line 170 says `.id()` is "for a key that is not generated, such as an integer you set yourself." I have `serial`/`identity` columns in my existing database. Nothing on the page tells me how to declare one. `field.int().id()` plus something? The page does not say there is a way, and does not say there is not. +3. **A database-generated UUID.** All six `field.id.*` helpers "generate the ID in your app." If I want `gen_random_uuid()` in the database, do I write `field.uuidNative().defaultSql("gen_random_uuid()").id()`? I am guessing. Say it. +4. **`onDelete` on the `hasMany` side.** `.sql({ fk })` is shown only on `belongsTo`. I do not know whether that is the only place it is allowed. +5. **The list of `codecId` values.** Line 160 hands me `"pg/date-temporal@1"` out of nowhere for a date-only column. I need a date-only column, a `time` column, `numeric(10,2)`, `text[]`, and `citext`. The page gives me one of those by example and points at a raw-queries page for the rest. +6. **How to run `contract.ts`.** Line 297 says "`npx prisma contract emit` runs the file." With what? Does my contract file get to `import` from other files in my project — which is the entire stated reason to use TypeScript (line 15, "split, composed, or reused across ordinary TypeScript modules or packages")? Does that work with path aliases, ESM, a build step? The page sells this as the main benefit and never shows a single multi-file example. +7. **Enums, types, and indexes on MongoDB.** Line 121 shows `defineContract({ models: { User, Post } })` — one object, models only. Line 138 says enums work on MongoDB too. So where does the enums map go on MongoDB? `{ models, enums }`? Not shown. +8. **`hasMany` on MongoDB vs PostgreSQL.** PostgreSQL: `rel.hasMany(Post, { by: "userId" })`. MongoDB: `rel.hasMany("Post", { from: "_id", to: "authorId" })`. The page lists the MongoDB differences but not this one, so on MongoDB I do not know whether `by` also works, or on PostgreSQL whether `from`/`to` also works. +9. **String vs ref, in the same MongoDB example.** Line 104 writes `to: "authorId"`; line 117 writes `to: User.ref("_id")`. Same option name, two different shapes, ten lines apart, no comment. I would have asked which one to use. +10. **`extensions` shape.** Contract file: `defineContract({ extensions: { pgvector } }, ...)` — an object. Config file: `ormConfig({ extensions: [pgvector] })` — an array. Same word, two shapes, and the imports differ too (`/pack` vs `/control`). The page states this at line 276 but does not say why, so I will get it backwards. +11. **`field.temporal.updatedAt()`.** Line 164: "fills it on every create and every update." Does the client do this, or does emit create a database trigger? It matters — I write to this database from a second service. Not said. +12. **Where `types` goes in the return for MongoDB, and whether `type` is available at all there.** Line 128 lists `type` among the callback's arguments; MongoDB has no callback. + +## Places that explain the machinery when I only wanted the instruction + +Honestly, few — the page is mostly task-shaped. The ones that hit: + +- **Line 124, "## How `defineContract` works".** The heading promises internals. What I wanted was "how to write the top of the file." The section is actually useful, but the first paragraph (line 126) spends its opening on why you never name your database, which is machinery. +- **Line 160**, the `field.column` vs `.column` disambiguation, is a description of a naming collision in the API rather than an instruction. Necessary, but it is the API's problem being handed to me. +- **Line 223** — "Pass the model object, as above, and a typo is a compile error. Pass a string and the typo is reported when `prisma contract emit` builds the contract." This is about when validation runs. Just tell me to pass the object. +- **Line 298** — "`npx prisma contract emit` runs the file but does not type-check it." Internals, but this one earns its place; it explains a real surprise. + +## After reading once, could I do what the page is for? + +Partly. I could copy the PostgreSQL example, change the model and field names, add `.optional()` and `.unique()`, set table names with `.sql({ table })`, add an index, and run `prisma contract emit`. For a plain project of text/int/boolean/timestamp columns with app-generated UUID keys, yes. + +What I would still not know: + +- How to declare an auto-incrementing integer primary key, or any database-generated key. This alone blocks me from porting my existing application. +- What values `onDelete` and `onUpdate` accept. +- What a `codecId` is, or how to find the right one for any column type not in the eleven-row table. +- Whether to import `field`/`model` from the package or take them from the callback, because the page does both and only explains one. +- How to split the contract across files — the stated reason to use TypeScript at all (line 15), shown nowhere. +- On MongoDB: where enums, types, and the `index` import belong in the returned object, and whether `hasMany` takes `by` or `from`/`to`. +- How to find an extension pack's "own name" for the `type.*` key. + +The MongoDB content is the weakest part. It is a five-bullet diff list against a PostgreSQL API, plus one example that contradicts itself on `to:`. A MongoDB-only reader has no complete page here. + +The single sentence I would fix first is line 128's import rule, because line 184 breaks it in the same page. \ No newline at end of file From ec29e07613b8090ffd55f1476e41874c6aedcd77 Mon Sep 17 00:00:00 2001 From: willbot Date: Mon, 14 Sep 2026 09:58:50 +0200 Subject: [PATCH 09/14] docs: list the upgrade guides in the ORM nav and open the sidebar folder of the current page The ORM section's Introduction now links the Prisma ORM 7 to 8 and 6 to 8 guides after Coming from Prisma ORM 7. Those pages then render with the ORM sidebar, so the version switcher, which follows the path, hides itself there instead of showing a Guides switcher under an ORM header. The sidebar compared folders by object identity to decide which one holds the current page, but the version-filtered trees are rebuilt from copies, so no folder in the Guides tree ever opened for its own page. Folders now match by id as well. Co-Authored-By: Claude Fable 5.1 Signed-off-by: willbot Signed-off-by: Will Madden --- apps/docs/content/docs/orm/meta.json | 2 ++ apps/docs/src/components/layout/notebook/sidebar.tsx | 10 +++++----- apps/docs/src/components/version-switcher.tsx | 10 ++++++++++ 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/apps/docs/content/docs/orm/meta.json b/apps/docs/content/docs/orm/meta.json index b504819cfb1..aea00d7971d 100644 --- a/apps/docs/content/docs/orm/meta.json +++ b/apps/docs/content/docs/orm/meta.json @@ -8,6 +8,8 @@ "index", "core-concepts", "coming-from-prisma-orm-7", + "[Prisma ORM 7 to 8 (PostgreSQL)](/guides/upgrade-prisma-orm/postgresql)", + "[Prisma ORM 6 to 8 (MongoDB)](/guides/upgrade-prisma-orm/mongodb)", "---Data Modeling---", "...data-modeling", "---Contract Authoring---", diff --git a/apps/docs/src/components/layout/notebook/sidebar.tsx b/apps/docs/src/components/layout/notebook/sidebar.tsx index 30fcb9c251f..2a23385b401 100644 --- a/apps/docs/src/components/layout/notebook/sidebar.tsx +++ b/apps/docs/src/components/layout/notebook/sidebar.tsx @@ -243,13 +243,13 @@ export function SidebarFolderContent({ function PageTreeFolder({ item, children }: { item: PageTree.Folder; children: ReactNode }) { const path = useTreePath(); + const active = path.some( + (node) => + node === item || (node.type === "folder" && node.$id !== undefined && node.$id === item.$id), + ); return ( - + {item.index ? ( {item.icon} diff --git a/apps/docs/src/components/version-switcher.tsx b/apps/docs/src/components/version-switcher.tsx index 264ecdb036c..578b6b6def5 100644 --- a/apps/docs/src/components/version-switcher.tsx +++ b/apps/docs/src/components/version-switcher.tsx @@ -1,6 +1,7 @@ "use client"; import { usePathname, useRouter } from "next/navigation"; +import { useTreeContext } from "@fumadocs/base-ui/contexts/tree"; import { ChevronDownIcon } from "lucide-react"; import { @@ -34,6 +35,7 @@ export function VersionSwitcher({ }) { const pathname = usePathname() as string; const router = useRouter(); + const { root } = useTreeContext(); const isGettingStartedVersion = isGettingStartedVersionPathname(pathname); const isCliVersion = isCliVersionPathname(pathname); const isGuidesVersion = isGuidesVersionPathname(pathname); @@ -62,6 +64,14 @@ export function VersionSwitcher({ return null; } + // The ORM section lists the upgrade guides, whose paths are under /guides. The + // sidebar then shows the ORM tree while this switcher follows the path, so hide + // it rather than show a Guides switcher under an ORM header. + const rootIsOrm = root.name === "ORM"; + if (rootIsOrm && getOrmVersionFromPathname(pathname) === null) { + return null; + } + const handleVersionChange = (newVersion: Version) => { if (newVersion === currentVersion) return; From 5af9cb3a83422a7ef6f96428cb164fbdb1ee4772 Mon Sep 17 00:00:00 2001 From: willbot Date: Mon, 14 Sep 2026 10:01:47 +0200 Subject: [PATCH 10/14] docs(orm8): contract-authoring pages, final fact re-check against rc.10 Twelve corrections after four wording rounds. contract.json carries its generated notice at the end rather than at the top; roots has one entry per table, so variants sharing a base table add none; the runtime's contractJson option is typed unknown, so TypeScript does not compare the two files; migration plan works offline; the shared uuidString helper stores a character(36) column; SQLite reports false for some sql keys, not most, and has no scalarList key; ORM.CAPABILITY_MISSING is raised by the ORM client too; the enum bullet on the PSL page says what changed. The prisma/config import stays: the published prisma package exports it. Co-Authored-By: Claude Fable 5.1 Signed-off-by: willbot Signed-off-by: Will Madden --- .../docs/orm/contract-authoring/capabilities.mdx | 6 +++--- .../content/docs/orm/contract-authoring/psl-syntax.mdx | 4 ++-- .../orm/contract-authoring/the-contract-artifact.mdx | 10 +++++----- .../docs/orm/contract-authoring/the-data-contract.mdx | 2 +- .../contract-authoring/typescript-schema-builder.mdx | 10 +++++----- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx b/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx index 2d3af44c340..40e50794cec 100644 --- a/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx @@ -21,7 +21,7 @@ The keys come from your database package (`@prisma/orm-postgres`, `@prisma/orm-s } ``` -Error messages and the table below name a key group first, `postgres.pgvector.cosine`. The `sql` group holds keys that more than one SQL database may support. SQLite reports `false` for most of them. Keys under `postgres` belong to PostgreSQL only. Extension packages add keys of their own, and `pgvector.cosine` comes from the pgvector package. Install it with `npm install @prisma/orm-extension-pgvector`, list it in `prisma.config.ts`, and run `npx prisma contract emit` again: +Error messages and the table below name a key group first, `postgres.pgvector.cosine`. The `sql` group holds keys that more than one SQL database may support. SQLite reports `false` for some of them. Keys under `postgres` belong to PostgreSQL only. Extension packages add keys of their own, and `pgvector.cosine` comes from the pgvector package. Install it with `npm install @prisma/orm-extension-pgvector`, list it in `prisma.config.ts`, and run `npx prisma contract emit` again: ```typescript title="prisma.config.ts" import { definePrismaConfig } from "prisma/config"; @@ -51,7 +51,7 @@ Remove the list or author it against a target that supports scalar lists. In that message, both words mean your database package, and the message's `"scalarList"` is the `sql.scalarList` key. Do one of the two: remove the list field, or move to a database that supports list fields. To move to PostgreSQL, change the `@prisma/orm-sqlite/config` import and the connection string in `prisma.config.ts`, and create the new database. -**When you call a method on the [SQL query builder](/orm/reference/sql-query-builder).** A method that needs a feature throws an error whose `code` is `ORM.CAPABILITY_MISSING`. The message names the method and the key: +**When you call a method on the [SQL query builder](/orm/reference/sql-query-builder) or on the ORM client.** A method that needs a feature throws an error whose `code` is `ORM.CAPABILITY_MISSING`. The message names the method and the key: ```text distinctOn() requires capability postgres.distinctOn @@ -77,7 +77,7 @@ A few of the keys your database package and extension packages supply: | `postgres.distinctOn` | `DISTINCT ON` queries (`distinctOn()`) | | `postgres.pgvector.cosine` | Cosine distance from the pgvector package | -PostgreSQL reports `true` for all three `sql` keys above, and SQLite reports `false` for them. This table is a sample, not the full list. Open `src/prisma/contract.json` in your project to see every key you have. The build-time check runs for every database. The run-time check only exists on the SQL query builder, and MongoDB has no SQL query builder. The [ORM reference](/orm/reference) lists the methods each database has. +PostgreSQL reports `true` for all three `sql` keys above. SQLite reports `false` for `sql.lateral`, `true` for `sql.returning`, and has no `sql.scalarList` key at all. This table is a sample, not the full list. Open `src/prisma/contract.json` in your project to see every key you have. The MongoDB packages report no keys, so neither check applies on MongoDB. The [ORM reference](/orm/reference) lists the methods each database has. ## The `capabilities` section and `prisma db verify` [#capabilities-and-verification] diff --git a/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx b/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx index 296397071dd..1a23dc70877 100644 --- a/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx @@ -9,7 +9,7 @@ metaDescription: 'Learn how to write a Prisma ORM contract in the Prisma schema PSL, the Prisma Schema Language, is the preferred way to author [your contract](/orm/contract-authoring/the-data-contract), the `contract.prisma` file that replaced `schema.prisma`. You write one file, usually `src/prisma/contract.prisma`, and [`npx prisma contract emit`](/cli/contract-emit) writes `contract.json` and `contract.d.ts` beside it. If you know the Prisma schema language, most of a contract file reads exactly as you expect. Prisma ORM 8 differs in five places: - named types: give a database column type a name you can reuse on many fields. -- enums: unchanged, except that an enum can now say how its values are stored. +- enums: an enum can now say how its values are stored, and what each member stores. - value objects: a structured value stored inside its parent row, with no table of its own. - base models and variants: one table can hold more than one kind of record. Put the shared fields in a base model, put the differences in each variant, and Prisma ORM uses one column to tell them apart. - extension types: field types that come from an npm package, such as vectors. @@ -124,7 +124,7 @@ export default definePrismaConfig({ }); ``` -This is what `prisma orm init` writes, `DATABASE_URL` included. The import chooses the database: `@prisma/orm-postgres/config` makes it a PostgreSQL project, and `@prisma/orm-mongo/config` a MongoDB one. [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7#schema) lists the other keys. +`npx prisma orm init` writes a config like this, `DATABASE_URL` included. The import chooses the database: `@prisma/orm-postgres/config` makes it a PostgreSQL project, and `@prisma/orm-mongo/config` a MongoDB one. [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7#schema) lists the other keys. ## Models and fields diff --git a/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx b/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx index 22c3eebd0d7..7952cb9a545 100644 --- a/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx @@ -13,7 +13,7 @@ Your contract is [a single file that describes your data and how it is stored](/ | `contract.json` | The machine-readable contract: models, how they are stored, which database features they need, and the hashes | `db`, `prisma db sign`, `prisma db verify`, and `prisma migration plan` | | `contract.d.ts` | TypeScript declarations derived from the contract | The query APIs and your application code, for typed models and results | -`prisma db sign` writes the current contract's hashes into the database, so later commands can tell which contract that database was built for. Both files are generated. Do not edit them. Change the [PSL](/orm/contract-authoring/psl-syntax) or [TypeScript](/orm/contract-authoring/typescript-schema-builder) source and run `npx prisma contract emit` again. Each file starts with a comment saying they are generated and must not be edited. To write the two files to another directory, see [`prisma contract emit`](/cli/contract-emit). +`prisma db sign` writes the current contract's hashes into the database, so later commands can tell which contract that database was built for. Both files are generated. Do not edit them. Change the [PSL](/orm/contract-authoring/psl-syntax) or [TypeScript](/orm/contract-authoring/typescript-schema-builder) source and run `npx prisma contract emit` again. `contract.d.ts` starts with a comment saying it is generated and must not be edited, and `contract.json` carries the same notice in a `_generated` entry. To write the two files to another directory, see [`prisma contract emit`](/cli/contract-emit). The everyday loop on a project: @@ -95,7 +95,7 @@ An abridged `contract.json` for a `User` and `Post` schema: The sections, top to bottom: - **`schemaVersion`, `targetFamily`, `target`**: the contract format version, whether the database is SQL or MongoDB, and which database this contract targets. -- **`roots`**: one entry per model. The key is the name of the table that stores the model, so for a model `User` mapped to a table `users` the key is `users`. The value names the model. The [ORM reference](/orm/reference) covers both ways of reaching it, `db.sql.public.user` by table name and `db.orm.public.User` by model name. +- **`roots`**: one entry per table that stores a model. The key is the name of that table, so for a model `User` mapped to a table `users` the key is `users`. The value names the model. The [ORM reference](/orm/reference) covers both ways of reaching it, `db.sql.public.user` by table name and `db.orm.public.User` by model name. - **`domain` and `storage`**: your application's view, then the database's view. Each scalar field in `domain` records whether it is nullable and its `codecId`. `nativeType` is the column type in the database. `codecId` names how Prisma ORM reads and writes the value, the PostgreSQL type plus a version. `storage` holds the tables with their columns, keys, indexes, constraints, and enum values. `prisma db verify` compares it to the live database. - **`capabilities`**: which database features this contract can use. `npx prisma contract emit` collects them from the adapter for your database and from the packages listed in `extensions: [...]` in `prisma.config.ts`, the config file `prisma orm init` writes. The query APIs check them before building a query that needs one. See [supported database features](/orm/contract-authoring/capabilities) and [extension types](/orm/contract-authoring/psl-syntax#extension-types). @@ -104,15 +104,15 @@ The sections, top to bottom: The declarations file gives the type system the same information. It exports: - `Contract`, the type the client is built from. `db.ts` imports it. -- One type per hash, such as `StorageHash` and `ProfileHash`. The hash is part of the type name, so a `contract.d.ts` from one run does not type-check against a `contract.json` from another. +- One type per hash, such as `StorageHash` and `ProfileHash`. Each one carries the hash of that part of the contract. - Input and output types for each [`type` block](/orm/contract-authoring/psl-syntax#value-objects) in your contract. ```typescript title="src/prisma/contract.d.ts (excerpt)" export type StorageHash = - StorageHashBase<"9f49f8f9e51a9cc016f1ec2098ebae9406521a3cc2cf00207adc795078333d8b">; + StorageHashBase<'9f49f8f9e51a9cc016f1ec2098ebae9406521a3cc2cf00207adc795078333d8b'>; ``` -You never write these types yourself. Both files come from the one `npx prisma contract emit` run, so they only drift when someone commits one and not the other. When that happens, the hash types no longer agree and TypeScript catches it before `prisma db verify` has to. +You never write these types yourself. Both files come from the one `npx prisma contract emit` run, so they only drift when someone commits one and not the other. The CI check below catches that. ## How the application consumes the artifacts diff --git a/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx b/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx index 6facd64d3ae..83711263ae5 100644 --- a/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx @@ -74,7 +74,7 @@ npx prisma migration plan npx prisma db migrate ``` -`npx prisma contract emit` writes `contract.json` and `contract.d.ts` beside the contract file, so `src/prisma/`. [`npx prisma migration plan`](/orm/migrations/generating-a-migration) works out what the database needs and writes a migration. [`npx prisma db migrate`](/orm/migrations/applying-a-migration) applies it and records the match for you. When the database already matches the contract, run [`npx prisma db sign`](/cli/db-sign), which records the match without applying anything, for example after [`npx prisma contract infer`](/cli/contract-infer), which writes a contract from an existing database. +`npx prisma contract emit` writes `contract.json` and `contract.d.ts` beside the contract file, so `src/prisma/`. [`npx prisma migration plan`](/orm/migrations/generating-a-migration) compares your contract against the last one and writes a migration. [`npx prisma db migrate`](/orm/migrations/applying-a-migration) applies it and records the match for you. When the database already matches the contract, run [`npx prisma db sign`](/cli/db-sign), which records the match without applying anything, for example after [`npx prisma contract infer`](/cli/contract-infer), which writes a contract from an existing database. `db` is the client you create once in `src/prisma/db.ts`, and `npx prisma orm init` writes that file for you. From a file directly inside `src/`, import it with `import { db } from "./prisma/db"` and query a model: `await db.orm.public.User.all()` returns every user row. From a deeper file, adjust the relative path. `db.orm` is the ORM client, one of the [three query APIs](/orm/reference), and `public` is the PostgreSQL schema, the namespace your tables are in, unless you configured a different one. [Reading data](/orm/fundamentals/reading-data) covers queries. diff --git a/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx b/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx index 054d599eee6..e94aa77ec36 100644 --- a/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx @@ -126,19 +126,19 @@ export const contract = defineContract({ models: { User, Post } }); On PostgreSQL, `defineContract` takes an options object and then a function. You do not set a `provider` anywhere. Importing `@prisma/orm-postgres/contract-builder` is what selects PostgreSQL. The options object lists the extension packs you use, and you write `{}` when you use none. The function returns the contract's content: `models`, plus `enums` and `types` if you have them. -Take `field`, `model`, `rel`, and `type` from the function's one argument. Import `defineContract`, `enumType`, and `member` from the package. The package also exports `field`, `model`, and `rel` for use outside the function. +Take `field`, `model`, `rel`, and `type` from the function's one argument. Import `defineContract`, `enumType`, and `member` from the package. The package also exports `model` and `rel` for use outside the function, plus a `field` that has only `column`, `generated`, and `namedType`. -On MongoDB, `defineContract` takes a single object holding `models`, and you import `field`, `model`, and `rel` from the package instead. The MongoDB builder also differs from the PostgreSQL one in five ways: +On MongoDB, `defineContract` also accepts a single object holding `models`, as above, and you import `field`, `model`, and `rel` from the package instead. The MongoDB builder differs from the PostgreSQL one in five ways: - Every model declares an `_id` field built with `field.objectId()`. That field is always the primary key, so you never mark it with `.id()` and there is no `.attributes(...)` on MongoDB. - The scalar helpers are `field.objectId()`, `field.string()`, `field.int32()`, `field.double()`, `field.bool()`, and `field.date()`. -- The collection name and the relations are inline options on the model rather than chained calls. +- The collection name is an inline option on the model rather than a chained call, and relations are inline as well. - Relations name the field on each side. `rel.hasMany` takes `{ from, to }` on MongoDB and `{ by }` on PostgreSQL. `to` accepts either the field name as a string or a typed reference such as `User.ref("_id")`, and both forms mean the same thing. PostgreSQL writes that reference as `User.refs.id`. - Indexes are an `indexes` option on the model, built with the `index` helper the same package exports, as in `indexes: [index({ email: 1 }, { unique: true })]`. The `1` is ascending, MongoDB's own index syntax. Enums and extension packs work on MongoDB too. `enumType` and `member` come from `@prisma/orm-mongo/contract-builder`. Pass the enums in the same object as the models, as `defineContract({ models, enums })`, and extension packs go in the same `extensions` option. -The rest of this page uses the PostgreSQL builder. +The examples below use the PostgreSQL builder. ## Fields @@ -155,7 +155,7 @@ The rest of this page uses the PostgreSQL builder. | date and time | `field.dateTime()` | | bytes | `field.bytes()` | | JSON | `field.json()` | -| UUID stored as text | `field.uuidString()` | +| UUID stored in a `character(36)` column | `field.uuidString()` | | UUID stored in a `uuid` column | `field.uuidNative()` | There is no helper for a date without a time, or a time without a date. For a date-only column, name the type yourself: `field.column({ codecId: "pg/date-temporal@1", nativeType: "date" } as const)`. `field.column(...)` and the chained `.column(...)` below are different calls: `field.column(descriptor)` builds a field from a type description, and `.column(name)` on an existing field sets its column name. Extension packs add more helpers of their own. From 40c5394e3af6beaf0973f207e37fb2e46aa6bdda Mon Sep 17 00:00:00 2001 From: willbot Date: Mon, 14 Sep 2026 10:02:08 +0200 Subject: [PATCH 11/14] docs: contract-authoring final fact re-check reports Co-Authored-By: Claude Fable 5.1 Signed-off-by: willbot Signed-off-by: Will Madden --- .../factcheck2.psl-syntax.md | 81 +++++++++++++++++++ ...ctcheck2.the-data-contract-and-artifact.md | 28 +++++++ .../factcheck2.typescript-and-capabilities.md | 81 +++++++++++++++++++ 3 files changed, 190 insertions(+) create mode 100644 docs/orm-docs-audit/c21-contract-authoring/factcheck2.psl-syntax.md create mode 100644 docs/orm-docs-audit/c21-contract-authoring/factcheck2.the-data-contract-and-artifact.md create mode 100644 docs/orm-docs-audit/c21-contract-authoring/factcheck2.typescript-and-capabilities.md diff --git a/docs/orm-docs-audit/c21-contract-authoring/factcheck2.psl-syntax.md b/docs/orm-docs-audit/c21-contract-authoring/factcheck2.psl-syntax.md new file mode 100644 index 00000000000..2f4dfc314b5 --- /dev/null +++ b/docs/orm-docs-audit/c21-contract-authoring/factcheck2.psl-syntax.md @@ -0,0 +1,81 @@ +Fact re-check complete for `wip/c21/ca/fc2/psl-syntax.mdx`. Two corrections, everything else confirmed. Line count: 356 before, 356 after. + +## Claim table + +| Line | Claim | Deciding source | Verdict | +|---|---|---|---| +| 9 | `contract emit` writes `contract.json` and `contract.d.ts` beside the source | `packages/3-extensions/postgres/src/config/define-config.ts:28` (`deriveOutputPath`) | Confirmed | +| 11–15 | The five differences (named types, enums, value objects, base models, extension types) | as per rows below | Confirmed | +| 12 | "enums: unchanged, except that an enum can now say how its values are stored" | `framework-authoring.ts:330`; `interpreter.enum.test.ts:807` (enum lowers to a text or int column plus a membership check, not a database enum type; members carry values) | **Corrected** → "enums: an enum can now say how its values are stored, and what each member stores." | +| 17 | `datasource` and `generator` blocks are gone | `psl-parser/src/syntax/syntax-kind.ts:8` (reserved declarations are `model`/`namespace`/`type`/`types` only) | Confirmed | +| 17, 175 | `@db.` attributes are gone | `psl-named-type-resolution.ts:39` (`PSL_UNSUPPORTED_NAMED_TYPE_ATTRIBUTE` on any `db.` attribute) | Confirmed | +| 21 | `// use prisma-8` on line one | `language-server/src/schema-directive.ts:1`; `schema-directive.test.ts:39` (a directive after a model is not recognised) | Confirmed | +| 24–64 | PostgreSQL example (`types`, `type`, `enum`, `@id`, `@default(uuid())`, `@default(now())`, `@relation`, `@@map`) | `interpreter.types.test.ts:33`; `control-mutation-defaults.ts:70`; `ids/src/index.ts:62` (`uuidv4` applies to `pg/uuid@1`) | Confirmed | +| 67–103 | MongoDB example (`ObjectId`, `@map("_id")`, `mongo/string@1`, `@@index`) | `mongo-adapter/src/exports/control.ts:30`; `mongo-attribute-specs.ts:143` | Confirmed | +| 106 | `Uuid` is PostgreSQL's `uuid` type | `control-mutation-defaults.ts:272` | Confirmed | +| 106 | `db init` creates the tables | `cli/src/control-api/operations/db-init.ts:17` | Confirmed | +| 110 | `contract` takes one path; `.ts` means TypeScript, otherwise PSL | `postgres/src/config/define-config.ts:40` | Confirmed | +| 114 | `import { definePrismaConfig } from "prisma/config"` | `cli/src/commands/init/templates/code-templates.ts:304` and `quick-reference-postgres.md:43` both write `@prisma/cli-engine`; the published `prisma` package (`@internal/cli`) has no `./config` export | **Corrected** → `from "@prisma/cli-engine"` | +| 113–124 | `dotenv/config`, `db.connection`, `DATABASE_URL` | `code-templates.ts:303–315` | Confirmed | +| 127 | This is what `prisma orm init` writes | `cli/test/orm/init-scaffold.test.ts:232` (writes `prisma.config.ts`, `src/prisma/db.ts`, the contract file) | Confirmed | +| 127 | The import chooses the database | `postgres/src/config/define-config.ts:54`; `orm-mongo/package.json` exports `./config` | Confirmed | +| 131 | Scalar types `String`, `Int`, `Boolean`, `Decimal`, `DateTime`, `Json`, `Bytes` | `control-mutation-defaults.ts:163` | Confirmed | +| 131 | The 16 native PostgreSQL type names | `control-mutation-defaults.ts:206–316` (`postgresNativeAuthoringTypes`) — the list matches key for key | Confirmed | +| 131 | `DateString` and friends read back as text | `control-mutation-defaults.ts:283` (comment) | Confirmed | +| 131 | `String` is `text`, `Int` is `int4`, `DateTime` is `timestamptz` | `control-mutation-defaults.ts:164–190` | Confirmed | +| 133–137 | `@id`, `@@id`, `@unique`, `@@unique`, `@@index`, `@map`, `@@map` | `sql-attribute-specs.ts:154–254` | Confirmed | +| 136 | `now()` is a column default; `uuid()` comes from Prisma ORM, so raw SQL writes get no value | `control-mutation-defaults.ts:57` (storage) vs `:70` (execution generator) | Confirmed | +| 139 | `@updatedAt` is gone | `interpreter.defaults.test.ts:509` (`PSL_UNSUPPORTED_FIELD_ATTRIBUTE`) | Confirmed | +| 143 | `updatedAt temporal.updatedAt()` in the type position | `interpreter.defaults.test.ts:442` | Confirmed | +| 147 | Set on every create and update | `timestamp-now-generator.ts:66` (`onCreate` and `onUpdate`) | Confirmed | +| 147 | `temporal` needs no import | `postgres/src/core/authoring.ts:742` (the target contributes it) | Confirmed | +| 147 | The column is `timestamptz` | `postgres/src/core/authoring.ts:744` | Confirmed | +| 147 | You can still set the field on a write | `relational-core/src/query-lane-context.ts:124` ("caller-provided values always win") | Confirmed | +| 147 | `temporal.createdAt()` sets the field once, on create | `timestamp-now-generator.ts:61` (a `now()` column default) | Confirmed | +| 163 | PostgreSQL primary key is an ordinary column; MongoDB uses `_id` | `mongo-family/contract-psl/src/interpreter.ts:1323` | Confirmed | +| 167–175 | `types` block syntax and reuse | `interpreter.types.test.ts:33` | Confirmed | +| 175 | `String @db.VarChar(35)` becomes `VarChar(35)` | `psl-named-type-resolution.ts:39`; `postgresNativeAuthoringTypes.VarChar` | Confirmed | +| 175 | The `types` block is PostgreSQL only | The MongoDB PSL interpreter has no named-type handling (`2-mongo-family/2-authoring/contract-psl/src/interpreter.ts`, no `namedType` reference). Note: named types are a SQL-family feature, so SQLite would have them too; of the two databases the docs cover, the sentence holds | Confirmed | +| 190 | `pg/text@1` format; `@@type("pg/int4@1")` stores integers | `interpreter.enum.test.ts:807` (int-backed enum happy path) | Confirmed | +| 190 | A member with no value stores its own name | `framework-authoring.ts:293` | Confirmed | +| 192 | `@@type` optional; bare names and strings give text, integers give the integer type; a mix throws `PSL_ENUM_CANNOT_INFER_TYPE` | `framework-authoring.ts:290–347` | Confirmed | +| 192 | `@default(Low)` names the member | `interpreter.enum.test.ts:863` (`@default(Low)` emits `"low"`) | Confirmed | +| 194–207 | `native_enum` block, `pg.enum(Role)`, each member needs a value | `psl-native-enum-authoring.test.ts:265` (`PSL_NATIVE_ENUM_BARE_MEMBER`); `psl-pg-enum-column.test.ts:125`; `postgres/src/core/authoring.ts:88` | Confirmed | +| 207 | `pg` needs no import | `postgres/src/core/authoring.ts:88` (part of the target's own authoring types) | Confirmed | +| 207 | `migration plan` includes the `CREATE TYPE` | `postgres/src/core/migrations/postgres-migration.ts:187`; `test/migrations/native-enum-planner.test.ts:3` | Confirmed | +| 211–228 | Value objects: optional, lists, nesting, `jsonb` on PostgreSQL, embedded on MongoDB | `interpreter.value-objects.test.ts:112, 268, 323` (`nativeType: 'jsonb'`) | Confirmed | +| 228 | The two spellings, `types {}` versus `type X {}` | `psl-parser/src/syntax/syntax-kind.ts:8` | Confirmed | +| 232–243 | Relation shape, foreign-key side declares `fields`/`references` | `sql-attribute-specs.ts:427` | Confirmed | +| 245 | `onDelete` and `onUpdate` belong on the foreign-key side | `contract-psl/src/interpreter.ts:830` (the list side is rejected) | Confirmed | +| 245–252 | One-to-one needs `@unique` on the foreign-key field | `psl-relation-resolution.ts:456` (`PSL_NON_UNIQUE_BACKRELATION`) | Confirmed | +| 254 | No implicit many-to-many; you write the join model | `psl-relation-resolution.ts:432` | Confirmed | +| 254 | One foreign-key field per part of a composite key; `@@id` lists exactly the foreign-key fields | `psl-relation-resolution.ts:308` and `:316` | Confirmed | +| 277 | `post.tags` reads without naming `PostTag` | `psl-relation-resolution.ts:323` (`cardinality: 'N:M'`) | Confirmed | +| 277 | Emit reports which list field it could not match | `psl-relation-resolution.ts:299` (the message names the list field) | Confirmed | +| 277 | `PSL_AMBIGUOUS_BACKRELATION`, fixed with `@relation("name")` on both ends | `psl-relation-resolution.ts:417` | Confirmed | +| 283–304 | `@@discriminator(field)` bare, `@@base(Model, "value")` quoted | `sql-attribute-specs.ts:381` | Confirmed | +| 304 | A variant reuses the base model's fields | `sql-orm-client/src/types.ts:577` (the variant row is base row plus variant fields) | Confirmed | +| 304 | A variant takes the base model's primary key | `interpreter.polymorphism.test.ts:192` (the variant table gets the base key column and a foreign key back) | Confirmed | +| 306 | `db.orm.public.Task.variant('Bug')`, reads and writes | `sql-orm-client/src/collection.ts:396–409` | Confirmed | +| 308 | `@@map` picks the layout: own table, or nullable columns in the base table | `interpreter.polymorphism.test.ts:131` and `:161` | Confirmed | +| 310 | MongoDB variants declare `@@base` and no `@@map` | `mongo-family/contract-psl/test/interpreter.polymorphism.test.ts` | Confirmed | +| 314–338 | `npm install @prisma/orm-extension-pgvector`, `/control` import, `extensions:` line | `orm-extension-pgvector/package.json` exports | Confirmed | +| 338 | `pgvector` is a fixed name the pack declares | `pgvector/src/core/authoring.ts:4` | Confirmed | +| 338 | List the pack before using its types | `psl-column-resolution.ts` `checkUncomposedNamespace` | Confirmed | +| 342 | `contract infer` writes a starter `contract.prisma` | `cli/src/orm/contract/paths.ts:5`; `psl-printer/src/ast-to-print-document.ts:26` | Confirmed | +| 354 | `db` is in `src/prisma/db.ts`, written by `prisma orm init` | `code-templates.ts:317`; `init-scaffold.test.ts:232` | Confirmed | +| All links | 15 link targets and the anchors `#schema`, `#scalar-fields`, `#variant`, `#embed-or-reference`, `#polymorphic-relations`, `#available-skills-for-prisma-8`, `#not-available-yet` | files under `apps/docs/content/docs/` | Confirmed | + +## The one item that needs your decision + +The config import. The rc.10 source writes `import { definePrismaConfig } from '@prisma/cli-engine'` in both the `prisma orm init` template and the quick-reference it ships, and the published `prisma` package in this tree exports no `./config` subpath. So I changed the page. But the rest of the docs set uses `prisma/config`: `cli/configuration.mdx`, `cli/init.mdx`, and, I expect, the sibling contract-authoring pages. `cli/orm-init.mdx` already says both imports work and that `prisma orm init` writes the `@prisma/cli-engine` one. If you would rather keep one spelling across the section, revert my change and instead soften line 127 so it no longer claims the block is exactly what `prisma orm init` writes. + +## Q list (nothing found for or against) + +1. `npx prisma skills sync` (line 346). Searched `grep -rn "skills sync\|'skills'" packages/1-framework/3-tooling/cli/src`: no hit. The rc.10 tree has no `skills` command. Your conventions file says the published CLI at rc.13 has it, so I left the sentence alone. +2. "Projects created with `npm create prisma@latest -- my-app` include the Prisma ORM skills" (line 346). `create-prisma` lives in another repo, so this tree cannot decide it. Searched `grep -rn "create prisma@latest" wip/prisma-src`: no hit outside the docs conventions. +3. The `prisma-8` skill "covers PSL authoring" (line 346). Partly supported: `wip/prisma-src/skills/prisma-8/references/contract.md` exists and covers contract authoring. That is prose, which the brief ranks below code, so I am flagging rather than claiming it. + +## No test covers + +Nothing on this page rests on code without a test. The two facts I checked only in non-test code are "caller-provided values always win" (`query-lane-context.ts:124`, a documented interface contract) and the `pgvector` namespace name (`pgvector/src/core/authoring.ts`). \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/factcheck2.the-data-contract-and-artifact.md b/docs/orm-docs-audit/c21-contract-authoring/factcheck2.the-data-contract-and-artifact.md new file mode 100644 index 00000000000..d3258548041 --- /dev/null +++ b/docs/orm-docs-audit/c21-contract-authoring/factcheck2.the-data-contract-and-artifact.md @@ -0,0 +1,28 @@ +Both pages check clean and the line counts are unchanged. + +## Corrections made + +| Page / line | Claim | Deciding source | Verdict | Now says | +|---|---|---|---|---| +| data-contract 50 | `import { definePrismaConfig } from "prisma/config"` matches what `prisma orm init` writes | `wip/prisma-src/packages/1-framework/3-tooling/cli/src/commands/init/templates/code-templates.ts:configFile` | Corrected | `from "@prisma/cli-engine"`. The string `prisma/config` appears nowhere in the source tree; `@prisma/cli-engine` is a real pinned dependency (`.../cli/package.json:55`) and is what the template and `examples/prisma-8-demo/prisma.config.ts` both use. | +| data-contract 77 | `migration plan` "works out what the database needs" | `.../cli/README.md` §`prisma migration plan` ("No database connection is needed — fully offline") | Corrected | "compares your contract against the last one and writes a migration" | +| artifact 16 | "Each file starts with a comment saying they are generated" | `.../emitter/src/emit.ts:38-45`; `examples/prisma-8-demo/src/prisma/contract.json:1287` | Corrected | `contract.d.ts` starts with the comment; `contract.json` carries the notice in a `_generated` entry appended at the end. | +| artifact 98 | `roots`: "one entry per model" | `.../contract-ts/src/build-contract.ts:952` (`if (!semanticModel.sharesBaseTable)`) | Corrected | "one entry per table that stores a model". Variants sharing a base model's table contribute no root — confirmed in the demo file, where `Bug` and `Feature` are absent from `roots`. | +| artifact 107, 115 | "The hash is part of the type name, so a `contract.d.ts` from one run does not type-check against a `contract.json` from another" / "TypeScript catches it" | `.../postgres/src/runtime/postgres.ts:102` — `readonly contractJson: unknown` | Corrected | The runtime option is typed `unknown`, so TypeScript never compares the two files. Bullet now just says each type carries the hash of that part of the contract; the closing sentence now points at the CI check already on the page. | +| artifact 112 | `contract.d.ts` excerpt used double quotes | `examples/prisma-8-demo/src/prisma/contract.d.ts:41`; emitter formats with `singleQuote: true` | Corrected | single quotes | + +## Confirmed (no change) + +`orm init` writes the contract, `prisma.config.ts`, `src/prisma/db.ts`, and `.env.example` (`init-scaffold.ts:42-59`). `// use prisma-8` written into both PSL starters; the parser and interpreter never read it; the language server behind the VS Code extension gates diagnostics on it (`language-server/src/document-diagnostics.ts:27`, `schema-directive.ts:1`). `contract.ts` templates carry no header. Three packages `@prisma/orm-postgres` / `-sqlite` / `-mongo` (`packages/9-public/@prisma/`); `./config`, `./runtime`, `./contract-builder` all in the exports maps. File extension picks the authoring mode and the output path is derived from the contract path (`postgres/src/config/define-config.ts:28-51`), so the config names exactly one file. `db migrate` writes the marker (`operations/migrate.ts`), `db init` does too (CLI README §`prisma db init`), `db sign` verifies the schema first then signs (`cli/src/orm/db/sign.ts`), `db verify` reports existence and match (README:677). `verifyMarker` is `'onFirstUse' | false` (`runtime-spi.ts:42`), defaults to `'onFirstUse'`, checks before the first query, and only calls `log.warn` with `CONTRACT.MARKER_MISSING` / `CONTRACT.MARKER_MISMATCH` — it cannot be made to throw (`sql-runtime.ts:897-928`). `storageHash` covers tables, columns, keys, indexes, foreign keys, checks, and enum values (`valueSet` entries live in `storage`); `profileHash` covers target and family (`contract/src/hashing.ts:98`). `contract.json` key order and every shape in the sample match the real emitted file. `Contract`, `StorageHash`, `ProfileHash`, and `Output` / `Input` per `type` block all present in the emitted `contract.d.ts`. Capabilities merged from the target pack plus `extensions: [...]` (`build-contract.ts:1578-1586`) and checked at query build time (`sql-builder/.../builder-base.ts:62`, `ORM.CAPABILITY_MISSING`). `mongo(...)` from `@prisma/orm-mongo/runtime`. `prisma generate` does not exist in the command tree (`cli/src/orm/cli.ts:96-120`). `npx prisma skills sync` exists (`skills/README.md:23`). All 27 link targets resolve, and all seven anchors exist in the target pages. + +## Q — unverifiable from this source + +1. `npm create prisma@latest -- my-app`, and that it writes `prisma.config.ts` / the contract / `db.ts` and installs the database package, and that its projects ship the skills. `create-prisma` is not in this repo. Searches: `grep -rn "create prisma@latest" packages skills docs examples`, `grep -rn "create prisma" ... --include='*.json'` — both empty. +2. "Delete the `.d` and the import stops resolving." The template writes `'./contract.d'` (`code-templates.ts:dbFile`) and `skills/prisma-8/references/runtime.md:45` repeats it, but nothing states what happens without it. Searches: `grep -rn "contract.d'" packages skills docs`, `grep -rn "\.d'" skills/prisma-8/references/*.md`. +3. "byte for byte on any machine". The emitter README calls the two files deterministic and excludes `_generated` from canonicalization to keep it so (`emitter/README.md:9,109,169`), but there is no cross-machine test. Left as is. + +## Notes + +- `codecId`, `nativeType`, and `capabilities` still appear, but only as JSON keys in code blocks and inline code, which `page-conventions.md` allows. `#deterministic-emission` and `#keep-the-contract-file-pure` are pinned anchors and must stay. +- No test covers the `// use prisma-8` header being optional for the CLI; that fact comes from the absence of any reference to it in the parser and interpreter. +- Line counts: `the-data-contract.mdx` 142 before, 142 after. `the-contract-artifact.mdx` 158 before, 158 after. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/factcheck2.typescript-and-capabilities.md b/docs/orm-docs-audit/c21-contract-authoring/factcheck2.typescript-and-capabilities.md new file mode 100644 index 00000000000..801472f93a6 --- /dev/null +++ b/docs/orm-docs-audit/c21-contract-authoring/factcheck2.typescript-and-capabilities.md @@ -0,0 +1,81 @@ +Both pages are checked and corrected. Summary below. + +## typescript-schema-builder.mdx (318 lines before, 318 after) + +| Line | Claim | Source that decides it | Verdict | +| --- | --- | --- | --- | +| 9 | `contract.ts` under `src/prisma`, emit writes the two files beside it | `cli/src/commands/init/templates/code-templates.ts:64`, `config/src/config-types.ts:45`, `postgres/src/config/define-config.ts:28-46`, `emitter/src/artifact-paths.ts:19` | Confirmed | +| 18 | `contract infer` writes PSL | `cli/src/orm/contract/infer.ts:83-88` | Confirmed | +| 22 | `orm init` asks how you want to write your schema | `cli/src/orm/init-inputs.ts:98-102` | Confirmed | +| 24 | `.ts` extension selects TypeScript authoring | `postgres/src/config/define-config.ts:43-45` | Confirmed | +| 27, 38, 279 | `import { definePrismaConfig } from "prisma/config"` | `cli/src/commands/init/templates/code-templates.ts:304`, `skills/prisma-8/references/contract.md:47-55`, `examples/prisma-8-demo/prisma.config.contract-ts.ts` | **Corrected** → `from "@prisma/cli-engine"`. `prisma/config` appears nowhere in rc.10. | +| 50 | Builder import paths | `orm-postgres/package.json:81`, `orm-mongo/package.json:72` | Confirmed | +| 52 | Export named `contract` or default only | `cli/src/load-ts-contract.ts:255-267`, `sql-contract-ts/src/config-types.ts:105-113` (`CONTRACT.MODULE_EXPORT_MISSING`) | Confirmed | +| 54-93 | Whole PostgreSQL example, every helper and option | `examples/prisma-8-demo/prisma/contract.ts` (same shape), `contract-dsl.ts`, `postgres/src/core/authoring.ts` | Confirmed | +| 57 | `nativeType` / `codecId` / `@1` comment | `framework-components/src/shared/column-spec.ts:20-24` | Confirmed | +| 95-123 | MongoDB example (`collection`, `field.objectId/string/date().optional()`, `rel.hasMany("Post", {from,to})`, `User.ref("_id")`) | `mongo-family/2-authoring/contract-ts/src/contract-builder.ts:1006-1030, 1157-1164, 1362, 252-254` | Confirmed | +| 127 | PostgreSQL `defineContract(options, factory)` | `postgres/src/contract/define-contract.ts:81-103` | Confirmed (a single-object form also exists; see Q1) | +| 129 | Package also exports `field`, `model`, `rel` | `contract-dsl.ts:2102-2106` — the exported `field` is only `column`/`generated`/`namedType` | **Corrected** → now says the package exports `model` and `rel`, "plus a `field` that has only `column`, `generated`, and `namedType`". | +| 131 | MongoDB `defineContract` takes a single object | `mongo/src/contract/define-contract.ts:45-65` — a factory form exists too | **Corrected** → "also accepts a single object holding `models`, as above". | +| 133 | `_id`, no `.id()`, no `.attributes(...)` on MongoDB | `mongo .../contract-builder.ts:163-177, 222-255`; PSL requires `_id` at `mongo/contract-psl/src/interpreter.ts:1326-1335` | Confirmed (see Q2) | +| 134 | MongoDB scalar helpers | `mongo .../contract-builder.ts:1006-1030` | Confirmed | +| 135 | Collection and relations inline "rather than chained calls" | `composed-authoring-helpers.ts:113-129` — PostgreSQL `model()` also accepts inline `relations` | **Corrected** → "The collection name is an inline option on the model rather than a chained call, and relations are inline as well." | +| 136 | `{from,to}` vs `{by}`, string or `User.ref()`, `User.refs.id` | `mongo .../contract-builder.ts:372-375, 1144-1155`; `contract-dsl.ts:1456` | Confirmed | +| 137 | `indexes: [index({ email: 1 }, { unique: true })]`, `1` is ascending | `mongo .../contract-builder.ts:1062-1085`; `mongo-contract/src/contract-types.ts:5` | Confirmed | +| 139 | MongoDB `enums` and `extensions` in the same object | `mongo .../contract-builder.ts:899-910` | Confirmed | +| 147-159 | Field-helper table | `postgres/src/core/authoring.ts:678-770`, `2-sql/9-family/src/core/authoring-field-presets.ts:41-50` | Confirmed, except the UUID row | +| 158 | "UUID stored as text" | `authoring-field-presets.ts:24-49` — `sql/char@1`, `character(36)` | **Corrected** → "UUID stored in a `character(36)` column". | +| 161 | `field.column({codecId:"pg/date-temporal@1", nativeType:"date"})`; `field.column` vs `.column(name)` | `postgres/src/core/codec-ids.ts:26`, `temporal-codecs.ts:77-78`; `contract-dsl.ts:217, 505` | Confirmed | +| 163 | `field.id.*` list, generated in your app, `*Native` variants, no auto-increment helper, `defaultSql("gen_random_uuid()").id()` | `authoring-field-presets.ts:93-200`, `postgres/src/core/authoring.ts:770-800`; no auto-increment preset in either file | Confirmed | +| 165 | `createdAt` uses `now()`; `updatedAt` set in the client | `2-sql/9-family/src/core/timestamp-now-generator.ts:60-79` | Confirmed | +| 167-172 | `.optional()`, `.default()`, `.defaultSql("now()")`, `.unique()`, `.id()`, `.column()` | `contract-dsl.ts:183, 217, 313, 320, 327, 379` | Confirmed | +| 176-180 | `enumType(name, codec, ...members)`, `member()`, `.members.Low`, `enums` map | `contract/src/enum-type.ts:23-28, 153-233`; `postgres/src/contract/enum-type.ts` | Confirmed | +| 178 | `pg/int4@1` / `int4` pair | `postgres/src/core/authoring.ts:686-690` | Confirmed | +| 182-195 | `nativeEnum("Role","user","admin")`, `field.column(pg.enum(Role))`, type name = the name you give, `Role` not in `enums` | `postgres/src/contract/native-enum.ts:20-35, 88-130, 176` | Confirmed | +| 201-203 | `hasMany/hasOne({by})`, `belongsTo({from,to})`, `manyToMany({through,from,to})` | `contract-dsl.ts:2001-2100` | Confirmed | +| 211-219 | `belongsTo` alone makes no foreign key; `.sql({fk})`; the five action values | `contract-lowering.ts:299-336`; `contract-dsl.ts:843-853` | Confirmed (`fk` also accepts `constraint` and `index`, which the page does not list) | +| 221 | No column-type check across a relation | No such check in `build-contract.ts` / `contract-lowering.ts` (see Q3) | Confirmed | +| 223 | Model object vs string name; string typo caught at emit | `contract-dsl.ts:2008-2011`; `build-contract.ts:630` (`CONTRACT.MODEL_UNKNOWN`) | Confirmed | +| 229 | `.relations`, `.attributes`, `.sql` in any order | `contract-dsl.ts:1520-1610` (all three are methods on the same builder) | Confirmed | +| 231-243 | `{cols, constraints}`, `constraints.index([...])` always a list, `{unique:true}` | `contract-dsl.ts:1248-1254, 1065-1090, 816` | Confirmed | +| 245 | `constraints.foreignKey(cols.userId, User.refs.id)` | `contract-dsl.ts:1119-1150, 1456` | Confirmed | +| 247-255 | `.attributes(({fields,constraints}))`, only `id` and `uniques` | `contract-dsl.ts:1210-1213, 1228-1231` | Confirmed | +| 259-274 | `extensions: {pgvector}`, `type.pgvector.Vector(1536)`, pack name fixed by the pack, `Embedding1536` in `contract.d.ts` | `pgvector/src/core/authoring.ts:4-19`; `examples/prisma-8-demo/prisma/contract.ts:21-22` and `src/prisma/contract.d.ts:927` | Confirmed | +| 276-289 | `/pack` in the contract, `/control` in the config, `extensions` inside `ormConfig` | `orm-extension-pgvector/package.json:46-48`; `postgres/src/config/define-config.ts:21` | Confirmed | +| 295-298 | Purity rules; emit loads the file with a dynamic import and does not type-check it | `sql-contract-ts/src/config-types.ts:104` (plain `await import(...)`) | Confirmed | +| 304-306 | One contract file per config; no command converts PSL to TypeScript | `define-config.ts:16-17`; no such command under `cli/src/orm/` | Confirmed | +| 141 | "The rest of this page uses..." | page-conventions ban on docs commentary | **Corrected** → "The examples below use the PostgreSQL builder." | +| all | Every link target and anchor | Files under `apps/docs/content/docs`; `raw-queries.mdx:78`, `ai/tools/skills.mdx:100` | Confirmed | + +## capabilities.mdx (96 lines before, 96 after) + +| Line | Claim | Source | Verdict | +| --- | --- | --- | --- | +| 9 | Emit writes the section; you never edit it | `build-contract.ts:1577-1590` (authors can no longer declare capabilities) | Confirmed | +| 13 | Keys come from the database package and the extension packages; emit never connects | `cli/src/control-api/contract-enrichment.ts:62-84`; no connection code in `operations/contract-emit.ts` | Confirmed | +| 15-22 | `contract.json` excerpt keys | `adapters/postgres/src/core/adapter.ts:24-40`; `pgvector/src/core/descriptor-meta.ts:66-70` | Confirmed | +| 24 | Group named first; `sql` is shared | same files | Confirmed | +| 24 | "SQLite reports `false` for most of them" | `adapters/sqlite/src/core/adapter.ts:76-85` — 2 of 6 are `false` | **Corrected** → "for some of them". | +| 26-38 | `prisma.config.ts` with `extensions: [pgvector]`, `db.connection` | `postgres/src/config/define-config.ts:16-24` | Confirmed | +| 27 | `definePrismaConfig` import path | as above | **Corrected** → `@prisma/cli-engine` | +| 40 | pgvector ships a migration that installs the server extension; apply with `db migrate` | `pgvector/migrations/20260601T0000_install_vector_extension/migration.ts` | Confirmed | +| 44-50 | SQLite has no `scalarList`; the message, verbatim including the remedy line | `contract-psl/src/psl-field-resolution.ts:497-503` | Confirmed, exact match | +| 52 | `@prisma/orm-sqlite/config` exists | `orm-sqlite/package.json:63` | Confirmed | +| 54 | "when you call a method on the SQL query builder" | `sql-orm-client/src/collection-contract.ts:578-602` and `collection.ts:1063, 1350` — the ORM client raises it too | **Corrected** → "on the SQL query builder or on the ORM client". | +| 54-57 | `ORM.CAPABILITY_MISSING`, `distinctOn() requires capability postgres.distinctOn` | `sql-builder/src/runtime/builder-base.ts:278-292`; `collection-contract.ts:599-600` | Confirmed, exact match | +| 60 | Error at call time, not at await | `builder-base.ts:57-64`; `collection.ts:1063` | Confirmed | +| 60-66 | `db.contract.capabilities...`, `db.ts` written by `orm init` | `postgres/src/runtime/postgres.ts:63, 286`; `code-templates.ts:318-333` | Confirmed | +| 72-78 | Table rows | `adapters/postgres/src/core/adapter.ts:24-40`; `builder-base` gated methods `lateralJoin`/`distinctOn`; `assertReturningCapability`; `pgvector/src/core/descriptor-meta.ts:68` | Confirmed | +| 80 | "SQLite reports `false` for them"; "build-time check runs for every database"; "run-time check only on the SQL query builder" | `adapters/sqlite/src/core/adapter.ts:76-85` (returning is `true`, `scalarList` absent); no capability keys or checks anywhere under `2-mongo-family` or the mongo target | **Corrected** → "PostgreSQL reports `true` for all three `sql` keys above. SQLite reports `false` for `sql.lateral`, `true` for `sql.returning`, and has no `sql.scalarList` key at all. … The MongoDB packages report no keys, so neither check applies on MongoDB." | +| 84 | `db verify` checks tables and columns, not this section | `cli/src/control-api/operations/db-verify.ts:38-58` | Confirmed | +| all | Link targets and anchors | as above | Confirmed | + +## Q list (unverifiable, page left alone) + +- **Q1.** PostgreSQL `defineContract` also accepts a one-argument definition object (`postgres/src/contract/define-contract.ts:81-89`), so line 127's "takes an options object and then a function" describes only the form the page teaches. I left it, because it is true of that form, and corrected the MongoDB sentence instead. Searches: `grep -n "export function defineContract" -A 25` in both `define-contract.ts` files. +- **Q2.** No code in the TypeScript MongoDB builder requires an `_id` field. The PSL interpreter does (`interpreter.ts:1326-1335`). Searches: `grep -rn "_id" packages/2-mongo-family/2-authoring/contract-ts/src/`, `grep -rn "must declare an _id\|DOCUMENT_ID" packages --include="*.ts"`. +- **Q3.** Line 221's "Prisma ORM does not check that the column types on the two sides of a relation match" rests on absence. Searches: `grep -rn "RELATION_INVALID\|FOREIGN_KEY_INVALID" build-contract.ts contract-lowering.ts`, `grep -n "codecId\|mismatch" derived-checks.ts`. The only relation checks I found are nullability and missing `through`. + +## One thing the operator should decide beyond my two pages + +`prisma/config` is not an import path in rc.10. Every `prisma.config.ts` example in the section that imports `definePrismaConfig` from `prisma/config` is wrong the same way. I fixed my two pages; the other contract-authoring pages (and any CLI page with a config example) likely need the same change. \ No newline at end of file From 206c046c4a2d7d9026a184fa25b066750b53106b Mon Sep 17 00:00:00 2001 From: willbot Date: Mon, 14 Sep 2026 10:02:08 +0200 Subject: [PATCH 12/14] docs: remove the contract-authoring review reports before merge They stay in this branch's history at 40c5394e3. Co-Authored-By: Claude Fable 5.1 Signed-off-by: willbot Signed-off-by: Will Madden --- .../capabilities.round1.md | 71 --------- .../capabilities.round2.md | 112 -------------- .../capabilities.round3.md | 56 ------- .../capabilities.round4.md | 82 ---------- .../c21-contract-authoring/conventions.md | 24 --- .../factcheck1.psl-syntax.md | 114 -------------- ...ctcheck1.the-data-contract-and-artifact.md | 104 ------------- .../factcheck1.typescript-and-capabilities.md | 145 ------------------ .../factcheck2.psl-syntax.md | 81 ---------- ...ctcheck2.the-data-contract-and-artifact.md | 28 ---- .../factcheck2.typescript-and-capabilities.md | 81 ---------- .../page-conventions.md | 14 -- .../psl-syntax.round1.md | 95 ------------ .../psl-syntax.round2.md | 96 ------------ .../psl-syntax.round3.md | 98 ------------ .../psl-syntax.round4.md | 78 ---------- .../the-contract-artifact.round1.md | 95 ------------ .../the-contract-artifact.round2.md | 88 ----------- .../the-contract-artifact.round3.md | 74 --------- .../the-contract-artifact.round4.md | 77 ---------- .../the-data-contract.round1.md | 70 --------- .../the-data-contract.round2.md | 76 --------- .../the-data-contract.round3.md | 90 ----------- .../the-data-contract.round4.md | 94 ------------ .../typescript-schema-builder.round1.md | 91 ----------- .../typescript-schema-builder.round2.md | 83 ---------- .../typescript-schema-builder.round3.md | 100 ------------ .../typescript-schema-builder.round4.md | 98 ------------ 28 files changed, 2315 deletions(-) delete mode 100644 docs/orm-docs-audit/c21-contract-authoring/capabilities.round1.md delete mode 100644 docs/orm-docs-audit/c21-contract-authoring/capabilities.round2.md delete mode 100644 docs/orm-docs-audit/c21-contract-authoring/capabilities.round3.md delete mode 100644 docs/orm-docs-audit/c21-contract-authoring/capabilities.round4.md delete mode 100644 docs/orm-docs-audit/c21-contract-authoring/conventions.md delete mode 100644 docs/orm-docs-audit/c21-contract-authoring/factcheck1.psl-syntax.md delete mode 100644 docs/orm-docs-audit/c21-contract-authoring/factcheck1.the-data-contract-and-artifact.md delete mode 100644 docs/orm-docs-audit/c21-contract-authoring/factcheck1.typescript-and-capabilities.md delete mode 100644 docs/orm-docs-audit/c21-contract-authoring/factcheck2.psl-syntax.md delete mode 100644 docs/orm-docs-audit/c21-contract-authoring/factcheck2.the-data-contract-and-artifact.md delete mode 100644 docs/orm-docs-audit/c21-contract-authoring/factcheck2.typescript-and-capabilities.md delete mode 100644 docs/orm-docs-audit/c21-contract-authoring/page-conventions.md delete mode 100644 docs/orm-docs-audit/c21-contract-authoring/psl-syntax.round1.md delete mode 100644 docs/orm-docs-audit/c21-contract-authoring/psl-syntax.round2.md delete mode 100644 docs/orm-docs-audit/c21-contract-authoring/psl-syntax.round3.md delete mode 100644 docs/orm-docs-audit/c21-contract-authoring/psl-syntax.round4.md delete mode 100644 docs/orm-docs-audit/c21-contract-authoring/the-contract-artifact.round1.md delete mode 100644 docs/orm-docs-audit/c21-contract-authoring/the-contract-artifact.round2.md delete mode 100644 docs/orm-docs-audit/c21-contract-authoring/the-contract-artifact.round3.md delete mode 100644 docs/orm-docs-audit/c21-contract-authoring/the-contract-artifact.round4.md delete mode 100644 docs/orm-docs-audit/c21-contract-authoring/the-data-contract.round1.md delete mode 100644 docs/orm-docs-audit/c21-contract-authoring/the-data-contract.round2.md delete mode 100644 docs/orm-docs-audit/c21-contract-authoring/the-data-contract.round3.md delete mode 100644 docs/orm-docs-audit/c21-contract-authoring/the-data-contract.round4.md delete mode 100644 docs/orm-docs-audit/c21-contract-authoring/typescript-schema-builder.round1.md delete mode 100644 docs/orm-docs-audit/c21-contract-authoring/typescript-schema-builder.round2.md delete mode 100644 docs/orm-docs-audit/c21-contract-authoring/typescript-schema-builder.round3.md delete mode 100644 docs/orm-docs-audit/c21-contract-authoring/typescript-schema-builder.round4.md diff --git a/docs/orm-docs-audit/c21-contract-authoring/capabilities.round1.md b/docs/orm-docs-audit/c21-contract-authoring/capabilities.round1.md deleted file mode 100644 index cc1b5be6338..00000000000 --- a/docs/orm-docs-audit/c21-contract-authoring/capabilities.round1.md +++ /dev/null @@ -1,71 +0,0 @@ -Read once, as a reader coming from Prisma 6 who has never seen this version. - -## Sentences I could not restate after one reading - -1. "The `capabilities` section of your contract, the `contract.prisma` file that replaced `schema.prisma`, records which features yours supports." - Stopped me: the page then never shows `contract.prisma` again. Every example is `contract.json`. So is the `capabilities` section in the file I write, or the file the tool generates? Two paragraphs later the page says "You do not write this section yourself", which makes the `contract.prisma` mention look wrong. Plainer: "Prisma records the supported features in `contract.json`, a file the CLI generates." - -2. "…while it does so it merges the declarations of every component composed in your project: the target (PostgreSQL, SQLite, MongoDB), its adapter, and any extension packs named in the config." - Stopped me: four new nouns in one sentence — "declarations", "component", "composed", "target", "adapter", "extension packs" — plus "the config", which is not named until the following paragraph (`prisma.config.ts`). I cannot tell what a "component" is versus a "pack". Plainer: "Your database type, its driver, and any add-on packages each state which features they support. `prisma contract emit` collects all of those statements into `contract.json`." - -3. "Because the packs declare the keys, adding or removing an extension in `prisma.config.ts` and running `npx prisma contract emit` again changes the set with no further work." - Stopped me: "the set" of what — keys? packs? And "no further work" compared with what earlier workflow? I have no baseline. - -4. "Query-builder methods that need a key read the contract's `capabilities` section and throw an error whose `code` is `ORM.CAPABILITY_MISSING`, naming the method and the key." - Stopped me: one sentence carrying a mechanism, an error code, and the error's contents. Also "need a key" is odd phrasing — methods need a *feature*, not a key. - -5. "MongoDB currently declares no keys: this mechanism mostly separates SQL targets and their extensions, and the MongoDB pipeline does not yet check features this way." - Stopped me: "this mechanism mostly separates SQL targets and their extensions" — I cannot work out what "separates" means here. And "the MongoDB pipeline" is a piece of internal machinery I have never heard of. Plainer: "MongoDB declares no keys, so none of these checks apply to it." - -6. "[`db verify`](/cli/db-verify) compares the database against the contract's schema and profile, using the hashes described in [the contract artifact](…#the-content-hashes)." - Stopped me: "profile" appears here for the first and only time and is never explained. "the hashes" likewise. Plainer: "`db verify` checks the live database against your contract; capabilities are not part of that check." - -7. "Projects created with `npm create prisma@latest` include the [Prisma ORM skills](…) for your coding agent; in an existing project, run `npx prisma skills sync`." - Stopped me: three unexplained things (what a "skill" is, what it does, why my agent needs one) in a sentence appended to a page about database features. - -## Words and phrases I had to guess - -- **"contract"** — guessed: the new name for the schema file, or maybe the whole set of generated files. Never actually defined on this page. -- **"target"** — guessed: the database engine. The parenthetical "(PostgreSQL, SQLite, MongoDB)" is the only reason I got there. -- **"adapter"** — guessed: the driver package, like `@prisma/adapter-pg` in v6. -- **"extension packs"** / "packs" / "extensions" — guessed: optional add-on npm packages. The page uses all three names for what I assume is one thing. -- **"composed" / "composing the pgvector pack"** — guessed: listed in config so it gets included. -- **"declarations" / "declares" / "reports"** — guessed: all mean "states that it supports". Three verbs, one idea. -- **"namespace"** — guessed: the top-level grouping key, `sql` or `postgres`. -- **"scalar list"** — guessed: a `String[]` field. -- **"profile"** — could not guess at all. -- **"the MongoDB pipeline"** — could not guess. -- **"diagnostic"** — guessed: a build error message. Why not say "error"? -- **"key"** — guessed: the name of one capability. But the page's title says "features", the section says "capabilities", and the table column says "Key". Three words for one thing, and I had to work that out myself. - -## Where I asked "so what do I actually type?" and got nothing - -1. **I hit `ORM.CAPABILITY_MISSING`. Now what?** The page shows the error and stops. Do I change the adapter, install a pack, rewrite the query, or edit something? This is the single most likely reason a reader lands here and the page does not answer it. -2. **I hit the `scalarList` build error on SQLite.** Same problem — the error text is shown, the fix is not. Remove the field? Switch targets? -3. **Can my own code check a capability before calling a method?** Nothing is shown. I do not know whether there is an API, or whether I am meant to read `contract.json` myself. -4. **How do I add pgvector?** The page says composing the pack "adds `pgvector.cosine`" and tells my *agent* to "Add pgvector to the project", but never shows me a line of `prisma.config.ts`, an install command, or a config key. -5. **Where does `contract.json` live?** "beside the source" — beside which source? The code block is captioned `prisma/contract.json`, but I had to infer that. -6. **`contract.d.ts` is emitted too.** Never mentioned again. What do I do with it? -7. **How do I see my full key list?** "Your project's emitted `contract.json` is the full list" — so, open the file? There is no `prisma contract` subcommand shown for printing it. - -## Where the page explains the inside when I only wanted the outside - -- "the keys are checked at two points" plus "Query-builder methods that need a key read the contract's `capabilities` section" — I wanted to know what breaks and how to fix it, not where the lookup happens. -- "They are recorded in the contract when you run `npx prisma contract emit` rather than read from the live database, so the same contract behaves identically in every environment." The last clause is useful. The mechanism before it is not what I asked. -- "using the hashes described in [the contract artifact]" — pure internals in a paragraph whose point is "verification is a different thing". -- "this mechanism mostly separates SQL targets and their extensions" — architecture, not instruction. -- The whole `sql` versus `postgres` namespace explanation is background. What I needed was: how do I find the key a failing method wants? - -## Could I do what the page is for, after one reading? - -Partly. I understand that Prisma now records supported features and fails early. That is the page's stated purpose, and it lands. - -What I still could not do: - -- Fix either of the two errors the page shows me. It tells me what the error means and not one word about what to change. -- Add or remove an extension pack, despite the page claiming that is easy. I have never seen the config syntax. -- Explain what a "contract" is, or reconcile `contract.prisma` with `contract.json`. The one sentence defining the contract contradicts the section right after it. -- Say what "profile" means, or what `db verify` actually compares. -- Tell whether `capabilities` is something I ever edit. I believe not, but the opening sentence says it is "of your contract, the `contract.prisma` file", which reads like it is mine. - -Concrete gap, ranked: the page is a description of a mechanism, not a page a stuck reader can act on. Two error messages are quoted and neither has a remedy. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/capabilities.round2.md b/docs/orm-docs-audit/c21-contract-authoring/capabilities.round2.md deleted file mode 100644 index d91a259cebc..00000000000 --- a/docs/orm-docs-audit/c21-contract-authoring/capabilities.round2.md +++ /dev/null @@ -1,112 +0,0 @@ -I read the page once, top to bottom, as someone who used Prisma 6 for two years and has never seen this version. - -## Sentences I could not restate after one reading - -**1. Line 9 (the opening paragraph).** Six ideas in one block, and it names four things I have never heard of before it explains any of them: - -> "[`npx prisma contract emit`](/cli/contract-emit) writes `contract.json` and `contract.d.ts` beside your [contract](/orm/contract-authoring/the-data-contract) source file." - -What stopped me: "contract" — I do not know what that is. In the version I know, I write `schema.prisma` and run `prisma generate`. Is the contract the schema renamed? Is it something extra? The link tells me to go read another page, which means this page does not stand on its own. Plainest wording I would have understood: one sentence saying "A contract is the new name for your schema file" or "A contract is X, and it replaces Y." - -**2. Line 13.** - -> "Three things each say which features they support: your database (PostgreSQL, SQLite, or MongoDB), its driver package, and any extension packages listed in `prisma.config.ts`." - -What stopped me: "its driver package." I do not know what package that is or where I chose it. The config example below imports `@prisma/orm-postgres/config` — is that the driver package? The page never says. Plainest wording: "your driver package (for example `@prisma/orm-postgres`)". - -**3. Line 13, second half.** - -> "collects all of those answers into `contract.json`, grouped by prefix" - -What stopped me: "grouped by prefix" does not match what I then see. The JSON shows a key `"pgvector.cosine"` nested inside `"postgres"`. The table later calls the same thing `postgres.pgvector.cosine`. Nobody tells me that the written form of a key joins the group and the key with a dot. I had to work that out by comparing two code blocks. Plainest wording: "Keys are written group-first: the `cosine` key inside `postgres` is written `postgres.pgvector.cosine`." - -**4. Lines 49-51 (the error text).** - -> "target "sqlite" does not support scalar lists (the adapter does not report the "scalarList" capability)." - -Two words stopped me. "target" appears nowhere else on the page — everywhere else the page says "database." Is a target a thing I set somewhere? "adapter" also appears nowhere else. Is an adapter the driver package from line 13, or a third thing? I now have three possible names for what might be one thing: driver package, adapter, target. - -**5. Line 63.** - -> "The error is raised when your code builds the query, so a test that builds the query catches it." - -What stopped me: I do not know what "builds the query" means as code I write. In the version I know, `db.user.findMany()` returns a promise and nothing happens until I await it. Does "builds" mean the call itself throws before I await? Does it mean there is a separate build step? The sentence tells me a test catches it but not what the test looks like. - -**6. Line 85.** - -> "[`db verify`](/cli/db-verify) compares the database against your contract, using the recorded hashes described in [contract.json and contract.d.ts](/orm/contract-authoring/the-contract-artifact#the-content-hashes)." - -What stopped me: "recorded hashes." I did not ask how the comparison is implemented, and this clause does not change anything I would do. Cut it. - -**7. Line 81.** - -> "MongoDB has no keys at all, so none of these checks apply to it." - -This contradicts the rule the page just taught me. The rule was: a missing key means the feature fails early. If MongoDB has no keys, then by that rule every feature should fail on MongoDB. Instead the page says the checks are skipped. I cannot tell from one reading what actually happens if I call `lateralJoin()` on MongoDB. - -## Words and phrases I had to guess at - -- **"contract"** — guessed: the new name for `schema.prisma`. Not confirmed anywhere on this page. -- **"vector distance operations"** (line 9) — guessed: similarity search for embeddings. Probably right, but it is the third item in a list where the first two are plain SQL, so it reads like I should already know it. -- **"driver package"** (line 13) — guessed: `@prisma/orm-postgres`. -- **"adapter"** (line 50) — guessed: the same thing as the driver package. -- **"target"** (line 49) — guessed: the database engine, set somewhere in the contract file. -- **`/control`** in `import pgvector from "@prisma/orm-extension-pgvector/control"` (line 34) — guessed: a required subpath for config-time imports. I would have typed the bare package name and got it wrong. -- **`defineConfig as ormConfig`** (line 35) — guessed: a rename to avoid clashing with the outer `definePrismaConfig`. The rename is never mentioned, so it looks like `ormConfig` is a real exported name. -- **`db`** (line 65) — guessed: my instantiated client. Never introduced on this page. -- **`?.`** in `db.contract.capabilities.sql?.lateral` — guessed: the whole `sql` group can be absent, not just the key. The page never says a group may be missing entirely. - -## "So what do I actually type?" - -**1. Line 54.** - -> "remove the list field, or point the contract at a database that supports list fields, such as PostgreSQL." - -How do I point the contract at a different database? Which file, which line, which field? The page shows me `contract: "./src/prisma/contract.prisma"` in the config but never shows the place where the database is chosen. This is the one action the page asks me to take and it does not show the edit. - -**2. Line 30.** - -> "then list it in `prisma.config.ts`" - -The example below shows a complete config with `extensions: [pgvector]`. But my existing config almost certainly does not have an `extensions` key yet, and I have to reverse-engineer the diff from a full file. Show the two added lines, not a whole file. - -**3. Line 63.** - -> "Rewrite the query without it" - -Rewrite it as what? For `lateralJoin()` and `distinctOn()` there is presumably a fallback shape. The page does not show one, so the advice is "do something else" with no second option. - -**4. Line 63.** - -> "a test that builds the query catches it" - -No test is shown. I would want three lines of code. - -**5. Line 79.** - -> "Open your project's `contract.json` to see every key your setup supports." - -No path given here. It is on line 15 in a code block title (`src/prisma/contract.json`), thirty lines earlier, and line 9 says "beside your contract source file," which is a different answer. Say the path once, here. - -## Places the page explains internals when I wanted instructions - -- **Line 44:** "Prisma ORM checks the keys twice, both times before any SQL reaches the database." The "twice" framing is about the tool's internal sequencing. What I need is: here are two errors you may see, here is what each means. The page does then give exactly that, so the framing sentence is the only part that is about the machine rather than about me. -- **Line 85, the whole section.** "The keys say what your installed software supports. They are recorded when you run `npx prisma contract emit`, not read from the live database, so the same contract behaves the same way in every environment." This is an explanation of a design decision. The only part I can act on is: if you want to check the real database, run `db verify`. The "recorded hashes" clause is pure internals. -- **Line 30:** "Keys under `sql` are shared across SQL databases. Keys under `postgres` belong to PostgreSQL only." This is taxonomy, not action. It is short enough to survive, but it is the answer to "how is the file organised," which I did not ask. - -## Could I do what the page is for, after one reading? - -No, because I cannot tell what the page is for. - -Line 9 tells me "you never edit it." So the page is not a task. It appears to be a reference for two error messages. If that is the purpose, it is buried: the errors are in the middle, under a heading called "What the `capabilities` section controls," which does not say "errors." - -What I would still not know after one reading: - -1. **What a contract is.** Everything on this page hangs off that word and the page does not define it. I would have to leave and read another page before this one makes sense. -2. **How to change the database my contract targets** — the fix the page recommends on line 54. -3. **Whether "driver package," "adapter," and "target" are one thing or three.** -4. **What happens on MongoDB.** The page says checks do not apply, but the stated rule says they should all fail. -5. **What a query "building" is in code**, and therefore what the test on line 63 looks like. -6. **Where to find the full key list** for a database I am considering but have not installed. The page says to open my own `contract.json`, which only tells me about the setup I already have. If I am deciding whether to switch from SQLite to PostgreSQL, that does not help. - -One thing that did work on first reading: the two error blocks on lines 48-52 and 58-61 are literal text I can paste into a search box, and the sentence "That message means the method is not available on the database you are connected to" is a plain restatement I understood immediately. That sentence is the model for how the rest of the page should read. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/capabilities.round3.md b/docs/orm-docs-audit/c21-contract-authoring/capabilities.round3.md deleted file mode 100644 index 7a8c1a7cd8e..00000000000 --- a/docs/orm-docs-audit/c21-contract-authoring/capabilities.round3.md +++ /dev/null @@ -1,56 +0,0 @@ -Read once, top to bottom, as a Prisma 6 user who has never seen this version. - -## Sentences I could not restate after one reading - -1. Line 9 (the whole opening paragraph). It carries six separate ideas in one block: contracts replaced `schema.prisma`; databases differ; `contract emit` writes two files; there is a `capabilities` section; you must not edit it; errors name a "key". By the third sentence I had lost the thread. Plainest version: split it. "Your contract file `contract.prisma` replaces `schema.prisma`. Running `npx prisma contract emit` produces `contract.json`. One part of that file, `capabilities`, lists the database features you can use. Prisma writes it; you never edit it." - -2. Line 13: "Three things each say which features they support: your database, your database package, and any extension packages listed in `prisma.config.ts`." I read this as "emit connects to my database and asks it". Line 84 then says the opposite: "not read from the live database". I still do not know which is true. If the database's contribution is really "which database you named in config", say that. - -3. Line 62: "The call throws as soon as you make it, so a test that calls it catches it." I cannot restate this. Throws as soon as you make it *instead of when?* I assume you mean it throws at the call and not when you await the query, but the page never says what the alternative was, so the sentence has no content for me. - -4. Line 78: "PostgreSQL has all three `sql` keys above, and SQLite has none of them." Line 30 told me "Keys under `sql` are shared across SQL databases." SQLite is a SQL database. So either the group name does not mean what line 30 said, or this sentence is wrong. Plainest version: "The `sql` group holds keys that more than one SQL database may support. It does not mean every SQL database supports them." - -5. Line 80: "The methods that need these keys exist only on the SQL query builder, and MongoDB does not have one, so nothing on MongoDB reads the `capabilities` section." This contradicts line 45, which says `contract emit` itself fails on a missing capability. Does emit skip that check on MongoDB? I cannot tell. - -6. Line 84, first sentence: "The keys say what your installed software supports." "Installed software" is a new term for the same three things named in line 13. Third name for one concept. - -## Words and phrases I had to guess - -- **"your setup"** (lines 3, 9, 13, 78). Guessed: database + database package + extensions. Never defined, and it is the subject of the page's main claim. -- **"key"** (line 9, before any example). Guessed: a JSON property name. Confirmed only at line 15. -- **"target"** and **"adapter"** (error text, line 48). The page tells me both mean the database package, which is the fourth and fifth name for the same thing. I guessed right only because line 53 told me. -- **"query-builder method"** (line 55) and **"the SQL query builder"** (line 80). Guessed: some new API that is not `prisma.user.findMany()`. In Prisma 6 there was no query builder. The page assumes I know what this is and never links to it until the very end. -- **"/control"** in `@prisma/orm-extension-pgvector/control` (line 33). Guessed: a subpath export for config-time code. No idea why it is not the package root. -- **"built-in extension packages"** (line 68). Guessed: first-party packages that you nonetheless have to install. The phrase reads as self-contradictory. -- **"Checking the live database is a separate job"** (line 84). Guessed: "a different command does that". -- **"db"** (line 64). Guessed: the replacement for `PrismaClient`. - -## "So what do I actually type?" - -- **Line 13, "you choose it by the import in `prisma.config.ts`".** I am never shown `prisma.config.ts`. Not once, in full. The only two glimpses are a four-line fragment (lines 35–38) that starts mid-object with `orm: ormConfig({`, and a bare import path at line 53. I do not know what encloses `orm:`, so I do not know where to paste the pgvector lines. -- **Line 53, "switch to a database that supports list fields... change the import".** Changing one import does not move me from SQLite to PostgreSQL. I need a connection string, a running Postgres, and my data moved. The page implies a one-line fix. I would have tried it and been stuck. -- **Line 30, installing pgvector.** `npm install`, add two lines, re-emit. Nothing about the database side. pgvector is a PostgreSQL server extension. Do I need it installed in Postgres? Does `contract emit` fail or silently omit the key if it is not? This is the exact "so what do I actually type" gap that would cost me an afternoon. -- **Line 64, reading capabilities in my own code.** One expression, `db.contract.capabilities.sql?.lateral`, and no surrounding code. I wanted a small working example: check the key, run the fast query, otherwise fall back. Also, `?.` returns `undefined` when the group is missing, not `false`. The page says it "is `true` when lateral joins are available" and stops there, so I would have written `=== false` and got it wrong. -- **Line 55, the runtime error.** I am told `distinctOn()` throws. I am never shown how to call `distinctOn()` or where it lives. I cannot reproduce, and I cannot fix by example. -- **Line 88, the agent prompt** "Which keys does the `capabilities` section of our contract require". The `capabilities` section does not require keys; my code requires keys that the section may or may not list. The prompt as written asks a question that does not parse. - -## Places that explain internals when I wanted instructions - -- **The whole "Where the `capabilities` section comes from" section (lines 11–39).** The first half explains how the tool assembles a file I was just told I must never edit. As a reader my questions were "how do I know if I have a key" and "how do I get one I lack". The provenance story answers neither. The only actionable content is the pgvector install, which is buried at the end of a paragraph about key-naming. -- **Line 84, "so the same contract behaves the same way in every environment".** This is a design rationale. It does not tell me to do anything, and it arrives in the same paragraph as `db verify` and an extensions link, so three unrelated things share one paragraph. -- **Line 30, "A key is written group first."** A naming rule presented before I have any reason to write a key myself. I do not write keys; I read them in errors. - -## Could I do what the page is for, after one reading? - -Partly. If one of the two errors in the page landed in my terminal verbatim, I could act: remove the list field, or install the extension and re-emit. That is a real win and it is the page's strongest part. - -What I still would not know: - -- Whether `contract emit` talks to my live database. The page says both. -- What the SQL query builder is, or how to call `distinctOn()` / `lateralJoin()` at all. I cannot use, or avoid, a feature I do not know how to invoke. -- What a complete `prisma.config.ts` looks like, so I cannot apply the pgvector diff with confidence. -- Whether adding a pgvector key needs anything done inside PostgreSQL itself. -- What `db verify` actually checks, or whether it would catch a capability problem. The page says it is "a separate job" and links away. -- Whether any of this applies to MongoDB. Lines 45 and 80 disagree. - -One more thing, blunt: the same concept is named five ways across this page — "your setup", "your database package", "target", "adapter", "installed software". Two of those come from error messages you do not control, but the prose adds three more on top. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/capabilities.round4.md b/docs/orm-docs-audit/c21-contract-authoring/capabilities.round4.md deleted file mode 100644 index 7f36911fa59..00000000000 --- a/docs/orm-docs-audit/c21-contract-authoring/capabilities.round4.md +++ /dev/null @@ -1,82 +0,0 @@ -Read once, top to bottom, as someone coming from Prisma 6. - -## Sentences I could not restate after one reading - -**Line 9** — "A feature that is not listed fails right away, with an error that names its key, instead of reaching the database and failing there." -A feature doesn't fail; my code does. And "right away" — right away *when*? At build? At startup? At the call? I only learned the answer two sections later. Plainer: "If you use a feature that is not listed, Prisma ORM raises an error instead of sending the query to the database." - -**Line 24** — "A key is written group first, so `pgvector.cosine` under `postgres` is `postgres.pgvector.cosine`." -Written *where*? In the JSON it is not written that way — the JSON shows `"pgvector.cosine"` nested inside `"postgres"`. I eventually guessed you mean "in error messages and in this page's table". Plainer: "Error messages name a key group-first." - -**Line 44** — "If your contract uses a feature your database cannot store, `npx prisma contract emit` fails." -This contradicts line 13, which says emit "never connects to your database." So it is not my database deciding, it is my database *package*. I stopped and re-read both lines. - -**Line 60** — "That message means the method is not available on the database you are connected to." -Same contradiction, sharper. Nothing so far involved a connection. The capability list came from a package on disk. - -**Line 60** — "The error arrives when you call the method, not when you `await` the query, so a test that calls it catches it." -Three ideas in one sentence, and the "so" does not follow on first reading. I think you mean: "You do not have to run the query to get the error, so a test that only builds the query still fails." - -**Line 60** — "A value such as `db.contract.capabilities.sql?.lateral` is `true`, `false`, or `undefined` when the whole group is absent." -The grammar breaks. It reads as if all three cases happen when the group is absent. Plainer: "…is `true` or `false`, or `undefined` if the whole group is missing." - -**Line 60** — "Your own code can read the same list through `db`, the client you create once in `src/prisma/db.ts`." -`db` and that file arrive as things I am assumed to already have. I have never made one. - -**Line 80** — "The check that runs when the contract is built applies on every database, MongoDB included. The keys that methods check at query time exist only on the SQL query builder, which MongoDB does not have." -"The keys that methods check at query time" is hard to parse — keys don't check, methods check keys. Plainer: "The build-time check runs for every database. The run-time check only exists on the SQL query builder, and MongoDB has no SQL query builder." - -**Line 84** — "The keys say what your packages support, recorded when you run `npx prisma contract emit`, so the same contract behaves the same way in every environment." -The "so" hides the reason (the list is frozen at emit time and never asks the live database). I had to reconstruct it. - -**Line 84** — the whole paragraph. The heading promises "The `capabilities` section and `prisma db verify`", then one sentence mentions `db verify`, then it changes subject to extension packages. It never answers the question the heading raised: does `db verify` tell me my real database is missing something the capabilities claim? - -## Words and phrases I had to guess - -- **"contract emit"** — guessed: a codegen step like `prisma generate` was in v6. -- **"database package"** — guessed: the v8 replacement for `provider = "postgresql"`. -- **"extension packages"** — guessed: optional npm packages that add features. -- **`@prisma/orm-extension-pgvector/control`** — guessed: the subpath you import for config registration. `/control` is never explained. -- **"target" and "adapter"** — the page tells me both mean the database package. So one thing has three names and I have to carry all three. -- **"group"** — guessed: the top-level object in `capabilities`. -- **"scalar list"** — guessed from `tags String[]` in the same sentence. Lucky. -- **`sql.lateral` / `lateralJoin()`** — guessed: SQL `LATERAL` joins. The table entry "Lateral joins (`lateralJoin()`)" defines the term with the term. -- **"SQL query builder"** — new in v8 as far as I know; guessed it is a raw-ish query API. -- **"the first migration the package ships"** — guessed: installing the npm package puts a migration file in my project. -- **`db.contract`** — guessed the emitted contract is readable at run time off the client. -- **`db verify`** — guessed: compares live schema to contract. -- **"skills sync", "the `prisma-8` skill"** — guessed: files for a coding agent. - -## "So what do I actually type?" — unanswered - -1. **`src/prisma/db.ts`.** Line 60 tells me to read capabilities "through `db`", and never shows me how `db` comes to exist or what to import. The one code sample assumes it. -2. **Reading a three-segment key.** `capabilities.sql?.lateral` is shown. What do I write for `postgres.pgvector.cosine`? `capabilities.postgres?.["pgvector.cosine"]`? The dot inside the key name makes this a real question and the page created it in line 24 and then dropped it. -3. **The pgvector migration.** "the first migration the package ships installs it in PostgreSQL for you" — installs it *when*? Do I run `prisma migrate dev`? Something else? The install instructions stop at `npx prisma contract emit`. -4. **Switching to PostgreSQL.** "change the import and the connection string in `prisma.config.ts`, then set up the new database." There are three imports in the sample above; I don't know which one. "Set up the new database" is an entire unnamed job. -5. **When must I re-run `contract emit`?** The page says "run it again" twice, both times inside a specific recipe. It never states the general rule. -6. **The key in the error vs the key in the table.** The error says `the "scalarList" capability`; the table says `sql.scalarList`. I had to assume those are the same thing. - -## Places that explain the machinery when I wanted instructions - -- Line 13: "`npx prisma contract emit` never connects to your database. It reads those packages and writes what they report." That is how it works. What I needed was: what do I do when a key is missing. -- Line 24: the whole "written group first" naming rule — internal naming mechanics, given before I have any reason to care. -- Line 52: "In that message, 'target' and 'adapter' both mean your database package." You are explaining the tool's inconsistent internal vocabulary to me rather than the tool using one word. -- Line 60: "The error arrives when you call the method, not when you `await` the query." Implementation timing. -- Line 84: "recorded when you run `npx prisma contract emit`, so the same contract behaves the same way in every environment." Design rationale for a decision I cannot change. - -## Could I do what the page is for, after one reading? - -Partly. - -I could handle both error messages it shows me. If I saw `distinctOn() requires capability postgres.distinctOn`, I would know to rewrite the query or install a package. If emit failed on `tags String[]` under SQLite, I would know to drop the field. That is the page's core job and it does it. - -What I would still not know: - -- How to write the capability check in my own code, because I do not have `db` and the page did not tell me where it comes from. -- How to check a nested key like `postgres.pgvector.cosine` in TypeScript. -- Whether adding pgvector actually worked — the migration step is asserted, not instructed. -- Whether the list can go stale, and what re-syncs it. -- What `db verify` has to do with any of this, despite a section heading promising to tell me. -- Whether `capabilities` itself can be absent (the sample uses `?.` on `sql` but not on `capabilities`, and I don't know if that is deliberate). - -One thing outside the reader's view: the heading anchors are `#what-capabilities-gate` and `#where-capabilities-come-from`. The first uses "gate", which the heading text itself avoided. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/conventions.md b/docs/orm-docs-audit/c21-contract-authoring/conventions.md deleted file mode 100644 index 75c20b9c746..00000000000 --- a/docs/orm-docs-audit/c21-contract-authoring/conventions.md +++ /dev/null @@ -1,24 +0,0 @@ -# Shared conventions for the C21 plain-language pass (carried from the fundamentals and reference sections) - -Verified facts (rc.10 source at wip/prisma-src/, commit cfccb09be2). Use these words on every page; do not restate them differently. - -- "contract": on first use on a page, say: "your contract, the `contract.prisma` file that replaced `schema.prisma`". After that, "contract" alone. -- `db`: "`db` is the client you create once in `src/prisma/db.ts`; `prisma orm init` writes that file." Examples import it as `import { db } from "./prisma/db"` from a file directly inside `src/`. -- `db.orm.public.User`: "`db.orm` holds your models by model name; `public` is the PostgreSQL schema (the namespace your tables live in, `public` unless you set one)." On MongoDB there is no schema segment and the accessor is the collection name: `db.orm.users`. -- `db.sql.public.user`: the SQL query builder, keyed by table name. The table name is the model name with a lowercase first letter unless the model sets `@@map`. Column names are the field names unless `@map`. Say this once where `db.sql` first appears. -- `db.raw.sql`: raw SQL as a template string. `db.runtime()` is the connection that runs a built query: `db.runtime().query(plan)` for rows, `db.runtime().execute(plan)` for a count. On PostgreSQL `db.runtime()` is synchronous; on MongoDB it must be awaited: `(await db.runtime()).query(plan)`. State this where `db.runtime()` first appears on a page. -- "plan": avoid the word. Say "the built query" or show the code. Every raw or builder query ends with `.build()` and is then passed to `query()` or `execute()`. -- `.all()`, `.first()`, `.create(...)`: "you `await` the whole chain; the last call says what you want back". Do not use "terminal". -- `.first()` returns `null` when nothing matches. `.all().firstOrThrow()` throws an error with code `RUNTIME.NO_ROWS`. -- `emit`: "`prisma contract emit`, which you run after every change to the contract". Never a bare "emit". -- Scaffold command: `npm create prisma@latest -- my-app` (never `npx create-prisma@latest` or the bare package name). Bun: `bun create prisma@latest my-app`. -- Skills note at the bottom of pages: keep, but say "Projects created with `npm create prisma@latest` include the Prisma ORM skills for your coding agent". -- `conflictOn: { email: "..." }` takes a unique column with its value; if a row with that value exists, `update` is applied to it. Leave it out to use the primary key. -- Nested writes that work: `create`, `connect`, `disconnect` (`posts: (p) => p.create([...])`). `connectOrCreate`, nested update/upsert/delete, and `set` do not exist; link /orm/coming-from-prisma-orm-7#not-available-yet. -- Transaction options (`isolationLevel`, `timeout`, `maxWait`) and nested transactions do not exist. Prisma ORM 7's interactive `$transaction(async (tx) => ...)` maps directly to `db.transaction(async (tx) => ...)`. -- `create-prisma` mentions of "skills": "Prisma ORM skills, instruction files for coding agents". -- Type names in raw SQL (`pg/int4@1`): "the PostgreSQL type plus a version, always `@1` today"; prefer taking the type from the table: `db.sql.public.user.columns.id`. There is no published list; the raw-queries page shows the ids a bare JavaScript value gets. -- Errors: "an error with code `X`" (it is `error.code`). -- Do not use: terminal, codec, envelope, facade, lane, junction model/table (say "join table" / "model for the join table"), surface, plan, "rides", "fan out", "drop one level", "field proxy" (say "the `p` argument"), "lambda" (say "callback"). -- `npx prisma skills sync` exists in the published CLI (8.0.0-rc.13, newer than the rc.9 source tree): "in an existing project, run `npx prisma skills sync`". Do not drop it because the rc.9 tree lacks it. -- The serverless client (`postgresServerless(...)` from `@prisma/orm-postgres/serverless`) has no `db.orm`, `runtime()`, or `transaction()`; it runs SQL builder and raw queries only, reading rows 100 at a time. Never show `db.orm` on it. diff --git a/docs/orm-docs-audit/c21-contract-authoring/factcheck1.psl-syntax.md b/docs/orm-docs-audit/c21-contract-authoring/factcheck1.psl-syntax.md deleted file mode 100644 index c6cd83011ce..00000000000 --- a/docs/orm-docs-audit/c21-contract-authoring/factcheck1.psl-syntax.md +++ /dev/null @@ -1,114 +0,0 @@ -Done. Report below. - -## 1. Banned-term and wording replacements - -| Line | Old | New | -| --- | --- | --- | -| 9 | "author the Prisma ORM [data contract]"; "usually `prisma/contract.prisma`"; "[`contract emit`] turns it into" | "author [your contract], the `contract.prisma` file that replaced `schema.prisma`"; "usually `src/prisma/contract.prisma`"; "[`prisma contract emit`] writes `contract.json` and `contract.d.ts` beside it" | -| 11 | "This page covers the shared basics briefly and the Prisma ORM 8 additions in detail: …" | "The Prisma ORM 8 additions are …" (docs-about-docs commentary cut) | -| 102 | "Run `npx prisma@latest contract emit` … to refresh the artifacts." | "Run `npx prisma contract emit` … to refresh the two files." | -| 106 | "names the source of truth" | "names the one file Prisma ORM reads" | -| 130 | "Scaffolded projects … already contain this wiring and a starter schema." | "Projects created with `npm create prisma@latest -- my-app` already have this config and a starter contract file." | -| 136–137 | semicolons joining clauses | split into separate sentences | -| 139 | "sets a field's physical name" | "sets a field's column name in the database" (and split the semicolon) | -| 153 | "an ordinary column; pick its type" | "an ordinary column. Pick its type" | -| 165 | "map it to the physical `_id` key" | "map it to `_id`" | -| 181 | "written in type position like this" | "written as the type" | -| 185 | "declares its storage codec with `@@type`" | "declares how its values are stored with `@@type`" | -| 200 | "a structured value that belongs to its parent record and has no identity or table of its own" | "a structured value stored inside its parent row, with no table of its own" (convention wording) | -| 216 | one sentence interleaving PostgreSQL and MongoDB; semicolon | separate sentences per database | -| 220 | semicolon joining clauses | split | -| 233 | "an explicit join model" | "an explicit model for the join table" | -| 256 | "The emitted contract records" | "`contract.json` records" | -| 285 | "constructor expressions"; "Compose the pack in the config" | "constructor calls"; "Add the pack to the config" | -| 311 | "fails the emit with a diagnostic" | "fails with an error whose `code` is `CONFIG.MISSING_EXTENSION_PACKS`" | -| 321 | "Projects scaffolded with `create-prisma@latest` install …" | convention sentence: "Projects created with `npm create prisma@latest` include the [Prisma ORM skills] …; in an existing project, run `npx prisma skills sync`." Also "covers this page" → "covers PSL authoring". | -| 329 | "Emit and inspect [the artifacts] the schema produces." | "Run `npx prisma contract emit` and inspect [the two files it writes]." | - -No heading was renamed, so no anchors were pinned. One misplaced paragraph (the "For which shape to choose…" sentence, previously stranded under "Starting from an existing database") was moved to the end of the Relations section. - -`check-plain.sh` prints **clean**. The extra page-convention words are gone (the only remaining "artifact" is inside a URL). - -## 2. Claim-by-claim check - -All line numbers are for the edited file. Source paths are relative to `wip/prisma-src/` (prisma/orm at `cfccb09be2`). - -| Line | Claim | Deciding source | Verdict | -| --- | --- | --- | --- | -| 9 | Contract file is `contract.prisma`; default location | `packages/1-framework/1-core/config/src/config-types.ts:45` (`DEFAULT_CONTRACT_SOURCE_DIR = 'src/prisma'`), `cli/src/commands/init/templates/code-templates.ts:63-68` | Corrected → `src/prisma/contract.prisma` (also lines 15, 59, 114, 125, 296, 302) | -| 9 | `contract emit` writes `contract.json` and `contract.d.ts` | `packages/1-framework/3-tooling/emitter/src/generate-contract-dts.ts`, `packages/3-extensions/postgres/src/config/define-config.ts:28-41` (output derived beside the source) | Confirmed | -| 16, 60 | `// use prisma-8` header line | `packages/1-framework/3-tooling/language-server/src/schema-directive.ts:1`; scaffold `code-templates.ts:165,197` | Confirmed | -| 18-20 | `types { ShortName = VarChar(35) }` | `contract-psl/test/interpreter.types.test.ts:33-70` (`Slug = VarChar(191)` → `sql/varchar@1`) | Confirmed | -| 22-27, 62-67 | `type Address { … }` value-object block | `contract-psl/test/interpreter.value-objects.test.ts:46-70`; mongo `2-mongo-family/2-authoring/contract-psl/test/interpreter.attribute-specs.test.ts:140` | Confirmed | -| 29-34 | `enum Priority { @@type("pg/text@1") Low = "low" … }` | `contract-psl/test/interpreter.enum.test.ts:125-150` (same schema verbatim) | Confirmed | -| 37, 47 | `Uuid @id @default(uuid())` | `contract-psl/test/interpreter.defaults.test.ts:152-200`; `3-targets/6-adapters/postgres/src/core/control-mutation-defaults.ts:276` | Confirmed | -| 38, 48 | `String` scalar | `control-mutation-defaults.ts:164` | Confirmed | -| 39, 51 | `DateTime @default(now())` | `control-mutation-defaults.ts:188`; `interpreter.defaults.test.ts:136-142` (storage default `now()`) | Confirmed | -| 40, 82 | `address Address?` (optional value object) | `interpreter.value-objects.test.ts:112-155` | Confirmed | -| 41, 83 | `posts Post[]` navigation list | `contract-psl/README.md` ("Relation navigation lists are supported") | Confirmed | -| 43, 55, 85, 98 | `@@map("user")` / `@@map("posts")` | `contract-psl/README.md`; `interpreter.polymorphism.test.ts:161-190` | Confirmed | -| 48 | `title ShortName` (alias in field position) | `interpreter.types.test.ts:40-60` | Confirmed | -| 50 | `priority Priority @default(Low)` | `interpreter.enum.test.ts:863-885` (`@default(Low)` → stored `"low"`) | Confirmed | -| 53, 95 | `@relation(fields: [...], references: [...])` | `interpreter.relations.test.ts`; mongo `interpreter.test.ts:355-385` | Confirmed | -| 70 | `@@type("mongo/string@1")` | `3-mongo-target/2-mongo-adapter/src/core/codec-ids.ts:2` | Confirmed | -| 77, 91 | `ObjectId @id @map("_id")` | mongo `interpreter.test.ts:146,358`; `2-mongo-family/2-authoring/contract-psl/src/interpreter.ts:1320-1331` | Confirmed | -| 93 | `DateTime` on MongoDB | mongo `interpreter.test.ts:45` (`mongo/date@1`) | Confirmed | -| 97 | `@@index([authorId])` on MongoDB | mongo `interpreter.attribute-specs.test.ts:169`, `interpreter.polymorphism.test.ts:476` | Confirmed | -| 102 | Re-run emit after every change | `page-conventions.md` + emit pipeline | Confirmed | -| 106 | A `.prisma` extension selects PSL authoring | `3-extensions/postgres/src/config/define-config.ts:42-50` (`.ts` → TypeScript, otherwise PSL) | Confirmed | -| 110 | `defineConfig as ormConfig` from `@prisma/orm-postgres/config` | `packages/9-public/@prisma/orm-postgres/package.json:69`; `postgres/src/config/define-config.ts:35` | Confirmed | -| 121 | `@prisma/orm-mongo/config` | `packages/9-public/@prisma/orm-mongo/package.json:60`; `mongo/src/config/define-config.ts` | Confirmed | -| 109, 120, 290 | `definePrismaConfig` from `"prisma/config"` | see Q1 | **Q** | -| 112-116 | `definePrismaConfig({ orm: ormConfig({ contract }) })` envelope | `code-templates.ts:302-313`; `skills/prisma-8/references/contract.md:47` | Confirmed | -| 130 | Scaffolded projects already have the config and a starter contract | `code-templates.ts:69-78` (`starterSchema`), `:299-313` (`configFile`) | Confirmed | -| 136 | `@id` / `@@id([a, b])` | `contract-psl/README.md`; `interpreter.relations.many-to-many.test.ts:52` | Confirmed | -| 137 | `@unique` / `@@index([...])` | `contract-psl/README.md` (`@@index` parameter surface); `interpreter.relations.one-to-one.test.ts:37` | Confirmed | -| 138 | `@default(now())` becomes a column default; `@default(uuid())` is applied by Prisma ORM before each write and appears in `execution` | `interpreter.defaults.test.ts:79-148`; `contract-psl/README.md` ("Execution defaults: `uuid()` …", "This is application-side") | Corrected (dropped the unsupported clause "so they work the same on every database"; "rather than as DDL" → "rather than as a column default") | -| 139 | `@map` sets the column name, `@@map` the table or collection name | `contract-psl/README.md` responsibilities list | Confirmed | -| 147-153 | PostgreSQL ID tab: `id Uuid @id @default(uuid())`, ordinary column | `interpreter.defaults.test.ts:152-200` | Confirmed | -| 159-165 | MongoDB ID tab: `id ObjectId @id @map("_id")`, is `_id` | `2-mongo-family/.../interpreter.ts:1318-1332` | Confirmed | -| 175-179 | `types` block declares reusable aliases | `interpreter.types.test.ts:33-80` | Confirmed | -| 181 | Alias binds `varchar(35)` rather than `text`; `VarChar(35)`, `Uuid`, `Timestamptz` usable directly | `control-mutation-defaults.ts:205-276` (`postgresNativeAuthoringTypes`) | Confirmed | -| 185 | `@@type` declares storage; member values optional | `interpreter.enum.test.ts:624-800` | Corrected → added "Leave `@@type` out and Prisma ORM infers it from the member values", since rc.10 infers the codec from bare, string, or integer members (`interpreter.enum.test.ts:624,650,676`) | -| 196 | `pg/text@1` stores values as `text` | `control-mutation-defaults.ts:164-167`; `interpreter.enum.test.ts:125` | Confirmed (added the convention gloss "the PostgreSQL type plus a version, always `@1` today") | -| 196 | A bare member stores the member name | `interpreter.enum.test.ts:624-647` (`values: ['Admin', 'User']`) | Confirmed | -| 196 | Allowed values enforced with a `CHECK` constraint on each column | `interpreter.enum.test.ts:803-825` (`"priority" IN (1, 10)`); `3-targets/3-targets/postgres/src/core/migrations/operations/constraints.ts:157` | Confirmed | -| 200 | `type` block = value object, no table of its own | `interpreter.value-objects.test.ts:46-78` | Confirmed | -| 216 | PostgreSQL: single `jsonb` column | `interpreter.value-objects.test.ts:112-165` (`pg/jsonb@1`) | Confirmed | -| 216 | MongoDB: embedded document | `2-mongo-family/2-authoring/contract-psl/src/derive-json-schema.ts:56-87` (`bsonType: 'object'`) | Confirmed | -| 216 | `contract.d.ts` types it as a structured object | `emitter/src/domain-type-generation.ts:607-640`; `generate-contract-dts.ts:100-110` | Confirmed | -| 220-231 | Relation shape: FK side declares the scalar field, other side the list | `contract-psl/README.md`; `interpreter.relations.test.ts` | Confirmed | -| 233 | Many-to-many needs an explicit join-table model whose primary key covers both foreign keys | `interpreter.relations.many-to-many.test.ts:35-60` and `:602-636` (`PSL_JUNCTION_ID_NOT_FK_COVERING`) | Confirmed (wording made precise: "Its primary key must cover both foreign keys") | -| 244-253 | `@@id([postId, tagId])` on the join model | same test, line 52 | Confirmed | -| 256 | Relation recorded as `N:M` with the join table's columns | `interpreter.relations.many-to-many.test.ts:63,77` (`cardinality: 'N:M'`, `through: {...}`) | Confirmed | -| 260-279 | `@@discriminator(type)` on the base, `@@base(Task, "bug")` on the variant | `interpreter.polymorphism.test.ts:68-130` | Confirmed | -| 281 | PostgreSQL: variant with its own `@@map` gets its own table sharing the base primary key | `interpreter.polymorphism.test.ts:161-232` (variant table has the base PK column, `primaryKey: ['id']`, FK to the base) | Confirmed | -| 281 | PostgreSQL: without `@@map`, variant fields are nullable columns in the base table | `interpreter.polymorphism.test.ts:131-160, 404-455` | Confirmed | -| 281 | MongoDB: variants stay in the base collection, so no `@@map` of their own | `2-mongo-family/.../interpreter.ts:460-483` (`PSL_MONGO_VARIANT_SEPARATE_COLLECTION`) | Confirmed | -| 285 | Extension packs contribute types via constructor calls in `types {}` | `contract-psl/README.md`; `psl-parser/test/parse-leaf.test.ts:432` | Confirmed | -| 289 | `import pgvector from "@prisma/orm-extension-pgvector/control"` | `packages/9-public/@prisma/orm-extension-pgvector/package.json` exports `"./control"`; `skills/prisma-8/SKILL.md:36` maps `@internal/` → `@prisma/orm-` | Confirmed | -| 295 | `extensions: [pgvector]` | `postgres/src/config/define-config.ts:21,36` | Confirmed | -| 302-303 | `Embedding1536 = pgvector.Vector(1536)` | `3-extensions/pgvector/src/core/authoring.ts:4-19`; `pgvector/src/contract.ts:34` | Confirmed | -| 313 | A type from an unlisted pack fails emit | `1-framework/1-core/errors/src/control.ts:350-370` | Corrected → now names the code `CONFIG.MISSING_EXTENSION_PACKS` | -| 317 | `contract infer` reads the live schema and writes a starter `contract.prisma` | `cli/src/orm/contract/paths.ts:4-24`; `cli/src/orm/contract/infer.ts:87-89` | Confirmed | -| 321 | Skills note | `page-conventions.md`; `skills/prisma-8/SKILL.md` | Corrected to the mandated sentence | -| 324 | "composite unique constraint on userId and title" is expressible | `interpreter.relations.one-to-one.test.ts:37` (`@@unique([a, b])`) | Confirmed | -| 329-331 | Next-steps link targets exist | `apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx`, `typescript-schema-builder.mdx`, `cli/db-init.mdx`, `cli/migration-plan.mdx`, `cli/contract-emit.mdx`, `cli/contract-infer.mdx` all present | Confirmed | -| 216, 281 | Anchors `#embed-or-reference`, `#polymorphic-relations`, `#available-skills-for-prisma-8` | headings in `orm/data-modeling/mongodb.mdx:28`, `relational-databases.mdx:166`, `ai/tools/skills.mdx:100` | Confirmed | - -## 3. Q list - -**Q1 — `import { definePrismaConfig } from "prisma/config"` (lines 109, 120, 290).** Every occurrence in the rc.10 tree imports it from `@prisma/cli-engine`, never from `prisma/config`: `cli/src/commands/init/templates/code-templates.ts:304`, `templates/quick-reference-postgres.md:43`, `quick-reference-mongo.md:45`, `skills/prisma-8/references/contract.md:47,52`, `docs/releases/v8.0.0-rc.4.md:25`. The tree contains no package named `prisma`, so it cannot confirm or rule out a `prisma/config` re-export in the published CLI. I left the page alone because this is a section-wide decision, not a page-level one: `prisma/config` appears **116 times** across `apps/docs/content/docs/orm/` and only 7 times as a target-specific config import. Changing one page would make the docs inconsistent. The operator should decide for the whole section. - -Searches run: `grep -rn "definePrismaConfig" packages skills docs`; `grep -rn '"name": "prisma"' packages --include=package.json`; `grep -rn "cli-engine" packages --include='*.json' -l`; `grep -rn "prisma/config" packages --include='*.ts'` (no hits); `grep -rho 'from "[^"]*config"' apps/docs/content/docs/orm/ | sort | uniq -c`. - -No other claims were left unchecked. - -## 4. Line count - -`apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx`: **331 before, 331 after.** - -## Notes for the operator - -- The sibling contract-authoring page `the-data-contract.mdx` still uses `prisma/contract.prisma` (lines 13 and 61). It is another agent's page, so I did not touch it, but it needs the same `src/prisma/` correction. -- Two short PSL snippets on the page (the Relations and many-to-many examples) omit the `id` fields their `references: [id]` clauses point at. They are deliberate excerpts, consistent with the page's style, so I left them. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/factcheck1.the-data-contract-and-artifact.md b/docs/orm-docs-audit/c21-contract-authoring/factcheck1.the-data-contract-and-artifact.md deleted file mode 100644 index 73a2a51e8d0..00000000000 --- a/docs/orm-docs-audit/c21-contract-authoring/factcheck1.the-data-contract-and-artifact.md +++ /dev/null @@ -1,104 +0,0 @@ -Both pages are edited and the plain-language checker prints clean on each. - -## 1. Banned-term and wording replacements - -**the-data-contract.mdx** - -| Line | Old | New | -| --- | --- | --- | -| 3, 6 | front matter: "single description", "typed, planned, and verified", "the same artifacts" | plain rewording, no "artifacts" | -| 9 | "That description is the data contract." | "…the data contract, the `contract.prisma` file that replaced `schema.prisma`." (section convention, first use) | -| 33 | "compiles this into a machine-readable artifact" | "`prisma contract emit` turns this source into two machine-readable files" | -| 45 | "Prisma ORM's current architecture" | "Prisma ORM 7" (now reads as a contrast) | -| 47 | "a canonical JSON description of models, storage, and capabilities"; "Both are deterministic … byte-identical output"; "the artifacts can be diffed" | "describes your models, how they are stored, and which database features the contract needs"; "The same source always produces the same two files, so you can read them in a code review…" | -| 49 | "records them in a small marker inside the database" | the section's mandated marker wording: "records which contract the database matches in a small table it creates in the database" | -| 68–72 | "Emitting turns the source into the artifacts"; `npx prisma@latest contract emit` | "Run `prisma contract emit` after every change… It writes `contract.json` and `contract.d.ts` beside the source"; `npx prisma contract emit` | -| 77 | "The runtime receives `contract.json`" | "`db` reads `contract.json`" ("runtime" as a noun is banned) | -| 80 | heading "## Two authoring modes, one artifact" | "## Two authoring modes, one \`contract.json\` [#two-authoring-modes-one-artifact]" (old anchor pinned) | -| 82 | "the preferred way … purpose-built for describing data"; "what `create-prisma` scaffolds"; "what the examples throughout these docs use" | "the usual way … a compact language for describing data"; "what `npm create prisma@latest` scaffolds"; docs-commentary clause cut | -| 86 | "emit the same `contract.json`"; "verification"; "exactly one source of truth" | "write the same `contract.json`"; "database checks"; "exactly one contract file, the one named by `contract` in the config" | -| 94–98 | "value objects"; "types and capabilities contributed by extension packs"; "committing … the emitted artifacts" | "`type` blocks, each a structured value stored inside its parent row with no table of its own"; "the types and database features that extension packs add"; "committing the source, `contract.json`, and `contract.d.ts`" | -| 102 | "Projects scaffolded with `create-prisma@latest` install…" | the mandated sentence, including `npx prisma skills sync` | -| 105 | "and emit it" | "and run prisma contract emit" | - -**the-contract-artifact.mdx** - -| Line | Old | New | -| --- | --- | --- | -| 3, 6 | "deterministic artifacts", "deterministic emission" | plain rewording | -| 9 | "This page explains what is in each, why they are deterministic…" | deleted (docs commentary) | -| 13 | "canonical, machine-readable contract … capabilities"; "The runtime, … verification" | "machine-readable contract: … which database features they need"; "`db`, the migration tooling, the database checks" | -| 18 | heading "## Deterministic emission" | "## The same source always produces the same two files [#deterministic-emission]" (old anchor pinned) | -| 20–22 | "Emission is deterministic"; "canonical order"; "the contract source must stay pure" | "Run `prisma contract emit` … byte-identical files"; "a fixed key order"; "must not read the environment, the clock, or random values" | -| 34 | "records the hashes in a marker inside the database"; "caught by verification rather than by a failing query" | mandated marker wording, plus the corrected behaviour (see below) | -| 105 | "the model it names is what `db.orm.public.User` hangs off" | "the model name is what `db.orm.public.User` uses" | -| 106–107 | "a `codecId` … which names the codec that encodes and decodes values"; "native type plus codec" | "its type id, such as `pg/text@1`, the PostgreSQL type plus a version, always `@1` today"; "a native type and a type id" | -| 109 | "Query APIs consult these before using gated features" | "The query APIs check these before building a query that needs one" | -| 129 | "type-incompatible with another version's artifacts"; "before verification has to" | "will not type-check against another version's files"; "before `prisma db verify` has to" | -| 151 | "without re-running emission"; "running the emit" | "without running `prisma contract emit` first"; "running the command" | -| 155, 158 | scaffold/skills sentence; "Re-emit the contract … in the artifact" | mandated sentence; "Run prisma contract emit and show me what changed in contract.json" | -| 162 | "how the contract's capabilities gate database features" | "which database features the contract needs, and how Prisma ORM checks that your database has them" | - -Two lines carry `{/* plain-language:defined */}`: the pinned anchor `[#deterministic-emission]` and the Next-steps link whose **URL path** is `/orm/contract-authoring/capabilities`. Both trip the checker on a code-like string, not on prose jargon. I removed a second link to that URL rather than add a third marker. - -## 2. Claims checked - -| Line | Claim | Deciding source | Verdict | -| --- | --- | --- | --- | -| dc 31 | `// use prisma-8` is the first line; marks a Prisma 8 contract | `packages/1-framework/3-tooling/language-server/src/schema-directive.ts:1`; `CHANGELOG.md:33` | Confirmed | -| dc 31 | language server only diagnoses/completes/formats files carrying it | `document-diagnostics.ts:27`; `server.ts:384`; completion needs `project.artifacts.document(uri)` (`server.ts:452`) | Confirmed | -| dc 31 | "excludes unmarked files from its schema composition" | `schema-inputs.ts` — the input set comes only from `contract.source.inputs` in the config; no composition exclusion in rc.10 code | **Corrected**: clause deleted (it was an rc.9 release-note line) | -| dc 31 | completion covers field/model/block attribute names and argument keys from target and extensions | `completion-provider.ts:41-47`; `server.ts:464-479` | Confirmed | -| dc 31 | pre-rc.10 contracts use `// use prisma-next`; still accepted, replaced on format | `docs/releases/v8.0.0-rc.10.md:25`; `schema-directive.ts:9`; `server.ts:395` | Confirmed | -| dc 33 | "the database is verified against it before your code runs" | `packages/2-sql/5-runtime/src/sql-runtime.ts:897-928` — mismatch only logs `CONTRACT.MARKER_MISMATCH` | **Corrected**: now "`prisma db verify` checks a live database against it" | -| dc 35 | `package-lock.json` comparison | wording only | Confirmed (unchanged) | -| dc 45 | Prisma ORM 7 compiles the schema into generated client code | contrast claim about v7, not in this tree | **Q1** | -| dc 47 | same source produces the same two files | `packages/1-framework/0-foundation/contract/src/canonicalization.ts:179-209`, `emit.ts:31-48` | Confirmed | -| dc 49 | `db sign` records hashes in a table it creates | `packages/2-sql/9-family/src/core/control-instance.ts:825-895`; table is `prisma_contract.marker` (`3-targets/6-adapters/postgres/src/core/control-adapter.ts:98`) | Confirmed | -| dc 49 | client checks before its first query; **fails on mismatch** | `sql-runtime.ts:368-371, 897-928`; `docs/releases/v0.12.0.md:87` ("no longer throws … emits one warn-level log line") | **Corrected**: "logs a warning … To fail instead of warn … run `prisma db verify`" | -| dc 56 | `import { definePrismaConfig } from "prisma/config"` | `cli/src/commands/init/templates/code-templates.ts:304` | **Corrected**: `from "@prisma/cli-engine"` | -| dc 57 | `defineConfig as ormConfig` from `@prisma/orm-postgres/config` | same file + every `examples/*/prisma.config.ts` | Confirmed | -| dc 66 | file extension selects PSL vs TypeScript mode | `packages/3-extensions/postgres/src/config/define-config.ts:43-52` | Confirmed | -| dc 71 | `npx prisma@latest contract emit` | page conventions (existing project) | **Corrected**: `npx prisma contract emit` | -| dc 74 | emit writes both files beside the source | `cli/src/orm/contract/emit.ts:55,84`; `define-config.ts:25-33` | Confirmed | -| dc 76–78 | who reads which file | `emit.ts`, `sql-runtime.ts`, `cli/src/orm/db/verify.ts:81-155` | Confirmed, wording corrected on the middle bullet | -| dc 82 | PSL is the usual mode; `contract infer` writes PSL | `cli/src/orm/contract/infer.ts:132-201`; `templates/code-templates.ts:67` (`src/prisma/contract.prisma`) | Confirmed | -| dc 82 | `npm create prisma@latest` scaffolds PSL | `create-prisma` is not in this tree | **Q2** | -| dc 86 | identical `contract.json` for an equivalent schema | `contract-psl/test/ts-psl-parity.test.ts:342-870`; `psl-ts-namespace-parity.test.ts` | Confirmed | -| dc 92–96 | what the contract contains | real `contract.json` at `examples/prisma-8-demo/src/prisma/contract.json` | Confirmed | -| ar 11–14 | the table | `emit.ts`, `cli/src/orm/contract/emit.ts:73-85` | Confirmed after rewording | -| ar 16 | generated-file notice in both files | `emitter/src/emit.ts:39-44`; `examples/prisma-8-demo/src/prisma/contract.d.ts:1-3` | Confirmed | -| ar 20 | fixed key order | `canonicalization.ts:66 TOP_LEVEL_ORDER`, `:179-209 sortObjectKeys` | Confirmed | -| ar 30 | `storageHash` covers "models, fields, relations, and the full storage layout" | `contract/src/hashing.ts:72-83` hashes only `storage`; `build-contract.ts:1540-1547` | **Corrected**: now "the storage layout: tables, columns, primary keys, uniques, indexes, foreign keys, and the value sets behind enums" | -| ar 31 | `executionHash` covers pre-write defaults | `hashing.ts:85-92`; `build-contract.ts:1596-1602`; real `execution.mutations.defaults` | Confirmed | -| ar 32 | `profileHash` covers the target database and its family | `hashing.ts:94-101` plus `build-contract.ts:1591-1595`, which passes `capabilities: {}` in rc.10 | Confirmed | -| ar 34 | `db sign` checks then records; `db verify` and the client compare | `control-instance.ts:825-895`; `cli/src/orm/db/verify.ts:81-155` (marker + schema, exit code 4) | Confirmed | -| ar 34 | "caught by verification rather than by a failing query" | `sql-runtime.ts:897-928` | **Corrected**: `db verify` fails; `db` warns and carries on | -| ar 38 | domain/storage split, MongoDB collections, namespace = PostgreSQL schema | `examples/prisma-8-demo/src/prisma/contract.json`; `examples/mongo-demo/src/contract.json` (`entries.collection`) | Confirmed, sentence split | -| ar 44–99 | the abridged `contract.json` (section names, `roots`, `domain` field shape, `storage`, `execution`, `capabilities`, `extensions`) | `examples/prisma-8-demo/src/prisma/contract.json`; `contract-ts/src/data-contract-json-schema.ts` | Confirmed; **Corrected** key order inside objects to the emitted alphabetical order (`codecId` before `kind`, `model` before `namespace`, `namespaces` before `storageHash`, etc.) | -| ar 105 | `roots` shape and what it keys | `build-contract.ts:1421-1437` (keyed by bare storage table name) | Confirmed, reworded | -| ar 109 | capabilities "merged from the target, adapter, and extension packs" | `build-contract.ts:1577-1590`; values declared in `3-targets/6-adapters/postgres/src/core/descriptor-meta.ts:172-194` | **Corrected**: "collected from the adapter for your database and any extension packs" | -| ar 109 | query APIs check capabilities before use | `2-sql/4-lanes/sql-builder/src/runtime/builder-base.ts:62, 278-290` (`ORM.CAPABILITY_MISSING`) | Confirmed | -| ar 116–126 | `contract.d.ts` excerpt: `StorageHashBase`, `ProfileHashBase`, `CodecTypes[...]["output"]`, `AddressOutput` | `examples/prisma-8-demo/src/prisma/contract.d.ts:41-46, 264-269`; `emitter/src/generate-contract-dts.ts` | Confirmed, verbatim match | -| ar 134–143 | `src/prisma/db.ts` example | `examples/prisma-8-demo/src/prisma/db.ts:1-11`; `templates/code-templates.ts:318-331` | Confirmed | -| ar 131 | `prisma orm init` writes `src/prisma/db.ts` (added) | `cli/src/orm/init-scaffold.ts:188`; `1-core/config/src/config-types.ts:45` | Confirmed | -| ar 151 | version-control advice | no rows/credentials in the emitted files; `--output-path` flag exists | Confirmed | -| ar 160–163 | Next-steps CLI links | `apps/docs/content/docs/cli/` contains `contract-emit`, `contract-infer`, `db-sign`, `db-verify`, `db-init`, `migration-plan` | Confirmed | - -No test covers the runtime warning path directly that I found; the behaviour above comes from `sql-runtime.ts` and the v0.12.0 release note. - -## 3. Q list - -- **Q1** (dc 45, "Prisma ORM 7 compiles your schema into generated client code"): nothing about Prisma ORM 7 exists in the rc.10 tree. Searches: `grep -rn "prisma-client-js\|generated client" wip/prisma-src/docs wip/prisma-src/skills`, `grep -rn "Prisma ORM 7" wip/prisma-src`. Left as written. -- **Q2** (dc 82, "`npm create prisma@latest` scaffolds PSL"): `create-prisma` is not in this checkout. Searches: `find wip/prisma-src -type d -name 'create-prisma*'`, `grep -rn "create-prisma" wip/prisma-src/packages`. I used the wording `conventions.md` mandates. The related fact that `prisma orm init` defaults to `src/prisma/contract.prisma` is confirmed. - -## 4. Line counts - -| Page | Before | After | -| --- | --- | --- | -| the-data-contract.mdx | 143 | 143 | -| the-contract-artifact.mdx | 165 | 163 | - -## One thing for you to decide - -The artifact page's front-matter `title` is still **"The emitted artifacts"**, which `page-conventions.md` bans as a phrase. I left it because three pages I must not touch link to it by that exact text (`orm/core-concepts.mdx:61`, `psl-syntax.mdx:329`, `typescript-schema-builder.mdx:281`) and `meta.json` orders the section by slug. Renaming it needs a coordinated pass across those pages. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/factcheck1.typescript-and-capabilities.md b/docs/orm-docs-audit/c21-contract-authoring/factcheck1.typescript-and-capabilities.md deleted file mode 100644 index d0ae7b667be..00000000000 --- a/docs/orm-docs-audit/c21-contract-authoring/factcheck1.typescript-and-capabilities.md +++ /dev/null @@ -1,145 +0,0 @@ -Both pages are done. Here is the report. - -## 1. Banned-term and wording replacements - -**typescript-schema-builder.mdx** - -| Line | Old | New | -| --- | --- | --- | -| 3, 6 | "Same models, same artifacts" / "Learn how to define…" | "Same models, same emitted files" / "How to define…" | -| 9 | "you define the Prisma ORM data contract in code… produces exactly the same `contract.json` and `contract.d.ts`" | first-use convention: "your contract, the `contract.ts` file that replaced `schema.prisma`"; "`npx prisma contract emit` then writes `contract.json` and `contract.d.ts` beside the source" | -| 13 | "Both modes emit identical artifacts… The TypeScript builder is an **escape hatch**" | "Both forms produce the same two files… Use the TypeScript builder for the cases PSL does not cover" | -| 16 | "(the **purity rules** still apply)" parenthetical caveat | cut | -| 22 | "names the **source of truth**" | "names the one file Prisma ORM reads" | -| 157 | "refresh the **artifacts**" | "refresh the two files" | -| 163 | — | "takes two arguments" → "takes an options object, and optionally a factory function after it" (both overloads exist) | -| 183–184 | semicolons joining clauses | split into sentences | -| 185 | "sets the **physical** column name" | "sets the column name in the database" | -| 189 | "with an explicit storage **codec**" | "an enum, the type its values are stored as, and its members" | -| 256 | "the CLI and **runtime** agree" + mid-sentence parenthetical | "Add the same pack to `prisma.config.ts` as `extensions: [pgvector]`, importing the pack's `/control` export. The CLI and your app then compose the same set the contract was built with." | -| 258 | heading "Keep the contract file **pure**" | "## Keep the contract file free of changing values `[#keep-the-contract-file-pure]`" — anchor pinned; `the-contract-artifact.mdx:22` links to it and still resolves | -| 260 | "Prisma ORM **canonicalizes** it to JSON… the file is **pure** data" | "writes it to JSON in a fixed key order and hashes it, so the same source always produces the same files… only if the file holds plain data" | -| 264 | "**Emission** evaluates it" | "`prisma contract emit` imports the file and reads its `default` or `contract` export" | -| 270 | "the same **canonical artifact**… exactly one **source of truth**" | "the same `contract.json` and `contract.d.ts`… A project names exactly one contract file in its config" | -| 274 | "Projects scaffolded with `create-prisma@latest` install…" | the required sentence, including `npx prisma skills sync` | -| 281 | "**Emit** and inspect the **artifacts**" | "Inspect the two files" | - -**capabilities.mdx** - -Prose use of "capability/capabilities" replaced throughout with "keys", "the `capabilities` section", "which database features your setup supports". Other replacements: - -| Line | Old | New | -| --- | --- | --- | -| 11 | "instead of **surfacing** as a database error mid-query" | "instead of reaching the database and failing there"; first-use "contract" convention added | -| 15 | "You do not write capabilities yourself. When `contract emit` runs…" | "You do not write this section yourself. `npx prisma contract emit` writes `contract.json` and `contract.d.ts` beside the source, and while it does so it merges…" | -| 43 | heading "What capabilities **gate**" | "## What capabilities control `[#what-capabilities-gate]`" — anchor pinned | -| 64 | "to give a sense of the granularity" (docs commentary) | cut | -| 67 | table column "Gates" | "What it covers" | -| 77 | "is the **authoritative** list" | "is the full list" | -| 79 | "does not yet **gate** features this way" | "does not yet check features this way" | -| 90 | scaffold sentence | required wording | - -The four headings containing "Capabilities", plus `title:`, `url:` and `metaTitle:`, were left alone as instructed. - -## 2. Claims checked - -**typescript-schema-builder.mdx** - -| Line | Claim | Deciding source | Verdict | -| --- | --- | --- | --- | -| 9 | emit writes `contract.json` + `.d.ts` beside the source | `contract-ts/src/config-types.ts:90-125` (`defaultOutputFromContractPath`) | Confirmed | -| 13 | PSL is the preferred form | `skills/prisma-8/references/contract.md:6,46` (secondary prose) | Confirmed | -| 15–16 | TS is for composed/programmatic definitions | same, line 46 | Confirmed | -| 18 | "what `create-prisma` scaffolds and `contract infer` writes" | `cli/src/orm/init-inputs.ts:98-103`; `cli/src/orm/contract/paths.ts:5` | **Corrected** — `prisma orm init` prompts for PSL or TypeScript and writes either, so the scaffold half was dropped. Now: "It is more compact, and it is what `contract infer` writes." | -| 22 | `.ts` extension selects TypeScript authoring | `3-extensions/postgres/src/config/define-config.ts:44-49` | Confirmed | -| 25, 36 | `import { definePrismaConfig } from "prisma/config"` | `cli/src/commands/init/templates/code-templates.ts:304`; `docs/releases/v8.0.0-rc.4.md:25` | **Corrected** — it is `@prisma/cli-engine`. No `prisma/config` specifier exists anywhere in the tree. | -| 26, 37 | `defineConfig as ormConfig` from `@prisma/orm-postgres/config` / `@prisma/orm-mongo/config` | `9-public/@prisma/orm-postgres/package.json` exports, `…/orm-mongo/package.json` | Confirmed | -| 48 | builders at `/contract-builder` on both packages | both package.json exports maps | Confirmed | -| 51 | `@prisma/orm-extension-pgvector/pack` | `…/orm-extension-pgvector/package.json` exports | Confirmed | -| 52 | `defineContract, enumType, member, rel` exported | `3-extensions/postgres/src/exports/contract-builder.ts` | Confirmed | -| 54, 194 | `{ codecId: "pg/text@1", nativeType: "text" }` | `3-targets/3-targets/postgres/src/core/authoring.ts:677` | Confirmed | -| 56–62, 192 | `enumType(name, codec, member(...))` | `1-framework/2-authoring/contract/src/enum-type.ts:153-183` | Confirmed | -| 64–67 | `defineContract(options, factory)` with `extensions` | `3-extensions/postgres/src/contract/define-contract.ts:89-102` | Confirmed | -| 68 | factory receives `{ field, model, type }` | `contract-ts/src/composed-authoring-helpers.ts:133-152` | Confirmed (`rel` also present) | -| 70, 249 | `type.pgvector.Vector(1536)` | `3-extensions/pgvector/src/core/authoring.ts:4-19` | Confirmed | -| 75, 85 | `field.id.uuidv4String()` | `2-sql/9-family/src/core/authoring-field-presets.ts:99` | Confirmed | -| 76, 86 | `field.text()` | `postgres/src/core/authoring.ts:678` | Confirmed | -| 77–78 | `field.temporal.createdAt()` / `.updatedAt()` | `2-sql/9-family/src/core/timestamp-now-generator.ts:61,69` | Confirmed | -| 79 | `field.json().optional()` | `authoring.ts:719`; `contract-dsl.ts:183` | Confirmed | -| 87 | `field.uuidString()` | `authoring-field-presets.ts:42` | Confirmed | -| 88 | `field.namedType(Priority).default(Priority.members.Low)` | `contract-dsl.ts:2105,313`; `enum-type.ts:219,233` (`members` maps name → stored value) | Confirmed | -| 100, 208 | `rel.hasMany(Post, { by })` | `contract-dsl.ts:2001-2021` | Confirmed | -| 105, 212 | `rel.belongsTo(User, { from, to })` | `contract-dsl.ts:1948-1999` | Confirmed | -| 101, 223 | `.sql({ table })` | `contract-dsl.ts:1574` | Confirmed | -| 106, 229 | `.sql(({ cols, constraints }) => …)` | `contract-dsl.ts:1624-1632`, `SqlContext` at 1246-1253 | Confirmed | -| 109, 232 | `constraints.foreignKey(cols.x, Model.refs.y, { name })` | `contract-dsl.ts:1121-1155` | Confirmed | -| 95–115 | returned `{ enums, types, models }` | `define-contract.ts:96-101` | Confirmed | -| 121–154 | whole MongoDB example | `cli/src/commands/init/templates/code-templates.ts:258-292` writes the same shapes; `field.objectId/string/date` at `2-mongo-family/…/contract-builder.ts:1008-1025`; `collection` at 1362; `rel.hasMany(target,{from,to})` at 1277-1296; `rel.belongsTo` at 1299-1322; `User.ref("_id")` at 1443; `defineContract({models})` at `3-extensions/mongo/src/contract/define-contract.ts` overload 1 | Confirmed | -| 157 | `npx prisma@latest contract emit` | page-conventions | **Corrected** to `npx prisma contract emit` | -| 159 | Mongo scalars `int32`, `double`, `bool` | `2-mongo-family/…/contract-builder.ts:1014-1025` | Confirmed | -| 163 | "takes two arguments" | `define-contract.ts` two overloads | **Corrected** — "takes an options object, and optionally a factory function after it" | -| 165 | target and family bound by the import | `define-contract.ts:113-121` (`buildBoundContract`) | Confirmed | -| 167 | helpers "composed from the target and every extension pack" | `composed-authoring-helpers.ts:270-277` composes family + target + extensions | **Corrected** — "composed from your database and every extension pack"; `rel` added to the list | -| 169 | Mongo `defineContract` "takes a single definition object" | `mongo/src/contract/define-contract.ts` also has a factory overload | **Corrected** — "accepts a single definition object holding the `models`" | -| 175–178 | field helper bullets | as above | Confirmed | -| 180 | helper set comes from target + packs | `composed-authoring-helpers.ts:270` | **Corrected** — "from your database and the extension packs you compose" (`uuidString` comes from the SQL family, not the target) | -| 182–185 | `.optional()`, `.default()`, `.defaultSql()`, `.unique()`, `.id()`, `.column()` | `contract-dsl.ts:183, 313, 320, 379, 327, 217` | Confirmed | -| 200 | enum must be in the returned `enums` map | `build-contract.ts:1463` registers only `definition.enums` | Confirmed | -| 216 | `rel.hasOne` and `rel.manyToMany` | `contract-dsl.ts:2023, 2045` | Confirmed | -| 216 | "a typo is a compile error either way" | `contract-dsl.ts:1948-2012` string overloads accept any string; `contract-lowering.ts:344` reports "references unknown model" at build time; `contract-warnings.ts:127-131` warns on string fallbacks | **Corrected** — now: model object gives a compile error, a string is caught when `prisma contract emit` builds the contract | -| 237 | `Model.refs` typed against the model | `contract-dsl.ts:1456, 1478, 1279-1302` | Confirmed | -| 256 | pack must also be in `prisma.config.ts` via `/control` | `postgres/src/config/define-config.ts:22` (`ControlExtensionDescriptor`); pgvector exports `./control` | Confirmed | -| 260 | fixed key order + hashing | `1-framework/0-foundation/contract/src/hashing.ts:1-60` (`canonicalizeContract` + sha256) | Confirmed | -| 264 | emit evaluates the file and nothing else | `contract-ts/src/config-types.ts:105-113` (dynamic import, reads `default ?? contract`) | Confirmed | -| 270 | PSL and TS produce the same output | `contract-psl/test/ts-psl-parity.test.ts`, `psl-ts-namespace-parity.test.ts:141` | Confirmed | -| 281–282 | links `/cli/db-init`, `/cli/migration-plan`, artifact page | pages exist in the repo | Confirmed | - -**capabilities.mdx** - -| Line | Claim | Deciding source | Verdict | -| --- | --- | --- | --- | -| 11 | error names the missing key before the database is reached | `sql-builder/src/runtime/builder-base.ts:278-292` | Confirmed | -| 15 | "you do not write them yourself" | `cli/src/control-api/operations/contract-emit.ts:252-268`; `contract-enrichment.ts:66` | Confirmed | -| 15 | merged from "target, its adapter **and driver**, and extension packs" | `contract-emit.ts:252` merges `[target, adapter, ...extensions]`; `control-stack.ts:662` the same | **Corrected** — "the target …, its adapter, and any extension packs named in the config" | -| 20–28 | `postgres.*` keys and their values | `3-targets/6-adapters/postgres/src/core/descriptor-meta.ts:179-186` | Confirmed | -| 26 | `pgvector.cosine` sits under `postgres` | `3-extensions/pgvector/src/core/descriptor-meta.ts:66-69` | Confirmed | -| 29–35 | `sql.*` keys | `postgres/…/descriptor-meta.ts:187-194` | Confirmed; added the real `checkConstraint: true` so the excerpt matches an emitted file | -| 41 | packs contribute keys; re-emitting changes the set | `capabilities.ts:70-87`; `contract-emit.ts:252` | Confirmed | -| 47–51 | SQLite scalar-list diagnostic text | `contract-psl/src/psl-field-resolution.ts:497-503` | Confirmed (quote is the first sentence of the message verbatim; the message continues "Remove the list or author it against a target that supports scalar lists.") | -| 54 | error code | `builder-base.ts:287`; `sql-orm-client/src/collection-contract.ts:583,599` | **Corrected** by addition — the page now names `ORM.CAPABILITY_MISSING` | -| 56–58 | both quoted messages | `builder-base.ts:288`; `collection-contract.ts:600` | Confirmed verbatim | -| 61 | error raised while the query is constructed | `builder-base.ts:56-64` (`_gate` wraps the method call) | Confirmed | -| 69 | `sql.lateral`; "SQLite does not declare it" | `6-adapters/sqlite/src/core/descriptor-meta.ts:23` declares `lateral: false` | **Corrected** — "PostgreSQL reports `true`, SQLite reports `false`" | -| 70 | `sql.returning` | `collection-contract.ts:578-585` | Confirmed | -| 71 | `sql.enums`; "SQLite does not declare it" | `sqlite/…/descriptor-meta.ts:27` declares `enums: false` | **Corrected** — "SQLite reports `false`" | -| 72 | `sql.defaultInInsert` for multi-row inserts | `sql-orm-client/src/collection.ts:1483` | Confirmed | -| 73 | `sql.scalarList` | `psl-field-resolution.ts:497` | Confirmed | -| 74 | `postgres.distinctOn` | `collection-contract.ts:587-602` | Confirmed | -| 75 | `postgres.jsonAgg` "JSON aggregation for nested reads" | declared only | **Q** (see below) | -| 76 | `postgres.pgvector.cosine` "cosine distance operations" | declared only; the operations are `cosineDistance` / `cosineSimilarity` | **Corrected** — "Contributed by the pgvector pack, alongside its cosine operations" | -| 80 | MongoDB declares no keys | `3-mongo-target/1-mongo-target/src/core/descriptor-meta.ts:15` (`capabilities: {}`); driver control at `3-mongo-driver/src/exports/control.ts:27` | Confirmed | -| 84 | recorded at emit time, not probed | static declarations on the descriptors; merged in `contract-enrichment.ts:66` | Confirmed | -| 84 | `db verify` compares schema and profile using the hashes | `cli/src/control-api/operations/db-verify.ts:287-305`; `cli/src/orm/db/verify.ts:149` | Confirmed | -| 86, 97, 98 | links to `/orm/extensions/using-extensions`, the artifact page, `/cli/configuration` | pages exist | Confirmed | - -## 3. Q list (unverifiable, page text left alone) - -1. **`postgres.jsonAgg` "JSON aggregation for nested reads"** — the key is declared by the PostgreSQL and SQLite adapters but nothing in `src` reads it in rc.10. Searches: `grep -rn "jsonAgg" --include='*.ts' packages` (only `descriptor-meta.ts`, `adapter.ts`, generated `contract.d.ts` files, and tests); `grep -rn "capabilities\['sql'\]\|capabilities\['postgres'\]"` across `packages`. -2. **`sql.enums` "Enum value sets enforced in the database"** — same situation: declared by both adapters, read nowhere. Searches: `grep -rn "'enums'" --include='*.ts' packages/2-sql packages/3-targets | grep -i capab` (no hits); the `capabilities['sql']` grep above returned only `checkConstraint` and `scalarList`. -3. **Purity bullets 2 and 3 on the TypeScript page** ("Functions, class instances, and `Date` objects do not serialize") — no code in the tree rejects such values or states the rule. Searches: `grep -rn "process.env\|Date\b\|non-serializable\|NOT_SERIALIZABLE" --include='*.ts' packages/2-sql/2-authoring/contract-ts/src/` (no hits); `grep -rn "throw\|error" .../is-plain-record.ts` (no hits). Bullet 1 is a fair reading of `hashing.ts`, which hashes the built contract, but the source does not state the rule either. - -Also worth flagging, not on the page: `CONFIG.MISSING_EXTENSION_PACKS` exists as an error factory in `1-framework/1-core/errors/src/control.ts:356` but has **no caller** in rc.10 outside its own unit test. I therefore did not add the "emit fails with…" sentence that would have made the extension-pack paragraph sharper. - -## 4. Line counts - -| Page | Before | After | -| --- | --- | --- | -| `typescript-schema-builder.mdx` | 282 | 282 | -| `capabilities.mdx` | 97 | 98 | - -The one added line on the capabilities page is the real `"checkConstraint": true` entry in the `contract.json` excerpt. - -## 5. Checker status - -- `typescript-schema-builder.mdx` — `check-plain.sh` prints **clean**; the extra page-conventions grep is clean (only `codecId` inside code, and the pinned `[#keep-the-contract-file-pure]` anchor). -- `capabilities.mdx` — the extra page-conventions grep is clean. `check-plain.sh` still reports seven lines, all of which you told me to keep: `title: Capabilities`, `url: /orm/contract-authoring/capabilities`, `metaTitle: Capabilities in Prisma ORM`, and the four headings. Every prose sentence is clean. Tell me if you want the headings and title reworded with pinned anchors instead, and I will do that in one pass. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/factcheck2.psl-syntax.md b/docs/orm-docs-audit/c21-contract-authoring/factcheck2.psl-syntax.md deleted file mode 100644 index 2f4dfc314b5..00000000000 --- a/docs/orm-docs-audit/c21-contract-authoring/factcheck2.psl-syntax.md +++ /dev/null @@ -1,81 +0,0 @@ -Fact re-check complete for `wip/c21/ca/fc2/psl-syntax.mdx`. Two corrections, everything else confirmed. Line count: 356 before, 356 after. - -## Claim table - -| Line | Claim | Deciding source | Verdict | -|---|---|---|---| -| 9 | `contract emit` writes `contract.json` and `contract.d.ts` beside the source | `packages/3-extensions/postgres/src/config/define-config.ts:28` (`deriveOutputPath`) | Confirmed | -| 11–15 | The five differences (named types, enums, value objects, base models, extension types) | as per rows below | Confirmed | -| 12 | "enums: unchanged, except that an enum can now say how its values are stored" | `framework-authoring.ts:330`; `interpreter.enum.test.ts:807` (enum lowers to a text or int column plus a membership check, not a database enum type; members carry values) | **Corrected** → "enums: an enum can now say how its values are stored, and what each member stores." | -| 17 | `datasource` and `generator` blocks are gone | `psl-parser/src/syntax/syntax-kind.ts:8` (reserved declarations are `model`/`namespace`/`type`/`types` only) | Confirmed | -| 17, 175 | `@db.` attributes are gone | `psl-named-type-resolution.ts:39` (`PSL_UNSUPPORTED_NAMED_TYPE_ATTRIBUTE` on any `db.` attribute) | Confirmed | -| 21 | `// use prisma-8` on line one | `language-server/src/schema-directive.ts:1`; `schema-directive.test.ts:39` (a directive after a model is not recognised) | Confirmed | -| 24–64 | PostgreSQL example (`types`, `type`, `enum`, `@id`, `@default(uuid())`, `@default(now())`, `@relation`, `@@map`) | `interpreter.types.test.ts:33`; `control-mutation-defaults.ts:70`; `ids/src/index.ts:62` (`uuidv4` applies to `pg/uuid@1`) | Confirmed | -| 67–103 | MongoDB example (`ObjectId`, `@map("_id")`, `mongo/string@1`, `@@index`) | `mongo-adapter/src/exports/control.ts:30`; `mongo-attribute-specs.ts:143` | Confirmed | -| 106 | `Uuid` is PostgreSQL's `uuid` type | `control-mutation-defaults.ts:272` | Confirmed | -| 106 | `db init` creates the tables | `cli/src/control-api/operations/db-init.ts:17` | Confirmed | -| 110 | `contract` takes one path; `.ts` means TypeScript, otherwise PSL | `postgres/src/config/define-config.ts:40` | Confirmed | -| 114 | `import { definePrismaConfig } from "prisma/config"` | `cli/src/commands/init/templates/code-templates.ts:304` and `quick-reference-postgres.md:43` both write `@prisma/cli-engine`; the published `prisma` package (`@internal/cli`) has no `./config` export | **Corrected** → `from "@prisma/cli-engine"` | -| 113–124 | `dotenv/config`, `db.connection`, `DATABASE_URL` | `code-templates.ts:303–315` | Confirmed | -| 127 | This is what `prisma orm init` writes | `cli/test/orm/init-scaffold.test.ts:232` (writes `prisma.config.ts`, `src/prisma/db.ts`, the contract file) | Confirmed | -| 127 | The import chooses the database | `postgres/src/config/define-config.ts:54`; `orm-mongo/package.json` exports `./config` | Confirmed | -| 131 | Scalar types `String`, `Int`, `Boolean`, `Decimal`, `DateTime`, `Json`, `Bytes` | `control-mutation-defaults.ts:163` | Confirmed | -| 131 | The 16 native PostgreSQL type names | `control-mutation-defaults.ts:206–316` (`postgresNativeAuthoringTypes`) — the list matches key for key | Confirmed | -| 131 | `DateString` and friends read back as text | `control-mutation-defaults.ts:283` (comment) | Confirmed | -| 131 | `String` is `text`, `Int` is `int4`, `DateTime` is `timestamptz` | `control-mutation-defaults.ts:164–190` | Confirmed | -| 133–137 | `@id`, `@@id`, `@unique`, `@@unique`, `@@index`, `@map`, `@@map` | `sql-attribute-specs.ts:154–254` | Confirmed | -| 136 | `now()` is a column default; `uuid()` comes from Prisma ORM, so raw SQL writes get no value | `control-mutation-defaults.ts:57` (storage) vs `:70` (execution generator) | Confirmed | -| 139 | `@updatedAt` is gone | `interpreter.defaults.test.ts:509` (`PSL_UNSUPPORTED_FIELD_ATTRIBUTE`) | Confirmed | -| 143 | `updatedAt temporal.updatedAt()` in the type position | `interpreter.defaults.test.ts:442` | Confirmed | -| 147 | Set on every create and update | `timestamp-now-generator.ts:66` (`onCreate` and `onUpdate`) | Confirmed | -| 147 | `temporal` needs no import | `postgres/src/core/authoring.ts:742` (the target contributes it) | Confirmed | -| 147 | The column is `timestamptz` | `postgres/src/core/authoring.ts:744` | Confirmed | -| 147 | You can still set the field on a write | `relational-core/src/query-lane-context.ts:124` ("caller-provided values always win") | Confirmed | -| 147 | `temporal.createdAt()` sets the field once, on create | `timestamp-now-generator.ts:61` (a `now()` column default) | Confirmed | -| 163 | PostgreSQL primary key is an ordinary column; MongoDB uses `_id` | `mongo-family/contract-psl/src/interpreter.ts:1323` | Confirmed | -| 167–175 | `types` block syntax and reuse | `interpreter.types.test.ts:33` | Confirmed | -| 175 | `String @db.VarChar(35)` becomes `VarChar(35)` | `psl-named-type-resolution.ts:39`; `postgresNativeAuthoringTypes.VarChar` | Confirmed | -| 175 | The `types` block is PostgreSQL only | The MongoDB PSL interpreter has no named-type handling (`2-mongo-family/2-authoring/contract-psl/src/interpreter.ts`, no `namedType` reference). Note: named types are a SQL-family feature, so SQLite would have them too; of the two databases the docs cover, the sentence holds | Confirmed | -| 190 | `pg/text@1` format; `@@type("pg/int4@1")` stores integers | `interpreter.enum.test.ts:807` (int-backed enum happy path) | Confirmed | -| 190 | A member with no value stores its own name | `framework-authoring.ts:293` | Confirmed | -| 192 | `@@type` optional; bare names and strings give text, integers give the integer type; a mix throws `PSL_ENUM_CANNOT_INFER_TYPE` | `framework-authoring.ts:290–347` | Confirmed | -| 192 | `@default(Low)` names the member | `interpreter.enum.test.ts:863` (`@default(Low)` emits `"low"`) | Confirmed | -| 194–207 | `native_enum` block, `pg.enum(Role)`, each member needs a value | `psl-native-enum-authoring.test.ts:265` (`PSL_NATIVE_ENUM_BARE_MEMBER`); `psl-pg-enum-column.test.ts:125`; `postgres/src/core/authoring.ts:88` | Confirmed | -| 207 | `pg` needs no import | `postgres/src/core/authoring.ts:88` (part of the target's own authoring types) | Confirmed | -| 207 | `migration plan` includes the `CREATE TYPE` | `postgres/src/core/migrations/postgres-migration.ts:187`; `test/migrations/native-enum-planner.test.ts:3` | Confirmed | -| 211–228 | Value objects: optional, lists, nesting, `jsonb` on PostgreSQL, embedded on MongoDB | `interpreter.value-objects.test.ts:112, 268, 323` (`nativeType: 'jsonb'`) | Confirmed | -| 228 | The two spellings, `types {}` versus `type X {}` | `psl-parser/src/syntax/syntax-kind.ts:8` | Confirmed | -| 232–243 | Relation shape, foreign-key side declares `fields`/`references` | `sql-attribute-specs.ts:427` | Confirmed | -| 245 | `onDelete` and `onUpdate` belong on the foreign-key side | `contract-psl/src/interpreter.ts:830` (the list side is rejected) | Confirmed | -| 245–252 | One-to-one needs `@unique` on the foreign-key field | `psl-relation-resolution.ts:456` (`PSL_NON_UNIQUE_BACKRELATION`) | Confirmed | -| 254 | No implicit many-to-many; you write the join model | `psl-relation-resolution.ts:432` | Confirmed | -| 254 | One foreign-key field per part of a composite key; `@@id` lists exactly the foreign-key fields | `psl-relation-resolution.ts:308` and `:316` | Confirmed | -| 277 | `post.tags` reads without naming `PostTag` | `psl-relation-resolution.ts:323` (`cardinality: 'N:M'`) | Confirmed | -| 277 | Emit reports which list field it could not match | `psl-relation-resolution.ts:299` (the message names the list field) | Confirmed | -| 277 | `PSL_AMBIGUOUS_BACKRELATION`, fixed with `@relation("name")` on both ends | `psl-relation-resolution.ts:417` | Confirmed | -| 283–304 | `@@discriminator(field)` bare, `@@base(Model, "value")` quoted | `sql-attribute-specs.ts:381` | Confirmed | -| 304 | A variant reuses the base model's fields | `sql-orm-client/src/types.ts:577` (the variant row is base row plus variant fields) | Confirmed | -| 304 | A variant takes the base model's primary key | `interpreter.polymorphism.test.ts:192` (the variant table gets the base key column and a foreign key back) | Confirmed | -| 306 | `db.orm.public.Task.variant('Bug')`, reads and writes | `sql-orm-client/src/collection.ts:396–409` | Confirmed | -| 308 | `@@map` picks the layout: own table, or nullable columns in the base table | `interpreter.polymorphism.test.ts:131` and `:161` | Confirmed | -| 310 | MongoDB variants declare `@@base` and no `@@map` | `mongo-family/contract-psl/test/interpreter.polymorphism.test.ts` | Confirmed | -| 314–338 | `npm install @prisma/orm-extension-pgvector`, `/control` import, `extensions:` line | `orm-extension-pgvector/package.json` exports | Confirmed | -| 338 | `pgvector` is a fixed name the pack declares | `pgvector/src/core/authoring.ts:4` | Confirmed | -| 338 | List the pack before using its types | `psl-column-resolution.ts` `checkUncomposedNamespace` | Confirmed | -| 342 | `contract infer` writes a starter `contract.prisma` | `cli/src/orm/contract/paths.ts:5`; `psl-printer/src/ast-to-print-document.ts:26` | Confirmed | -| 354 | `db` is in `src/prisma/db.ts`, written by `prisma orm init` | `code-templates.ts:317`; `init-scaffold.test.ts:232` | Confirmed | -| All links | 15 link targets and the anchors `#schema`, `#scalar-fields`, `#variant`, `#embed-or-reference`, `#polymorphic-relations`, `#available-skills-for-prisma-8`, `#not-available-yet` | files under `apps/docs/content/docs/` | Confirmed | - -## The one item that needs your decision - -The config import. The rc.10 source writes `import { definePrismaConfig } from '@prisma/cli-engine'` in both the `prisma orm init` template and the quick-reference it ships, and the published `prisma` package in this tree exports no `./config` subpath. So I changed the page. But the rest of the docs set uses `prisma/config`: `cli/configuration.mdx`, `cli/init.mdx`, and, I expect, the sibling contract-authoring pages. `cli/orm-init.mdx` already says both imports work and that `prisma orm init` writes the `@prisma/cli-engine` one. If you would rather keep one spelling across the section, revert my change and instead soften line 127 so it no longer claims the block is exactly what `prisma orm init` writes. - -## Q list (nothing found for or against) - -1. `npx prisma skills sync` (line 346). Searched `grep -rn "skills sync\|'skills'" packages/1-framework/3-tooling/cli/src`: no hit. The rc.10 tree has no `skills` command. Your conventions file says the published CLI at rc.13 has it, so I left the sentence alone. -2. "Projects created with `npm create prisma@latest -- my-app` include the Prisma ORM skills" (line 346). `create-prisma` lives in another repo, so this tree cannot decide it. Searched `grep -rn "create prisma@latest" wip/prisma-src`: no hit outside the docs conventions. -3. The `prisma-8` skill "covers PSL authoring" (line 346). Partly supported: `wip/prisma-src/skills/prisma-8/references/contract.md` exists and covers contract authoring. That is prose, which the brief ranks below code, so I am flagging rather than claiming it. - -## No test covers - -Nothing on this page rests on code without a test. The two facts I checked only in non-test code are "caller-provided values always win" (`query-lane-context.ts:124`, a documented interface contract) and the `pgvector` namespace name (`pgvector/src/core/authoring.ts`). \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/factcheck2.the-data-contract-and-artifact.md b/docs/orm-docs-audit/c21-contract-authoring/factcheck2.the-data-contract-and-artifact.md deleted file mode 100644 index d3258548041..00000000000 --- a/docs/orm-docs-audit/c21-contract-authoring/factcheck2.the-data-contract-and-artifact.md +++ /dev/null @@ -1,28 +0,0 @@ -Both pages check clean and the line counts are unchanged. - -## Corrections made - -| Page / line | Claim | Deciding source | Verdict | Now says | -|---|---|---|---|---| -| data-contract 50 | `import { definePrismaConfig } from "prisma/config"` matches what `prisma orm init` writes | `wip/prisma-src/packages/1-framework/3-tooling/cli/src/commands/init/templates/code-templates.ts:configFile` | Corrected | `from "@prisma/cli-engine"`. The string `prisma/config` appears nowhere in the source tree; `@prisma/cli-engine` is a real pinned dependency (`.../cli/package.json:55`) and is what the template and `examples/prisma-8-demo/prisma.config.ts` both use. | -| data-contract 77 | `migration plan` "works out what the database needs" | `.../cli/README.md` §`prisma migration plan` ("No database connection is needed — fully offline") | Corrected | "compares your contract against the last one and writes a migration" | -| artifact 16 | "Each file starts with a comment saying they are generated" | `.../emitter/src/emit.ts:38-45`; `examples/prisma-8-demo/src/prisma/contract.json:1287` | Corrected | `contract.d.ts` starts with the comment; `contract.json` carries the notice in a `_generated` entry appended at the end. | -| artifact 98 | `roots`: "one entry per model" | `.../contract-ts/src/build-contract.ts:952` (`if (!semanticModel.sharesBaseTable)`) | Corrected | "one entry per table that stores a model". Variants sharing a base model's table contribute no root — confirmed in the demo file, where `Bug` and `Feature` are absent from `roots`. | -| artifact 107, 115 | "The hash is part of the type name, so a `contract.d.ts` from one run does not type-check against a `contract.json` from another" / "TypeScript catches it" | `.../postgres/src/runtime/postgres.ts:102` — `readonly contractJson: unknown` | Corrected | The runtime option is typed `unknown`, so TypeScript never compares the two files. Bullet now just says each type carries the hash of that part of the contract; the closing sentence now points at the CI check already on the page. | -| artifact 112 | `contract.d.ts` excerpt used double quotes | `examples/prisma-8-demo/src/prisma/contract.d.ts:41`; emitter formats with `singleQuote: true` | Corrected | single quotes | - -## Confirmed (no change) - -`orm init` writes the contract, `prisma.config.ts`, `src/prisma/db.ts`, and `.env.example` (`init-scaffold.ts:42-59`). `// use prisma-8` written into both PSL starters; the parser and interpreter never read it; the language server behind the VS Code extension gates diagnostics on it (`language-server/src/document-diagnostics.ts:27`, `schema-directive.ts:1`). `contract.ts` templates carry no header. Three packages `@prisma/orm-postgres` / `-sqlite` / `-mongo` (`packages/9-public/@prisma/`); `./config`, `./runtime`, `./contract-builder` all in the exports maps. File extension picks the authoring mode and the output path is derived from the contract path (`postgres/src/config/define-config.ts:28-51`), so the config names exactly one file. `db migrate` writes the marker (`operations/migrate.ts`), `db init` does too (CLI README §`prisma db init`), `db sign` verifies the schema first then signs (`cli/src/orm/db/sign.ts`), `db verify` reports existence and match (README:677). `verifyMarker` is `'onFirstUse' | false` (`runtime-spi.ts:42`), defaults to `'onFirstUse'`, checks before the first query, and only calls `log.warn` with `CONTRACT.MARKER_MISSING` / `CONTRACT.MARKER_MISMATCH` — it cannot be made to throw (`sql-runtime.ts:897-928`). `storageHash` covers tables, columns, keys, indexes, foreign keys, checks, and enum values (`valueSet` entries live in `storage`); `profileHash` covers target and family (`contract/src/hashing.ts:98`). `contract.json` key order and every shape in the sample match the real emitted file. `Contract`, `StorageHash`, `ProfileHash`, and `Output` / `Input` per `type` block all present in the emitted `contract.d.ts`. Capabilities merged from the target pack plus `extensions: [...]` (`build-contract.ts:1578-1586`) and checked at query build time (`sql-builder/.../builder-base.ts:62`, `ORM.CAPABILITY_MISSING`). `mongo(...)` from `@prisma/orm-mongo/runtime`. `prisma generate` does not exist in the command tree (`cli/src/orm/cli.ts:96-120`). `npx prisma skills sync` exists (`skills/README.md:23`). All 27 link targets resolve, and all seven anchors exist in the target pages. - -## Q — unverifiable from this source - -1. `npm create prisma@latest -- my-app`, and that it writes `prisma.config.ts` / the contract / `db.ts` and installs the database package, and that its projects ship the skills. `create-prisma` is not in this repo. Searches: `grep -rn "create prisma@latest" packages skills docs examples`, `grep -rn "create prisma" ... --include='*.json'` — both empty. -2. "Delete the `.d` and the import stops resolving." The template writes `'./contract.d'` (`code-templates.ts:dbFile`) and `skills/prisma-8/references/runtime.md:45` repeats it, but nothing states what happens without it. Searches: `grep -rn "contract.d'" packages skills docs`, `grep -rn "\.d'" skills/prisma-8/references/*.md`. -3. "byte for byte on any machine". The emitter README calls the two files deterministic and excludes `_generated` from canonicalization to keep it so (`emitter/README.md:9,109,169`), but there is no cross-machine test. Left as is. - -## Notes - -- `codecId`, `nativeType`, and `capabilities` still appear, but only as JSON keys in code blocks and inline code, which `page-conventions.md` allows. `#deterministic-emission` and `#keep-the-contract-file-pure` are pinned anchors and must stay. -- No test covers the `// use prisma-8` header being optional for the CLI; that fact comes from the absence of any reference to it in the parser and interpreter. -- Line counts: `the-data-contract.mdx` 142 before, 142 after. `the-contract-artifact.mdx` 158 before, 158 after. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/factcheck2.typescript-and-capabilities.md b/docs/orm-docs-audit/c21-contract-authoring/factcheck2.typescript-and-capabilities.md deleted file mode 100644 index 801472f93a6..00000000000 --- a/docs/orm-docs-audit/c21-contract-authoring/factcheck2.typescript-and-capabilities.md +++ /dev/null @@ -1,81 +0,0 @@ -Both pages are checked and corrected. Summary below. - -## typescript-schema-builder.mdx (318 lines before, 318 after) - -| Line | Claim | Source that decides it | Verdict | -| --- | --- | --- | --- | -| 9 | `contract.ts` under `src/prisma`, emit writes the two files beside it | `cli/src/commands/init/templates/code-templates.ts:64`, `config/src/config-types.ts:45`, `postgres/src/config/define-config.ts:28-46`, `emitter/src/artifact-paths.ts:19` | Confirmed | -| 18 | `contract infer` writes PSL | `cli/src/orm/contract/infer.ts:83-88` | Confirmed | -| 22 | `orm init` asks how you want to write your schema | `cli/src/orm/init-inputs.ts:98-102` | Confirmed | -| 24 | `.ts` extension selects TypeScript authoring | `postgres/src/config/define-config.ts:43-45` | Confirmed | -| 27, 38, 279 | `import { definePrismaConfig } from "prisma/config"` | `cli/src/commands/init/templates/code-templates.ts:304`, `skills/prisma-8/references/contract.md:47-55`, `examples/prisma-8-demo/prisma.config.contract-ts.ts` | **Corrected** → `from "@prisma/cli-engine"`. `prisma/config` appears nowhere in rc.10. | -| 50 | Builder import paths | `orm-postgres/package.json:81`, `orm-mongo/package.json:72` | Confirmed | -| 52 | Export named `contract` or default only | `cli/src/load-ts-contract.ts:255-267`, `sql-contract-ts/src/config-types.ts:105-113` (`CONTRACT.MODULE_EXPORT_MISSING`) | Confirmed | -| 54-93 | Whole PostgreSQL example, every helper and option | `examples/prisma-8-demo/prisma/contract.ts` (same shape), `contract-dsl.ts`, `postgres/src/core/authoring.ts` | Confirmed | -| 57 | `nativeType` / `codecId` / `@1` comment | `framework-components/src/shared/column-spec.ts:20-24` | Confirmed | -| 95-123 | MongoDB example (`collection`, `field.objectId/string/date().optional()`, `rel.hasMany("Post", {from,to})`, `User.ref("_id")`) | `mongo-family/2-authoring/contract-ts/src/contract-builder.ts:1006-1030, 1157-1164, 1362, 252-254` | Confirmed | -| 127 | PostgreSQL `defineContract(options, factory)` | `postgres/src/contract/define-contract.ts:81-103` | Confirmed (a single-object form also exists; see Q1) | -| 129 | Package also exports `field`, `model`, `rel` | `contract-dsl.ts:2102-2106` — the exported `field` is only `column`/`generated`/`namedType` | **Corrected** → now says the package exports `model` and `rel`, "plus a `field` that has only `column`, `generated`, and `namedType`". | -| 131 | MongoDB `defineContract` takes a single object | `mongo/src/contract/define-contract.ts:45-65` — a factory form exists too | **Corrected** → "also accepts a single object holding `models`, as above". | -| 133 | `_id`, no `.id()`, no `.attributes(...)` on MongoDB | `mongo .../contract-builder.ts:163-177, 222-255`; PSL requires `_id` at `mongo/contract-psl/src/interpreter.ts:1326-1335` | Confirmed (see Q2) | -| 134 | MongoDB scalar helpers | `mongo .../contract-builder.ts:1006-1030` | Confirmed | -| 135 | Collection and relations inline "rather than chained calls" | `composed-authoring-helpers.ts:113-129` — PostgreSQL `model()` also accepts inline `relations` | **Corrected** → "The collection name is an inline option on the model rather than a chained call, and relations are inline as well." | -| 136 | `{from,to}` vs `{by}`, string or `User.ref()`, `User.refs.id` | `mongo .../contract-builder.ts:372-375, 1144-1155`; `contract-dsl.ts:1456` | Confirmed | -| 137 | `indexes: [index({ email: 1 }, { unique: true })]`, `1` is ascending | `mongo .../contract-builder.ts:1062-1085`; `mongo-contract/src/contract-types.ts:5` | Confirmed | -| 139 | MongoDB `enums` and `extensions` in the same object | `mongo .../contract-builder.ts:899-910` | Confirmed | -| 147-159 | Field-helper table | `postgres/src/core/authoring.ts:678-770`, `2-sql/9-family/src/core/authoring-field-presets.ts:41-50` | Confirmed, except the UUID row | -| 158 | "UUID stored as text" | `authoring-field-presets.ts:24-49` — `sql/char@1`, `character(36)` | **Corrected** → "UUID stored in a `character(36)` column". | -| 161 | `field.column({codecId:"pg/date-temporal@1", nativeType:"date"})`; `field.column` vs `.column(name)` | `postgres/src/core/codec-ids.ts:26`, `temporal-codecs.ts:77-78`; `contract-dsl.ts:217, 505` | Confirmed | -| 163 | `field.id.*` list, generated in your app, `*Native` variants, no auto-increment helper, `defaultSql("gen_random_uuid()").id()` | `authoring-field-presets.ts:93-200`, `postgres/src/core/authoring.ts:770-800`; no auto-increment preset in either file | Confirmed | -| 165 | `createdAt` uses `now()`; `updatedAt` set in the client | `2-sql/9-family/src/core/timestamp-now-generator.ts:60-79` | Confirmed | -| 167-172 | `.optional()`, `.default()`, `.defaultSql("now()")`, `.unique()`, `.id()`, `.column()` | `contract-dsl.ts:183, 217, 313, 320, 327, 379` | Confirmed | -| 176-180 | `enumType(name, codec, ...members)`, `member()`, `.members.Low`, `enums` map | `contract/src/enum-type.ts:23-28, 153-233`; `postgres/src/contract/enum-type.ts` | Confirmed | -| 178 | `pg/int4@1` / `int4` pair | `postgres/src/core/authoring.ts:686-690` | Confirmed | -| 182-195 | `nativeEnum("Role","user","admin")`, `field.column(pg.enum(Role))`, type name = the name you give, `Role` not in `enums` | `postgres/src/contract/native-enum.ts:20-35, 88-130, 176` | Confirmed | -| 201-203 | `hasMany/hasOne({by})`, `belongsTo({from,to})`, `manyToMany({through,from,to})` | `contract-dsl.ts:2001-2100` | Confirmed | -| 211-219 | `belongsTo` alone makes no foreign key; `.sql({fk})`; the five action values | `contract-lowering.ts:299-336`; `contract-dsl.ts:843-853` | Confirmed (`fk` also accepts `constraint` and `index`, which the page does not list) | -| 221 | No column-type check across a relation | No such check in `build-contract.ts` / `contract-lowering.ts` (see Q3) | Confirmed | -| 223 | Model object vs string name; string typo caught at emit | `contract-dsl.ts:2008-2011`; `build-contract.ts:630` (`CONTRACT.MODEL_UNKNOWN`) | Confirmed | -| 229 | `.relations`, `.attributes`, `.sql` in any order | `contract-dsl.ts:1520-1610` (all three are methods on the same builder) | Confirmed | -| 231-243 | `{cols, constraints}`, `constraints.index([...])` always a list, `{unique:true}` | `contract-dsl.ts:1248-1254, 1065-1090, 816` | Confirmed | -| 245 | `constraints.foreignKey(cols.userId, User.refs.id)` | `contract-dsl.ts:1119-1150, 1456` | Confirmed | -| 247-255 | `.attributes(({fields,constraints}))`, only `id` and `uniques` | `contract-dsl.ts:1210-1213, 1228-1231` | Confirmed | -| 259-274 | `extensions: {pgvector}`, `type.pgvector.Vector(1536)`, pack name fixed by the pack, `Embedding1536` in `contract.d.ts` | `pgvector/src/core/authoring.ts:4-19`; `examples/prisma-8-demo/prisma/contract.ts:21-22` and `src/prisma/contract.d.ts:927` | Confirmed | -| 276-289 | `/pack` in the contract, `/control` in the config, `extensions` inside `ormConfig` | `orm-extension-pgvector/package.json:46-48`; `postgres/src/config/define-config.ts:21` | Confirmed | -| 295-298 | Purity rules; emit loads the file with a dynamic import and does not type-check it | `sql-contract-ts/src/config-types.ts:104` (plain `await import(...)`) | Confirmed | -| 304-306 | One contract file per config; no command converts PSL to TypeScript | `define-config.ts:16-17`; no such command under `cli/src/orm/` | Confirmed | -| 141 | "The rest of this page uses..." | page-conventions ban on docs commentary | **Corrected** → "The examples below use the PostgreSQL builder." | -| all | Every link target and anchor | Files under `apps/docs/content/docs`; `raw-queries.mdx:78`, `ai/tools/skills.mdx:100` | Confirmed | - -## capabilities.mdx (96 lines before, 96 after) - -| Line | Claim | Source | Verdict | -| --- | --- | --- | --- | -| 9 | Emit writes the section; you never edit it | `build-contract.ts:1577-1590` (authors can no longer declare capabilities) | Confirmed | -| 13 | Keys come from the database package and the extension packages; emit never connects | `cli/src/control-api/contract-enrichment.ts:62-84`; no connection code in `operations/contract-emit.ts` | Confirmed | -| 15-22 | `contract.json` excerpt keys | `adapters/postgres/src/core/adapter.ts:24-40`; `pgvector/src/core/descriptor-meta.ts:66-70` | Confirmed | -| 24 | Group named first; `sql` is shared | same files | Confirmed | -| 24 | "SQLite reports `false` for most of them" | `adapters/sqlite/src/core/adapter.ts:76-85` — 2 of 6 are `false` | **Corrected** → "for some of them". | -| 26-38 | `prisma.config.ts` with `extensions: [pgvector]`, `db.connection` | `postgres/src/config/define-config.ts:16-24` | Confirmed | -| 27 | `definePrismaConfig` import path | as above | **Corrected** → `@prisma/cli-engine` | -| 40 | pgvector ships a migration that installs the server extension; apply with `db migrate` | `pgvector/migrations/20260601T0000_install_vector_extension/migration.ts` | Confirmed | -| 44-50 | SQLite has no `scalarList`; the message, verbatim including the remedy line | `contract-psl/src/psl-field-resolution.ts:497-503` | Confirmed, exact match | -| 52 | `@prisma/orm-sqlite/config` exists | `orm-sqlite/package.json:63` | Confirmed | -| 54 | "when you call a method on the SQL query builder" | `sql-orm-client/src/collection-contract.ts:578-602` and `collection.ts:1063, 1350` — the ORM client raises it too | **Corrected** → "on the SQL query builder or on the ORM client". | -| 54-57 | `ORM.CAPABILITY_MISSING`, `distinctOn() requires capability postgres.distinctOn` | `sql-builder/src/runtime/builder-base.ts:278-292`; `collection-contract.ts:599-600` | Confirmed, exact match | -| 60 | Error at call time, not at await | `builder-base.ts:57-64`; `collection.ts:1063` | Confirmed | -| 60-66 | `db.contract.capabilities...`, `db.ts` written by `orm init` | `postgres/src/runtime/postgres.ts:63, 286`; `code-templates.ts:318-333` | Confirmed | -| 72-78 | Table rows | `adapters/postgres/src/core/adapter.ts:24-40`; `builder-base` gated methods `lateralJoin`/`distinctOn`; `assertReturningCapability`; `pgvector/src/core/descriptor-meta.ts:68` | Confirmed | -| 80 | "SQLite reports `false` for them"; "build-time check runs for every database"; "run-time check only on the SQL query builder" | `adapters/sqlite/src/core/adapter.ts:76-85` (returning is `true`, `scalarList` absent); no capability keys or checks anywhere under `2-mongo-family` or the mongo target | **Corrected** → "PostgreSQL reports `true` for all three `sql` keys above. SQLite reports `false` for `sql.lateral`, `true` for `sql.returning`, and has no `sql.scalarList` key at all. … The MongoDB packages report no keys, so neither check applies on MongoDB." | -| 84 | `db verify` checks tables and columns, not this section | `cli/src/control-api/operations/db-verify.ts:38-58` | Confirmed | -| all | Link targets and anchors | as above | Confirmed | - -## Q list (unverifiable, page left alone) - -- **Q1.** PostgreSQL `defineContract` also accepts a one-argument definition object (`postgres/src/contract/define-contract.ts:81-89`), so line 127's "takes an options object and then a function" describes only the form the page teaches. I left it, because it is true of that form, and corrected the MongoDB sentence instead. Searches: `grep -n "export function defineContract" -A 25` in both `define-contract.ts` files. -- **Q2.** No code in the TypeScript MongoDB builder requires an `_id` field. The PSL interpreter does (`interpreter.ts:1326-1335`). Searches: `grep -rn "_id" packages/2-mongo-family/2-authoring/contract-ts/src/`, `grep -rn "must declare an _id\|DOCUMENT_ID" packages --include="*.ts"`. -- **Q3.** Line 221's "Prisma ORM does not check that the column types on the two sides of a relation match" rests on absence. Searches: `grep -rn "RELATION_INVALID\|FOREIGN_KEY_INVALID" build-contract.ts contract-lowering.ts`, `grep -n "codecId\|mismatch" derived-checks.ts`. The only relation checks I found are nullability and missing `through`. - -## One thing the operator should decide beyond my two pages - -`prisma/config` is not an import path in rc.10. Every `prisma.config.ts` example in the section that imports `definePrismaConfig` from `prisma/config` is wrong the same way. I fixed my two pages; the other contract-authoring pages (and any CLI page with a config example) likely need the same change. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/page-conventions.md b/docs/orm-docs-audit/c21-contract-authoring/page-conventions.md deleted file mode 100644 index 5f1ae1caa82..00000000000 --- a/docs/orm-docs-audit/c21-contract-authoring/page-conventions.md +++ /dev/null @@ -1,14 +0,0 @@ -# Conventions for the orm/contract-authoring pages (in addition to conventions.md) - -- These are explanation and how-to pages, not reference pages: The data contract (what it is), Author in PSL, Author in TypeScript, The emitted artifacts, Capabilities. Each one answers "what is this and what do I write". Keep the heading order. Do not renumber, rename, or reorder headings; if a heading must change because it contains a banned term, pin the old anchor with `[#old-anchor]`. -- No commentary about the docs inside the docs. Cut sentences such as "This page explains", "This page covers the basics briefly", "the examples throughout these docs", "to give a sense of the granularity". Write the thing itself. -- Never interleave PostgreSQL and MongoDB sentence by sentence. Where both databases matter, give PostgreSQL its own sentences or block and then MongoDB its own. Tabs already on a page stay. -- The word "contract" is the subject of this section. On first use on a page: "your contract, the `contract.prisma` file that replaced `schema.prisma`" (or `contract.ts` when the page is about TypeScript authoring). After that, "contract" alone. "Data contract" is allowed in the page title and once in the first sentence of the-data-contract; elsewhere say "contract". -- `prisma contract emit` "writes `contract.json` and `contract.d.ts` beside the source". Say this once per page where the command first appears. Never a bare "emit" as a noun ("the emit", "emission", "emitted artifacts"). "The artifacts" is allowed after the page has named the two files. -- "Hashes" and the marker: "when you run `prisma db sign`, Prisma ORM records which contract the database matches in a small table it creates in the database; the client checks that record before its first query" (verify against source; use the wording the check confirms). Do not write "signature" or "marker" without that definition on the same page. -- Banned on these pages in addition to the list: gate / gated / gates (write "checks", "requires", "is only available when"); capability in prose (write "a database feature the contract needs", "what your database supports"; the code identifier `capabilities` for the section of `contract.json`, and the key names such as `sql.lateral`, are code and stay); codec (write "how the value is stored and read", or name the type id such as `pg/text@1`); canonical / canonicalizes (write "in a fixed key order"); deterministic (write "the same source always produces the same files"); pure (as in "the contract file must stay pure": write "must not read the environment, the clock, or random values"); artifact when the two files can be named instead; "source of truth" (write "the one file the config points at"); "escape hatch"; "purpose-built"; "first-class"; "wiring" / "wired"; "hangs off"; "consult"; "authoritative"; "physical name" (write "column name in the database"); "value object" only with its definition on the same page ("a `type` block: a structured value stored inside its parent row, with no table of its own"). -- Type ids such as `pg/text@1`: "the PostgreSQL type plus a version, always `@1` today". Do not explain the format twice on one page. -- "Prompt your coding agent" sections: keep, but the first sentence reads "Projects created with `npm create prisma@latest` include the Prisma ORM skills for your coding agent. In an existing project, run `npx prisma skills sync`." Keep the example prompts. -- Scaffold command: `npm create prisma@latest -- my-app`. Emit command in prose and code: `npx prisma contract emit` (not `prisma@latest`, once the page has shown installation; the pages assume an existing project). -- The source of truth is prisma/orm at 8.0.0-rc.10 (`cfccb09be2`), checked out at `wip/prisma-src/`. Where a page names an rc.9 behaviour that rc.10 changed, rc.10 wins. -- The CLI pages under `/cli/` (`contract-emit`, `contract-infer`, `db-sign`, `db-verify`, `db-init`, `migration-plan`, `configuration`) are out of scope; link to them, do not restate their flags. diff --git a/docs/orm-docs-audit/c21-contract-authoring/psl-syntax.round1.md b/docs/orm-docs-audit/c21-contract-authoring/psl-syntax.round1.md deleted file mode 100644 index 42620c39f87..00000000000 --- a/docs/orm-docs-audit/c21-contract-authoring/psl-syntax.round1.md +++ /dev/null @@ -1,95 +0,0 @@ -I read the page once, start to finish, as a Prisma 6 user. - -## Sentences I could not restate after one reading - -1. Line 9 — "PSL is the preferred way to author [your contract](...), the `contract.prisma` file that replaced `schema.prisma`." - Stopped by: "PSL" is never expanded anywhere in the body, and "contract" is never defined. Line 11 says "the Prisma schema language" but never joins the two. I guessed PSL = Prisma Schema Language from the meta description in the frontmatter, which readers do not see. - Plainer: "PSL (Prisma Schema Language) is the schema language you already use. In Prisma 8 the schema file is called `contract.prisma` instead of `schema.prisma`." - -2. Line 11 — "The Prisma ORM 8 additions are named types, typed enums, value objects, base models with variants, and extension types." - Stopped by: five invented-sounding terms in one sentence, none defined yet. "typed enums" is worse than the rest, because I already have enums in v6 and the page never says what the "typed" part changes. The section heading later is just "Enums", so I could not match the promise to the section. - -3. Line 138 — "Generated defaults such as `@default(uuid())` are applied by Prisma ORM before each write. They appear in the contract's `execution` section rather than as a column default." - Stopped by: "the contract's `execution` section" — a thing I have never seen and the page never shows. I only wanted to know whether `@default(uuid())` still works. - Plainer: drop the second sentence. - -4. Line 181 — "Native PostgreSQL types are written as the type, so a field can also use `VarChar(35)`, `Uuid`, or `Timestamptz` directly without an alias." - Stopped by: "written as the type" is not English I can parse. More importantly, this is the page's only hint that `@db.VarChar(35)` — the syntax I have typed for two years — is gone. It never says so. - Plainer: "In Prisma 8 you write the native type as the field's type. `String @db.VarChar(35)` in Prisma 6 becomes `VarChar(35)`." - -5. Line 196 — "That name is the PostgreSQL type plus a version, always `@1` today." - Stopped by: I do not know what the version is for, why it exists if it is always `@1`, or what changes when it is not. - -6. Line 196 — "Leave `@@type` out and Prisma ORM infers it from the member values." - Stopped by: this contradicts line 185, "An enum declares how its values are stored with `@@type`." Is `@@type` required or not? And infers it to what — `text`? I do not know. - -7. Line 233 — "Many-to-many relations go through an explicit model for the join table. Its primary key must cover both foreign keys. The list fields on both sides then resolve through it." - Stopped by: "resolve through it". In the example, `Post.tags` and `Tag.posts` carry no `@relation` and never name `PostTag`. I cannot tell how Prisma connects them. Is it because `PostTag` is the only model holding both foreign keys? The page does not say. - -8. Line 283 — "On PostgreSQL, the variant's `@@map` picks its storage layout: with its own `@@map`, as here, the variant's fields live in their own table sharing the base model's primary key. Without one, they live in the base table as nullable columns." - Stopped by: too many ideas in one sentence, and a surprise. `@@map` has always been a naming attribute. Here its presence or absence silently switches the whole storage strategy. I read it three times. - -9. Line 287 — "Extension packs contribute types through constructor calls in the `types` block." - Stopped by: "extension packs" is undefined, and "constructor calls" is TypeScript vocabulary used to describe something in a `.prisma` file. I could not picture it until I read the code block. - -## Words and phrases I had to guess - -- **"PSL"** — guessed Prisma Schema Language. -- **"contract"** — guessed it is just the new word for the schema. Still not sure whether it means more than that. -- **"emit"** in `prisma contract emit` — guessed "generate". -- **"named types"** — guessed "type aliases", confirmed two sections later. -- **"value object"** — guessed "embedded/inline struct". The page confirms it, but only after using the term in the intro. -- **"base models with variants"** — guessed single-table inheritance, from other ORMs. "variant" is never defined; it is only used. -- **"extension types"** and **"extension packs"** — guessed npm packages that add custom column types. -- **"the `prisma-8` skill"** (line 321) — guessed some agent instruction file. I do not know what a "skill" is here. -- **`// use prisma-8`** (lines 16, 60) — guessed a required pragma, not a comment. See below. - -## Places I asked "so what do I actually type?" and got no answer - -1. **`// use prisma-8` at the top of every contract file.** It appears in both complete examples and is never mentioned in prose. It is spelled like a comment but reads like a directive. Is it required? What happens without it? And the extension-types example at line 302 omits it, so I now think it is optional, or I think the example is wrong. I cannot tell which. - -2. **Where do `datasource db` and `generator client` go?** Every schema I have written for two years starts with both. Neither appears in either complete example, and the page never says they are gone. So: where does my connection URL live now? How do I choose the client output path? The config file at line 108 sets only `contract`. I do not know how Prisma reaches my database. - -3. **How the provider is chosen.** I inferred it from `@prisma/orm-postgres/config` versus `@prisma/orm-mongo/config`. The page never states that the import picks the database. - -4. **`@@unique`.** The attribute list at lines 134–139 covers `@id`, `@@id`, `@unique`, `@@index`, `@default`, `@map`, `@@map`. It omits `@@unique`. Then line 325 tells me to ask my agent for "a composite unique constraint on userId and title" — a thing the page never shows me how to write. - -5. **Valid `@@type` names.** I have `"pg/text@1"` and `"mongo/string@1"`. What do I write for an enum stored as an integer, or as a real PostgreSQL `enum` type? Is the native PG `CREATE TYPE` enum still available at all? No list, no link. - -6. **Where the name `pgvector` in `pgvector.Vector(1536)` comes from.** Is it the default import identifier I chose in `prisma.config.ts`, or a fixed name the pack declares? If I write `import pgv from ...`, do I then write `pgv.Vector(1536)`? The page does not say, and this is the exact thing I would get wrong. - -7. **What a variant model inherits.** `model Bug` has no `id` and no `title`. Does `Bug` have them? Do I query `prisma.bug.findMany()` or `prisma.task.findMany()` and narrow? Neither is stated. - -8. **`@updatedAt`, `onDelete`, `onUpdate`, one-to-one relations.** All routine in v6, none mentioned. I do not know whether they survived. - -9. **Multi-file schemas.** Line 106 says the config "names the one file Prisma ORM reads". I use `prismaSchemaFolder` today. That reads like a removal, but the page never confirms it. - -10. **How I use `contract.d.ts`.** It is produced twice in the page and never used. Do I import from it? Does the client pick it up automatically? - -## Places explaining the internals when I only wanted to know what to do - -- Line 138 — "They appear in the contract's `execution` section rather than as a column default." -- Line 196 — "In the database, Prisma ORM enforces the allowed values with a `CHECK` constraint on each column that uses the enum." -- Line 256 — "`contract.json` records the relation as `N:M` with the join table's columns, so queries can traverse `post.tags` directly." -- Line 216 — "On PostgreSQL a value object field is stored in a single `jsonb` column." This one earns its place; it changes how I would query. The other three do not. - -## Two smaller inconsistencies - -- Line 130 says `npm create prisma@latest -- my-app`; line 321 says `npm create prisma@latest`. Same command, two spellings. -- Line 102 and 313 say `npx prisma contract emit`; line 313's first sentence and line 9 say `prisma contract emit`. - -## Could I do what the page is for, after one reading? - -Partly. I could write a model with fields, ids, defaults, maps, a one-to-many relation, an enum, and a value object, by copying the complete contract at the top and editing it. That part works. - -I could not ship a working project, because after one reading I still do not know: - -- where my database connection URL goes, now that `datasource` has vanished from the examples; -- whether `// use prisma-8` is required or decorative; -- whether `@db.VarChar(35)` still works, or how to convert my existing native-type annotations; -- how to write `@@unique`; -- which `@@type` strings are legal beyond the two shown; -- whether implicit many-to-many (`Post[]`/`Tag[]` with no join model) still exists, or whether I must now write the join model by hand for every one of them; -- what a variant model inherits, or how to query one. - -The biggest single gap is the silent disappearance of `datasource` and `generator`. For a reader upgrading from v6, that is the first thing they will notice and the page acts as if it never existed. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/psl-syntax.round2.md b/docs/orm-docs-audit/c21-contract-authoring/psl-syntax.round2.md deleted file mode 100644 index f8618cc2f7f..00000000000 --- a/docs/orm-docs-audit/c21-contract-authoring/psl-syntax.round2.md +++ /dev/null @@ -1,96 +0,0 @@ -# Reader report: `orm/contract-authoring/psl-syntax.mdx` - -Background I brought: two years of Prisma 7. I have never seen Prisma 8. - -## Sentences I could not restate after one reading - -**1.** "base models and variants: one model reusing another model's fields and rows." (line 16) - -What stopped me: "and rows." Fields I understand. A model reusing another model's *rows* means nothing to me at this point — models don't have rows, tables do. I only half-understood it four sections later. - -Plainer: "base models and variants: several models that share one table, told apart by the value of one field." - -**2.** "`@updatedAt` no longer exists. Write `updatedAt temporal.updatedAt()`, with the call where the type would go." (line 153) - -What stopped me: `temporal`. I don't know what it is, where it comes from, or whether I have to declare or import it. And "with the call where the type would go" is a clue that the syntax is strange, but it's a description of a code sample instead of a code sample. Every other feature on this page gets a code block; this one gets a sentence. - -Plainer: show it. -```prisma -model Post { - updatedAt temporal.updatedAt() -} -``` -And say in one clause what `temporal` is (built in? a namespace I get for free?). - -**3.** "In Prisma ORM 8 the native type is the field's type, so `String @db.VarChar(35)` from Prisma ORM 7 becomes `VarChar(35)`. The `@db.` attributes are gone." (line 197) - -What stopped me: the examples above this use `String`, `DateTime`, and `Uuid`. So sometimes the type is a native PostgreSQL type and sometimes it is a Prisma scalar. The page never says both sets exist side by side, never says which is which, and never links to a list of either. After one reading I do not know whether `String` is still legal, or whether it is a preferred spelling of `text`. - -**4.** "Prisma ORM matches the two list fields to the model that holds a foreign key to each side and whose primary key is exactly those two foreign keys" (line 255) - -What stopped me: three conditions welded into one sentence, and it's written from the tool's point of view ("Prisma ORM matches"), not mine. I had to read it twice to work out that it's telling me the rules my join model must satisfy. - -Plainer: "For Prisma ORM to connect `Post.tags` and `Tag.posts`, the join model must (a) hold a foreign key to each side, and (b) use exactly those two foreign keys as its primary key." - -And then: what happens if I get it wrong? Silent? An error at `emit`? The page doesn't say. - -**5.** "`@@type("pg/text@1")` stores the values as `text` on PostgreSQL. That name is the PostgreSQL type plus a version, always `@1` today." (line 212) - -What stopped me: "always `@1` today" tells me a version exists but gives me no reason to care and no rule for what to write when it isn't 1. If it is always `@1`, why am I typing it? I could not restate the purpose of the version because the page doesn't give one. - -**6.** "If your database already has a PostgreSQL `enum` type, declare it in a `native_enum` block and type the field `pg.enum(Role)`." (line 216) - -What stopped me: two new pieces of syntax (`native_enum`, `pg.enum(...)`) described in prose only, and `pg.` appears here for the first and only time with no explanation. I cannot write this after reading it. - -**7.** "With its own `@@map`, as `Bug` has here, they are in a table of their own that shares the base model's primary key. Without one, they are nullable columns in the base table." (line 307) - -What stopped me: it's not hard, but it's backwards for a reader. It tells me the consequence of a choice before telling me a choice exists. I had to reconstruct: "omitting `@@map` on a variant is a real design decision, not a default." - -## Words and phrases I had to guess - -- **"storage details"** (lines 13, 187). Guessed: the database column type and its size, e.g. `varchar(35)`. Confirmed later by example, but I guessed first. -- **`Uuid`** (line 45). Guessed: the PostgreSQL `uuid` column type, now spelled as a Prisma type. Not stated until line 197. -- **"named types"** vs **"extension types"** vs **"value objects"** (lines 13-17). Guessed all three from the one-line glosses; the value-object gloss ("a structured value stored inside its parent row, with no table of its own") is the only one that actually landed. -- **`pg/` and `mongo/` prefixes** in `@@type` (lines 38, 78). Guessed: the database family. Never stated. -- **`@1`** (line 38). Guessed: a format version for how values are encoded. Never stated. -- **`/control`** in `import pgvector from "@prisma/orm-extension-pgvector/control"` (line 317). Guessed: a subpath export meant for config files, as opposed to a runtime one. Never mentioned. -- **`public`** in `db.orm.public.Task.variant('Bug')` (line 305). Guessed: the PostgreSQL schema name. Nothing on this page introduces schemas, so it reads like a typo the first time. -- **`@@discriminator(type)`** (line 292). Guessed: `type` is the unquoted field name. Jarring, because `@@map("task")` two lines below quotes its argument and `@@base(Task, "bug")` mixes both styles in one call. I guessed the rule is "identifiers bare, strings quoted" and moved on. -- **`temporal`** (line 153). Guessed: a built-in namespace of time-related function types. Pure guess. -- **"emit"** (line 9). Guessed: generate. Fine, but it is a new verb replacing `generate`, and the page doesn't say that. - -## Places I asked "so what do I actually type?" and got no answer - -1. **The connection URL.** "The connection URL goes in this file too, in a `db` key beside `contract`." (line 140) The config example directly above it is complete except for the one thing every project needs. Adding two lines to the sample would cost nothing; instead I'm sent to the migration page. -2. **`updatedAt`.** Line 153, above. Described, never shown. -3. **`native_enum` and `pg.enum(Role)`.** Line 216. Described, never shown. -4. **The type list.** I have `String`, `DateTime`, `Uuid`, `VarChar(n)`, `ObjectId`, `Timestamptz`. What about integers, booleans, decimals, JSON, arrays of scalars, `Bytes`? No list, no link to one. For a page whose job is "here is how you write the schema", this is the biggest hole. -5. **Value objects, beyond the one shape.** Can I write `addresses Address[]`? Can a `type` contain another `type`? Can a `type` field have `@map` or a default? Not answered. -6. **Extension type names.** "The `pgvector` part … is a fixed name the pack declares." (line 341) Good to know it isn't the import name — but for any pack other than pgvector, how do I find out what its fixed name is? Its README? `contract emit` output? Not said. -7. **Enum defaults.** `priority Priority @default(Low)` (line 58) uses the member name while the member stores `"low"`. I guessed that's always right, but the Enums section never mentions defaults. -8. **The variant's missing `@id`.** `model Bug` (line 296) has no `@id` and no `id` field, unlike every other model on the page. I assume `@@base` supplies it. The page doesn't confirm it, so I don't know whether I may add one, must not add one, or should. -9. **Creating `db`.** "`db`, the client you create in `src/prisma/db.ts`, reads both." (line 359) I create it — how? That's the only sentence on the page that tells me to write a file and it has no link and no snippet. -10. **`Json`.** Value objects are stored "in a single `jsonb` column" (line 236). So is there still a plain `Json` field type for data I don't want to give a shape to? Unanswered. - -## Places the page explains internals when I only wanted to know what to do - -- **Line 236:** "On PostgreSQL a value object field is stored in a single `jsonb` column. On MongoDB it is an embedded document." I can live with this — it does affect what I can query — but it arrives before I've been told anything about *using* value objects. -- **Line 255:** the join-model matching rule, written as what the tool does rather than what my schema must look like. See above. -- **Line 341:** "It is not the name you gave the import in `prisma.config.ts`, so renaming that import does not change what you write here." This is a paragraph about a mistake I hadn't made yet, explaining a resolution mechanism, in place of telling me where to find the name I need. -- **Line 110:** "The first line, `// use prisma-8`, tells the Prisma editor extension that the file is a Prisma ORM 8 contract. It is not required: the contract works without it. `prisma orm init` writes it, so keep it." Three sentences that cancel each other out — it does nothing, it isn't required, keep it anyway. One sentence would do: "Leave the `// use prisma-8` comment in place; the editor extension uses it." -- **Line 151:** the split between database defaults and Prisma-generated defaults is genuinely useful — that one earns its place. Noting it so the list above isn't read as "cut all mechanism." - -## Could I do what the page is for, after one reading? - -Partly. I could write a plausible PostgreSQL contract with models, fields, `@id`, `@unique`, `@@map`, relations, an enum, and a value object, and I could point `prisma.config.ts` at it. The complete example at the top is the most useful thing on the page and carries most of that. - -What I would still not know: - -- **Which field types exist.** This is the failure that matters. I can copy `String`, `Uuid`, `DateTime`, `ObjectId` from the example and I can write `VarChar(35)`. For an integer, a boolean, a decimal, a date-only column, or a string array, I would be guessing, and the page gives me nowhere to look. -- **How to connect to my database**, because the `db` key is named but never shown. -- **How to write `updatedAt`**, `native_enum`, or `pg.enum(...)` — three features the page tells me about but does not show. -- **How to create `db`**, the client I'm told in the last section that I create. -- **Whether my many-to-many join model is correct**, or what I'll see if it isn't. -- **What `public` is** in `db.orm.public.Task`, and therefore whether schemas are something I need to declare in the contract. - -The page reads like it was written for someone who already knows Prisma 8 and needs the deltas listed. As someone arriving from 7, I got a good tour of the five new things and lost the ordinary ground under them — the type list, the connection string, and the client file. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/psl-syntax.round3.md b/docs/orm-docs-audit/c21-contract-authoring/psl-syntax.round3.md deleted file mode 100644 index 729832d6a87..00000000000 --- a/docs/orm-docs-audit/c21-contract-authoring/psl-syntax.round3.md +++ /dev/null @@ -1,98 +0,0 @@ -## Sentences I could not restate after one reading - -**1.** "named types: one name for a type and the database column type it uses." -Stopped me: this promises two things bound together, but the only example, `ShortName = VarChar(35)`, shows one thing. I could not tell which half of `VarChar(35)` is "the type" and which is "the database column type." Plainer: "named types: give a database column type a name you can reuse on many fields." - -**2.** "base models and variants: a variant model adds fields to a base model, and one field's value says which variant a row is." -Stopped me: three ideas in one sentence, and I did not yet know the word "variant" in this product. Plainer: "base models and variants: you can store several kinds of record in one table. A base model holds the shared fields; each variant adds its own. One column says which kind each row is." - -**3.** "A `.prisma` extension selects PSL authoring." -Stopped me: I could not work out what "selects" means here — does the file extension switch Prisma into a mode? What is the other mode, and what extension picks it? Plainer: "If the path ends in `.prisma`, Prisma reads it as PSL. If it ends in `.ts`, Prisma reads it as a TypeScript schema." - -**4.** "A native PostgreSQL type such as `VarChar(35)` or `Timestamptz` is written in the same position as one of those." -Stopped me: "in the same position as one of those" made me re-read to find the antecedent. Plainer: "You can write a PostgreSQL type such as `VarChar(35)` or `Timestamptz` wherever you would write `String` or `DateTime`." - -**5.** "`@updatedAt` no longer exists. Write the call where the type would go:" -Stopped me: "the call" — a call to what? And the example field has no type at all, which contradicts every other field on the page. Plainer: "`@updatedAt` is gone. Instead of a type, write `temporal.updatedAt()` as the field's type." - -**6.** "An alias binds a type and its database column type under one name." -Same problem as (1). The example does not show two things being bound. - -**7.** "Write the `@1` as shown, because there is no other version yet." -Stopped me: a version of *what*? The encoding? The PostgreSQL type? The Prisma package? The sentence tells me to copy it without telling me what it is. Plainer: "`@1` is the version of the storage format. Only version 1 exists, so always write `@1`." - -**8.** "That model must follow two rules. It holds a foreign key to each side, and each foreign key references that side's full `@id`." -Stopped me: "full `@id`" — I guessed this means "every field of a composite primary key, not just one of them," but the page never says so, and the example only shows single-field keys. - -**9.** "Break either rule and `npx prisma contract emit` throws an error whose `code` is `PSL_JUNCTION_ID_NOT_FK_COVERING` when the `@@id` lists anything other than the two foreign-key fields, or `PSL_JUNCTION_TARGET_FK_NOT_ID` when a foreign key does not reference the other side's `@id`. The message names the list field it could not match." -Stopped me: one sentence, two error codes, two conditions, and a nested clause. I had to read it three times. Split it into two sentences or a two-row table. - -**10.** "On PostgreSQL you choose where a variant's own fields are stored, and the variant's `@@map` is the choice." -Stopped me: "the `@@map` is the choice" is not a statement I can act on. The real rule is hidden in the next sentence. Plainer: "On PostgreSQL, whether you write `@@map` on the variant decides where its fields live." - -**11.** "Rows with `type = "bug"` are `Bug` records." -Minor, but `type` here is a field name I just read as a PSL keyword three sections earlier (`type Address`). I stalled on which one it meant. - -## Words and phrases I had to guess - -- **"contract"** — I guessed it is what I used to call the schema. The page also calls it "the schema" at the end ("don't write the schema by hand"), which made me doubt my guess. -- **"discriminator"** — I guessed: the column whose value says which kind of row this is. The page never defines it; it only uses it in `@@discriminator(type)`. -- **"variant"** — I guessed: a subclass. -- **"value object"** — I guessed: a nested object with no table of its own. The page does define it, which helped. -- **"extension pack"** — I guessed: a plugin npm package. -- **"`pg/text@1`"** — I guessed `pg` = PostgreSQL, `text` = column type. The page confirmed both. `@1` I still cannot explain. -- **"`db.orm.public.Task`"** — I guessed `db.orm` is a namespace and `public` is a schema. The page confirms `public` only. -- **"full `@id`"** — guessed: all fields of a composite key. -- **"`temporal`"** — guessed: a built-in namespace of date/time helpers. The page says it is built in but not what else is in it. - -## Places I asked "so what do I actually type?" and got no answer - -**1. The many-to-many example does not connect.** You show: -```prisma -model Post { tags Tag[] } -model Tag { posts Post[] } -model PostTag { ... } -``` -Nothing in `Post` or `Tag` mentions `PostTag`. How does Prisma know `tags Tag[]` goes through `PostTag` and not some other join model? If two join models exist between the same pair, what do I type? Also, neither `Post` nor `Tag` has an `id` in this snippet, yet `PostTag` references `[id]` on both. - -**2. `temporal.updatedAt()` — what is the field's TypeScript type?** Is it a `DateTime`? Can it be `?`. Can I set it by hand on a write? Are there other `temporal.*` calls? Nothing. - -**3. Plain `String` — what column do I get?** The page explains `VarChar(35)` and says "The `@db.` attributes are gone," but never says what plain `String`, `Int`, or `DateTime` map to on PostgreSQL. That is the first thing I need, because most of my fields are plain `String`. - -**4. One-to-one has no code.** "make the other side singular instead of a list, and put `@unique` on the foreign-key field" — every other relation shape gets a snippet; this one is the easiest to get wrong and gets prose only. - -**5. `native_enum` has loose ends.** Can I write `@default(...)` on a `pg.enum(Role)` field? Do I write the member name or the quoted value? Is `Role` usable as a named type in `types`? Is there a `mongo.enum`? - -**6. Lists of value objects on PostgreSQL.** You say "a value object field is stored in a single `jsonb` column." Is `addresses Address[]` also one `jsonb` column holding an array? You show the field in the example and then do not cover it. - -**7. Variants on MongoDB and the `variant()` call.** `db.orm.public.Task.variant('Bug')` — `public` is the PostgreSQL schema. On MongoDB, what goes in that slot? I am left unable to type the MongoDB version of the line you just showed me. - -**8. Where does `// use prisma-8` have to be?** "Keep the first line" — must it be line 1 exactly? What if I want a licence comment above it? And this is explained 80 lines *after* the first time it appears in code. - -**9. Adding an extension pack.** The config snippet shows `extensions: [pgvector]`, but not the `npm install` line. The page says "Using extensions covers installing a pack" — but I am already here, mid-task, and cannot finish without leaving. - -**10. `types` versus `type`.** `types { ... }` declares aliases; `type X { ... }` declares a value object. Those are one character apart and mean entirely different things, and the page never points that out. I will type the wrong one. - -## Places that explain the internals when I only wanted to know what to do - -- "Generated defaults such as `@default(uuid())` are produced by Prisma ORM before each write, not by the database." Tell me the consequence I care about: a row inserted by raw SQL or another application will not get a value. -- "`npx prisma contract emit` resolves extension types through the packs listed in the config, so list the pack before using its types." The first clause is machinery. Only "list the pack before using its types" is an instruction. -- The two `PSL_JUNCTION_*` error codes. Useful if I am already broken, but they sit in the middle of the how-to, ahead of the sentence that tells me which relation shape to pick. -- "In `pg/text@1`, `pg` is PostgreSQL and `text` is the column type, so the values are stored as `text`." Fine, but it is the decoding of a string I was going to copy anyway. - -## Could I do what the page is for, after one reading? - -Partly. I could write a plain model with fields, IDs, `@map`, `@@map`, `@unique`, `@@index`, and a one-to-many relation, and I could write the config file. That is the majority of what I need, and I got it. - -I could **not** do these without a second pass or a guess: - -- A many-to-many relation. The example does not show how the two list fields find the join model, so I would type it and hope. -- A one-to-one relation. No code shown. -- `temporal.updatedAt()`. I do not know the resulting type or whether I can write to the field. -- Any base model and variant on MongoDB, or the `variant()` query on MongoDB. -- Choosing a column type for ordinary fields, because the page never says what plain `String` becomes. -- A `native_enum` with a default. - -I also finished unsure of two words the page leans on the whole way through: what exactly a "contract" is as opposed to a schema, and what a "discriminator" is. Both are used before they are explained, and "discriminator" is never explained at all. - -Page read: `/Users/wmadden/Projects/prisma/web/.claude/worktrees/error-reference-follow-ups-81a8e7/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx` \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/psl-syntax.round4.md b/docs/orm-docs-audit/c21-contract-authoring/psl-syntax.round4.md deleted file mode 100644 index 1e97a96ec33..00000000000 --- a/docs/orm-docs-audit/c21-contract-authoring/psl-syntax.round4.md +++ /dev/null @@ -1,78 +0,0 @@ -I read the page once, top to bottom, as a Prisma 7 user who has never seen version 8. - -## Sentences I could not restate after one reading - -**Line 14** — "base models and variants: store several kinds of record in one table. A base model holds the shared fields, each variant adds its own, and one column says which kind each row is." -Three new ideas in one bullet, and "variant" is a word this page invents without warning. Plainer: "Base models and variants: one table can hold more than one kind of record. Put the shared fields in a base model, put the differences in each variant, and Prisma uses one column to tell them apart." - -**Line 137** — "`@updatedAt` is gone. Instead of a type, write `temporal.updatedAt()` as the field's type." -This contradicts itself in one sentence: "instead of a type" and "as the field's type." I had to read it three times. Plainer: "`@updatedAt` is gone. Write `temporal.updatedAt()` where the field's type would go." - -**Line 145** — "The other member is `temporal.createdAt()`." -"Member" of what? I guessed `temporal` is a namespace with exactly two entries. The sentence also never tells me what `temporal.createdAt()` does or how it differs from `createdAt DateTime @default(now())`, which the page's own example on line 45 uses. - -**Line 188** — "In `pg/text@1`, `pg` is PostgreSQL, `text` is the column type, and `@1` is the version, always `@1` today." -Version of *what*? The encoding? The type? The package? I still do not know, and "always `@1` today" tells me it will change without telling me what changes. - -**Line 190** — "Leave it out and Prisma ORM picks the type from the member values: bare member names and string values give the database's text type, and integer values give its integer type." -Two rules compressed into one clause, and it does not say what happens if I mix string and integer values, which the sentence structure invites me to try. - -**Line 252** — "Each foreign key references every field of that side's `@id`, and its own `@@id([...])` lists exactly those two foreign-key fields." -"Every field of that side's `@id`" made me stop. I think it means composite primary keys, but "each foreign key" is singular-plural in the same breath. Plainer: "If a side has a composite primary key, the join model must have one foreign-key field for each part of it. The join model's `@@id([...])` must list exactly the foreign-key fields, and nothing else." - -**Line 275** — "Put the same `@relation("name")` on the list field and on `PostTag`'s field that points back at it." -`PostTag` has two relation fields, `post` and `tag`. "The field that points back at it" does not tell me which one, and "it" could be the list field or the model. No example is shown for the fix, only for the thing that breaks. - -**Line 306** — "On PostgreSQL, whether you write `@@map` on the variant decides where its fields are stored." -I could restate the words, but I did not believe them. `@@map` is a naming attribute everywhere else on this page. Here its presence or absence silently changes the physical storage layout. That is a large consequence hidden behind a rename attribute, and the page states it as if it were obvious. - -## Words and phrases I had to guess - -- **"named types"** (line 11) — guessed: a type alias. Confirmed later at line 165, but the `types { ... }` block appears in the example at line 24 before anything explains it. -- **"value object"** (line 13) — guessed: an embedded struct. The page explains it, but the term itself is not one I brought with me. -- **"variant"** — guessed: subclass. -- **"discriminator"** (line 281) — I know this term from ORMs, but the page defines it inline, which was fine. -- **"extension pack"** (line 312) — guessed: a plugin. The page says "npm package that adds field types," which helped. -- **`// use prisma-8`** (line 22) — on first sight I guessed this was a required directive that the parser reads. The explanation is 80 lines later at line 104, and says it is only for the editor extension. By then I had already assumed it was mandatory. -- **`Uuid`** (line 43) — guessed a built-in type. It is never introduced. It just appears in the first example and is used everywhere after. Line 173 mentions it in passing as a "native PostgreSQL type." -- **`db.orm.public.Task`** (line 304) — guessed `db.orm` is the generated client and `public` is a schema segment. The page explains `public` and nothing else. -- **`/control`** in `@prisma/orm-extension-pgvector/control` (line 320) — no guess. I do not know why it is not the package root. -- **"the two files"** (line 104) — guessed `contract.json` and `contract.d.ts`. - -## Places I asked "so what do I actually type?" and got no answer - -1. **How does `temporal.updatedAt()` actually behave?** The page never says the field updates automatically on every write. It only says "you can still set the field yourself on a write." It assumes I already know what `@updatedAt` did. Can I write `temporal.updatedAt()?`? Can I add `@map`? No example shows either. -2. **Which native types can I write?** Line 129 says "a PostgreSQL type such as `VarChar(35)` or `Timestamptz`." Such as which others? There is no list and no link to one. I do not know if `Numeric(10,2)` or `Inet` work. -3. **How do I insert a variant row?** Line 304 shows how to read `Bug` rows. It never shows how to create one. That is the first thing I would try. -4. **What does a variant with no `@@map` look like?** Line 306 describes it in prose; the only example given has `@@map`. -5. **The ambiguous-relation fix.** As above: no code for the `@relation("name")` resolution, only for the failure. -6. **The second `prisma.config.ts`** (line 318) has `extensions` but no `db` key, and no `import 'dotenv/config'`. Am I replacing my config or adding a line to it? I would have guessed wrong. -7. **Does `native_enum` create the PostgreSQL type for me?** Line 192 to 205 tells me how to declare it and how to type the field. It never says whether `db init` or `migration plan` will run `CREATE TYPE`, or whether I must make it myself first. -8. **Is `types { }` available on MongoDB?** The PostgreSQL tab has one, the MongoDB tab does not, and nothing says whether that is a limitation or just a shorter example. -9. **Can `types { }` appear more than once, and does declaration order matter?** Not addressed. -10. **What replaces `datasource`'s other settings?** Line 17 says the block is gone and points at a migration page. Fine as a pointer, but `provider`, `shadowDatabaseUrl`, and `relationMode` all vanish with no local hint. -11. **How do I get any of this into a database?** The answer is the last bullet on the page, line 360. After writing a contract I ran `contract emit` and had nothing in Postgres. - -## Places the page explains internals when I wanted instructions - -- **Line 134** — "Generated defaults such as `@default(uuid())` are produced by Prisma ORM before each write, so a row written by raw SQL or another application gets no value." The consequence is worth knowing. "Produced by Prisma ORM before each write" is how it works inside; "the database will not fill this in for you" is what I needed. -- **Line 226** — "On PostgreSQL a value object field is stored in a single `jsonb` column, and a list such as `addresses Address[]` is one `jsonb` column holding an array. On MongoDB it is an embedded document." Three storage details when my question was "can I put a struct on a model, yes or no." -- **Line 252** — "Prisma ORM matches a list field such as `tags Tag[]` to the one model that holds a foreign key to each side." This is the matching algorithm. What I wanted was: "write the join model like this, and here are the two rules it must follow." The rules do follow, but the algorithm comes first. -- **Line 104** — "The Prisma editor extension reads it to tell that the file is a Prisma ORM 8 contract." Tooling internals. "Keep this line at the top" would have done, and would have answered it at line 22 where I first asked. - -## Could I do what the page is for, after one reading? - -Partly. I could write a contract file with models, fields, IDs, an enum, a value object, a one-to-many, and a one-to-one, and I could point `prisma.config.ts` at it. That much the page teaches cleanly, and the two-tab PostgreSQL/MongoDB examples are the best part of it. - -What I would still not know: - -- Whether `temporal.updatedAt()` updates the field by itself. This is the single worst gap, because the page treats the behavior as already known and only documents the syntax change. -- Which native column types I am allowed to write. I have two examples and no list. -- How to create or write a variant record, only how to read one. -- Whether `native_enum` produces the PostgreSQL type or expects me to. -- How to fix `PSL_AMBIGUOUS_BACKRELATION`, because the instruction names a field I cannot identify. -- What the `@1` in `pg/text@1` versions, and when it would become `@2`. -- Whether the extension config example replaces my config or adds to it. -- That I need `db init` to see a table, until the last line of the page. - -Two smaller things. Line 9 says "Prisma ORM 8 adds five things," then the second bullet is "enums: unchanged, except that an enum can now say how its values are stored" — an unchanged feature is not an addition, and it made me wonder if I had misread the list. And the first code block uses `types { }`, `@@type("pg/text@1")`, `Uuid`, and `// use prisma-8` before any of the four is explained, so my first read of the page's centerpiece example was mostly guessing. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/the-contract-artifact.round1.md b/docs/orm-docs-audit/c21-contract-authoring/the-contract-artifact.round1.md deleted file mode 100644 index 971ec47194b..00000000000 --- a/docs/orm-docs-audit/c21-contract-authoring/the-contract-artifact.round1.md +++ /dev/null @@ -1,95 +0,0 @@ -Reader review of `/Users/wmadden/Projects/prisma/web/.claude/worktrees/error-reference-follow-ups-81a8e7/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx` - -## Sentences I could not restate after one reading - -**Line 9** — "writes `contract.json` and `contract.d.ts` beside the source by default" -Stopped by "the source." Which file is the source? A `.prisma` file? A `.ts` file? Where does it live? The whole page hangs off a noun it never names. Plainer: "writes both files next to your schema file (`prisma/schema.ts` or `prisma/schema.prisma`)." - -**Line 13** — "Read by `db`, the migration tooling, the database checks, and anything else that needs to read your schema" -`db` appears here as if I already know it. I guessed it was the client object, and only got confirmation 120 lines later at line 133. "The database checks" is also vague — which checks? Name the commands. - -**Line 30** — "and the value sets behind enums" -"Value sets behind enums" made me stop. I guessed it means the list of allowed values for each enum. Say that. - -**Line 32** — "`profileHash` | The database this contract targets, and its family" -I do not know what "family" means. I guessed from `targetFamily: "sql"` further down that "family" means SQL vs MongoDB, but the table asks me to understand it 6 lines before the example shows it. - -**Line 34** — "[`prisma db sign`](/cli/db-sign) checks that the database satisfies the contract, then records `storageHash` and `profileHash` in a small table it creates in the database." -Two problems. "Sign" made me expect cryptographic signing; it is actually "stamp the hashes into a table." And "a small table it creates" — what is it called? Will it show up in my migrations? Does it need permissions? Nothing. - -**Line 34** — "`db` makes the same comparison before its first query, but it logs a warning and carries on." -I could restate it, but it is said again in full at line 145. One of the two is redundant. - -**Line 38** — "The contract separates what your application models from how it is stored." -"What your application models" reads as a noun phrase on first pass ("the things your application models") before I realised "models" is the verb. Rewrite: "The contract keeps your application's view of the data separate from the way the database stores it." - -**Line 38** — "On PostgreSQL the namespace is the schema, `public` unless you set one." -Set one where? In PSL? In TypeScript? In the connection string? The page does not say, and does not link anywhere that does. - -**Line 105** — "**`roots`**: each table name mapped to the model stored in it. The SQL query builder is keyed by those table names, as in `db.sql.public.user`, and the model name is what `db.orm.public.User` uses." -Too many new things in one bullet: `roots`, "SQL query builder", `db.sql.*`, `db.orm.*`. None of `db.sql` or `db.orm` has been introduced anywhere on this page, and neither is linked. I could not restate what `roots` is *for* — only what it contains. - -**Line 106** — "its type id, such as `pg/text@1`, the PostgreSQL type plus a version, always `@1` today" -The prose calls it a "type id" but the JSON key is `codecId`. I had to work out these are the same thing. And "always `@1` today" left me wondering whether I ever need to care. - -**Line 109** — "collected from the adapter for your database and any extension packs when you run `prisma contract emit`" -"Extension packs" is new and undefined and unlinked. I guessed it means optional plugin packages. Also, the bullet covers both `capabilities` and `extensions` but only explains `capabilities`; `extensions: {}` is never described. - -**Line 113** — "It exports the contract type, branded hash types matching `contract.json`, and input/output types for every model" -"Branded hash types" stopped me cold. I guessed, from the code block, that the hash string is baked into the type so two different contracts cannot be mixed up. Line 129 then explains it properly — so the jargon at 113 buys nothing. Also "the contract type" is unnamed here; I only learn it is called `Contract` from the import on line 137. - -**Line 129** — "TypeScript catches a mismatched contract before `prisma db verify` has to." -Restatable, but I do not know what "mismatched" means in practice — I regenerated one file but not the other? I upgraded the CLI? What actually triggers this in a real day? - -**Line 149** — "A CI job can enforce this by running the command and failing if the working tree changes." -I know what it means, but not what to write. See below. - -## Words and phrases I had to guess - -- **"contract"** — the central word of the page and of the URL, never defined here. I guessed it means the whole schema definition, compiled. If there is a page that defines it, this page should link to it in the first sentence. -- **`db`** — guessed: the client instance, the Prisma 6 `PrismaClient` equivalent. -- **"sign" / "verify"** — guessed: record the hashes, and check the hashes. -- **"family"** — guessed: SQL vs MongoDB. -- **"codecId"** — guessed: an identifier for how a value is converted between the database and TypeScript. -- **"branded"** — guessed: a type that carries a literal string so mismatches fail to compile. -- **"extension packs"** — guessed: optional add-on packages. -- **"roots"** — guessed: the entry points you address models through. -- **`CodecTypes`, `StorageHashBase`, `ProfileHashBase`** — appear in the excerpt with no explanation. Guessed they are internal helper types I never write myself. -- **"the database checks"** (line 13) — guessed: `prisma db verify`. - -## Places I asked "so what do I actually type?" - -1. **Line 9.** What is the command's argument, and where do I run it from? The page says "beside the source by default" — implying a flag exists to change it — and never shows the flag or the default path. -2. **Line 38, namespaces.** "`public` unless you set one." How do I set one? No syntax, no link. -3. **Lines 135–143, `db.ts`.** This is the one code block I would actually copy, and it is incomplete as a mental model: `postgres({ contractJson })` takes no connection string and no URL. Where does the database URL come from? Environment variable? A default name? I would get stuck here. -4. **Line 137.** `import type { Contract } from "./contract.d";` — importing a path ending in `.d` is unusual enough that I would assume it was a typo and change it. If it is deliberate, say so. -5. **Line 113, input/output types.** I am told input and output types exist for every model. What are they named? I can infer `AddressOutput` from the example, so presumably `AddressInput`, but I am guessing at the API surface I have to type every day. -6. **Line 149, the CI job.** "Running the command and failing if the working tree changes" — show it. Two lines would do: `prisma contract emit` then `git diff --exit-code prisma/`. -7. **Line 34.** When do I run `db sign`? Once at setup? After every migration? The page tells me what it does, never when I do it. - -## Places that explain internals when I wanted to know what to do - -- **The whole "The same source always produces the same two files" section (lines 18–22).** Byte-identical output, fixed key order, normalized values — that is how the tool works. What I need to act on is one sentence: do not read `process.env`, `Date.now()`, or random values in your schema source. The section links elsewhere for the actual rules, so the explanation here is background I cannot use. -- **"The content hashes" (lines 24–34).** Three hashes, what each covers, what makes each change. As a reader I do not compute or compare these; the tool does. The actionable part is the last paragraph. The table is the tool's internals. -- **Line 108** — "These are not column defaults in the database." A true internal detail, but it raises a practical question it does not answer: if I insert with raw SQL, do I get no default at all? -- **Line 20** — "Keys are written in a fixed key order and values in a normalized form, which is what lets you read the files in a code review and hash them." The justification chain (fixed order → hashable) is engine reasoning. "You can read these diffs in a code review" is the part that matters to me. - -## Inconsistencies that cost me a re-read - -- The JSON block is titled `prisma/contract.json`, but `db.ts` imports `./contract.json` from `src/prisma/db.ts`, so the file is at `src/prisma/contract.json`. Two different locations for the same file in the same page. -- The `contract.json` example is `User` and `Post`. The `contract.d.ts` example is `Address`, with fields that appear nowhere else. I spent a moment looking for the `Address` model before concluding it is an unrelated excerpt. -- Line 105 refers to `db.sql.public.user` and `db.orm.public.User` — two APIs introduced in a parenthetical, in a bullet about a different topic. - -## Could I do what the page is for? - -The page's job is to tell me what is in these two files and what reads them. Partly, yes: I could open a `contract.json` in a code review and roughly follow `domain` versus `storage`, and I would know not to hand-edit either file and to commit both. - -What I would still not know after one reading: - -- What "contract" means as a concept, which is the page's own subject. -- Where the source file lives, what it is called, and what `prisma contract emit` actually takes as arguments. -- How to finish `db.ts` — specifically how the database connection is configured. This is the block I would copy, and it would not work. -- When to run `db sign` versus `db verify` in a real workflow, and what the table `db sign` creates is called. -- What `db.sql` and `db.orm` are, though the page uses both. -- How to set a namespace other than `public`. -- What the input types are called, when the page tells me they exist. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/the-contract-artifact.round2.md b/docs/orm-docs-audit/c21-contract-authoring/the-contract-artifact.round2.md deleted file mode 100644 index 44282190921..00000000000 --- a/docs/orm-docs-audit/c21-contract-authoring/the-contract-artifact.round2.md +++ /dev/null @@ -1,88 +0,0 @@ -Read once, top to bottom, as someone coming from Prisma 6. - -## Sentences I could not restate after one reading - -**1. Line 13, the `contract.json` row** - -> "The machine-readable contract: models, how they are stored, which database features they need, and the hashes" -> Read by: "`db`, `prisma db sign`, `prisma db verify`, and `prisma migration plan`" - -Three of those four are commands I type. `db` is not. I read the whole row assuming `db` was a fourth command before line 16 told me it is a client object. Put line 16 before the table, or write "your `db` client". - -**2. Line 36 — one paragraph, six separate facts** - -> "The hashes are how Prisma ORM connects the contract to a live database. `npx prisma db sign` checks that the database satisfies the contract, then records `storageHash` and `profileHash` in a table it creates called `prisma_contract.marker`. You never write to that table yourself. Run `db sign` when the live database already matches the contract, for example after `prisma contract infer` on an existing database, or after a deployment applied the changes. `npx prisma db verify` compares the contract you hold against that record and against the live schema, and fails when they disagree." - -What stopped me: two new commands, a new table, a third command (`contract infer`) I have never heard of, and a conditional rule about when to run `db sign`, all in one block. I also could not work out whether `db sign` is something I run routinely or only in the two listed situations. Plainer: split it. "`prisma db sign` records the hashes in the database. Run it after X or after Y. `prisma db verify` compares them later and fails if they differ." - -**3. Line 40** - -> "Each model's own `storage` block joins the two." - -"Joins" how? I had to scroll to the JSON and work out from `"storage": { "fields": { "email": { "column": "email" } } }` that it means "maps each field to its column". Say that. - -**4. Line 107** - -> "**`roots`**: the table name of every model, so the query APIs know what to call each one." - -This contradicts the JSON directly above it. In the JSON, `roots.user` has value `{ "model": "User", "namespace": "public" }` — the value is the *model* name, and `user` is a key. Is `user` the table name or a lowercased alias? The `storage` section separately says `"table": "user"`. After one reading I do not know whether `roots` holds table names, lowercased model names, or something else. - -**5. Line 110** - -> "They are not column defaults in the database, so a write that does not go through Prisma ORM gets whatever the column itself defaults to." - -I can restate this, but it is the single most surprising behaviour change from Prisma 6 on the page and it is the last clause of a bullet. In Prisma 6 I thought of `@default(uuid())` as belonging to the schema. "If something else writes to this table, it will not get a UUID" deserves its own sentence, or a warning. - -**6. Line 137** - -> "Because the hash string is part of the type, files from two different runs of `npx prisma contract emit` do not type-check against each other." - -Two files from two runs — which two? I eventually decided it means "a `contract.json` from one run and a `contract.d.ts` from another". The next sentence says that, so the first one is a harder restatement of the second. Cut it. - -**7. Line 155** - -> "Before its first query, `db` reads the record `prisma db sign` wrote in the database and compares it with the contract's hashes. If they do not match, it logs a warning and runs the query anyway." - -Not the wording, the gap: what if `db sign` was never run and there is no record? Does it warn on every app start? I do not know after one reading. - -## Words and phrases I had to guess - -- **"contract"** — the whole page rests on it and never defines it. I guessed: the new name for `schema.prisma`, plus type information. The link on line 9 goes elsewhere. -- **"artifact"** (`metaTitle`, the URL, the heading "How the application consumes the artifacts") — guessed: "generated file". The word never appears in the body text as a defined thing. -- **"codecId"**, `"pg/text@1"` — line 108 says it is "the PostgreSQL type plus a version". So what is a codec? I guessed: the thing that converts a database value to a JavaScript value. Never stated. -- **"namespace"** — I guessed it means "PostgreSQL schema", which line 40 half-confirms, but then "On MongoDB it describes collections" leaves what a namespace is there unsaid. -- **"extension pack"** (lines 111, 112) — guessed: an npm package adding types and database features. "composed in your project" — I guessed "installed and listed in config". -- **"`type` block in your contract"** (line 120) — guessed: a reusable struct in PSL, like `type Address { ... }`. Never shown on this page, yet `AddressOutput` is the example. -- **`"returning": true`** (line 99) — guessed: PostgreSQL `RETURNING`. -- **"skills"** (line 168) — guessed: prompt files for a coding agent. -- **"`profileHash`: The database this contract targets, and whether it is SQL or MongoDB"** — I guessed "profile" is just a name for that pair. Fine, but the word does nothing. -- **"`prisma_contract.marker`"** — guessed: a table called `marker` in a schema called `prisma_contract`. - -## "So what do I actually type?" and the page does not say - -- **Where does `capabilities` come from?** Line 111: "from any extension packs named in your config". Which config file? What does the entry look like? No file name, no example, no link. -- **Do I run `prisma db sign` on a brand-new project?** Line 36 lists two situations (after `contract infer`, after a deploy). Line 176 says to use `prisma db init` for a fresh database. Whether `db init` signs for me is not stated. -- **Where do I put `prisma db verify`?** Line 155 says run it "when you need the check to fail instead" — instead of a startup warning. In CI? A start script? The Version Control section gives a CI snippet for `contract emit` but not for `verify`. -- **What do I do with `StorageHash`, `ProfileHash`, `FieldOutputTypes`, `FieldInputTypes`?** They are listed as exports with no example of importing or using one. `FieldOutputTypes` gets "keyed by namespace and model name" and nothing else. -- **How do I fix a mismatch?** If `db` warns at startup that hashes differ, or `db verify` fails, the page never says which command repairs it. - -## Places explaining how it works inside when I wanted to know what to do - -- **"Inside `contract.json`", lines 38–112.** Sixty lines of JSON plus seven bullets explaining every section of a file I was told on line 18 never to edit. Nothing in this section tells me to do anything. The parts I would actually use — that `db.sql.public.user` is keyed off `roots` and `db.orm.public.User` off the model name — are buried in the `roots` bullet. -- **"The content hashes", lines 26–36.** The section opens with "You never compute or compare them yourself" and then gives me a three-row table of hash names and what each covers. If I never touch them, the table is internals. The useful part is the one paragraph about `db sign` and `db verify`. -- **Line 137:** "`CodecTypes`, `StorageHashBase`, and `ProfileHashBase` are helper types the file declares for itself. You never write them." Telling me the names of three things I must never use is internals. -- **Line 155:** "The import path `./contract.d` is written that way on purpose. It is not a typo, and TypeScript resolves it to `contract.d.ts`." This one earns its place — I would have "fixed" it. - -## Could I do what the page is for after one reading? - -The page is explaining two generated files, not walking me through a task, so partly yes. I came away knowing: I edit the source, not these two files; I run `npx prisma contract emit` after every source change; I commit both files; and I can add the two-line CI check on lines 162–163. That is real and I could act on it today. - -What I still would not know: - -- What a "contract" is, in one sentence. The page assumes it. -- The order of operations on a real project. `contract emit`, `db init`, `db sign`, `db verify`, `contract infer`, `migration plan` — six commands are named and the page never places them on a timeline. -- Whether my Prisma 6 habits still apply. `PrismaClient` is now `db`; `@default(uuid())` is now applied by the client, not the column. Both changes are mentioned in passing. Nothing else is mapped. -- What a codec, an extension pack, or a `type` block is, or where the config file that names extension packs lives. -- What to do when the hash check fails. - -Biggest single fix: define "contract" in the first paragraph, and move the six commands into one short ordered list, before the tour of the JSON. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/the-contract-artifact.round3.md b/docs/orm-docs-audit/c21-contract-authoring/the-contract-artifact.round3.md deleted file mode 100644 index ca29fd3dc00..00000000000 --- a/docs/orm-docs-audit/c21-contract-authoring/the-contract-artifact.round3.md +++ /dev/null @@ -1,74 +0,0 @@ -Read once, top to bottom, as a Prisma 6 user who has never seen this version. - -## Sentences I could not restate after one reading - -**Line 9:** "Your contract, [the one description of your data and how it is stored](/orm/contract-authoring/the-data-contract), is one source file" - -"the one description" stopped me. I could not tell if "one" means "single" or is the start of a phrase like "the one that...". Plainest version: "Your contract is a single file that describes your data and how it is stored." - -**Line 25:** "Applying a migration records which contract the database now matches, so you do not sign it yourself afterwards." - -Two problems. "Records" where? Nothing has told me yet that there is a table anywhere. And "sign" has not been defined at this point — it first appears as a bare command name in the table on line 13. I only learned what signing is twelve lines later, on line 37. Plainest version, if it moved after the hashes section: "Migrations write the marker row for you. Only run `prisma db sign` when the database already matches the contract but no marker row exists." - -**Line 35:** "`executionHash` covers the defaults Prisma ORM applies before a write." - -I can restate the words but not the point. Line 37 then says signing records `storageHash` and `profileHash`. So `executionHash` is described and never used. I finished the page not knowing what reads it or what breaks when it changes. - -**Line 110:** "This is the section the migration tooling compares between versions and `prisma db verify` checks the live schema against." - -Two subjects, two verbs, and a dangling "against" at the end. I had to re-read to work out that `db verify` compares this section to the live schema. Plainest version: "Migration tooling compares this section between two contract versions. `prisma db verify` compares it to the live database." - -**Line 120 and 137:** "One type per hash, such as `StorageHash` and `ProfileHash`. The hash string is part of the type." / "If you regenerate `contract.json` and not `contract.d.ts`, TypeScript catches it before `prisma db verify` has to." - -I cannot picture how this works. Both files come out of one command. How do I ever end up with one regenerated and not the other? And what does TypeScript actually compare the hash type against, given the `.json` is plain data? The page states the outcome and skips the only part I would need to believe it. - -**Line 155:** "The import path `./contract.d` is written that way on purpose. It is not a typo, and TypeScript resolves it to `contract.d.ts`." - -I believe you, but "on purpose" for what purpose? The sentence defends the spelling without saying why the ordinary `./contract` does not work. If I tidy it up later, I will not know what I broke. - -## Words and phrases I had to guess - -- **"contract"** — guessed: the new name for `schema.prisma`. The page never says so plainly, and I am a reader coming from the old version who needs that one line. -- **"profile"** in `profileHash` — guessed: the database target, from "covers which database the contract targets". But "profile" appears nowhere else, so I do not know what the noun refers to. -- **"codecId"**, `pg/text@1` — guessed: an identifier for the type-conversion code, with a version number. Line 109 says "the PostgreSQL type plus a version", which does not explain the word "codec". -- **"marker"**, `prisma_contract.marker` — guessed: a one-row bookkeeping table recording which contract the database matches. -- **"sign"** — guessed: write that marker row. Confirmed only at line 37, long after first use. -- **"extension pack"** (line 112, 113) — guessed: an npm package that adds database types. Never defined here, and the link is labelled "extension types", which is a different phrase. -- **"roots"** — guessed: the entry points of the client API. The key/value direction ("Each key is the table name, and its value names the model") took two readings against the JSON. -- **`StorageHashBase`, `ProfileHashBase`, `CodecTypes`** (lines 126–131) — appear in the example code and are not in the list of exports above them. I guessed they are internal types I never touch. -- **`prisma contract infer`** (line 25) — guessed: the replacement for `prisma db pull`. -- **"targetFamily": "sql"** — guessed from the bullet, fine. - -## Places I asked "so what do I actually type?" - -1. **How do I get a `contract.prisma` at all?** Line 9 assumes it exists. Line 9 says `prisma orm init` writes `db.ts`. Line 168 says `npm create prisma@latest`. Line 168 also says `npx prisma skills sync`. Three commands, no statement of which one starts a project. -2. **`DATABASE_URL`** (line 155): "The connection string comes from the `DATABASE_URL` environment variable." The code imports `dotenv/config`, so presumably a `.env` file. The page never says to create one or what goes in it. -3. **The JSON import** (line 147): `import contractJson from "./contract.json" with { type: "json" }`. That syntax needs specific `tsconfig.json` and Node settings. If it fails to compile, this page gives me nothing. It is generated code, but the page is the one showing it to me. -4. **`namespace auth { }`** (line 41): mentioned in a subordinate clause with no example and no link to a fuller example. -5. **`extensions: [...]` in `prisma.config.ts`** (line 112): named twice, never shown. -6. **"Run `npx prisma db sign` only when the database already matches the contract and nothing recorded it"** (line 25): how do I check whether anything recorded it? `db verify`, presumably, but the page does not say to run it first. -7. **Making the startup check fail instead of warn** (line 155): "Run `prisma db verify` when you need the check to fail instead." That is a separate CLI command, not something my app can do at boot. Is there an option on `postgres({...})` for this? Not said either way. -8. **`AddressInput` / `AddressOutput` and `FieldInputTypes` / `FieldOutputTypes`** (lines 121–122): I am told they exist and what they hold. Not one line of code using them. - -## Places that explain the internals when I only wanted to know what to do - -- **The whole "Inside `contract.json`" section** (lines 39–113), about 75 lines, for a file that line 16 tells me never to edit. I read a field-by-field walkthrough of a generated file before I was told anything about writing one. -- **"The same source always produces the same two files"** (lines 27–31). Byte-for-byte reproducibility is a property of the tool. The one thing here I can act on — do not read env vars, the clock, or randomness in your contract file — is a single sentence buried in the middle, and it is punted to another page. -- **"The content hashes"** (lines 33–37) opens with "You never compute or compare them yourself" and then spends three sentences describing what each one covers. If I never touch them, tell me the one fact I need: a mismatch means the database and the contract disagree, and here is how to fix it. -- **`schemaVersion`, `codecId`, `roots`, the `domain`/`storage` split** — all file-format internals. -- **Line 108** drops two entire query APIs (`db.sql.public.user` and `db.orm.public.User`) inside a bullet about a JSON key. That is a major thing to learn about, introduced as a footnote to something I do not need. - -## Could I do what the page is for after one reading? - -Partly. - -I can do the loop on lines 18–23: edit the source, run `npx prisma contract emit`, run `migration plan`, read it, `db migrate`, and `db verify` in CI. I can commit both files and add the two-line CI check on lines 162–163. That part is clear and I would get it right. - -What I would still not know: - -- What a contract actually is compared to the `schema.prisma` I have used for two years. The page assumes I already know. -- When I need `prisma db sign` in real life. The one example given is "after `prisma contract infer`", and I do not know what that command does either. -- What `executionHash` is for. -- How to write anything in the source files. This page is entirely about the output. -- What to do when `CONTRACT.MARKER_MISMATCH` shows up in my production logs. The page tells me it warns and keeps going, which sounds like something I should act on, and then sends me to a CLI command I cannot run from inside my app. -- Whether `contract.d.ts` can ever actually get out of sync with `contract.json`, given one command writes both. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/the-contract-artifact.round4.md b/docs/orm-docs-audit/c21-contract-authoring/the-contract-artifact.round4.md deleted file mode 100644 index a6366c9bc7b..00000000000 --- a/docs/orm-docs-audit/c21-contract-authoring/the-contract-artifact.round4.md +++ /dev/null @@ -1,77 +0,0 @@ -Read once, top to bottom, as someone coming from `schema.prisma` in Prisma 7. - -## Sentences I could not restate after one reading - -1. **"`prisma db sign` records in the database which contract it matches."** (line 16) - What stopped me: I cannot tell what "it" is — the database or the contract. Plainest wording I'd have understood: "`prisma db sign` writes the current contract's hashes into the database, so later commands can tell which contract that database was built for." - -2. **"Each file starts with a notice that says exactly this."** (line 16) - "This" points back at three sentences ("Both files are generated. Do not edit them. Change the source and re-run emit."). I had to guess which one is in the file header. Plainer: "Both files begin with a comment saying they are generated and must not be edited." - -3. **"Applying a migration records which contract the database now matches, so you do not sign it yourself afterwards. … `npx prisma db verify` tells you whether anything recorded it."** (line 25) - Two problems. "which contract the database now matches" is the same tangled phrasing as above. Then "whether anything recorded it" — "it" has no clear referent at all, and "anything" is so vague I had to invent a meaning ("whether any command has ever written the marker row"). Plainer: "`prisma db verify` reports whether any command has written that record yet." - -4. **The whole of line 25 as one paragraph.** It names five commands (`db migrate`, `db init`, `db verify`, `db sign`, `contract infer`) and three separate rules in four sentences. I could not hold it after one pass. It needs to be a short list: normally signing happens for you; you only sign by hand after `contract infer`. - -5. **"The key is the table name, and its value names the model that table stores."** (line 97) - The example has key `"user"` and value `{"model": "User"}`. Table name and lowercased model name are identical here, so the example cannot tell me which rule is true. If my table were `users` I would not know what the key becomes. - -6. **"Each scalar field in `domain` records whether it is nullable and its type id, such as `pg/text@1`, which is the PostgreSQL type plus a version."** (line 98) - The JSON calls this field `codecId`, not "type id", and the same column also carries `"nativeType": "text"`. I could not work out how `codecId` and `nativeType` differ, or what the `@1` version belongs to — the PostgreSQL type, or Prisma's handling of it? - -7. **"One type per hash, such as `StorageHash` and `ProfileHash`. The hash string is part of the type."** (line 106) - "Part of the type" is doing a lot of work. From the excerpt I guessed it means the hash is a string literal type argument, so two mismatched files stop compiling. The page never says that, and never says why I should care. - -8. **"Both files come from the one `npx prisma contract emit` run, so they only drift when someone commits one and not the other. When that happens, TypeScript catches it before `prisma db verify` has to."** (line 116) - I cannot restate *how* TypeScript catches it. I had to connect it back to point 7 myself. - -9. **"The client can only warn or skip this check."** (line 136) - "Skip" how? By my choice or by itself? The link to `verifyMarker` suggests I control it, but this sentence reads as a statement about behaviour, not an option. - -10. **"Keep the `.d` in the import path."** (line 134) - No reason given. It looks like a typo, and my instinct on first read was to delete it. Tell me it will break otherwise. - -## Words and phrases I had to guess - -- **"PSL"** (line 9) — never expanded anywhere on the page. Guessed: Prisma Schema Language, the `schema.prisma` syntax I already know. -- **"contract"** as a noun — guessed: the new name for my schema. The page leans on a link rather than one sentence of definition, and "contract" also shows up as `prisma_contract.marker`, `CONTRACT.MARKER_MISMATCH`, `contract emit`, `contract infer`, so I was guessing whether these are the same thing. -- **"artifact"** (metaTitle, URL) — never used in the body. Guessed: the two generated files. -- **"codecId"** — guessed: an id for the code that converts a database value to a JavaScript value. -- **"profileHash" / "which database the contract targets"** — "profile" appears nowhere else. Guessed: a fingerprint of the target database and its feature set. I could not tell whether a "profile" is something I write. -- **"namespace"** — the page says "On PostgreSQL the namespace is the schema", which helped, but then `namespaceId` appears in the JSON and I guessed it is the same value again. -- **"the adapter for your database"** (line 99) — never introduced. Guessed: the `@prisma/orm-postgres` package in `db.ts`. -- **"roots"** — guessed from the sentence alone; the name itself gave me nothing. -- **"marker"** — guessed: the row recording which contract the database matches. -- **"extension types"** (line 99) — link text only, no clue what an extension type is. - -## Places I asked "so what do I actually type?" and got no answer - -- **Choosing PSL or TypeScript.** Line 9 offers both and never says how I pick, or whether `prisma orm init` asks me. -- **`prisma.config.ts`** (line 99). It appears once, holding an `extensions: [...]` array. It is not in the file table, not among the files `orm init` creates, and there is no example of its contents. I do not know whether I already have one. -- **Import attributes.** `import contractJson from "./contract.json" with { type: "json" };` requires particular `tsconfig` settings. The page shows the line but not the config that makes it compile. This is exactly where a Prisma 7 user will get stuck. -- **MongoDB.** Mentioned twice (lines 39, 96) but the only client example is `postgres({...})`. If I am on MongoDB I do not know what to import. -- **Where the warnings go.** "it logs a warning whose `code` is `CONTRACT.MARKER_MISMATCH`" — to stdout? To a logger I configure? Nothing to type, nothing to grep for. -- **`FieldOutputTypes` / `FieldInputTypes`** (line 107). Listed as exports, with no example of importing or using them. Why are they in a "what you should know" list if I never type them? -- **Writing to another directory.** "To write the two files to another directory, see `prisma contract emit`." One flag name here would have saved a click. - -## Places that explain the inside when I only wanted to know what to do - -- **The whole "The same source always produces the same two files" section** (lines 27–29). Byte-for-byte determinism is a property of the tool. The only actionable part is the one clause about not reading env, clock, or random values, and that is itself deferred to another page. -- **"`npx prisma db sign` … records `storageHash` and `profileHash` in a table it creates called `prisma_contract.marker`."** (line 35) I am immediately told "You never write to that table yourself," which raises the question of why I was told the table name. -- **"Migration tooling compares `storage` between two contract versions."** (line 98) Internal mechanics. -- **Three separate "you never do this yourself" sentences** (lines 33, 35, 116). Each one describes machinery and then tells me to ignore it. - -## Could I do what the page is for, after one reading? - -Partly. I came away able to say what the two files are, that they are generated, that I commit them, and what my edit-emit-plan-migrate loop looks like. Lines 18–23 and the "Version control" section are the parts I could act on straight away. - -What I would still not know: - -- When I am supposed to run `prisma db sign` by hand. I read line 25 twice and still could not state the rule. -- What to do the first time I see `CONTRACT.MARKER_MISMATCH` locally, as opposed to "in production". -- Whether `contract.d.ts` being a `.d.ts` means I need any tsconfig change, and what makes `import ... with { type: "json" }` work in my project. -- What `prisma.config.ts` is, whether I have one, and what goes in it. -- Whether the `roots` key follows my table name or my model name. -- What a "codec" is, and why a column carries both `codecId` and `nativeType`. - -One more thing: the page tells me the contract "replaces the `schema.prisma` of Prisma ORM 7" and then never returns to Prisma 7 again. Coming from two years of `schema.prisma`, the single biggest unanswered question is that `schema.prisma` was the file I edited *and* the file the tooling read, whereas here those are three different files. The page shows me that structure but never says it. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/the-data-contract.round1.md b/docs/orm-docs-audit/c21-contract-authoring/the-data-contract.round1.md deleted file mode 100644 index 176478b87e7..00000000000 --- a/docs/orm-docs-audit/c21-contract-authoring/the-data-contract.round1.md +++ /dev/null @@ -1,70 +0,0 @@ -I read the page once, as a Prisma 7 user would. Findings below. - -File: `/Users/wmadden/Projects/prisma/web/.claude/worktrees/error-reference-follow-ups-81a8e7/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx` - -## Sentences I could not restate after one reading - -1. Line 31: "Completion covers field, model, and block attribute names and their argument keys, taken from the installed target and extensions." - - Stopped by: "the installed target" (a target of what? I have no idea what a target is — the word appears here and nowhere else on the page) and "extensions" (first use, undefined). "block attribute names and their argument keys" is three nested nouns. - - Plainer: "Autocomplete suggests attribute names and their arguments, based on the database package and extension packages you have installed." - -2. Line 49: "The contract also identifies the exact version of the schema it describes." - - Stopped by: the note at line 39 just told me schema means the database's actual structure. So this says the contract identifies a version of my live database, which cannot be right. Same clash at line 96 ("hashes that identify this exact version of the schema") and line 78 ("plan schema changes"). After one reading I cannot tell which meaning of "schema" is in force in any given sentence. - - Plainer: "The contract also carries a hash that identifies this exact version of the contract." - -3. Line 49: "`prisma contract emit` computes hashes over its contents." - - Stopped by: "its" — the source file's contents, or the emitted files' contents? And what do I do with a hash? - -4. Line 49: "When you run [`prisma db sign`](/cli/db-sign), Prisma ORM records which contract the database matches in a small table it creates in the database." - - Stopped by: too much at once, and no answer to when I run this. After a migration? Before deploy? Once ever? Also "a small table" — named what? It will show up in my database and I will want to know. - -5. Line 86: "Keep the other form out of the project, or treat it as a generated reference, so the two can never disagree." - - Stopped by: "the other form", "a generated reference". Generated by what? I do not know what action I am being told to take or avoid. - -6. Line 94: "named types, enums, and `type` blocks, each a structured value stored inside its parent row with no table of its own" - - Stopped by: "each" — does it apply to enums too? An enum is not a structured value. And I do not know the difference between "named types" and "`type` blocks"; they read as the same thing listed twice. - -7. Line 77: "`db` reads `contract.json` and compares its hashes with the record `prisma db sign` wrote in the database." - - Stopped by: bare `db`. Is that a CLI command group, a runtime object, a package? The other two bullets name real things (`contract.d.ts`, "the migration tooling"); this one names a two-letter thing I have never seen. - -## Words and phrases I had to guess - -- "the installed target" (line 31) — guessed: the database-specific package, maybe `@prisma/orm-postgres`. -- "extensions" (line 31) vs "extension packs" (line 95) vs "extension-provided types" (line 66) — guessed these are all the same thing. The page uses three names for it and defines none. -- "machine-readable files" (line 33) — guessed `contract.json` and `contract.d.ts`, confirmed 14 lines later. Name them where you first mention them. -- "which database features the contract needs" (line 47) — guessed things like JSON columns or pgvector. -- "The query APIs" (line 76) — guessed this is the new name for what I call PrismaClient. Plural, so possibly several. -- "contract source" (line 47) vs "contract source" declared in config (line 53) — guessed both mean the `.prisma` or `.ts` file I write. -- "`prisma contract infer`" (line 82) — guessed it reads an existing database and writes a contract for it. The page never says. -- "storage details" (line 93) — guessed: the database-level objects. - -## Places I asked "so what do I actually type?" and got no answer - -1. **I have a `schema.prisma` today.** Line 9 says `contract.prisma` "replaced `schema.prisma`". The page never tells me how to move from one to the other. Is there a command? Do I rename the file and add `// use prisma-8`? This is the single biggest gap for anyone coming from 7. -2. **Installing.** `@prisma/orm-postgres` appears in the config snippet with no install command. No `npm install` anywhere on the page. -3. **`prisma generate` is gone.** I ran it after every schema change for two years. Line 68 says run `prisma contract emit` instead, but never says `generate` is retired, so I am left wondering whether I now run both. -4. **Does anything run `emit` for me?** Line 68 says "after every change". Does `prisma migrate dev` do it? Is there a watch mode? Not said. -5. **When to run `prisma db sign`.** Mentioned twice, never placed in a workflow. -6. **Where `contract.d.ts` gets consumed.** Line 76 says the query APIs load it. No import line, no client construction, nowhere on the page. -7. **"For the cases PSL does not cover" (line 84).** Which cases? I cannot decide between the two authoring modes without knowing. And line 86 then says "You give up nothing by staying with PSL", which directly contradicts line 84. -8. **TypeScript mode config.** Line 66 says the extension selects the mode. Do I just point `contract:` at `./prisma/contract.ts`? Probably, but the page only shows the `.prisma` case. - -## Places that explain the insides when I wanted instructions - -1. Line 31, the whole language-server paragraph. I asked "what goes at the top of my file". I got diagnostics, completion internals, argument keys, and the `// use prisma-next` history from a release candidate I never used. One sentence would do: put `// use prisma-8` on the first line; editor support depends on it. -2. Line 49: "The client reads that record before its first query and logs a warning if the record does not match the contract the client holds." That is the client's startup sequence. What I need is: my app warns when the database is out of date; use `prisma db verify` in CI to fail instead. -3. Line 47: "The same source always produces the same two files, so you can read them in a code review, hash them, and hand them to tools and coding agents." This is a design rationale. Fine in "Why a contract", but it sits where I am still trying to learn what the files are. - -## Could I do what the page is for, after one reading? - -Partly. If I were starting a brand-new project I could write a `contract.prisma`, put `// use prisma-8` at the top, point `prisma.config.ts` at it, and run `npx prisma contract emit`. That much is clear. - -What I would still not know: - -- How to convert my existing `schema.prisma` to a contract. The page announces the replacement and then never addresses the upgrade. -- What to install, or what `@prisma/orm-postgres` is. -- How to query. I never see a client imported or constructed, and "the query APIs" is not a thing I can type. -- Whether `prisma generate` still exists. -- When to run `prisma db sign`, and what table it creates. -- Which situations require the TypeScript builder, given the page both names such cases and says I give up nothing by avoiding it. -- Whether "schema" means my database or my contract, in any sentence after the note at line 39. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/the-data-contract.round2.md b/docs/orm-docs-audit/c21-contract-authoring/the-data-contract.round2.md deleted file mode 100644 index e16b558f6a1..00000000000 --- a/docs/orm-docs-audit/c21-contract-authoring/the-data-contract.round2.md +++ /dev/null @@ -1,76 +0,0 @@ -Read once, as a Prisma 7 user. Report below. - -## Sentences I could not restate after one reading - -**Line 47** — "The contract also identifies its own exact version. `prisma contract emit` computes hashes from the contract and writes them into `contract.json`. [`prisma db sign`](/cli/db-sign) checks that your database satisfies the contract, then records those hashes in the `prisma_contract.marker` table, which it creates in your database. Run it once the database matches the contract, after you have applied your migrations." - -Four separate ideas in a row, and two things stopped me. "identifies its own exact version" — version of what? There is no version number here, only hashes, so the word "version" points at something I cannot find. And "Run it once the database matches the contract" is ambiguous: "once" could mean "one single time" or "as soon as". I read it both ways and could not settle it. Plainest wording I would have understood: "`prisma contract emit` writes a hash of your contract into `contract.json`. After you apply your migrations, run `prisma db sign`. It checks the database, then stores that hash in a table it creates called `marker` in the `prisma_contract` schema. Run it again every time the contract changes." - -**Line 47** — "Your app then logs a warning when the database stops matching." - -Logs where? At startup, on the first query, on every query? I cannot picture when I would see this, so I cannot tell whether it is useful to me or noise. - -**Line 45** — "Prisma ORM keeps that description in the open." - -"in the open" is a metaphor doing the work of the real claim. I think it means "in plain files you can read", which the next sentence says anyway. Plainest wording: delete the sentence. - -**Line 45** — "so you can read them in a code review, hash them, and hand them to tools and coding agents." - -"hash them" stopped me. Why would I hash a file, and what would I do with the hash? The page already says Prisma computes hashes itself (line 47), so this reads like a second, unrelated hashing that is never explained. - -**Line 84** — "Do not keep a `contract.prisma` and a `contract.ts` in the same project, so the two can never disagree." - -The "so" clause makes it sound like keeping both causes the guarantee. I had to re-read to work out the reason is "otherwise they could disagree". Plainest wording: "Keep only one. If you had both, they could describe different things." - -**Line 93** — "[`type` blocks](/…), each a structured value stored inside its parent row with no table of its own" - -I do not know what this actually becomes in my Postgres database. A `jsonb` column? A composite type? "Structured value stored inside its parent row" tells me the idea but not the thing. - -**Line 39** — "In Prisma ORM, the **contract** is what you write in your code, and the **schema** is your database's actual structure." - -I could restate it, but it contradicts the rest of the page as I read it. The file is called `contract.prisma`, the language is "PSL, the Prisma schema language" (line 11), and the card on line 118 says "Write the contract as a Prisma schema file." So "schema" is used for my authored file three times after the note tells me it means the database. That left me unsure which meaning applies where. - -## Words and phrases I had to guess - -- **"the data contract"** (line 9) — guessed: just the new name for `schema.prisma`. The page confirms this later, but the title made me expect a new concept. -- **"extension packages"** (lines 64, 94) — guessed: npm packages that add extra field types, since pgvector is the example. Never defined, and the only link is to a PSL anchor. -- **"pgvector"** and **"`Vector`"** (lines 45, 94) — guessed: a Postgres extension for vector columns. Fine if you know it; nothing on the page says what it is. -- **"`prisma_contract.marker` table"** (line 47) — guessed: a table named `marker` in a schema named `prisma_contract`. The dotted name is never explained, and it matters because it is a table appearing in my database. -- **"the migration tooling"** (line 76) — guessed: some `prisma migrate` command. The page never names one. -- **"named types"** (line 92) — guessed: aliases for scalar types. No idea, really. -- **"the typed contract builder"** (line 125) vs **"TypeScript builder"** (line 82) — guessed these are the same thing. -- **"PSL"** — expanded once on line 11 as "the Prisma schema language", then used as an unexpanded acronym. I coped, but it is the only expansion. - -## "So what do I actually type?" — unanswered - -1. **Migrations.** The page tells me three times that migrations are planned from the contract (lines 33, 47, 76) and tells me to run `db sign` "after you have applied your migrations". It never gives the migration command. This is the single biggest gap: the page describes the workflow but omits its middle step. -2. **The everyday loop.** I wanted a numbered list: edit contract → `prisma contract emit` → migrate (?) → `prisma db sign`. I had to assemble that from lines 47, 66, and 76, and I am still not sure `db sign` is needed after every change or only the first time. -3. **Starting a project.** Line 64 says `prisma orm init` writes the config, the contract, and `db.ts`. Line 80 says `npm create prisma@latest` scaffolds PSL, and line 101 says projects created that way get the skills. Two different starting commands, never compared. I do not know which one to run. -4. **Using `db`.** Line 72 says "`db` is the client you create once in `src/prisma/db.ts`" and then three bullets about what `db` does internally. It never shows the import line or a single query. After reading a page about the thing that types my queries, I cannot type one. -5. **Anything but PostgreSQL.** The only database package named is `@prisma/orm-postgres` (lines 55, 64). I use MySQL. The page does not say what I import, and does not say Postgres is just the example. -6. **TypeScript authoring.** Line 64 says point `contract` at `./src/prisma/contract.ts`, and line 82 says use the builder. There is no fragment of `contract.ts` anywhere, so I cannot tell what the mode even looks like before clicking away. -7. **`// use prisma-8` on the TypeScript side.** Line 31 says "The first line of every `.prisma` contract" must be that comment. Does `contract.ts` need something equivalent? Unanswered. -8. **The two config functions.** Line 54-55 imports both `definePrismaConfig` and `ormConfig` from two packages. I would not have guessed that shape, and nothing says why there are two or when the inner one changes. -9. **`prisma db sign` cadence.** After I change the contract and migrate, do I re-sign? Line 47's phrasing does not settle it. - -## Where it explains the internals when I wanted instructions - -- **Line 72-76**, the three bullets under "From there, the rest of the toolchain takes over." All three describe what `db` and the tooling do to the files: "`db` loads `contract.d.ts`", "`db` reads `contract.json` and compares its hashes with the record `prisma db sign` wrote". None of it changes anything I type. This is the section where I expected to learn how to use `db`. -- **Line 45**, the whole "Why a contract" opening. "Prisma ORM 7 compiles your `schema.prisma` into generated client code. The description of your data is in there, but only the generated client can read it." This is a design rationale. I came to find out how to author the file. -- **Line 31**, the language-server detail: "the Prisma language server only reports problems, autocompletes, and formats files that start with it". The instruction is "put `// use prisma-8` on line one." The mechanism behind editor support is more than I needed, and the `prisma-next` history sentence is about a release candidate I never used. -- **Line 47**, hash computation and the marker table. What I need is "run `prisma db sign` after migrating". The hashing mechanism is the answer to a question I did not ask. - -## Could I do what the page is for, after one reading? - -Partly. I could copy the `contract.prisma` example, copy `prisma.config.ts`, and run `npx prisma contract emit`. That is real. - -What I would still not know: - -- How to change my database after I change the contract. No migration command appears on the page. -- Whether to start with `prisma orm init` or `npm create prisma@latest`. -- What to import or query with. I never see `db` used. -- What to do on MySQL or SQLite. -- Whether `prisma db sign` is a one-time setup step or part of every change. -- What `contract.ts` looks like, so I cannot decide between the two authoring modes the page spends a section comparing. - -The page succeeds at telling me what the contract *is*. It does not get me through one full change to my data model. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/the-data-contract.round3.md b/docs/orm-docs-audit/c21-contract-authoring/the-data-contract.round3.md deleted file mode 100644 index ab1f076d988..00000000000 --- a/docs/orm-docs-audit/c21-contract-authoring/the-data-contract.round3.md +++ /dev/null @@ -1,90 +0,0 @@ -I read the page once, top to bottom, as a Prisma 7 user who has never seen version 8. - -## Sentences I could not restate after one reading - -**1. Line 30 — the header comment** - -> "Start every `.prisma` contract with `// use prisma-8`. Your editor's Prisma support only works on files that have that line." - -Two ideas that fight each other. "Start every" reads as a hard requirement. The reason given is only about my editor. So: does the CLI fail without it, or do I just lose syntax highlighting? What is "your editor's Prisma support" — the VS Code extension? Something else? Plainer: "Add `// use prisma-8` as the first line. Without it, the Prisma VS Code extension ignores the file. The CLI works either way." (If the CLI does *not* work either way, say that instead — that is the version I needed and did not get.) - -**2. Line 46 — the whole hash paragraph** - -> "`prisma contract emit` also writes a hash of the contract into `contract.json`. When you apply a contract change to the database, the command that applies it records that hash in a small table it creates in your database. Before its first query, `db` compares the two and logs a warning if they differ." - -Four moving parts in three sentences, and every noun is vague. "the command that applies it" — which command? (I only learn it is `prisma db migrate` thirty lines later.) "a small table it creates in your database" — named what? Do I need to know it exists when I review a migration? "Before its first query" — the first query of what, the process? per connection? "compares the two" — the two hashes, I assume, but "the two" has no clear antecedent because three things were just named. And `db` is used here as if I know what it is; the page does not tell me until line 77. - -Plainer: "`prisma contract emit` writes a hash of your contract into `contract.json`. `prisma db migrate` stores the same hash in a table in your database. The first time your app runs a query, Prisma compares the two hashes and logs a warning if they do not match." - -**3. Line 63 — the config paragraph** - -> "`definePrismaConfig` holds the whole config. `ormConfig` comes from your database package and holds the settings for that database" - -`ormConfig` does not exist. The code block imports `defineConfig as ormConfig`. So the prose names a thing that is only a local rename I made up in my own file. I stopped and re-read the import three times to confirm. Say "the `defineConfig` export from your database package, imported here as `ormConfig`." - -The same paragraph then packs in: what the two functions do, the three database packages, which one the examples use, two different init commands, and the four things those commands write. That is six unrelated facts in one paragraph. I could not hold them. - -**4. Line 75 — signing** - -> "so there is no separate signing step. Run [`prisma db sign`](/cli/db-sign) only when the database already matches the contract and nothing needs applying, for example after `prisma contract infer`." - -"Signing" is never defined. The page has never used the word before this sentence. I had to guess it means "write the hash into the database table." Also `prisma contract infer` is referenced here but not explained until line 81. Plainer: "`prisma db migrate` records the new hash for you. Run `prisma db sign` only to record the hash without applying anything — for example after `prisma contract infer`, when the database already matches." - -**5. Line 77 — `public`** - -> "`public` is the PostgreSQL schema, the namespace your tables are in, which is `public` unless you set one." - -The callout at line 38 just told me, in bold, that "the **schema** is your database's actual structure." Now "schema" means a PostgreSQL namespace. That is a third meaning for a word the page went out of its way to define. I re-read the callout to check whether I had misunderstood it. The sentence also defines `public` using the word `public` twice. Plainer: "`public` is the PostgreSQL namespace your tables live in. It is `public` unless you configured a different one." - -**6. Line 85 — the two-files warning** - -> "Do not keep both a `contract.prisma` and a `contract.ts`. If you had both, they could describe different things." - -"Could describe different things" tells me a risk, not a consequence. Does the CLI error? Silently use the one in the config and ignore the other? I could not restate what actually happens to me. - -**7. Line 94 — `type` blocks** - -> "[`type` blocks](/orm/contract-authoring/psl-syntax#value-objects), each a structured value stored inside its parent row with no table of its own." - -I understood "no table of its own" and "stored in a `jsonb` column." I did not understand what a `type` block *is for* or how it differs from a model, and the link text says "value objects," a term the sentence never uses. - -## Words and phrases I had to guess - -- **"the rest of the toolchain"** (line 32) — guessed: the CLI, the client, and my editor. Not stated. -- **"an exact, versioned record"** (line 32) — guessed: versioned as in checked into git, not as in "has a version number." The `package-lock.json` comparison is what let me guess. -- **"named types"** (line 93) — guessed: a reusable alias, from the appositive. Fine, but "reusable aliases for a type and its storage details" is itself guesswork about what "storage details" covers. -- **"capabilities"** — the link at line 44 says "which database features they need" and the card at line 135 says "Supported database features," but the URL is `/capabilities`. Guessed they are the same thing. -- **"extension packages"** (line 65) — the pgvector example saved me. Without it I had nothing. -- **"artifact"** — the section heading anchor and the card at line 128 use "artifact" for `contract.json`. The prose never does. Mild, but I noticed the mismatch. -- **"signing" / `db sign`** — guessed: recording the hash. See above. -- **"logs a warning"** (line 46) — guessed: to stdout, at app startup. Where do I see it? - -## "So what do I actually type?" - -1. **How does any of this reach my database?** The page tells me to run `prisma db migrate` but never mentions a connection string, `DATABASE_URL`, `.env`, or a `datasource` block. In Prisma 7 the connection lived in `schema.prisma`. The example contract has no datasource block at all. So where does the URL go now — `prisma.config.ts`? An env file? This is the single biggest hole. I could not run the three commands after reading this page. -2. **`npx` or not?** The code block says `npx prisma contract emit`. The prose says "run `prisma orm init`" and "run `npx prisma skills sync`." Inconsistent, so I do not know whether `prisma` is expected on my PATH. -3. **`npm create prisma@latest -- my-app`** — the bare `--` is unexplained and easy to mistype. And I am not told what it produces on disk beyond a list of four files with no directory layout. -4. **Where does `contract.json` go?** "beside the source" (line 75). So `src/prisma/contract.json`. I had to infer that from the config path. -5. **Converting my existing Prisma 7 project.** Line 34 sends me to two other pages. Fine for an overview page, but then line 63 tells me `prisma orm init` "adds Prisma ORM to a project you already have" — which is it for a Prisma 7 project? I would have run the wrong one. -6. **Is the `// use prisma-8` line something the generator writes, or something I type?** Not said. - -## Places the page explains internals when I wanted instructions - -- **Line 46, the whole paragraph.** I wanted "run these commands and your database stays in sync." I got a mechanism: a hash, a table the tool creates, a comparison before the first query. The one actionable sentence — "To fail instead of warn... run `prisma db verify`" — is buried at the end. -- **Line 44, "In Prisma ORM 7 the same description existed, but only the generated client could read it."** Interesting, changes nothing about what I type. -- **Line 85, "For the same models they write the same `contract.json`."** The useful part is "pick either, it does not matter." The identical-output detail is implementation reassurance. - -## Could I do what the page is for? - -The page is an orientation page, and on that narrow bar it mostly works. After one reading I can say what the contract is, that it replaced `schema.prisma`, that it compiles to `contract.json` and `contract.d.ts`, that I can write it in PSL or TypeScript but not both, and that the three-command loop is emit, plan, migrate. - -What I still would not know: - -- **How to point Prisma at my actual database.** No connection string anywhere. This is disqualifying for anyone trying to follow along. -- **Whether `// use prisma-8` is required or cosmetic.** -- **What "signing" is**, so I cannot tell when `prisma db sign` applies to me. -- **What happens if the hashes do not match** beyond "logs a warning" — can I still query? Is my app broken? -- **Which init command my Prisma 7 project needs.** -- **What a `type` block is for**, only where it is stored. - -The three-command block at line 69 is the best part of the page. Everything I needed to act on is either in that block or scattered around it in prose; the paragraphs between the blocks are where I got lost. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/the-data-contract.round4.md b/docs/orm-docs-audit/c21-contract-authoring/the-data-contract.round4.md deleted file mode 100644 index 12502990465..00000000000 --- a/docs/orm-docs-audit/c21-contract-authoring/the-data-contract.round4.md +++ /dev/null @@ -1,94 +0,0 @@ -Read once, top to bottom, as a Prisma 7 user who has never seen version 8. - -## Sentences I could not restate after one reading - -**1.** "`npx prisma orm init` writes the first line, `// use prisma-8`, into every `.prisma` contract. The CLI works without that line, and the Prisma VS Code extension needs it to recognize the file." - -What stopped me: the second sentence says two opposite-sounding things joined by "and". Is the line optional or not? I could not tell whether I must type it if I create the file by hand. - -Plainer: "The CLI does not need this line. The VS Code extension does, so keep it." - -**2.** "Think of it as a `package-lock.json` for your data: a file in version control that records exactly what your application expects from its database." - -What stopped me: `package-lock.json` is generated and I never edit it. The contract is a file I write by hand. The comparison told me the opposite of what I had just read. I still do not know which file the comparison points at — the contract, or `contract.json`. - -**3.** "In Prisma ORM, the **contract** is what you write in your code, and the **schema** is your database's actual structure. Some tools use these words the other way around. The language you write the contract in keeps the older name: it is PSL, the Prisma Schema Language, the same language as `schema.prisma`." - -What stopped me: three redefinitions of two words in four lines, plus an unnamed "some tools". I finished the note less sure of the vocabulary than when I started. The page already said "the same language as `schema.prisma`" in the first paragraph, so the note repeats it without adding anything. - -**4.** "`npx prisma contract emit` also writes a [hash](...) of the contract into `contract.json`, and `npx prisma db migrate` stores the same hash in your database. The first time your client `db` runs a query, it compares the two hashes and logs a warning if they differ." - -What stopped me: too many ideas in one place — two commands, two storage locations, a runtime comparison, a warning, and a link out. I also do not know what "hash" means here in practice: a fingerprint of what exactly? The contract source, or `contract.json`? - -**5.** "Run [`npx prisma db sign`](/cli/db-sign) only to record the hash without applying anything, for example after `npx prisma contract infer`, when the database already matches the contract." - -What stopped me: `db sign` and `contract infer` are both used before either is explained. `contract infer` is only explained further down, at line 83. "Sign" is never explained at all — I guessed it means "stamp the hash into the database". - -**6.** "The database settings come from the `defineConfig` export from your database package, imported here as `ormConfig`." - -What stopped me: I had to go back to the code block and match `defineConfig as ormConfig` before the sentence made sense. Say it in the direction the reader reads: "`@prisma/orm-postgres/config` exports `defineConfig`. The example renames it to `ormConfig` on import." - -**7.** "`db.orm` holds your models by model name. `public` is the PostgreSQL schema, the namespace your tables are in, unless you configured a different one." - -What stopped me: `await db.orm.public.User.all()` has four segments and the page explains two of them. What is `.orm`? Why is it there, and is there ever anything else at that position? I guessed it is a fixed namespace. - -## Words and phrases I had to guess - -- **"data contract"** — guessed: the new name for `schema.prisma`. The page confirms this but never says plainly "this is the file you used to call your schema." -- **"emit"** (`prisma contract emit`) — guessed: it means generate, like `prisma generate` in version 7. Nothing says that. -- **"hash"** — guessed: a fingerprint used to detect drift between contract and database. -- **"sign"** (`prisma db sign`) — guessed: write the hash into the database without running a migration. -- **"infer"** (`prisma contract infer`) — guessed: the new `prisma db pull`. Explained one section later than first used. -- **"capabilities"** / "database features they need" — guessed: things like extensions or column types the database must support. Only a link, no one-line definition. -- **"named types"** — "reusable aliases for a type and its storage details". I do not know what "storage details" means as something I would type. -- **"`type` blocks"** — the link text is `#value-objects` but the page never uses the phrase "value object". I guessed the two are the same thing. -- **"extension packages"** — explained, but only after the term is used twice. -- **"artifact"** — never used in the prose, but the anchor is `#two-authoring-modes-one-artifact` and a card says "the contract artifact". I guessed it means `contract.json` plus `contract.d.ts`. -- **"migration plan"** vs **"db migrate"** — guessed: plan writes the SQL, migrate runs it. The page says this, but "works out what the database needs" is vague about what lands on disk. - -## Places I asked "so what do I actually type?" and got no answer - -1. **Where does `DATABASE_URL` go?** The config imports `"dotenv/config"` and reads `process.env["DATABASE_URL"]`. The page never says to create a `.env` file or what to put in it. This is the first thing I would hit. - -2. **The three commands take no arguments here.** In version 7 I named my migrations. Does `npx prisma migration plan` prompt me for a name? Take a `--name`? The page shows a bare command and I would not know. - -3. **The very first migration.** Do the same three commands work on an empty database, or is there a different first-run path? - -4. **`npx prisma db sign`** — no flags, no example, no output shown. Same for `npx prisma db verify` and `npx prisma contract infer`. - -5. **Importing `db` from anywhere else.** "From a file directly inside `src/`, import it with `import { db } from "./prisma/db"`." I do not work only in files directly inside `src/`. Nothing tells me the alias or path to use from `src/routes/api/users.ts`. - -6. **Switching authoring modes.** "Do not keep both a `contract.prisma` and a `contract.ts`." So how do I move from one to the other? Delete the old file and change the config string? The page warns me off the wrong thing without telling me the right steps. - -7. **Seeing the hash mismatch.** "logs a warning if they differ" — what does the warning look like, and where does it appear? Without that, I will not recognize it when it happens. - -8. **Creating data.** The only query shown is `.all()`. This page is my entry point and I finish it able to read rows but not write one. A link to "Reading data" is there; nothing covers writing. - -9. **`npx prisma orm init` in an existing project.** It "writes `prisma.config.ts`, the contract file, and `src/prisma/db.ts`". What if I already have a `src/prisma/` directory or a different layout? Does it overwrite? - -## Places the page explains the insides when I only wanted to know what to do - -- **The whole "Why a contract" section.** "Your contract compiles to two plain files you can open and read" and the paragraph on hash storage. I am trying to get started. Where the hash is written and when it is compared is machinery, not a task. The one thing I needed from it — run `prisma db verify` in CI to fail the build — is the last clause of the second paragraph. - -- **"On PostgreSQL each one is stored in a single `jsonb` column."** Storage implementation for a feature I have not been shown how to write. - -- **The "Contract vs. schema" note.** It tells me that other tools use the words differently and that PSL kept its old name. Neither changes anything I type. - -- **"The same source always produces the same two files"** — this is a property of the compiler. It matters to the toolchain, not to me on first read. - -## Could I do what the page is for? - -The page is an orientation: what the contract is and how to get one working. **Partly.** - -What I could do after one read: run `npm create prisma@latest -- my-app`, recognize `contract.prisma` as my old `schema.prisma`, write a model, and run the three commands. - -What I would still not know: - -- Where my connection string lives on disk. This blocks me immediately. -- Whether `// use prisma-8` is required. -- What `emit`, `sign`, `infer`, and `verify` each do well enough to choose between them. I would run all three commands every time and hope. -- Why `db.orm.public.User` has the shape it does, so I could not guess the path for a model in a non-`public` schema even though the page mentions those exist. -- How to write data at all. -- How to get from my existing Prisma 7 project to this, since the page sends me to a different guide at exactly the point I was reading for. - -The biggest single problem: the terms **contract**, **schema**, **artifact**, **hash**, and **emit** all arrive in the first two screens, and the page defines them by relating them to each other rather than to the file I would open or the command I would type. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/typescript-schema-builder.round1.md b/docs/orm-docs-audit/c21-contract-authoring/typescript-schema-builder.round1.md deleted file mode 100644 index 69647ab9c46..00000000000 --- a/docs/orm-docs-audit/c21-contract-authoring/typescript-schema-builder.round1.md +++ /dev/null @@ -1,91 +0,0 @@ -I read the page once, top to bottom, as someone coming from Prisma 6 with `schema.prisma`. - -## Sentences I could not restate after one reading - -1. "With TypeScript authoring you write your contract, the `contract.ts` file that replaced `schema.prisma`, in code rather than in a [`.prisma` file]." - Stopped by: "your contract" is a brand-new term, and the definition is crammed in as an aside between the subject and the verb. I had to read it three times to see that "write ... in code" was the main clause. Plainer: "Prisma 8 replaces `schema.prisma` with a *contract*. You can write the contract either as a `.prisma` file or as TypeScript in `prisma/contract.ts`." - -2. "the same two files a PSL contract produces." - Stopped by: "PSL" is never spelled out anywhere on the page. It is only a link. Plainer: spell it out once — "PSL (Prisma Schema Language, the `.prisma` file format)". - -3. "parts of the contract are assembled programmatically from other static definitions" - Stopped by: "other static definitions" of what? Static as opposed to what? I cannot picture a case. Plainer: give one concrete example, e.g. "you generate models from a list of table names you already have in TypeScript". - -4. "The database and its family are already fixed by the import: `@prisma/orm-postgres/contract-builder` produces PostgreSQL contracts, so you never name them yourself." - Stopped by: "its family". I do not know what a database *family* is, and "them" has no clear antecedent. Plainer: "You never pass the database name. It comes from which package you import." - -5. "The factory receives authoring helpers composed from your database and every extension pack you declared" - Stopped by: "authoring helpers composed from" is three abstractions stacked. Plainer: "Which helpers you get depends on your database and the extensions you listed." - -6. "The two builders share the same shape but differ where the databases do." - Stopped by: this claims the two examples are the same shape, but they are not. The PostgreSQL tab calls `defineContract(options, factory)`; the MongoDB tab calls `defineContract({ models })` with no factory at all. They look like two different APIs. The sentence told me not to notice a difference I had already noticed. - -7. "`prisma contract emit` writes it to JSON in a fixed key order and hashes it, so the same source always produces the same files." - Stopped by: nothing here tells me what to do. See "internals" below. - -8. "Both builders also accept the model name as a string." - Stopped by: "both builders." Five paragraphs earlier "the two builders" meant PostgreSQL and MongoDB. Here it means `rel.hasMany` and `rel.belongsTo`. Plainer: "`rel.hasMany` and `rel.belongsTo` also accept the model name as a string." - -## Words and phrases I had to guess - -- **"contract"** — I guessed: the new name for the schema. -- **"PSL"** — I guessed: Prisma Schema Language. -- **"codecId: "pg/text@1""** — I guessed: some identifier for how a value is encoded to and from the column. I have no idea where the list of valid values lives. It appears twice in code and is never explained in prose. -- **"nativeType: "text""** — I guessed: the raw PostgreSQL type name. -- **"as const"** — I guessed it is required, not stylistic, but the page never says why. -- **"your database's target package"** — I guessed: the `@prisma/orm-*` package for your database. -- **"extension pack" / "pack"** — I guessed: a plugin. The contract imports `.../pack` and the config imports `/control`, with no explanation of why the same extension has two entry points. -- **"declared named types"** — I guessed: things like the pgvector `Vector(1536)` you put in the `types` map. -- **"emitted"** — I guessed: written into `contract.json`. -- **"contract infer"** — I guessed: reads an existing database and writes a `.prisma` file. -- **"the two files"** — used in Next steps without repeating which two. - -## Places I asked "so what do I actually type?" and got no answer - -1. **The full list of field helpers.** The page shows `field.text()`, `field.uuidString()`, `field.json()`, `field.id.uuidv4String()`, `field.temporal.*`, `field.namedType()`. I have integers, booleans, decimals, plain dates, and byte columns in my app. "The exact helper set comes from your database and the extension packs you compose" is not an answer and there is no link to the list. This is the single biggest gap. -2. **`.defaultSql(expression)`** — is the argument a string? `defaultSql("now()")`? No example. -3. **`rel.hasOne` and `rel.manyToMany`** — named and then dropped. Many-to-many was the hardest relation in v6. What are the options? Is there a join table, and do I name it? -4. **Indexes.** `.unique()` is covered. There is no way shown to add a plain, non-unique index, or a multi-column index. Meanwhile the agent-prompt section says "add a unique index to the email field", which mixes up index and constraint. -5. **Composite primary keys.** Both `field.id.uuidv4String()` and a chained `.id()` exist. Which do I use, and how do I mark two fields as the key together? -6. **Adding the extension to `prisma.config.ts`.** "Add the same pack to `prisma.config.ts` as `extensions: [pgvector]`, importing the pack's `/control` export." No code block. I do not know the import line, and I do not know whether `extensions` goes at the top level of `definePrismaConfig` or inside `ormConfig({ ... })`. The config example earlier shows only `contract`, so I cannot infer it. -7. **Do I have to declare foreign keys myself?** The PostgreSQL example has both `rel.belongsTo` *and* `constraints.foreignKey(...)` for the same relation. The prose says the callback form is "for foreign keys with explicit names", which suggests the constraint already exists and I am only naming it — but that is my guess, and the example reads as if both are required. -8. **Can I call `.sql()` without `.relations()`?** Every example is `Model.relations({...}).sql({...})`. A model with no relations is common. Is `Model.sql({ table })` legal? -9. **Postgres enums.** The enum is stored as `text`. In v6 I got a real Postgres `enum` type. Can I still have one? If so, what `codecId` and `nativeType`? -10. **What the emit step needs from my TypeScript setup.** Does `prisma contract emit` type-check the file? Does the file have to be inside my `tsconfig.json`? If I import a helper from another package, does emit resolve it? -11. **Converting an existing project.** "Keep the other form out of the project so the two can never disagree." Do I delete `schema.prisma`? Is there a command? The only conversion path offered is asking a coding agent. -12. **MongoDB primary keys.** `_id: field.objectId()` is never marked with `.id()`. I assume `_id` is special, but the page does not say so. - -## Places that explain how the tool works when I only wanted to know what to do - -- **The whole "How `defineContract` works" section.** The useful part is one sentence: MongoDB takes one object, PostgreSQL takes options plus a factory. The rest describes argument plumbing I would have learned from the example. -- **"`prisma contract emit` writes it to JSON in a fixed key order and hashes it, so the same source always produces the same files. That works only if the file holds plain data."** I do not need the key ordering or the hash. The rule I need is the three bullets that follow. Lead with the rule. -- **"`prisma contract emit` imports the file and reads its `default` or `contract` export, and nothing else."** Half internals, half the only place the page tells me my export must be named `contract` or be the default. That is a required instruction buried in a section about purity, six screens below the example that relies on it. -- **"The factory receives authoring helpers composed from ..."** — describes the mechanism. What I need is: "`field`, `model`, `rel` and `type` come from the factory's argument, not from imports." - -## Contradictions between the two tabs that no prose resolves - -Reading top to bottom, the MongoDB tab looks like a different product: - -- PostgreSQL destructures `{ field, model, type }` from a factory; MongoDB imports `field, model, rel` at the top. -- PostgreSQL: `rel.hasMany(Post, { by: "userId" })`. MongoDB: `rel.hasMany("Post", { from: "_id", to: "authorId" })`. Different option names for the same idea, with no note that `by` and `from`/`to` differ per database. -- PostgreSQL: `User.refs.id` (property, plural). MongoDB: `User.ref("_id")` (method, singular). Never mentioned. -- PostgreSQL puts relations in a chained `.relations()`; MongoDB puts them inline in the model options. The prose mentions the `collection` difference but not this one. -- The MongoDB `rel` import is used, but the MongoDB list of helpers in the prose ("`field.string()`, `field.int32()` ...") omits `field.objectId()`, which the example actually uses for the key. - -## Could I do what the page is for after one reading? - -Partly. I could copy the PostgreSQL example, change the model and field names, point `prisma.config.ts` at it, and run `npx prisma contract emit`. That is real, and it is the page's main job. - -I could not write a contract for my actual application. I would still not know: - -- what to type for an integer, boolean, decimal, plain date, or binary field — the majority of my columns; -- how to write a many-to-many relation, which every app of mine has; -- how to add a non-unique or multi-column index; -- how to write a composite primary key; -- where `extensions` goes in `prisma.config.ts`, or what the `/control` import line is; -- whether I must declare foreign key constraints by hand or only when I want to name them; -- what a `codecId` is, or where to find valid values, even though I must type one for every enum; -- whether `.sql()` works on a model with no relations; -- how to move my existing `schema.prisma` over, other than asking an agent. - -The page reads as a tour of the builder's design. It is not yet something I can write a real contract from without a second, longer reference next to it — and it does not link to one. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/typescript-schema-builder.round2.md b/docs/orm-docs-audit/c21-contract-authoring/typescript-schema-builder.round2.md deleted file mode 100644 index e898d7759bb..00000000000 --- a/docs/orm-docs-audit/c21-contract-authoring/typescript-schema-builder.round2.md +++ /dev/null @@ -1,83 +0,0 @@ -Read once, top to bottom, as someone coming from Prisma 6. - -## Sentences I could not restate after one reading - -**1.** "Prisma ORM 8 replaces `schema.prisma` with [a contract](/orm/contract-authoring/the-data-contract)." -Stopped by: "a contract" is a new word and the page never says what it is. I only learn it is a file that emits two other files. Plainer: "Instead of `schema.prisma`, Prisma 8 reads a single file called the contract, which describes your models." - -**2.** "[`npx prisma contract emit`](/cli/contract-emit) writes `contract.json` and `contract.d.ts` beside the source, and both forms produce those same two files." -Stopped by: "beside the source" — beside which source? I guessed `src/prisma/`. Also three ideas in one sentence. Plainer: "Run `npx prisma contract emit`. It writes `contract.json` and `contract.d.ts` into the same folder as your contract file." - -**3.** "Its one argument gives you `field`, `model`, `rel`, and `type`. Those four come from the argument, not from imports." -This contradicts the example directly above it. The example destructures only three — `({ field, model, type })` — and imports `rel` from `@prisma/orm-postgres/contract-builder`. So which is right for `rel`? I could not tell, and this is the kind of thing that makes the file not compile. Same for `enumType` and `member`, which are imported and never mentioned in this list. - -**4.** "`constraints.index` takes one column for a plain index and a list for a multi-column index." -The example right above passes a list in both cases: `constraints.index([cols.userId])`. So do I type `cols.userId` or `[cols.userId]` for one column? The sentence and the code disagree. - -**5.** "`codecId` is the type id, which is the PostgreSQL type plus a version, always `@1` today." -"pg/text@1" is not "the PostgreSQL type plus a version" — there is a `pg/` prefix the sentence does not account for. And "codec" is never defined. Plainer: "`codecId` is a string of the form `pg/@1`." - -**6.** "A primary key made of two fields is a model attribute rather than storage mapping." -Stopped by: this is a category distinction internal to Prisma. As a reader I do not know what "model attribute" means or why I should care. Plainer: "For a primary key made of two fields, use `.attributes(...)` instead of `.sql(...)`." - -**7.** "`.relations(...)`, `.attributes(...)`, and `.sql(...)` are independent." -"Independent" of what? Can I call them in any order? Can I call `.sql(...)` twice? Can I chain all three? The example only ever shows `.relations().sql()`. Plainer: "You can chain any of these three, in any order, and you can skip any of them." - -**8.** "`prisma contract emit` writes the contract to JSON in a fixed key order and hashes it. A contract that changes per machine or per run breaks that hash and the checks that rely on it." -Two problems. "the checks that rely on it" — which checks? Never named, so I cannot judge how bad this is. And the hashing detail is machinery I did not ask about. Plainer: "Do not read `process.env`, the clock, or random values in the contract file. Prisma compares a hash of the contract across runs, and a contract that changes every run fails that comparison." - -## Words and phrases I had to guess - -- **"contract"** — guessed: the new name for the schema. -- **"PSL"** — expanded on first use, good, but I guessed it is basically the old `schema.prisma` language. -- **"codec"** in `codecId` — guessed: a rule for converting between the database value and the JavaScript value. -- **"extension pack"** / **"pack"** — guessed: an npm package that adds column types, like pgvector. -- **`/pack` vs `/control` exports** — guessed: two entry points of the same package, one for the contract and one for the config. I do not know why, but the page tells me which goes where, which is enough. -- **`ulid`, `nanoid`, `cuid2`, `ksuid`** — guessed: ID string formats. I do not know how to choose between them and the page does not link anywhere. -- **`field.uuidNative()` vs `field.uuidString()`** — guessed from the table: one makes a `uuid` column, one a text column. -- **"emitted files"** (description) / "the two files" — guessed: `contract.json` and `contract.d.ts`. -- **`Model.refs` vs `Model.ref("_id")`** — the page uses `User.refs.id` for PostgreSQL and `User.ref("_id")` for MongoDB. I guessed these are two different APIs for the two databases. Never stated. -- **`cols` vs `fields`** — `.sql(({ cols, constraints }) => ...)` and `.attributes(({ fields, constraints }) => ...)`. I guessed they are the same thing under two names. Never stated. - -## "So what do I actually type?" — not answered - -1. **How do I create the contract file in the first place?** The page shows a finished file and a config pointing at it, but never says whether `npx prisma init` makes it or whether I create `src/prisma/contract.ts` by hand. The only creation advice is in "Parity with PSL," and only for converting an existing project. - -2. **A date without a time.** "There is no helper for a date without a time, or a time without a date." So what do I type? A `field.dateTime()` with `.column()`? A codec? The page names the gap and then stops. - -3. **Where does `.sql({ fk: ... })` go on a relation?** "Chain `.sql({ fk: { name: "post_userId_fkey" } })` on the relation." Chain it onto what expression — `rel.belongsTo(User, {...}).sql({...})`, inside the `.relations({ ... })` object? No code sample for this form, while the other form gets a full one. - -4. **A non-text enum.** "Members are stored in whatever column type you name here." If I want integers, what `codecId` and `nativeType` do I type? I am sent to the raw-queries page to hunt for it. - -5. **The `native_enum` block in PSL.** "To get a PostgreSQL `enum` type, write the contract in PSL and use a `native_enum` block." No syntax, no link to that block. - -6. **The key in `.attributes(...)`.** `id: constraints.id([fields.userId, fields.teamId])` — is `id` a required key name, or any label I pick? If I also add `constraints.unique(...)` "in the same block," what key do I give it? - -7. **Does the foreign-key field type have to match the key it points at?** `id: field.id.uuidv4String()` and `userId: field.uuidString()` in the example. I noticed the pairing but the page never states the rule, so I do not know if `field.text()` would also work. - -8. **Does `.relations()` change `User`, or return a new object?** In the example, `Post`'s `belongsTo(User, ...)` references the bare `User` from `model(...)`, while `models.User` is `User.relations(...).sql(...)`. If these are different objects, which one do relations have to point at? - -9. **MongoDB's limits.** The page says the MongoDB builder differs in five ways, then: "The rest of this page uses the PostgreSQL builder." So do enums, `enumType`, extension packs, `.attributes(...)`, and indexes work on MongoDB at all? I am left with a full page of material and no idea how much of it applies to me. - -## Places the page explains the machinery when I only wanted instructions - -- **The whole "How `defineContract` works" section.** The title promises internals, and some of it is internals ("You never pass the name of your database. It comes from the package you import."). But it is also the only place the argument list and the MongoDB differences are written down, so I cannot skip it. The useful parts are buried under a how-it-works heading. -- "`prisma contract emit` writes the contract to JSON in a fixed key order and hashes it." — I want the rule, not the hashing scheme. -- "`prisma contract emit` imports the file and reads its `default` or `contract` export, and nothing else. It runs the file, but it does not type-check it." — the second half is useful (type errors will not stop emit). The first half repeats what the "A complete contract" section already told me. -- "`as const` keeps both strings as literal types so the builder can check them." — borderline. I would have accepted "write `as const` after the object." - -## Could I do this after one reading? - -For the narrow case the example covers — PostgreSQL, a few models with text, UUID, JSON and timestamp fields, one-to-many and belongs-to relations, table renames, one named foreign key — yes. The full example is good and I could copy it. - -Outside that, no. After one reading I would still not know: - -- whether to import `rel` or take it from the callback argument (the page and its own example disagree) -- whether `constraints.index` wants a bare column or a list for one column (same problem) -- what to type for a date-only column -- what `codecId` to use for anything other than text -- where `.sql({ fk: ... })` attaches on a relation -- how much of the page applies if I use MongoDB -- how to create the contract file for a brand-new project - -The two contradictions between the prose and the code samples are the worst of it. Both are in areas where I would be typing code straight from the page, and both would fail to compile with no hint from the page about which version is right. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/typescript-schema-builder.round3.md b/docs/orm-docs-audit/c21-contract-authoring/typescript-schema-builder.round3.md deleted file mode 100644 index 406deb79634..00000000000 --- a/docs/orm-docs-audit/c21-contract-authoring/typescript-schema-builder.round3.md +++ /dev/null @@ -1,100 +0,0 @@ -# Reader review: `orm/contract-authoring/typescript-schema-builder.mdx` - -I have used Prisma 6 for two years. I have never seen "contract", `defineContract`, or any of this vocabulary before. - -## Sentences I could not restate after one reading - -**1. Line 163, the whole `defineContract` paragraph.** The worst part of the page. Eight facts in one block, several of them about the library's internal wiring rather than what I type: - -> "So `field`, `model`, and `rel` have two sources and either one works. The example takes `field`, `model`, and `type` from the argument, and imports `defineContract`, `enumType`, `member`, and `rel`." - -What stopped me: I now know the same three names exist in two places and either works, which is exactly the fact that makes me unable to decide what to write. Then the page tells me what its own example chose, which does not tell me what I should choose. Plainer: "Take `field`, `model`, `rel`, and `type` from the callback argument. Import `defineContract`, `enumType`, and `member` from the package, because you use them outside the callback." - -**2. Line 165, the MongoDB paragraph.** One paragraph, about fourteen separate facts, announced as "four more ways" and then listing more than four. I cannot hold it. This should be a bulleted list or its own page. I gave up halfway and skimmed. - -**3. Line 187.** - -> "To store a date with no time, write that model in [PSL](...) and give the field the `Date` type." - -This contradicts line 314: "A project names exactly one contract file in its config." If I can only have one contract file and it is TypeScript, I cannot "write that model in PSL". So what do I actually do if one model needs a date-only column — abandon TypeScript authoring entirely for the whole project? The page does not say, and it reads like the two sentences were written by different people. - -**4. Line 244.** - -> "Prisma ORM checks that the two sides of a relation name the same number of fields." - -I cannot resolve this. Every relation helper on this page names exactly one field per side (`by`, `from`, `to` are all single strings). Nothing on the page shows a relation over more than one field, so I do not know what this check is checking or how I would ever trip it. - -**5. Line 252.** - -> "Each returns a new model builder and leaves the one you called it on unchanged. `rel.belongsTo(User, ...)` may point at either the bare `User` or the chained one, because both carry the same model name." - -This is a description of how the builder is implemented. What I wanted to know is "can I pass `User` before I have chained `.sql()` onto it?" Answer: yes. Say that. - -**6. Line 281.** - -> "The keys in that object are fixed." - -Fixed to what? The paragraph mentions `id` and `uniques`. Are those the only two? Is there a third? "The object accepts exactly two keys, `id` and `uniques`" would have been readable. - -**7. Line 306.** - -> "Prisma ORM compares a hash of the contract across runs, and a contract that changes every run fails that comparison." - -Internal mechanism, and the consequence is vague. What fails, and when? Does `prisma contract emit` error out? Does a migration get regenerated? Plainer: "If the contract file produces different output on each run, `prisma ` reports ." - -**8. Line 308.** - -> "Keep the file free of side effects. `prisma contract emit` runs the file, but it does not type-check it. Type errors show in your editor and when you run `tsc`." - -The second and third sentences have nothing to do with side effects. Three unrelated ideas under one bullet. Also, the bullet tells me to avoid side effects and never says what counts as one, or what breaks if I have one. The other two bullets in this list both say what goes wrong; this one does not. - -## Words and phrases I had to guess - -- **"contract"** — I guessed this is the new name for what `schema.prisma` was. The page half-confirms it in the first sentence but then line 22 says the init command "asks how you want to write your **schema**", and the page title/URL says "schema builder". Three words for one thing. -- **`contract.json` and `contract.d.ts`** — named five times, never described. I guessed `.d.ts` is the types my client code uses. I do not know what `contract.json` is for or whether I commit either file. -- **`codecId: "pg/text@1"`** — I guessed "codec" means the rule for converting between the database value and the JavaScript value. I have no idea what the `@1` means — a version of what? The explanation on line 204 only restates the shape, and then sends me to a **raw queries** page to find valid values, which is a strange place to keep a list the enum section depends on. -- **`nativeType`** — guessed: the literal SQL column type. -- **"extension pack"** — line 285 defines it, 230 lines after line 55 uses it. On first read I guessed it was a plugin. -- **`ulid`, `nanoid`, `cuid2`, `ksuid`** (line 189) — I know `cuid` from Prisma 6. The page says "Each names a different format" and stops. I would be picking at random. -- **`pack` vs `control` exports** (line 287) — guessed that one is for the build tooling and one for the contract file. The page says which import goes where and never says why, so I cannot reason about it if I get it wrong. -- **`refs` vs `ref`** — `User.refs.id` on PostgreSQL, `User.ref("_id")` on MongoDB. I guessed they do the same thing. -- **`cols` vs `fields`** — line 281 says they are "the same model fields under two names", which confirms my guess but leaves me wondering why I have to remember two names. - -## Places I asked "so what do I actually type?" - -1. **`field.column(pg.enum(Role))`, line 215.** Line 198 told me `.column("column_name")` takes a string and sets the column name. Now the same method takes a type. Same name, two unrelated jobs, no acknowledgement. On top of that the snippet has no `import` line, so I do not know where `pg` comes from as a value (the prose says the package exports it, the code does not show it), and it never says whether `Role` has to go in the returned `enums` map the way `Priority` does. -2. **Does `defineContract` on PostgreSQL require the options object?** Every PostgreSQL example passes `{ extensions: { pgvector } }`. I have no extensions. Do I write `defineContract({}, ({ field, model }) => ...)` or `defineContract(({ field, model }) => ...)`? The page never shows the common case — a plain contract with no extensions — which is exactly the case I have. -3. **The two reasons to use TypeScript at all (line 15-16) are never demonstrated.** "build models in a loop from data you already keep in TypeScript, such as one model per entry in a list of table names" is the reason I would come to this page. There is no example of a loop, and no example of splitting models across modules. The page sells a capability and then never shows it. -4. **`.sql({ fk: { name } })` vs `.sql({ foreignKeys: [...] })`, lines 238 and 242.** Two spellings for one thing, `fk` singular object versus `foreignKeys` array. When do I use which? "Ask for one in either of two places" does not help me choose. -5. **Where does the `pgvector` key on `type.pgvector` come from?** I assume it is the key I used in `extensions: { pgvector }`, so renaming the import renames the accessor. Never stated. -6. **`type.pgvector.Vector(1536)` is stored under the key `Embedding1536`.** Is that name arbitrary, or does it appear in the database or in `contract.d.ts`? I do not know whether I am naming something or just holding a local variable. -7. **MongoDB indexes.** "indexes are an `indexes` option on the model" — no example, and the PostgreSQL index syntax uses `constraints.index([cols.x])` which clearly does not apply, since MongoDB has no `.sql(...)`. -8. **`.id()` on a field.** Line 197 lists it. Line 281 says the `field.id.*` helpers already do it. So when do I ever call `.id()` myself — an integer primary key? An existing column? Not shown. -9. **`rel.manyToMany`, line 229.** The example references `Membership` and `Team` out of nowhere. It says "You declare the model for the join table yourself" but does not show what that model has to contain (does it need its own `id`? just the two foreign keys?). The composite-key example on line 276 uses `Membership` again, still never defined. -10. **MongoDB relation arguments are inconsistent with PostgreSQL and with themselves.** `rel.hasMany("Post", { from: "_id", to: "authorId" })` uses a string model name; `rel.belongsTo(User, { from: "authorId", to: User.ref("_id") })` uses the object, and mixes a plain string `from` with a ref-object `to`. PostgreSQL's `hasMany` uses `{ by }` instead of `{ from, to }`. Four shapes across two examples. I would be copying and pasting and hoping. -11. **Do I commit `contract.json` and `contract.d.ts`, and when do I re-run `emit`?** Only after editing the contract? As part of a build script? Never said. - -## Places explaining how the tool works when I only wanted to know what to do - -- Line 163, the "two sources and either one works" explanation. -- Line 252, "Each returns a new model builder and leaves the one you called it on unchanged." -- Line 281, "`cols` in `.sql(...)` and `fields` in `.attributes(...)` are the same model fields under two names." -- Line 306, "Prisma ORM compares a hash of the contract across runs." -- Line 52, "`prisma contract emit` looks for those two names and nothing else" — the useful half is "export it as `contract` or as the default export"; the rest is about the tool's lookup. -- Line 246, "Pass a string and the typo is reported when `prisma contract emit` builds the contract" — this one earns its place, because it changes what I should type. Most of the others do not. - -## Could I do what the page is for, after one reading? - -Partly. I could copy the PostgreSQL example, rename models, change field helpers, and get a contract with a one-to-many relation and a table mapping. That is real. - -I could not do any of the following without rereading or guessing: - -- Write a contract with **no** extension packs, because the shape of `defineContract`'s first argument in that case is never shown. -- Use TypeScript for the reason the page told me to use TypeScript — generating models in a loop or splitting them across modules. No example exists. -- Add a real PostgreSQL `enum` type. The only snippet for it has no imports, reuses `field.column` for an unrelated purpose, and never says where `Role` goes in the returned object. -- Handle a date-only column, because the page's advice contradicts its own rule about having one contract file. -- Choose between `.sql({ fk })` and `.sql({ foreignKeys })`, or between importing `field`/`model`/`rel` and destructuring them. -- Pick an ID format, because the page lists six and describes none. -- Explain to a colleague what `contract.json` is, what `codecId`'s `@1` means, or why a pack has both a `/pack` and a `/control` export. - -The page is roughly two pages: a reference for someone who already knows the builder, and a tutorial that never quite commits to showing the plain case. The single biggest fix would be a short, complete, extension-free PostgreSQL example near the top — two models, one relation, no `pgvector`, no `codecId`, no callback destructuring puzzle — before any of the advanced material. \ No newline at end of file diff --git a/docs/orm-docs-audit/c21-contract-authoring/typescript-schema-builder.round4.md b/docs/orm-docs-audit/c21-contract-authoring/typescript-schema-builder.round4.md deleted file mode 100644 index b2f18e417ea..00000000000 --- a/docs/orm-docs-audit/c21-contract-authoring/typescript-schema-builder.round4.md +++ /dev/null @@ -1,98 +0,0 @@ -Read once, top to bottom, as a Prisma 6/7 user who has never seen version 8. - -## Sentences I could not restate after one reading - -**1. Line 9 — "Prisma ORM 8 reads a single file called [the contract](...), which describes your models."** -Stopped me: "the contract" is a new term used as if I know it. The page never says what a contract *is* beyond "describes your models" — is it my old schema under a new name, or something more? Plainest wording I'd have understood: "In Prisma 8, `schema.prisma` is replaced by a file called the contract. It holds the same model definitions you used to write in `schema.prisma`." - -**2. Line 57 — `const pgText = { codecId: "pg/text@1", nativeType: "text" } as const;`** -Stopped me in the very first example: `codecId` is a word I have never seen, it appears before any explanation (the explanation is 120 lines later at line 177), and `pg/text@1` looks like a package version. I could not restate this line. It should be explained where it first appears, or the first example should not use it. - -**3. Line 126 — "You never name your database, because the package you import carries it."** -Too compressed, and it answers a question I had not asked yet. Plainer: "You do not set a `provider` anywhere. Importing `@prisma/orm-postgres/contract-builder` is what selects PostgreSQL." - -**4. Line 128 — "Take `field`, `model`, `rel`, and `type` from the function's one argument. Import `defineContract`, `enumType`, and `member` from the package, because you use them outside the function."** -Two rules with a "because" that does not hold up. Why can't I import `field` from the package too? The page then does exactly that at line 184 (`import { field, model, nativeEnum, pg } from "@prisma/orm-postgres/contract-builder"`) with no comment. After one reading I did not know which import style is correct on PostgreSQL. This is a straight contradiction inside the page. - -**5. Line 135 — "a field on another model is written `User.ref("_id")` where PostgreSQL writes `User.refs.id`."** -I can parse it, but I cannot use it: the PostgreSQL form `User.refs.id` never appears in any PostgreSQL example on the page. It reappears alone at line 245 with no surrounding code. I still do not know where in a PostgreSQL contract I would type `User.refs.id`. - -**6. Line 136 — "`indexes: [index({ email: 1 }, { unique: true })]`"** -What is `1`? Nothing on the page says. I guessed "ascending, the MongoDB convention" from memory of MongoDB, not from this page. - -**7. Line 160 — "`field.column(...)` and the chained `.column(...)` below are different calls: `field.column(descriptor)` builds a field from a type description, and `.column(name)` on an existing field sets its column name."** -I understood it only because the sentence explicitly warns me. The fact that a sentence this defensive is needed means the API reuses one name for two unrelated things. Not something the page can fix, but the sentence itself needed two readings. - -**8. Line 177 — "`codecId` names the PostgreSQL type plus a version, always `@1` today."** -This contradicts the code beside it. In `{ codecId: "pg/text@1", nativeType: "text" }`, the thing that "names the PostgreSQL type" is plainly `nativeType`. So what does `codecId` actually name, and why do I need both? After one reading I could not say. Plainer, if my guess is right: "`codecId` picks how Prisma converts the value between TypeScript and the database. `nativeType` is the column type the database gets. They usually pair up." - -**9. Line 177 — "[Raw queries](/orm/reference/raw-queries#binding-a-bare-value-with-param) lists more type ids."** -A reference I cannot resolve while reading. I am authoring a schema; why would the complete list of type ids live on the raw-query page? If that is where it is, say why, or put the list here. - -**10. Line 229 — "You can pass `User` to `rel.belongsTo(User, ...)` before chaining `.sql()` onto it."** -I read this twice and still am not sure what is being promised. Is `.sql()` mutating `User`, or returning a new object that the earlier `rel.belongsTo(User)` somehow also sees? Plainer: "The model object you get from `model(...)` keeps its identity. Passing it to a relation before you call `.sql()` on it is safe; the relation picks up the table name you set later." - -**11. Line 255 — "`cols` in `.sql(...)` and `fields` in `.attributes(...)` are the same model fields under two names."** -I can restate the words, but not the reason. Why does the same thing have two names in two callbacks on the same object? The page states the oddity without resolving it. - -**12. Line 274 — "The key on `type.pgvector` is the pack's own name, not the name you gave the import."** -Understood the rule; cannot apply it. Where do I find "the pack's own name"? The page does not say (package README? a field in the pack?). This is the exact sentence that will make me guess wrong. - -## Words and phrases I had to guess at - -- **"contract"** — guessed: the new name for `schema.prisma`. -- **"PSL"** — expanded on first use, good, but "the preferred way" (line 13) is asserted with no reason until line 18. -- **"codecId"** — guessed: an identifier for the code that converts values between TypeScript and the database column. -- **"`pg/text@1`" / "`@1`"** — guessed: a version number for that converter, not a package version. -- **"`nativeType`"** — guessed: the literal SQL column type. -- **"extension pack"** — line 259 defines it ("an npm package that adds column types"), but line 126 uses it 130 lines earlier with no definition. -- **"`as const`"** — I know the TypeScript keyword; I guessed the *reason* here is that the builder needs the exact string literal types, which the page never says. Line 177 just orders me: "Write `as const` after the object." -- **"the two files"** (used four times, lines 13, 179, 274) — guessed: `contract.json` and `contract.d.ts`. Fine on first use, tiring by the fourth. -- **"artifact"** — never in prose, only in the link text `/the-contract-artifact`. -- **"`contract infer`"** (line 18) — guessed: something that reads an existing database and writes a contract. Never explained here. -- **"`{ email: 1 }`"** — guessed: ascending index direction. -- **"`Model.refs`"** — guessed: an object of typed handles to that model's fields. -- **"`through`"** (line 203) — guessed: the join-table model. `from` and `to` in that call I guessed as "the join table's column pointing at me" and "the one pointing at the other model." The page does not say which is which. -- **"storage mapping"** (section title, line 225) — guessed: naming tables, columns, and indexes. - -## Places I asked "so what do I actually type?" and got no answer - -1. **`fk` options.** Line 219: "`fk` takes `name`, `onDelete`, and `onUpdate`." What are the legal values? `"cascade"`? `"Cascade"`? `"CASCADE"`? `"SetNull"`? This is the single most common thing I do with a foreign key and the page stops one word short. -2. **An auto-incrementing integer primary key.** Line 170 says `.id()` is "for a key that is not generated, such as an integer you set yourself." I have `serial`/`identity` columns in my existing database. Nothing on the page tells me how to declare one. `field.int().id()` plus something? The page does not say there is a way, and does not say there is not. -3. **A database-generated UUID.** All six `field.id.*` helpers "generate the ID in your app." If I want `gen_random_uuid()` in the database, do I write `field.uuidNative().defaultSql("gen_random_uuid()").id()`? I am guessing. Say it. -4. **`onDelete` on the `hasMany` side.** `.sql({ fk })` is shown only on `belongsTo`. I do not know whether that is the only place it is allowed. -5. **The list of `codecId` values.** Line 160 hands me `"pg/date-temporal@1"` out of nowhere for a date-only column. I need a date-only column, a `time` column, `numeric(10,2)`, `text[]`, and `citext`. The page gives me one of those by example and points at a raw-queries page for the rest. -6. **How to run `contract.ts`.** Line 297 says "`npx prisma contract emit` runs the file." With what? Does my contract file get to `import` from other files in my project — which is the entire stated reason to use TypeScript (line 15, "split, composed, or reused across ordinary TypeScript modules or packages")? Does that work with path aliases, ESM, a build step? The page sells this as the main benefit and never shows a single multi-file example. -7. **Enums, types, and indexes on MongoDB.** Line 121 shows `defineContract({ models: { User, Post } })` — one object, models only. Line 138 says enums work on MongoDB too. So where does the enums map go on MongoDB? `{ models, enums }`? Not shown. -8. **`hasMany` on MongoDB vs PostgreSQL.** PostgreSQL: `rel.hasMany(Post, { by: "userId" })`. MongoDB: `rel.hasMany("Post", { from: "_id", to: "authorId" })`. The page lists the MongoDB differences but not this one, so on MongoDB I do not know whether `by` also works, or on PostgreSQL whether `from`/`to` also works. -9. **String vs ref, in the same MongoDB example.** Line 104 writes `to: "authorId"`; line 117 writes `to: User.ref("_id")`. Same option name, two different shapes, ten lines apart, no comment. I would have asked which one to use. -10. **`extensions` shape.** Contract file: `defineContract({ extensions: { pgvector } }, ...)` — an object. Config file: `ormConfig({ extensions: [pgvector] })` — an array. Same word, two shapes, and the imports differ too (`/pack` vs `/control`). The page states this at line 276 but does not say why, so I will get it backwards. -11. **`field.temporal.updatedAt()`.** Line 164: "fills it on every create and every update." Does the client do this, or does emit create a database trigger? It matters — I write to this database from a second service. Not said. -12. **Where `types` goes in the return for MongoDB, and whether `type` is available at all there.** Line 128 lists `type` among the callback's arguments; MongoDB has no callback. - -## Places that explain the machinery when I only wanted the instruction - -Honestly, few — the page is mostly task-shaped. The ones that hit: - -- **Line 124, "## How `defineContract` works".** The heading promises internals. What I wanted was "how to write the top of the file." The section is actually useful, but the first paragraph (line 126) spends its opening on why you never name your database, which is machinery. -- **Line 160**, the `field.column` vs `.column` disambiguation, is a description of a naming collision in the API rather than an instruction. Necessary, but it is the API's problem being handed to me. -- **Line 223** — "Pass the model object, as above, and a typo is a compile error. Pass a string and the typo is reported when `prisma contract emit` builds the contract." This is about when validation runs. Just tell me to pass the object. -- **Line 298** — "`npx prisma contract emit` runs the file but does not type-check it." Internals, but this one earns its place; it explains a real surprise. - -## After reading once, could I do what the page is for? - -Partly. I could copy the PostgreSQL example, change the model and field names, add `.optional()` and `.unique()`, set table names with `.sql({ table })`, add an index, and run `prisma contract emit`. For a plain project of text/int/boolean/timestamp columns with app-generated UUID keys, yes. - -What I would still not know: - -- How to declare an auto-incrementing integer primary key, or any database-generated key. This alone blocks me from porting my existing application. -- What values `onDelete` and `onUpdate` accept. -- What a `codecId` is, or how to find the right one for any column type not in the eleven-row table. -- Whether to import `field`/`model` from the package or take them from the callback, because the page does both and only explains one. -- How to split the contract across files — the stated reason to use TypeScript at all (line 15), shown nowhere. -- On MongoDB: where enums, types, and the `index` import belong in the returned object, and whether `hasMany` takes `by` or `from`/`to`. -- How to find an extension pack's "own name" for the `type.*` key. - -The MongoDB content is the weakest part. It is a five-bullet diff list against a PostgreSQL API, plus one example that contradicts itself on `to:`. A MongoDB-only reader has no complete page here. - -The single sentence I would fix first is line 128's import rule, because line 184 breaks it in the same page. \ No newline at end of file From 6214a500b7c687989d92b8a2e6f67f3b8d6fc180 Mon Sep 17 00:00:00 2001 From: willbot Date: Mon, 14 Sep 2026 10:23:50 +0200 Subject: [PATCH 13/14] docs(orm8): load .env in the capabilities page's config example Co-Authored-By: Claude Fable 5.1 Signed-off-by: willbot Signed-off-by: Will Madden --- apps/docs/content/docs/orm/contract-authoring/capabilities.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx b/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx index 40e50794cec..54f924cbaaf 100644 --- a/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx @@ -24,6 +24,7 @@ The keys come from your database package (`@prisma/orm-postgres`, `@prisma/orm-s Error messages and the table below name a key group first, `postgres.pgvector.cosine`. The `sql` group holds keys that more than one SQL database may support. SQLite reports `false` for some of them. Keys under `postgres` belong to PostgreSQL only. Extension packages add keys of their own, and `pgvector.cosine` comes from the pgvector package. Install it with `npm install @prisma/orm-extension-pgvector`, list it in `prisma.config.ts`, and run `npx prisma contract emit` again: ```typescript title="prisma.config.ts" +import "dotenv/config"; import { definePrismaConfig } from "prisma/config"; import pgvector from "@prisma/orm-extension-pgvector/control"; import { defineConfig as ormConfig } from "@prisma/orm-postgres/config"; From 90496f8a78e94e682553cf1a6f2178e35d340723 Mon Sep 17 00:00:00 2001 From: willbot Date: Mon, 14 Sep 2026 12:50:03 +0200 Subject: [PATCH 14/14] docs: add the Supported databases page for Prisma ORM 8 PostgreSQL is a release candidate, MongoDB is in early access, and MySQL, MariaDB, SQLite, SQL Server, and CockroachDB are coming. The page says what each label means, how to ask for a database, and replaces the database section of prisma/orm issue 25843 for Prisma ORM 8. Release status links to it. Co-Authored-By: Claude Fable 5.1 Signed-off-by: willbot Signed-off-by: Will Madden --- .../content/docs/(index)/prisma-orm/meta.json | 9 ++++- .../(index)/prisma-orm/release-status.mdx | 2 +- .../prisma-orm/supported-databases.mdx | 36 +++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 apps/docs/content/docs/(index)/prisma-orm/supported-databases.mdx diff --git a/apps/docs/content/docs/(index)/prisma-orm/meta.json b/apps/docs/content/docs/(index)/prisma-orm/meta.json index 8c1ad499fd8..aadc57bc05b 100644 --- a/apps/docs/content/docs/(index)/prisma-orm/meta.json +++ b/apps/docs/content/docs/(index)/prisma-orm/meta.json @@ -1,5 +1,12 @@ { "title": "Prisma ORM", "defaultOpen": true, - "pages": ["index", "release-status", "create-prisma", "quickstart", "add-to-existing-project"] + "pages": [ + "index", + "release-status", + "supported-databases", + "create-prisma", + "quickstart", + "add-to-existing-project" + ] } diff --git a/apps/docs/content/docs/(index)/prisma-orm/release-status.mdx b/apps/docs/content/docs/(index)/prisma-orm/release-status.mdx index 6ba9592f9a6..612f4b5377b 100644 --- a/apps/docs/content/docs/(index)/prisma-orm/release-status.mdx +++ b/apps/docs/content/docs/(index)/prisma-orm/release-status.mdx @@ -33,7 +33,7 @@ npm install --save-dev prisma npm install @prisma/orm-postgres ``` -For MongoDB the library is `@prisma/orm-mongo`, and for SQLite it is `@prisma/orm-sqlite`. MySQL is not supported yet. +For MongoDB the library is `@prisma/orm-mongo`. [Supported databases](/prisma-orm/supported-databases) lists every database and its status. The two packages have different version numbers, and that is normal: they are released separately, and the command-line tool gets more frequent releases. Install both at `latest`; any two `latest` versions work together, and you do not pair the numbers yourself. diff --git a/apps/docs/content/docs/(index)/prisma-orm/supported-databases.mdx b/apps/docs/content/docs/(index)/prisma-orm/supported-databases.mdx new file mode 100644 index 00000000000..0b304593f9e --- /dev/null +++ b/apps/docs/content/docs/(index)/prisma-orm/supported-databases.mdx @@ -0,0 +1,36 @@ +--- +title: Supported databases +description: 'Which databases Prisma ORM 8 supports today, what release candidate and early access mean, and which databases are coming.' +url: /prisma-orm/supported-databases +metaTitle: Databases supported by Prisma ORM 8 +metaDescription: 'PostgreSQL is a release candidate and MongoDB is in early access. MySQL, MariaDB, SQLite, SQL Server, and CockroachDB are coming. What each label means and how to ask for a database.' +--- + +Prisma ORM 8 ships one library per database. Today there are two you can build on. + +| Database | Status | Library | +| --- | --- | --- | +| PostgreSQL, including Prisma Postgres | Release candidate | `@prisma/orm-postgres` | +| MongoDB | Early access | `@prisma/orm-mongo` | +| MySQL | Coming soon | | +| MariaDB | Coming soon | | +| SQLite | Coming soon | | +| Microsoft SQL Server | Coming soon | | +| CockroachDB | Coming soon | | + +## What the labels mean + +**Release candidate.** Install it and build with it today. Until the final release, some details of the API may still change, so code you write now may need small edits later. Each release candidate fixes bugs found in the last one. [Release status](/prisma-orm/release-status) has the dates and what `npm install` gives you. + +**Early access.** The library works and you can build with it, but some of what the PostgreSQL library does is not there yet. The reference pages say on each method whether it works on MongoDB, and [what is not available yet](/orm/coming-from-prisma-orm-7#not-available-yet) lists the gaps. Report what you hit on [GitHub](https://github.com/prisma/orm/issues). + +**Coming soon.** There is no Prisma ORM 8 library for the database yet. Prisma ORM 7 supports it, and Prisma ORM 7 keeps receiving bug fixes and security updates for 18 months after Prisma ORM 8 reaches general availability. [How to stay on Prisma ORM 7](/prisma-orm/release-status#how-to-stay-on-prisma-orm-7) shows how to lock the version. + +## How to ask for a database + +Add a 👍 to the GitHub issue for the database you need, or open one if there is none. The number of reactions is one of the inputs to what gets built next. Organizations that need a database sooner can talk to [Prisma enterprise support](https://www.prisma.io/enterprise). + +## Next steps + +- The quickstart sets up a project on [PostgreSQL](/prisma-orm/quickstart/postgresql) or [MongoDB](/prisma-orm/quickstart/mongodb). +- [Release status](/prisma-orm/release-status) covers versions, dates, and staying on Prisma ORM 7.