From 3e513b3aace806b4e0e8552d1d1f22723525eab6 Mon Sep 17 00:00:00 2001 From: Alexey Orlenko's AI Agent Date: Mon, 6 Jul 2026 19:44:03 +0200 Subject: [PATCH 1/6] docs: add Prisma Next contract authoring section (DR-8680) Add a Data contract group under orm/next with four pages: - The data contract: concept overview, authoring modes, artifacts - Author in PSL: schema syntax incl. named types, enums, value objects, relations, inheritance, and extension types - Author in TypeScript: the defineContract builder API, fields, relations, storage mapping, and purity rules - The emitted artifacts: contract.json/contract.d.ts anatomy, deterministic emission, and the content hashes Also link the new section from the Prisma Next overview cards. All examples are taken from or verified against the prisma-next repository (examples/prisma-next-demo and the authoring packages). Signed-off-by: Alexey Orlenko's AI Agent --- .../docs/orm/next/contract/artifacts.mdx | 155 ++++++++++++ .../content/docs/orm/next/contract/index.mdx | 98 ++++++++ .../content/docs/orm/next/contract/psl.mdx | 231 ++++++++++++++++++ .../docs/orm/next/contract/typescript.mdx | 213 ++++++++++++++++ apps/docs/content/docs/orm/next/index.mdx | 8 + apps/docs/content/docs/orm/next/meta.json | 7 +- 6 files changed, 711 insertions(+), 1 deletion(-) create mode 100644 apps/docs/content/docs/orm/next/contract/artifacts.mdx create mode 100644 apps/docs/content/docs/orm/next/contract/index.mdx create mode 100644 apps/docs/content/docs/orm/next/contract/psl.mdx create mode 100644 apps/docs/content/docs/orm/next/contract/typescript.mdx diff --git a/apps/docs/content/docs/orm/next/contract/artifacts.mdx b/apps/docs/content/docs/orm/next/contract/artifacts.mdx new file mode 100644 index 0000000000..cf91bde1ce --- /dev/null +++ b/apps/docs/content/docs/orm/next/contract/artifacts.mdx @@ -0,0 +1,155 @@ +--- +title: The emitted artifacts +description: contract.json and contract.d.ts are the deterministic artifacts every other part of Prisma Next consumes. Here is what is inside them. +url: /orm/next/contract/artifacts +metaTitle: The Prisma Next 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. +--- + +[`prisma-next contract emit`](/cli/next/contract-emit) compiles your contract source into two files, written next to the source by default: + +| File | Contents | Consumed 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.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/next/contract/psl) or [TypeScript](/orm/next/contract/typescript) source and re-run `contract emit`. The files carry a generated-file notice that says exactly this. + +## 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. + +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/next/contract/typescript#keep-the-contract-file-pure) lists the concrete rules. + +## The content hashes + +Emission computes hashes over distinct parts of the contract. Each answers a different question: + +| Hash | Covers | Changes when | +| --- | --- | --- | +| `storageHash` | Models, fields, relations, and the full storage layout: tables, columns, keys, indexes | Any schema change | +| `executionHash` | Defaults Prisma Next applies before writes, such as `uuid()` generators | Generated defaults change | +| `profileHash` | The capability profile the contract declares for the target and its extensions | Capabilities or extension packs change, even with an identical schema | + +The hashes are how Prisma Next connects the contract to a live database. [`prisma-next db sign`](/cli/next/db-sign) checks that the database satisfies the contract and records the hashes in a marker inside the database. From then on, [`prisma-next db verify`](/cli/next/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. + +## 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; each model's `storage` block bridges the two. Everything is grouped by namespace (on PostgreSQL, the schema, typically `public`). + +An abridged emit of a `User`/`Post` schema: + +```json title="prisma/contract.json (abridged)" +{ + "schemaVersion": "1", + "targetFamily": "sql", + "target": "postgres", + "profileHash": "sha256:…", + "roots": { + "user": { "namespace": "public", "model": "User" }, + "post": { "namespace": "public", "model": "Post" } + }, + "domain": { + "namespaces": { + "public": { + "models": { + "User": { + "fields": { + "id": { "nullable": false, "type": { "kind": "scalar", "codecId": "pg/uuid@1" } }, + "email": { "nullable": false, "type": { "kind": "scalar", "codecId": "pg/text@1" } } + }, + "relations": { + "posts": { + "cardinality": "1:N", + "to": { "namespace": "public", "model": "Post" }, + "on": { "localFields": ["id"], "targetFields": ["userId"] } + } + }, + "storage": { + "namespaceId": "public", + "table": "user", + "fields": { "id": { "column": "id" }, "email": { "column": "email" } } + } + } + } + } + } + }, + "storage": { + "storageHash": "sha256:…", + "namespaces": { + "public": { + "entries": { + "table": { + "user": { + "columns": { + "id": { "nativeType": "uuid", "codecId": "pg/uuid@1", "nullable": false }, + "email": { "nativeType": "text", "codecId": "pg/text@1", "nullable": false } + }, + "primaryKey": { "columns": ["id"] } + } + } + } + } + } + }, + "execution": { "executionHash": "sha256:…" }, + "capabilities": { "postgres": { "returning": true } }, + "extensionPacks": {} +} +``` + +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. `db.user` exists because `roots.user` points at `User`. +- **`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 Next applies before writes, such as UUID generation, kept out of the database's DDL. +- **`capabilities`** and **`extensionPacks`**: the database and extension features the contract relies on, pinned so verification can check the target actually provides them. + +## 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: + +```typescript title="prisma/contract.d.ts (excerpt)" +export type StorageHash = + StorageHashBase<"sha256:9f49f8f9e51a9cc016f1ec2098ebae9406521a3cc2cf00207adc795078333d8b">; +export type ProfileHash = + ProfileHashBase<"sha256: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; + readonly country: CodecTypes["pg/text@1"]["output"]; +}; +``` + +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. + +## 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. + +```typescript title="src/prisma/db.ts" +import postgres from "@prisma-next/postgres/runtime"; +import type { Contract } from "./contract.d"; +import contractJson from "./contract.json" with { type: "json" }; + +export const db = postgres({ + contractJson, +}); +``` + +At startup, the client carries the contract's hashes and verifies them against the database marker before executing queries. + +## 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. + +## Next steps + +- Apply the contract to a fresh database with [`prisma-next db init`](/cli/next/db-init). +- Check a live database against the contract with [`prisma-next db verify`](/cli/next/db-verify). +- Plan schema changes between contract versions with [`prisma-next migration plan`](/cli/next/migration-plan). diff --git a/apps/docs/content/docs/orm/next/contract/index.mdx b/apps/docs/content/docs/orm/next/contract/index.mdx new file mode 100644 index 0000000000..19d78f1eec --- /dev/null +++ b/apps/docs/content/docs/orm/next/contract/index.mdx @@ -0,0 +1,98 @@ +--- +title: The data contract +description: The data contract is the single description of your data model and its storage layout. Everything in Prisma Next is typed, planned, and verified against it. +url: /orm/next/contract +metaTitle: The Prisma Next data contract +metaDescription: What the Prisma Next data contract is, how PSL and TypeScript authoring produce the same artifacts, and how the contract drives typing, migrations, and verification. +--- + +The data contract is the single description of your application's data model and its storage layout. Everything else in Prisma Next works against it: queries are typed by it, migrations are planned as transitions between versions of it, and the database is verified against it before your code runs. + +:::note[Prisma Next is in Early Access] + +Prisma Next is the next major version of Prisma ORM, available now in Early Access. It's the cutting-edge version of Prisma ORM and will become the future of Prisma, so we'd love for you to try it, explore what's new, and [share your feedback in Discord](https://pris.ly/discord). + +::: + +## 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 Next keeps the schema knowledge in the open. Your schema 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. + +The contract also carries identity. Emission computes hashes over the contract's content, and [`prisma-next db sign`](/cli/next/db-sign) records them in a small marker inside the database. Before executing queries, Prisma Next compares the contract it was built against with the marker in the database it is talking to. A mismatch, such as a deploy against an unmigrated database, fails verification instead of failing at query time. + +## How it works + +A Prisma Next project declares one contract source in `prisma-next.config.ts`: + +```typescript title="prisma-next.config.ts" +import { defineConfig } from "@prisma-next/postgres/config"; + +export default defineConfig({ + contract: "./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. + +Emitting turns the source into the artifacts: + +```bash +npx prisma-next contract emit +``` + +This writes `contract.json` and `contract.d.ts` next to the source. 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 [`prisma-next db verify`](/cli/next/db-verify) checks a live database against the current contract. + +## Two authoring modes, one artifact + +PSL and TypeScript authoring are two front ends to the same contract. For an equivalent schema they emit the same `contract.json`, so nothing downstream, including migrations, verification, and the query APIs, cares which one you write. + +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. + +Choose based on how you want to write schemas: + +- **[PSL](/orm/next/contract/psl)** is the Prisma schema language you already know, extended for Prisma Next. It is the default for scaffolded projects, and it is what [`prisma-next contract infer`](/cli/next/contract-infer) writes when you start from an existing database. +- **[TypeScript](/orm/next/contract/typescript)** defines the same models with a typed builder API. There is no separate language to learn, your editor type-checks the schema as you write it, and you can compose and reuse definitions with ordinary TypeScript modules. + +## 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 value objects +- types and capabilities contributed by extension packs, such as pgvector's `Vector` +- content 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. + +## Next steps + + + } + > + Write the contract as a Prisma schema file. + + } + > + Define the same models with the typed contract builder. + + } + > + What is inside contract.json and contract.d.ts, and how the hashes work. + + diff --git a/apps/docs/content/docs/orm/next/contract/psl.mdx b/apps/docs/content/docs/orm/next/contract/psl.mdx new file mode 100644 index 0000000000..e1c059173d --- /dev/null +++ b/apps/docs/content/docs/orm/next/contract/psl.mdx @@ -0,0 +1,231 @@ +--- +title: Author in PSL +description: Write the Prisma Next contract as a Prisma schema file, using the schema language you already know plus the Prisma Next additions. +url: /orm/next/contract/psl +metaTitle: Author the Prisma Next contract in PSL +metaDescription: Learn how to write a Prisma Next contract in the Prisma schema language, including named types, enums, value objects, relations, and extension types. +--- + +PSL is the default authoring mode for the Prisma Next [data contract](/orm/next/contract). You write a Prisma schema file, usually `prisma/contract.prisma`, and [`prisma-next contract emit`](/cli/next/contract-emit) turns it into `contract.json` and `contract.d.ts`. + +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 Next additions in detail: named types, typed enums, value objects, model inheritance, and extension types. + +## Point the config at the schema + +The config's `contract` path names the source of truth. A `.prisma` extension selects PSL authoring: + +```typescript title="prisma-next.config.ts" +import { defineConfig } from "@prisma-next/postgres/config"; + +export default defineConfig({ + contract: "./prisma/contract.prisma", +}); +``` + +Scaffolded projects (`npx create-prisma@next`) already contain this wiring and a starter schema. + +## A complete contract + +```prisma title="prisma/contract.prisma" +// use prisma-next + +types { + Uuid = String @db.Uuid +} + +type Address { + street String + city String + zip String? + country String +} + +enum Priority { + @@type("pg/text@1") + Low = "low" + High = "high" + Urgent = "urgent" +} + +model User { + id Uuid @id @default(uuid()) + email String + createdAt DateTime @default(now()) + address Address? + posts Post[] + + @@map("user") +} + +model Post { + id Uuid @id @default(uuid()) + title String + userId Uuid + priority Priority @default(Low) + createdAt DateTime @default(now()) + + user User @relation(fields: [userId], references: [id]) + + @@map("post") +} +``` + +Run `npx prisma-next contract emit` after any change to refresh the artifacts. + +## 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. +- `@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 Next before each write instead, so they work the same on every database and appear in the contract's `execution` section rather than as DDL. +- `@@map("table_name")` sets the table name when it differs from the model name. + +## Named types + +The `types` block declares reusable type aliases. An alias binds a base type and its storage details under one name: + +```prisma +types { + Uuid = String @db.Uuid +} +``` + +Fields then use `Uuid` like any built-in type. The alias keeps the storage decision (`uuid` columns rather than `text`) in one place. + +## Enums + +An enum in Prisma Next declares its storage codec with `@@type` and, optionally, the stored value for each member: + +```prisma +enum Priority { + @@type("pg/text@1") + Low = "low" + High = "high" + Urgent = "urgent" +} +``` + +`@@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, the emitted contract 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. + +```prisma +type Address { + street String + city String + zip String? + country String +} + +model User { + id Uuid @id @default(uuid()) + address Address? +} +``` + +On PostgreSQL, a value object field is stored in a single `jsonb` column, and `contract.d.ts` types it as a structured object rather than untyped JSON. + +## 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: + +```prisma +model Post { + userId Uuid + user User @relation(fields: [userId], references: [id]) +} + +model User { + posts Post[] +} +``` + +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: + +```prisma +model Post { + tags Tag[] +} + +model Tag { + posts Post[] +} + +model PostTag { + postId Uuid + tagId Uuid + + post Post @relation(fields: [postId], references: [id]) + tag Tag @relation(fields: [tagId], references: [id]) + + @@id([postId, tagId]) + @@map("post_tag") +} +``` + +The emitted contract records the relation as `N:M` with the join table's columns, so queries can traverse `post.tags` directly. + +## Model inheritance + +A base model declares a discriminator field, and each variant names its base and its discriminator value: + +```prisma +model Task { + id Uuid @id @default(uuid()) + title String + type String + + @@discriminator(type) + @@map("task") +} + +model Bug { + severity String + stepsToRepro String? + + @@base(Task, "bug") + @@map("bug") +} +``` + +Rows with `type = "bug"` are `Bug` records. The variant's own fields live in its own table (here `bug`), which shares the base model's primary key. + +## Extension types + +Extension packs contribute types through constructor expressions in the `types` block. Compose the pack in the config, then use its types: + +```typescript title="prisma-next.config.ts" +import pgvector from "@prisma-next/extension-pgvector/control"; +import { defineConfig } from "@prisma-next/postgres/config"; + +export default defineConfig({ + contract: "./prisma/contract.prisma", + extensions: [pgvector], +}); +``` + +```prisma title="prisma/contract.prisma" +types { + Embedding1536 = pgvector.Vector(1536) +} + +model Post { + id Uuid @id @default(uuid()) + embedding Embedding1536? +} +``` + +Emission validates extension types against the composed packs: a type from a pack that is not listed in the config fails the emit with a diagnostic. Re-run `contract emit` after changing the extension list. + +## Starting from an existing database + +If the database already exists, don't write the schema by hand. [`prisma-next contract infer`](/cli/next/contract-infer) reads the live schema and writes a starter `contract.prisma` for you to review and edit. + +## Next steps + +- Emit and inspect [the artifacts](/orm/next/contract/artifacts) the schema produces. +- Prefer defining models in code? See [authoring in TypeScript](/orm/next/contract/typescript). +- Apply the contract to a database with [`prisma-next db init`](/cli/next/db-init) or plan changes with [`prisma-next migration plan`](/cli/next/migration-plan). diff --git a/apps/docs/content/docs/orm/next/contract/typescript.mdx b/apps/docs/content/docs/orm/next/contract/typescript.mdx new file mode 100644 index 0000000000..794bc24622 --- /dev/null +++ b/apps/docs/content/docs/orm/next/contract/typescript.mdx @@ -0,0 +1,213 @@ +--- +title: Author in TypeScript +description: Define the Prisma Next contract with a typed builder API instead of a schema file. Same models, same artifacts, no separate language. +url: /orm/next/contract/typescript +metaTitle: Author the Prisma Next contract in TypeScript +metaDescription: Learn how to define a Prisma Next contract in TypeScript with defineContract, including fields, relations, enums, storage mapping, and extension types. +--- + +TypeScript authoring defines the Prisma Next [data contract](/orm/next/contract) in code. Instead of a `.prisma` file, you write `prisma/contract.ts` with the `defineContract` builder, and [`prisma-next contract emit`](/cli/next/contract-emit) produces exactly the same `contract.json` and `contract.d.ts` a PSL schema would. + +The trade is straightforward: you give up PSL's compact syntax and get the full TypeScript toolchain in return. Your editor type-checks model definitions as you write them, invalid references are compile errors instead of emit-time diagnostics, and you can split, compose, and reuse definitions with ordinary modules. + +## Point the config at the contract file + +The config's `contract` path names the source of truth. A `.ts` extension selects TypeScript authoring: + +```typescript title="prisma-next.config.ts" +import { defineConfig } from "@prisma-next/postgres/config"; + +export default defineConfig({ + contract: "./prisma/contract.ts", +}); +``` + +## A complete contract + +The builder comes from your database's target package. For PostgreSQL that is `@prisma-next/postgres/contract-builder`: + +```typescript title="prisma/contract.ts" +import pgvector from "@prisma-next/extension-pgvector/pack"; +import { defineContract, enumType, member, rel } from "@prisma-next/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( + { + extensionPacks: { 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", + }), + ], + })), + }, + }; + }, +); +``` + +Run `npx prisma-next contract emit` after any change to refresh the artifacts. + +## How `defineContract` works + +`defineContract` takes two arguments: an options object and a factory function. + +The options object declares what the contract is built from, most importantly `extensionPacks`. The target and database family are already bound by the import: `@prisma-next/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 (which is why `type.pgvector` exists in the example, and only when `pgvector` is in `extensionPacks`). The factory returns the contract's content: `models`, plus optional `enums` and `types`. + +## Fields + +`field` provides typed constructors for common field shapes: + +- `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 + +The exact helper set comes from the target and the composed extension packs. 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. + +## Enums + +`enumType` declares an enum with an explicit storage codec and members: + +```typescript +const Priority = enumType( + "Priority", + { codecId: "pg/text@1", nativeType: "text" } as const, + member("Low", "low"), + member("High", "high"), +); +``` + +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. + +## Relations + +Relations are declared on the model builder with `.relations(...)` and the `rel` helpers: + +```typescript +User.relations({ + posts: rel.hasMany(Post, { by: "userId" }), +}); + +Post.relations({ + user: rel.belongsTo(User, { from: "userId", to: "id" }), +}); +``` + +`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. Because the arguments are model objects, not strings, a typo in a relation target is a compile error. + +## Storage mapping + +`.sql(...)` maps a model to its table. The object form covers the common case: + +```typescript +User.relations({ ... }).sql({ table: "user" }); +``` + +The callback form additionally exposes the model's columns and constraint builders, for foreign keys with explicit names: + +```typescript +Post.relations({ ... }).sql(({ cols, constraints }) => ({ + table: "post", + foreignKeys: [ + constraints.foreignKey(cols.userId, User.refs.id, { name: "post_userId_fkey" }), + ], +})); +``` + +`Model.refs` provides typed references to another model's fields, so `User.refs.id` is checked against the actual `User` definition. + +## Extension types + +Declare packs in `defineContract`'s options and the factory's `type` helper exposes their constructors: + +```typescript +import pgvector from "@prisma-next/extension-pgvector/pack"; + +export const contract = defineContract( + { extensionPacks: { pgvector } }, + ({ field, model, type }) => { + const types = { Embedding1536: type.pgvector.Vector(1536) } as const; + // ... use field.namedType(types.Embedding1536) in a model + return { types, models: { /* ... */ } }; + }, +); +``` + +The same pack must also be composed in `prisma-next.config.ts` (as `extensions: [pgvector]`, using the pack's `/control` export) so the CLI and runtime agree with the contract. + +## Keep the contract file pure + +The contract file describes structure; emission 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: + +- 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. + +Configuration that legitimately varies per environment, such as the database URL, belongs in `prisma-next.config.ts`, not in the contract. + +## Parity with PSL + +TypeScript and [PSL](/orm/next/contract/psl) authoring emit the same canonical artifact for an equivalent schema. You can move between the modes without any downstream change, but a project 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. + +## Next steps + +- Emit and inspect [the artifacts](/orm/next/contract/artifacts) the contract produces. +- Apply the contract to a database with [`prisma-next db init`](/cli/next/db-init) or plan changes with [`prisma-next migration plan`](/cli/next/migration-plan). diff --git a/apps/docs/content/docs/orm/next/index.mdx b/apps/docs/content/docs/orm/next/index.mdx index 57db7497a0..056db5f3f4 100644 --- a/apps/docs/content/docs/orm/next/index.mdx +++ b/apps/docs/content/docs/orm/next/index.mdx @@ -84,4 +84,12 @@ Prisma Next is in active development, and full conceptual and reference document Create a Prisma Next app, initialize the database, seed data, and run your first query. + } + > + Learn how your schema becomes a verifiable contract, authored in PSL or + TypeScript. + diff --git a/apps/docs/content/docs/orm/next/meta.json b/apps/docs/content/docs/orm/next/meta.json index ae5f3be609..95c945d3eb 100644 --- a/apps/docs/content/docs/orm/next/meta.json +++ b/apps/docs/content/docs/orm/next/meta.json @@ -4,6 +4,11 @@ "root": true, "pages": [ "---Introduction---", - "index" + "index", + "---Data contract---", + "contract/index", + "contract/psl", + "contract/typescript", + "contract/artifacts" ] } From f7e20e0337b35eb058845833bfd81e1691c6ff92 Mon Sep 17 00:00:00 2001 From: Alexey Orlenko's AI Agent Date: Tue, 7 Jul 2026 12:28:18 +0200 Subject: [PATCH 2/6] docs: align contract authoring section with DR-8680 spec Restructure the section from orm/next/contract to the ticket's URLs under /next/contract-authoring and complete the required page set: - Move and rename the four pages to the-data-contract, psl-syntax, typescript-schema-builder, and the-contract-artifact - Add the fifth page, capabilities, documenting the implemented behavior: capabilities are derived from target/adapter/extension pack declarations at emit time, gate contract build (e.g. scalar lists on SQLite) and query building (assertCapability errors), and are not probed from the live database - Add the contract-vs-schema terminology note and the three-jobs framing to the first page - Add PostgreSQL/MongoDB tabs where authoring differs (config, full examples, ID mapping), with MongoDB examples verified against prisma-next (mongo-demo, authoring side-by-side suite) - Correct the profileHash row on the artifact page: per current code it covers target/family, not the capability subset - Add the five temporary redirects from Prisma 7 schema URLs listed in the ticket (permanent: false) - Wire the section into the Getting Started sidebar and revert the orm/next nav entries from the previous layout Signed-off-by: Alexey Orlenko's AI Agent --- apps/docs/content/docs/(index)/meta.json | 1 + .../next/contract-authoring/capabilities.mdx | 86 +++++++++++++ .../(index)/next/contract-authoring/meta.json | 10 ++ .../next/contract-authoring/psl-syntax.mdx} | 121 ++++++++++++++++-- .../the-contract-artifact.mdx} | 13 +- .../contract-authoring/the-data-contract.mdx} | 30 +++-- .../typescript-schema-builder.mdx} | 93 +++++++++++++- apps/docs/content/docs/orm/next/index.mdx | 2 +- apps/docs/content/docs/orm/next/meta.json | 7 +- apps/docs/vercel.json | 25 ++++ 10 files changed, 353 insertions(+), 35 deletions(-) create mode 100644 apps/docs/content/docs/(index)/next/contract-authoring/capabilities.mdx create mode 100644 apps/docs/content/docs/(index)/next/contract-authoring/meta.json rename apps/docs/content/docs/{orm/next/contract/psl.mdx => (index)/next/contract-authoring/psl-syntax.mdx} (69%) rename apps/docs/content/docs/{orm/next/contract/artifacts.mdx => (index)/next/contract-authoring/the-contract-artifact.mdx} (87%) rename apps/docs/content/docs/{orm/next/contract/index.mdx => (index)/next/contract-authoring/the-data-contract.mdx} (72%) rename apps/docs/content/docs/{orm/next/contract/typescript.mdx => (index)/next/contract-authoring/typescript-schema-builder.mdx} (69%) diff --git a/apps/docs/content/docs/(index)/meta.json b/apps/docs/content/docs/(index)/meta.json index c7375e791a..017392b1b2 100644 --- a/apps/docs/content/docs/(index)/meta.json +++ b/apps/docs/content/docs/(index)/meta.json @@ -11,6 +11,7 @@ "---Prisma Next---", "next/quickstart", "next/add-to-existing-project", + "next/contract-authoring", "---Prisma ORM---", "...prisma-orm", "---Prisma Postgres---", diff --git a/apps/docs/content/docs/(index)/next/contract-authoring/capabilities.mdx b/apps/docs/content/docs/(index)/next/contract-authoring/capabilities.mdx new file mode 100644 index 0000000000..35a44ab177 --- /dev/null +++ b/apps/docs/content/docs/(index)/next/contract-authoring/capabilities.mdx @@ -0,0 +1,86 @@ +--- +title: Capabilities +description: Capabilities record what your database stack supports, so Prisma Next can reject unsupported features early with a clear error. +url: /next/contract-authoring/capabilities +metaTitle: Capabilities in Prisma Next +metaDescription: How Prisma Next capabilities are derived from the target, adapter, and extension packs, what they gate, and what happens when a required capability is missing. +--- + +Capabilities are flags in the [data contract](/next/contract-authoring/the-data-contract) that record what your database stack supports: whether the target can do lateral joins, `RETURNING` clauses, native value sets, vector distance operations, and so on. Prisma Next consults them before using a gated feature, so an unsupported feature fails early with an error that names the missing capability, instead of failing as a database error mid-query. + +## Where capabilities come from + +You do not write capabilities yourself. When [`prisma-next contract emit`](/cli/next/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: + +```json title="prisma/contract.json (excerpt)" +{ + "capabilities": { + "postgres": { + "distinctOn": true, + "jsonAgg": true, + "lateral": true, + "limit": true, + "orderBy": true, + "pgvector.cosine": true, + "returning": true + }, + "sql": { + "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-next.config.ts` and re-running `contract emit` changes the capability set with no further work. + +## What capabilities gate + +Capabilities are checked at two points, both before any SQL reaches the database. + +**When the contract is built.** A schema feature the target cannot store fails emission 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: + +```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 matrix and throw if the key is missing, naming the method and the capability: + +```text +distinctOn() requires capability postgres.distinctOn +lateralJoin() requires capability sql.lateral +``` + +The error fires when your code constructs the query, which makes it easy to catch in tests: a query that uses a feature your stack lacks cannot be built at all. + +## Example capabilities + +A few of the keys the built-in components declare, to give a sense of the granularity: + +| Capability | Gates | +| --- | --- | +| `sql.lateral` | Lateral joins (`lateralJoin()`); PostgreSQL declares it, SQLite does not | +| `sql.returning` | `RETURNING` clauses on writes | +| `sql.enums` | Enum value sets enforced in the database; SQLite does not declare it | +| `sql.defaultInInsert` | Using `DEFAULT` as a value in multi-row inserts | +| `sql.scalarList` | Scalar list fields in the schema | +| `postgres.distinctOn` | `DISTINCT ON` queries (`distinctOn()`) | +| `postgres.jsonAgg` | JSON aggregation for nested reads | +| `postgres.pgvector.cosine` | Cosine distance operations, contributed by the pgvector pack | + +This is an illustration, not the full registry; each target, adapter, and pack ships its own declarations. The emitted `contract.json` of your own project is the authoritative 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. + +## 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 the separate hash-and-marker mechanism described in [the contract artifact](/next/contract-authoring/the-contract-artifact#the-content-hashes): [`prisma-next db verify`](/cli/next/db-verify) checks that the database matches the contract's schema and profile. + +## Next steps + +- See where the `capabilities` block sits in [the contract artifact](/next/contract-authoring/the-contract-artifact). +- Compose extension packs, and the capabilities they bring, in the [CLI configuration](/cli/next/configuration). diff --git a/apps/docs/content/docs/(index)/next/contract-authoring/meta.json b/apps/docs/content/docs/(index)/next/contract-authoring/meta.json new file mode 100644 index 0000000000..14a42a9e60 --- /dev/null +++ b/apps/docs/content/docs/(index)/next/contract-authoring/meta.json @@ -0,0 +1,10 @@ +{ + "title": "Contract authoring", + "pages": [ + "the-data-contract", + "psl-syntax", + "typescript-schema-builder", + "the-contract-artifact", + "capabilities" + ] +} diff --git a/apps/docs/content/docs/orm/next/contract/psl.mdx b/apps/docs/content/docs/(index)/next/contract-authoring/psl-syntax.mdx similarity index 69% rename from apps/docs/content/docs/orm/next/contract/psl.mdx rename to apps/docs/content/docs/(index)/next/contract-authoring/psl-syntax.mdx index e1c059173d..a814853d13 100644 --- a/apps/docs/content/docs/orm/next/contract/psl.mdx +++ b/apps/docs/content/docs/(index)/next/contract-authoring/psl-syntax.mdx @@ -1,12 +1,12 @@ --- title: Author in PSL description: Write the Prisma Next contract as a Prisma schema file, using the schema language you already know plus the Prisma Next additions. -url: /orm/next/contract/psl +url: /next/contract-authoring/psl-syntax metaTitle: Author the Prisma Next contract in PSL metaDescription: Learn how to write a Prisma Next contract in the Prisma schema language, including named types, enums, value objects, relations, and extension types. --- -PSL is the default authoring mode for the Prisma Next [data contract](/orm/next/contract). You write a Prisma schema file, usually `prisma/contract.prisma`, and [`prisma-next contract emit`](/cli/next/contract-emit) turns it into `contract.json` and `contract.d.ts`. +PSL is the default authoring mode for the Prisma Next [data contract](/next/contract-authoring/the-data-contract). You write a Prisma schema file, usually `prisma/contract.prisma`, and [`prisma-next contract emit`](/cli/next/contract-emit) turns it into `contract.json` and `contract.d.ts`. 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 Next additions in detail: named types, typed enums, value objects, model inheritance, and extension types. @@ -14,6 +14,10 @@ If you know the Prisma schema language, most of a contract file reads exactly as The config's `contract` path names the source of truth. A `.prisma` extension selects PSL authoring: + + + + ```typescript title="prisma-next.config.ts" import { defineConfig } from "@prisma-next/postgres/config"; @@ -22,10 +26,30 @@ export default defineConfig({ }); ``` + + + + +```typescript title="prisma-next.config.ts" +import { defineConfig } from "@prisma-next/mongo/config"; + +export default defineConfig({ + contract: "./prisma/contract.prisma", +}); +``` + + + + + Scaffolded projects (`npx create-prisma@next`) already contain this wiring and a starter schema. ## A complete contract + + + + ```prisma title="prisma/contract.prisma" // use prisma-next @@ -70,6 +94,57 @@ model Post { } ``` + + + + +```prisma title="prisma/contract.prisma" +// use prisma-next + +type Address { + street String + city String + zip String? + country String +} + +enum UserRole { + @@type("mongo/string@1") + Admin = "admin" + Author = "author" + Reader = "reader" +} + +model User { + id ObjectId @id @map("_id") + name String + email String + bio String? + role UserRole + address Address? + posts Post[] + + @@map("users") +} + +model Post { + id ObjectId @id @map("_id") + title String + content String + authorId ObjectId + createdAt DateTime + + author User @relation(fields: [authorId], references: [id]) + + @@index([authorId]) + @@map("posts") +} +``` + + + + + Run `npx prisma-next contract emit` after any change to refresh the artifacts. ## Models and fields @@ -77,9 +152,39 @@ Run `npx prisma-next contract emit` after any change to refresh the artifacts. 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. +- `@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 Next before each write instead, so they work the same on every database and appear in the contract's `execution` section rather than as DDL. -- `@@map("table_name")` sets the table name when it differs from the model name. +- `@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. + +How IDs map differs by database: + + + + + +```prisma +model User { + id Uuid @id @default(uuid()) +} +``` + +The primary key is an ordinary column; pick its type and default yourself. + + + + + +```prisma +model User { + id ObjectId @id @map("_id") +} +``` + +The primary key is MongoDB's `_id`: type it `ObjectId` and map it to the physical `_id` key. + + + + ## Named types @@ -126,7 +231,7 @@ model User { } ``` -On PostgreSQL, a value object field is stored in a single `jsonb` column, and `contract.d.ts` types it as a structured object rather than untyped JSON. +Storage follows the database's nature: 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. ## Relations @@ -191,7 +296,7 @@ model Bug { } ``` -Rows with `type = "bug"` are `Bug` records. The variant's own fields live in its own table (here `bug`), which shares the base model's primary key. +Rows with `type = "bug"` are `Bug` records. On PostgreSQL, a variant's own fields live in the variant's own table (here `bug`), which shares the base model's primary key. On MongoDB, variants add their fields to documents in the base model's collection instead, so a variant declares `@@base` but no `@@map` of its own. ## Extension types @@ -226,6 +331,6 @@ If the database already exists, don't write the schema by hand. [`prisma-next co ## Next steps -- Emit and inspect [the artifacts](/orm/next/contract/artifacts) the schema produces. -- Prefer defining models in code? See [authoring in TypeScript](/orm/next/contract/typescript). +- Emit and inspect [the artifacts](/next/contract-authoring/the-contract-artifact) the schema produces. +- Prefer defining models in code? See [authoring in TypeScript](/next/contract-authoring/typescript-schema-builder). - Apply the contract to a database with [`prisma-next db init`](/cli/next/db-init) or plan changes with [`prisma-next migration plan`](/cli/next/migration-plan). diff --git a/apps/docs/content/docs/orm/next/contract/artifacts.mdx b/apps/docs/content/docs/(index)/next/contract-authoring/the-contract-artifact.mdx similarity index 87% rename from apps/docs/content/docs/orm/next/contract/artifacts.mdx rename to apps/docs/content/docs/(index)/next/contract-authoring/the-contract-artifact.mdx index cf91bde1ce..67ac2e2e48 100644 --- a/apps/docs/content/docs/orm/next/contract/artifacts.mdx +++ b/apps/docs/content/docs/(index)/next/contract-authoring/the-contract-artifact.mdx @@ -1,7 +1,7 @@ --- title: The emitted artifacts description: contract.json and contract.d.ts are the deterministic artifacts every other part of Prisma Next consumes. Here is what is inside them. -url: /orm/next/contract/artifacts +url: /next/contract-authoring/the-contract-artifact metaTitle: The Prisma Next 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. --- @@ -13,13 +13,13 @@ metaDescription: What contract.json and contract.d.ts contain, how deterministic | `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.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/next/contract/psl) or [TypeScript](/orm/next/contract/typescript) 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](/next/contract-authoring/psl-syntax) or [TypeScript](/next/contract-authoring/typescript-schema-builder) source and re-run `contract emit`. The files carry a generated-file notice that says exactly this. ## 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. -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/next/contract/typescript#keep-the-contract-file-pure) lists the concrete rules. +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](/next/contract-authoring/typescript-schema-builder#keep-the-contract-file-pure) lists the concrete rules. ## The content hashes @@ -29,13 +29,13 @@ Emission computes hashes over distinct parts of the contract. Each answers a dif | --- | --- | --- | | `storageHash` | Models, fields, relations, and the full storage layout: tables, columns, keys, indexes | Any schema change | | `executionHash` | Defaults Prisma Next applies before writes, such as `uuid()` generators | Generated defaults change | -| `profileHash` | The capability profile the contract declares for the target and its extensions | Capabilities or extension packs change, even with an identical schema | +| `profileHash` | The profile the contract is built for: the target database and its family | The contract targets a different database | The hashes are how Prisma Next connects the contract to a live database. [`prisma-next db sign`](/cli/next/db-sign) checks that the database satisfies the contract and records the hashes in a marker inside the database. From then on, [`prisma-next db verify`](/cli/next/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. ## 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; 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 (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`). An abridged emit of a `User`/`Post` schema: @@ -106,7 +106,7 @@ The sections, top to bottom: - **`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 Next applies before writes, such as UUID generation, kept out of the database's DDL. -- **`capabilities`** and **`extensionPacks`**: the database and extension features the contract relies on, pinned so verification can check the target actually provides them. +- **`capabilities`** and **`extensionPacks`**: 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](/next/contract-authoring/capabilities). ## Inside `contract.d.ts` @@ -150,6 +150,7 @@ Commit the artifacts alongside the source. They contain structure only, no data ## Next steps +- Learn how the contract's [capabilities](/next/contract-authoring/capabilities) gate database features. - Apply the contract to a fresh database with [`prisma-next db init`](/cli/next/db-init). - Check a live database against the contract with [`prisma-next db verify`](/cli/next/db-verify). - Plan schema changes between contract versions with [`prisma-next migration plan`](/cli/next/migration-plan). diff --git a/apps/docs/content/docs/orm/next/contract/index.mdx b/apps/docs/content/docs/(index)/next/contract-authoring/the-data-contract.mdx similarity index 72% rename from apps/docs/content/docs/orm/next/contract/index.mdx rename to apps/docs/content/docs/(index)/next/contract-authoring/the-data-contract.mdx index 19d78f1eec..bbf89a216d 100644 --- a/apps/docs/content/docs/orm/next/contract/index.mdx +++ b/apps/docs/content/docs/(index)/next/contract-authoring/the-data-contract.mdx @@ -1,12 +1,18 @@ --- title: The data contract description: The data contract is the single description of your data model and its storage layout. Everything in Prisma Next is typed, planned, and verified against it. -url: /orm/next/contract +url: /next/contract-authoring/the-data-contract metaTitle: The Prisma Next data contract metaDescription: What the Prisma Next data contract is, how PSL and TypeScript authoring produce the same artifacts, and how the contract drives typing, migrations, and verification. --- -The data contract is the single description of your application's data model and its storage layout. Everything else in Prisma Next works against it: queries are typed by it, migrations are planned as transitions between versions of it, and the database is verified against it before your code runs. +The data contract is the single description of your application's data model and its storage layout: a machine-readable record of exactly what your application expects from its database, like a `package-lock.json` for your data. It does three jobs. Your queries are type-checked against it, migrations are planned as transitions between versions of it, and the database is verified against it before your code runs. + +:::note[Contract vs. schema] + +In Prisma Next, 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 Next checks that the database's schema satisfies it. + +::: :::note[Prisma Next is in Early Access] @@ -56,8 +62,8 @@ A project declares exactly one source of truth: the file named by `contract` in Choose based on how you want to write schemas: -- **[PSL](/orm/next/contract/psl)** is the Prisma schema language you already know, extended for Prisma Next. It is the default for scaffolded projects, and it is what [`prisma-next contract infer`](/cli/next/contract-infer) writes when you start from an existing database. -- **[TypeScript](/orm/next/contract/typescript)** defines the same models with a typed builder API. There is no separate language to learn, your editor type-checks the schema as you write it, and you can compose and reuse definitions with ordinary TypeScript modules. +- **[PSL](/next/contract-authoring/psl-syntax)** is the Prisma schema language you already know, extended for Prisma Next. It is the default for scaffolded projects, and it is what [`prisma-next contract infer`](/cli/next/contract-infer) writes when you start from an existing database. +- **[TypeScript](/next/contract-authoring/typescript-schema-builder)** defines the same models with a typed builder API. There is no separate language to learn, your editor type-checks the schema as you write it, and you can compose and reuse definitions with ordinary TypeScript modules. ## What the contract contains @@ -75,24 +81,32 @@ It contains no rows, no credentials, and no connection details, so committing th } > Write the contract as a Prisma schema file. } > Define the same models with the typed contract builder. } > What is inside contract.json and contract.d.ts, and how the hashes work. + } + > + How Prisma Next checks that your database supports what the contract + needs. + diff --git a/apps/docs/content/docs/orm/next/contract/typescript.mdx b/apps/docs/content/docs/(index)/next/contract-authoring/typescript-schema-builder.mdx similarity index 69% rename from apps/docs/content/docs/orm/next/contract/typescript.mdx rename to apps/docs/content/docs/(index)/next/contract-authoring/typescript-schema-builder.mdx index 794bc24622..e689aae054 100644 --- a/apps/docs/content/docs/orm/next/contract/typescript.mdx +++ b/apps/docs/content/docs/(index)/next/contract-authoring/typescript-schema-builder.mdx @@ -1,19 +1,31 @@ --- title: Author in TypeScript description: Define the Prisma Next contract with a typed builder API instead of a schema file. Same models, same artifacts, no separate language. -url: /orm/next/contract/typescript +url: /next/contract-authoring/typescript-schema-builder metaTitle: Author the Prisma Next contract in TypeScript metaDescription: Learn how to define a Prisma Next contract in TypeScript with defineContract, including fields, relations, enums, storage mapping, and extension types. --- -TypeScript authoring defines the Prisma Next [data contract](/orm/next/contract) in code. Instead of a `.prisma` file, you write `prisma/contract.ts` with the `defineContract` builder, and [`prisma-next contract emit`](/cli/next/contract-emit) produces exactly the same `contract.json` and `contract.d.ts` a PSL schema would. +TypeScript authoring defines the Prisma Next [data contract](/next/contract-authoring/the-data-contract) in code. Instead of a `.prisma` file, you write `prisma/contract.ts` with the `defineContract` builder, and [`prisma-next contract emit`](/cli/next/contract-emit) produces exactly the same `contract.json` and `contract.d.ts` a PSL schema would. -The trade is straightforward: you give up PSL's compact syntax and get the full TypeScript toolchain in return. Your editor type-checks model definitions as you write them, invalid references are compile errors instead of emit-time diagnostics, and you can split, compose, and reuse definitions with ordinary modules. +## When to choose TypeScript over PSL + +The trade is straightforward: you give up PSL's compact syntax and get the full TypeScript toolchain in return. Choose TypeScript when: + +- you want the schema type-checked as you write it, with invalid references caught as compile errors instead of emit-time diagnostics +- you want to split, compose, or reuse model definitions with ordinary modules +- your team prefers one language for everything over learning PSL + +Choose [PSL](/next/contract-authoring/psl-syntax) when you want the most compact syntax, or when you start from an existing database, since [`prisma-next contract infer`](/cli/next/contract-infer) writes PSL. ## Point the config at the contract file The config's `contract` path names the source of truth. A `.ts` extension selects TypeScript authoring: + + + + ```typescript title="prisma-next.config.ts" import { defineConfig } from "@prisma-next/postgres/config"; @@ -22,9 +34,29 @@ export default defineConfig({ }); ``` + + + + +```typescript title="prisma-next.config.ts" +import { defineConfig } from "@prisma-next/mongo/config"; + +export default defineConfig({ + contract: "./prisma/contract.ts", +}); +``` + + + + + ## A complete contract -The builder comes from your database's target package. For PostgreSQL that is `@prisma-next/postgres/contract-builder`: +The builder comes from your database's target package: `@prisma-next/postgres/contract-builder` for PostgreSQL, `@prisma-next/mongo/contract-builder` for MongoDB. + + + + ```typescript title="prisma/contract.ts" import pgvector from "@prisma-next/extension-pgvector/pack"; @@ -96,8 +128,55 @@ export const contract = defineContract( ); ``` + + + + +```typescript title="prisma/contract.ts" +import { defineContract, field, model, rel } from "@prisma-next/mongo/contract-builder"; + +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" }), + }, +}); + +const Post = model("Post", { + collection: "posts", + fields: { + _id: field.objectId(), + authorId: field.objectId(), + title: field.string(), + publishedAt: field.date().optional(), + }, + relations: { + author: rel.belongsTo(User, { from: "authorId", to: User.ref("_id") }), + }, +}); + +export const contract = defineContract({ + models: { + User, + Post, + }, +}); +``` + + + + + Run `npx prisma-next contract emit` after any change to refresh the artifacts. +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`, scalar helpers are `field.string()`, `field.int32()`, `field.double()`, `field.bool()`, and `field.date()`, and 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. @@ -106,6 +185,8 @@ The options object declares what the contract is built from, most importantly `e 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 (which is why `type.pgvector` exists in the example, and only when `pgvector` is in `extensionPacks`). 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. + ## Fields `field` provides typed constructors for common field shapes: @@ -205,9 +286,9 @@ Configuration that legitimately varies per environment, such as the database URL ## Parity with PSL -TypeScript and [PSL](/orm/next/contract/psl) authoring emit the same canonical artifact for an equivalent schema. You can move between the modes without any downstream change, but a project 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](/next/contract-authoring/psl-syntax) authoring emit the same canonical artifact for an equivalent schema. You can move between the modes without any downstream change, but a project 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. ## Next steps -- Emit and inspect [the artifacts](/orm/next/contract/artifacts) the contract produces. +- Emit and inspect [the artifacts](/next/contract-authoring/the-contract-artifact) the contract produces. - Apply the contract to a database with [`prisma-next db init`](/cli/next/db-init) or plan changes with [`prisma-next migration plan`](/cli/next/migration-plan). diff --git a/apps/docs/content/docs/orm/next/index.mdx b/apps/docs/content/docs/orm/next/index.mdx index 056db5f3f4..acecda9693 100644 --- a/apps/docs/content/docs/orm/next/index.mdx +++ b/apps/docs/content/docs/orm/next/index.mdx @@ -85,7 +85,7 @@ Prisma Next is in active development, and full conceptual and reference document first query. } > diff --git a/apps/docs/content/docs/orm/next/meta.json b/apps/docs/content/docs/orm/next/meta.json index 95c945d3eb..ae5f3be609 100644 --- a/apps/docs/content/docs/orm/next/meta.json +++ b/apps/docs/content/docs/orm/next/meta.json @@ -4,11 +4,6 @@ "root": true, "pages": [ "---Introduction---", - "index", - "---Data contract---", - "contract/index", - "contract/psl", - "contract/typescript", - "contract/artifacts" + "index" ] } diff --git a/apps/docs/vercel.json b/apps/docs/vercel.json index aff111ca08..99e1be92ff 100644 --- a/apps/docs/vercel.json +++ b/apps/docs/vercel.json @@ -6084,6 +6084,31 @@ "source": "/docs/cli/console/platform", "destination": "/docs/cli/console", "permanent": true + }, + { + "source": "/docs/orm/prisma-schema", + "destination": "/docs/next/contract-authoring/the-data-contract", + "permanent": false + }, + { + "source": "/docs/orm/prisma-schema/overview", + "destination": "/docs/next/contract-authoring/psl-syntax", + "permanent": false + }, + { + "source": "/docs/orm/prisma-schema/overview/data-sources", + "destination": "/docs/next/contract-authoring/psl-syntax", + "permanent": false + }, + { + "source": "/docs/orm/prisma-schema/overview/location", + "destination": "/docs/next/contract-authoring/psl-syntax", + "permanent": false + }, + { + "source": "/docs/orm/prisma-client/type-safety", + "destination": "/docs/next/contract-authoring/the-data-contract", + "permanent": false } ] } From 174cea931641afa8d3293d54088a8833d95e97e1 Mon Sep 17 00:00:00 2001 From: Ankur Datta <64993082+ankur-arch@users.noreply.github.com> Date: Tue, 7 Jul 2026 16:42:28 +0200 Subject: [PATCH 3/6] docs(contract-authoring): move to /orm/next, park redirects, wire the section Per review: - The five pages move from the getting-started tree to the ORM Next version tree: content/docs/orm/next/contract-authoring, served at /orm/next/contract-authoring/*, registered as a Contract Authoring section between Data Modeling and Fundamentals. The getting-started meta change is reverted. - The vercel.json redirects are reverted and parked, commented out, in the shared "Prisma Next URL cutover (DR-8687)" block in next.config.mjs with /orm/next destinations. They ship when the docs go GA, not now. - Cross-links into the merged sections: data modeling and Fundamentals from the data-contract page, relational/MongoDB modeling from the PSL relations syntax, Using extensions from capabilities. All internal links updated to the new paths (markdown and card hrefs). - Every page ends with the standard "Prompt your coding agent" section pointing at the Prisma Next skills, with prompts scoped to the page (prisma-next-contract). - Per-page Early Access notes removed for consistency with the other orm/next sections (the tree carries the badge); cspell terms added. Co-Authored-By: Claude Fable 5 --- apps/docs/content/docs/(index)/meta.json | 1 - .../next/contract-authoring/capabilities.mdx | 17 ++++++-- .../next/contract-authoring/meta.json | 0 .../next/contract-authoring/psl-syntax.mdx | 18 ++++++-- .../the-contract-artifact.mdx | 17 +++++--- .../contract-authoring/the-data-contract.mdx | 31 ++++++++------ .../typescript-schema-builder.mdx | 17 +++++--- apps/docs/content/docs/orm/next/index.mdx | 2 +- apps/docs/content/docs/orm/next/meta.json | 5 +-- apps/docs/cspell.json | 41 +++++++++++-------- apps/docs/next.config.mjs | 7 ++++ apps/docs/vercel.json | 25 ----------- 12 files changed, 102 insertions(+), 79 deletions(-) rename apps/docs/content/docs/{(index) => orm}/next/contract-authoring/capabilities.mdx (75%) rename apps/docs/content/docs/{(index) => orm}/next/contract-authoring/meta.json (100%) rename apps/docs/content/docs/{(index) => orm}/next/contract-authoring/psl-syntax.mdx (88%) rename apps/docs/content/docs/{(index) => orm}/next/contract-authoring/the-contract-artifact.mdx (88%) rename apps/docs/content/docs/{(index) => orm}/next/contract-authoring/the-data-contract.mdx (77%) rename apps/docs/content/docs/{(index) => orm}/next/contract-authoring/typescript-schema-builder.mdx (87%) diff --git a/apps/docs/content/docs/(index)/meta.json b/apps/docs/content/docs/(index)/meta.json index 017392b1b2..c7375e791a 100644 --- a/apps/docs/content/docs/(index)/meta.json +++ b/apps/docs/content/docs/(index)/meta.json @@ -11,7 +11,6 @@ "---Prisma Next---", "next/quickstart", "next/add-to-existing-project", - "next/contract-authoring", "---Prisma ORM---", "...prisma-orm", "---Prisma Postgres---", diff --git a/apps/docs/content/docs/(index)/next/contract-authoring/capabilities.mdx b/apps/docs/content/docs/orm/next/contract-authoring/capabilities.mdx similarity index 75% rename from apps/docs/content/docs/(index)/next/contract-authoring/capabilities.mdx rename to apps/docs/content/docs/orm/next/contract-authoring/capabilities.mdx index 35a44ab177..7bea75306d 100644 --- a/apps/docs/content/docs/(index)/next/contract-authoring/capabilities.mdx +++ b/apps/docs/content/docs/orm/next/contract-authoring/capabilities.mdx @@ -1,12 +1,12 @@ --- title: Capabilities description: Capabilities record what your database stack supports, so Prisma Next can reject unsupported features early with a clear error. -url: /next/contract-authoring/capabilities +url: /orm/next/contract-authoring/capabilities metaTitle: Capabilities in Prisma Next metaDescription: How Prisma Next capabilities are derived from the target, adapter, and extension packs, what they gate, and what happens when a required capability is missing. --- -Capabilities are flags in the [data contract](/next/contract-authoring/the-data-contract) that record what your database stack supports: whether the target can do lateral joins, `RETURNING` clauses, native value sets, vector distance operations, and so on. Prisma Next consults them before using a gated feature, so an unsupported feature fails early with an error that names the missing capability, instead of failing as a database error mid-query. +Capabilities are flags in the [data contract](/orm/next/contract-authoring/the-data-contract) that record what your database stack supports: whether the target can do lateral joins, `RETURNING` clauses, native value sets, vector distance operations, and so on. Prisma Next consults them before using a gated feature, so an unsupported feature fails early with an error that names the missing capability, instead of failing as a database error mid-query. ## Where capabilities come from @@ -78,9 +78,18 @@ MongoDB currently declares no capability keys: the capability system mostly diff ## 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 the separate hash-and-marker mechanism described in [the contract artifact](/next/contract-authoring/the-contract-artifact#the-content-hashes): [`prisma-next db verify`](/cli/next/db-verify) checks that the database matches the contract's schema and profile. +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 the separate hash-and-marker mechanism described in [the contract artifact](/orm/next/contract-authoring/the-contract-artifact#the-content-hashes): [`prisma-next db verify`](/cli/next/db-verify) checks that the database matches the contract's schema and profile. + +Extensions are the main source of capabilities beyond the core; [Using extensions](/orm/next/extensions/using-extensions) shows the full install-to-query flow. + +## Prompt your coding agent + +Projects scaffolded with `create-prisma` install [Prisma Next skills](/ai/tools/skills) for your coding agent; the `prisma-next-contract` skill covers this page. 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." ## Next steps -- See where the `capabilities` block sits in [the contract artifact](/next/contract-authoring/the-contract-artifact). +- See where the `capabilities` block sits in [the contract artifact](/orm/next/contract-authoring/the-contract-artifact). - Compose extension packs, and the capabilities they bring, in the [CLI configuration](/cli/next/configuration). diff --git a/apps/docs/content/docs/(index)/next/contract-authoring/meta.json b/apps/docs/content/docs/orm/next/contract-authoring/meta.json similarity index 100% rename from apps/docs/content/docs/(index)/next/contract-authoring/meta.json rename to apps/docs/content/docs/orm/next/contract-authoring/meta.json diff --git a/apps/docs/content/docs/(index)/next/contract-authoring/psl-syntax.mdx b/apps/docs/content/docs/orm/next/contract-authoring/psl-syntax.mdx similarity index 88% rename from apps/docs/content/docs/(index)/next/contract-authoring/psl-syntax.mdx rename to apps/docs/content/docs/orm/next/contract-authoring/psl-syntax.mdx index a814853d13..6631eee66c 100644 --- a/apps/docs/content/docs/(index)/next/contract-authoring/psl-syntax.mdx +++ b/apps/docs/content/docs/orm/next/contract-authoring/psl-syntax.mdx @@ -1,12 +1,12 @@ --- title: Author in PSL description: Write the Prisma Next contract as a Prisma schema file, using the schema language you already know plus the Prisma Next additions. -url: /next/contract-authoring/psl-syntax +url: /orm/next/contract-authoring/psl-syntax metaTitle: Author the Prisma Next contract in PSL metaDescription: Learn how to write a Prisma Next contract in the Prisma schema language, including named types, enums, value objects, relations, and extension types. --- -PSL is the default authoring mode for the Prisma Next [data contract](/next/contract-authoring/the-data-contract). You write a Prisma schema file, usually `prisma/contract.prisma`, and [`prisma-next contract emit`](/cli/next/contract-emit) turns it into `contract.json` and `contract.d.ts`. +PSL is the default authoring mode for the Prisma Next [data contract](/orm/next/contract-authoring/the-data-contract). You write a Prisma schema file, usually `prisma/contract.prisma`, and [`prisma-next contract emit`](/cli/next/contract-emit) turns it into `contract.json` and `contract.d.ts`. 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 Next additions in detail: named types, typed enums, value objects, model inheritance, and extension types. @@ -329,8 +329,18 @@ Emission validates extension types against the composed packs: a type from a pac If the database already exists, don't write the schema by hand. [`prisma-next contract infer`](/cli/next/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/next/data-modeling/relational-databases) and [MongoDB data modeling](/orm/next/data-modeling/mongodb). + +## Prompt your coding agent + +Projects scaffolded with `create-prisma` install [Prisma Next skills](/ai/tools/skills) for your coding agent; the `prisma-next-contract` skill covers this page. Ask your agent to: + +- "Using the prisma-next-contract 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." +- "Move the auth models into their own namespace." + ## Next steps -- Emit and inspect [the artifacts](/next/contract-authoring/the-contract-artifact) the schema produces. -- Prefer defining models in code? See [authoring in TypeScript](/next/contract-authoring/typescript-schema-builder). +- Emit and inspect [the artifacts](/orm/next/contract-authoring/the-contract-artifact) the schema produces. +- Prefer defining models in code? See [authoring in TypeScript](/orm/next/contract-authoring/typescript-schema-builder). - Apply the contract to a database with [`prisma-next db init`](/cli/next/db-init) or plan changes with [`prisma-next migration plan`](/cli/next/migration-plan). diff --git a/apps/docs/content/docs/(index)/next/contract-authoring/the-contract-artifact.mdx b/apps/docs/content/docs/orm/next/contract-authoring/the-contract-artifact.mdx similarity index 88% rename from apps/docs/content/docs/(index)/next/contract-authoring/the-contract-artifact.mdx rename to apps/docs/content/docs/orm/next/contract-authoring/the-contract-artifact.mdx index 67ac2e2e48..408c8ab0ba 100644 --- a/apps/docs/content/docs/(index)/next/contract-authoring/the-contract-artifact.mdx +++ b/apps/docs/content/docs/orm/next/contract-authoring/the-contract-artifact.mdx @@ -1,7 +1,7 @@ --- title: The emitted artifacts description: contract.json and contract.d.ts are the deterministic artifacts every other part of Prisma Next consumes. Here is what is inside them. -url: /next/contract-authoring/the-contract-artifact +url: /orm/next/contract-authoring/the-contract-artifact metaTitle: The Prisma Next 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. --- @@ -13,13 +13,13 @@ metaDescription: What contract.json and contract.d.ts contain, how deterministic | `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.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](/next/contract-authoring/psl-syntax) or [TypeScript](/next/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/next/contract-authoring/psl-syntax) or [TypeScript](/orm/next/contract-authoring/typescript-schema-builder) source and re-run `contract emit`. The files carry a generated-file notice that says exactly this. ## 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. -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](/next/contract-authoring/typescript-schema-builder#keep-the-contract-file-pure) lists the concrete rules. +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/next/contract-authoring/typescript-schema-builder#keep-the-contract-file-pure) lists the concrete rules. ## The content hashes @@ -106,7 +106,7 @@ The sections, top to bottom: - **`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 Next applies before writes, such as UUID generation, kept out of the database's DDL. -- **`capabilities`** and **`extensionPacks`**: 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](/next/contract-authoring/capabilities). +- **`capabilities`** and **`extensionPacks`**: 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/next/contract-authoring/capabilities). ## Inside `contract.d.ts` @@ -148,9 +148,16 @@ At startup, the client carries the contract's hashes and verifies them against t 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. +## Prompt your coding agent + +Projects scaffolded with `create-prisma` install [Prisma Next skills](/ai/tools/skills) for your coding agent; the `prisma-next-contract` skill covers this page. 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." + ## Next steps -- Learn how the contract's [capabilities](/next/contract-authoring/capabilities) gate database features. +- Learn how the contract's [capabilities](/orm/next/contract-authoring/capabilities) gate database features. - Apply the contract to a fresh database with [`prisma-next db init`](/cli/next/db-init). - Check a live database against the contract with [`prisma-next db verify`](/cli/next/db-verify). - Plan schema changes between contract versions with [`prisma-next migration plan`](/cli/next/migration-plan). diff --git a/apps/docs/content/docs/(index)/next/contract-authoring/the-data-contract.mdx b/apps/docs/content/docs/orm/next/contract-authoring/the-data-contract.mdx similarity index 77% rename from apps/docs/content/docs/(index)/next/contract-authoring/the-data-contract.mdx rename to apps/docs/content/docs/orm/next/contract-authoring/the-data-contract.mdx index bbf89a216d..7f3dd32cd9 100644 --- a/apps/docs/content/docs/(index)/next/contract-authoring/the-data-contract.mdx +++ b/apps/docs/content/docs/orm/next/contract-authoring/the-data-contract.mdx @@ -1,7 +1,7 @@ --- title: The data contract description: The data contract is the single description of your data model and its storage layout. Everything in Prisma Next is typed, planned, and verified against it. -url: /next/contract-authoring/the-data-contract +url: /orm/next/contract-authoring/the-data-contract metaTitle: The Prisma Next data contract metaDescription: What the Prisma Next data contract is, how PSL and TypeScript authoring produce the same artifacts, and how the contract drives typing, migrations, and verification. --- @@ -14,12 +14,6 @@ In Prisma Next, the **contract** is what you write in your code, and the **schem ::: -:::note[Prisma Next is in Early Access] - -Prisma Next is the next major version of Prisma ORM, available now in Early Access. It's the cutting-edge version of Prisma ORM and will become the future of Prisma, so we'd love for you to try it, explore what's new, and [share your feedback in Discord](https://pris.ly/discord). - -::: - ## 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. @@ -62,8 +56,8 @@ A project declares exactly one source of truth: the file named by `contract` in Choose based on how you want to write schemas: -- **[PSL](/next/contract-authoring/psl-syntax)** is the Prisma schema language you already know, extended for Prisma Next. It is the default for scaffolded projects, and it is what [`prisma-next contract infer`](/cli/next/contract-infer) writes when you start from an existing database. -- **[TypeScript](/next/contract-authoring/typescript-schema-builder)** defines the same models with a typed builder API. There is no separate language to learn, your editor type-checks the schema as you write it, and you can compose and reuse definitions with ordinary TypeScript modules. +- **[PSL](/orm/next/contract-authoring/psl-syntax)** is the Prisma schema language you already know, extended for Prisma Next. It is the default for scaffolded projects, and it is what [`prisma-next contract infer`](/cli/next/contract-infer) writes when you start from an existing database. +- **[TypeScript](/orm/next/contract-authoring/typescript-schema-builder)** defines the same models with a typed builder API. There is no separate language to learn, your editor type-checks the schema as you write it, and you can compose and reuse definitions with ordinary TypeScript modules. ## What the contract contains @@ -77,32 +71,43 @@ The contract describes structure, not data: 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. +## Prompt your coding agent + +Projects scaffolded with `create-prisma` install [Prisma Next skills](/ai/tools/skills) for your coding agent; the `prisma-next-contract` skill covers this page. Ask your agent to: + +- "Using the prisma-next-contract skill, explain what our contract.json currently declares." +- "Add an Invoice model to the contract and emit it." +- "Check whether our database still satisfies the contract." + ## Next steps +- [Model your data](/orm/next/data-modeling) before writing the contract: models, keys, and relations. +- [Query against the contract](/orm/next/fundamentals/reading-data): every result is typed by what you authored here. + } > Write the contract as a Prisma schema file. } > Define the same models with the typed contract builder. } > What is inside contract.json and contract.d.ts, and how the hashes work. } > diff --git a/apps/docs/content/docs/(index)/next/contract-authoring/typescript-schema-builder.mdx b/apps/docs/content/docs/orm/next/contract-authoring/typescript-schema-builder.mdx similarity index 87% rename from apps/docs/content/docs/(index)/next/contract-authoring/typescript-schema-builder.mdx rename to apps/docs/content/docs/orm/next/contract-authoring/typescript-schema-builder.mdx index e689aae054..33cecec7eb 100644 --- a/apps/docs/content/docs/(index)/next/contract-authoring/typescript-schema-builder.mdx +++ b/apps/docs/content/docs/orm/next/contract-authoring/typescript-schema-builder.mdx @@ -1,12 +1,12 @@ --- title: Author in TypeScript description: Define the Prisma Next contract with a typed builder API instead of a schema file. Same models, same artifacts, no separate language. -url: /next/contract-authoring/typescript-schema-builder +url: /orm/next/contract-authoring/typescript-schema-builder metaTitle: Author the Prisma Next contract in TypeScript metaDescription: Learn how to define a Prisma Next contract in TypeScript with defineContract, including fields, relations, enums, storage mapping, and extension types. --- -TypeScript authoring defines the Prisma Next [data contract](/next/contract-authoring/the-data-contract) in code. Instead of a `.prisma` file, you write `prisma/contract.ts` with the `defineContract` builder, and [`prisma-next contract emit`](/cli/next/contract-emit) produces exactly the same `contract.json` and `contract.d.ts` a PSL schema would. +TypeScript authoring defines the Prisma Next [data contract](/orm/next/contract-authoring/the-data-contract) in code. Instead of a `.prisma` file, you write `prisma/contract.ts` with the `defineContract` builder, and [`prisma-next contract emit`](/cli/next/contract-emit) produces exactly the same `contract.json` and `contract.d.ts` a PSL schema would. ## When to choose TypeScript over PSL @@ -16,7 +16,7 @@ The trade is straightforward: you give up PSL's compact syntax and get the full - you want to split, compose, or reuse model definitions with ordinary modules - your team prefers one language for everything over learning PSL -Choose [PSL](/next/contract-authoring/psl-syntax) when you want the most compact syntax, or when you start from an existing database, since [`prisma-next contract infer`](/cli/next/contract-infer) writes PSL. +Choose [PSL](/orm/next/contract-authoring/psl-syntax) when you want the most compact syntax, or when you start from an existing database, since [`prisma-next contract infer`](/cli/next/contract-infer) writes PSL. ## Point the config at the contract file @@ -286,9 +286,16 @@ Configuration that legitimately varies per environment, such as the database URL ## Parity with PSL -TypeScript and [PSL](/next/contract-authoring/psl-syntax) authoring emit the same canonical artifact for an equivalent schema. You can move between the modes without any downstream change, but a project 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/next/contract-authoring/psl-syntax) authoring emit the same canonical artifact for an equivalent schema. You can move between the modes without any downstream change, but a project 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. + +## Prompt your coding agent + +Projects scaffolded with `create-prisma` install [Prisma Next skills](/ai/tools/skills) for your coding agent; the `prisma-next-contract` skill covers this page. Ask your agent to: + +- "Convert this contract.prisma to the TypeScript schema builder." +- "Using the prisma-next-contract skill, add a unique index to the email field in our TypeScript schema." ## Next steps -- Emit and inspect [the artifacts](/next/contract-authoring/the-contract-artifact) the contract produces. +- Emit and inspect [the artifacts](/orm/next/contract-authoring/the-contract-artifact) the contract produces. - Apply the contract to a database with [`prisma-next db init`](/cli/next/db-init) or plan changes with [`prisma-next migration plan`](/cli/next/migration-plan). diff --git a/apps/docs/content/docs/orm/next/index.mdx b/apps/docs/content/docs/orm/next/index.mdx index 25a23cdbc1..4b94e89e42 100644 --- a/apps/docs/content/docs/orm/next/index.mdx +++ b/apps/docs/content/docs/orm/next/index.mdx @@ -93,7 +93,7 @@ Prisma Next is in active development, and full conceptual and reference document first query. } > diff --git a/apps/docs/content/docs/orm/next/meta.json b/apps/docs/content/docs/orm/next/meta.json index 83bff2fb31..f35013acbb 100644 --- a/apps/docs/content/docs/orm/next/meta.json +++ b/apps/docs/content/docs/orm/next/meta.json @@ -7,13 +7,12 @@ "index", "---Data Modeling---", "...data-modeling", - + "---Contract Authoring---", + "...contract-authoring", "---Fundamentals---", "...fundamentals", - "---Middleware---", "...middleware", - "---Extensions---", "...extensions" ] diff --git a/apps/docs/cspell.json b/apps/docs/cspell.json index cce0b6b964..53f515f88c 100644 --- a/apps/docs/cspell.json +++ b/apps/docs/cspell.json @@ -4,14 +4,11 @@ "language": "en", "words": [ "ABAC", - "Activeusers", "accountid", + "Activeusers", "Aiven", "amcheck", "amet", - "lanczos", - "lanczos3", - "EXIF", "Amplication", "Ania", "anotherproduct", @@ -22,11 +19,11 @@ "Atrue", "authjs", "autoinc", - "autosuspend", "autoincrement", "AUTOINCREMENT", "autoincrementing", "AUTOINSTALL", + "autosuspend", "baselining", "Baselining", "behaviour", @@ -34,10 +31,6 @@ "betterauth", "bigserial", "BIGSERIAL", - "btree", - "btrees", - "Btree", - "Btrees", "bindefault", "biograpy", "blobshape", @@ -45,6 +38,10 @@ "Bobo", "Bpchar", "bridg", + "btree", + "Btree", + "btrees", + "Btrees", "Buildpacks", "bunx", "Burk", @@ -82,27 +79,28 @@ "dbml", "DBML", "dbname", + "diffable", "distancesphere", - "dogfooding", "Distroless", "distros", "Dmmf", "Dockerfiles", + "dogfooding", "dotenvx", - "dwithin", "Dreamies", - "geospatial", + "dwithin", "earthdistance", "ecommerce", "Ecommerce", "edouardb", "elysia", "Elysia", + "emailverified", "Emelie", "Enya", "epsg", - "emailverified", "everytime", + "EXIF", "extralight", "favorited", "Felinecitas", @@ -120,6 +118,7 @@ "geofencing", "Geofencing", "geometrycollection", + "geospatial", "glassmorphism", "Glassmorphism", "gofmt", @@ -155,6 +154,8 @@ "Kwame", "kysely", "Kysely", + "lanczos", + "lanczos3", "lastname", "lastproduct", "lexmata", @@ -205,8 +206,8 @@ "mysqldump", "napi", "NDEKTSV", - "neondb", "neondatabase", + "neondb", "Neward", "nextauth", "nextval", @@ -234,8 +235,8 @@ "packagemanager", "Pacman", "pageinspect", - "pegasusheavy", "paradedb", + "pegasusheavy", "permitio", "pgbouncer", "pgcat", @@ -293,6 +294,7 @@ "rootca", "RRFFQ", "RSPCA", + "s3cret", "Sabelle", "safeql", "Saqui", @@ -318,7 +320,6 @@ "sqlcommenter", "srid", "SRID", - "s3cret", "sslaccept", "sslcert", "sslidentity", @@ -379,13 +380,15 @@ "unikernel", "unikernels", "unindexed", - "untick", "unixepoch", + "unmigrated", + "untick", "uploadthing", "UPLOADTHING", "upserting", "Upserting", "upserts", + "uuidv", "Valkey", "VARBINARY", "varbit", @@ -415,6 +418,8 @@ "pattern": "/videoId=\"[A-Za-z0-9_-]{6,}\"/g" } ], - "ignoreRegExpList": ["mdxVideoIdAttribute"], + "ignoreRegExpList": [ + "mdxVideoIdAttribute" + ], "ignorePaths": [] } diff --git a/apps/docs/next.config.mjs b/apps/docs/next.config.mjs index 3fae170b2e..4e30943e82 100644 --- a/apps/docs/next.config.mjs +++ b/apps/docs/next.config.mjs @@ -315,6 +315,13 @@ const config = { // { source: "/orm/prisma-client/queries/transactions", destination: "/orm/next/fundamentals/transactions", permanent: false }, // { source: "/orm/prisma-client/using-raw-sql", destination: "/orm/next/fundamentals/advanced-queries", permanent: false }, // + // DR-8680 Contract authoring: + // { source: "/orm/prisma-schema", destination: "/orm/next/contract-authoring/the-data-contract", permanent: false }, + // { source: "/orm/prisma-schema/overview", destination: "/orm/next/contract-authoring/psl-syntax", permanent: false }, + // { source: "/orm/prisma-schema/overview/data-sources", destination: "/orm/next/contract-authoring/psl-syntax", permanent: false }, + // { source: "/orm/prisma-schema/overview/location", destination: "/orm/next/contract-authoring/psl-syntax", permanent: false }, + // { source: "/orm/prisma-client/type-safety", destination: "/orm/next/contract-authoring/the-data-contract", permanent: false }, + // // No Prisma Next equivalent yet (stay on the Prisma 7 tree, flag to the // SEO owner at cutover): /orm/prisma-client/queries/full-text-search, // /orm/prisma-client/queries/advanced/query-optimization-performance, diff --git a/apps/docs/vercel.json b/apps/docs/vercel.json index 99e1be92ff..aff111ca08 100644 --- a/apps/docs/vercel.json +++ b/apps/docs/vercel.json @@ -6084,31 +6084,6 @@ "source": "/docs/cli/console/platform", "destination": "/docs/cli/console", "permanent": true - }, - { - "source": "/docs/orm/prisma-schema", - "destination": "/docs/next/contract-authoring/the-data-contract", - "permanent": false - }, - { - "source": "/docs/orm/prisma-schema/overview", - "destination": "/docs/next/contract-authoring/psl-syntax", - "permanent": false - }, - { - "source": "/docs/orm/prisma-schema/overview/data-sources", - "destination": "/docs/next/contract-authoring/psl-syntax", - "permanent": false - }, - { - "source": "/docs/orm/prisma-schema/overview/location", - "destination": "/docs/next/contract-authoring/psl-syntax", - "permanent": false - }, - { - "source": "/docs/orm/prisma-client/type-safety", - "destination": "/docs/next/contract-authoring/the-data-contract", - "permanent": false } ] } From 517526c59adfe726cdef7e556552cf27cae0391b Mon Sep 17 00:00:00 2001 From: Ankur Datta <64993082+ankur-arch@users.noreply.github.com> Date: Tue, 7 Jul 2026 16:57:50 +0200 Subject: [PATCH 4/6] docs(contract-authoring): scope the namespace prompt to what the page teaches Live validation confirmed the page's enum syntax (member values compile and the applied column carries the documented CHECK constraint) and both TypeScript builder examples (the full PostgreSQL contract with the pgvector pack emits and initializes against a live Prisma Postgres database; the MongoDB contract emits). The agent prompt that referenced namespaces, which this page does not teach, now asks for a composite unique constraint instead. Co-Authored-By: Claude Fable 5 --- .../content/docs/orm/next/contract-authoring/psl-syntax.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/content/docs/orm/next/contract-authoring/psl-syntax.mdx b/apps/docs/content/docs/orm/next/contract-authoring/psl-syntax.mdx index 6631eee66c..a8152008cf 100644 --- a/apps/docs/content/docs/orm/next/contract-authoring/psl-syntax.mdx +++ b/apps/docs/content/docs/orm/next/contract-authoring/psl-syntax.mdx @@ -337,7 +337,7 @@ Projects scaffolded with `create-prisma` install [Prisma Next skills](/ai/tools/ - "Using the prisma-next-contract 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." -- "Move the auth models into their own namespace." +- "Give the Post model a composite unique constraint on userId and title." ## Next steps From b8690d529e2724fc995e02ab3ab58405c63ff06d Mon Sep 17 00:00:00 2001 From: Ankur Datta <64993082+ankur-arch@users.noreply.github.com> Date: Tue, 7 Jul 2026 17:16:38 +0200 Subject: [PATCH 5/6] docs(contract-authoring): teachable openings, aligned with data modeling Style and coherence pass across the Prisma Next tree: - The data contract page opens plainly: what the contract is, a blog example, then the package-lock analogy, instead of a definition paragraph doing three jobs at once. Capabilities opens with the problem (databases differ) before the mechanism. The artifact page gets a one-sentence lead before its reference table. - psl-syntax now uses the same framing as the merged data modeling section: "Base models and variants" instead of "Model inheritance", the variant prose covers both storage layouts (with and without @@map) and links the page that teaches choosing between them, and value objects link the embed-or-reference decision on MongoDB. Co-Authored-By: Claude Fable 5 --- .../docs/orm/next/contract-authoring/capabilities.mdx | 4 +++- .../docs/orm/next/contract-authoring/psl-syntax.mdx | 8 ++++---- .../orm/next/contract-authoring/the-contract-artifact.mdx | 4 +++- .../orm/next/contract-authoring/the-data-contract.mdx | 6 +++++- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/apps/docs/content/docs/orm/next/contract-authoring/capabilities.mdx b/apps/docs/content/docs/orm/next/contract-authoring/capabilities.mdx index 7bea75306d..9c249b4b24 100644 --- a/apps/docs/content/docs/orm/next/contract-authoring/capabilities.mdx +++ b/apps/docs/content/docs/orm/next/contract-authoring/capabilities.mdx @@ -6,7 +6,9 @@ metaTitle: Capabilities in Prisma Next metaDescription: How Prisma Next capabilities are derived from the target, adapter, and extension packs, what they gate, and what happens when a required capability is missing. --- -Capabilities are flags in the [data contract](/orm/next/contract-authoring/the-data-contract) that record what your database stack supports: whether the target can do lateral joins, `RETURNING` clauses, native value sets, vector distance operations, and so on. Prisma Next consults them before using a gated feature, so an unsupported feature fails early with an error that names the missing capability, instead of failing as a database error mid-query. +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/next/contract-authoring/the-data-contract) records what yours supports. Prisma Next checks them before using a gated feature, so an unsupported feature fails early, with an error that names the missing capability, instead of surfacing as a database error mid-query. ## Where capabilities come from diff --git a/apps/docs/content/docs/orm/next/contract-authoring/psl-syntax.mdx b/apps/docs/content/docs/orm/next/contract-authoring/psl-syntax.mdx index a8152008cf..aec02bb127 100644 --- a/apps/docs/content/docs/orm/next/contract-authoring/psl-syntax.mdx +++ b/apps/docs/content/docs/orm/next/contract-authoring/psl-syntax.mdx @@ -8,7 +8,7 @@ metaDescription: Learn how to write a Prisma Next contract in the Prisma schema PSL is the default authoring mode for the Prisma Next [data contract](/orm/next/contract-authoring/the-data-contract). You write a Prisma schema file, usually `prisma/contract.prisma`, and [`prisma-next contract emit`](/cli/next/contract-emit) turns it into `contract.json` and `contract.d.ts`. -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 Next additions in detail: named types, typed enums, value objects, model inheritance, and extension types. +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 Next additions in detail: named types, typed enums, value objects, base models with variants, and extension types. ## Point the config at the schema @@ -231,7 +231,7 @@ model User { } ``` -Storage follows the database's nature: 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. +Storage follows the database's nature: 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/next/data-modeling/mongodb#embed-or-reference) covers it. ## Relations @@ -273,7 +273,7 @@ model PostTag { The emitted contract records the relation as `N:M` with the join table's columns, so queries can traverse `post.tags` directly. -## Model inheritance +## Base models and variants A base model declares a discriminator field, and each variant names its base and its discriminator value: @@ -296,7 +296,7 @@ model Bug { } ``` -Rows with `type = "bug"` are `Bug` records. On PostgreSQL, a variant's own fields live in the variant's own table (here `bug`), which shares the base model's primary key. On MongoDB, variants add their fields to documents in the base model's collection instead, so a variant declares `@@base` but no `@@map` of its own. +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/next/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. ## Extension types diff --git a/apps/docs/content/docs/orm/next/contract-authoring/the-contract-artifact.mdx b/apps/docs/content/docs/orm/next/contract-authoring/the-contract-artifact.mdx index 408c8ab0ba..e5ff97ee21 100644 --- a/apps/docs/content/docs/orm/next/contract-authoring/the-contract-artifact.mdx +++ b/apps/docs/content/docs/orm/next/contract-authoring/the-contract-artifact.mdx @@ -6,7 +6,9 @@ metaTitle: The Prisma Next 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. --- -[`prisma-next contract emit`](/cli/next/contract-emit) compiles your contract source into two files, written next to the source by default: +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-next contract emit`](/cli/next/contract-emit) writes both next to the source by default: | File | Contents | Consumed by | | --- | --- | --- | diff --git a/apps/docs/content/docs/orm/next/contract-authoring/the-data-contract.mdx b/apps/docs/content/docs/orm/next/contract-authoring/the-data-contract.mdx index 7f3dd32cd9..4aabc9c03e 100644 --- a/apps/docs/content/docs/orm/next/contract-authoring/the-data-contract.mdx +++ b/apps/docs/content/docs/orm/next/contract-authoring/the-data-contract.mdx @@ -6,7 +6,11 @@ metaTitle: The Prisma Next data contract metaDescription: What the Prisma Next data contract is, how PSL and TypeScript authoring produce the same artifacts, and how the contract drives typing, migrations, and verification. --- -The data contract is the single description of your application's data model and its storage layout: a machine-readable record of exactly what your application expects from its database, like a `package-lock.json` for your data. It does three jobs. Your queries are type-checked against it, migrations are planned as transitions between versions of it, and the database is verified against it before your code runs. +Every Prisma Next project has one description of its data: the models, their fields, and how they map to database tables. That description is the data contract. + +For example, a blog's contract declares a `User` and a `Post`, the fields each carries, and which tables store them. Prisma Next compiles that into a machine-readable artifact, and everything else checks itself 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. + +Think of it as a `package-lock.json` for your data: an exact, versioned record of what your application expects from its database. :::note[Contract vs. schema] From 3d482dbb56a0df3f7880ca0d007d5a78d5f8322e Mon Sep 17 00:00:00 2001 From: Alexey Orlenko's AI Agent Date: Tue, 7 Jul 2026 17:43:43 +0200 Subject: [PATCH 6/6] docs(contract-authoring): address review comments - Lead the data contract page with a tiny PSL example and frame PSL as the preferred authoring surface with the TypeScript builder as an escape hatch for composed or programmatically assembled schemas - Drop the editor-type-checking criterion from the TS-vs-PSL comparison; PSL gets the same real-time feedback from the language server, so it is not a differentiator - Fix wording: contract source, not schema source - Move the complete PSL example above the config section so the syntax page opens with schema code Signed-off-by: Alexey Orlenko's AI Agent --- .../next/contract-authoring/psl-syntax.mdx | 70 +++++++++---------- .../contract-authoring/the-data-contract.mdx | 31 +++++--- .../typescript-schema-builder.mdx | 9 ++- 3 files changed, 62 insertions(+), 48 deletions(-) diff --git a/apps/docs/content/docs/orm/next/contract-authoring/psl-syntax.mdx b/apps/docs/content/docs/orm/next/contract-authoring/psl-syntax.mdx index aec02bb127..f31667773b 100644 --- a/apps/docs/content/docs/orm/next/contract-authoring/psl-syntax.mdx +++ b/apps/docs/content/docs/orm/next/contract-authoring/psl-syntax.mdx @@ -6,44 +6,10 @@ metaTitle: Author the Prisma Next contract in PSL metaDescription: Learn how to write a Prisma Next contract in the Prisma schema language, including named types, enums, value objects, relations, and extension types. --- -PSL is the default authoring mode for the Prisma Next [data contract](/orm/next/contract-authoring/the-data-contract). You write a Prisma schema file, usually `prisma/contract.prisma`, and [`prisma-next contract emit`](/cli/next/contract-emit) turns it into `contract.json` and `contract.d.ts`. +PSL is the preferred way to author the Prisma Next [data contract](/orm/next/contract-authoring/the-data-contract). You write a Prisma schema file, usually `prisma/contract.prisma`, and [`prisma-next contract emit`](/cli/next/contract-emit) turns it into `contract.json` and `contract.d.ts`. 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 Next additions in detail: named types, typed enums, value objects, base models with variants, and extension types. -## Point the config at the schema - -The config's `contract` path names the source of truth. A `.prisma` extension selects PSL authoring: - - - - - -```typescript title="prisma-next.config.ts" -import { defineConfig } from "@prisma-next/postgres/config"; - -export default defineConfig({ - contract: "./prisma/contract.prisma", -}); -``` - - - - - -```typescript title="prisma-next.config.ts" -import { defineConfig } from "@prisma-next/mongo/config"; - -export default defineConfig({ - contract: "./prisma/contract.prisma", -}); -``` - - - - - -Scaffolded projects (`npx create-prisma@next`) already contain this wiring and a starter schema. - ## A complete contract @@ -147,6 +113,40 @@ model Post { Run `npx prisma-next contract emit` after any change to refresh the artifacts. +## Point the config at the schema + +The config's `contract` path names the source of truth. A `.prisma` extension selects PSL authoring: + + + + + +```typescript title="prisma-next.config.ts" +import { defineConfig } from "@prisma-next/postgres/config"; + +export default defineConfig({ + contract: "./prisma/contract.prisma", +}); +``` + + + + + +```typescript title="prisma-next.config.ts" +import { defineConfig } from "@prisma-next/mongo/config"; + +export default defineConfig({ + contract: "./prisma/contract.prisma", +}); +``` + + + + + +Scaffolded projects (`npx create-prisma@next`) already contain this wiring and a starter schema. + ## Models and fields Models declare fields with a type, an optional `?` marker, and attributes: diff --git a/apps/docs/content/docs/orm/next/contract-authoring/the-data-contract.mdx b/apps/docs/content/docs/orm/next/contract-authoring/the-data-contract.mdx index 4aabc9c03e..289c914fa7 100644 --- a/apps/docs/content/docs/orm/next/contract-authoring/the-data-contract.mdx +++ b/apps/docs/content/docs/orm/next/contract-authoring/the-data-contract.mdx @@ -8,7 +8,25 @@ metaDescription: What the Prisma Next data contract is, how PSL and TypeScript a Every Prisma Next project has one description of its data: the models, their fields, and how they map to database tables. That description is the data contract. -For example, a blog's contract declares a `User` and a `Post`, the fields each carries, and which tables store them. Prisma Next compiles that into a machine-readable artifact, and everything else checks itself 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. +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: + +```prisma title="prisma/contract.prisma" +model User { + id Int @id @default(autoincrement()) + email String @unique + posts Post[] +} + +model Post { + id Int @id @default(autoincrement()) + title String + userId Int + + user User @relation(fields: [userId], references: [id]) +} +``` + +Prisma Next compiles this into a machine-readable artifact, and everything else checks itself 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. Think of it as a `package-lock.json` for your data: an exact, versioned record of what your application expects from its database. @@ -22,7 +40,7 @@ In Prisma Next, the **contract** is what you write in your code, and the **schem 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 Next keeps the schema knowledge in the open. Your schema 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 Next 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. The contract also carries identity. Emission computes hashes over the contract's content, and [`prisma-next db sign`](/cli/next/db-sign) records them in a small marker inside the database. Before executing queries, Prisma Next compares the contract it was built against with the marker in the database it is talking to. A mismatch, such as a deploy against an unmigrated database, fails verification instead of failing at query time. @@ -54,14 +72,11 @@ This writes `contract.json` and `contract.d.ts` next to the source. From there, ## Two authoring modes, one artifact -PSL and TypeScript authoring are two front ends to the same contract. For an equivalent schema they emit the same `contract.json`, so nothing downstream, including migrations, verification, and the query APIs, cares which one you write. - -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. +**[PSL](/orm/next/contract-authoring/psl-syntax)** is the preferred authoring surface: a compact language purpose-built for describing data. It is what `create-prisma` scaffolds, what [`prisma-next contract infer`](/cli/next/contract-infer) writes when you start from an existing database, and what the examples throughout these docs use. -Choose based on how you want to write schemas: +For the cases PSL does not cover, defining models with a **[TypeScript builder](/orm/next/contract-authoring/typescript-schema-builder)** exists as an escape hatch: reach for it when model definitions must be composed, generated, or shared as ordinary TypeScript modules. -- **[PSL](/orm/next/contract-authoring/psl-syntax)** is the Prisma schema language you already know, extended for Prisma Next. It is the default for scaffolded projects, and it is what [`prisma-next contract infer`](/cli/next/contract-infer) writes when you start from an existing database. -- **[TypeScript](/orm/next/contract-authoring/typescript-schema-builder)** defines the same models with a typed builder API. There is no separate language to learn, your editor type-checks the schema as you write it, and you can compose and reuse definitions with ordinary TypeScript modules. +Both modes are front ends to the same contract. For an equivalent schema they emit the same `contract.json`, so nothing downstream, including migrations, verification, and the query APIs, cares which one you write, and 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. ## What the contract contains diff --git a/apps/docs/content/docs/orm/next/contract-authoring/typescript-schema-builder.mdx b/apps/docs/content/docs/orm/next/contract-authoring/typescript-schema-builder.mdx index 33cecec7eb..2c6434c85a 100644 --- a/apps/docs/content/docs/orm/next/contract-authoring/typescript-schema-builder.mdx +++ b/apps/docs/content/docs/orm/next/contract-authoring/typescript-schema-builder.mdx @@ -10,13 +10,12 @@ TypeScript authoring defines the Prisma Next [data contract](/orm/next/contract- ## When to choose TypeScript over PSL -The trade is straightforward: you give up PSL's compact syntax and get the full TypeScript toolchain in return. Choose TypeScript when: +[PSL](/orm/next/contract-authoring/psl-syntax) is the preferred authoring surface, and since both modes emit identical artifacts, you give up nothing by staying with it. The TypeScript builder is an escape hatch for the cases PSL does not cover. Reach for it when: -- you want the schema type-checked as you write it, with invalid references caught as compile errors instead of emit-time diagnostics -- you want to split, compose, or reuse model definitions with ordinary modules -- your team prefers one language for everything over learning PSL +- 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) -Choose [PSL](/orm/next/contract-authoring/psl-syntax) when you want the most compact syntax, or when you start from an existing database, since [`prisma-next contract infer`](/cli/next/contract-infer) writes PSL. +If neither applies, write PSL: it is more compact, and it is what `create-prisma` scaffolds and [`prisma-next contract infer`](/cli/next/contract-infer) writes. ## Point the config at the contract file