diff --git a/.claude/skills/content-write-blog/SKILL.md b/.claude/skills/content-write-blog/SKILL.md index c4ce1ce0b5b..0aef1ec1462 100644 --- a/.claude/skills/content-write-blog/SKILL.md +++ b/.claude/skills/content-write-blog/SKILL.md @@ -2,7 +2,7 @@ name: content-write-blog description: Use when the operator wants to write a blog post, draft a blog article, start a new post for the Prisma blog, or publish to prisma.io/blog. metadata: - version: "2026.7.9" + version: "2026.9.15" --- # Write Blog Post @@ -180,7 +180,8 @@ The lead you draft, and the prose the author later writes over your stubs, ship - **Be specific.** Replace "this changes everything" or "the implications are significant" with the concrete change. Drop "every", "always", and "never" where a real number fits better. - **Cut filler.** No "really", "simply", "just", and no marketing verbs like "leverage", "unlock", "seamless", "powerful". - **Say it straight.** "Not X, but Y" and "X isn't the problem, Y is" telegraph the reversal. State Y. -- **Vary sentence length**, and use a comma, colon, or period in place of em dashes. +- **Vary sentence length.** A run of short, clipped sentences with no connectives reads as staccato ("The fix shipped. Latency dropped. Users noticed."), and it is the most common tell of a model-drafted post. When facts are cause and effect or contrast, join them with "because", "so", or "but", and let a longer sentence carry them. Then let a short one land the point. +- **Use a comma, colon, or period in place of em dashes.** ## Anti-patterns diff --git a/.claude/skills/docs-reader-review/SKILL.md b/.claude/skills/docs-reader-review/SKILL.md index d0194537f67..f188438e23e 100644 --- a/.claude/skills/docs-reader-review/SKILL.md +++ b/.claude/skills/docs-reader-review/SKILL.md @@ -23,6 +23,10 @@ Use it after `docs-writer` (or any other writing pass) and after the fact review Run `scripts/check-plain.sh` on every changed page. It fails on the words in `references/banned-terms.md`, which lists source-code vocabulary and the plain words to use instead, and it ignores code blocks and inline code. Replace every hit before going further. Do not argue that a term is fine in context. The one exception: if the sentence itself defines the term in plain words, add `{/* plain-language:defined */}` to that line; the checker skips it and the reader review will confirm the definition landed. Terms the list marks "(unexplained)" are not checked by the script; the reviewer catches them. +### 1b. Run the staccato check + +Run `scripts/check-staccato.py` on every changed page. It flags paragraphs with three or more consecutive sentences under nine words, the usual signature of a page whose long sentences were split without keeping the connectives. Read each flagged paragraph aloud. Rejoin sentences that are cause and effect, contrast, or condition and result with "because", "so", "but", "while", or a colon, and leave apart the ones that are separate ideas. The script only finds the worst runs; a paragraph of eleven-word sentences that all land the same way is still staccato, so read the whole page for rhythm, not only the hits. Reference entries that are fragments by design (a `Payload:` line, a one-line table note) are not prose and do not count. + ### 2. Dispatch the reader Hand the page to a fresh reviewer that has no memory of writing it, using `references/reader-persona.md` verbatim as its instructions. The reviewer reads the page once, top to bottom, with nothing else open, and reports: @@ -42,7 +46,7 @@ For each reported sentence: | The reviewer said | Do this | | --- | --- | | a word it had to guess | replace with the plain words from `references/banned-terms.md`, or define it in that sentence | -| too many ideas in one sentence | one idea per sentence; a table cell with three ideas becomes a note under the table | +| too many ideas in one sentence | one idea per sentence, keeping the connective (because, so, but) that ties related facts together; a row of clipped one-clause sentences is its own failure, not a fix. A table cell with three ideas becomes a note under the table | | "so what do I type?" | add the command or the code, or the link to the page that has it | | mechanism instead of action | delete the mechanism, keep what the reader does and what they see | | a reference it could not resolve ("the plan", "the ref", "spec") | show the thing, or name where it comes from | diff --git a/.claude/skills/docs-reader-review/scripts/check-staccato.py b/.claude/skills/docs-reader-review/scripts/check-staccato.py new file mode 100755 index 00000000000..eeb2d11ec88 --- /dev/null +++ b/.claude/skills/docs-reader-review/scripts/check-staccato.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +"""Flags staccato prose: runs of short, one-clause sentences with no connective between them. + +Usage: check-staccato.py [more files...] Exit 1 on any hit. + +A hit is a paragraph containing three or more consecutive sentences that are each +under MAX_WORDS words. Fenced code, tables, headings, list items, front matter, and +import lines are skipped. Colons that introduce a code span or example are not +treated as sentence ends. The heuristic is deliberately simple: it finds the +paragraphs a human should read aloud, and the reader decides whether the sentences +belong together. Rejoin them with "because", "so", "but", "while", or a colon when +they are cause and effect, contrast, or condition and result. Leave them apart when +they are separate ideas. +""" +import re +import sys + +MAX_WORDS = 9 +RUN = 3 + +CODE = re.compile(r"`[^`]*`") +LINK = re.compile(r"\]\([^)]*\)") +SENT_END = re.compile(r"(?<=[.!?])\s+(?=[A-Z`\"'(])") + + +def paragraphs(text): + """Yield (line_number, paragraph) for prose paragraphs only.""" + lines = text.split("\n") + out, start, infence, infront = [], None, False, False + for i, line in enumerate(lines, 1): + if i == 1 and line.strip() == "---": + infront = True + continue + if infront: + if line.strip() == "---": + infront = False + continue + if re.match(r"^\s*(```|~~~)", line): + infence = not infence + continue + if infence or line.startswith((" ", "\t")): + continue + stripped = line.strip() + if not stripped: + if out: + yield start, " ".join(out) + out, start = [], None + continue + if re.match(r"^(#|\||[-*+] |\d+\. |import |export |<|{/\*)", stripped): + if out: + yield start, " ".join(out) + out, start = [], None + continue + if start is None: + start = i + out.append(stripped) + if out: + yield start, " ".join(out) + + +def sentences(para): + para = CODE.sub("CODE", para) + para = LINK.sub("]", para) + return [s.strip() for s in SENT_END.split(para) if s.strip()] + + +def check(path): + hits = [] + text = open(path, encoding="utf-8").read() + for lineno, para in paragraphs(text): + sents = sentences(para) + short = [len(s.split()) < MAX_WORDS for s in sents] + run = 0 + for j, is_short in enumerate(short): + run = run + 1 if is_short else 0 + if run == RUN: + first = j - RUN + 1 + hits.append((lineno, " | ".join(sents[first : j + 1]))) + break + return hits + + +def main(argv): + status = 0 + for path in argv: + hits = check(path) + if hits: + status = 1 + print(f"== {path}") + for lineno, snippet in hits: + print(f"{lineno}: {snippet[:180]}") + print("staccato check: clean" if status == 0 else "staccato check: clipped runs found, read them aloud") + return status + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) diff --git a/.claude/skills/docs-writer/SKILL.md b/.claude/skills/docs-writer/SKILL.md index 4736cd0c023..91cd69c71d7 100644 --- a/.claude/skills/docs-writer/SKILL.md +++ b/.claude/skills/docs-writer/SKILL.md @@ -3,7 +3,7 @@ name: docs-writer description: Use when writing, rewriting, or improving technical docs (quickstarts, how-tos, tutorials, concept pages, or API references). metadata: author: Prisma - version: "2026.9.11" + version: "2026.9.15" --- # Docs Writer @@ -163,6 +163,7 @@ Model-drafted docs share habits a reviewer can spot in one pass. None of these i - **Contrast slogans**: "X, not Y" constructions ("injected, not discovered", "built for agents, not just terminals", "refuses to guess"). One per page at most. A page of balanced contrasts reads like ad copy; state the behavior plainly instead. - **Absolute stacking**: "never", "every", "nothing", "always", "cannot drift" piling up across a page. Each absolute is a promise the product has to keep. Keep the ones you can verify and that the reader needs; describe what happens rather than asserting what can't. - **Exception-packed sentences**: one sentence carrying the normal case, an exception, its reason, the alternative, and a safety condition, held together by semicolons. One idea per sentence: state the default first ("By default, the command uses…"), then the exception as its own sentence ("In CI or other headless environments, set…"). A semicolon joining distinct ideas is usually two sentences. +- **Staccato**: the opposite failure, and the usual result of over-correcting the one above. A run of short, clipped sentences, each holding one fact, with no connective between them: "The field takes no `@default`. Omit `id` when you create a document and MongoDB assigns it." or "A profile needs a user. A user does not need a profile. So `Profile` holds `userId`." Every sentence lands with the same thud and the reader has to work out how the facts relate. One idea per sentence does not mean one clause per sentence. When two facts are cause and effect, contrast, or condition and result, join them with "because", "so", "but", "while", or a colon, and let one sentence carry both: "A profile needs a user, but a user does not need a profile, so `Profile` holds `userId`." Vary sentence length within a paragraph, and read it aloud before finishing: if three sentences in a row are under ten words, at least two of them probably belong together. - **Manager-voice openers**: "`service` manages services…", "handles", "manages everything around". Lead with the user action or outcome ("Use `service` commands to manage…") and use concrete verbs: creates, stores, uses, selects, targets, deploys, builds. - **Coined shorthand**: compressed phrases invented mid-page ("local pin", "pick a target") instead of saying what actually happens. Spell out the relationship, and keep one consistent user-facing vocabulary: service, project, configuration, deployment, credentials. - **Implementation language**: internal detail that doesn't help the reader act ("the root node is ``'s default export"). Translate it into the behavior they observe ("the application exported as the default export from ``"). @@ -192,5 +193,6 @@ Before you finish, check: - [ ] Is there a way to verify success at the end? - [ ] Did you cut every phrase from "Cut the slop"? - [ ] Did you check the page against every pattern in "Don't write like a model"? +- [ ] Did you read the prose aloud and rejoin any run of clipped sentences that belong together? `.claude/skills/docs-reader-review/scripts/check-staccato.py ` finds the worst runs. - [ ] Are product names and limitations accurate? - [ ] Has the page been through `docs-reader-review`? A fact review checks truth; the reader review checks whether a reader without your context can follow it. Nothing is finished until that pass reports no sentence the reader could not restate. diff --git a/apps/docs/content/docs/(index)/getting-started.mdx b/apps/docs/content/docs/(index)/getting-started.mdx index 69361d9b1d7..66e5155dcdb 100644 --- a/apps/docs/content/docs/(index)/getting-started.mdx +++ b/apps/docs/content/docs/(index)/getting-started.mdx @@ -8,7 +8,7 @@ metaDescription: Choose a Prisma ORM quickstart for a new project or add Prisma Start with a quickstart if you want [Prisma ORM](/orm) to create the app. Use the existing-project path if you already have an app and database. -Coming from an earlier version? [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7) maps every Prisma ORM 7 API to its Prisma ORM 8 form, and [Release status](/prisma-orm/release-status) says where Prisma ORM 8 stands today. +Coming from an earlier version? [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7) maps every Prisma ORM 7 API to its Prisma ORM 8 form, and [Release status](/orm/release-status) says where Prisma ORM 8 stands today. ## Start a new project diff --git a/apps/docs/content/docs/(index)/index.mdx b/apps/docs/content/docs/(index)/index.mdx index c5b1cacfb0f..27666209ee3 100644 --- a/apps/docs/content/docs/(index)/index.mdx +++ b/apps/docs/content/docs/(index)/index.mdx @@ -9,7 +9,7 @@ full: true Prisma is a complete TypeScript stack with one workflow: **build** your app and run all of it on your machine, then **deploy** it to the Prisma platform with one command. Check out the [full-stack tutorial](/full-stack-tutorial). -Here for the ORM? Jump straight to [Prisma ORM 7](/orm/v7) or [Prisma ORM 8](/orm), or check the [release status](/prisma-orm/release-status) first. +Here for the ORM? Jump straight to [Prisma ORM 7](/orm/v7) or [Prisma ORM 8](/orm), or check the [release status](/orm/release-status) first. diff --git a/apps/docs/content/docs/(index)/prisma-orm/add-to-existing-project/mongodb.mdx b/apps/docs/content/docs/(index)/prisma-orm/add-to-existing-project/mongodb.mdx index 0974249ff1b..2536a238fa3 100644 --- a/apps/docs/content/docs/(index)/prisma-orm/add-to-existing-project/mongodb.mdx +++ b/apps/docs/content/docs/(index)/prisma-orm/add-to-existing-project/mongodb.mdx @@ -14,7 +14,7 @@ Use this path when you already have an application and database. Make sure the a Prisma ORM 8 is the current release, as a release candidate. Prisma ORM 7 remains fully supported; its docs live at [/orm/v7](/orm/v7) and its setup paths at [/v7/getting-started](/v7/getting-started). -For what release candidate means, when the final release is expected, and how to stay on version 7, see [Release status](/prisma-orm/release-status). +For what release candidate means, when the final release is expected, and how to stay on version 7, see [Release status](/orm/release-status). ::: diff --git a/apps/docs/content/docs/(index)/prisma-orm/add-to-existing-project/postgresql.mdx b/apps/docs/content/docs/(index)/prisma-orm/add-to-existing-project/postgresql.mdx index 2d3108679cf..b1e3ba92659 100644 --- a/apps/docs/content/docs/(index)/prisma-orm/add-to-existing-project/postgresql.mdx +++ b/apps/docs/content/docs/(index)/prisma-orm/add-to-existing-project/postgresql.mdx @@ -14,7 +14,7 @@ Use this path when you already have an application and database. Make sure the a Prisma ORM 8 is the current release, as a release candidate. Prisma ORM 7 remains fully supported; its docs live at [/orm/v7](/orm/v7) and its setup paths at [/v7/getting-started](/v7/getting-started). -For what release candidate means, when the final release is expected, and how to stay on version 7, see [Release status](/prisma-orm/release-status). For the Prisma ORM 8 name of every Prisma ORM 7 API, see [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7). +For what release candidate means, when the final release is expected, and how to stay on version 7, see [Release status](/orm/release-status). For the Prisma ORM 8 name of every Prisma ORM 7 API, see [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7). ::: diff --git a/apps/docs/content/docs/(index)/prisma-orm/index.mdx b/apps/docs/content/docs/(index)/prisma-orm/index.mdx index fefed162bd3..857a578fefd 100644 --- a/apps/docs/content/docs/(index)/prisma-orm/index.mdx +++ b/apps/docs/content/docs/(index)/prisma-orm/index.mdx @@ -12,7 +12,7 @@ Prisma ORM 8 is a ground-up rebuild of Prisma ORM, from the runtime and query AP Prisma ORM 8 is the current release, as a release candidate. Prisma ORM 7 remains fully supported; its docs live at [/orm/v7](/orm/v7) and its setup paths at [/v7/getting-started](/v7/getting-started). -For what release candidate means, when the final release is expected, and how to stay on version 7, see [Release status](/prisma-orm/release-status). For the Prisma ORM 8 name of every Prisma ORM 7 API, see [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7). +For what release candidate means, when the final release is expected, and how to stay on version 7, see [Release status](/orm/release-status). For the Prisma ORM 8 name of every Prisma ORM 7 API, see [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7). ::: diff --git a/apps/docs/content/docs/(index)/prisma-orm/meta.json b/apps/docs/content/docs/(index)/prisma-orm/meta.json index aadc57bc05b..c4a694feacb 100644 --- a/apps/docs/content/docs/(index)/prisma-orm/meta.json +++ b/apps/docs/content/docs/(index)/prisma-orm/meta.json @@ -3,8 +3,8 @@ "defaultOpen": true, "pages": [ "index", - "release-status", - "supported-databases", + "[Release status](/orm/release-status)", + "[Supported databases](/orm/supported-databases)", "create-prisma", "quickstart", "add-to-existing-project" diff --git a/apps/docs/content/docs/(index)/prisma-orm/quickstart/mongodb.mdx b/apps/docs/content/docs/(index)/prisma-orm/quickstart/mongodb.mdx index e33900c983b..abff817e1ee 100644 --- a/apps/docs/content/docs/(index)/prisma-orm/quickstart/mongodb.mdx +++ b/apps/docs/content/docs/(index)/prisma-orm/quickstart/mongodb.mdx @@ -12,7 +12,7 @@ Create a Prisma ORM app with MongoDB, apply the first migration, and run your fi Prisma ORM 8 is the current release, as a release candidate. Prisma ORM 7 remains fully supported; its docs live at [/orm/v7](/orm/v7) and its setup paths at [/v7/getting-started](/v7/getting-started). -For what release candidate means, when the final release is expected, and how to stay on version 7, see [Release status](/prisma-orm/release-status). +For what release candidate means, when the final release is expected, and how to stay on version 7, see [Release status](/orm/release-status). ::: diff --git a/apps/docs/content/docs/(index)/prisma-orm/quickstart/postgresql.mdx b/apps/docs/content/docs/(index)/prisma-orm/quickstart/postgresql.mdx index ca5362698c7..4e3b4a8c6e2 100644 --- a/apps/docs/content/docs/(index)/prisma-orm/quickstart/postgresql.mdx +++ b/apps/docs/content/docs/(index)/prisma-orm/quickstart/postgresql.mdx @@ -12,7 +12,7 @@ Create a Prisma ORM app with PostgreSQL and run your first query against seeded Prisma ORM 8 is the current release, as a release candidate. Prisma ORM 7 remains fully supported; its docs live at [/orm/v7](/orm/v7) and its setup paths at [/v7/getting-started](/v7/getting-started). -For what release candidate means, when the final release is expected, and how to stay on version 7, see [Release status](/prisma-orm/release-status). For the Prisma ORM 8 name of every Prisma ORM 7 API, see [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7). +For what release candidate means, when the final release is expected, and how to stay on version 7, see [Release status](/orm/release-status). For the Prisma ORM 8 name of every Prisma ORM 7 API, see [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7). ::: diff --git a/apps/docs/content/docs/(index)/prisma-orm/supported-databases.mdx b/apps/docs/content/docs/(index)/prisma-orm/supported-databases.mdx deleted file mode 100644 index 0b304593f9e..00000000000 --- a/apps/docs/content/docs/(index)/prisma-orm/supported-databases.mdx +++ /dev/null @@ -1,36 +0,0 @@ ---- -title: Supported databases -description: 'Which databases Prisma ORM 8 supports today, what release candidate and early access mean, and which databases are coming.' -url: /prisma-orm/supported-databases -metaTitle: Databases supported by Prisma ORM 8 -metaDescription: 'PostgreSQL is a release candidate and MongoDB is in early access. MySQL, MariaDB, SQLite, SQL Server, and CockroachDB are coming. What each label means and how to ask for a database.' ---- - -Prisma ORM 8 ships one library per database. Today there are two you can build on. - -| Database | Status | Library | -| --- | --- | --- | -| PostgreSQL, including Prisma Postgres | Release candidate | `@prisma/orm-postgres` | -| MongoDB | Early access | `@prisma/orm-mongo` | -| MySQL | Coming soon | | -| MariaDB | Coming soon | | -| SQLite | Coming soon | | -| Microsoft SQL Server | Coming soon | | -| CockroachDB | Coming soon | | - -## What the labels mean - -**Release candidate.** Install it and build with it today. Until the final release, some details of the API may still change, so code you write now may need small edits later. Each release candidate fixes bugs found in the last one. [Release status](/prisma-orm/release-status) has the dates and what `npm install` gives you. - -**Early access.** The library works and you can build with it, but some of what the PostgreSQL library does is not there yet. The reference pages say on each method whether it works on MongoDB, and [what is not available yet](/orm/coming-from-prisma-orm-7#not-available-yet) lists the gaps. Report what you hit on [GitHub](https://github.com/prisma/orm/issues). - -**Coming soon.** There is no Prisma ORM 8 library for the database yet. Prisma ORM 7 supports it, and Prisma ORM 7 keeps receiving bug fixes and security updates for 18 months after Prisma ORM 8 reaches general availability. [How to stay on Prisma ORM 7](/prisma-orm/release-status#how-to-stay-on-prisma-orm-7) shows how to lock the version. - -## How to ask for a database - -Add a 👍 to the GitHub issue for the database you need, or open one if there is none. The number of reactions is one of the inputs to what gets built next. Organizations that need a database sooner can talk to [Prisma enterprise support](https://www.prisma.io/enterprise). - -## Next steps - -- The quickstart sets up a project on [PostgreSQL](/prisma-orm/quickstart/postgresql) or [MongoDB](/prisma-orm/quickstart/mongodb). -- [Release status](/prisma-orm/release-status) covers versions, dates, and staying on Prisma ORM 7. diff --git a/apps/docs/content/docs/cli/index.mdx b/apps/docs/content/docs/cli/index.mdx index 69f6f463fb4..866a3c89a13 100644 --- a/apps/docs/content/docs/cli/index.mdx +++ b/apps/docs/content/docs/cli/index.mdx @@ -12,7 +12,7 @@ Prisma ORM ships with a unified Prisma CLI. One binary contains the ORM commands Prisma ORM 8 is the current release, as a release candidate. Prisma ORM 7 remains fully supported; its docs live at [/orm/v7](/orm/v7) and its setup paths at [/v7/getting-started](/v7/getting-started). -For what release candidate means, when the final release is expected, and how to stay on version 7, see [Release status](/prisma-orm/release-status). For the Prisma ORM 8 name of every Prisma ORM 7 API, see [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7). +For what release candidate means, when the final release is expected, and how to stay on version 7, see [Release status](/orm/release-status). For the Prisma ORM 8 name of every Prisma ORM 7 API, see [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7). ::: diff --git a/apps/docs/content/docs/guides/deployment/meta.json b/apps/docs/content/docs/guides/deployment/meta.json index 7a5905c0a88..1c26adff3ed 100644 --- a/apps/docs/content/docs/guides/deployment/meta.json +++ b/apps/docs/content/docs/guides/deployment/meta.json @@ -1,10 +1,4 @@ { "title": "Deployment", - "pages": [ - "cloudflare-workers", - "docker", - "turborepo", - "pnpm-workspaces", - "bun-workspaces" - ] + "pages": ["cloudflare-workers", "docker", "turborepo", "pnpm-workspaces", "bun-workspaces"] } diff --git a/apps/docs/content/docs/guides/postgres/vercel.mdx b/apps/docs/content/docs/guides/postgres/vercel.mdx index 357510290ff..c696af39173 100644 --- a/apps/docs/content/docs/guides/postgres/vercel.mdx +++ b/apps/docs/content/docs/guides/postgres/vercel.mdx @@ -81,7 +81,7 @@ Prisma ORM has no build-time generate step. `npx prisma@latest contract emit` wr :::note[Using Prisma ORM 7?] -For a Prisma ORM 7 project, keep the `prisma.config.ts` that reads `DATABASE_URL` and add `"postinstall": "prisma generate"` to `package.json` so Prisma Client is generated on every deploy. See the [Prisma ORM 7 Next.js guide](/guides/v7/frameworks/nextjs), and [Release status](/prisma-orm/release-status) for where Prisma ORM 8 stands and how to pin Prisma ORM 7. +For a Prisma ORM 7 project, keep the `prisma.config.ts` that reads `DATABASE_URL` and add `"postinstall": "prisma generate"` to `package.json` so Prisma Client is generated on every deploy. See the [Prisma ORM 7 Next.js guide](/guides/v7/frameworks/nextjs), and [Release status](/orm/release-status) for where Prisma ORM 8 stands and how to pin Prisma ORM 7. ::: diff --git a/apps/docs/content/docs/guides/v7/integrations/meta.json b/apps/docs/content/docs/guides/v7/integrations/meta.json index 625f5b50667..9f95f1a5834 100644 --- a/apps/docs/content/docs/guides/v7/integrations/meta.json +++ b/apps/docs/content/docs/guides/v7/integrations/meta.json @@ -1,9 +1,4 @@ { "title": "Integrations", - "pages": [ - "ai-sdk", - "github-actions", - "neon-accelerate", - "supabase-accelerate" - ] + "pages": ["ai-sdk", "github-actions", "neon-accelerate", "supabase-accelerate"] } diff --git a/apps/docs/content/docs/orm/coming-from-prisma-orm-7.mdx b/apps/docs/content/docs/orm/coming-from-prisma-orm-7.mdx index b725d890162..63f897ba4a1 100644 --- a/apps/docs/content/docs/orm/coming-from-prisma-orm-7.mdx +++ b/apps/docs/content/docs/orm/coming-from-prisma-orm-7.mdx @@ -8,7 +8,7 @@ metaDescription: 'A lookup table from Prisma ORM 7 to Prisma ORM 8: schema attri If you know Prisma ORM 7, this page tells you what each thing you already use is called in Prisma ORM 8. Two renamed things to know before the tables: the schema file is now called the contract (the same file, renamed), and generating the client types is now called `emit`. This page is a lookup table, not a tutorial: one table each for the schema, the command-line tool, and the query API, and a final section on what is missing. -To move a running application, follow [Migrate from Prisma ORM 7 to 8](/guides/upgrade-prisma-orm/postgresql) instead. It runs both versions side by side against the same database and moves one part of the app at a time. To decide whether to move at all, see [Release status](/prisma-orm/release-status). +To move a running application, follow [Migrate from Prisma ORM 7 to 8](/guides/upgrade-prisma-orm/postgresql) instead. It runs both versions side by side against the same database and moves one part of the app at a time. To decide whether to move at all, see [Release status](/orm/release-status). All examples use PostgreSQL. @@ -62,13 +62,13 @@ The biggest visible change is in field types. Where Prisma ORM 7 wrote a Prisma The `DateTime` type reads and writes its values as `Temporal.Instant` objects. If you would rather work with the timestamp as a string, write `TimestamptzString` in place of `DateTime`. -`Temporal` is built into Node.js 26 and later. It is not in Node.js 22 or 24. On those versions, install `temporal-polyfill` and add this line at the top of `src/prisma/db.ts`: +`Temporal` is built into Node.js 26 and later, but not in Node.js 22 or 24. On those versions, install `temporal-polyfill` and add this line at the top of `src/prisma/db.ts`: ```ts import "temporal-polyfill/full/global"; ``` -Homebrew's build of Node.js 26 leaves `Temporal` out. Check with `node -p "typeof Temporal"`; if it prints `undefined`, add the polyfill. +Homebrew's build of Node.js 26 leaves `Temporal` out, so check with `node -p "typeof Temporal"`; if it prints `undefined`, add the polyfill. ::: @@ -95,7 +95,7 @@ model PostTag { } ``` -That is complete as written. You do not need a `PostTag[]` field on `Post` or `Tag`. `.include("tags")` gives you `post.tags` as a `Tag[]`, and `connect` and `disconnect` work on it. `set` does not; see [Not available](#not-available-yet) below. +That is complete as written: you do not need a `PostTag[]` field on `Post` or `Tag`. `.include("tags")` gives you `post.tags` as a `Tag[]`, and `connect` and `disconnect` work on it. `set` does not; see [Not available](#not-available-yet) below. If the join table already exists from Prisma ORM 7, it is named `_PostToTag` with columns `A` and `B`. Keep the table by mapping the model to those names: `@@map("_PostToTag")` on the model, `@map("A")` on the field that points at the model whose name sorts first alphabetically (`postId`), and `@map("B")` on the other (`tagId`). @@ -159,7 +159,7 @@ The rest are in the [CLI reference](/cli). ## `src/prisma/db.ts` replaces `PrismaClient` -There is no generated `PrismaClient`. You create the client once, in `src/prisma/db.ts`, from the two files `prisma contract emit` wrote: +There is no generated `PrismaClient`: you create the client once, in `src/prisma/db.ts`, from the two files `prisma contract emit` wrote: ```ts title="src/prisma/db.ts" import "dotenv/config"; @@ -279,7 +279,7 @@ const update = db.raw.sql`UPDATE "user" SET active = false WHERE id = ${id}` await db.runtime().execute(update); ``` -The table is `user`, not `User`: Prisma ORM 8 names a table after its model with a lowercase first letter, unless the model sets `@@map`. Every raw query that returns rows needs `.returnsRow(...)` with a type per column. Take the type from the table, as above. For a computed column, write the type name instead, such as `pg/int4@1` or `pg/text@1`; the `@1` is a version number and is always `1` today. [Raw queries](/orm/reference/raw-queries) lists the names. Finish the query with `.build()` and pass it to `db.runtime().query()` for rows or `db.runtime().execute()` for a count. +The table is `user`, not `User`: Prisma ORM 8 names a table after its model with a lowercase first letter, unless the model sets `@@map`. Every raw query that returns rows needs `.returnsRow(...)` with a type per column, and you take the type from the table, as above. For a computed column, write the type name instead, such as `pg/int4@1` or `pg/text@1`; the `@1` is a version number and is always `1` today. [Raw queries](/orm/reference/raw-queries) lists the names. Finish the query with `.build()` and pass it to `db.runtime().query()` for rows or `db.runtime().execute()` for a count. ### Transaction @@ -333,4 +333,4 @@ Also not available today: - [Core concepts](/orm/core-concepts), for what a contract is and what `contract emit` does. - [ORM client reference](/orm/reference/orm-client), for every query method. - [Reading data](/orm/fundamentals/reading-data) and [Writing data](/orm/fundamentals/writing-data), for the query API in full. -- [Release status](/prisma-orm/release-status), for how close Prisma ORM 8 is to its final release, and how to stay on Prisma ORM 7. +- [Release status](/orm/release-status), for how close Prisma ORM 8 is to its final release, and how to stay on Prisma ORM 7. diff --git a/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx b/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx index 54f924cbaaf..b62d0d117ba 100644 --- a/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/capabilities.mdx @@ -10,7 +10,7 @@ Your [contract](/orm/contract-authoring/the-data-contract), the `contract.prisma ## Where the `capabilities` section comes from [#where-capabilities-come-from] -The keys come from your database package (`@prisma/orm-postgres`, `@prisma/orm-sqlite`, or `@prisma/orm-mongo`) and any extension packages listed in `prisma.config.ts`. `npx prisma contract emit` never connects to your database. It reads those packages and writes what they report, in groups: +The keys come from your database package (`@prisma/orm-postgres`, `@prisma/orm-sqlite`, or `@prisma/orm-mongo`) and any extension packages listed in `prisma.config.ts`. `npx prisma contract emit` never connects to your database: it reads those packages and writes what they report, in groups: ```json title="src/prisma/contract.json (excerpt)" { @@ -21,7 +21,7 @@ The keys come from your database package (`@prisma/orm-postgres`, `@prisma/orm-s } ``` -Error messages and the table below name a key group first, `postgres.pgvector.cosine`. The `sql` group holds keys that more than one SQL database may support. SQLite reports `false` for some of them. Keys under `postgres` belong to PostgreSQL only. Extension packages add keys of their own, and `pgvector.cosine` comes from the pgvector package. Install it with `npm install @prisma/orm-extension-pgvector`, list it in `prisma.config.ts`, and run `npx prisma contract emit` again: +Error messages and the table below name a key group first, `postgres.pgvector.cosine`. The `sql` group holds keys that more than one SQL database may support, and SQLite reports `false` for some of them. Keys under `postgres` belong to PostgreSQL only. Extension packages add keys of their own, and `pgvector.cosine` comes from the pgvector package. Install it with `npm install @prisma/orm-extension-pgvector`, list it in `prisma.config.ts`, and run `npx prisma contract emit` again: ```typescript title="prisma.config.ts" import "dotenv/config"; @@ -52,13 +52,13 @@ Remove the list or author it against a target that supports scalar lists. In that message, both words mean your database package, and the message's `"scalarList"` is the `sql.scalarList` key. Do one of the two: remove the list field, or move to a database that supports list fields. To move to PostgreSQL, change the `@prisma/orm-sqlite/config` import and the connection string in `prisma.config.ts`, and create the new database. -**When you call a method on the [SQL query builder](/orm/reference/sql-query-builder) or on the ORM client.** A method that needs a feature throws an error whose `code` is `ORM.CAPABILITY_MISSING`. The message names the method and the key: +**When you call a method on the [SQL query builder](/orm/reference/sql-query-builder) or on the ORM client.** A method that needs a feature throws an error whose `code` is `ORM.CAPABILITY_MISSING`, and the message names the method and the key: ```text distinctOn() requires capability postgres.distinctOn ``` -That message means the method is not available on the database package you chose. Rewrite the query without it, or move to a database that supports the feature. If the key comes from an extension package, install that package and list it in `prisma.config.ts`. You do not have to run the query to get the error, so a test that only builds the query still fails. Your own code can read the same list through `db`, the client you create once in `src/prisma/db.ts`, which `npx prisma orm init` writes. A value such as `db.contract.capabilities.sql?.lateral` is `true` or `false`, or `undefined` if the whole group is missing. For a key with a dot in it, write `db.contract.capabilities.postgres?.["pgvector.cosine"]`: +That message means the method is not available on the database package you chose, so rewrite the query without it, or move to a database that supports the feature. If the key comes from an extension package, install that package and list it in `prisma.config.ts`. You do not have to run the query to get the error, so a test that only builds the query still fails. Your own code can read the same list through `db`, the client you create once in `src/prisma/db.ts`, which `npx prisma orm init` writes. A value such as `db.contract.capabilities.sql?.lateral` is `true` or `false`, or `undefined` if the whole group is missing. For a key with a dot in it, write `db.contract.capabilities.postgres?.["pgvector.cosine"]`: ```typescript if (db.contract.capabilities.sql?.lateral) { @@ -78,15 +78,15 @@ A few of the keys your database package and extension packages supply: | `postgres.distinctOn` | `DISTINCT ON` queries (`distinctOn()`) | | `postgres.pgvector.cosine` | Cosine distance from the pgvector package | -PostgreSQL reports `true` for all three `sql` keys above. SQLite reports `false` for `sql.lateral`, `true` for `sql.returning`, and has no `sql.scalarList` key at all. This table is a sample, not the full list. Open `src/prisma/contract.json` in your project to see every key you have. The MongoDB packages report no keys, so neither check applies on MongoDB. The [ORM reference](/orm/reference) lists the methods each database has. +PostgreSQL reports `true` for all three `sql` keys above, while SQLite reports `false` for `sql.lateral`, `true` for `sql.returning`, and has no `sql.scalarList` key at all. This table is a sample, not the full list, so open `src/prisma/contract.json` in your project to see every key you have. The MongoDB packages report no keys, so neither check applies on MongoDB. The [ORM reference](/orm/reference) lists the methods each database has. ## The `capabilities` section and `prisma db verify` [#capabilities-and-verification] -[`npx prisma db verify`](/cli/db-verify) checks the live database against the contract's tables and columns, not against this section. The keys say what your packages support, recorded when you run `npx prisma contract emit`. +[`npx prisma db verify`](/cli/db-verify) checks the live database against the contract's tables and columns, not against this section: the keys say what your packages support, recorded when you run `npx prisma contract emit`. ## Prompt your coding agent -Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent. In an existing project, run `npx prisma skills sync`. The `prisma-8` skill covers this material. Ask your agent to: +Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent. In an existing project, run `npx prisma skills sync` to add them. The `prisma-8` skill covers this material, so ask your agent to: - "Which keys in the `capabilities` section does our code rely on, and which package provides each?" - "Add pgvector to the project and confirm its key shows up in the emitted contract." diff --git a/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx b/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx index 1a23dc70877..fa9de1e3c63 100644 --- a/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/psl-syntax.mdx @@ -14,11 +14,11 @@ PSL, the Prisma Schema Language, is the preferred way to author [your contract]( - base models and variants: one table can hold more than one kind of record. Put the shared fields in a base model, put the differences in each variant, and Prisma ORM uses one column to tell them apart. - extension types: field types that come from an npm package, such as vectors. -The `datasource` and `generator` blocks are gone. The connection URL and the file paths are set in `prisma.config.ts` instead. [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7#schema) lists every change to the schema file, including what each old `@db.` attribute becomes. +The `datasource` and `generator` blocks are gone: the connection URL and the file paths are set in `prisma.config.ts` instead. [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7#schema) lists every change to the schema file, including what each old `@db.` attribute becomes. ## A complete contract -Every contract starts with `// use prisma-8`. Keep that line at the top. +Every contract starts with `// use prisma-8`, so keep that line at the top. ```prisma title="src/prisma/contract.prisma" tab="PostgreSQL" // use prisma-8 @@ -103,11 +103,11 @@ model Post { } ``` -`Uuid` is PostgreSQL's `uuid` type written as a field type. Run `npx prisma contract emit` after any change to refresh `contract.json` and `contract.d.ts`. Then [`npx prisma db init`](/cli/db-init) creates the tables. +`Uuid` is PostgreSQL's `uuid` type written as a field type. Run `npx prisma contract emit` after any change to refresh `contract.json` and `contract.d.ts`, and then [`npx prisma db init`](/cli/db-init) creates the tables. ## Point the config at the schema -The config's `contract` path names the one file Prisma ORM reads. It takes a single path, so there is no folder of contract files. If the path ends in `.prisma`, Prisma ORM reads it as PSL. If it ends in `.ts`, as TypeScript. The `db` key holds the connection URL: +The config's `contract` path names the one file Prisma ORM reads: it takes a single path, so there is no folder of contract files. If the path ends in `.prisma`, Prisma ORM reads it as PSL, and if it ends in `.ts`, as TypeScript. The `db` key holds the connection URL: ```typescript title="prisma.config.ts" import 'dotenv/config'; @@ -136,7 +136,7 @@ Models declare fields with a type, an optional `?` marker, and attributes. [Scal - `@default(...)` sets a default. Database function defaults such as `@default(now())` become column defaults in the database. Generated defaults such as `@default(uuid())` come from Prisma ORM, and the database will not fill them in for you, so a row written by raw SQL or another application gets no value. - `@map("column_name")` sets a field's column name in the database. `@@map("table_name")` sets the table or collection name when it differs from the model name. -`@updatedAt` is gone. Write `temporal.updatedAt()` where the field's type would go: +`@updatedAt` is gone, so write `temporal.updatedAt()` where the field's type would go: ```prisma model Post { @@ -144,7 +144,7 @@ model Post { } ``` -Prisma ORM sets the field to the current time on every create and update. `temporal` is built in. You do not import it or declare it. On PostgreSQL the column is `timestamptz`, and you can still set the field yourself on a write. `temporal.createdAt()` sets the field once, when the row is created. +Prisma ORM sets the field to the current time on every create and update. `temporal` is built in, so you do not import it or declare it. On PostgreSQL the column is `timestamptz`, and you can still set the field yourself on a write. `temporal.createdAt()` sets the field once, when the row is created. How IDs map differs by database: @@ -160,7 +160,7 @@ model User { } ``` -On PostgreSQL the primary key is an ordinary column, so pick its type and default yourself. On MongoDB the primary key is the document's `_id`. Type it `ObjectId` and map it to `_id`. +On PostgreSQL the primary key is an ordinary column, so pick its type and default yourself. On MongoDB the primary key is the document's `_id`, so type it `ObjectId` and map it to `_id`. ## Named types @@ -172,11 +172,11 @@ types { } ``` -Fields then use `ShortName` like any built-in type. The name keeps the column decision in one place: a `varchar(35)` column rather than `text`. A name is optional, and a field can use the native PostgreSQL type directly, such as `VarChar(35)`, `Uuid`, or `Timestamptz`. In Prisma ORM 8 the native type is the field's type, so `String @db.VarChar(35)` from Prisma ORM 7 becomes `VarChar(35)`. The `@db.` attributes are gone. The `types` block is PostgreSQL only. +Fields then use `ShortName` like any built-in type. The name keeps the column decision in one place: a `varchar(35)` column rather than `text`. A name is optional, and a field can use the native PostgreSQL type directly, such as `VarChar(35)`, `Uuid`, or `Timestamptz`. In Prisma ORM 8 the native type is the field's type, so `String @db.VarChar(35)` from Prisma ORM 7 becomes `VarChar(35)`, and the `@db.` attributes are gone. The `types` block is PostgreSQL only. ## Enums -An enum lists its members. It can also say how their values are stored, with `@@type`, and what each member stores: +An enum lists its members. It can also say, with `@@type`, how their values are stored, and what each member stores: ```prisma enum Priority { @@ -189,7 +189,7 @@ enum Priority { In `pg/text@1`, `pg` is PostgreSQL, `text` is the column type, and `@1` is the version of how the value is stored and read. Write `@@type("pg/int4@1")` to store the values as integers instead. When a member has no explicit value, the member name itself is stored. -`@@type` is optional. Leave it out and Prisma ORM picks the type from the member values: bare member names and string values give the database's text type, and integer values give its integer type. Give every member the same kind of value, because a mix of string and integer values throws an error whose `code` is `PSL_ENUM_CANNOT_INFER_TYPE`. A `@default` names the member, as in `priority Priority @default(Low)`, even where the member stores a different value. +`@@type` is optional, and when you leave it out Prisma ORM picks the type from the member values: bare member names and string values give the database's text type, and integer values give its integer type. Give every member the same kind of value, because a mix of string and integer values throws an error whose `code` is `PSL_ENUM_CANNOT_INFER_TYPE`. A `@default` names the member, as in `priority Priority @default(Low)`, even where the member stores a different value. An `enum` block is not a PostgreSQL `enum` type: the column is text or an integer. For a PostgreSQL `enum` type, declare it in a `native_enum` block and type the field `pg.enum(Role)`: @@ -225,11 +225,11 @@ model User { } ``` -A value object field can be optional or a list, and a `type` block can hold a field of another `type`. Watch the two spellings: `types { ... }` declares named types, and `type X { ... }` declares a value object. Storage differs by database. On PostgreSQL a value object field is stored in a single `jsonb` column. On MongoDB it is an embedded document. Either way, `contract.d.ts` types it as a structured object rather than untyped JSON. On MongoDB, whether to embed or reference is the central modeling decision. [MongoDB data modeling](/orm/data-modeling/mongodb#embed-or-reference) covers it. +A value object field can be optional or a list, and a `type` block can hold a field of another `type`. Watch the two spellings: `types { ... }` declares named types, and `type X { ... }` declares a value object. Storage differs by database: on PostgreSQL a value object field is stored in a single `jsonb` column, while 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, and [MongoDB data modeling](/orm/data-modeling/mongodb#embed-or-reference) covers it. ## Relations -Relations use the `@relation` syntax you know from Prisma ORM. The side that holds the foreign key declares the scalar field and the mapping. The other side declares a list: +Relations use the `@relation` syntax you know from Prisma ORM. The side that holds the foreign key declares the scalar field and the mapping, and the other side declares a list: ```prisma model Post { @@ -242,7 +242,7 @@ model User { } ``` -Add `onDelete` and `onUpdate` to the same `@relation`. They belong on the side that holds the foreign key, not on the list side. For a one-to-one, make the other side singular instead of a list, so `User` declares `profile Profile?`, and put `@unique` on the foreign-key field: +Add `onDelete` and `onUpdate` to the same `@relation`: they belong on the side that holds the foreign key, not on the list side. For a one-to-one, make the other side singular instead of a list, so `User` declares `profile Profile?`, and put `@unique` on the foreign-key field: ```prisma model Profile { @@ -251,7 +251,7 @@ model Profile { } ``` -Many-to-many relations need a model for the join table. You write that model yourself: there is no implicit many-to-many. That model must follow two rules. If a side has a composite primary key, that model needs one foreign-key field for each part of it. Its `@@id([...])` must list exactly the foreign-key fields, and nothing else. +Many-to-many relations need a model for the join table, and you write that model yourself: there is no implicit many-to-many. That model must follow two rules: if a side has a composite primary key, that model needs one foreign-key field for each part of it, and its `@@id([...])` must list exactly the foreign-key fields and nothing else. ```prisma model Post { @@ -274,7 +274,7 @@ model PostTag { } ``` -You then read `post.tags` as a list of `Tag`, without mentioning `PostTag` in the query. Break either rule and `npx prisma contract emit` reports which list field it could not match to a model. If two models qualify, `npx prisma contract emit` throws an error whose `code` is `PSL_AMBIGUOUS_BACKRELATION`. Put the same `@relation("name")` on both ends of one pair, so on `PostTag.post` for `Post.tags`. +You then read `post.tags` as a list of `Tag`, without mentioning `PostTag` in the query. Break either rule and `npx prisma contract emit` reports which list field it could not match to a model. If two models qualify, `npx prisma contract emit` throws an error whose `code` is `PSL_AMBIGUOUS_BACKRELATION`: put the same `@relation("name")` on both ends of one pair, so on `PostTag.post` for `Post.tags`. For which shape to choose and which side owns the foreign key, see [relational data modeling](/orm/data-modeling/relational-databases) and [MongoDB data modeling](/orm/data-modeling/mongodb). @@ -303,9 +303,9 @@ model Bug { A field name is written bare, as `type` is in `@@discriminator(type)`, and a database name is quoted, as in `@@map("task")`. Rows whose `type` column holds `"bug"` are `Bug` records. A variant reuses its base model's fields, so a `Bug` has `id`, `title`, and `type` as well as `severity` and `stepsToRepro`. A variant does not declare an `@id` of its own: it takes the base model's primary key. -You query a variant through the base model: `db.orm.public.Task.variant('Bug')` returns the `Bug` rows only. Here `public` is the PostgreSQL schema. Writing a row of a variant starts with the same `variant('Bug')` call. See [`variant()`](/orm/reference/orm-client#variant). +You query a variant through the base model: `db.orm.public.Task.variant('Bug')` returns the `Bug` rows only, and `public` here is the PostgreSQL schema. Writing a row of a variant starts with the same `variant('Bug')` call. See [`variant()`](/orm/reference/orm-client#variant). -On a variant, `@@map` does more than rename. On PostgreSQL it chooses between two storage layouts. Give the variant its own `@@map`, as `Bug` has here, and its fields are in a table of their own that shares the base model's primary key. Leave `@@map` out and they are nullable columns in the base table. [Relational data modeling](/orm/data-modeling/relational-databases#polymorphic-relations) covers choosing between the two. +On a variant, `@@map` does more than rename: on PostgreSQL it chooses between two storage layouts. Give the variant its own `@@map`, as `Bug` has here, and its fields are in a table of their own that shares the base model's primary key. Leave `@@map` out and they are nullable columns in the base table. [Relational data modeling](/orm/data-modeling/relational-databases#polymorphic-relations) covers choosing between the two. On MongoDB, a variant adds its fields to documents in the base model's collection, so it declares `@@base` but no `@@map` of its own. @@ -335,15 +335,15 @@ model Post { } ``` -The `pgvector` part of `pgvector.Vector(1536)` is a fixed name the pack declares, not the name you gave the import. List the pack before using its types. Run `npx prisma contract emit` again after changing the extension list. [Using extensions](/orm/extensions/using-extensions) covers installing a pack and names the packs you can add. +The `pgvector` part of `pgvector.Vector(1536)` is a fixed name the pack declares, not the name you gave the import. List the pack before using its types, and run `npx prisma contract emit` again after changing the extension list. [Using extensions](/orm/extensions/using-extensions) covers installing a pack and names the packs you can add. ## Starting from an existing database -If the database already exists, don't write the contract by hand. [`contract infer`](/cli/contract-infer) reads the live schema and writes a starter `contract.prisma` for you to review and edit. +If the database already exists, don't write the contract by hand: [`contract infer`](/cli/contract-infer) reads the live schema and writes a starter `contract.prisma` for you to review and edit. ## Prompt your coding agent -Projects created with `npm create prisma@latest -- my-app` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent. Skills are instruction files the agent reads. In an existing project, run `npx prisma skills sync`. The `prisma-8` skill covers PSL authoring. Ask your agent to: +Projects created with `npm create prisma@latest -- my-app` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent: skills are instruction files the agent reads. In an existing project, run `npx prisma skills sync` to add them. The `prisma-8` skill covers PSL authoring, so ask your agent to: - "Using the prisma-8 skill, add a Status enum stored as text and use it on the Order model." - "Add a one-to-many between User and Post with the foreign key on Post." diff --git a/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx b/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx index 7952cb9a545..df30ca00ec8 100644 --- a/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/the-contract-artifact.mdx @@ -6,14 +6,14 @@ metaTitle: The Prisma ORM contract files metaDescription: 'What contract.json and contract.d.ts contain, why the same source always produces the same two files, how the hashes work, and how your app and the CLI read them.' --- -Your contract is [a single file that describes your data and how it is stored](/orm/contract-authoring/the-data-contract). It replaces the `schema.prisma` of Prisma ORM 7, and it is `src/prisma/contract.prisma` in Prisma Schema Language (PSL) or `src/prisma/contract.ts` in TypeScript. [`npx prisma contract emit`](/cli/contract-emit) reads that file and writes two files beside it. In the table below, `db` is your client, the object you run queries on. [`npx prisma orm init`](/cli/orm-init) creates the contract source, `src/prisma/db.ts`, and `.env.example` for you. In Prisma ORM 7 you edited `schema.prisma` and the tooling read the same file. Now you edit the contract and the tooling reads the two files it produces. +Your contract is [a single file that describes your data and how it is stored](/orm/contract-authoring/the-data-contract). It replaces the `schema.prisma` of Prisma ORM 7 and is either `src/prisma/contract.prisma` in Prisma Schema Language (PSL) or `src/prisma/contract.ts` in TypeScript. [`npx prisma contract emit`](/cli/contract-emit) reads that file and writes two files beside it. In the table below, `db` is your client, the object you run queries on. [`npx prisma orm init`](/cli/orm-init) creates the contract source, `src/prisma/db.ts`, and `.env.example` for you. In Prisma ORM 7 you edited `schema.prisma` and the tooling read the same file, but now you edit the contract and the tooling reads the two files it produces. | File | Contents | Read by | | --- | --- | --- | | `contract.json` | The machine-readable contract: models, how they are stored, which database features they need, and the hashes | `db`, `prisma db sign`, `prisma db verify`, and `prisma migration plan` | | `contract.d.ts` | TypeScript declarations derived from the contract | The query APIs and your application code, for typed models and results | -`prisma db sign` writes the current contract's hashes into the database, so later commands can tell which contract that database was built for. Both files are generated. Do not edit them. Change the [PSL](/orm/contract-authoring/psl-syntax) or [TypeScript](/orm/contract-authoring/typescript-schema-builder) source and run `npx prisma contract emit` again. `contract.d.ts` starts with a comment saying it is generated and must not be edited, and `contract.json` carries the same notice in a `_generated` entry. To write the two files to another directory, see [`prisma contract emit`](/cli/contract-emit). +`prisma db sign` writes the current contract's hashes into the database, so later commands can tell which contract that database was built for. Both files are generated, so do not edit them: change the [PSL](/orm/contract-authoring/psl-syntax) or [TypeScript](/orm/contract-authoring/typescript-schema-builder) source and run `npx prisma contract emit` again. `contract.d.ts` starts with a comment saying it is generated and must not be edited, and `contract.json` carries the same notice in a `_generated` entry. To write the two files to another directory, see [`prisma contract emit`](/cli/contract-emit). The everyday loop on a project: @@ -33,11 +33,11 @@ Run `npx prisma contract emit` on the same source on any machine and you get the ## The content hashes -`npx prisma contract emit` computes a hash over each part of the contract. You never compute or compare them yourself. `storageHash` covers the storage layout: tables, columns, primary keys, uniques, indexes, foreign keys, and the allowed values of each enum. `profileHash` covers which database the contract targets. The hashes are how Prisma ORM connects the contract to a live database. `npx prisma db sign` checks that the database satisfies the contract, then records `storageHash` and `profileHash` in the database. `npx prisma db verify` compares the contract you hold against that record and against the live schema, and fails when they disagree. To fix a mismatch, apply the pending migration, or run `npx prisma db sign` when the database already matches the contract. +`npx prisma contract emit` computes a hash over each part of the contract, and you never compute or compare them yourself. `storageHash` covers the storage layout: tables, columns, primary keys, uniques, indexes, foreign keys, and the allowed values of each enum. `profileHash` covers which database the contract targets. The hashes are how Prisma ORM connects the contract to a live database: `npx prisma db sign` checks that the database satisfies the contract, then records `storageHash` and `profileHash` in the database. `npx prisma db verify` compares the contract you hold against that record and against the live schema, and fails when they disagree. To fix a mismatch, apply the pending migration, or run `npx prisma db sign` when the database already matches the contract. ## Inside `contract.json` -The contract keeps your application's view of the data separate from the way the database stores it. The `domain` section describes models, fields, and relations. The `storage` section describes tables, columns, keys, and indexes. On MongoDB it describes collections and indexes instead. Each model's own `storage` block maps each field to its column. Everything is grouped by namespace. On PostgreSQL the namespace is the schema. It is `public` unless you put the models in a `namespace` block in your [PSL source](/orm/contract-authoring/psl-syntax). +The contract keeps your application's view of the data separate from the way the database stores it. The `domain` section describes models, fields, and relations, while the `storage` section describes tables, columns, keys, and indexes. On MongoDB it describes collections and indexes instead. Each model's own `storage` block maps each field to its column. Everything is grouped by namespace, and on PostgreSQL the namespace is the schema, which is `public` unless you put the models in a `namespace` block in your [PSL source](/orm/contract-authoring/psl-syntax). An abridged `contract.json` for a `User` and `Post` schema: @@ -130,13 +130,13 @@ export const db = postgres({ }); ``` -The connection string comes from the `DATABASE_URL` environment variable. `prisma orm init` writes `.env.example`. Put your `DATABASE_URL` in `.env`. Keep the `.d` in the import path. Delete the `.d` and the import stops resolving. The `tsconfig.json` settings the JSON import needs are on the [transactions and runtime page](/orm/reference/transactions-and-runtime). Before its first query, `db` reads the record in the database of which contract it matches and compares it with the contract's hashes. If they do not match, it logs a warning whose `code` is `CONTRACT.MARKER_MISMATCH` and runs the query anyway. If there is no record at all, because nothing has signed the database yet, it logs a warning whose `code` is `CONTRACT.MARKER_MISSING` and again runs the query. On MongoDB the client is `mongo(...)` from `@prisma/orm-mongo/runtime`, described under [`mongo(options)`](/orm/reference/transactions-and-runtime#mongooptions). +The connection string comes from the `DATABASE_URL` environment variable: `prisma orm init` writes `.env.example`, and you put your `DATABASE_URL` in `.env`. Keep the `.d` in the import path, because if you delete it the import stops resolving. The `tsconfig.json` settings the JSON import needs are on the [transactions and runtime page](/orm/reference/transactions-and-runtime). Before its first query, `db` reads the record in the database of which contract it matches and compares it with the contract's hashes. If they do not match, it logs a warning whose `code` is `CONTRACT.MARKER_MISMATCH` and runs the query anyway. If there is no record at all, because nothing has signed the database yet, it logs a warning whose `code` is `CONTRACT.MARKER_MISSING` and again runs the query. On MongoDB the client is `mongo(...)` from `@prisma/orm-mongo/runtime`, described under [`mongo(options)`](/orm/reference/transactions-and-runtime#mongooptions). -When `CONTRACT.MARKER_MISMATCH` shows up in production, apply the pending migration, or run `npx prisma db sign` when the database already matches the contract. The client warns by default. Set [`verifyMarker`](/orm/reference/transactions-and-runtime#postgresoptions) to `false` to skip the check. It cannot be made to fail. Run `prisma db verify` when you need a check that fails. +When `CONTRACT.MARKER_MISMATCH` shows up in production, apply the pending migration, or run `npx prisma db sign` when the database already matches the contract. The client warns by default, and you can set [`verifyMarker`](/orm/reference/transactions-and-runtime#postgresoptions) to `false` to skip the check. It cannot be made to fail, so run `prisma db verify` when you need a check that fails. ## Version control -Commit `contract.json` and `contract.d.ts` alongside the source. They hold structure only, no data and no credentials. Committing them lets teammates, CI, and deploys read the contract without running `npx prisma contract emit` first. Run `npx prisma contract emit` after every source change so the two files never trail the source. A CI job can check this in two lines: +Commit `contract.json` and `contract.d.ts` alongside the source: they hold structure only, no data and no credentials. Committing them lets teammates, CI, and deploys read the contract without running `npx prisma contract emit` first. Run `npx prisma contract emit` after every source change so the two files never trail the source. A CI job can check this in two lines: ```bash npx prisma contract emit @@ -145,7 +145,7 @@ git diff --exit-code src/prisma ## Prompt your coding agent -Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent. In an existing project, run `npx prisma skills sync`. The `prisma-8` skill covers the contract. Ask your agent to: +Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent. In an existing project, run `npx prisma skills sync` to add them. The `prisma-8` skill covers the contract, so ask your agent to: - "Explain the difference between contract.json and contract.d.ts in this project." - "Run prisma contract emit and show me what changed in contract.json." diff --git a/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx b/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx index 83711263ae5..fe5aef2417a 100644 --- a/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/the-data-contract.mdx @@ -25,7 +25,7 @@ model Post { } ``` -`npx prisma orm init` writes the first line, `// use prisma-8`, into every `.prisma` contract. The CLI does not need this line. The VS Code extension does, so keep it. A `contract.ts` file needs no header line. +`npx prisma orm init` writes the first line, `// use prisma-8`, into every `.prisma` contract. The CLI does not need this line, but the VS Code extension does, so keep it. A `contract.ts` file needs no header line. `npx prisma contract emit` turns this file into two files that the CLI and your application read: `contract.json` and `contract.d.ts`. Your queries are type-checked against the contract, migrations are planned as changes to it, and `npx prisma db verify` checks a live database against it. @@ -37,9 +37,9 @@ In Prisma ORM, the **contract** is what you write in your code. The **schema** i ## Why a contract -Your contract compiles to two plain files you can open and read. `contract.json` describes your models, how they are stored, and the [database features they need](/orm/contract-authoring/capabilities). `contract.d.ts` holds the TypeScript types derived from it. You can read both files in a code review and hand them to tools and coding agents. +Your contract compiles to two plain files you can open and read: `contract.json` describes your models, how they are stored, and the [database features they need](/orm/contract-authoring/capabilities), and `contract.d.ts` holds the TypeScript types derived from it. You can read both files in a code review and hand them to tools and coding agents. -`npx prisma db migrate` records in your database which contract it applied. The first time `db` runs a query it checks that record and logs a warning on a mismatch. To fail instead, run [`npx prisma db verify`](/cli/db-verify) in a deploy check. +`npx prisma db migrate` records in your database which contract it applied, and the first time `db` runs a query it checks that record and logs a warning on a mismatch. To fail instead, run [`npx prisma db verify`](/cli/db-verify) in a deploy check. ## How it works @@ -60,7 +60,7 @@ export default definePrismaConfig({ }); ``` -`definePrismaConfig` holds the whole config: which contract file to use and how to reach your database. Your connection string is in `prisma.config.ts` now, and the contract has no `datasource` block. `npx prisma orm init` writes `.env.example`, and your `DATABASE_URL` goes in `.env`. `@prisma/orm-postgres/config` exports `defineConfig`. The example renames it to `ormConfig` on import. The packages are `@prisma/orm-postgres` for PostgreSQL, `@prisma/orm-sqlite` for SQLite, and `@prisma/orm-mongo` for MongoDB. The examples here use PostgreSQL. +`definePrismaConfig` holds the whole config: which contract file to use and how to reach your database. Your connection string is in `prisma.config.ts` now, and the contract has no `datasource` block. `npx prisma orm init` writes `.env.example`, and your `DATABASE_URL` goes in `.env`. `@prisma/orm-postgres/config` exports `defineConfig`, which the example renames to `ormConfig` on import. The packages are `@prisma/orm-postgres` for PostgreSQL, `@prisma/orm-sqlite` for SQLite, and `@prisma/orm-mongo` for MongoDB. The examples here use PostgreSQL. To start a new project, run `npm create prisma@latest -- my-app`. To add Prisma ORM 8 to a project that has no Prisma ORM at all, run `npx prisma orm init`. If you already have a Prisma ORM 7 `schema.prisma`, follow the [PostgreSQL upgrade guide](/guides/upgrade-prisma-orm/postgresql) instead, and [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7) lists what changed. Both `npm create prisma@latest` and `npx prisma orm init` write `prisma.config.ts`, the contract file, and `src/prisma/db.ts`, and install the database package. @@ -80,11 +80,11 @@ npx prisma db migrate ## Two authoring modes, one `contract.json` [#two-authoring-modes-one-artifact] -**[PSL](/orm/contract-authoring/psl-syntax)** is the usual way to write the contract. It is a compact language for describing data. It is what `npm create prisma@latest` scaffolds, and what [`npx prisma contract infer`](/cli/contract-infer) writes when you start from an existing database. +**[PSL](/orm/contract-authoring/psl-syntax)**, a compact language for describing data, is the usual way to write the contract: it is what `npm create prisma@latest` scaffolds, and what [`npx prisma contract infer`](/cli/contract-infer) writes when you start from an existing database. -Define models with the **[TypeScript builder](/orm/contract-authoring/typescript-schema-builder)** instead when you want to build them with code. Reach for it when model definitions must be composed, generated, or shared as ordinary TypeScript modules. That page shows what a `contract.ts` file looks like. +Define models with the **[TypeScript builder](/orm/contract-authoring/typescript-schema-builder)** instead when you want to build them with code: reach for it when model definitions must be composed, generated, or shared as ordinary TypeScript modules. That page shows what a `contract.ts` file looks like. -Both modes produce the same contract, so migrations, database checks, and `db` behave the same no matter which mode you write. A project has exactly one contract file. Do not keep both a `contract.prisma` and a `contract.ts`: the config names one file and Prisma ORM reads only that one, so edits to the other file do nothing. To switch modes, change the path in `contract` and delete the old file. +Both modes produce the same contract, so migrations, database checks, and `db` behave the same no matter which mode you write. A project has exactly one contract file, so do not keep both a `contract.prisma` and a `contract.ts`: the config names one file and Prisma ORM reads only that one, so edits to the other file do nothing. To switch modes, change the path in `contract` and delete the old file. ## What the contract contains @@ -100,7 +100,7 @@ It contains no rows, no credentials, and no connection details, so committing th ## Prompt your coding agent -Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent. In an existing project, run `npx prisma skills sync`. The `prisma-8` skill covers the contract. Ask your agent to: +Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent. In an existing project, run `npx prisma skills sync` to add them. The `prisma-8` skill covers the contract, so ask your agent to: - "Using the prisma-8 skill, explain what our contract.json currently declares." - "Add an Invoice model to the contract and run prisma contract emit." diff --git a/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx b/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx index e94aa77ec36..5f4b1826ba4 100644 --- a/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx +++ b/apps/docs/content/docs/orm/contract-authoring/typescript-schema-builder.mdx @@ -6,22 +6,22 @@ metaTitle: Author the Prisma ORM contract in TypeScript metaDescription: 'How to define a Prisma ORM contract in TypeScript with defineContract, including fields, relations, enums, storage mapping, and extension types.' --- -In Prisma ORM 8, `schema.prisma` is replaced by a file called [the contract](/orm/contract-authoring/the-data-contract). It holds the model definitions you used to write in `schema.prisma`. You can write it in two forms. One is a `.prisma` file written in PSL, short for Prisma Schema Language. The other is TypeScript, in `src/prisma/contract.ts`, built with the `defineContract` builder. Both forms produce the same two files. [`npx prisma contract emit`](/cli/contract-emit) writes `contract.json` and `contract.d.ts` into the folder that holds your contract file. +In Prisma ORM 8, `schema.prisma` is replaced by a file called [the contract](/orm/contract-authoring/the-data-contract), which holds the model definitions you used to write in `schema.prisma`. You can write it in two forms: one is a `.prisma` file written in PSL, short for Prisma Schema Language, and the other is TypeScript, in `src/prisma/contract.ts`, built with the `defineContract` builder. Both forms produce the same two files: [`npx prisma contract emit`](/cli/contract-emit) writes `contract.json` and `contract.d.ts` into the folder that holds your contract file. ## When to choose TypeScript over PSL -[PSL](/orm/contract-authoring/psl-syntax) is the preferred way to write the contract. Both forms produce the same two files, so you give up nothing by staying with PSL. Use the TypeScript builder for the cases PSL does not cover. Reach for it when: +[PSL](/orm/contract-authoring/psl-syntax) is the preferred way to write the contract, and both forms produce the same two files, so you give up nothing by staying with PSL. Use the TypeScript builder for the cases PSL does not cover, and reach for it when: - model definitions must be split, composed, or reused across ordinary TypeScript modules or packages - you want to build models in a loop from data you already keep in TypeScript, such as one model per entry in a list of table names -If neither applies, write PSL. It is more compact, and it is what [`contract infer`](/cli/contract-infer) writes. +If neither applies, write PSL, which is more compact and is what [`contract infer`](/cli/contract-infer) writes. ## Point the config at the contract file -For a new project, run [`npx prisma orm init`](/cli/orm-init). It asks how you want to write your schema, and choosing TypeScript creates the contract file and the config together. +For a new project, run [`npx prisma orm init`](/cli/orm-init), which asks how you want to write your schema, and choosing TypeScript creates the contract file and the config together. -The config's `contract` path names the one file Prisma ORM reads. A `.ts` extension selects TypeScript authoring: +The config's `contract` path names the one file Prisma ORM reads, and a `.ts` extension selects TypeScript authoring: ```typescript title="prisma.config.ts" tab="PostgreSQL" import { definePrismaConfig } from "prisma/config"; @@ -49,7 +49,7 @@ export default definePrismaConfig({ The builder comes from your database's package: `@prisma/orm-postgres/contract-builder` for PostgreSQL, `@prisma/orm-mongo/contract-builder` for MongoDB. -Export the contract under the name `contract`, as below, or as the file's default export. Those are the only two names `prisma contract emit` looks for. Run `npx prisma contract emit` again after every edit to the contract, and commit the contract file together with [`contract.json` and `contract.d.ts`](/orm/contract-authoring/the-contract-artifact). +Export the contract under the name `contract`, as below, or as the file's default export, because those are the only two names `prisma contract emit` looks for. Run `npx prisma contract emit` again after every edit to the contract, and commit the contract file together with [`contract.json` and `contract.d.ts`](/orm/contract-authoring/the-contract-artifact). ```typescript title="src/prisma/contract.ts" tab="PostgreSQL" import { defineContract, enumType, member } from "@prisma/orm-postgres/contract-builder"; @@ -124,9 +124,9 @@ export const contract = defineContract({ models: { User, Post } }); ## How `defineContract` works -On PostgreSQL, `defineContract` takes an options object and then a function. You do not set a `provider` anywhere. Importing `@prisma/orm-postgres/contract-builder` is what selects PostgreSQL. The options object lists the extension packs you use, and you write `{}` when you use none. The function returns the contract's content: `models`, plus `enums` and `types` if you have them. +On PostgreSQL, `defineContract` takes an options object and then a function. You do not set a `provider` anywhere: importing `@prisma/orm-postgres/contract-builder` is what selects PostgreSQL. The options object lists the extension packs you use, and you write `{}` when you use none. The function returns the contract's content: `models`, plus `enums` and `types` if you have them. -Take `field`, `model`, `rel`, and `type` from the function's one argument. Import `defineContract`, `enumType`, and `member` from the package. The package also exports `model` and `rel` for use outside the function, plus a `field` that has only `column`, `generated`, and `namedType`. +Take `field`, `model`, `rel`, and `type` from the function's one argument, and import `defineContract`, `enumType`, and `member` from the package. The package also exports `model` and `rel` for use outside the function, plus a `field` that has only `column`, `generated`, and `namedType`. On MongoDB, `defineContract` also accepts a single object holding `models`, as above, and you import `field`, `model`, and `rel` from the package instead. The MongoDB builder differs from the PostgreSQL one in five ways: @@ -136,7 +136,7 @@ On MongoDB, `defineContract` also accepts a single object holding `models`, as a - Relations name the field on each side. `rel.hasMany` takes `{ from, to }` on MongoDB and `{ by }` on PostgreSQL. `to` accepts either the field name as a string or a typed reference such as `User.ref("_id")`, and both forms mean the same thing. PostgreSQL writes that reference as `User.refs.id`. - Indexes are an `indexes` option on the model, built with the `index` helper the same package exports, as in `indexes: [index({ email: 1 }, { unique: true })]`. The `1` is ascending, MongoDB's own index syntax. -Enums and extension packs work on MongoDB too. `enumType` and `member` come from `@prisma/orm-mongo/contract-builder`. Pass the enums in the same object as the models, as `defineContract({ models, enums })`, and extension packs go in the same `extensions` option. +Enums and extension packs work on MongoDB too: `enumType` and `member` come from `@prisma/orm-mongo/contract-builder`. Pass the enums in the same object as the models, as `defineContract({ models, enums })`, and extension packs go in the same `extensions` option. The examples below use the PostgreSQL builder. @@ -160,9 +160,9 @@ The examples below use the PostgreSQL builder. There is no helper for a date without a time, or a time without a date. For a date-only column, name the type yourself: `field.column({ codecId: "pg/date-temporal@1", nativeType: "date" } as const)`. `field.column(...)` and the chained `.column(...)` below are different calls: `field.column(descriptor)` builds a field from a type description, and `.column(name)` on an existing field sets its column name. Extension packs add more helpers of their own. -For a primary key whose value your app generates, use `field.id.uuidv4String()`, `field.id.uuidv7String()`, `field.id.ulid()`, `field.id.nanoid()`, `field.id.cuid2()`, or `field.id.ksuid()`. They all generate the ID in your app, and `uuidv4String` is the common choice. Each marks the field as the primary key for you. `field.id.uuidv4Native()` and `field.id.uuidv7Native()` do the same in a `uuid` column. There is no auto-increment helper, so use one of those `field.id.*` helpers. For a key the database generates instead, write `field.uuidNative().defaultSql("gen_random_uuid()").id()`. +For a primary key whose value your app generates, use `field.id.uuidv4String()`, `field.id.uuidv7String()`, `field.id.ulid()`, `field.id.nanoid()`, `field.id.cuid2()`, or `field.id.ksuid()`. They all generate the ID in your app, and `uuidv4String` is the common choice. Each marks the field as the primary key for you, and `field.id.uuidv4Native()` and `field.id.uuidv7Native()` do the same in a `uuid` column. There is no auto-increment helper, so use one of those `field.id.*` helpers. For a key the database generates instead, write `field.uuidNative().defaultSql("gen_random_uuid()").id()`. -`field.temporal.createdAt()` fills the column with `now()` when the row is created. `field.temporal.updatedAt()` fills it on every create and every update, and Prisma ORM sets it in the client, not with a database trigger. `field.namedType(x)` takes an enum or a type from an extension pack. +`field.temporal.createdAt()` fills the column with `now()` when the row is created, while `field.temporal.updatedAt()` fills it on every create and every update, and Prisma ORM sets it in the client, not with a database trigger. `field.namedType(x)` takes an enum or a type from an extension pack. Every field builder supports chained modifiers: @@ -192,15 +192,15 @@ export const contract = defineContract({}, ({ field, model }) => { }); ``` -`Role` does not go in the returned `enums` map. Using it on a field is enough to create the type in the database. +`Role` does not go in the returned `enums` map: using it on a field is enough to create the type in the database. ## Relations Relations are declared on the model builder with `.relations(...)` and the `rel` helpers, as the full example above shows. -`rel.hasMany(Model, { by })` names the foreign key field on the other model. `rel.hasOne(Model, { by })` is the same with at most one row on the other side. `rel.belongsTo(Model, { from, to })` maps the local foreign key field to the field it points at. +`rel.hasMany(Model, { by })` names the foreign key field on the other model, and `rel.hasOne(Model, { by })` is the same with at most one row on the other side. `rel.belongsTo(Model, { from, to })` maps the local foreign key field to the field it points at. -`rel.manyToMany(Model, { through, from, to })` goes through a join table. You declare the model for the join table yourself and pass it as `through`. Below, `PostTag` is that model, and it holds the two foreign key fields `postId` and `tagId`: +`rel.manyToMany(Model, { through, from, to })` goes through a join table: you declare the model for the join table yourself and pass it as `through`. Below, `PostTag` is that model, holding the two foreign key fields `postId` and `tagId`: ```typescript Post.relations({ @@ -208,7 +208,7 @@ Post.relations({ }); ``` -`rel.belongsTo` on its own does not create a foreign key constraint in the database. Ask for one by chaining `.sql(...)` on the relation itself, inside the `.relations({ ... })` object, as the full example above does: +`rel.belongsTo` on its own does not create a foreign key constraint in the database, so ask for one by chaining `.sql(...)` on the relation itself, inside the `.relations({ ... })` object, as the full example above does: ```typescript Post.relations({ @@ -220,13 +220,13 @@ Post.relations({ Prisma ORM does not check that the column types on the two sides of a relation match, so choose a field helper that produces the same column type as the key you point at. -`rel.hasMany`, `rel.hasOne`, `rel.belongsTo`, and `rel.manyToMany` also accept the model name as a string. Pass the model object, as above, and a typo is a compile error. Pass a string and the typo is reported when `prisma contract emit` builds the contract. +`rel.hasMany`, `rel.hasOne`, `rel.belongsTo`, and `rel.manyToMany` also accept the model name as a string. Pass the model object, as above, and a typo is a compile error, but pass a string and the typo is reported when `prisma contract emit` builds the contract. ## Storage mapping -`.sql(...)` maps a model to its table. The object form covers the common case: `User.sql({ table: "user" })`. +`.sql(...)` maps a model to its table, and the object form covers the common case: `User.sql({ table: "user" })`. -You can chain `.relations(...)`, `.attributes(...)`, and `.sql(...)` in any order, and you can skip any of them. A model with no relations calls `.sql({ table })` straight after `model(...)`. +You can chain `.relations(...)`, `.attributes(...)`, and `.sql(...)` in any order, and you can skip any of them, so a model with no relations calls `.sql({ table })` straight after `model(...)`. The callback form gives you the model's columns as `cols` and the constraint builders as `constraints`. Use it for indexes: @@ -273,7 +273,7 @@ export const contract = defineContract({ extensions: { pgvector } }, ({ field, m The key on `type.pgvector` is the pack's own name, not the name you gave the import. The name is in the pack's documentation, and pgvector's is `pgvector`. `Embedding1536` is a name you choose, and it appears under that name in `contract.d.ts`. Return the `types` map so the name reaches the two files. -Add the same pack to `prisma.config.ts`. It goes inside `ormConfig({ ... })`, beside `contract`, and the config imports the pack's `/control` export while the contract file imports its `/pack` export: +Add the same pack to `prisma.config.ts`: it goes inside `ormConfig({ ... })`, beside `contract`, and the config imports the pack's `/control` export while the contract file imports its `/pack` export: ```typescript title="prisma.config.ts" import { definePrismaConfig } from "prisma/config"; @@ -290,7 +290,7 @@ export default definePrismaConfig({ ## Keep the contract file free of changing values [#keep-the-contract-file-pure] -The contract file describes structure. These rules keep it usable: +The contract file describes structure, and these rules keep it usable: - Do not read `process.env`, the current time, or random values into the contract. A contract built from those values makes `npx prisma contract emit` produce a different `contract.json` on each run. - Keep field values plain: strings, numbers, booleans, and the builder's own objects. Functions, class instances, and `Date` objects do not serialize. @@ -301,13 +301,13 @@ Configuration that legitimately varies per environment, such as the database URL ## Parity with PSL -TypeScript and [PSL](/orm/contract-authoring/psl-syntax) authoring produce the same `contract.json` and `contract.d.ts` for an equivalent contract. You can move between the two forms without changing anything downstream. A project names exactly one contract file in its config. +TypeScript and [PSL](/orm/contract-authoring/psl-syntax) authoring produce the same `contract.json` and `contract.d.ts` for an equivalent contract, so you can move between the two forms without changing anything downstream. A project names exactly one contract file in its config. -No command converts a `.prisma` contract into TypeScript. To move an existing project, write the TypeScript file by hand, point `contract` in `prisma.config.ts` at it, and delete the `.prisma` file so the two can never disagree. +No command converts a `.prisma` contract into TypeScript, so to move an existing project, write the TypeScript file by hand, point `contract` in `prisma.config.ts` at it, and delete the `.prisma` file so the two can never disagree. ## Prompt your coding agent -Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent. In an existing project, run `npx prisma skills sync`. The `prisma-8` skill covers TypeScript authoring. Ask your agent to: +Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent. In an existing project, run `npx prisma skills sync` to add them. The `prisma-8` skill covers TypeScript authoring, so ask your agent to: - "Convert this contract.prisma to the TypeScript schema builder." - "Using the prisma-8 skill, add a unique constraint to the email field in our TypeScript schema." diff --git a/apps/docs/content/docs/orm/core-concepts.mdx b/apps/docs/content/docs/orm/core-concepts.mdx index 09a055a4607..e4a6290357c 100644 --- a/apps/docs/content/docs/orm/core-concepts.mdx +++ b/apps/docs/content/docs/orm/core-concepts.mdx @@ -54,9 +54,9 @@ npx prisma@latest contract emit 1. `contract.json`: a canonical JSON description of your models, storage layout, and required capabilities. 2. `contract.d.ts`: the TypeScript types derived from it, which is what makes your queries type-safe. -Every other part of the toolchain reads these artifacts, not your source file. The query APIs read `contract.d.ts` for types. The migration planner diffs two `contract.json` files. The runtime verifies `contract.json` against the database. That is why `contract emit` comes first in almost every workflow: after any contract change, emit before you plan, migrate, or run. +Every other part of the toolchain reads these artifacts, not your source file: the query APIs read `contract.d.ts` for types, the migration planner diffs two `contract.json` files, and the runtime verifies `contract.json` against the database. That is why `contract emit` comes first in almost every workflow: after any contract change, emit before you plan, migrate, or run. -Emission is deterministic. The same source always produces byte-identical artifacts, so both files are committed to version control and diff cleanly in code review. Think of the pair like `package.json` and `package-lock.json`: the source is what you ask for, the artifacts are the exact resolved result. +Emission is deterministic: the same source always produces byte-identical artifacts, so both files are committed to version control and diff cleanly in code review. Think of the pair like `package.json` and `package-lock.json`: the source is what you ask for, the artifacts are the exact resolved result. See [contract.json and contract.d.ts](/orm/contract-authoring/the-contract-artifact) for what is inside each file. diff --git a/apps/docs/content/docs/orm/data-modeling/index.mdx b/apps/docs/content/docs/orm/data-modeling/index.mdx index 374d6709f1b..0a8be0c8ca8 100644 --- a/apps/docs/content/docs/orm/data-modeling/index.mdx +++ b/apps/docs/content/docs/orm/data-modeling/index.mdx @@ -10,18 +10,18 @@ Data modeling is the process of describing the data your application needs and h For example, a blog has users, posts, and comments. A user has fields like an email and a name. A post has fields like a title and content. These models also relate to each other: a user can write many posts, and a post can have many comments. -In Prisma ORM, you define this structure in a `contract.prisma` file. This file becomes the shared contract between your application code, your database migrations, and your developer tools. +In Prisma ORM, you define this structure in your contract, the `contract.prisma` file that replaced `schema.prisma`. It holds your models the way `schema.prisma` did, and Prisma ORM derives both your TypeScript types and your migrations from it. -If you are coming from Prisma ORM 7, `contract.prisma` plays a similar role to the `schema.prisma` file you used before. +There is no `datasource` or `generator` block any more, because the connection string has moved to `prisma.config.ts`. [A complete contract](/orm/contract-authoring/psl-syntax#a-complete-contract) shows a whole file together with the config file, and if you are [coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7), that page covers the rename and the rest of the differences. -This page introduces the four building blocks of every Prisma ORM contract: +Prisma ORM 8 supports PostgreSQL and MongoDB today, and [Supported databases](/orm/supported-databases) lists the ones that are coming. Whichever you use, a contract is built from the same four building blocks: - [Models](#models): the things your application works with - [Primary keys](#primary-keys): how each record is identified - [Scalar fields](#scalar-fields): the values a model stores - [Relations](#relations): how models connect to each other -These building blocks apply to every database. Where databases differ is in how you model relations, and the [relational](/orm/data-modeling/relational-databases) and [MongoDB](/orm/data-modeling/mongodb) guides cover that in depth. +All four blocks apply to both databases. How you model relations is where the two differ, so continue with [relational data modeling](/orm/data-modeling/relational-databases) or [MongoDB data modeling](/orm/data-modeling/mongodb). ## Models @@ -35,13 +35,11 @@ model User { } ``` -Every record of a model has its own identity and its own lifecycle. Two posts with the same title are still two different posts, and a user who changes their email address is still the same user. - On a relational database, a model becomes a table. On MongoDB, it becomes a collection. ## Primary keys -A primary key is the field that uniquely identifies each record. Every model needs one. Mark it with `@id`: +A primary key is the field that uniquely identifies each record. Give every model one and mark it with `@id`, because `update` and `delete` fail at runtime on a model that has neither a primary key nor a unique field: ```prisma tab="PostgreSQL" model User { @@ -58,13 +56,13 @@ model User { } ``` -On MongoDB, the primary key maps to the document's mandatory `_id` field, and `ObjectId` is the idiomatic type for it. +On MongoDB, the primary key is the document's mandatory `_id` field, and it must be an `ObjectId`. `@map("_id")` gives the field that name in the database. The field takes no `@default`, because MongoDB assigns the id itself when you create a document without one. `@@map("users")` names the collection and is optional: without it, the collection is the model name with a lowercase first letter. ### Natural keys -A natural key is a value that already identifies the record in the real world. Use one when the value is stable, unique, and assigned outside your application: a standardized code you receive rather than invent. +A natural key is a value that already identifies the record in the real world. Use one when the value is stable, unique, and assigned outside your application, such as a standardized code you receive rather than invent. -Reference tables are the classic fit. A country is its ISO code, and the code never changes: +Natural keys fit reference tables best, the tables that hold a fixed set of values other records point at. A country, for example, is identified by its ISO code, and that code never changes: ```prisma model Country { @@ -73,26 +71,19 @@ model Country { } ``` -Records that point at `Country` now store a readable value (`US`, `DE`) instead of an opaque number. Currencies (`USD`, `EUR`) and similar lookup tables work the same way. +Records that point at `Country` now store a readable value (`US`, `DE`) instead of a meaningless number. Currencies (`USD`, `EUR`) and other reference tables work the same way. ### Surrogate keys -A surrogate key is a generated value with no business meaning: an auto-incrementing integer, a UUID, an ObjectId. It is the better default for records your application creates. - -The reason is stability. Values you might be tempted to use as a key, like an email address or a product SKU, change in practice. Changing a primary key is expensive because every record that points at the old value must be updated too. A surrogate key never changes. +A surrogate key is a generated value with no business meaning: an auto-incrementing integer, a UUID, an ObjectId. It is the better default for records your application creates, because it is stable. Values you might be tempted to use as a key, like an email address or a product SKU, change in practice, and changing a primary key is expensive because every record that points at the old value must be updated too. A surrogate key never changes. -Keep the natural value as a regular field and enforce its uniqueness with `@unique`. You get a stable key and the uniqueness guarantee: +Keep the natural value as a regular field and enforce its uniqueness with `@unique`, so you get a stable key and the uniqueness guarantee at the same time: ```prisma model User { id Int @id @default(autoincrement()) email String @unique } - -model Product { - id Int @id @default(autoincrement()) - sku String @unique -} ``` ### Which surrogate type to pick @@ -100,9 +91,8 @@ model Product { Pick by how the record is created and where its id travels: ```prisma -// Auto-incrementing integer: smallest and fastest to index, -// ordered by insertion. Good for internal records whose id -// never leaves your system. +// Auto-incrementing integer: smallest and fastest to index. +// Good for internal records whose id never leaves your system. model Invoice { id Int @id @default(autoincrement()) } @@ -118,16 +108,7 @@ model ApiToken { } ``` -A UUID is wider than an integer and random UUIDs index a little worse, but independent services never collide and nothing is leaked. - -```prisma -// ObjectId: the idiomatic MongoDB key. Generated without -// coordination and embeds its own creation time. -model Post { - id ObjectId @id @map("_id") - @@map("posts") -} -``` +A UUID takes more space than an integer, but independent services never produce the same one. `@default(uuid())` generates a random UUID, and `@default(uuid(7))` generates one that sorts by creation time. On MongoDB there is no choice to make, because the key is always the `ObjectId` shown above. ### Composite keys @@ -142,34 +123,45 @@ model UserTag { } ``` -A duplicate `(userId, tagId)` pair would be meaningless, so the pair is the key. Avoid composite keys on ordinary models: everything that points at the model then has to carry all of the key's fields. +A duplicate `(userId, tagId)` pair would be meaningless, so the pair itself is the key. [Many-to-many](/orm/data-modeling/relational-databases#many-to-many) writes a model like this one out with its relation fields. Avoid composite keys for ordinary models, though, because everything that points at the model then has to store all of the key's fields. ## Scalar fields A scalar field holds a single value. The common types: -| Type | Stores | -| ---------- | --------------------------------------------------------------------------- | -| `String` | Text | -| `Int` | 32-bit integer | -| `BigInt` | 64-bit integer | -| `Float` | Floating-point number | -| `Decimal` | Exact decimal, PostgreSQL `numeric`. The runtime value is a decimal string, so binary floating point never loses digits. Write the type as `Numeric(10, 2)` when you want an explicit precision and scale; PostgreSQL then rounds stored values to that scale and rejects values that exceed the precision | -| `Boolean` | `true` / `false` | -| `DateTime` | Timestamp | -| `Bytes` | Binary data, PostgreSQL `bytea`. The runtime value is a `Uint8Array` | -| `Json` | Arbitrary JSON value, stored in PostgreSQL's native `json` | -| `Jsonb` | Arbitrary JSON value, stored in PostgreSQL's `jsonb`. This is what `field.json()` and value objects use, and what a `Json` field meant before 0.17 | -| `ObjectId` | MongoDB document identifier | +| Type | Stores | +| --------------------------------------------------------- | ------------------------------------------------ | +| `String` | Text | +| `Int` | 32-bit integer | +| `BigInt` | 64-bit integer | +| `Float` | Floating-point number | +| `Decimal`, or `Numeric(10, 2)` with a precision and scale | Exact decimal, PostgreSQL `numeric` | +| `Boolean` | `true` / `false` | +| `DateTime` | Timestamp, PostgreSQL `timestamptz` | +| `Bytes` | Binary data, PostgreSQL `bytea` | +| `Json` | Arbitrary JSON value, PostgreSQL's native `json` | +| `Jsonb` | Arbitrary JSON value, PostgreSQL's `jsonb` | +| `ObjectId` | MongoDB document identifier | -Filtering on a `Jsonb` field is whole-value equality only: you can ask whether the column equals a given document, not whether a key inside it matches. A `Json` field offers no comparison at all in the ORM filter. Reach into the document with [raw SQL](/orm/reference/raw-queries) when you need a path query. +The PostgreSQL names in the table are the column types Prisma ORM creates. `ObjectId` is the only MongoDB-only type. -Prisma ORM's type system is extensible, so extensions can add more types, such as vectors or geometry. +Three types arrive in your code as something other than a plain JavaScript value. A `Decimal` reaches you as a string, so nothing is rounded through a JavaScript number, and a `Bytes` value reaches you as a `Uint8Array`. A `DateTime` reaches you as a `Temporal.Instant`, the standard JavaScript type for a point in time. `Temporal` is built into Node.js 26.8.2 and later, but Homebrew's build of Node.js 26 leaves it out, so check with `node -p "typeof Temporal"`. If that prints `undefined`, install the `temporal-polyfill` package and add `import "temporal-polyfill/full/global"` to your app's entry file, before any query runs. -Two modifiers change a field's shape: +`Json` and `Jsonb` are two separate types, and they differ in how you can filter on them. On a `Jsonb` field, `.where()` compares the whole value, so you can ask whether the column equals a given document but not whether a key inside it matches. A `Json` field cannot be compared in `.where()` at all, so use `Jsonb` unless you specifically need PostgreSQL's plain `json` column. When you need a path query into the document, reach for [raw SQL](/orm/reference/raw-queries). [Extensions](/orm/extensions) add more types, such as vectors or geometry. -- A trailing `?` makes the field optional: `name String?` -- A trailing `[]` makes it a list: `tags String[]` +A trailing `?` makes a field optional: `name String?`. A trailing `[]` makes it a list: `tags String[]`. + +A field can also hold one of a fixed set of values. Declare them in an `enum` block: + +```prisma +enum Priority { + @@type("pg/text@1") + Low = "low" + High = "high" +} +``` + +`@@type` says how the values are stored, here as PostgreSQL `text`, written as the database type plus a version that is always `@1` today. On MongoDB, write `@@type("mongo/string@1")`. You can also leave `@@type` out, in which case Prisma ORM infers the storage from the members: text for string values and an integer for whole numbers. [Enums](/orm/contract-authoring/psl-syntax#enums) has the rest of the syntax. ### How to pick a data type @@ -184,7 +176,7 @@ model Address { } ``` -Money is not a `Float`. Floating-point numbers cannot represent decimal amounts exactly, so sums drift by fractions of a cent. Store an integer number of the smallest unit: +Money is not a `Float`, because floating-point numbers cannot represent decimal amounts exactly and sums drift by fractions of a cent. For ordinary money, use `Int` and store the amount in the currency's smallest unit, such as cents, which also gives you a plain JavaScript number to work with. Reserve `Decimal` for values that need more decimal places than the currency has, such as a per-unit rate: ```prisma model Product { @@ -193,7 +185,7 @@ model Product { } ``` -A point in time is a `DateTime`, not a `String`. A real timestamp type gives you correct comparison, sorting, and range queries: +A point in time is a `DateTime`, not a `String`, because a real timestamp type gives you correct comparison, sorting, and range queries: ```prisma model Post { @@ -202,22 +194,27 @@ model Post { } ``` -When you are unsure between two sizes, lean toward the wider one (`BigInt` over `Int` for a counter that could grow). Widening a type later is a migration; the wider type today is free. +When you are unsure between two sizes, lean toward the wider one. For a counter that could grow past two billion, that means `BigInt` over `Int`, since changing `Int` to `BigInt` later needs a migration that picking `BigInt` now lets you skip. `BigInt` does cost you something in your code, though: the value reaches you as a JavaScript `bigint`, which `JSON.stringify` refuses to serialize until you call `.toString()` on it. -Mark a field optional (`?`) only when "absent" means something different from a sensible default. A required field with a default is often the clearer model. +Mark a field optional (`?`) only when "absent" means something different from a sensible default, such as `publishedAt DateTime?` where no value means unpublished. A required field with a default is often the clearer model. ## Relations A relation connects two models: a user has many posts, a post belongs to one user. -Two kinds of field describe a relation: +A relation uses two kinds of field. The model that holds the link needs both of them, while the model on the other end needs only a relation field: -- A field that stores the connection. This is a real column or document field, like `authorId`, holding the other record's primary key. -- A relation field, typed as the other model, like `author User`. It stores nothing itself; it tells Prisma ORM how to navigate the connection in queries. +- The link field stores the other record's primary key, like `authorId`. Prisma ORM 7 called this the relation scalar field. On a relational database it is the foreign key column, and on MongoDB it is the field holding the other document's `_id`. +- The relation field is typed as the other model, like `author User`. It creates no column of its own and only tells Prisma ORM how to follow the connection in a query. -The `@relation` attribute ties them together: `fields` names the local field that stores the link, and `references` names the field it points at on the other model. +The `@relation` attribute ties them together: `fields` names the link field, and `references` names the field it points at on the other model. On the other model, `posts Post[]` is the other end of the same relation. ```prisma +model User { + id Int @id @default(autoincrement()) + posts Post[] +} + model Post { id Int @id @default(autoincrement()) authorId Int @@ -225,20 +222,20 @@ model Post { } ``` -Relations come in three shapes: +Relations come in three kinds: -- One-to-one: a user has at most one profile. -- One-to-many: a user has many posts. -- Many-to-many: a post has many tags, and a tag appears on many posts. +- One-to-one: a user has at most one profile. The model that holds the link gets `@unique` on its link field, and the field on `User` is `profile Profile?`, not a list. [One-to-one](/orm/data-modeling/relational-databases#one-to-one) has both models written out. +- One-to-many: a user has many posts, as in the example above. +- Many-to-many: a post has many tags, and a tag appears on many posts. This needs a model for the join table, with one record per connected pair. Prisma ORM 7 let you write a list field on both sides with no such model, but that form is gone and `npx prisma contract emit` rejects it, so write the model out as [many-to-many](/orm/data-modeling/relational-databases#many-to-many) shows. -How each shape is stored, and which model should hold the connecting field, is where relational and document databases differ. Continue with the guide for your database: +How each kind is stored, and which model should hold the link field, is where relational and document databases differ. Continue with the guide for your database: -- [Relational data modeling](/orm/data-modeling/relational-databases) for PostgreSQL and other SQL databases: foreign keys, junction tables, and which side owns the key. +- [Relational data modeling](/orm/data-modeling/relational-databases) for PostgreSQL: foreign keys, the model for the join table, and which side owns the key. - [MongoDB data modeling](/orm/data-modeling/mongodb): embedding versus referencing, and polymorphic collections. ## Prompt your coding agent -Projects scaffolded with `create-prisma@latest` install [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent; the `prisma-8` skill covers contract authoring. Prompts that map to each section: +Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent, instruction files that tell it how Prisma ORM 8 works. In an existing project, run `npx prisma skills sync` to add them, then try prompts that map to each section: - "Using the prisma-8 skill, add a Product model with a surrogate id and a unique sku field." - "Add a Country reference table keyed by its ISO code." @@ -247,6 +244,7 @@ Projects scaffolded with `create-prisma@latest` install [Prisma ORM skills](/ai/ ## Next steps -- [Model relational data](/orm/data-modeling/relational-databases): one-to-one, one-to-many, many-to-many, and polymorphic relations on SQL databases. -- [Model MongoDB data](/orm/data-modeling/mongodb): embed or reference, and single-collection polymorphism. -- [Query your models](/orm/fundamentals/reading-data) once the schema is in place. +- [Model relational data](/orm/data-modeling/relational-databases): one-to-one, one-to-many, many-to-many, and polymorphic relations on PostgreSQL. +- [Model MongoDB data](/orm/data-modeling/mongodb): embed or reference, and polymorphic collections. +- [Generate a migration](/orm/migrations/generating-a-migration) to get the models you wrote into the database. The loop is: edit the contract, run `npx prisma contract emit` to check it and regenerate your types (it replaces `prisma generate`), run `npx prisma migration plan` to write the migration files, then run `npx prisma db migrate` to apply them. That page has the flags and explains how the next plan finds its starting point. +- [Query your models](/orm/fundamentals/reading-data) once your contract is in place. diff --git a/apps/docs/content/docs/orm/data-modeling/mongodb.mdx b/apps/docs/content/docs/orm/data-modeling/mongodb.mdx index 0f535271eb1..28a62020d65 100644 --- a/apps/docs/content/docs/orm/data-modeling/mongodb.mdx +++ b/apps/docs/content/docs/orm/data-modeling/mongodb.mdx @@ -6,34 +6,40 @@ metaTitle: MongoDB data modeling in Prisma ORM metaDescription: 'How to model data in Prisma ORM for MongoDB, including documents and collections, embedded documents versus references, and polymorphic collections.' --- -MongoDB stores data as documents grouped into collections. A document can nest objects and arrays directly, which gives you a real choice for related data: keep it inside the document (embed it) or store it in its own collection and link to it (reference it). - -Making that choice well is the core of MongoDB data modeling, and it is what this page focuses on. If you are new to models and keys, start with the [data modeling overview](/orm/data-modeling). +MongoDB stores data as documents grouped into collections. A document can nest objects and arrays directly, which gives you a real choice for related data: keep it inside the document (embed it) or store it in its own collection and link to it (reference it). Making that choice well is the core of MongoDB data modeling. If you are new to models and keys, start with the [data modeling overview](/orm/data-modeling). ## Documents, collections, and the _id key -A Prisma ORM model maps to a collection, and each record is a document. Every document has an `_id` field as its primary key. `ObjectId` is the idiomatic type for it: +Your models go in your contract, the `contract.prisma` file that replaced `schema.prisma`. A model maps to a collection, each record is a document, and every document has an `_id` field as its primary key. There is no `datasource` block on MongoDB: your connection string goes in `prisma.config.ts` instead, passed to `defineConfig` from `@prisma/orm-mongo/config`, and [the data contract](/orm/contract-authoring/the-data-contract) page shows that config file. ```prisma model User { id ObjectId @id @map("_id") name String - email String + email String @unique @@map("users") } ``` -`@map("_id")` maps the model's `id` field to MongoDB's mandatory `_id` field, and `@@map("users")` names the collection. +In the contract you call the field `id`, but in TypeScript it is always `_id`, never `id`, because `@map("_id")` sets the name in the database and in your code alike. So you write `user._id` and `where({ _id })`. `@@map("users")` names the collection and is optional: without it the collection takes the model name with a lowercase first letter, so `user`. `@unique` on `email` becomes a unique index when you migrate. -## Embed or reference +Write `ObjectId` for the key type. That is the contract type only: in TypeScript a document's own `_id` is a plain string, so there is no `ObjectId` to construct. The old `String @db.ObjectId` and `@default(auto())` are no longer allowed. -Two pieces of related data can live together in one document (embedded) or in separate collections linked by an id (referenced). +Leave the id out of the values you pass to `create`, and MongoDB assigns it. In the examples below, `db` is the client created in `src/prisma/db.ts`, and `db.orm` is the ORM client, one of the query APIs on `db` that [the ORM reference](/orm/reference) covers. -Embedding nests the data inside its parent: an order and its line items in one document. One read returns everything, and a write updates parent and children together, atomically. The trade-off is that embedded data has no independent life. You cannot query it on its own, and it rides along on every read of the parent. +```ts +import { db } from "./prisma/db"; +const user = await db.orm.users.create({ name: "Ada", email: "ada@example.com" }); +const found = await db.orm.users.where({ _id: user._id }).first(); +``` -Referencing keeps the data in its own collection and stores the linked document's `_id`. Loading both takes a second lookup, but each record stands on its own: it can be queried, listed, and updated independently, and it is not duplicated when several parents point at it. +The name after `db.orm.` is the collection name, so with `@@map("users")` you write `db.orm.users`, not `db.orm.user`. The values go straight into `create`, without the `data:` wrapper Prisma ORM 7 used. You `await` the whole chain, and the last call says what you want back: `.first()` for one document or `null`, `.all()` for every matching document. -A quick way to decide: +## Embed or reference + +Two pieces of related data can live together in one document (embedded) or in separate collections linked by an id (referenced). Embedding nests the data inside its parent: an order and its line items in one document. One read returns everything, and a write updates parent and children together, atomically. The trade-off is that you cannot read embedded data without its parent, and it comes back on every read of the parent. + +Referencing keeps the data in its own collection and stores the linked document's `_id`. Loading both takes a second read, but each record stands on its own: it can be queried, listed, and updated independently, and it is not duplicated when several parents point at it. A quick way to decide: | Signal | Example | Lean toward | | ------ | ------- | ----------- | @@ -48,49 +54,46 @@ A blog post's comments make it concrete. If you always render them with the post ## Embedded documents -Describe the shape of embedded data with a `type` block, then use it as a field. An embedded type has no collection of its own; it exists only inside its parent document. +Describe the shape of embedded data with a `type` block, then use it as a field. An embedded value has no `_id` and no collection of its own, so it is created, loaded, updated, and deleted with the document that holds it. ```prisma type Address { street String city String - zip String? country String } - model User { id ObjectId @id @map("_id") name String + tags String[] address Address? @@map("users") } ``` -A single embedded value models a one-to-one. A list of embedded values models a one-to-many: +A single embedded value models a one-to-one relation. A list of embedded values models a one-to-many relation: `addresses Address[]` stores every address inside the user document, with no second collection and no join to load them. Pass an embedded value to `create` as a plain nested object: -```prisma -type CartItem { - productId String - name String - amount Int -} - -model Cart { - id ObjectId @id @map("_id") - items CartItem[] - @@map("carts") -} +```ts +const address = { street: "1 Main St", city: "Berlin", country: "DE" }; +await db.orm.users.create({ name: "Ada", tags: [], address }); ``` -`Cart.items` lives inside the cart document. There is no separate collection and no join to load the items; every read of a cart returns them. +In an `update`, the callback argument `u` builds the changes you want, and because the callback returns an array, several changes go in one call. Use `u.fieldName` to change a top-level field, or `u("path.to.field")` for a field inside an embedded document, written as a string because a dotted path is not a property name. On a list of scalars, `.push(...)` adds values and `.pull(...)` removes them: -An embedded `Address` has no `_id` and cannot be queried on its own. It is loaded, updated, and deleted with the document that holds it. That is the right behavior for values that belong to their parent, and the wrong one for records with their own life. +```ts +await db.orm.users.where({ _id: user._id }).update((u) => [u("address.city").set("Hamburg")]); +await db.orm.users.where({ _id: user._id }).update((u) => [u.tags.push("admin"), u.tags.pull("new")]); +``` + +Replace a whole embedded value with `u.address.set({ ... })`, remove it with `u.address.unset()`, and replace a list of embedded values as a whole with `u.addresses.set([ ... ])`. Filtering is more limited: the object form of `.where()` filters on top-level model fields only, so to match on a field inside an embedded document you use the pipeline builder, a chained builder for MongoDB aggregation pipelines: -:::warning +```ts +const query = db.query.from("users").match((f) => f("address.city").eq("Berlin")).build(); +``` -Embedded arrays are only a good idea while they stay bounded. An array that grows forever slows every read of the parent and pushes the document toward MongoDB's 16 MB limit. When a list has no natural cap, reference it instead. +`build()` returns the query rather than the rows, and [the pipeline builder](/orm/fundamentals/advanced-queries#mongodb-pipeline-builder) page explains how to run it and what comes back. -::: +Embedded arrays are only a good idea while they stay bounded. An array that grows forever slows every read of the parent and pushes the document toward MongoDB's 16 MB limit, where a write that exceeds it fails, so when a list has no natural cap, reference it instead. ## References across collections @@ -103,7 +106,6 @@ model User { posts Post[] @@map("users") } - model Post { id ObjectId @id @map("_id") title String @@ -113,47 +115,53 @@ model Post { } ``` -This is a one-to-many by reference: many posts point at one user, and each post is queryable on its own. When you [include the relation in a query](/orm/fundamentals/relations-and-joins), Prisma ORM resolves it with a `$lookup` aggregation. +This is a one-to-many by reference: many posts point at one user, and each post is queryable on its own. Note that in `references: [id]` you write the field's contract-side name, `id`, not `_id`. There are no nested writes on MongoDB, so create the user first and then the post with its `authorId`. To load a post together with its author, chain `.include("author")`, which adds the related record to every row you get back: -A `$lookup` is a real cost on hot paths, so document models often denormalize: embed a small copy of the fields a read needs (a comment stores its author's name) while the full record stays in its own collection. You trade some duplication, and the work of keeping the copy in sync, for reads that touch one document. +```ts +await db.orm.posts.create({ title: "Hello", authorId: user._id }); +const posts = await db.orm.posts.include("author").all(); +``` -## Polymorphic collections +`.include("posts")` on `db.orm.users` works the other way and gives each user an array of their posts. See [relations and joins](/orm/fundamentals/relations-and-joins) for the rest of the query API. -MongoDB collections do not enforce a single shape, so it is idiomatic to store documents of different kinds in one collection and tell them apart with a discriminator field. +Resolving a reference means a lookup into the other collection on every read. If a read is frequent, you can copy the few fields it needs into the parent document, such as a comment storing its author's name, but the copy does not follow the original when it changes, so you update it yourself. -Prisma ORM models this with a base model plus variant models, all sharing one collection: +## Polymorphic collections + +One collection can hold more than one kind of document. You declare a base model for the shared fields and a variant model for each kind, and a discriminator field on the base model records which kind each document is. ```prisma model Post { id ObjectId @id @map("_id") title String kind String - @@discriminator(kind) @@map("posts") } - model Article { summary String - @@base(Post, "article") } - model Tutorial { difficulty String duration Int - @@base(Post, "tutorial") } ``` -`@@discriminator(kind)` names the field that records each document's variant. `@@base(Post, "article")` declares `Article` as the `"article"` variant of `Post`. A query for posts returns articles and tutorials together, and you can narrow to one variant in queries. +`@@discriminator(kind)` takes the name of the field that records each document's variant, so write `kind` and not `"kind"`. `@@base(Post, "article")` declares `Article` as the `"article"` variant of `Post`. A variant declares only its own fields, since `id`, `title`, and `kind` come from `Post`, and it uses its base's collection, so it needs no `@@map` of its own. + +A query for posts returns articles and tutorials together. To reach a single variant, go through the base model: `db.orm.posts.variant("Article")` limits the query to one variant, and it takes the variant's model name, `"Article"`, not the discriminator value `"article"`. Creating through a variant fills in `kind` for you: + +```ts +const article = await db.orm.posts.variant("Article").create({ title: "Hello", summary: "A short post" }); +``` -Use one polymorphic collection when the variants are handled together far more than separately: a notifications collection of email, SMS, and push messages read as one stream. Prefer separate collections when the types rarely appear in the same query or need very different indexes. +Use one collection when you almost always query the variants together, for example email, SMS, and push notifications that you read as a single feed. Prefer separate collections when the types rarely appear in the same query or need very different indexes. ## Prompt your coding agent -Projects scaffolded with `create-prisma@latest` install [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent; the `prisma-8` skill covers document modeling. Prompts that map to each section: +Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8), instruction files for coding agents. In an existing project, run `npx prisma skills sync` to add them. The `prisma-8` skill covers document modeling, so try prompts that map to each section: - "Using the prisma-8 skill, add an embedded Address type to the User model." - "Cart items are always read with the cart. Model them as an embedded list." @@ -162,6 +170,7 @@ Projects scaffolded with `create-prisma@latest` install [Prisma ORM skills](/ai/ ## Next steps +- Start a new project with `npm create prisma@latest -- my-app`, or run `npx prisma orm init` in an existing one. After every change to your contract, run `npx prisma contract emit` to check it and regenerate your types, then `npx prisma migration plan` to write a migration and `npx prisma db migrate` to apply it, which creates the collections and indexes. [Generating a migration](/orm/migrations/generating-a-migration) has the flags and explains how the next plan finds its starting point. - [Query documents](/orm/fundamentals/reading-data) and [resolve references](/orm/fundamentals/relations-and-joins) with `.include(...)`. - Aggregate and reshape documents with the [pipeline builder](/orm/fundamentals/advanced-queries#mongodb-pipeline-builder). - To model relational data, see the [relational data modeling guide](/orm/data-modeling/relational-databases). diff --git a/apps/docs/content/docs/orm/data-modeling/relational-databases.mdx b/apps/docs/content/docs/orm/data-modeling/relational-databases.mdx index 2c925ac076e..bcc70118349 100644 --- a/apps/docs/content/docs/orm/data-modeling/relational-databases.mdx +++ b/apps/docs/content/docs/orm/data-modeling/relational-databases.mdx @@ -1,18 +1,18 @@ --- title: Relational data modeling -description: 'Model one-to-one, one-to-many, many-to-many, and polymorphic relations for PostgreSQL and other SQL databases.' +description: 'Model one-to-one, one-to-many, many-to-many, and polymorphic relations for PostgreSQL.' url: /orm/data-modeling/relational-databases metaTitle: Relational data modeling in Prisma ORM metaDescription: 'How to model one-to-one, one-to-many, many-to-many, and polymorphic relations in Prisma ORM for PostgreSQL, including which side owns the foreign key.' --- -In a relational database, each model becomes a table and each scalar field becomes a column. Models connect through foreign keys: a column in one table that holds the primary key of a row in another table. +In a relational database, each model becomes a table and each scalar field becomes a column. Models connect through foreign keys: a column in one table that holds the primary key of a row in another table. If you are new to models and keys, start with the [data modeling overview](/orm/data-modeling). -This page shows how to model each relation shape and, for each one, where the foreign key belongs. If you are new to models and keys, start with the [data modeling overview](/orm/data-modeling). +You write your models in your contract, the `contract.prisma` file that replaced `schema.prisma`. After every change to it, run `npx prisma contract emit`, which checks the contract and regenerates your TypeScript types without touching the database. The query examples below use `db`, the client you create once in `src/prisma/db.ts`, the file `npx prisma orm init` writes. On it, `db.orm` holds your models by model name, and `public` is the PostgreSQL schema your tables are in. ## How relations are declared -The model that stores the connection carries two fields: a scalar field for the foreign key column, and a relation field that navigates it. `@relation(fields:, references:)` ties them together: +The model whose table gets the foreign key column declares two fields: a scalar field for the foreign key column, and a relation field typed as the other model. `@relation(fields:, references:)` ties them together: ```prisma model Post { @@ -22,11 +22,23 @@ model Post { } ``` -The side that carries the `fields` argument owns the foreign key. Which side that should be is the main decision in the shapes below. +The side that declares the `fields` argument owns the foreign key, and deciding which side that should be is the main choice in each relation kind below. + +To let a post have no author, make the foreign key field and the relation field both optional: `authorId Int?` and `author User?`. The two fields have to agree, so if one is optional and the other is required, `npx prisma contract emit` reports an error. + +When a `User` row is deleted or its `id` changes, referential actions decide what happens to the `Post` rows that point at it. Add `onDelete` and `onUpdate` to `@relation`, as in `@relation(fields: [userId], references: [id], onDelete: Cascade)`, and they become the foreign key's `ON DELETE` and `ON UPDATE` clauses. If you write neither, the foreign key gets neither clause and the database decides what happens. The five actions are: + +- `Cascade` deletes or updates the matching rows. +- `Restrict` blocks the change. +- `SetNull` clears the foreign key, so the foreign key field has to be optional. +- `SetDefault` writes the foreign key's default value, which has to point at a row that exists. +- `NoAction` is the same as writing nothing. + +Two relations between the same pair of models need a name on both sides, such as `@relation("Authored", fields: [authorId], references: [id])` on one side and `@relation("Authored")` on the other. Without the name, `npx prisma contract emit` reports an error. ## One-to-many -A one-to-many relation (`1:n`) connects one record to many: one user writes many posts, and each post belongs to one user. This is the most common relation shape. +A one-to-many relation (`1:n`) connects one record to many: one user writes many posts, and each post belongs to one user. This is the most common relation kind. ```prisma model User { @@ -43,20 +55,22 @@ model Post { } ``` -The foreign key lives on the "many" side, `Post.authorId`, because each post points at exactly one user, while a user points at an open-ended list of posts. The `posts Post[]` field on `User` is the back-relation: it stores nothing in the database and is inferred from the foreign key on `Post`. +The foreign key is on the "many" side, `Post.authorId`, because each post points at exactly one user, while a user points at an open-ended list of posts. Use a one-to-many whenever a record belongs to one parent and a parent has many children: comments on a post, line items on an order, employees in a department. A model can also point at itself: a category with a parent category is an ordinary one-to-many whose relation field is typed as the same model, and a single self-relation like this needs no relation name. -Use a one-to-many whenever a record belongs to one parent and a parent has many children: comments on a post, line items on an order, employees in a department. +`posts Post[]` on `User` is the mirror field, the field on the other side of the relation, which stores nothing in the database. You can leave it out, and the relation still works: you then read it from the foreign key side with `db.orm.public.Post.where({ authorId: user.id }).all()`, where `.all()` is the call that runs the query and returns every matching row. -Because the list field is virtual, you never write to it. To connect a post to a user, set the foreign key: +To connect a post to a user, set the foreign key when you create the post. Here `user` is a user record you read earlier: ```typescript +import { db } from "./prisma/db"; + const post = await db.orm.public.Post.create({ title: "Hello", authorId: user.id, }); ``` -See [Relations and joins](/orm/fundamentals/relations-and-joins#one-to-many) for querying in both directions. +Reading back the other way, `db.orm.public.Post.include("author").all()` returns each post with its user nested on it. [Relations and joins](/orm/fundamentals/relations-and-joins#one-to-many) covers querying in both directions. ## One-to-one @@ -78,37 +92,23 @@ model Profile { } ``` -Declare the relation on the side that holds the foreign key (`Profile` here). You can add the mirror field on the other model as well (`profile Profile?` on `User`); a bare optional model-typed field is enough, and it needs no `@relation` of its own. The foreign key it mirrors must be unique, or emit rejects the field with `PSL_NON_UNIQUE_BACKRELATION`. With the mirror field in place, [the relation reads from either side](/orm/fundamentals/relations-and-joins#one-to-one). +Declare the relation on the side that holds the foreign key, `Profile` here. You can also add the mirror field `profile Profile?` to `User`, with a type and nothing else, because it needs no `@relation` of its own. It is a single `Profile?` rather than a list because `userId` is unique, and it has to be optional because nothing in the database guarantees a matching profile row. With it in place you can [read the relation from either side](/orm/fundamentals/relations-and-joins#one-to-one), as in `db.orm.public.User.include("profile").all()`. ### Which side owns the foreign key -Put the foreign key on the dependent side: the record that cannot exist on its own. - -A profile needs a user. A user does not need a profile. So `Profile` carries `userId`: - -```prisma -model Profile { - id Int @id @default(autoincrement()) - userId Int @unique - user User @relation(fields: [userId], references: [id]) -} -``` +Put the foreign key on the dependent side, meaning the record that cannot exist on its own. A profile needs a user, but a user does not need a profile, so `Profile` holds `userId`. The other signals all point at the same side. The dependent record is: - The one you create second. Insert the `User`, then its `Profile`. - The one you would delete when the parent goes away. -- The one whose absence is normal. A user without a profile is fine; a profile without a user is broken data. - -Placing the key on the dependent side keeps `User` free of profile concerns, lets a user exist with no profile, and guarantees every profile has exactly one user. - -If neither side is clearly dependent, put the key on the side you query from less often. +- The one whose absence is normal. A user without a profile is fine, and a profile without a user is broken data. ## Many-to-many A many-to-many relation (`m:n`) connects many records on each side: a post has many tags, and a tag applies to many posts. -A single foreign key cannot express this, because each side needs to point at many records. The answer is a junction model: a third model that holds one record per connected pair. +One foreign key column cannot hold many values, so you add a third model, the model for the join table, which holds one record per connected pair. ```prisma model Post { @@ -133,41 +133,23 @@ model PostTag { } ``` -`PostTag` is the model for the join table, and it is two one-to-many relations back to back. Its composite primary key, `@@id([postId, tagId])`, enforces one record per pair. The list fields on `Post` and `Tag` name the far side (`Tag[]`, `Post[]`), and the schema compiler connects them through `PostTag`, so queries read `post.tags` directly. +The composite primary key on `PostTag`, `@@id([postId, tagId])`, allows one record per pair. Note that `Post.tags` is typed `Tag[]`, not `PostTag[]`, and the same goes for `Tag.posts`: you name the model at the other end, not the join table. If a second join table connects the same two models, the relation name goes on both sides again, which here means the list field `Post.tags` and the field that points back at `Post`, `PostTag.post`. -Connect a post to a tag with a nested write on the list field, or create the `PostTag` record yourself. Both write the same row, so pick one per pair: running both inserts the same composite key twice, and the second fails. +Connect a post to a tag with a nested write on the mirror field: ```typescript -// Nested write on the list field await db.orm.public.Post.where({ id: post.id }).update({ tags: (t) => t.connect([{ id: tag.id }]), }); - -// Or, equivalently, create the junction record yourself -await db.orm.public.PostTag.create({ postId: post.id, tagId: tag.id }); ``` -The junction is an ordinary model, so it can carry data about the pairing itself. When the tag was added, who added it, a sort order: add the field to the junction model. - -```prisma -model PostTag { - postId Int - tagId Int - addedAt DateTime @default(now()) - post Post @relation(fields: [postId], references: [id]) - tag Tag @relation(fields: [tagId], references: [id]) - - @@id([postId, tagId]) -} -``` +`t` is the argument of the nested write callback: `t.create([...])` makes new related records and links them, `t.connect([...])` links existing ones, and `t.disconnect([...])` removes the link. The nested write goes through the mirror fields, `Post.tags` and `Tag.posts`, so if you leave them out you write the `PostTag` rows yourself with `db.orm.public.PostTag.create({ postId: post.id, tagId: tag.id })`. Because `PostTag` is an ordinary model, the join table can also hold data about the pairing itself: to record when a tag was added, add `addedAt DateTime @default(now())` to `PostTag`. -An implicit many-to-many (list fields on both sides with no junction model, as in Prisma ORM 7) is not supported yet: the schema compiler rejects it and asks for an explicit join model. Model the junction explicitly; [Relations and joins](/orm/fundamentals/relations-and-joins#many-to-many) shows how to traverse it in one query. +An implicit many-to-many, with list fields on both sides and no join table as in Prisma ORM 7, is not supported, so declare `PostTag` yourself. If you skip it, `npx prisma contract emit` reports an error whose `code` is `PSL_ORPHANED_BACKRELATION`. [Relations and joins](/orm/fundamentals/relations-and-joins#many-to-many) shows how to read across the join table in one query. ## Polymorphic relations -Sometimes several kinds of record share a common core but each carries its own extra fields: every `Task` has a title, but a `Bug` also has a severity and a `Feature` has a target release. - -Prisma ORM models this with a base model plus variant models. A discriminator field records which variant each row is: +Sometimes several kinds of record share a common core but each has its own extra fields: every `Task` has a title, but a `Bug` also has a severity and a `Feature` has a target release. Prisma ORM models this with a base model plus variant models. One field on the base model, the discriminator, records which variant each row is: ```prisma model Task { @@ -193,34 +175,40 @@ model Feature { } ``` -`@@discriminator(type)` marks the field that records the variant. `@@base(Task, "bug")` declares that `Bug` is the `"bug"` variant of `Task`. +`@@discriminator(type)` marks the field that records the variant, and `@@base(Task, "bug")` declares that `Bug` is the `"bug"` variant of `Task`. Both take a field or model name without quotes, while the variant value is a string. A variant has every field of its base plus the ones it declares, and it needs no `@id` of its own because it uses the base model's primary key. -Variants appear as their own models in queries (`db.orm.public.Bug` alongside `db.orm.public.Task`), and a query on the base model returns every variant. To write a variant, narrow the base collection with `.variant(...)` and create through it; the discriminator is injected for you: +Read and write variants through the base model, where `.variant(...)` limits the query to one variant. It takes the model name, `"Bug"`, with a capital letter, and passing the discriminator value `"bug"` instead is a type error. On a create, the discriminator value is filled in for you: ```typescript await db.orm.public.Task.variant("Bug").create({ title: "Crash on save", severity: "high" }); ``` -Chaining `.variant(...)` on a read narrows the same way, applying the discriminator filter and giving the collection the variant's row shape. +A query on `Task` with no `.variant(...)` returns every variant. Each row carries the base fields plus only its own variant's fields, and the discriminator field holds that variant's value, such as `"bug"`. Switch on `task.type` to reach a variant's own fields: -Each variant picks one of two storage layouts: +```typescript +switch (task.type) { + case "bug": + return task.severity; + case "feature": + return task.targetRelease; +} +``` -- A variant with no `@@map`, like `Bug`, shares the base table. Its extra columns live alongside the base columns and must be nullable at the storage level, because a `Feature` row has no severity. -- A variant with its own `@@map`, like `Feature`, gets its own table holding only its extra columns, linked back to the base table's primary key. Its columns keep their constraints, at the cost of a join to read a full variant. +`@@map` decides how a variant is stored, which is a new job for it: in Prisma ORM 7, `@@map` only renamed a table. A variant with no `@@map`, like `Bug`, is stored in the base model's table, and its extra columns are added there as nullable columns, because a `Feature` row has no severity. A variant with its own `@@map`, like `Feature`, is split into its own table, named by the string you pass, and its columns there can be `NOT NULL`, though reading a full variant then costs a join. -Use polymorphism when you query the variants together (one feed of tasks, one stream of events) and each variant carries real structure of its own. If you never query them together, separate models are simpler. If the variants differ by one nullable field, a single model is simpler still. +Start with the shared table, and give a variant its own table only when its columns have to be `NOT NULL` in the database or when the variant has many columns of its own. The pattern as a whole fits when you query the variants together (one feed of tasks, one stream of events) and each variant has real structure of its own. If you never query them together, separate models are simpler, and if the variants differ by one nullable field, a single model is simpler still. ## Prompt your coding agent -Projects scaffolded with `create-prisma@latest` install [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent; the `prisma-8` skill covers relation modeling. Prompts that map to each section: +Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent. In an existing project, run `npx prisma skills sync` to add them. The `prisma-8` skill covers relation modeling, so try prompts that map to each section: - "Using the prisma-8 skill, add a one-to-many between User and Post with the foreign key on Post." - "Make the Profile relation one-to-one by adding a unique constraint to its foreign key." -- "Model a many-to-many between Post and Tag with an explicit junction model and an addedAt timestamp." +- "Model a many-to-many between Post and Tag with an explicit model for the join table and an addedAt timestamp." - "Split Task into Bug and Feature variants with a discriminator field." ## Next steps -- [Query relations](/orm/fundamentals/relations-and-joins) with `.include(...)`, relation predicates, and junction traversal. +- [Query relations](/orm/fundamentals/relations-and-joins) with `.include(...)`, filters on relation data, and reads across the join table. +- To change the database to match the contract, edit `contract.prisma`, run `npx prisma contract emit`, then run `npx prisma migration plan` to write the migration files and `npx prisma db migrate` to apply them. [Generating a migration](/orm/migrations/generating-a-migration) has the flags and explains how the next plan finds its starting point. - To model for MongoDB, see the [MongoDB data modeling guide](/orm/data-modeling/mongodb). -- Referential actions work today: add `onDelete` and `onUpdate` to `@relation` with `Cascade`, `Restrict`, `SetNull`, `SetDefault`, or `NoAction`, as in `@relation(fields: [userId], references: [id], onDelete: Cascade)`, and the emitter lowers them to the foreign key's `ON DELETE` / `ON UPDATE` clause. diff --git a/apps/docs/content/docs/orm/extensions/index.mdx b/apps/docs/content/docs/orm/extensions/index.mdx index 85f8eead23f..fc7add35ecb 100644 --- a/apps/docs/content/docs/orm/extensions/index.mdx +++ b/apps/docs/content/docs/orm/extensions/index.mdx @@ -42,4 +42,4 @@ When the package is on npm, [submit it to the directory](https://www.prisma.io/e ## See also - [Using extensions](/orm/extensions/using-extensions): install an extension and run your first query with it -- [Supported databases](/orm/reference/supported-databases): which databases Prisma ORM ships and what is planned +- [Supported databases](/orm/supported-databases): which databases Prisma ORM ships and what is planned diff --git a/apps/docs/content/docs/orm/extensions/using-extensions.mdx b/apps/docs/content/docs/orm/extensions/using-extensions.mdx index c7281d4c359..2e7f7e56927 100644 --- a/apps/docs/content/docs/orm/extensions/using-extensions.mdx +++ b/apps/docs/content/docs/orm/extensions/using-extensions.mdx @@ -57,7 +57,7 @@ export const db = postgres({ ## 4. Use the new type in your schema -The extension's types are now available in your contract. Declare a vector column with an explicit dimension: +The extension's types are now available in your contract, so declare a vector column with an explicit dimension: ```prisma title="src/prisma/contract.prisma" types { @@ -96,7 +96,7 @@ One package, two registrations, one database: ## Capabilities -Each extension names what it adds under a key like `pgvector.cosine`. You never write these keys by hand. Registering the extension in the config records them in your contract. Registering it in `db.ts` provides them at runtime. +Each extension names what it adds under a key like `pgvector.cosine`, but you never write these keys by hand. Registering the extension in the config records them in your contract, and registering it in `db.ts` provides them at runtime. The point of this bookkeeping is to fail early: @@ -127,7 +127,7 @@ The database packages are extensions too: `@prisma/orm-postgres`, `@prisma/orm-s :::note[Supabase does not follow the recipe above] -Supabase ships no `/control` subpath. Import its pack from `@prisma/orm-extension-supabase/pack` instead and pass it to the same `ormConfig({ extensions: [supabasePack] })` call the recipe above uses. The client is where the wiring differs: build it with the `supabase()` factory from `@prisma/orm-extension-supabase/runtime` rather than `postgres()`. That factory is role-first (`asUser(jwt)`, `asAnon()`, `asServiceRole()`) and carries the JWT validation and RLS binding. See the [runnable example](https://github.com/prisma/orm/tree/main/examples/supabase) for the whole wiring. +Supabase ships no `/control` subpath, so import its pack from `@prisma/orm-extension-supabase/pack` instead and pass it to the same `ormConfig({ extensions: [supabasePack] })` call the recipe above uses. The client is where the wiring differs: build it with the `supabase()` factory from `@prisma/orm-extension-supabase/runtime` rather than `postgres()`. That factory is role-first (`asUser(jwt)`, `asAnon()`, `asServiceRole()`) and carries the JWT validation and RLS binding. See the [runnable example](https://github.com/prisma/orm/tree/main/examples/supabase) for the whole wiring. ::: diff --git a/apps/docs/content/docs/orm/fundamentals/advanced-queries.mdx b/apps/docs/content/docs/orm/fundamentals/advanced-queries.mdx index dbea0b6d249..f9feb755d06 100644 --- a/apps/docs/content/docs/orm/fundamentals/advanced-queries.mdx +++ b/apps/docs/content/docs/orm/fundamentals/advanced-queries.mdx @@ -6,13 +6,13 @@ metaTitle: Advanced queries in Prisma ORM metaDescription: 'Use the Prisma ORM SQL query builder for explicit joins, grouped aggregates, and RETURNING, and the typed aggregation pipeline builder on MongoDB.' --- -Most queries fit the ORM API. When one doesn't, there is a query builder. Prisma ORM 8 supports PostgreSQL, SQLite, and MongoDB; the SQL query builder is for PostgreSQL and SQLite, the pipeline builder for MongoDB. Neither builder asks you to write SQL as a string. When the builder cannot express a query, `db.raw.sql` lets you write one, with values still sent as parameters. Both builders come with `@prisma/orm-postgres` and `@prisma/orm-mongo`, so there is nothing extra to install. +Most queries fit the ORM API, and when one doesn't, there is a query builder. Prisma ORM 8 supports PostgreSQL, SQLite, and MongoDB; the SQL query builder is for PostgreSQL and SQLite, the pipeline builder for MongoDB. Neither builder asks you to write SQL as a string. When the builder cannot express a query, `db.raw.sql` lets you write one, with values still sent as parameters. Both builders come with `@prisma/orm-postgres` and `@prisma/orm-mongo`, so there is nothing extra to install. -The schema file is now `contract.prisma`; it is the same schema language as Prisma ORM 7 (with the type changes on [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7#schema)). The docs call it your contract. TypeScript checks both builders against it. +The schema file is now `contract.prisma`; it is the same schema language as Prisma ORM 7 (with the type changes on [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7#schema)). The docs call it your contract, and TypeScript checks both builders against it. -You choose per query, not per app. Using the ORM API everywhere and a builder in the three places that need it is normal. +You choose per query, not per app, so using the ORM API everywhere and a builder in the three places that need it is normal. -Every example here imports `db`. `db` is the client you create once in `src/prisma/db.ts`, and `prisma orm init` writes that file for you. The examples run from a file directly inside `src/`. +Every example here imports `db`, the client you create once in `src/prisma/db.ts`, and `prisma orm init` writes that file for you. The examples run from a file directly inside `src/`. Every example on this page queries these four models: @@ -55,7 +55,7 @@ In Prisma ORM 8 a many-to-many relation is the two list fields plus a model for ## PostgreSQL: SQL query builder -A SQL builder query takes two steps. First you build it, then you run it. You decide the exact SQL: the joins, the grouping, and the columns that come back. +A SQL builder query takes two steps: first you build it, then you run it. You decide the exact SQL: the joins, the grouping, and the columns that come back. **Use it when:** @@ -68,7 +68,7 @@ A SQL builder query takes two steps. First you build it, then you run it. You de ### Build a query and run it -Start from a table with `db.sql.public.`. `db.sql` is the SQL query builder, and it is keyed by table name. A table's name is the model name with a lowercase first letter, unless the model sets `@@map`. Column names are the field names, unless a field sets `@map`. `public` is the PostgreSQL schema your tables are in, unless the model sits in a `namespace billing { ... }` block in your contract, in which case the path is `db.sql.billing.
`. +Start from a table with `db.sql.public.
`. `db.sql` is the SQL query builder, keyed by table name. A table's name is the model name with a lowercase first letter unless the model sets `@@map`, and column names are the field names unless a field sets `@map`. `public` is the PostgreSQL schema your tables are in, unless the model sits in a `namespace billing { ... }` block in your contract, in which case the path is `db.sql.billing.
`. `db.runtime()` is the database connection; call it whenever you run a query. `query()` always resolves to an array of rows, even when the query matches one row. @@ -86,7 +86,7 @@ const publishedPosts = db.sql.public.post const rows = await db.runtime().query(publishedPosts); ``` -The `.where(...)` callback receives two arguments. The first, `f`, holds the column references. The second, `fns`, holds the operators: `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `notIn`, `and`, `or`, and `ilike` for text columns on PostgreSQL. There are more; the [SQL query builder reference](/orm/reference/sql-query-builder#built-in-functions) has the full list. +The `.where(...)` callback receives two arguments. The first, `f`, holds the column references, and the second, `fns`, holds the operators: `eq`, `ne`, `gt`, `gte`, `lt`, `lte`, `in`, `notIn`, `and`, `or`, and `ilike` for text columns on PostgreSQL. There are more; the [SQL query builder reference](/orm/reference/sql-query-builder#built-in-functions) has the full list. Pass a value from your own code straight into an operator. Here `authorId` is a variable, such as one you read from a request: @@ -110,7 +110,7 @@ function titles(posts: PublishedPost[]) { } ``` -`ResultType<...>` is one row, not the array. `await db.runtime().query(publishedPosts)` resolves to `PublishedPost[]`. See [`ResultType`](/orm/reference/sql-query-builder#resulttype) in the reference. +`ResultType<...>` is one row, not the array, so `await db.runtime().query(publishedPosts)` resolves to `PublishedPost[]`. See [`ResultType`](/orm/reference/sql-query-builder#resulttype) in the reference. ### Join tables with precise control @@ -177,7 +177,7 @@ const rows = await db.runtime().query(topAuthors); Pass the same aggregate to `.orderBy(...)` that you passed to `.select(...)`, and the database does the sorting and the limiting. -Use `fns.countBigInt()` when a count can grow past `Number.MAX_SAFE_INTEGER`. It returns a `bigint`. `fns.count()` returns a JavaScript `number`. A count larger than `Number.MAX_SAFE_INTEGER` throws an error with code `RUNTIME.DECODE_FAILED` rather than rounding. +Use `fns.countBigInt()` when a count can grow past `Number.MAX_SAFE_INTEGER`, because it returns a `bigint` while `fns.count()` returns a JavaScript `number`. A count larger than `Number.MAX_SAFE_INTEGER` throws an error with code `RUNTIME.DECODE_FAILED` rather than rounding. ### Write with RETURNING @@ -210,7 +210,7 @@ Neither of those two returns rows, so both go to `execute()`. Add `.returning(.. ### Raw SQL -Raw SQL comes in two sizes. When the operators don't cover a single expression you need, write that expression with `fns.raw` and declare what type it returns with `.returns(...)`. The rest of the query stays typed. +Raw SQL comes in two sizes. When the operators don't cover a single expression you need, write that expression with `fns.raw` and declare what type it returns with `.returns(...)`, and the rest of the query stays typed. Calling `.select` a second time adds columns rather than replacing the ones already chosen. The second call takes a name for the new column, then the expression: @@ -224,9 +224,9 @@ const rows = await db.runtime().query(users); // [{ id: 'cuid20000000000000000001', email: 'alice@prisma.io', upperEmail: 'ALICE@PRISMA.IO' }, ...] ``` -`.returns(...)` takes a type name as a string. `"pg/text@1"` is the PostgreSQL type `text`. Type names always end in `@1`. The [raw queries reference](/orm/reference/raw-queries) covers how a bare value picks its type name. +`.returns(...)` takes a type name as a string: `"pg/text@1"` is the PostgreSQL type `text`, and type names always end in `@1`. The [raw queries reference](/orm/reference/raw-queries) covers how a bare value picks its type name. -A raw fragment is as safe as an operator. You can interpolate columns and other typed expressions the same way. +A raw fragment is as safe as an operator, and you can interpolate columns and other typed expressions the same way. When the whole statement has to be raw, write it with `db.raw.sql` and end it with one of two calls. Use `.returnsRow(...)` when the statement returns rows, then run the built query with `query()`: @@ -249,7 +249,7 @@ const rows = await db.runtime().query(authorPostCounts); Quote identifiers as PostgreSQL requires; `"authorId"` keeps the camel case. Each entry in the `.returnsRow(...)` object says how to read one result column. An entry is either a column from the contract, as in `user.columns.id`, or a type name as a string for a result column the contract has no counterpart for. `"pg/int8@1"` is a 64-bit integer, so `postCount` comes back as a `bigint`. -Use `.affectedCount()` when the statement returns no rows, then run the built query with `execute()`. It resolves to `{ affectedRows }`: +Use `.affectedCount()` when the statement returns no rows, then run the built query with `execute()`, which resolves to `{ affectedRows }`: ```typescript const publishDrafts = db.raw.sql` @@ -293,7 +293,7 @@ A column the inner statement already declared can be reused in the outer row obj On MongoDB the builder is at `db.query`, and it builds a typed MongoDB aggregation pipeline: a sequence of stages such as `$match`, `$group`, `$sort`, and `$lookup`, checked against your contract. There is no `db.sql` on MongoDB. -All MongoDB aggregation happens here. The ORM API has no `.aggregate(...)` on MongoDB, so counts, sums, and grouping are pipeline builder work. +All MongoDB aggregation happens here: the ORM API has no `.aggregate(...)` on MongoDB, so counts, sums, and grouping are pipeline builder work. **Use it when:** @@ -302,11 +302,11 @@ All MongoDB aggregation happens here. The ORM API has no `.aggregate(...)` on Mo - A query needs MongoDB pipeline stages that don't map to the ORM API, such as multi-stage filtering and projection. - You want to filter with an operator such as `gt` or `ne`. On MongoDB the ORM API's `.where(...)` matches exact values only, and `.match(...)` here takes the operators. -**Prefer the ORM API when** the query is document create, read, update, or delete. Prefer it as well for reading a reference relation. A reference relation is a relation stored as an id in the document rather than as an embedded document, and `.include(...)` already covers the common `$lookup` case. +**Prefer the ORM API when** the query is document create, read, update, or delete, and for reading a reference relation. A reference relation is a relation stored as an id in the document rather than as an embedded document, and `.include(...)` already covers the common `$lookup` case. ### Build a pipeline and run it -Start from a collection with `db.query.from(...)`. It takes the collection name as a string: the model's `@@map` value, or the model name with a lowercase first letter when the model has no `@@map`. The models above set no `@@map` except `PostTag`, so the collections are `user`, `post`, `tag`, and `post_tag`. That is the same name the ORM API uses, as in `db.orm.post`. +Start from a collection with `db.query.from(...)`, which takes the collection name as a string: the model's `@@map` value, or the model name with a lowercase first letter when the model has no `@@map`. The models above set no `@@map` except `PostTag`, so the collections are `user`, `post`, `tag`, and `post_tag`. That is the same name the ORM API uses, as in `db.orm.post`. `acc` holds the aggregate functions you can use inside `.group(...)`, such as `acc.count()` and `acc.max(f.createdAt)`. In the pipeline builder, fields use their stored names, so the id is `_id` (see the note under the contract above), and `_id` is also the grouping key in `.group(...)`. On MongoDB `db.runtime()` returns a promise, so await it before you run anything. Chain stages, call `.build()`, and run the result: @@ -348,7 +348,7 @@ const draftsByAuthor = db.query const rows = await runtime.query(draftsByAuthor); ``` -In a `.match(...)` callback the operators are on the field itself, as in `f.published.eq(false)`. The SQL query builder puts them on `fns` instead. The [pipeline builder reference](/orm/reference/pipeline-builder#match) lists the operators you can use here. +In a `.match(...)` callback the operators are on the field itself, as in `f.published.eq(false)`, while the SQL query builder puts them on `fns` instead. The [pipeline builder reference](/orm/reference/pipeline-builder#match) lists the operators you can use here. ### Join collections with $lookup @@ -387,7 +387,7 @@ Inside a transaction, `tx` is the argument your callback receives, and you run t ## Prompt your coding agent -Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent. The `prisma-8` skill covers both builders and the choice between them and the ORM API. Prompts that map to each section: +Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent. The `prisma-8` skill covers both builders and the choice between them and the ORM API, so try prompts that map to each section: - "Using the prisma-8 skill, write a SQL builder query for the top 10 authors by post count." - "This report needs post and author columns in one flat result. Build the join with the SQL query builder." diff --git a/apps/docs/content/docs/orm/fundamentals/reading-data.mdx b/apps/docs/content/docs/orm/fundamentals/reading-data.mdx index 7475e0d015b..9f783d4c475 100644 --- a/apps/docs/content/docs/orm/fundamentals/reading-data.mdx +++ b/apps/docs/content/docs/orm/fundamentals/reading-data.mdx @@ -8,7 +8,7 @@ metaDescription: 'Query PostgreSQL and MongoDB with Prisma ORM. Filter with wher This page shows how to read data with Prisma ORM: fetching [many records or one](#fetch-many-records-or-one), [filtering](#filter-records), [selecting fields](#select-fields), [sorting and paginating](#sort-and-paginate), [counting](#count-records), and [iterating large results](#iterate-a-large-result). -Every query chains methods on a model. The last call in the chain says what you want back and runs the query. Usually that is `.all()` or `.first()`: +Every query chains methods on a model, and the last call in the chain says what you want back and runs the query, usually `.all()` or `.first()`: ```typescript tab="PostgreSQL" import { db } from "./prisma/db"; @@ -30,15 +30,15 @@ const posts = await db.orm.posts.where({ published: true }).all(); const user = await db.orm.users.where({ email: "alice@prisma.io" }).first(); ``` -`npm create prisma@latest` scaffolds a new project. `npx prisma orm init` adds Prisma ORM to a project you already have. +`npm create prisma@latest` scaffolds a new project, while `npx prisma orm init` adds Prisma ORM to a project you already have. -Your contract is the `contract.prisma` file that replaced `schema.prisma`. It is where you declare your models, and every result on this page is typed from it. See [Core concepts](/orm/core-concepts) for how the contract and the client fit together. +Your contract is the `contract.prisma` file that replaced `schema.prisma`: it is where you declare your models, and every result on this page is typed from it. See [Core concepts](/orm/core-concepts) for how the contract and the client fit together. -`db` is the client you create once in `src/prisma/db.ts`. `npx prisma orm init` writes that file for you. Commit it; unlike Prisma ORM 7's generated client, it is your code. `db.ts` reads two generated files that `npx prisma contract emit` writes next to it; you do not edit those. These examples are written from a file in `src/`, so the import is `./prisma/db`; from elsewhere, change the relative path. +`db` is the client you create once in `src/prisma/db.ts`, and `npx prisma orm init` writes that file for you. Commit it; unlike Prisma ORM 7's generated client, it is your code. `db.ts` reads two generated files that `npx prisma contract emit` writes next to it; you do not edit those. These examples are written from a file in `src/`, so the import is `./prisma/db`; from elsewhere, change the relative path. `db.orm` holds your models. On PostgreSQL you reach a model through the database schema it is in: in `db.orm.public.User`, `User` is the model name and `public` is the PostgreSQL schema. It is `public` unless the model sits in a `namespace` block in your contract; see [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7#schema). On MongoDB there is no schema segment, and the name is the collection: `db.orm.users`. -You can write `.where`, `.select`, `.orderBy`, `.limit`, and `.offset` in any order. `.cursor(...)` must come after `.orderBy(...)`. +You can write `.where`, `.select`, `.orderBy`, `.limit`, and `.offset` in any order, but `.cursor(...)` must come after `.orderBy(...)`. For Prisma ORM 7 users, `findMany` and `findFirst` / `findUnique` map directly onto `.all()` and `.first()` ([Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7) maps the rest of the API): @@ -126,9 +126,9 @@ const users = await db.orm.public.User.all(); ] ``` -`.all()` takes no filter of its own. Put the filter in `.where(...)` before it. `.all()` also applies no limit, so combine it with [`limit`](#sort-and-paginate) on tables that can grow. +`.all()` takes no filter of its own, so put the filter in `.where(...)` before it. `.all()` also applies no limit, so combine it with [`limit`](#sort-and-paginate) on tables that can grow. -Use `.first()` when you want a single record. It returns the record, or `null` when nothing matches. On both databases it asks for at most one row: +Use `.first()` when you want a single record. It returns the record, or `null` when nothing matches, and on both databases it asks for at most one row: ```typescript const user = await db.orm.public.User.where({ email: "alice@prisma.io" }).first(); @@ -138,7 +138,7 @@ const user = await db.orm.public.User.where({ email: "alice@prisma.io" }).first( { id: 'cuid20000000000000000001', email: 'alice@prisma.io', name: 'Alice', createdAt: 2026-07-06T09:03:13.808Z } ``` -On PostgreSQL you can skip `.where(...)` and pass the filter straight to `.first(...)`. It takes anything `.where(...)` takes, not just the primary key. On MongoDB, `.first()` takes no argument, so filter with `.where(...)`: +On PostgreSQL you can skip `.where(...)` and pass the filter straight to `.first(...)`, which takes anything `.where(...)` takes, not just the primary key. On MongoDB, `.first()` takes no argument, so filter with `.where(...)`: ```typescript tab="PostgreSQL" const user = await db.orm.public.User.first({ id: userId }); @@ -148,23 +148,23 @@ const user = await db.orm.public.User.first({ id: userId }); const user = await db.orm.users.where({ _id: userId }).first(); ``` -If you want an error instead of `null` when nothing matches, call `.firstOrThrow()`. This is what replaces `findUniqueOrThrow` and `findFirstOrThrow`: +If you want an error instead of `null` when nothing matches, call `.firstOrThrow()`, the replacement for `findUniqueOrThrow` and `findFirstOrThrow`: ```typescript const user = await db.orm.public.User.where({ email }).limit(1).all().firstOrThrow(); ``` -`.limit(1)` keeps it to one row. `.firstOrThrow()` throws an error with code `RUNTIME.NO_ROWS` when nothing matches. There is no `.firstOrThrow()` on `.first()`. The error is an `Error` with a `code` property, so check `"code" in error` before you read it. The same form works on MongoDB, with `db.orm.users`. +`.limit(1)` keeps it to one row, and `.firstOrThrow()` throws an error with code `RUNTIME.NO_ROWS` when nothing matches. There is no `.firstOrThrow()` on `.first()`. The error is an `Error` with a `code` property, so check `"code" in error` before you read it. The same form works on MongoDB, with `db.orm.users`. ## Filter records -Use `.where(...)` to narrow a query. Pass an object to match fields by equality: +Use `.where(...)` to narrow a query, and pass an object to match fields by equality: ```typescript const drafts = await db.orm.public.Post.where({ published: false }).all(); ``` -Chain several `.where(...)` calls to combine conditions with AND. This is also how you express a range: +Chain several `.where(...)` calls to combine conditions with AND, which is also how you express a range: ```typescript const recentPosts = await db.orm.public.Post @@ -209,13 +209,13 @@ const notHello = await db.orm.public.Post ### Filter operators on MongoDB -On MongoDB, `.where(...)` does not take a callback. The object form covers equality: +On MongoDB, `.where(...)` does not take a callback, but the object form covers equality: ```typescript const drafts = await db.orm.posts.where({ published: false }).all(); ``` -For anything else, pass `.where(...)` a filter built with `MongoFieldFilter`, imported from `@prisma/orm-mongo/query-ast/execution`. `@prisma/orm-mongo` is the package `db.ts` imports from. `MongoFieldFilter` has one static method per operator: `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `in`, `nin` (not in), `isNull`, and `isNotNull`. Each one takes the field name first: +For anything else, pass `.where(...)` a filter built with `MongoFieldFilter`, imported from `@prisma/orm-mongo/query-ast/execution`. `@prisma/orm-mongo` is the package `db.ts` imports from. `MongoFieldFilter` has one static method per operator: `eq`, `neq`, `gt`, `gte`, `lt`, `lte`, `in`, `nin` (not in), `isNull`, and `isNotNull`, and each one takes the field name first: ```typescript import { MongoFieldFilter } from "@prisma/orm-mongo/query-ast/execution"; @@ -251,7 +251,7 @@ The full operator list is in [Filter conditions and operators](/orm/reference/or ## Select fields -Use `.select(...)` to fetch only the fields you need. On PostgreSQL the result type narrows to match. On MongoDB only the fetched document narrows; the type keeps every field, so a field you left out is `undefined` at runtime. On both, you can filter on fields you did not select: +Use `.select(...)` to fetch only the fields you need. On PostgreSQL the result type narrows to match, while on MongoDB only the fetched document narrows; the type keeps every field, so a field you left out is `undefined` at runtime. On both, you can filter on fields you did not select: ```typescript tab="PostgreSQL" const users = await db.orm.public.User.select("id", "email").all(); @@ -272,7 +272,7 @@ const users = await db.orm.users.select("_id", "email").all(); Use `.orderBy(...)` to sort, `.limit(n)` to limit, and `.offset(n)` to offset. -On PostgreSQL, sort with a callback that calls `.asc()` or `.desc()` on a field. On MongoDB, sort with MongoDB's own direction numbers: `1` for ascending and `-1` for descending. +On PostgreSQL, sort with a callback that calls `.asc()` or `.desc()` on a field, and on MongoDB, sort with MongoDB's own direction numbers: `1` for ascending and `-1` for descending. ```typescript tab="PostgreSQL" // Second page of posts, newest first @@ -292,7 +292,7 @@ const page = await db.orm.posts .all(); ``` -For a composite sort on PostgreSQL, pass an array of callbacks. Records are sorted by the first field, with the second as tiebreaker: +For a composite sort on PostgreSQL, pass an array of callbacks, and records are sorted by the first field, with the second as tiebreaker: ```typescript const posts = await db.orm.public.Post @@ -302,11 +302,11 @@ const posts = await db.orm.public.Post ### Cursor pagination -Offset gets slower on deep pages. For stable pagination over a large table, follow `.orderBy(...)` with `.cursor(...)` and resume from the last record you returned. `.cursor(...)` is PostgreSQL only. +Offset gets slower on deep pages, so for stable pagination over a large table, follow `.orderBy(...)` with `.cursor(...)` and resume from the last record you returned. `.cursor(...)` is PostgreSQL only. -The cursor record is excluded from the next page. Pages pick up strictly after it. +The cursor record is excluded from the next page, so pages pick up strictly after it. -Pass `.cursor(...)` a value for every field you sorted by, as in the example below. Keep the `id` tiebreaker in both the sort and the cursor. `createdAt` is not unique, and a cursor on a non-unique field alone can skip or repeat records that share the boundary value. With `id` in the cursor, pages never overlap even when timestamps tie: +Pass `.cursor(...)` a value for every field you sorted by, as in the example below. Keep the `id` tiebreaker in both the sort and the cursor, because `createdAt` is not unique, and a cursor on a non-unique field alone can skip or repeat records that share the boundary value. With `id` in the cursor, pages never overlap even when timestamps tie: ```typescript const page1 = await db.orm.public.Post @@ -370,7 +370,7 @@ const [result] = await (await db.runtime()).query(built); You can `await` a read for an array, or loop over it with `for await`. Pick one; once you iterate a result with `for await` it is used up (see below). -`await` runs the query and gives you an array. This is the right default. You get the whole result in memory and can read the array as often as you like. +`await` runs the query and gives you an array, which is the right default: you get the whole result in memory and can read the array as often as you like. ```typescript const posts = await db.orm.public.Post.all(); @@ -434,7 +434,7 @@ const users = await db.orm.public.User.where({ email }).all(); const user = users[0]; ``` -This fetches every matching record and throws away the rest. Use `.first()` instead. It returns one record or `null`, and it asks the database for at most one row: +This fetches every matching record and throws away the rest. Use `.first()` instead: it returns one record or `null`, and it asks the database for at most one row: ```typescript const user = await db.orm.public.User.where({ email }).first(); @@ -442,7 +442,7 @@ const user = await db.orm.public.User.where({ email }).first(); ### Forgetting that .all() has no limit -`.all()` returns every match. On a table that grows, yesterday's fast query becomes today's slow one. Add `.limit(n)` when you don't genuinely need every record. When you do need every record, page through the table: `.cursor(...)` on PostgreSQL, `.offset(n)` on MongoDB. +`.all()` returns every match, so on a table that grows, yesterday's fast query becomes today's slow one. Add `.limit(n)` when you don't genuinely need every record, and when you do need every record, page through the table: `.cursor(...)` on PostgreSQL, `.offset(n)` on MongoDB. ### Reusing an iterated result @@ -455,7 +455,7 @@ const posts = await db.orm.public.Post.all(); ## Prompt your coding agent -Projects created with `npm create prisma@latest` include the Prisma ORM skills for your coding agent; in an existing project, run `npx prisma skills sync`. Skills are instruction files a coding agent reads, and the [`prisma-8` skill](/ai/tools/skills#available-skills-for-prisma-8) covers everything on this page. Prompts that map to each section: +Projects created with `npm create prisma@latest` include the Prisma ORM skills for your coding agent, and in an existing project you run `npx prisma skills sync` to add them. Skills are instruction files a coding agent reads, and the [`prisma-8` skill](/ai/tools/skills#available-skills-for-prisma-8) covers everything on this page, so try prompts that map to each section: - "Using the prisma-8 skill, write a query that returns the 20 newest published posts." - "Add a case-insensitive title search to the posts query, using the .ilike operator." diff --git a/apps/docs/content/docs/orm/fundamentals/relations-and-joins.mdx b/apps/docs/content/docs/orm/fundamentals/relations-and-joins.mdx index 40e1692b115..867b31cdcc1 100644 --- a/apps/docs/content/docs/orm/fundamentals/relations-and-joins.mdx +++ b/apps/docs/content/docs/orm/fundamentals/relations-and-joins.mdx @@ -28,7 +28,7 @@ const posts = await db.orm.posts // posts[0].author is the referenced user document ``` -If this is your first Prisma ORM 8 query, start with the two files it needs. Your models live in `src/prisma/contract.prisma`. It is the same file Prisma ORM 7 called `schema.prisma`; the docs call it your contract. Both commands below write `src/prisma/db.ts`, which creates the client and exports it as `db`. It reads `DATABASE_URL` from `.env`. +If this is your first Prisma ORM 8 query, start with the two files it needs. Your models live in `src/prisma/contract.prisma`, the same file Prisma ORM 7 called `schema.prisma`; the docs call it your contract. Both commands below write `src/prisma/db.ts`, which creates the client and exports it as `db`, and it reads `DATABASE_URL` from `.env`. ```bash npm create prisma@latest -- my-app # a new project @@ -42,15 +42,15 @@ That is the file the examples above import as `./prisma/db`, from a file directl - You `await` the whole chain, and the last call says what you want back. `.all()` returns every matching record as an array. `.first()` returns one record, or `null` when nothing matches. - `.where(...)` takes two forms. Pass an object, like `.where({ published: true })`, to match fields to exact values. Pass a callback for a comparison, like `.where((p) => p.title.like("Hello%"))`. Chaining `.where(...)` twice requires both to match. -The name you pass to `.include(...)` is the relation field name you declared in your contract. It is not a table name. `.include(...)` fetches the relation in the same query as the parent: one SQL statement, which reads the relation with a correlated subquery. `.include("author")` replaces Prisma ORM 7's `include: { author: true }`; see [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7#queries) for the rest of the query API side by side. Use `.include(...)` when the caller needs the related data in the same response. Skip it when the foreign key already on the record is enough. This page walks through the three kinds of relationship, from the data model to the query and the result. If you already know how relational data is modeled, jump to [filtering by relation data](#filter-parent-records-by-relation-data) or the [limitations](#current-limitations). +The name you pass to `.include(...)` is the relation field name you declared in your contract, not a table name. `.include(...)` fetches the relation in the same query as the parent: one SQL statement, which reads the relation with a correlated subquery. `.include("author")` replaces Prisma ORM 7's `include: { author: true }`; see [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7#queries) for the rest of the query API side by side. Use `.include(...)` when the caller needs the related data in the same response, and skip it when the foreign key already on the record is enough. This page walks through the three kinds of relationship, from the data model to the query and the result. If you already know how relational data is modeled, jump to [filtering by relation data](#filter-parent-records-by-relation-data) or the [limitations](#current-limitations). ## One-to-one -One record is linked to at most one other record. For example, every profile belongs to exactly one user. +One record is linked to at most one other record: for example, every profile belongs to exactly one user. -The model that holds the foreign key declares the relation. The `@unique` on the foreign key is what makes it one-to-one: +The model that holds the foreign key declares the relation, and the `@unique` on the foreign key is what makes it one-to-one: ```prisma model Profile { @@ -71,7 +71,7 @@ const profileWithUser = await db.orm.public.Profile // { id, bio, userId, user: { id, email } } ``` -`.first()` returns `null` when the profile doesn't exist, so a user without a profile is a `null` check, not an error. You can also declare the matching field on the other side and start from users. An optional field typed as the other model is enough. It needs no `@relation` of its own: +`.first()` returns `null` when the profile doesn't exist, so a user without a profile is a `null` check, not an error. You can also declare the matching field on the other side and start from users: an optional field typed as the other model is enough, and it needs no `@relation` of its own: ```prisma model User { @@ -81,7 +81,7 @@ model User { } ``` -This works as long as the foreign key on the other side is unique. So `Profile.userId` must carry `@unique`. Without it, `npx prisma contract emit` fails with the error code `PSL_NON_UNIQUE_BACKRELATION`. +This works as long as the foreign key on the other side is unique, so `Profile.userId` must carry `@unique`. Without it, `npx prisma contract emit` fails with the error code `PSL_NON_UNIQUE_BACKRELATION`. Then run `npx prisma contract emit`, the command that regenerates the client types from your contract. Run it after every change you make to the contract. Then apply the change to the database with `npx prisma db update` (the replacement for `prisma db push`) during development, or with a migration: run `npx prisma migration plan` then `npx prisma db migrate`; see [Generating a migration](/orm/migrations/generating-a-migration). With the field in place, `include("profile")` returns one record or `null`: @@ -93,7 +93,7 @@ const usersWithProfiles = await db.orm.public.User // Array<{ id, email, profile: { id, bio, userId } | null }> ``` -`.select(...)` narrows the parent's own fields only. An included relation still comes back with all of its fields, which is why `profile` above has `bio` and `userId`. To narrow the included relation's fields, use the callback form shown in the next section. +`.select(...)` narrows the parent's own fields only, while an included relation still comes back with all of its fields, which is why `profile` above has `bio` and `userId`. To narrow the included relation's fields, use the callback form shown in the next section. ## One-to-many @@ -120,7 +120,7 @@ model Post { } ``` -Query it in either direction. From the parent, the children arrive as an array. From the child, the parent arrives as one object: +Query it in either direction: from the parent, the children arrive as an array, and from the child, the parent arrives as one object: ```typescript // Each user with their posts @@ -132,7 +132,7 @@ const postsWithAuthors = await db.orm.public.Post.include("author").all(); // Array<{ id, title, published, createdAt, authorId, author: User }> ``` -To choose what each relation returns, pass a callback as the second argument. Inside it, chain `.where`, `.select`, `.orderBy`, and `.limit` exactly like a top-level query. `.desc()` sorts newest first, and `.asc()` sorts the other way. This is how you fetch "each user with their five newest posts" in one query: +To choose what each relation returns, pass a callback as the second argument, and inside it, chain `.where`, `.select`, `.orderBy`, and `.limit` exactly like a top-level query. `.desc()` sorts newest first, and `.asc()` sorts the other way. This is how you fetch "each user with their five newest posts" in one query: ```typescript const usersWithRecentPosts = await db.orm.public.User @@ -157,7 +157,7 @@ const usersWithPostsAndComments = await db.orm.public.User // Array<{ id, email, posts: Array<{ id, title, published, createdAt, authorId, comments: Comment[] }> }> ``` -To write a parent and its children in one call, pass a callback for the list field and Prisma ORM fills in the foreign key on each child: `db.orm.public.User.create({ email: "jane@prisma.io", posts: (p) => p.create([{ title: "Hello", published: false }]) })` writes the user and the post together. The common mistake here is the N+1 loop: fetching users, then querying posts inside a `for` loop over them. That runs one query per user. One `.include("posts")` on the user query returns the same data in a single query. +To write a parent and its children in one call, pass a callback for the list field and Prisma ORM fills in the foreign key on each child: `db.orm.public.User.create({ email: "jane@prisma.io", posts: (p) => p.create([{ title: "Hello", published: false }]) })` writes the user and the post together. The common mistake here is the N+1 loop: fetching users, then querying posts inside a `for` loop over them, which runs one query per user. One `.include("posts")` on the user query returns the same data in a single query. ## Many-to-many @@ -165,7 +165,7 @@ Records on both sides connect to many on the other: a post has many tags, and a -Write three models. On each side, declare a list field typed as the other model. Then write a third model for the join table, and give it a primary key made of exactly two foreign keys, one to each side: +Write three models: on each side, declare a list field typed as the other model, then write a third model for the join table, and give it a primary key made of exactly two foreign keys, one to each side: ```prisma model Post { @@ -191,7 +191,7 @@ model PostTag { } ``` -Prisma ORM finds the model for the join table by that shape: the one model whose primary key is exactly a foreign key to `Post` and a foreign key to `Tag`. There is no other way to name it. If two models have that shape, `npx prisma contract emit` fails with `PSL_AMBIGUOUS_BACKRELATION`, and you pick one by adding the same `@relation("...")` name to the list field and to the foreign key pointing back at it. You do not add a `PostTag[]` field to `Post` or `Tag`. `.include(...)` then reads a post's tags in one query, and returns the tags themselves, not the link records. The callback that filters and narrows a relation works here like anywhere else: +Prisma ORM finds the model for the join table by that shape: the one model whose primary key is exactly a foreign key to `Post` and a foreign key to `Tag`, and there is no other way to name it. If two models have that shape, `npx prisma contract emit` fails with `PSL_AMBIGUOUS_BACKRELATION`, and you pick one by adding the same `@relation("...")` name to the list field and to the foreign key pointing back at it. You do not add a `PostTag[]` field to `Post` or `Tag`. `.include(...)` then reads a post's tags in one query, and returns the tags themselves, not the link records. The callback that filters and narrows a relation works here like anywhere else: ```typescript const postsWithTags = await db.orm.public.Post @@ -204,7 +204,7 @@ const postsWithTags = await db.orm.public.Post [{ title: 'Hello Prisma 8', tags: [{ id: 't2…', name: 'databases' }] }] ``` -Writes reach the join table the same way. Pass a callback for the relation and use `create`, `connect`, or `disconnect`: +Writes reach the join table the same way: pass a callback for the relation and use `create`, `connect`, or `disconnect`: ```typescript // Create a post and two new tags in one call @@ -226,7 +226,7 @@ await db.orm.public.Post.where({ id: post.id }).update({ `connect` and `disconnect` identify the tag by any primary key or `@unique` field, so `{ name: "typescript" }` works here because `Tag.name` is `@unique`. When that key is made of several columns, pass every one of them in the object. `update(...)` returns the updated post, or `null` when no post matched the filter. `create` always inserts a new tag record, so creating a tag whose name already exists fails on that same unique constraint. Use `connect` for tags that already exist. -There is no `connectOrCreate` in Prisma ORM 8. Upsert the tag first, which creates it or leaves an existing one alone, then connect it: +There is no `connectOrCreate` in Prisma ORM 8, so upsert the tag first, which creates it or leaves an existing one alone, then connect it: ```typescript const tag = await db.orm.public.Tag.upsert({ @@ -239,9 +239,9 @@ await db.orm.public.Post.where({ id: post.id }).update({ }); ``` -The upsert is safe when two requests create the same tag at once. The `connect` is not: if both requests also link that tag to the same post, the second fails with an error whose code is `ORM.RELATION_LINK_DUPLICATE`. Catch that code and treat it as done. +The upsert is safe when two requests create the same tag at once, but the `connect` is not: if both requests also link that tag to the same post, the second fails with an error whose code is `ORM.RELATION_LINK_DUPLICATE`. Catch that code and treat it as done. -See [what is not available yet](/orm/coming-from-prisma-orm-7#not-available-yet) for the rest of the gaps, and [Upsert a record](/orm/fundamentals/writing-data#upsert-a-record) for the top-level `.upsert(...)` call. These nested writes are for relational databases. On MongoDB, write the parent document and the child document in two calls, and set the reference field yourself: +See [what is not available yet](/orm/coming-from-prisma-orm-7#not-available-yet) for the rest of the gaps, and [Upsert a record](/orm/fundamentals/writing-data#upsert-a-record) for the top-level `.upsert(...)` call. These nested writes are for relational databases, so on MongoDB, write the parent document and the child document in two calls, and set the reference field yourself: ```typescript const user = await db.orm.users.create({ email: "jane@prisma.io" }); @@ -255,7 +255,7 @@ const links = await db.orm.public.PostTag.include("tag").all(); // Array<{ postId, tagId, tag: { id, name } }> ``` -A join table may carry columns beyond the two foreign keys. Those extra columns must not be part of the primary key. Nested `create` and `connect` are rejected when one of them is required, because neither can supply a value for it, so insert the link records yourself: +A join table may carry columns beyond the two foreign keys, but those extra columns must not be part of the primary key. Nested `create` and `connect` are rejected when one of them is required, because neither can supply a value for it, so insert the link records yourself: ```typescript const tag = await db.orm.public.Tag.first({ name: "typescript" }); @@ -264,7 +264,7 @@ await db.orm.public.PostTag.create({ postId: post.id, tagId: tag!.id, addedBy: " ## Filter parent records by relation data -On PostgreSQL, `.where(...)` can reach into a relation. `.some(...)` matches parents with at least one matching child. `.none(...)` matches parents with none. `.every(...)` matches parents whose children all match. A parent with no children also matches. +On PostgreSQL, `.where(...)` can reach into a relation: `.some(...)` matches parents with at least one matching child, `.none(...)` matches parents with none, and `.every(...)` matches parents whose children all match. A parent with no children also matches. ```typescript // Users who have at least one published post @@ -278,7 +278,7 @@ const taggedPosts = await db.orm.public.Post .all(); ``` -Chaining `.where(...)` combines conditions with AND. For OR, call the `or` helper inside a single callback. It comes from `@prisma/orm-postgres/orm-client`, already installed with `@prisma/orm-postgres`: +Chaining `.where(...)` combines conditions with AND, so for OR, call the `or` helper inside a single callback. It comes from `@prisma/orm-postgres/orm-client`, already installed with `@prisma/orm-postgres`: ```typescript import { or } from "@prisma/orm-postgres/orm-client"; @@ -288,7 +288,7 @@ const posts = await db.orm.public.Post .all(); ``` -See [filter operators](/orm/fundamentals/reading-data#filter-operators-on-postgresql) for the comparisons you can pass to `.where(...)`. On MongoDB, relation filters are not available. Query the child collection directly instead, and include the parent: +See [filter operators](/orm/fundamentals/reading-data#filter-operators-on-postgresql) for the comparisons you can pass to `.where(...)`. On MongoDB, relation filters are not available, so query the child collection directly instead, and include the parent: ```typescript // MongoDB: the published posts, each with its author @@ -331,7 +331,7 @@ model Post { } ``` -`Post.author` is a reference: the user is a separate document in the `users` collection, and `.include("author")` reads it. `User.address` is an embedded document, and you can tell by the declaration: `Address` is written with `type`, not `model`, so its fields are stored inside the user document itself. Embedded documents come back with every read, so you never include them. Calling `.include(...)` on an embedded relation throws an error with code `ORM.INCLUDE_UNSUPPORTED`. Use `.include(...)` only for documents stored in their own collection. [MongoDB data modeling](/orm/data-modeling/mongodb#embed-or-reference) covers which to choose. +`Post.author` is a reference: the user is a separate document in the `users` collection, and `.include("author")` reads it. `User.address` is an embedded document, and you can tell by the declaration: `Address` is written with `type`, not `model`, so its fields are stored inside the user document itself. Embedded documents come back with every read, so you never include them. Calling `.include(...)` on an embedded relation throws an error with code `ORM.INCLUDE_UNSUPPORTED`, so use `.include(...)` only for documents stored in their own collection. [MongoDB data modeling](/orm/data-modeling/mongodb#embed-or-reference) covers which to choose. ## Current limitations @@ -341,7 +341,7 @@ model Post { ## Prompt your coding agent -Projects created with `npm create prisma@latest -- my-app` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8), instruction files for your coding agent. The `prisma-8` skill covers both relation queries and the relation fields in your contract. Prompts that map to each section: +Projects created with `npm create prisma@latest -- my-app` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8), instruction files for your coding agent. The `prisma-8` skill covers both relation queries and the relation fields in your contract, so try prompts that map to each section: - "Using the prisma-8 skill, add a one-to-one Profile model with a unique foreign key to User." - "Using the prisma-8 skill, fetch each user with their five newest posts in one query." diff --git a/apps/docs/content/docs/orm/fundamentals/transactions.mdx b/apps/docs/content/docs/orm/fundamentals/transactions.mdx index fc72313d00f..36c59f1d607 100644 --- a/apps/docs/content/docs/orm/fundamentals/transactions.mdx +++ b/apps/docs/content/docs/orm/fundamentals/transactions.mdx @@ -8,11 +8,11 @@ metaDescription: 'Group writes atomically with db.transaction() on PostgreSQL, a Run several writes as one unit with `db.transaction(...)`: they all commit together, or they all roll back. -Prisma ORM 8 runs on PostgreSQL, SQLite, and MongoDB. There is no MySQL support. The examples on this page are PostgreSQL; `db.transaction(...)` works the same way on SQLite, and MongoDB is covered in [Transactions on MongoDB](#transactions-on-mongodb). +Prisma ORM 8 runs on PostgreSQL, SQLite, and MongoDB, but there is no MySQL support. The examples on this page are PostgreSQL; `db.transaction(...)` works the same way on SQLite, and MongoDB is covered in [Transactions on MongoDB](#transactions-on-mongodb). ## Run writes in a transaction -Use a transaction whenever one business operation spans more than one write: creating a user with their first records, moving a value between two rows, or deleting a parent after its children. Pass a callback to `db.transaction(...)`. Inside it, query through `tx` instead of `db`. `tx.orm` is the model API, what `prisma.user` was in Prisma ORM 7; `tx.sql` is the SQL query builder. Both work the same as `db.orm` and `db.sql`. Every query you make on `tx` runs inside the transaction, and `db.transaction(...)` returns whatever your callback returns: +Use a transaction whenever one business operation spans more than one write: creating a user with their first records, moving a value between two rows, or deleting a parent after its children. Pass a callback to `db.transaction(...)`, and inside it, query through `tx` instead of `db`. `tx.orm` is the model API, what `prisma.user` was in Prisma ORM 7; `tx.sql` is the SQL query builder, and both work the same as `db.orm` and `db.sql`. Every query you make on `tx` runs inside the transaction, and `db.transaction(...)` returns whatever your callback returns: ```typescript import { db } from "./prisma/db"; @@ -25,11 +25,11 @@ const result = await db.transaction(async (tx) => { `db` is the client you create once in `src/prisma/db.ts`, and `prisma orm init` writes that file for you. It writes the file once; it is your code, so edit it freely. Under `tx.orm.public`, the property name is the model name, so `User` and `Post` are spelled exactly as you spelled them in your contract. -`public` is the PostgreSQL schema, the namespace your tables live in. It is `public` unless you set one. `create(...)` takes the fields of the record directly, with no `data:` wrapper. Pass a field if it is required and has no default. Everything else is optional, and [Write data](/orm/fundamentals/writing-data) has the full rules. You don't need a transaction for a single write; every mutation is already atomic on its own. +`public` is the PostgreSQL schema, the namespace your tables live in, which is `public` unless you set one. `create(...)` takes the fields of the record directly, with no `data:` wrapper: pass a field if it is required and has no default, while everything else is optional, and [Write data](/orm/fundamentals/writing-data) has the full rules. You don't need a transaction for a single write; every mutation is already atomic on its own. ### Read inside a transaction -Reads work the same way as writes. Query through `tx.orm` and you see the rows the transaction has already written. `.first()` returns `null` when nothing matches. +Reads work the same way as writes: query through `tx.orm` and you see the rows the transaction has already written. `.first()` returns `null` when nothing matches. ```typescript await db.transaction(async (tx) => { @@ -54,8 +54,8 @@ await db.transaction(async (tx) => { }); ``` -Prisma ORM does not export a type for `tx`. Add this line to `src/prisma/db.ts`: `export type Tx = Parameters[0]>[0];`, then import `Tx` wherever a helper needs it. -Do not open a second transaction inside the helper. Calling `db.transaction(...)` again inside a callback does not nest: the inner call is a separate transaction that commits or rolls back on its own, and the outer one cannot roll it back. Pass `tx` down instead. +Prisma ORM does not export a type for `tx`, so add this line to `src/prisma/db.ts`: `export type Tx = Parameters[0]>[0];`, then import `Tx` wherever a helper needs it. +Do not open a second transaction inside the helper, because calling `db.transaction(...)` again inside a callback does not nest: the inner call is a separate transaction that commits or rolls back on its own, and the outer one cannot roll it back. Pass `tx` down instead. ### Options and isolation level @@ -72,7 +72,7 @@ There is no transaction timeout either, so a transaction stays open for as long ## Roll back on errors -The transaction commits when the callback returns and rolls back when it throws. Nothing inside the callback survives an error: +The transaction commits when the callback returns and rolls back when it throws, so nothing inside the callback survives an error: ```typescript try { @@ -120,11 +120,11 @@ await db.transaction(async (tx) => { ``` Two things differ from `tx.orm`. Under `tx.sql.public` the property name is the table name, so it is `tx.sql.public.post` where the ORM API has `tx.orm.public.Post`. The table name is the model name with a lowercase first letter, so the model `BlogPost` has the table `blogPost`, and you set `@@map` on the model to use a different table name. -The second difference is that you chain clauses and then call `.build()` to finish the query. `.build()` returns the query without running it. `tx.execute(...)` runs it and returns `{ affectedRows }`, the number of rows it changed. The `.where(...)` callback receives two arguments: `f` holds the columns, and `fns` holds the operators (`lt` is less-than). [Advanced queries](/orm/fundamentals/advanced-queries#build-a-query-and-run-it) covers both. +The second difference is that you chain clauses and then call `.build()` to finish the query: `.build()` returns the query without running it, and `tx.execute(...)` runs it and returns `{ affectedRows }`, the number of rows it changed. The `.where(...)` callback receives two arguments: `f` holds the columns, and `fns` holds the operators (`lt` is less-than). [Advanced queries](/orm/fundamentals/advanced-queries#build-a-query-and-run-it) covers both. ## Transactions on MongoDB -Prisma ORM does not support MongoDB transactions yet: there is no `db.transaction(...)` on the MongoDB client. To run a multi-document transaction today, use the MongoDB driver directly. Share one `MongoClient` between Prisma ORM and your code, and group the writes in a driver session. +Prisma ORM does not support MongoDB transactions yet: there is no `db.transaction(...)` on the MongoDB client. To run a multi-document transaction today, use the MongoDB driver directly: share one `MongoClient` between Prisma ORM and your code, and group the writes in a driver session. MongoDB only runs transactions on a replica set, and a standalone server rejects them. To turn a standalone server into a single-member replica set, follow the MongoDB guide on [converting a standalone to a replica set](https://www.mongodb.com/docs/manual/tutorial/convert-standalone-to-replica-set/). Install `@prisma/orm-mongo`, which a MongoDB project already has, together with version 7 of the `mongodb` driver: @@ -152,7 +152,7 @@ export const db = mongo({ ``` Keep the `contractJson` import and the `Contract` type, and run `prisma contract emit` after every change to your contract, on PostgreSQL and on MongoDB alike. -`dbName` is now the only thing that picks the database. Set it to the database your driver code reads, so `client.db("app")` and Prisma ORM stay on the same one. An empty `dbName` is an error. +`dbName` is now the only thing that picks the database, so set it to the database your driver code reads, and `client.db("app")` and Prisma ORM stay on the same one. An empty `dbName` is an error. ```typescript import { client, db } from "./prisma/db"; @@ -173,11 +173,11 @@ const jane = await db.orm.users.where({ email: "jane@prisma.io" }).first(); ``` On MongoDB there is no schema segment in the path, and the property name is the collection name. The collection name is the model name with a lowercase first letter, so a model `User` is `db.orm.user`. `db.orm.users` above is the collection name; the model is `User` with `@@map("users")`. -Writes you make through the driver skip the type-checking that Prisma ORM queries get. Put each driver transaction in its own small function, and write everything around it as Prisma ORM queries. +Writes you make through the driver skip the type-checking that Prisma ORM queries get, so put each driver transaction in its own small function, and write everything around it as Prisma ORM queries. ## Coming from Prisma ORM 7 -Prisma ORM 8 has no `$transaction`. Both of the Prisma ORM 7 forms become `db.transaction(async (tx) => ...)`. The interactive form maps one for one: rename the method and query through `tx`. +Prisma ORM 8 has no `$transaction`, so both of the Prisma ORM 7 forms become `db.transaction(async (tx) => ...)`. The interactive form maps one for one: rename the method and query through `tx`. ```diff - const result = await prisma.$transaction(async (prisma) => { @@ -191,7 +191,7 @@ Prisma ORM 8 has no `$transaction`. Both of the Prisma ORM 7 forms become `db.tr + }); ``` -The array form has no replacement of its own. Write the queries out in the callback, one after another: +The array form has no replacement of its own, so write the queries out in the callback, one after another: ```diff - const [user, post] = await prisma.$transaction([ @@ -205,7 +205,7 @@ The array form has no replacement of its own. Write the queries out in the callb + }); ``` -You get the same atomicity. You also get what the array form never allowed: one query's result (here `user.id`) can feed the next query in the same transaction. +You get the same atomicity, and you also get what the array form never allowed: one query's result (here `user.id`) can feed the next query in the same transaction. ## Common mistakes @@ -241,11 +241,11 @@ await db.transaction(async (tx) => { }); ``` -Queries on `db` run on their own connection, outside the open transaction. They commit immediately and won't roll back with the rest of the callback. Use `tx` for every query inside the callback: `tx.orm` for models, `tx.sql` and `tx.execute` for the SQL query builder. +Queries on `db` run on their own connection, outside the open transaction, so they commit immediately and won't roll back with the rest of the callback. Use `tx` for every query inside the callback: `tx.orm` for models, `tx.sql` and `tx.execute` for the SQL query builder. ## Prompt your coding agent -Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8), instruction files for coding agents; in an existing project, run `npx prisma skills sync`. The `prisma-8` skill covers transactions. Prompts that map to each section: +Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8), instruction files for coding agents, and in an existing project you run `npx prisma skills sync` to add them. The `prisma-8` skill covers transactions, so try prompts that map to each section: - "Using the prisma-8 skill, wrap this signup flow (create user, create welcome post) in a db.transaction so both writes commit together." - "Check this transaction callback for queries that use db instead of tx, and move the email send after the commit." diff --git a/apps/docs/content/docs/orm/fundamentals/writing-data.mdx b/apps/docs/content/docs/orm/fundamentals/writing-data.mdx index 59baf529923..a39083875b5 100644 --- a/apps/docs/content/docs/orm/fundamentals/writing-data.mdx +++ b/apps/docs/content/docs/orm/fundamentals/writing-data.mdx @@ -8,11 +8,11 @@ metaDescription: 'Create, update, delete, and upsert records in PostgreSQL and M This page shows how to write data with Prisma ORM: [creating](#create-one-record), [updating](#update-one-record), [deleting](#delete-one-record), and [upserting](#upsert-a-record) single records, and [writing many records at once](#write-many-records). -Every example imports `db`. New project: `npm create prisma@latest -- my-app`. Existing project: `npx prisma orm init`. Either way you get `src/prisma/db.ts`, which exports `db`. The import is `./prisma/db` from a file in `src/`. +Every example imports `db`. In a new project run `npm create prisma@latest -- my-app`, and in an existing project run `npx prisma orm init`. Either way you get `src/prisma/db.ts`, which exports `db`, and the import is `./prisma/db` from a file in `src/`. This page uses `db.orm`, which holds your models. The rest of `db` is `db.sql` for the SQL query builder, `db.raw.sql` for raw SQL, and `db.transaction` for running several writes together. -On PostgreSQL the path to a model is `db.orm..`, so the `User` model is `db.orm.public.User`. `public` is the PostgreSQL schema; you type it. It is `public` unless the model sits in a `namespace` block (shown under [Example schema](#example-schema)). On MongoDB there is no schema segment. The path is `db.orm.`, so the model below is `db.orm.users`. The collection name is the model's `@@map(...)`, or the model name with a lowercase first letter. +On PostgreSQL the path to a model is `db.orm..`, so the `User` model is `db.orm.public.User`. `public` is the PostgreSQL schema; you type it. It is `public` unless the model sits in a `namespace` block (shown under [Example schema](#example-schema)). On MongoDB there is no schema segment, and the path is `db.orm.`, so the model below is `db.orm.users`. The collection name is the model's `@@map(...)`, or the model name with a lowercase first letter. ## Example schema @@ -82,7 +82,7 @@ The model is then only at `db.orm.billing.Invoice`, and the block name is the Po ## Create one record -Use `.create(...)` to insert one record. Pass the fields directly. Prisma ORM returns the inserted record, including generated values such as IDs and database defaults: +Use `.create(...)` to insert one record, and pass the fields directly. Prisma ORM returns the inserted record, including generated values such as IDs and database defaults: ```typescript tab="PostgreSQL" import { db } from "./prisma/db"; @@ -111,7 +111,7 @@ The returned record is complete, so you can use the generated values right away { id: 'cuid20000000000000000003', email: 'jane@prisma.io', name: 'Jane', createdAt: 2026-07-06T09:09:56.119Z } ``` -To get back only some fields, chain `.select(...)` before `.create(...)`. The record is still inserted in full. You only get back the fields you listed: +To get back only some fields, chain `.select(...)` before `.create(...)`: the record is still inserted in full, but you only get back the fields you listed: ```typescript const account = await db.orm.public.User @@ -125,7 +125,7 @@ const account = await db.orm.public.User `.select(...)` works the same before `update`, `delete`, `upsert`, `createAll`, `updateAll`, and `deleteAll`. The `AndCount` methods give you back a number, so `.select(...)` has no effect on them. -When a write breaks a unique constraint, the call throws. Errors have no shared class. A PostgreSQL database error carries `sqlState` (a five-character SQL state code), a MongoDB driver error carries a numeric `code`, and a Prisma ORM error carries a string `code` such as `RUNTIME.ITERATOR_CONSUMED`. On PostgreSQL, `23505` is the SQL state code for a unique violation. The [error reference](/orm/reference/error-reference) lists Prisma ORM's own codes: +When a write breaks a unique constraint, the call throws, and errors have no shared class: a PostgreSQL database error carries `sqlState` (a five-character SQL state code), a MongoDB driver error carries a numeric `code`, and a Prisma ORM error carries a string `code` such as `RUNTIME.ITERATOR_CONSUMED`. On PostgreSQL, `23505` is the SQL state code for a unique violation. The [error reference](/orm/reference/error-reference) lists Prisma ORM's own codes: ```typescript try { @@ -137,11 +137,11 @@ try { } ``` -On MongoDB the driver throws its own error. A duplicate key gives you an error whose `error.code` is `11000`. +On MongoDB the driver throws its own error, and a duplicate key gives you an error whose `error.code` is `11000`. :::note -MongoDB contracts do not support `@default`, so pass timestamp fields such as `createdAt` yourself. On PostgreSQL the database fills them in. +MongoDB contracts do not support `@default`, so pass timestamp fields such as `createdAt` yourself, while on PostgreSQL the database fills them in. `@map("_id")` renames the id field to `_id` everywhere, so the returned document has an `_id` key and not an `id` key. @@ -160,7 +160,7 @@ const user = await db.orm.public.User.create({ }); ``` -You do not pass `authorId` on the nested posts. Prisma ORM fills it in from the user it just inserted. +You do not pass `authorId` on the nested posts, because Prisma ORM fills it in from the user it just inserted. `connect` links a record that already exists, and works in `.update(...)` too: @@ -170,13 +170,13 @@ await db.orm.public.User .update({ posts: (p) => p.connect([{ id: existingPostId }]) }); ``` -`p.disconnect(...)` unlinks a related record. It applies on `.update(...)` only, not on `.create(...)`. +`p.disconnect(...)` unlinks a related record, but it applies on `.update(...)` only, not on `.create(...)`. -`connectOrCreate`, the relation `set`, and nested updates, upserts, and deletes do not exist. See [Not available](/orm/coming-from-prisma-orm-7#not-available-yet). For the full picture of relations, see [Relations and joins](/orm/fundamentals/relations-and-joins). +`connectOrCreate`, the relation `set`, and nested updates, upserts, and deletes do not exist: see [Not available](/orm/coming-from-prisma-orm-7#not-available-yet). For the full picture of relations, see [Relations and joins](/orm/fundamentals/relations-and-joins). ## Update one record -Use `.where(...)` to pick the record, then `.update(...)` with the fields to change. It updates **one** matching record and returns it: +Use `.where(...)` to pick the record, then `.update(...)` with the fields to change, which updates **one** matching record and returns it: ```typescript tab="PostgreSQL" const updatedUser = await db.orm.public.User @@ -194,7 +194,7 @@ const updatedUser = await db.orm.users { id: 'cuid20000000000000000003', email: 'jane@prisma.io', name: 'Jane Doe', createdAt: 2026-07-06T09:09:56.119Z } ``` -When nothing matches the filter, `.update(...)` returns `null`. It does not throw. +When nothing matches the filter, `.update(...)` returns `null` rather than throwing. When the filter matches more than one record, `.update(...)` still changes only one of the matching records, with no guaranteed order. Use [`updateAll` or `updateAndCount`](#write-many-records) if you mean all of them. @@ -208,9 +208,9 @@ await db.orm.posts The callback returns an array, so you can apply several operations in one update. -`set` and `unset` work on any field. `inc` and `mul` are on number fields only. `push`, `pull`, `addToSet`, and `pop` are for array fields, and you call them the same way: `p.tags.push("news")`. These field operations are MongoDB only. PostgreSQL has no callback form of `update`, so on PostgreSQL you pass an object. See [Field update operations](/orm/reference/orm-client#field-update-operations) in the reference. +`set` and `unset` work on any field, while `inc` and `mul` are on number fields only. `push`, `pull`, `addToSet`, and `pop` are for array fields, and you call them the same way: `p.tags.push("news")`. These field operations are MongoDB only: PostgreSQL has no callback form of `update`, so on PostgreSQL you pass an object. See [Field update operations](/orm/reference/orm-client#field-update-operations) in the reference. -There is no `increment` on PostgreSQL. To add to a number in place, write the update as raw SQL. For a `views Int` column added to `Post`, `db.raw.sql` writes a raw statement, `.affectedCount()` says you want the row count back as `{ affectedRows }`, `.build()` finishes it, and `db.runtime().execute(...)` runs it; [Advanced queries](/orm/fundamentals/advanced-queries) explains raw SQL: +There is no `increment` on PostgreSQL, so to add to a number in place, write the update as raw SQL. For a `views Int` column added to `Post`, `db.raw.sql` writes a raw statement, `.affectedCount()` says you want the row count back as `{ affectedRows }`, `.build()` finishes it, and `db.runtime().execute(...)` runs it; [Advanced queries](/orm/fundamentals/advanced-queries) explains raw SQL: ```typescript const query = db.raw.sql`UPDATE post SET views = views + 1 WHERE id = ${postId}` @@ -221,7 +221,7 @@ const { affectedRows } = await db.runtime().execute(query); ## Delete one record -Use `.where(...)` then `.delete()`. It deletes **one** matching record and returns it: +Use `.where(...)` then `.delete()`, which deletes **one** matching record and returns it: ```typescript tab="PostgreSQL" const deletedUser = await db.orm.public.User @@ -239,7 +239,7 @@ const deletedUser = await db.orm.users ## Upsert a record -Use `.upsert(...)` to update a record if it exists and create it otherwise. Pass the two branches separately: +Use `.upsert(...)` to update a record if it exists and create it otherwise, and pass the two branches separately: ```typescript tab="PostgreSQL" await db.orm.public.User.upsert({ @@ -258,7 +258,7 @@ await db.orm.users.where({ email: "eve@prisma.io" }).upsert({ `conflictOn` repeats the unique field and its value from `create`; Prisma ORM uses it to look for an existing row. For a unique constraint over several columns, pass them all in one object: `conflictOn: { tenantId, email }`. Without `conflictOn` on PostgreSQL, the upsert looks for a row by primary key, which a new record does not have, so the insert runs and fails on the unique constraint. Always pass `conflictOn`. -On MongoDB, there is no `conflictOn`. Put the match in `.where(...)` before `.upsert(...)`. +On MongoDB, there is no `conflictOn`, so put the match in `.where(...)` before `.upsert(...)`. ## Write many records @@ -294,7 +294,7 @@ const deletedCount = await db.orm.public.Post.where((p) => p.title.ilike("draft% [{ id: 'cuid20000000000000000101', title: 'One', published: false, /* ... */ }, { id: 'cuid20000000000000000102', title: 'Two', published: false, /* ... */ }] ``` -The `AndCount` methods return a plain number. If three posts match, `updatedCount` is `3`, not an object. +The `AndCount` methods return a plain number, so if three posts match, `updatedCount` is `3`, not an object. `.where((p) => p.title.ilike("draft%"))` is the callback form of a filter, for conditions an object cannot express. Its argument gives you one entry per field, and each field carries the comparisons you can apply to it. [Reading data](/orm/fundamentals/reading-data) covers the filters you can write. @@ -302,7 +302,7 @@ The bulk methods work the same on MongoDB, on the collection: `db.orm.posts`. Pa ## Return rows or counts -Each write comes in three forms. Pick by what you need back: +Each write comes in three forms, and you pick by what you need back: | Form | What it writes | What you get back | | --- | --- | --- | @@ -360,7 +360,7 @@ Use `updateAll` or `deleteAll` when you also need the changed records back, and ### Wrapping create fields in a data object -You wrote the Prisma ORM 7 shape, and it fails type-checking. There is no field named `data` on your models: +You wrote the Prisma ORM 7 shape, which fails type-checking because there is no field named `data` on your models: ```diff - await db.orm.public.User.create({ data: { email, name } }); @@ -392,7 +392,7 @@ const user = await db.orm.public.User.create({ email, name }); const post = await db.orm.public.Post.create({ title, published: false, authorId: user.id }); ``` -If the second write fails, the first has already committed, and you're left with half the operation. When writes must succeed together, run them in a [transaction](/orm/fundamentals/transactions). Inside the callback, query through `tx` instead of `db`: +If the second write fails, the first has already committed, and you're left with half the operation. When writes must succeed together, run them in a [transaction](/orm/fundamentals/transactions), and inside the callback, query through `tx` instead of `db`: ```typescript await db.transaction(async (tx) => { @@ -403,11 +403,11 @@ await db.transaction(async (tx) => { ### Passing an array of queries to a transaction -Prisma ORM 7 supported `$transaction([query1, query2])`. Prisma ORM 8 does not: there is no `$transaction`, and queries don't queue up in arrays. Put the calls inside one `db.transaction(...)` callback instead. The [Transactions page](/orm/fundamentals/transactions) shows the pattern. +Prisma ORM 7 supported `$transaction([query1, query2])`, but Prisma ORM 8 does not: there is no `$transaction`, and queries don't queue up in arrays. Put the calls inside one `db.transaction(...)` callback instead, and the [Transactions page](/orm/fundamentals/transactions) shows the pattern. ## Prompt your coding agent -Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent; in an existing project, run `npx prisma skills sync`. Skills are instruction files that tell the agent how Prisma ORM 8 works, and the `prisma-8` skill covers everything on this page. Prompts that map to each section: +Projects created with `npm create prisma@latest` include the [Prisma ORM skills](/ai/tools/skills#available-skills-for-prisma-8) for your coding agent, and in an existing project you run `npx prisma skills sync` to add them. Skills are instruction files that tell the agent how Prisma ORM 8 works, and the `prisma-8` skill covers everything on this page, so try prompts that map to each section: - "Using the prisma-8 skill, add a signup function that creates a User and returns only its id and email." - "Write an upsert that creates a user by email or updates their name if they exist." diff --git a/apps/docs/content/docs/orm/index.mdx b/apps/docs/content/docs/orm/index.mdx index 87a0ac0c5b7..f5409b4061c 100644 --- a/apps/docs/content/docs/orm/index.mdx +++ b/apps/docs/content/docs/orm/index.mdx @@ -6,13 +6,13 @@ metaTitle: What is Prisma ORM? metaDescription: 'Prisma ORM 8 is the next major version. It introduces a TypeScript runtime, contract-based data models, a new query API, and a revised migration workflow.' --- -Prisma ORM 8 rewrites Prisma ORM in TypeScript. The schema-first workflow and model-first queries from earlier versions stay. What changes is what your schema produces and how queries run. This page gives you the mental model, and the [quickstart](/prisma-orm/quickstart/postgresql) gets you to a running query. +Prisma ORM 8 rewrites Prisma ORM in TypeScript. The schema-first workflow and model-first queries from earlier versions stay, but what changes is what your schema produces and how queries run. This page gives you the mental model, and the [quickstart](/prisma-orm/quickstart/postgresql) gets you to a running query. :::note[Using Prisma ORM 7?] Prisma ORM 8 is the current release, as a release candidate. Prisma ORM 7 remains fully supported; its docs live at [/orm/v7](/orm/v7) and its setup paths at [/v7/getting-started](/v7/getting-started). -For what release candidate means, when the final release is expected, and how to stay on version 7, see [Release status](/prisma-orm/release-status). For the Prisma ORM 8 name of every Prisma ORM 7 API, see [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7). +For what release candidate means, when the final release is expected, and how to stay on version 7, see [Release status](/orm/release-status). For the Prisma ORM 8 name of every Prisma ORM 7 API, see [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7). ::: diff --git a/apps/docs/content/docs/orm/meta.json b/apps/docs/content/docs/orm/meta.json index aea00d7971d..2edb0d9ac77 100644 --- a/apps/docs/content/docs/orm/meta.json +++ b/apps/docs/content/docs/orm/meta.json @@ -7,6 +7,8 @@ "---Introduction---", "index", "core-concepts", + "release-status", + "supported-databases", "coming-from-prisma-orm-7", "[Prisma ORM 7 to 8 (PostgreSQL)](/guides/upgrade-prisma-orm/postgresql)", "[Prisma ORM 6 to 8 (MongoDB)](/guides/upgrade-prisma-orm/mongodb)", diff --git a/apps/docs/content/docs/orm/middleware/authoring-custom-middleware.mdx b/apps/docs/content/docs/orm/middleware/authoring-custom-middleware.mdx index c18cbee2d60..6a0c35b70e3 100644 --- a/apps/docs/content/docs/orm/middleware/authoring-custom-middleware.mdx +++ b/apps/docs/content/docs/orm/middleware/authoring-custom-middleware.mdx @@ -160,7 +160,7 @@ You have a working, configurable middleware. The rest of this page describes the ## The hooks -`afterQuery` is one hook on one of two lifecycles. Row queries and non-returning writes take different paths, and `beforeCompile` is the only hook they share. Implement any subset: +`afterQuery` is one hook on one of two lifecycles: row queries and non-returning writes take different paths, and `beforeCompile` is the only hook they share. Implement any subset: | Hook | Lifecycle | Runs | Use it to | | --- | --- | --- | --- | @@ -179,7 +179,7 @@ There is no generic `intercept` and no fallback between the two lifecycles. A mi ### Rewrite queries with beforeCompile -`beforeCompile` sees the typed AST before it becomes SQL. Return a new draft to rewrite the query. This middleware adds a predicate to every `SELECT` on the `user` table: +`beforeCompile` sees the typed AST before it becomes SQL, so return a new draft to rewrite the query. This middleware adds a predicate to every `SELECT` on the `user` table: ```ts title="src/prisma/scope-user-selects.ts" import type { SqlMiddleware } from '@prisma/orm-postgres/family-runtime'; diff --git a/apps/docs/content/docs/orm/middleware/built-in-cache.mdx b/apps/docs/content/docs/orm/middleware/built-in-cache.mdx index c549540646e..1b1fa687037 100644 --- a/apps/docs/content/docs/orm/middleware/built-in-cache.mdx +++ b/apps/docs/content/docs/orm/middleware/built-in-cache.mdx @@ -80,7 +80,7 @@ By default the cache key is a content hash of the executed query: the post-lower Cache hits are still visible to the rest of the middleware chain: `beforeQuery` hooks have already run, the driver and `onRow` are skipped, and `afterQuery` fires on every middleware with `result.source` set to `'middleware'` instead of `'driver'`, so logging and metrics keep working for cached reads. -The cache only acts at runtime scope. Queries executed inside a transaction or on an explicitly checked-out connection always go to the database, because read-after-write consistency is the caller's expectation there. +The cache only acts at runtime scope: queries executed inside a transaction or on an explicitly checked-out connection always go to the database, because read-after-write consistency is the caller's expectation there. ## Options @@ -94,7 +94,7 @@ The cache only acts at runtime scope. Queries executed inside a transaction or o :::warning -The default store is in-memory and per-process. Two instances of your app do not share it, and a deploy clears it. That makes it a good fit for hot, tolerant-of-staleness reads, and a poor fit as a source of truth. For shared caching, implement `store` against your infrastructure. +The default store is in-memory and per-process, so two instances of your app do not share it, and a deploy clears it. That makes it a good fit for hot, tolerant-of-staleness reads, and a poor fit as a source of truth. For shared caching, implement `store` against your infrastructure. ::: diff --git a/apps/docs/content/docs/orm/middleware/built-in-lints.mdx b/apps/docs/content/docs/orm/middleware/built-in-lints.mdx index b11b0fb44bf..759d964a9c6 100644 --- a/apps/docs/content/docs/orm/middleware/built-in-lints.mdx +++ b/apps/docs/content/docs/orm/middleware/built-in-lints.mdx @@ -71,7 +71,7 @@ There are five severity keys: `deleteWithoutWhere`, `updateWithoutWhere`, `noLim Queries built with the ORM client or the SQL query builder carry a typed AST, and the rules above inspect it structurally. -A whole-query raw statement built with [`db.raw.sql`](/orm/reference/raw-queries) does have an AST, of kind `raw-query`. It has no structural shape the runtime authored, so the structural rules are skipped for it and the text heuristics always run instead: they flag `select *`, a missing `LIMIT`, and a mutation smuggled through a query marked read-only (`LINT.READ_ONLY_MUTATION`). The raw heuristics carry their own defaults: `select *` escalates to `error` for raw plans (stricter than the AST rule's `warn`), a missing `LIMIT` stays a warning, and a read-only-intent mutation is an `error`. The `severities` keys above override raw findings too. +A whole-query raw statement built with [`db.raw.sql`](/orm/reference/raw-queries) does have an AST, of kind `raw-query`, but it has no structural shape the runtime authored, so the structural rules are skipped for it and the text heuristics always run instead: they flag `select *`, a missing `LIMIT`, and a mutation smuggled through a query marked read-only (`LINT.READ_ONLY_MUTATION`). The raw heuristics carry their own defaults: `select *` escalates to `error` for raw plans (stricter than the AST rule's `warn`), a missing `LIMIT` stays a warning, and a read-only-intent mutation is an `error`. The `severities` keys above override raw findings too. `fallbackWhenAstMissing` matters only for a plan that has no `plan.ast` at all, not for `raw-query` plans: diff --git a/apps/docs/content/docs/orm/middleware/how-middleware-works.mdx b/apps/docs/content/docs/orm/middleware/how-middleware-works.mdx index 31ae1435dcb..d1d983963b2 100644 --- a/apps/docs/content/docs/orm/middleware/how-middleware-works.mdx +++ b/apps/docs/content/docs/orm/middleware/how-middleware-works.mdx @@ -112,7 +112,7 @@ Prisma ORM groups databases into families: the SQL family (PostgreSQL and other - `budgets` and `lints` declare `familyId: 'sql'`. They register on PostgreSQL and are rejected on MongoDB. - The cache declares no `familyId`. It works against the family-neutral parts of a query, so it runs on both PostgreSQL and MongoDB. -The runtime checks this when you construct the client. A middleware that does not match the runtime fails at startup with `RUNTIME.MIDDLEWARE_FAMILY_MISMATCH`, not at query time in production. +The runtime checks this when you construct the client, so a middleware that does not match the runtime fails at startup with `RUNTIME.MIDDLEWARE_FAMILY_MISMATCH`, not at query time in production. ## When a middleware or the driver throws diff --git a/apps/docs/content/docs/orm/migrations/applying-a-migration.mdx b/apps/docs/content/docs/orm/migrations/applying-a-migration.mdx index 9953420a4f8..f17cc31e3e4 100644 --- a/apps/docs/content/docs/orm/migrations/applying-a-migration.mdx +++ b/apps/docs/content/docs/orm/migrations/applying-a-migration.mdx @@ -12,7 +12,7 @@ Applying is the one step that touches a database, via a single command: npx prisma@latest db migrate ``` -Note that the command is `db migrate`, not `migration apply`. The `migration ...` subcommands manage files on disk; `db migrate` moves a database. It reads where the database currently is (its **marker**), finds a path through the [migration graph](/orm/migrations/the-migration-graph) to the target, and applies each migration along the path, running every operation's precheck, execute, and postcheck as it goes. +Note that the command is `db migrate`, not `migration apply`: the `migration ...` subcommands manage files on disk, while `db migrate` moves a database. It reads where the database currently is (its **marker**), finds a path through the [migration graph](/orm/migrations/the-migration-graph) to the target, and applies each migration along the path, running every operation's precheck, execute, and postcheck as it goes. By default, the connection comes from `db.connection` in `prisma.config.ts`. Pass `--db` to override it: @@ -97,7 +97,7 @@ npx prisma@latest db migrate --to prod --db $DATABASE_URL If the graph has branched and more than one tip is reachable, `db migrate` stops and asks for an explicit `--to`. That's the graph protecting you: two feature branches may both be valid futures, and picking one is a human decision. -`--advance-ref` moves a named ref to the post-apply state in the same step, which is what keeps [`migration plan`](/orm/migrations/generating-a-migration#the-db-ref-skipping---from) incremental. Plain `db migrate` never moves a ref. That is deliberate: a deploy or a CI apply should not move a ref that lives in the repository, so in development you pass `--advance-ref db` and in production you leave it off. The [`db migrate` reference](/cli/db-migrate#options) has the full `--to` grammar (refs, hashes, migration names, `^`) and the flag contract. +`--advance-ref` moves a named ref to the post-apply state in the same step, which is what keeps [`migration plan`](/orm/migrations/generating-a-migration#the-db-ref-skipping---from) incremental. Plain `db migrate` never moves a ref, which is deliberate: a deploy or a CI apply should not move a ref that lives in the repository, so in development you pass `--advance-ref db` and in production you leave it off. The [`db migrate` reference](/cli/db-migrate#options) has the full `--to` grammar (refs, hashes, migration names, `^`) and the flag contract. ## When something goes wrong @@ -119,7 +119,7 @@ Before running any DDL, `db migrate` also verifies the database's marker is a st ## Development vs. production -The commands are the same everywhere. What changes is where the files come from and who runs them. +The commands are the same everywhere: what changes is where the files come from and who runs them. **In development**, you're the one planning and editing, and you apply immediately: @@ -137,7 +137,7 @@ npx prisma@latest db migrate --db $DATABASE_URL The runner executes only `ops.json`, which is plain data, so your `migration.ts` files, and any TypeScript they import, are never executed with production credentials. -Concurrent deploys are safe: on PostgreSQL the whole apply runs inside a transaction guarded by an advisory lock, so two `db migrate` runs serialize instead of interleaving. On MongoDB, cross-collection DDL transactions don't exist. Instead, each migration advances the marker with compare-and-swap, and the runner verifies the resulting schema before committing the marker, so a re-run converges rather than double-applying. +Concurrent deploys are safe: on PostgreSQL the whole apply runs inside a transaction guarded by an advisory lock, so two `db migrate` runs serialize instead of interleaving. On MongoDB, cross-collection DDL transactions don't exist: instead, each migration advances the marker with compare-and-swap, and the runner verifies the resulting schema before committing the marker, so a re-run converges rather than double-applying. ## Extension spaces diff --git a/apps/docs/content/docs/orm/migrations/editing-a-migration.mdx b/apps/docs/content/docs/orm/migrations/editing-a-migration.mdx index 9347995c8b2..6cbd3a617f5 100644 --- a/apps/docs/content/docs/orm/migrations/editing-a-migration.mdx +++ b/apps/docs/content/docs/orm/migrations/editing-a-migration.mdx @@ -57,7 +57,7 @@ override get operations() { } ``` -A `dataTransform` takes two closures. `check` asks "are there rows that still need this?" It must be a row-returning query where *any row* means work remains. The conventional shape is `select('id').where().limit(1)`. `run` performs the change. Fill them with the typed SQL query builder, wired to the contract snapshot the migration imports from `migrations/snapshots/`: +A `dataTransform` takes two closures. `check` asks "are there rows that still need this?", so it must be a row-returning query where *any row* means work remains. The conventional shape is `select('id').where().limit(1)`. `run` performs the change. Fill them with the typed SQL query builder, wired to the contract snapshot the migration imports from `migrations/snapshots/`: ```ts title="migration.ts (filled in)" import type { Contract as End } from '../../snapshots/e6b5c2849eca8d24ff1e8e88ab2a4234db8e74c497c035cb7ce42e814f31cd63/contract'; @@ -119,7 +119,7 @@ These two details should be noted: - **The types come from the migration's end-contract snapshot** (`migrations/snapshots//contract`), not your live app contract. You're type-checking against the schema *as it will exist when this step runs*. That's why the builder happily references `displayName` even though the column doesn't exist yet, and why a migration written months ago keeps compiling after your contract moves on. - **The query is real application-grade TypeScript.** You can import shared constants, and typos in column names fail the type check instead of failing in production. -The wiring block at the top (deserializing the contract, building the `db` handle) is the current shape. Expect it to shrink to a one-liner. It's the price of a query builder that's typed against this migration's snapshot instead of your live app client. +The wiring block at the top (deserializing the contract, building the `db` handle) is the current shape, but expect it to shrink to a one-liner. It's the price of a query builder that's typed against this migration's snapshot instead of your live app client. Recompile with `node migration.ts` and inspect what the backfill became: @@ -174,7 +174,7 @@ rawSql({ }), ``` -The prechecks and postchecks are optional, but they're what makes a failed run resumable and a mistake diagnosable: skipping them trades away most of what this system gives you. If you write the same `rawSql` twice, lift it into a function. The built-in factories are plain functions doing exactly this. +The prechecks and postchecks are optional, but they're what makes a failed run resumable and a mistake diagnosable: skipping them trades away most of what this system gives you. If you write the same `rawSql` twice, lift it into a function: the built-in factories are plain functions doing exactly this. ## The same pattern on MongoDB diff --git a/apps/docs/content/docs/orm/migrations/generating-a-migration.mdx b/apps/docs/content/docs/orm/migrations/generating-a-migration.mdx index 53fb5d2627b..aa842c4a52b 100644 --- a/apps/docs/content/docs/orm/migrations/generating-a-migration.mdx +++ b/apps/docs/content/docs/orm/migrations/generating-a-migration.mdx @@ -6,7 +6,7 @@ metaTitle: Generating a migration in Prisma ORM metaDescription: 'A step-by-step tutorial for the migration plan command, from contract change to a planned, reviewable migration, entirely offline.' --- -This tutorial takes a contract change from your editor to a planned migration on disk. Planning is **fully offline**. `migration plan` reads your emitted contract and your existing migrations; it never connects to a database. This means you can plan on a plane, in CI, or in a sandbox with no credentials. +This tutorial takes a contract change from your editor to a planned migration on disk. Planning is **fully offline**: `migration plan` reads your emitted contract and your existing migrations, and it never connects to a database. This means you can plan on a plane, in CI, or in a sandbox with no credentials. Start from a minimal project; you can scaffold one with `npm create prisma@latest`. @@ -24,13 +24,13 @@ model User { } ``` -First, emit the contract. This compiles the schema into the `contract.json` artifact that every other command reads: +First, emit the contract, which compiles the schema into the `contract.json` artifact that every other command reads: ```npm npx prisma@latest contract emit ``` -Now plan. `--name` sets the human-readable part of the directory name. If you omit it, the directory will be called `migration`: +Now plan. `--name` sets the human-readable part of the directory name, and if you omit it, the directory will be called `migration`: ```npm npx prisma@latest migration plan --name init @@ -114,7 +114,7 @@ export default class M extends Migration { MigrationCLI.run(import.meta.url, M); ``` -When reading it, note two things: the `col(...)` calls mirror your contract fields, and you never write `codecRef` by hand. The planner fills it in to pin how values convert between TypeScript and Postgres. Treat it as noise when reviewing. +When reading it, note two things: the `col(...)` calls mirror your contract fields, and you never write `codecRef` by hand. The planner fills it in to pin how values convert between TypeScript and Postgres, so treat it as noise when reviewing. For a simple change you don't need to touch this file. When you do (to add a data backfill, reorder operations, or drop in raw SQL), refer to [Editing a migration](/orm/migrations/editing-a-migration). @@ -159,7 +159,7 @@ ALTER TABLE "public"."user" ADD COLUMN "phone" text; ### The db ref: skipping --from -You don't have to pass `--from` every time. If a [ref](/orm/migrations/the-migration-graph#name-important-states-with-refs) named **`db`** exists, planning starts from whatever it points at by default. Four things move it forward: +You don't have to pass `--from` every time: if a [ref](/orm/migrations/the-migration-graph#name-important-states-with-refs) named **`db`** exists, planning starts from whatever it points at by default. Four things move it forward: - `db init` and `db update` advance it after a successful run, but only when you leave `--db` off and let the connection come from `prisma.config.ts`. Passing `--db` suppresses the advancement unless you also pass `--advance-ref db`. - `db sign` advances it after a successful signature, with or without `--db`. This is what makes the first plan after adopting an existing database contain only your change. Pass `--no-advance-ref` to sign without moving it. @@ -178,7 +178,7 @@ npx prisma@latest migration plan --name next_change # starts from the db ref a If there's no `db` ref and you omit `--from`, `migration plan` refuses with `MIGRATION.PLAN_ORIGIN_UNKNOWN` whenever migrations already exist on disk. Planning from empty there would produce a full `CREATE`-everything package that no database carrying the earlier migrations could apply, so the command stops instead and names the three exits: `migration ref set db `, `--from `, or `--from @empty` to plan from an empty database deliberately. -Only an empty `migrations/app/` still plans from empty on its own. That is the first migration in a new project, described above. The command prints `No db ref set — planning from an empty database` under its summary when that happens, so you can tell a fresh first plan from a plan that recreates a database you already have. +Only an empty `migrations/app/` still plans from empty on its own: that is the first migration in a new project, described above. The command prints `No db ref set — planning from an empty database` under its summary when that happens, so you can tell a fresh first plan from a plan that recreates a database you already have. ::: @@ -222,7 +222,7 @@ npx prisma@latest migration check :::note[What's early] -Planning covers tables, columns, indexes, constraints, and the backfill scaffold shown above. Rename inference is not built yet: renaming a field plans as **drop column + add column**, flagged destructive with a data-loss warning. For a true rename, edit the migration and replace the pair with a `rawSql` `ALTER TABLE ... RENAME COLUMN`. There is also no interactive mode. The planner writes its best answer and leaves refinement to you in `migration.ts`. +Planning covers tables, columns, indexes, constraints, and the backfill scaffold shown above. Rename inference is not built yet: renaming a field plans as **drop column + add column**, flagged destructive with a data-loss warning. For a true rename, edit the migration and replace the pair with a `rawSql` `ALTER TABLE ... RENAME COLUMN`. There is also no interactive mode: the planner writes its best answer and leaves refinement to you in `migration.ts`. ::: diff --git a/apps/docs/content/docs/orm/migrations/how-migrations-work.mdx b/apps/docs/content/docs/orm/migrations/how-migrations-work.mdx index a47396f6119..504199a1ed8 100644 --- a/apps/docs/content/docs/orm/migrations/how-migrations-work.mdx +++ b/apps/docs/content/docs/orm/migrations/how-migrations-work.mdx @@ -67,7 +67,7 @@ migrations/ Contract snapshots live once per migrations root, in `migrations/snapshots/`, keyed by the contract's hash. A migration imports the states it moves between from there (a baseline migration starts from nothing and imports only its end contract), so a contract state shared by several migrations is stored once. -Three files matter day to day. Each has one job: +Three files matter day to day, and each has one job: | File | Purpose | | --- | --- | @@ -77,7 +77,7 @@ Three files matter day to day. Each has one job: ### migration.ts: the file you edit -`migration.ts` is the authoring file. It contains the migration steps as ordinary TypeScript: +`migration.ts` is the authoring file and contains the migration steps as ordinary TypeScript: ```ts this.addColumn(...) @@ -91,9 +91,9 @@ When you want to review or change what the migration does, use this file. For mo ### ops.json: the file Prisma runs -`ops.json` is the compiled form of the migration. When `migration.ts` runs, Prisma turns the TypeScript steps into JSON operations. The planner runs it for you. After an edit, re-run it yourself with `node migration.ts`. The migration runner reads only this file. +`ops.json` is the compiled form of the migration: when `migration.ts` runs, Prisma turns the TypeScript steps into JSON operations. The planner runs it for you, but after an edit, re-run it yourself with `node migration.ts`. The migration runner reads only this file. -That means production never executes your TypeScript. It reads the compiled operations, so no application code runs with production credentials. +That means production never executes your TypeScript: it reads the compiled operations, so no application code runs with production credentials. ```text migration.ts -> ops.json @@ -191,7 +191,7 @@ This structure is what makes Prisma ORM migrations safe in the situations where ## The commands -These commands live under the [Prisma ORM CLI](/cli). Run them with `npx prisma@latest `. The planning and inspection commands work offline. They read files, not your database: +These commands live under the [Prisma ORM CLI](/cli), and you run them with `npx prisma@latest `. The planning and inspection commands work offline, because they read files, not your database: | Command | What it does | | ------------------------- | ----------------------------------------------------------------------- | diff --git a/apps/docs/content/docs/orm/migrations/rollbacks-and-recovery.mdx b/apps/docs/content/docs/orm/migrations/rollbacks-and-recovery.mdx index 2a6eea6d05e..e8191774b9e 100644 --- a/apps/docs/content/docs/orm/migrations/rollbacks-and-recovery.mdx +++ b/apps/docs/content/docs/orm/migrations/rollbacks-and-recovery.mdx @@ -48,7 +48,7 @@ The planner diffs the two contract states: it writes the operations that undo th npx prisma@latest db migrate --to 20260707T1008_add_display_name^ ``` -A rollback also leaves a cycle in the graph, so the next `migration plan` cannot pick its starting point automatically. It fails with `MIGRATION.NO_TARGET` and lists the reachable states. Plan with an explicit `--from ` until history moves forward again. +A rollback also leaves a cycle in the graph, so the next `migration plan` cannot pick its starting point automatically: it fails with `MIGRATION.NO_TARGET` and lists the reachable states. Plan with an explicit `--from ` until history moves forward again. One loose end remains after the database moves back: your contract source still contains the change, so `db verify` reports a hash mismatch until you revert the schema and re-run `contract emit`. Roll back the contract in the same commit as the rollback migration and the two stay in step. @@ -91,7 +91,7 @@ The playbook: 2. **Nothing to clean up.** On PostgreSQL the whole run was one transaction, so the failure rolled it back completely: the database is exactly where it was before the run, and migrations applied in earlier runs are untouched. There is no "half-applied migration" to untangle by hand. 3. **Fix the cause, then re-run `db migrate`.** Sometimes the cause is the environment (extension missing, permissions). Sometimes it's the migration itself; here you'd [edit the migration](/orm/migrations/editing-a-migration) to add a backfill before the `setNotNull`, recompile with `node migration.ts`, and apply again. Re-running is safe, for the reasons covered in [the failure model](/orm/migrations/applying-a-migration#when-something-goes-wrong). -The precheck also protects you when a migration works in development but would fail in production. Your dev database had no `NULL`s, but production does. The precheck halts production *before* the destructive `ALTER` touches anything, with an error pointing at the exact rows-with-NULLs condition instead of a generic constraint violation mid-statement. +The precheck also protects you when a migration works in development but would fail in production. Your dev database had no `NULL`s, but production does, so the precheck halts production *before* the destructive `ALTER` touches anything, with an error pointing at the exact rows-with-NULLs condition instead of a generic constraint violation mid-statement. ### Drift: when the database isn't where migrations left it @@ -103,7 +103,7 @@ If someone changed the database outside of migrations (a hand-run `ALTER`, a res :::note[What's early] -Reverse planning (`--to ^`), destructive-operation warnings, resumable re-runs, and the ledger all work today, as shown above. Some gaps remain. There's no rehearsal mode that executes a migration against a shadow copy first. Recovery from drift beyond the `db verify`/`db update`/`db init` trio is manual. The planning caveat after cycles (explicit `--from`) is a real papercut we expect to smooth out. For deep or unusual situations, `#prisma-next` on [Discord](https://pris.ly/discord) is the fastest route. +Reverse planning (`--to ^`), destructive-operation warnings, resumable re-runs, and the ledger all work today, as shown above. Some gaps remain: there's no rehearsal mode that executes a migration against a shadow copy first, and recovery from drift beyond the `db verify`/`db update`/`db init` trio is manual. The planning caveat after cycles (explicit `--from`) is a real papercut we expect to smooth out. For deep or unusual situations, `#prisma-next` on [Discord](https://pris.ly/discord) is the fastest route. ::: diff --git a/apps/docs/content/docs/orm/migrations/the-migration-graph.mdx b/apps/docs/content/docs/orm/migrations/the-migration-graph.mdx index e6e51e64aad..f32e6556f51 100644 --- a/apps/docs/content/docs/orm/migrations/the-migration-graph.mdx +++ b/apps/docs/content/docs/orm/migrations/the-migration-graph.mdx @@ -6,7 +6,7 @@ metaTitle: The migration graph in Prisma ORM metaDescription: 'How Prisma ORM models migrations as a graph of contract states, why that makes branching and parallel work safe, and the commands to inspect it.' --- -Consider this situation: you add a `phone` column to the schema on your branch. A teammate adds an `avatarUrl` column on theirs. Both branches merge the same afternoon. Now your laptop, your teammate's laptop, staging, and production each have a slightly different version of the schema. Something has to bring them all to the merged state, without losing work or running the same change twice. +Consider this situation: you add a `phone` column to the schema on your branch, a teammate adds an `avatarUrl` column on theirs, and both branches merge the same afternoon. Now your laptop, your teammate's laptop, staging, and production each have a slightly different version of the schema. Something has to bring them all to the merged state, without losing work or running the same change twice. Classic migration tools make this a complicated and time-consuming process. Migrations are kept in a single folder and run just once, in timestamp order; as a result, when multiple branches each add a migration, the timestamps interleave. This breaks the "run once in order" assumption, and someone has to spend an afternoon renaming files to force a clean line. Prisma ORM avoids this with a different approach: the migration graph. @@ -45,13 +45,13 @@ Usually you won't ever touch the graph directly. To see it, run `migration graph ## How it works -Every time you emit your contract, you get a deterministic JSON artifact. Hashing it produces an identifier for that exact schema shape. Those hashes are the **nodes** of the graph. +Every time you emit your contract, you get a deterministic JSON artifact, and hashing it produces an identifier for that exact schema shape. Those hashes are the **nodes** of the graph. -A migration is an **edge**. It records the contract hash it starts `from` and the hash it moves the database `to`. Nothing about a migration's place in history comes from its file name: the timestamps in directory names are for humans, and the real linkage lives in the `from`/`to` hashes inside each `migration.json`. +A migration is an **edge**: it records the contract hash it starts `from` and the hash it moves the database `to`. Nothing about a migration's place in history comes from its file name: the timestamps in directory names are for humans, and the real linkage lives in the `from`/`to` hashes inside each `migration.json`. -A database always sits at exactly one node. Its **marker**, a record Prisma ORM keeps in the database itself, holds the hash of the contract it currently matches. Applying a migration means walking one edge and moving the marker. +A database always sits at exactly one node: its **marker**, a record Prisma ORM keeps in the database itself, holds the hash of the contract it currently matches. Applying a migration means walking one edge and moving the marker. ## A worked example @@ -80,7 +80,7 @@ npx prisma@latest migration graph Read it bottom-up. From an empty database (`-`), `init` establishes state `705b1a6`. Alice and Bob both branch from `705b1a6`: her migration produces `93be6c2`, his produces `7e3fa7f`. Each branch then has a merge migration into the combined state `f9a41d7`, which the `prod` ref points at. This shape is a diamond: one start, two parallel branches, one shared end. -The payoff is in the last column and the two merge edges. A database that follows Alice's branch is sitting at `93be6c2` and reaches `f9a41d7` through `merge_alice`. A database that follows Bob's is at `7e3fa7f` and takes `merge_bob`. Neither developer needed to know about the other's migration, and no files were renamed to make the timestamps line up. +The payoff is in the last column and the two merge edges. A database that follows Alice's branch is sitting at `93be6c2` and reaches `f9a41d7` through `merge_alice`, while a database that follows Bob's is at `7e3fa7f` and takes `merge_bob`. Neither developer needed to know about the other's migration, and no files were renamed to make the timestamps line up. (The summary line counts contract *spaces*: independent migration lanes, one for your app plus one per [database extension](/orm/migrations/applying-a-migration#extension-spaces).) @@ -94,7 +94,7 @@ When you run `db migrate`, the runner walks the graph for you: 4. It applies each edge in order, moving the marker after each one. 5. If any migration's precheck fails, it stops before running SQL and names the mismatch. -If two branch tips are both reachable and neither is clearly the target, the runner does not guess. It stops and asks you to pick one with `--to`. +If two branch tips are both reachable and neither is clearly the target, the runner does not guess: it stops and asks you to pick one with `--to`. ## Inspecting the graph @@ -134,7 +134,7 @@ npx prisma@latest db migrate --to prod Because refs are committed, "where production should be" is a reviewable fact in a pull request instead of tribal knowledge in someone's terminal history. -One ref name is special. A ref called `db` is the default starting point `migration plan` uses when you do not pass `--from`. Keep it current and planning stays incremental on its own. `db init` and `db update` advance it after a successful run, but only when you leave `--db` off and let the connection come from `prisma.config.ts`; with `--db` you have to add `--advance-ref db`. `db sign` advances it whenever it signs, `--db` or not. At apply time, `db migrate --advance-ref db` moves it; plain `db migrate` never does. +One ref name is special: a ref called `db` is the default starting point `migration plan` uses when you do not pass `--from`. Keep it current and planning stays incremental on its own. `db init` and `db update` advance it after a successful run, but only when you leave `--db` off and let the connection come from `prisma.config.ts`; with `--db` you have to add `--advance-ref db`. `db sign` advances it whenever it signs, `--db` or not. At apply time, `db migrate --advance-ref db` moves it; plain `db migrate` never does. Three reserved tokens also work where a command accepts a contract reference, without setting up a ref: @@ -146,7 +146,7 @@ Three reserved tokens also work where a command accepts a contract reference, wi ### Parallel work without ordering conflicts -Alice and Bob each planned a migration from `705b1a6` on their own branches. In a timestamp-ordered system, whoever merges second inherits a broken history. Here, both edges are in the graph and every environment finds its own path to the merged state. This matters even more when the "two developers" are two AI agents planning migrations concurrently, since neither has to know anything at all about the other. +Alice and Bob each planned a migration from `705b1a6` on their own branches. In a timestamp-ordered system, whoever merges second inherits a broken history, but here both edges are in the graph and every environment finds its own path to the merged state. This matters even more when the "two developers" are two AI agents planning migrations concurrently, since neither has to know anything at all about the other. ### History you can trust @@ -166,7 +166,7 @@ A database that already exists can become the graph's starting node without repl For a deployed database you must not touch, `--from @empty` is the explicit retrofit: bring the contract source back to the deployed state, emit, plan the baseline, then restore the current contract and plan the delta from it. -A baseline is never replayed against a database that already carries a marker. The runner starts at the marker and applies only the edges past it. +A baseline is never replayed against a database that already carries a marker: the runner starts at the marker and applies only the edges past it. ## Database-specific details diff --git a/apps/docs/content/docs/orm/reference/index.mdx b/apps/docs/content/docs/orm/reference/index.mdx index 2114cbe9a2a..208090e5d7e 100644 --- a/apps/docs/content/docs/orm/reference/index.mdx +++ b/apps/docs/content/docs/orm/reference/index.mdx @@ -6,7 +6,7 @@ metaTitle: Prisma ORM API reference metaDescription: 'Reference index for the Prisma ORM client, SQL query builder, pipeline builder, raw queries, and runtime APIs.' --- -Prisma ORM gives you three typed ways to write a query. In Prisma ORM 7 you wrote `findMany({ where, include })`. Now each of those arguments is a method you chain: `.where(...).include(...)`. See [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7), which maps each argument to its method. +Prisma ORM gives you three typed ways to write a query. In Prisma ORM 7 you wrote `findMany({ where, include })`, but now each of those arguments is a method you chain: `.where(...).include(...)`. See [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7), which maps each argument to its method. - The **ORM client** is at `db.orm`. It has model methods such as `where()`, `create()`, and `include()`, it works on both PostgreSQL and MongoDB, and it covers everyday reads and writes across your models. - The **SQL query builder** is at `db.sql`. It has table methods that match SQL, such as `select()`, `innerJoin()`, and `groupBy()`, it is PostgreSQL only, and it returns whatever shape your `select()` asks for, not objects shaped like your models. @@ -16,9 +16,9 @@ When none of the three can express the query you need, write raw SQL on PostgreS ## Running a query -The ORM client sends its own queries. `first()`, `create()`, `update()`, `upsert()`, `delete()`, and `aggregate()` send the query the moment you call them. `all()`, `createAll()`, `updateAll()`, and `deleteAll()` return a result object and send the query when you `await` that object or loop over it with `for await`. `all()` is what replaced `findMany`. +The ORM client sends its own queries: `first()`, `create()`, `update()`, `upsert()`, `delete()`, and `aggregate()` send the query the moment you call them. `all()`, `createAll()`, `updateAll()`, and `deleteAll()` return a result object and send the query when you `await` that object or loop over it with `for await`. `all()` is what replaced `findMany`. -The SQL query builder and the pipeline builder send nothing on their own. `.build()` gives you a built query. Pass it to `runtime.query(...)` for rows, or to `runtime.execute(...)` for a write that returns no rows. Get `runtime` from `db.runtime()` once and reuse it. +The SQL query builder and the pipeline builder send nothing on their own: `.build()` gives you a built query. Pass it to `runtime.query(...)` for rows, or to `runtime.execute(...)` for a write that returns no rows. Get `runtime` from `db.runtime()` once and reuse it. ### PostgreSQL @@ -29,7 +29,7 @@ const plan = db.sql.public.user.select('id', 'email').where((f, fns) => fns.eq(f const rows = await runtime.query(plan); ``` -`User` is the model name and `user` is the table name. The ORM client compares on the column, `u.kind.eq(...)`. The SQL query builder compares through `fns`, `fns.eq(f.kind, ...)`. +`User` is the model name and `user` is the table name. The ORM client compares on the column, `u.kind.eq(...)`, while the SQL query builder compares through `fns`, `fns.eq(f.kind, ...)`. ### MongoDB @@ -40,7 +40,7 @@ const plan = db.query.from('users').match((f) => f.role.eq('admin')).build(); const docs = await runtime.query(plan); ``` -`db.runtime()` returns a promise on MongoDB, so `await` it. `users` is the collection name, and both `db.orm` and `db.query.from()` are keyed by it. The ORM client's `where()` takes an object of field values. The pipeline builder's `match()` takes a callback that compares on the field, `f.role.eq(...)`. Two different things are named `query`: `db.query` builds a pipeline, and `runtime.query(...)` runs a query that has already been built. +`db.runtime()` returns a promise on MongoDB, so `await` it. `users` is the collection name, and both `db.orm` and `db.query.from()` are keyed by it. The ORM client's `where()` takes an object of field values, while the pipeline builder's `match()` takes a callback that compares on the field, `f.role.eq(...)`. Two different things are named `query`: `db.query` builds a pipeline, and `runtime.query(...)` runs a query that has already been built. }>Every ORM client method, with PostgreSQL and MongoDB behavior documented side by side. diff --git a/apps/docs/content/docs/orm/reference/meta.json b/apps/docs/content/docs/orm/reference/meta.json index 7ff96f8cc77..6f0774462df 100644 --- a/apps/docs/content/docs/orm/reference/meta.json +++ b/apps/docs/content/docs/orm/reference/meta.json @@ -7,7 +7,6 @@ "pipeline-builder", "raw-queries", "transactions-and-runtime", - "supported-databases", "error-reference" ] } diff --git a/apps/docs/content/docs/orm/reference/orm-client.mdx b/apps/docs/content/docs/orm/reference/orm-client.mdx index e8e3fc26037..102e35ffcbe 100644 --- a/apps/docs/content/docs/orm/reference/orm-client.mdx +++ b/apps/docs/content/docs/orm/reference/orm-client.mdx @@ -12,7 +12,7 @@ This page assumes your tables already exist. [Coming from Prisma ORM 7](/orm/com ## Example schema -All examples on this page run against the schema below, except the grouped-aggregate ones, which use a separate `Customer` / `Order` schema shown under [Grouped aggregates](#grouped-aggregates). `contract.prisma` replaces `schema.prisma`. This page calls it the contract. Running `npx prisma contract emit` compiles it into two generated files: `contract.json`, read at run time, and `contract.d.ts`, read by TypeScript. You import both, as the examples below do. Run `npx prisma contract emit` again after every change to `contract.prisma`. A new project keeps all three files in `src/prisma/`. +All examples on this page run against the schema below, except the grouped-aggregate ones, which use a separate `Customer` / `Order` schema shown under [Grouped aggregates](#grouped-aggregates). `contract.prisma` replaces `schema.prisma`, and this page calls it the contract. Running `npx prisma contract emit` compiles it into two generated files: `contract.json`, read at run time, and `contract.d.ts`, read by TypeScript. You import both, as the examples below do. Run `npx prisma contract emit` again after every change to `contract.prisma`. A new project keeps all three files in `src/prisma/`.
@@ -199,7 +199,7 @@ Some parts of the schema the examples rely on: ## Setting up the client -Create a client with `postgres(...)` or `mongo(...)`, then read and write your models through `db.orm`. The same `db` also has a query builder, `db.sql` on PostgreSQL and `db.query` on MongoDB, plus `db.raw` for raw queries. This page covers none of those three. See [Advanced queries](/orm/fundamentals/advanced-queries). Create `db` once per process, and close it with `await db.close()` when the process shuts down. The two databases have different entry points, and they name the models differently. +Create a client with `postgres(...)` or `mongo(...)`, then read and write your models through `db.orm`. The same `db` also has a query builder, `db.sql` on PostgreSQL and `db.query` on MongoDB, plus `db.raw` for raw queries. This page covers none of those three: see [Advanced queries](/orm/fundamentals/advanced-queries). Create `db` once per process, and close it with `await db.close()` when the process shuts down. The two databases have different entry points, and they name the models differently. ### PostgreSQL @@ -245,7 +245,7 @@ const users = await db.orm.users.all(); ## Query-building methods -These methods narrow a query. Each returns a collection. On this page, a collection is the query object you chain more methods on, such as `db.orm.public.User` or `db.orm.public.User.where(...)`. When this page says "a MongoDB collection", it means that query object. MongoDB itself calls its store of documents a collection too, and the name is the same one. You run the query with a [read method](#read-terminals) such as `all()` or `first()`. MongoDB supports fewer of these methods than PostgreSQL, and each method's Remarks say which databases it works on. +These methods narrow a query, and each returns a collection. On this page, a collection is the query object you chain more methods on, such as `db.orm.public.User` or `db.orm.public.User.where(...)`. When this page says "a MongoDB collection", it means that query object. MongoDB itself calls its store of documents a collection too, and the name is the same one. You run the query with a [read method](#read-terminals) such as `all()` or `first()`. MongoDB supports fewer of these methods than PostgreSQL, and each method's Remarks say which databases it works on. Some examples import a filter helper you can use on its own. On PostgreSQL those come from `@prisma/orm-postgres/orm-client`, on MongoDB from `@prisma/orm-mongo/query-ast/execution`. See [Filter conditions and operators](#filter-conditions-and-operators). @@ -408,7 +408,7 @@ For a task-oriented guide to loading related records, see [Relations and joins]( ### Refinements, aggregates, and combine -On PostgreSQL, the callback you pass to `include()` receives the related rows as a collection. This page calls that callback a refinement, which is the word in the heading above. You can filter, order, and paginate the related rows in it. You can also reduce them to a single value, or use `combine()` to return several results at once. To count or sum a whole model instead of a relation, see [Grouped aggregates](#grouped-aggregates), where `aggregate((agg) => ({ n: agg.count() }))` counts every row. +On PostgreSQL, the callback you pass to `include()` receives the related rows as a collection. This page calls that callback a refinement, which is the word in the heading above. You can filter, order, and paginate the related rows in it, and you can also reduce them to a single value, or use `combine()` to return several results at once. To count or sum a whole model instead of a relation, see [Grouped aggregates](#grouped-aggregates), where `aggregate((agg) => ({ n: agg.count() }))` counts every row. #### Remarks @@ -493,7 +493,7 @@ const users = await db.orm.public.User.include('posts', (posts) => ``` :::note[`min()` and `max()` over a date column] -`min('createdAt')` over a `DateTime` column compiles. It returns a `Temporal.Instant`, the standard JavaScript object for a point in time. Not every JavaScript engine has `Temporal` yet. Check with `typeof Temporal === 'undefined'`, and if yours does not, install `temporal-polyfill` and add `import 'temporal-polyfill/full/global'`. +`min('createdAt')` over a `DateTime` column compiles and returns a `Temporal.Instant`, the standard JavaScript object for a point in time. Not every JavaScript engine has `Temporal` yet. Check with `typeof Temporal === 'undefined'`, and if yours does not, install `temporal-polyfill` and add `import 'temporal-polyfill/full/global'`. `sum('createdAt')` and `avg('createdAt')` do not compile. ::: @@ -747,9 +747,9 @@ const tutorials = await db.orm.posts.variant('Tutorial').all(); ## Read methods [#read-terminals] -A read method runs the query and gives you the rows. `all()` and `first()` are available on both databases. `aggregate()` and `groupBy()` are PostgreSQL only and are documented under [Grouped aggregates](#grouped-aggregates). +A read method runs the query and gives you the rows. `all()` and `first()` are available on both databases, while `aggregate()` and `groupBy()` are PostgreSQL only and are documented under [Grouped aggregates](#grouped-aggregates). -There is no `count()` method on either database. On PostgreSQL, count with `aggregate((a) => ({ n: a.count() }))`. On MongoDB, counting takes two steps. `db.query`, the [pipeline builder](/orm/fundamentals/advanced-queries#mongodb-pipeline-builder), builds the pipeline, and `runtime.query(...)` runs it: +There is no `count()` method on either database, so on PostgreSQL, count with `aggregate((a) => ({ n: a.count() }))`. On MongoDB, counting takes two steps: `db.query`, the [pipeline builder](/orm/fundamentals/advanced-queries#mongodb-pipeline-builder), builds the pipeline, and `runtime.query(...)` runs it. ```typescript const built = db.query.from('posts').count('total').build(); @@ -912,14 +912,14 @@ const features = await tasks.Task.features().all(); ## Write methods [#mutation-terminals] -A write method changes the database. `create`, `createAll`, `createAndCount`, `update`, `updateAll`, `updateAndCount`, `delete`, `deleteAll`, `deleteAndCount`, and `upsert` are available on both databases. The differences between the two are listed under each method. +A write method changes the database. `create`, `createAll`, `createAndCount`, `update`, `updateAll`, `updateAndCount`, `delete`, `deleteAll`, `deleteAndCount`, and `upsert` are available on both databases, and the differences between the two are listed under each method. For Prisma ORM 7 users: `createMany` is `createAll()` or `createAndCount()`, `updateMany` is `updateAll()` or `updateAndCount()`, `deleteMany` is `deleteAll()` or `deleteAndCount()`, and `count` is [`aggregate()`](#grouped-aggregates) on PostgreSQL, or the two steps shown under [Read methods](#read-terminals) on MongoDB. There is no `skipDuplicates` option. To catch one of the errors named below, wrap the call in `try` and `catch`, then compare `error.code` with the string given here. For a task-oriented walkthrough, see [Writing data](/orm/fundamentals/writing-data). On MongoDB, a write method rejects a chain that already has `orderBy()`, `limit()`, or `offset()` on it, and every write except `updateAll()` and `deleteAll()` also rejects `include()`. Both throw an error whose `code` is `ORM.OPERATION_UNSUPPORTED`. For grouping several writes into one unit, see [Transactions](/orm/fundamentals/transactions). :::warning[Filter before you update or delete] -Always call `where()` before an update or a delete. On MongoDB the six update and delete methods and `upsert()` check for a filter and throw an error whose `code` is `ORM.WHERE_MISSING`, with a message naming the method you called, such as `updateAll() requires a .where() filter. Call .where() before .updateAll()`. On PostgreSQL, these methods do not compile without a `where()`. Nothing checks at run time. If a call without a filter does reach the database, `update()` and `delete()` change a single row and you cannot predict which one, and `updateAll()` and `deleteAll()` change every row. +Always call `where()` before an update or a delete. On MongoDB the six update and delete methods and `upsert()` check for a filter and throw an error whose `code` is `ORM.WHERE_MISSING`, with a message naming the method you called, such as `updateAll() requires a .where() filter. Call .where() before .updateAll()`. On PostgreSQL, these methods do not compile without a `where()`, but nothing checks at run time. If a call without a filter does reach the database, `update()` and `delete()` change a single row and you cannot predict which one, and `updateAll()` and `deleteAll()` change every row. ::: The examples below use `aliceId`, `bobId`, `carolId`, `postId`, `tagId`, `tutorialId`, and `userId` for the ids of rows that already exist. Substitute your own. @@ -1433,7 +1433,7 @@ For Prisma ORM 7 users, `upsert()` replaces the `where` argument with `conflictO ## Grouped aggregates -These methods exist only on PostgreSQL collections. `aggregate()` treats every row the query matches as one group. `groupBy().aggregate()` aggregates per group, and `having()` filters those groups. A MongoDB model's collection has none of them. To aggregate on MongoDB, use the pipeline builder, which is Prisma ORM's API for MongoDB's aggregation pipeline. It is described in [MongoDB: Pipeline builder](/orm/fundamentals/advanced-queries#mongodb-pipeline-builder). +These methods exist only on PostgreSQL collections. `aggregate()` treats every row the query matches as one group, while `groupBy().aggregate()` aggregates per group and `having()` filters those groups. A MongoDB model's collection has none of them, so to aggregate on MongoDB, use the pipeline builder, which is Prisma ORM's API for MongoDB's aggregation pipeline and is described in [MongoDB: Pipeline builder](/orm/fundamentals/advanced-queries#mongodb-pipeline-builder). The examples below use a separate `Customer` / `Order` schema, where `Order.amount` is a numeric column: @@ -1487,7 +1487,7 @@ Compute aggregates over the rows the query matches. `aggregate()` runs the query | `Int`, `BigInt` | `number \| null` | `number \| null` | `sumBigInt()`, `avgDecimal()` | | `Decimal` | `string \| null` | `string \| null` | `avgDecimal()` | -Use `sumBigInt()` when the total can pass `Number.MAX_SAFE_INTEGER`. A `sum()` whose total passes it throws an error whose `code` is `RUNTIME.DECODE_FAILED`, thrown when the query result is read. Use `avgDecimal()` when you need the exact mean as a string, such as `'10.3333333333333333'`. Over a `Decimal` column `sum()` and `avg()` are already exact, and `sumBigInt()` is not available. +Use `sumBigInt()` when the total can pass `Number.MAX_SAFE_INTEGER`, because a `sum()` whose total passes it throws an error whose `code` is `RUNTIME.DECODE_FAILED`, thrown when the query result is read. Use `avgDecimal()` when you need the exact mean as a string, such as `'10.3333333333333333'`. Over a `Decimal` column `sum()` and `avg()` are already exact, and `sumBigInt()` is not available. #### Options @@ -1654,7 +1654,7 @@ const bigSpenders = await db.orm.public.Order.groupBy('customerId') ## Filter conditions and operators -A condition says which rows match. You write one inside `where()`, and inside a relation filter such as `u.posts.some(...)`. If you call `where()` more than once, the conditions are ANDed. One call can take the object form and the next the callback form. PostgreSQL and MongoDB write conditions differently. +A condition says which rows match. You write one inside `where()`, and inside a relation filter such as `u.posts.some(...)`. If you call `where()` more than once, the conditions are ANDed, and one call can take the object form and the next the callback form. PostgreSQL and MongoDB write conditions differently. - **PostgreSQL** compares a field with a method on that field, such as `u.email.eq(...)`, inside a callback. You name the callback's argument yourself, so the `u` and `p` below are only names. To combine conditions, import `and`, `or`, `not`, and `all` from `@prisma/orm-postgres/orm-client`. - **MongoDB** has no callback form. `where()` takes either the shorthand object or a filter such as `MongoFieldFilter.eq('name', 'Alice')`. The full list is under [`MongoFieldFilter`](#mongofieldfilter). Import `MongoFieldFilter`, `MongoAndExpr`, and `MongoOrExpr` from `@prisma/orm-mongo/query-ast/execution`. To combine conditions, use `.and()`, `.not()`, and `MongoOrExpr.of([...])`, described under [Combinators](#combinators). @@ -1917,7 +1917,7 @@ import { MongoFieldFilter } from '@prisma/orm-mongo/query-ast/execution'; const usersInSf = await db.orm.users.where(MongoFieldFilter.eq('address.city', 'San Francisco')).all(); ``` -`MongoFieldFilter.eq` is the recommended form. The object form needs a cast, and the cast drops type checking for every key in that object: +`MongoFieldFilter.eq` is the recommended form, because the object form needs a cast, and the cast drops type checking for every key in that object: ```typescript const usersInSf = await db.orm.users @@ -1926,12 +1926,12 @@ const usersInSf = await db.orm.users ``` ## Field update operations -On MongoDB, `update()`, `updateAll()`, `updateAndCount()`, and `upsert()` take a callback in place of a data object. The callback gets one argument, the update builder, written `u` in these examples, and returns an array of the operations you want. `updateAndCount()` takes the callback in the same place as `update()`. `upsert()` takes it as its `update` key, beside `create`. PostgreSQL has no callback form: pass an object to [`update()`](#update) instead. +On MongoDB, `update()`, `updateAll()`, `updateAndCount()`, and `upsert()` take a callback in place of a data object. The callback gets one argument, the update builder, written `u` in these examples, and returns an array of the operations you want. `updateAndCount()` takes the callback in the same place as `update()`, while `upsert()` takes it as its `update` key, beside `create`. PostgreSQL has no callback form: pass an object to [`update()`](#update) instead. - For a top-level field, read it off the update builder as a property: `u.bio.set('Hello')`. - For a field inside an embedded object, call the update builder as a function with the dot path: `u('address.city').set('San Francisco')`. The `upsert()` callback cannot use a dot path. Given one, `upsert()` throws an error whose `code` is `ORM.OPERATION_UNSUPPORTED`. Set the whole embedded object on its top-level field instead, as in `u.address.set({ street: '1 Market St', city: 'San Francisco', zip: '94105', country: 'US' })`. Give every field of the embedded object, including the optional ones. -All four methods require `.where()`. Without one they throw an error whose `code` is `ORM.WHERE_MISSING`. `.where({})` does not satisfy that rule, because an empty object adds no condition. To change every document, pass a filter that matches them all, such as `MongoFieldFilter.isNotNull('_id')`. A `_id` filter takes the hex string that a read gives you back, as the examples below show. On `upsert()`, `.where()` picks the document to update, exactly as on `update()`, and the full example is under [`upsert()`](#upsert). `update()` returns the updated document, or `null` when nothing matched. `updateAll()` returns an [`AsyncIterableResult`](#asynciterableresult) of the changed documents. [`updateAndCount()`](#updateandcount) exists on both databases, and it returns the number of changed documents. +All four methods require `.where()`, and without one they throw an error whose `code` is `ORM.WHERE_MISSING`. `.where({})` does not satisfy that rule, because an empty object adds no condition. To change every document, pass a filter that matches them all, such as `MongoFieldFilter.isNotNull('_id')`. A `_id` filter takes the hex string that a read gives you back, as the examples below show. On `upsert()`, `.where()` picks the document to update, exactly as on `update()`, and the full example is under [`upsert()`](#upsert). `update()` returns the updated document, or `null` when nothing matched, and `updateAll()` returns an [`AsyncIterableResult`](#asynciterableresult) of the changed documents. [`updateAndCount()`](#updateandcount) exists on both databases, and it returns the number of changed documents. ### Available operations (MongoDB) @@ -1950,7 +1950,7 @@ These examples use the MongoDB accessors from [Setting up the client](#setting-u ##### `set()` (MongoDB) -Return as many operations as you want. They are applied together, in one write. +Return as many operations as you want, and they are applied together, in one write. ```typescript const updated = await db.orm.users @@ -2044,7 +2044,7 @@ The methods `all()` / `createAll()` / `updateAll()` / `deleteAll()` return an `A - `await` the result (or call `.toArray()`) to get every row in one array. - Loop over it with `for await ... of` to take rows one at a time. -`createAll()`, `updateAll()`, and `deleteAll()` exist on PostgreSQL as well as MongoDB. [Write methods](#mutation-terminals) documents what each one takes, with examples. If you need the type by name, import it from `@prisma/orm-postgres/components/runtime` on PostgreSQL, or from `@prisma/orm-mongo/components/runtime` on MongoDB. +`createAll()`, `updateAll()`, and `deleteAll()` exist on PostgreSQL as well as MongoDB, and [Write methods](#mutation-terminals) documents what each one takes, with examples. If you need the type by name, import it from `@prisma/orm-postgres/components/runtime` on PostgreSQL, or from `@prisma/orm-mongo/components/runtime` on MongoDB. #### Single consumption and mode switching @@ -2053,7 +2053,7 @@ Each result is read once, one way: - Re-`await`ing (or calling `.toArray()` on) a result you already awaited is **safe**. It returns the same array. - Looping a result a second time with `for await` throws an error whose `code` is `RUNTIME.ITERATOR_CONSUMED`. So does switching from one way to the other, such as awaiting a result and then looping it. The message contains `already been consumed`. To recognise the error, import `isRuntimeError` from `@prisma/orm-postgres/components/runtime` on PostgreSQL, or from `@prisma/orm-mongo/components/runtime` on MongoDB, and check `error.code`. -PostgreSQL and MongoDB behave the same here. The example below uses the PostgreSQL accessor. On MongoDB the same methods are on `db.orm.users`, see [Setting up the client](#setting-up-the-client). `all()` returns the result object straight away, and the `await` is what runs the query, so the example holds the result in a variable. +PostgreSQL and MongoDB behave the same here, and the example below uses the PostgreSQL accessor. On MongoDB the same methods are on `db.orm.users`, see [Setting up the client](#setting-up-the-client). `all()` returns the result object straight away, and the `await` is what runs the query, so the example holds the result in a variable. ```typescript import { isRuntimeError } from '@prisma/orm-postgres/components/runtime'; diff --git a/apps/docs/content/docs/orm/reference/pipeline-builder.mdx b/apps/docs/content/docs/orm/reference/pipeline-builder.mdx index af9175d0d3d..3d28460be8e 100644 --- a/apps/docs/content/docs/orm/reference/pipeline-builder.mdx +++ b/apps/docs/content/docs/orm/reference/pipeline-builder.mdx @@ -8,7 +8,7 @@ metaDescription: 'Reference for the Prisma ORM MongoDB pipeline builder''s stage The pipeline builder gives you a typed way to build MongoDB aggregation pipelines. You reach it through `db.query`, chain aggregation stages onto a starting collection, and finish the chain with the call that builds the query. It is the MongoDB counterpart to the [SQL query builder](/orm/reference/sql-query-builder): a lower-level API for the queries the [ORM client](/orm/reference/orm-client) doesn't express. -Everything the builder produces becomes a MongoDB aggregation pipeline. There is no `find()` or `distinct()` here. Use `db.orm` or [`rawCommand()`](#rawcommand) for those. To fetch a single document, filter and then `limit(1)`. Use the ORM client (`db.orm`) for everyday reads and writes across models and relations. Reach for the pipeline builder when you need to group documents, to join another collection and keep transforming the result, or to write the result into a collection (`$out` and `$merge`). This page documents every stage, accumulator, expression helper, and write method. +Everything the builder produces becomes a MongoDB aggregation pipeline. There is no `find()` or `distinct()` here, so use `db.orm` or [`rawCommand()`](#rawcommand) for those. To fetch a single document, filter and then `limit(1)`. Use the ORM client (`db.orm`) for everyday reads and writes across models and relations. Reach for the pipeline builder when you need to group documents, to join another collection and keep transforming the result, or to write the result into a collection (`$out` and `$merge`). This page documents every stage, accumulator, expression helper, and write method. :::note[This is the MongoDB pipeline builder] The pipeline builder targets MongoDB only. PostgreSQL has no pipeline builder: use the [SQL query builder](/orm/reference/sql-query-builder) for PostgreSQL joins and aggregates, and the [ORM client](/orm/reference/orm-client) for everyday queries on either database. @@ -79,7 +79,7 @@ model Tutorial { ## Entry points -Every pipeline starts with `db.query.from('posts')`, naming the collection to aggregate over. It ends with a call that turns the chain into a query you can run. Between them you chain stages. +Every pipeline starts with `db.query.from('posts')`, naming the collection to aggregate over. It ends with a call that turns the chain into a query you can run, and between them you chain stages. `db` is the client you create with `mongo(...)`, whose `dbName` option is the name of the MongoDB database to use. See [Transactions and runtime](/orm/reference/transactions-and-runtime#mongooptions) for every option and for how to create the client. Three Prisma ORM import paths appear on this page: `@prisma/orm-mongo/runtime` holds the `mongo()` client, `@prisma/orm-mongo/query-builder` holds `mongoQuery`, `fn`, `acc`, and `expr`, and `@prisma/orm-mongo/query-ast/execution` holds the `Mongo*Stage` classes, `MongoAggFieldRef`, and `MongoFieldFilter`. Every example holds the built query in a variable named `plan`. @@ -203,7 +203,7 @@ const recent = await runtime.query(plan); ``` :::warning[`_id` equality filters do not work through the pipeline builder] -Filtering by `_id` equality inside `match()` **never matches any document**. Neither a hex string (`f._id.eq('507f...')`) nor an `ObjectId` matches. There is no supported way to filter by `_id` equality through the typed pipeline builder. To fetch one document by its id, use the [ORM client](/orm/reference/orm-client), which converts the hex string for you. The pipeline builder does not convert it: +Filtering by `_id` equality inside `match()` **never matches any document**: neither a hex string (`f._id.eq('507f...')`) nor an `ObjectId` matches. There is no supported way to filter by `_id` equality through the typed pipeline builder. To fetch one document by its id, use the [ORM client](/orm/reference/orm-client), which converts the hex string for you, while the pipeline builder does not convert it: ```typescript const postId = '6650f1c2a1b2c3d4e5f60003'; @@ -833,7 +833,7 @@ const [{ firstTitle, lastTitle }] = await runtime.query(plan); ##### `push()` and `addToSet()` -Both take one expression: `group((f) => ({ _id: null, allTitles: acc.push(f.title), distinctKinds: acc.addToSet(f.kind) }))`. `allTitles` holds one entry per document. `distinctKinds` holds each value once. +Both take one expression: `group((f) => ({ _id: null, allTitles: acc.push(f.title), distinctKinds: acc.addToSet(f.kind) }))`. `allTitles` holds one entry per document, while `distinctKinds` holds each value once. ##### `firstN()` (an `N`-variant) @@ -851,7 +851,7 @@ const [{ firstTwo, newestTitle }] = await runtime.query(plan); ## Expression helpers -Expression helpers (`fn.*`) build the computed values used inside stages like `addFields()`, `project()`, `group()` accumulators, and `match()` (via `expr()`). Inside a stage, write `f.title` or `fn.toUpper(...)`, never `'hello'` or `3` on their own. Wrap a constant in `fn.literal('hello')`. The update operators on the [write methods](#write-methods) and the `MongoFieldFilter` methods work the other way round. They take ordinary JavaScript values, so write `f.bio.set('hello')`, not `f.bio.set(fn.literal('hello'))`. Import the helpers from `@prisma/orm-mongo/query-builder`: +Expression helpers (`fn.*`) build the computed values used inside stages like `addFields()`, `project()`, `group()` accumulators, and `match()` (via `expr()`). Inside a stage, write `f.title` or `fn.toUpper(...)`, never `'hello'` or `3` on their own, so wrap a constant in `fn.literal('hello')`. The update operators on the [write methods](#write-methods) and the `MongoFieldFilter` methods work the other way round: they take ordinary JavaScript values, so write `f.bio.set('hello')`, not `f.bio.set(fn.literal('hello'))`. Import the helpers from `@prisma/orm-mongo/query-builder`: ```typescript import { fn } from '@prisma/orm-mongo/query-builder'; @@ -1063,7 +1063,7 @@ const [insertManyResult] = await runtime.query(manyPlan); ##### `upsertOne()` on the root (filter, then updater) -`upsertOne(filterCallback, updaterCallback)`. The first callback returns the filter. The second returns the array of update operations. +`upsertOne(filterCallback, updaterCallback)`. The first callback returns the filter, and the second returns the array of update operations. ```typescript const plan = db.query.from('users') @@ -1113,7 +1113,7 @@ const [result] = await runtime.query(plan); ##### `findOneAndUpdate()` and `returnDocument` -`findOneAndUpdate()` returns the matched document. Its second argument is optional. That argument takes `returnDocument`, which controls which version you get back: `'before'` returns the document before the update, `'after'` returns it after. The default is `'after'`. The same argument takes `upsert`, which defaults to `false` and inserts a document when nothing matches. +`findOneAndUpdate()` returns the matched document. Its second argument is optional and takes `returnDocument`, which controls which version you get back: `'before'` returns the document before the update, `'after'` returns it after. The default is `'after'`. The same argument takes `upsert`, which defaults to `false` and inserts a document when nothing matches. ```typescript const plan = db.query.from('users').match((f) => f.email.eq('alice@example.com')) @@ -1123,7 +1123,7 @@ const [beforeDoc] = await runtime.query(plan); ``` :::warning[The option is `returnDocument`, not `returnNewDocument`] -Use `returnDocument: 'before' | 'after'`. `returnNewDocument` is not a valid option name. TypeScript rejects it and suggests `returnDocument`. If a cast gets it past TypeScript, the unknown key is ignored and you silently get the default `'after'`. +Use `returnDocument: 'before' | 'after'`. `returnNewDocument` is not a valid option name, so TypeScript rejects it and suggests `returnDocument`. If a cast gets it past TypeScript, the unknown key is ignored and you silently get the default `'after'`. ::: ##### `findOneAndDelete()` diff --git a/apps/docs/content/docs/orm/reference/raw-queries.mdx b/apps/docs/content/docs/orm/reference/raw-queries.mdx index 251a6205bb4..30e9c402311 100644 --- a/apps/docs/content/docs/orm/reference/raw-queries.mdx +++ b/apps/docs/content/docs/orm/reference/raw-queries.mdx @@ -19,7 +19,7 @@ A **raw fragment** is a piece of SQL you put inside a builder query. Write it as A **whole raw statement** is a complete SQL statement written with `db.raw.sql`. If it returns rows, end it with `.returnsRow(spec).build()`, where the spec is a column list [defined below](#the-client-level-dbrawsql-tag), and pass the result to `runtime.query(...)`. If it does not, end it with `.affectedCount().build()` and pass the result to `runtime.execute(...)`. :::warning[Only `.returnsRow()` converts values for you] -A type id such as `pg/text@1` tells Prisma ORM what JavaScript type a column should become. The property is named `codecId`. `.returnsRow(spec)` names a type id for every column of a whole `db.raw.sql` statement, so you get real JavaScript values back. A fragment's `.returns(...)` does not convert anything. It only tells TypeScript what type to expect, so the value is whatever the `pg` package, the PostgreSQL driver underneath, gives you. For a `text` or an `integer` column that is already a JavaScript string or number. A `numeric` column arrives as a string. A date column gets no conversion by Prisma ORM, so you get the `pg` package's own value for that column. +A type id such as `pg/text@1` tells Prisma ORM what JavaScript type a column should become, and the property is named `codecId`. `.returnsRow(spec)` names a type id for every column of a whole `db.raw.sql` statement, so you get real JavaScript values back. A fragment's `.returns(...)` does not convert anything: it only tells TypeScript what type to expect, so the value is whatever the `pg` package, the PostgreSQL driver underneath, gives you. For a `text` or an `integer` column that is already a JavaScript string or number, while a `numeric` column arrives as a string. A date column gets no conversion by Prisma ORM, so you get the `pg` package's own value for that column. ::: The examples in this section run against the `user` / `post` schema from the SQL query builder page. See its [example schema](/orm/reference/sql-query-builder#example-schema) for the full model definitions. As on that page, create the client with `postgres(...)` and reach tables through `db.sql.public`, which is keyed by table name (the model name with a lowercase first letter unless the model sets the `@@map` attribute, which renames a table). `db.runtime()` gives you the connection that runs a built query. @@ -47,7 +47,7 @@ const rows = await runtime.query(plan); // rows === [{ id: aliceId, upperEmail: 'ALICE@EXAMPLE.COM' }] ``` -`.returns(...)` only tells TypeScript what to expect. It does not cast the value in SQL and it does not change how the `pg` package reads it. It takes a type id string, or an object of the form `{ codecId, nullable }`. See [Binding a bare value with `param()`](#binding-a-bare-value-with-param) for the type ids you are most likely to write, and the SQL query builder's [`fns.raw` and `.returns()`](/orm/reference/sql-query-builder#fnsraw-and-returns) for the full treatment. +`.returns(...)` only tells TypeScript what to expect: it does not cast the value in SQL and it does not change how the `pg` package reads it. It takes a type id string, or an object of the form `{ codecId, nullable }`. See [Binding a bare value with `param()`](#binding-a-bare-value-with-param) for the type ids you are most likely to write, and the SQL query builder's [`fns.raw` and `.returns()`](/orm/reference/sql-query-builder#fnsraw-and-returns) for the full treatment. ### `fns.raw` as a `where()` predicate @@ -133,7 +133,7 @@ const plan = db.sql.public.user const rows = await runtime.query(plan); ``` -Use `.returnsRow(spec)` to run a whole statement and get converted rows back. The spec names the columns of every returned row. Each entry is either a column from your contract or a type id. A column such as `db.sql.public.user.columns.id` brings its own conversion, nullability, and TypeScript type. Write a type id for a column your contract has no match for, and the object form `{ codecId: 'pg/text@1', nullable: true }` when that column can be `NULL`, as a `LEFT JOIN` column can. If a returned row has no column your spec names, the query throws an error whose `code` is `RUNTIME.RAW_ROW_COLUMN_MISSING`. A failing statement throws a `SqlQueryError` whose `sqlState` is the PostgreSQL error code for the failure, such as `23505` for a unique-constraint violation. +Use `.returnsRow(spec)` to run a whole statement and get converted rows back. The spec names the columns of every returned row, and each entry is either a column from your contract or a type id. A column such as `db.sql.public.user.columns.id` brings its own conversion, nullability, and TypeScript type. Write a type id for a column your contract has no match for, and the object form `{ codecId: 'pg/text@1', nullable: true }` when that column can be `NULL`, as a `LEFT JOIN` column can. If a returned row has no column your spec names, the query throws an error whose `code` is `RUNTIME.RAW_ROW_COLUMN_MISSING`. A failing statement throws a `SqlQueryError` whose `sqlState` is the PostgreSQL error code for the failure, such as `23505` for a unique-constraint violation. ```ts const user = db.sql.public.user; @@ -154,7 +154,7 @@ const rows = await runtime.query(plan); // row.email is a string; row.postCount is a bigint ``` -Use `.affectedCount()` for a statement that returns no rows. It takes no spec. Pass the built query to `runtime.execute(...)`, which resolves to `{ affectedRows }`, the number of rows the statement changed. You can also run a built raw query inside a transaction: [`db.transaction()`](/orm/reference/transactions-and-runtime#dbtransactioncallback) gives you a `tx`, and `tx.query(...)` and `tx.execute(...)` take the same built queries as `runtime.query(...)` and `runtime.execute(...)` do. +Use `.affectedCount()`, which takes no spec, for a statement that returns no rows. Pass the built query to `runtime.execute(...)`, which resolves to `{ affectedRows }`, the number of rows the statement changed. You can also run a built raw query inside a transaction: [`db.transaction()`](/orm/reference/transactions-and-runtime#dbtransactioncallback) gives you a `tx`, and `tx.query(...)` and `tx.execute(...)` take the same built queries as `runtime.query(...)` and `runtime.execute(...)` do. ```ts const plan = db.raw.sql` @@ -167,7 +167,7 @@ const stats = await runtime.execute(plan); // stats.affectedRows ``` -You can nest a `.returnsRow()` query inside another. That is how you write a subquery or a CTE, which is a `WITH` query. Interpolate it before `.build()`, and call `.build()` on the outer one only. Its `.returns` property holds its declared columns, so `outer.returnsRow({ postCount: inner.returns.postCount })` reuses a declaration. You cannot nest a statement that ends with `.affectedCount()`, because it produces no rows. You can nest an `UPDATE ... RETURNING`, because it does. +You can nest a `.returnsRow()` query inside another: that is how you write a subquery or a CTE, which is a `WITH` query. Interpolate it before `.build()`, and call `.build()` on the outer one only. Its `.returns` property holds its declared columns, so `outer.returnsRow({ postCount: inner.returns.postCount })` reuses a declaration. You cannot nest a statement that ends with `.affectedCount()`, because it produces no rows. You can nest an `UPDATE ... RETURNING`, because it does. :::note[Three names look alike] `.returns(typeId)` is a method on a fragment, `.returnsRow(spec)` is a method on a statement, and `.returns` is a property on the object `.returnsRow()` gives you. @@ -204,7 +204,7 @@ To bind any value whose type is not in the list, wrap it in [`param(value, { cod `db.raw.collection(collectionName)` returns an object with nine methods for running MongoDB commands directly against a collection. The collection name is the one your contract declares (`'users'`, `'posts'`), the same name the ORM client uses (`db.orm.users`). MongoDB has no schemas, so there is no `public` segment here and none on `db.orm`. There is no raw `find()`, so use `aggregate()` with a `$match` stage. MongoDB has no transactions in Prisma ORM, so a raw command cannot run inside one. See [Transactions (MongoDB)](/orm/reference/transactions-and-runtime#transactions-mongodb). -Each of these nine methods builds a command. Call `.build()` on it, then run every one of them with `runtime.query(...)`, including the writes. The raw collection methods all run through `query()`. A write returns a one-element array, so destructure it with `const [result] = ...`. +Each of these nine methods builds a command: call `.build()` on it, then run every one of them with `runtime.query(...)`, including the writes. The raw collection methods all run through `query()`. A write returns a one-element array, so destructure it with `const [result] = ...`. :::warning[MongoDB raw results are native BSON values] Nothing on the MongoDB side of this page is converted for you. An `_id` comes back as it is stored: one that MongoDB generated is an `ObjectId` instance from the `mongodb` package, the MongoDB driver underneath, not a hex string. Compare one with `String(row._id) === aliceId`. Raw filters take native BSON values too, so build ids with that package's `ObjectId` class. The write methods give you the counts and ids under the `mongodb` package's own key names, `{ insertedId }`, `{ matchedCount, modifiedCount, upsertedCount, upsertedId }`, and `{ deletedCount }`, and you read those keys directly. @@ -250,7 +250,7 @@ const [manyResult] = await runtime.query(manyPlan); ### `updateOne()` and `updateMany()` -Update one or every matching document. The first argument is a raw filter document. The second is either an update document such as `{ $set: ... }` or an aggregation pipeline, which is an array of stages. Both return `{ matchedCount, modifiedCount, upsertedCount, upsertedId }`. +Update one or every matching document. The first argument is a raw filter document, and the second is either an update document such as `{ $set: ... }` or an aggregation pipeline, which is an array of stages. Both return `{ matchedCount, modifiedCount, upsertedCount, upsertedId }`. #### Update document form @@ -346,7 +346,7 @@ const rows = (await runtime.query(plan)) as { total: number }[]; // [{ total: 2 }] ``` -`rawCommand()` is the way out when the typed pipeline builder can't express a query, such as filtering by `_id` equality. The command you pass is sent unchanged, so a real `ObjectId` in a pipeline document matches. `aggregate()` on `db.raw.collection()` handles `_id` filtering too, and it gives you a row type. Use `rawCommand()` when you are already working in `db.query` and need one raw command there. For the full treatment see the pipeline builder's [`rawCommand()`](/orm/reference/pipeline-builder#rawcommand) section. +`rawCommand()` is the way out when the typed pipeline builder can't express a query, such as filtering by `_id` equality. The command you pass is sent unchanged, so a real `ObjectId` in a pipeline document matches. `aggregate()` on `db.raw.collection()` handles `_id` filtering too, and it gives you a row type, so use `rawCommand()` when you are already working in `db.query` and need one raw command there. For the full treatment see the pipeline builder's [`rawCommand()`](/orm/reference/pipeline-builder#rawcommand) section. ```ts const plan = db.query.rawCommand(new RawAggregateCommand('posts', [{ $match: { _id: new ObjectId('6539a1f2c3d4e5f60718293c') } }])); diff --git a/apps/docs/content/docs/orm/reference/sql-query-builder.mdx b/apps/docs/content/docs/orm/reference/sql-query-builder.mdx index 138417b8dfb..d9e2f4d488c 100644 --- a/apps/docs/content/docs/orm/reference/sql-query-builder.mdx +++ b/apps/docs/content/docs/orm/reference/sql-query-builder.mdx @@ -130,7 +130,7 @@ model Feature { In Prisma ORM 8 your `schema.prisma` is called `contract.prisma`. [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7) gets you from a Prisma ORM 7 project to that file, and has the rest of the setup steps. Install the client with `npm install @prisma/orm-postgres`. Run `npx prisma contract emit` to write `contract.json` and `contract.d.ts` beside `contract.prisma`. The examples below assume your own file is in `src/prisma/`, next to those two files, and they write the type import path as `./contract.d`, exactly like that. -You build queries from the client's `sql` property. Create a Postgres client with `postgres(...)`, then reach tables through `db.sql.public`. `public` is the PostgreSQL schema your tables are in. A model goes in `public` unless your contract puts it in a `namespace` block. Table accessors are keyed by table name, not model name. A table name is the model name with a lowercase first letter unless the model sets `@@map`, so the `User` model mapped to `user` is reached as `db.sql.public.user`, and the model for the `Post`/`Tag` join table, mapped to `post_tag`, is reached as `db.sql.public.post_tag`. Column names are the field names unless a field sets `@map`. +You build queries from the client's `sql` property. Create a Postgres client with `postgres(...)`, then reach tables through `db.sql.public`. `public` is the PostgreSQL schema your tables are in, and a model goes in `public` unless your contract puts it in a `namespace` block. Table accessors are keyed by table name, not model name. A table name is the model name with a lowercase first letter unless the model sets `@@map`, so the `User` model mapped to `user` is reached as `db.sql.public.user`, and the model for the `Post`/`Tag` join table, mapped to `post_tag`, is reached as `db.sql.public.post_tag`. Column names are the field names unless a field sets `@map`. ### The `db.sql` builder [#the-dbsql-facet] @@ -150,7 +150,7 @@ const plan = db.sql.public.user.select('id', 'email').build(); const users = await runtime.query(plan); ``` -SQLite works the same way. Create the client with `sqlite(...)` from `@prisma/orm-sqlite/runtime`. SQLite has no schemas, so there is no `public` in the path and the same table is `db.sql.user`. Of the SELECT methods on this page, `lateralJoin()`, `outerLateralJoin()`, and `distinctOn()` are not available on SQLite. Every other one is. The rest of this page shows PostgreSQL and writes the root as `db.sql.public`. +SQLite works the same way: create the client with `sqlite(...)` from `@prisma/orm-sqlite/runtime`. SQLite has no schemas, so there is no `public` in the path and the same table is `db.sql.user`. Of the SELECT methods on this page, `lateralJoin()`, `outerLateralJoin()`, and `distinctOn()` are not available on SQLite, but every other one is. The rest of this page shows PostgreSQL and writes the root as `db.sql.public`. ### Table access @@ -167,11 +167,11 @@ Call `.as(alias)` on a `SELECT` chain before `build()` to use that query as a su ## SELECT queries -These methods build and refine a `SELECT`. They chain, and you resolve the query with [`build()`](#build) followed by `runtime.query(...)`. Chain the methods in any order, with two exceptions. A join must come before any call that names the joined table's columns. The columns you pass to `distinctOn()` must be the first sort keys in `orderBy()`. `SelectQuery` in the tables below is the type they return. +These methods build and refine a `SELECT`. They chain, and you resolve the query with [`build()`](#build) followed by `runtime.query(...)`. Chain the methods in any order, with two exceptions: a join must come before any call that names the joined table's columns, and the columns you pass to `distinctOn()` must be the first sort keys in `orderBy()`. `SelectQuery` in the tables below is the type they return. -The set of column names a query can use is its scope. It starts as the columns of the table you began with, and every join adds the joined table's columns to it. +The set of column names a query can use is its scope: it starts as the columns of the table you began with, and every join adds the joined table's columns to it. -The `where()`, `select()`, `orderBy()`, `update()`, and other callback forms receive two arguments. The first is `f`, which has one property per column in scope. After a join, every column is on `f` under its table name, such as `f.user.email` or `f.post.id`. A column name that only one table has is also on `f` directly. The second is `fns`, an object of [expression helpers](#expressions-and-functions) such as `fns.eq()` and `fns.raw`. For an enum column you pass the value stored in the database. That is the string after `=`, so `High = "high"` is matched by `fns.eq(f.priority, 'high')`. A member with no `=`, such as `admin` in `user_type`, is matched by its own name, `fns.eq(f.kind, 'admin')`. +The `where()`, `select()`, `orderBy()`, `update()`, and other callback forms receive two arguments. The first is `f`, which has one property per column in scope. After a join, every column is on `f` under its table name, such as `f.user.email` or `f.post.id`. A column name that only one table has is also on `f` directly. The second is `fns`, an object of [expression helpers](#expressions-and-functions) such as `fns.eq()` and `fns.raw`. For an enum column you pass the value stored in the database, which is the string after `=`, so `High = "high"` is matched by `fns.eq(f.priority, 'high')`. A member with no `=`, such as `admin` in `user_type`, is matched by its own name, `fns.eq(f.kind, 'admin')`. ### `select()` @@ -187,7 +187,7 @@ Reduce each row to a chosen set of columns or computed expressions. | Aliased expression | `select(alias, (f, fns) => expr)` | Add one computed column under `alias`. | | Object of expressions | `select((f, fns) => ({ alias: expr, ... }))` | Add several computed columns at once. The row type is inferred from the object's shape. | -Every form adds to what the query already returns. Calling `select()` a second time never replaces the first call's columns, whichever form you use. To return fewer columns, start a new query instead. +Every form adds to what the query already returns: calling `select()` a second time never replaces the first call's columns, whichever form you use. To return fewer columns, start a new query instead. A computed expression's result type comes from `.returns(...)` on `fns.raw`, or from the `fns.*` function you called. Every `fns.raw` fragment needs `.returns()`. A type id is `pg/` plus the PostgreSQL type name plus a version, always `@1` today, such as `pg/int4@1` or `pg/uuid@1`. A few use a longer name, such as `pg/timestamptz-temporal@1` for `DateTime`. The common ones are `pg/text@1`, `pg/int4@1`, `pg/int8@1`, `pg/float8@1`, `pg/bool@1`, `pg/uuid@1`, and `pg/timestamptz-temporal@1`. The full list is what your contract uses. Read any column's id from `db.sql.public.
.columns..codecId`, and see [Binding a bare value with `param()`](/orm/reference/raw-queries#binding-a-bare-value-with-param) for which JavaScript type becomes which id. See [`fns.raw` and `.returns()`](#fnsraw-and-returns). @@ -382,7 +382,7 @@ Sort the result set by a column or a computed expression, in a direction you cho | `key` | Column name (`string`) or `(f, fns) => Expression` | Yes | The column or computed value to sort by. | | `options.direction` | `'asc' \| 'desc'` | No | Sort direction. | -TypeScript also accepts a `nulls` option. It does nothing, and no `NULLS FIRST` or `NULLS LAST` reaches the SQL. To put nulls last, sort by a computed value first, then by the column: +TypeScript also accepts a `nulls` option, but it does nothing, and no `NULLS FIRST` or `NULLS LAST` reaches the SQL. To put nulls last, sort by a computed value first, then by the column: ```ts db.sql.public.user @@ -504,7 +504,7 @@ const rows = await runtime.query(plan); ### Subquery via `.as()` -Call `.as(alias)` on a `SELECT` chain before `build()` to use it as a subquery. Pass the result to any join method as the `other` argument. `SELECT` queries and [grouped queries](#grouped-queries) have `.as()`. Queries started with `insert()`, `update()`, or `delete()` do not, so you cannot join against the rows they return. +Call `.as(alias)` on a `SELECT` chain before `build()` to use it as a subquery. Pass the result to any join method as the `other` argument. `SELECT` queries and [grouped queries](#grouped-queries) have `.as()`, but queries started with `insert()`, `update()`, or `delete()` do not, so you cannot join against the rows they return. #### Options @@ -617,7 +617,7 @@ Annotate with the type PostgreSQL computes, not the type you want. Here `pg/nume .groupBy((f, fns) => fns.raw`EXTRACT(YEAR FROM ${f.placedAt})`.returns('pg/numeric@1')) ``` -A name that is neither a column of the table you started from nor an alias you selected throws an error whose `code` is `ORM.COLUMN_UNKNOWN`, at the moment you call `groupBy()`. If you select a column you did not group or aggregate, the query fails when it runs, not when you build it. That failure comes back as PostgreSQL's own error, not a Prisma ORM code. +A name that is neither a column of the table you started from nor an alias you selected throws an error whose `code` is `ORM.COLUMN_UNKNOWN`, at the moment you call `groupBy()`. If you select a column you did not group or aggregate, the query fails when it runs, not when you build it, and that failure comes back as PostgreSQL's own error, not a Prisma ORM code. ### `having()` @@ -691,7 +691,7 @@ const rows = await runtime.query(plan); // the single highest-spending customer ## Mutations :::warning[An update or delete with no `where()` hits every row] -`where()` is optional on `update()` and `delete()`. Leave it out and every row in the table is changed, with no warning. +`where()` is optional on `update()` and `delete()`: leave it out and every row in the table is changed, with no warning. ::: Mutations start from a table with `insert()`, `update()`, or `delete()`, and end with [`build()`](#build). Add [`returning()`](#returning) to get the affected rows back. Run a mutation with no `returning()` using `runtime.execute(plan)`, which resolves to `{ affectedRows }`. With `returning()`, run it with `runtime.query(plan)`. The examples below go back to this page's example models, so they use `db.sql.public.tag` and `db.sql.public.user` again. @@ -772,7 +772,7 @@ Update matched rows. Set columns with a values object, or derive new values from #### Options -`update()` accepts a values object or an expression callback. You cannot mix the two forms in one call. +`update()` accepts a values object or an expression callback, but you cannot mix the two forms in one call. | Form | Signature | Description | |---|---|---| @@ -910,7 +910,7 @@ const authors = db.sql.public.user .build(); ``` -Which aggregate functions exist depends on your database. PostgreSQL gives you these eight. The contract type is the one you write in `contract.prisma`, and the PostgreSQL type is in brackets: +Which aggregate functions exist depends on your database, and PostgreSQL gives you these eight. The contract type is the one you write in `contract.prisma`, and the PostgreSQL type is in brackets: | Function | Returns | |---|---| @@ -927,7 +927,7 @@ On PostgreSQL you also get `ilike(expr, pattern)` for text columns, as in `fns.i ### `fns.raw` and `.returns()` -Write a raw SQL fragment as a tagged template. Interpolate columns, typed expressions, and bare JavaScript values with `${...}`. For a bare value, the builder picks the type id from its JavaScript type. [Binding a bare value with `param()`](/orm/reference/raw-queries#binding-a-bare-value-with-param) lists which JavaScript type becomes which type id, and shows how to choose a different one with `param(...)`. Call `.returns(typeId)` to declare the fragment's result type. +Write a raw SQL fragment as a tagged template, and interpolate columns, typed expressions, and bare JavaScript values with `${...}`. For a bare value, the builder picks the type id from its JavaScript type. [Binding a bare value with `param()`](/orm/reference/raw-queries#binding-a-bare-value-with-param) lists which JavaScript type becomes which type id, and shows how to choose a different one with `param(...)`. Call `.returns(typeId)` to declare the fragment's result type. #### Options @@ -1003,6 +1003,6 @@ const rows = await runtime.query(plan); // Row[] ### Streaming vs. collecting -`await runtime.query(plan)` collects every row into an array. This is the common case and what every example on this page uses. You can also loop the same result with `for await` to handle one row at a time. On PostgreSQL every row is loaded before the loop starts, so `for await` saves nothing there. Use `await`. +`await runtime.query(plan)` collects every row into an array, which is the common case and what every example on this page uses. You can also loop the same result with `for await` to handle one row at a time. On PostgreSQL every row is loaded before the loop starts, so `for await` saves nothing there. Use `await`. Keep the result in a variable, as in `const result = runtime.query(plan)`, and you can `await result` as many times as you like. You cannot mix `await` and `for await` on the same result, and you cannot `for await` it twice. Either one throws an error whose `code` is `RUNTIME.ITERATOR_CONSUMED`. See [`AsyncIterableResult`](/orm/reference/orm-client#asynciterableresult). diff --git a/apps/docs/content/docs/orm/reference/supported-databases.mdx b/apps/docs/content/docs/orm/reference/supported-databases.mdx deleted file mode 100644 index 6cbbd7f50a6..00000000000 --- a/apps/docs/content/docs/orm/reference/supported-databases.mdx +++ /dev/null @@ -1,63 +0,0 @@ ---- -title: Supported databases -description: 'Prisma ORM supports PostgreSQL and MongoDB. This page lists what each database gets, which managed providers work, and what is planned next.' -url: /orm/reference/supported-databases -metaTitle: Databases supported by Prisma ORM -metaDescription: 'The databases Prisma ORM supports today (PostgreSQL and MongoDB), which query surfaces, extensions, and middleware each one gets, the managed providers that work, and the databases planned next.' ---- - -Prisma ORM ships first-class support for **PostgreSQL** and **MongoDB**. PostgreSQL is the primary target and the one every extension targets today. **SQLite** ships as an experimental package, and **MySQL** is planned next. - -Each database is its own package, and that package is an extension like any other: it plugs a database family and target into the Prisma ORM core. Install the one that matches your database and it brings the config, runtime, contract authoring, and migration tooling with it: - -| Database | Package | Install | -| ---------- | -------------------- | ---------------------------------- | -| PostgreSQL | `@prisma/orm-postgres` | `npm install @prisma/orm-postgres` | -| MongoDB | `@prisma/orm-mongo` | `npm install @prisma/orm-mongo` | -| SQLite (experimental) | `@prisma/orm-sqlite` | `npm install @prisma/orm-sqlite` | - -`npx create-prisma@latest` asks which database you use and installs the right package for you. See the [PostgreSQL quickstart](/prisma-orm/quickstart/postgresql) or the [MongoDB quickstart](/prisma-orm/quickstart/mongodb). - -## What each database supports - -The ORM client works the same on both databases. The lower-level surfaces follow the database's own query model: - -| Capability | PostgreSQL | MongoDB | -| --------------------------------------------------------------------- | ---------- | ------- | -| [ORM client](/orm/reference/orm-client) | Yes | Yes | -| [SQL query builder](/orm/reference/sql-query-builder) | Yes | No | -| [Pipeline builder](/orm/reference/pipeline-builder) | No | Yes | -| [Raw queries](/orm/reference/raw-queries) | Raw SQL | Raw commands | -| [Transactions](/orm/fundamentals/transactions) | Yes | Not yet | -| [Migrations](/orm/migrations/how-migrations-work) | Yes | Yes | -| [Extensions](/orm/extensions) (pgvector, PostGIS, ParadeDB, and more) | Yes | Not yet | -| [Middleware](/orm/middleware/how-middleware-works) | Yes | Yes, except `beforeCompile` | - -`db.transaction()` exists on the PostgreSQL client only. Prisma ORM does not support MongoDB transactions yet; for multi-document writes, [use the MongoDB driver's session API](/orm/fundamentals/transactions#transactions-on-mongodb) alongside the client. - -Middleware hooks are the same on both databases apart from `beforeCompile`, which rewrites the SQL AST and so exists only on PostgreSQL. The built-in `lints` and `budgets` middleware inspect SQL and run on PostgreSQL; the `cache` middleware runs on both. See [which databases a middleware runs on](/orm/middleware/how-middleware-works#which-databases-a-middleware-runs-on). - -Every extension in the [directory](https://www.prisma.io/extensions) lists the databases it targets. Today every extension that adds column types, operators, or index types targets PostgreSQL; the database packages and the `cache` middleware are the entries that list MongoDB. - -## Managed and hosted databases - -Prisma ORM connects with a standard connection string, so any host that gives you one works. - -| Provider | Database | Notes | -| ------------------------------------------ | ---------- | ------------------------------------------------------------------------------------------------- | -| [Prisma Postgres](/postgres) | PostgreSQL | The default in `create-prisma@latest`. Provisioned from the CLI, with connection pooling built in. | -| Supabase | PostgreSQL | The [Supabase extension](https://www.prisma.io/extensions/supabase) adds the auth and storage tables and role-bound clients. | -| Neon, Railway, Render, AWS RDS, Cloud SQL, self-hosted | PostgreSQL | Any PostgreSQL server. Extensions such as pgvector and PostGIS need the server-side extension to be installable, which most managed providers include. | -| MongoDB Atlas, self-hosted MongoDB | MongoDB | Any MongoDB deployment reachable with a `mongodb://` or `mongodb+srv://` URL. | - -## Planned - -SQLite is published as an experimental package ([`@prisma/orm-sqlite`](https://www.prisma.io/extensions/sqlite)) without a quickstart yet; MySQL is planned next and is not part of the Prisma ORM release candidate. To stay on a supported path for those databases today, use [Prisma ORM 7](/orm/v7/reference/supported-databases), which supports PostgreSQL, MySQL, MariaDB, SQLite, SQL Server, CockroachDB, and MongoDB. - -Because a database package is an extension, a community package can add a database Prisma does not ship. If you publish one, [submit it to the directory](https://www.prisma.io/extensions/submit) and name the database it adds in the submission. - -## See also - -- [Prisma ORM 7 supported databases](/orm/v7/reference/supported-databases) for the full version matrix of the current generally available release -- [Prisma ORM overview](/orm) for what changed and how to get started -- [Extensions](/orm/extensions) for the capabilities you can add to PostgreSQL diff --git a/apps/docs/content/docs/orm/reference/transactions-and-runtime.mdx b/apps/docs/content/docs/orm/reference/transactions-and-runtime.mdx index 25c63cc7747..b71d63d397f 100644 --- a/apps/docs/content/docs/orm/reference/transactions-and-runtime.mdx +++ b/apps/docs/content/docs/orm/reference/transactions-and-runtime.mdx @@ -6,9 +6,9 @@ metaTitle: Prisma ORM transactions and runtime reference metaDescription: 'Reference for the Prisma ORM client lifecycle, transactions, prepared statements, and execution options.' --- -Prisma ORM 8 renames `schema.prisma` to `contract.prisma`. Run `npx prisma contract emit`. It writes two files next to it: `contract.json`, which your app imports, and `contract.d.ts`, which gives you the TypeScript types. A new project keeps all three files in `src/prisma/`. Every client you create needs `contract.json`. The query you came for, `prisma.user.findMany({ where })`, is now `db.orm.public.User.where(...).all()`. [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7) lists the packages to install, the commands to run, and the rest of the renamed calls. +Prisma ORM 8 renames `schema.prisma` to `contract.prisma`. Run `npx prisma contract emit`, which writes two files next to it: `contract.json`, which your app imports, and `contract.d.ts`, which gives you the TypeScript types. A new project keeps all three files in `src/prisma/`. Every client you create needs `contract.json`. The query you came for, `prisma.user.findMany({ where })`, is now `db.orm.public.User.where(...).all()`. [Coming from Prisma ORM 7](/orm/coming-from-prisma-orm-7) lists the packages to install, the commands to run, and the rest of the renamed calls. -`db` is the client that `postgres(...)` or `mongo(...)` returns. `db.runtime()` gives you the object that actually runs queries. That object is called the runtime. +`db` is the client that `postgres(...)` or `mongo(...)` returns. `db.runtime()` gives you the object that actually runs queries, and that object is called the runtime. Create, connect, and close the client, run transactions, prepare statements, and set per-query execution options, on PostgreSQL and MongoDB. For a task-oriented walkthrough, see the Fundamentals guide to [Transactions](/orm/fundamentals/transactions). For the ways to query inside a transaction, see the [ORM client reference](/orm/reference/orm-client), the [SQL query builder reference](/orm/reference/sql-query-builder), and the [raw queries reference](/orm/reference/raw-queries). @@ -71,7 +71,7 @@ import contractJson from './contract.json' with { type: 'json' }; const db = postgres({ contractJson, url: process.env.DATABASE_URL! }); ``` -These examples assume the file is beside `contract.json` in `src/prisma/`. From anywhere else, change the relative paths. Write `./contract.d` exactly like that, with no extension. The line `import contractJson from './contract.json' with { type: 'json' }` is what `prisma orm init` writes. It needs Node.js 22.18 or newer, and these `tsconfig.json` settings: `resolveJsonModule: true`, `module: "preserve"`, and `moduleResolution: "bundler"`. TypeScript types every `process.env` value as possibly undefined, so the examples write `!` after it, the same way `prisma orm init` does. +These examples assume the file is beside `contract.json` in `src/prisma/`, so from anywhere else, change the relative paths. Write `./contract.d` exactly like that, with no extension. The line `import contractJson from './contract.json' with { type: 'json' }`, which `prisma orm init` writes, needs Node.js 22.18 or newer, and these `tsconfig.json` settings: `resolveJsonModule: true`, `module: "preserve"`, and `moduleResolution: "bundler"`. TypeScript types every `process.env` value as possibly undefined, so the examples write `!` after it, the same way `prisma orm init` does. ##### Bind to an existing `pg` pool @@ -420,7 +420,7 @@ await db.transaction(async (tx) => { ``` :::warning[An escaped `AsyncIterableResult` throws after the transaction ends] -Collect the rows inside the callback by awaiting the result there, and return the array instead. A result is tied to the transaction connection. If you return one from the callback and read it after the transaction has committed, it rejects with an error whose `code` is `RUNTIME.TRANSACTION_CLOSED`, before yielding any row. +Collect the rows inside the callback by awaiting the result there, and return the array instead. A result is tied to the transaction connection, so if you return one from the callback and read it after the transaction has committed, it rejects with an error whose `code` is `RUNTIME.TRANSACTION_CLOSED`, before yielding any row. ```typescript const escaped = await db.transaction(async (tx) => { @@ -560,7 +560,7 @@ await connection.destroy(); ## Transactions (MongoDB) :::note[MongoDB transactions are not available in Prisma ORM yet] -There is no `db.transaction(...)` on the MongoDB client, and the MongoDB runtime has no `connection()`, `prepare()`, or `telemetry()`: it has `query`, `execute`, and `close` and nothing else. Single-document write operations are atomic on their own. Multi-document transactions through Prisma ORM are planned, not shipped. To run one today, create your own `MongoClient`, pass it as the `mongoClient` option when you create the client, and group the writes in `session.withTransaction(...)`, as the [Fundamentals transactions guide](/orm/fundamentals/transactions#transactions-on-mongodb) shows. The reading rules under [`AsyncIterableResult`](#asynciterableresult) apply to MongoDB too. +There is no `db.transaction(...)` on the MongoDB client, and the MongoDB runtime has no `connection()`, `prepare()`, or `telemetry()`: it has `query`, `execute`, and `close` and nothing else. Single-document write operations are atomic on their own, while multi-document transactions through Prisma ORM are planned, not shipped. To run one today, create your own `MongoClient`, pass it as the `mongoClient` option when you create the client, and group the writes in `session.withTransaction(...)`, as the [Fundamentals transactions guide](/orm/fundamentals/transactions#transactions-on-mongodb) shows. The reading rules under [`AsyncIterableResult`](#asynciterableresult) apply to MongoDB too. ::: ## Prepared statements (PostgreSQL) @@ -772,6 +772,6 @@ const after = runtime.telemetry(); [`all()`](/orm/reference/orm-client#all), [`createAll()`](/orm/reference/orm-client#createall), and the runtime's `query(...)` return an `AsyncIterableResult`. `all()` and `query(...)` read rows, and `createAll()` writes rows and returns the ones it wrote. `await` the result to collect an array, or `for await` it to take rows one at a time. -Use `await`. Use `for await` only to handle rows as they arrive. On PostgreSQL it does not reduce memory, because every row is loaded first. +Use `await`, and use `for await` only to handle rows as they arrive. On PostgreSQL it does not reduce memory, because every row is loaded first. -Pick `await` or `for await` for a given result and do not mix the two. Calling `.toArray()` does the same thing as `await`. Re-`await`ing a result you already awaited is safe and returns the same array. Switching between `await` and `for await`, or looping a second time with `for await`, throws an error whose `code` is `RUNTIME.ITERATOR_CONSUMED`. For the full rules on reading a result, shared identically by PostgreSQL and MongoDB, see [`AsyncIterableResult`](/orm/reference/orm-client#asynciterableresult) in the ORM client reference. +Pick `await` or `for await` for a given result and do not mix the two. Calling `.toArray()` does the same thing as `await`. Re-`await`ing a result you already awaited is safe and returns the same array, but switching between `await` and `for await`, or looping a second time with `for await`, throws an error whose `code` is `RUNTIME.ITERATOR_CONSUMED`. For the full rules on reading a result, shared identically by PostgreSQL and MongoDB, see [`AsyncIterableResult`](/orm/reference/orm-client#asynciterableresult) in the ORM client reference. diff --git a/apps/docs/content/docs/(index)/prisma-orm/release-status.mdx b/apps/docs/content/docs/orm/release-status.mdx similarity index 83% rename from apps/docs/content/docs/(index)/prisma-orm/release-status.mdx rename to apps/docs/content/docs/orm/release-status.mdx index 3999ba99c94..52e36218a86 100644 --- a/apps/docs/content/docs/(index)/prisma-orm/release-status.mdx +++ b/apps/docs/content/docs/orm/release-status.mdx @@ -1,12 +1,12 @@ --- title: Release status description: 'Where Prisma ORM 8 stands today, what you get when you install it, and how to stay on Prisma ORM 7.' -url: /prisma-orm/release-status +url: /orm/release-status metaTitle: Prisma ORM release status metaDescription: 'Prisma ORM 8 is a release candidate, with general availability expected in October 2026. What npm installs today, how long Prisma ORM 7 is supported and Prisma ORM 6 receives security patches, and how to stay on version 7.' --- -Prisma ORM 8 is a **release candidate**. You can install it and build with it today. Until the final release, some details of the API may still change, so code you write now may need small edits later. Each release candidate fixes bugs found in the previous one. What Prisma ORM 8 changes, and why, is on the [Prisma ORM overview](/orm). +Prisma ORM 8 is a **release candidate**: you can install it and build with it today. Until the final release, some details of the API may still change, so code you write now may need small edits later. Each release candidate fixes bugs found in the previous one. What Prisma ORM 8 changes, and why, is on the [Prisma ORM overview](/orm). It needs Node.js 22.18 or newer (on the 24 line, 24.11 or newer; 24.0 to 24.10 do not work) and TypeScript 5.9 or newer. @@ -22,7 +22,7 @@ Prisma ORM 7 keeps receiving bug fixes and security updates for 18 months from t ## Prisma ORM 6 receives security patches until 19 November 2026 -Prisma ORM 6 receives security patches, but no other updates, for 12 months after the release of Prisma ORM 7. That period ends on 19 November 2026. Its documentation stays at [/orm/v6](/orm/v6). +Prisma ORM 6 receives security patches, but no other updates, for 12 months after the release of Prisma ORM 7, a period that ends on 19 November 2026. Its documentation stays at [/orm/v6](/orm/v6). ## What you get when you install today @@ -33,7 +33,7 @@ npm install --save-dev prisma npm install @prisma/orm-postgres ``` -For MongoDB the library is `@prisma/orm-mongo`. [Supported databases](/prisma-orm/supported-databases) lists every database and its status. +For MongoDB the library is `@prisma/orm-mongo`. [Supported databases](/orm/supported-databases) lists every database and its status. The two packages have different version numbers, and that is normal: they are released separately, and the command-line tool gets more frequent releases. Install both at `latest`; any two `latest` versions work together, and you do not pair the numbers yourself. @@ -45,11 +45,11 @@ The two packages have different version numbers, and that is normal: they are re Versions were checked on 14 September 2026; run `npm view prisma dist-tags`, `npm view @prisma/orm-postgres dist-tags`, and `npm view @prisma/orm-mongo dist-tags` for today's, since the command-line tool and the libraries are versioned separately. -`@prisma/client`, the library that Prisma ORM 7 apps import, stays at `7.10.0`. It belongs to Prisma ORM 7 and is not part of Prisma ORM 8. +`@prisma/client`, the library that Prisma ORM 7 apps import, stays at `7.10.0`, because it belongs to Prisma ORM 7 and is not part of Prisma ORM 8. :::warning[If you have a Prisma ORM 7 project] -`npm install prisma` and `npx prisma` now give you the Prisma ORM 8 command-line tool. It does not read `schema.prisma` and has no `generate`, `migrate dev`, or `db push` commands. If your project is on Prisma ORM 7, run `npm install --save-dev prisma@7` now, and lock the version in `package.json` as shown below. If version 8 is already installed, that same command downgrades it. To see what you have now, run `npm ls prisma @prisma/client`. +`npm install prisma` and `npx prisma` now give you the Prisma ORM 8 command-line tool, which does not read `schema.prisma` and has no `generate`, `migrate dev`, or `db push` commands. If your project is on Prisma ORM 7, run `npm install --save-dev prisma@7` now, and lock the version in `package.json` as shown below. If version 8 is already installed, that same command downgrades it. To see what you have now, run `npm ls prisma @prisma/client`. ::: diff --git a/apps/docs/content/docs/orm/supported-databases.mdx b/apps/docs/content/docs/orm/supported-databases.mdx new file mode 100644 index 00000000000..f2861c3eca4 --- /dev/null +++ b/apps/docs/content/docs/orm/supported-databases.mdx @@ -0,0 +1,78 @@ +--- +title: Supported databases +description: 'Which databases Prisma ORM 8 supports today, what each one gets, which hosted providers work, and which databases are coming.' +url: /orm/supported-databases +metaTitle: Databases supported by Prisma ORM 8 +metaDescription: 'PostgreSQL is a release candidate, MongoDB is in early access, and SQLite is an experimental package. What each database gets (query APIs, transactions, migrations, extensions, middleware), which managed providers work, and which databases are coming, with MySQL planned next.' +--- + +Prisma ORM 8 ships one library per database, and installing the one that matches yours brings the config, the client, contract authoring, and migrations with it. Today there are two you can build on, and an experimental third. + +| Database | Status | Library | Install | +| --- | --- | --- | --- | +| PostgreSQL, including Prisma Postgres | Release candidate | `@prisma/orm-postgres` | `npm install @prisma/orm-postgres` | +| MongoDB | Early access | `@prisma/orm-mongo` | `npm install @prisma/orm-mongo` | +| MySQL | Coming soon, planned next | | | +| MariaDB | Coming soon | | | +| SQLite | Experimental | `@prisma/orm-sqlite` | `npm install @prisma/orm-sqlite` | +| Microsoft SQL Server | Coming soon | | | +| CockroachDB | Coming soon | | | + +`npm create prisma@latest -- my-app` asks which database you use and installs the right library for you. See the [PostgreSQL quickstart](/prisma-orm/quickstart/postgresql) or the [MongoDB quickstart](/prisma-orm/quickstart/mongodb). + +## What the labels mean + +**Release candidate.** Install it and build with it today. Until the final release, some details of the API may still change, so code you write now may need small edits later. Each release candidate fixes bugs found in the last one. [Release status](/orm/release-status) has the dates and what `npm install` gives you. + +**Early access.** The library works and you can build with it, but some of what the PostgreSQL library does is not there yet. The table below and the reference pages say what works on MongoDB, and [what is not available yet](/orm/coming-from-prisma-orm-7#not-available-yet) lists the gaps. Report what you hit on [GitHub](https://github.com/prisma/orm/issues). + +**Experimental.** SQLite is published as an experimental package, [`@prisma/orm-sqlite`](https://www.prisma.io/extensions/sqlite), without a quickstart yet. `db.transaction(...)` works the same way as on PostgreSQL. + +**Coming soon.** There is no Prisma ORM 8 library for the database yet. Prisma ORM 7 supports it, and Prisma ORM 7 keeps receiving bug fixes and security updates for 18 months after Prisma ORM 8 reaches general availability. [How to stay on Prisma ORM 7](/orm/release-status#how-to-stay-on-prisma-orm-7) shows how to lock the version. + +## What each database supports + +The ORM client works the same on both databases. The lower-level query APIs follow the database's own query model. + +| Feature | PostgreSQL | MongoDB | +| --- | --- | --- | +| [ORM client](/orm/reference/orm-client) | Yes | Yes | +| [SQL query builder](/orm/reference/sql-query-builder) | Yes | No | +| [Pipeline builder](/orm/reference/pipeline-builder) | No | Yes | +| [Raw queries](/orm/reference/raw-queries) | Raw SQL | Raw commands | +| [Transactions](/orm/fundamentals/transactions) | Yes | Not yet | +| [Migrations](/orm/migrations/how-migrations-work) | Yes | Yes | +| [Extensions](/orm/extensions) (pgvector, PostGIS, ParadeDB, and more) | Yes | Not yet | +| [Middleware](/orm/middleware/how-middleware-works) | Yes | Yes, except `beforeCompile` | + +`db.transaction()` exists on the PostgreSQL client only, because Prisma ORM does not support MongoDB transactions yet. For multi-document writes on MongoDB, [use the MongoDB driver's session API](/orm/fundamentals/transactions#transactions-on-mongodb) alongside the client. + +Middleware hooks are the same on both databases apart from `beforeCompile`, which rewrites the SQL query before it runs and so exists only on PostgreSQL. The built-in `lints` and `budgets` middleware inspect SQL and run on PostgreSQL. The `cache` middleware runs on both. See [which databases a middleware runs on](/orm/middleware/how-middleware-works#which-databases-a-middleware-runs-on). + +Every extension in the [directory](https://www.prisma.io/extensions) lists the databases it targets. Today every extension that adds column types, operators, or index types targets PostgreSQL, and the only entries that list MongoDB are the database libraries and the `cache` middleware. + +## Managed and hosted databases + +Prisma ORM connects with a standard connection string, so any host that gives you one works. + +| Provider | Database | Notes | +| --- | --- | --- | +| [Prisma Postgres](/postgres) | PostgreSQL | The default in `npm create prisma@latest`. Provisioned from the CLI, with connection pooling built in. | +| Supabase | PostgreSQL | The [Supabase extension](https://www.prisma.io/extensions/supabase) adds the auth and storage tables and role-bound clients. | +| Neon, Railway, Render, AWS RDS, Cloud SQL, self-hosted | PostgreSQL | Any PostgreSQL server. Extensions such as pgvector and PostGIS need the server-side extension to be installable, which most managed providers include. | +| MongoDB Atlas, self-hosted MongoDB | MongoDB | Any MongoDB deployment reachable with a `mongodb://` or `mongodb+srv://` URL. | + +## Databases that are coming + +MySQL is planned next and is not part of the Prisma ORM release candidate. MariaDB, Microsoft SQL Server, and CockroachDB have no Prisma ORM 8 library yet either. To stay on a supported path for those databases today, use [Prisma ORM 7](/orm/v7/reference/supported-databases), which supports all of them. + +Add a 👍 to the GitHub issue for the database you need, or open one if there is none, because the number of reactions is one of the inputs to what gets built next. Organizations that need a database sooner can talk to [Prisma enterprise support](https://www.prisma.io/enterprise). + +Because a database library is an extension, a community package can add a database Prisma does not ship. If you publish one, [submit it to the directory](https://www.prisma.io/extensions/submit) and name the database it adds in the submission. + +## Next steps + +- The quickstart sets up a project on [PostgreSQL](/prisma-orm/quickstart/postgresql) or [MongoDB](/prisma-orm/quickstart/mongodb). +- [Release status](/orm/release-status) covers versions, dates, and staying on Prisma ORM 7. +- [Prisma ORM 7 supported databases](/orm/v7/reference/supported-databases) has the full matrix for the current generally available release. +- [Extensions](/orm/extensions) lists what you can add to PostgreSQL. diff --git a/apps/docs/next.config.mjs b/apps/docs/next.config.mjs index c31d4d18897..eb806cc2cc2 100644 --- a/apps/docs/next.config.mjs +++ b/apps/docs/next.config.mjs @@ -414,6 +414,12 @@ const config = { { source: "/guides/next", destination: "/guides", permanent: true }, { source: "/guides/next/:path*", destination: "/guides/:path*", permanent: true }, { source: "/llms/next.txt", destination: "/llms/orm.txt", permanent: true }, + { source: "/prisma-orm/release-status", destination: "/orm/release-status", permanent: true }, + { + source: "/prisma-orm/supported-databases", + destination: "/orm/supported-databases", + permanent: true, + }, // The CLI engine composes structured-error docsUrls as // / (path form); the pages anchor codes as #. { @@ -637,9 +643,11 @@ const config = { destination: "/orm/v7/reference/database-features", permanent: false, }, - // /orm/reference/supported-databases is a live Prisma 8 page again - // (content/docs/orm/reference/supported-databases.mdx), so it is not - // redirected to the Prisma 7 tree like its siblings. + { + source: "/orm/reference/supported-databases", + destination: "/orm/supported-databases", + permanent: true, + }, { source: "/orm/reference/system-requirements", destination: "/orm/v7/reference/system-requirements", diff --git a/apps/docs/scripts/audit-redirects.mjs b/apps/docs/scripts/audit-redirects.mjs index 7ef5522ab03..b9be1466806 100644 --- a/apps/docs/scripts/audit-redirects.mjs +++ b/apps/docs/scripts/audit-redirects.mjs @@ -4,11 +4,7 @@ import { fileURLToPath } from "node:url"; const cwd = path.dirname(fileURLToPath(import.meta.url)); const docsRoot = path.resolve(cwd, ".."); -const broadDestinations = new Set([ - "/docs", - "/docs/orm", - "/docs/orm/reference/supported-databases", -]); +const broadDestinations = new Set(["/docs", "/docs/orm", "/docs/orm/supported-databases"]); const acceptableBroadRedirects = new Set([ "/docs/orm/more/upgrade-guides/upgrading-versions/codemods -> /docs/guides/upgrade-prisma-orm/v7", "/docs/orm/accelerate/getting-started/connection-pooler/client-extensions -> /docs/postgres/database/connection-pooling", diff --git a/apps/docs/src/lib/version-metadata.test.ts b/apps/docs/src/lib/version-metadata.test.ts index a5c19cbca13..65c61af399f 100644 --- a/apps/docs/src/lib/version-metadata.test.ts +++ b/apps/docs/src/lib/version-metadata.test.ts @@ -27,7 +27,7 @@ test("returns null for latest and unversioned pages", () => { for (const url of [ "/", "/orm", - "/orm/reference/supported-databases", + "/orm/supported-databases", "/cli/commands", "/guides/nextjs", "/getting-started",