diff --git a/apps/docs/AGENTS.md b/apps/docs/AGENTS.md index 909e122554..cfd8a6bab7 100644 --- a/apps/docs/AGENTS.md +++ b/apps/docs/AGENTS.md @@ -35,4 +35,4 @@ Run from `apps/docs`: 2. `pnpm audit:redirects:strict` after adding redirects. 3. `pnpm lint:agent-ready` after touching sections, llms surfaces, or the OpenAPI explorer. 4. `pnpm types:check` and `pnpm build` for code changes. -5. `pnpm lint:versions` after pinning a Prisma package version. Every pin in the current docs must be the package's `latest` on npm; write "since `8.0.0-rc.10`" or use an "Added in" column for history. +5. `pnpm lint:versions` after pinning a Prisma package version. Every pin in the current docs must be the package's `latest` on npm, except `@prisma/cli-engine`, which must be the version that `prisma@latest` depends on; write "since `8.0.0-rc.10`" or use an "Added in" column for history. diff --git a/apps/docs/content/docs/guides/database/schema-changes.mdx b/apps/docs/content/docs/guides/database/schema-changes.mdx index 6109027312..3ba94c9cde 100644 --- a/apps/docs/content/docs/guides/database/schema-changes.mdx +++ b/apps/docs/content/docs/guides/database/schema-changes.mdx @@ -129,10 +129,10 @@ App space └─ marker 91e7f9f035806fa2789a4d726ef7724cad434fd6b00014d47ebf12d6e6bb784e ✔ Advanced ref "db" → 91e7f9f035806fa2789a4d726ef7724cad434fd6b00014d47ebf12d6e6bb784e -→ Check every space against the database: {bin} migration status +→ Check every space against the database: prisma migration status ``` -The `{bin}` in the hint is the CLI's placeholder for however you invoked it. Read it as `npx prisma migration status`. Every developer clones this repository, points `DATABASE_URL` in their `.env` at their own database, and runs `npx prisma db migrate` once to bring it to the same state. +Every developer clones this repository, points `DATABASE_URL` in their `.env` at their own database, and runs `npx prisma db migrate` once to bring it to the same state. ## 2. Incorporate team changes @@ -379,7 +379,7 @@ npx prisma migration status │↑ 20260910T1543_init ∅ → 91e7f9f 6 ops ✓ applied ○ ∅ -⚠ No migration path from the database state (91e7f9f03580) to the application's contract (1e5059c1976c). Run `{bin} migration plan --name ` to author one. +⚠ No migration path from the database state (91e7f9f03580) to the application's contract (1e5059c1976c). Run `prisma migration plan --name ` to author one. ``` Do not follow that hint literally. Without `--from`, the planner takes the `db` ref as its origin, and after `git checkout --ours` that ref is Ania's tip, so `migration plan --name merge_schema` writes only the half of the merge her branch is missing. Javier's database would still have no path to the top. Had the ref still pointed at `init`, the planner would have started a third branch from the divergence point instead, with a warning that it forks the graph. Neither is the merge. Do not delete either branch's migration directory to get out of this: Ania's and Javier's databases have already applied those migrations. Name the origin yourself, once per tip. @@ -485,7 +485,7 @@ npx prisma migration status │↑ 20260910T1543_init ∅ → 91e7f9f 6 ops ✓ applied ○ ∅ -⚠ 1 pending: run `{bin} db migrate --to 1e5059c1976c` +⚠ 1 pending: run `prisma db migrate --to 1e5059c1976c` ``` ```npm @@ -613,9 +613,9 @@ const tag = await db.orm.public.Tag.create({ tagName: "arcade", tagCategory: "ga ```text no-copy ✘ [MIGRATION.PLAN_ORIGIN_UNKNOWN] Cannot determine the plan origin: migrations exist but no origin is named why: Migrations exist on disk, but there is no `db` ref and no --from was given, so the plan origin would silently fall back to an empty database and the resulting migration would recreate everything the existing migrations already create. -→ Point the db ref at the origin contract: {bin} migration ref set db -→ Plan from an explicit origin: {bin} migration plan --from -→ Plan from an empty database deliberately: {bin} migration plan --from @empty +→ Point the db ref at the origin contract: prisma migration ref set db +→ Plan from an explicit origin: prisma migration plan --from +→ Plan from an empty database deliberately: prisma migration plan --from @empty ``` Pick the first or second. `--from @empty` over an existing history is only right for a deliberate rebuild. diff --git a/apps/docs/content/docs/guides/frameworks/solid-start.mdx b/apps/docs/content/docs/guides/frameworks/solid-start.mdx index d729892fb4..4234a3c642 100644 --- a/apps/docs/content/docs/guides/frameworks/solid-start.mdx +++ b/apps/docs/content/docs/guides/frameworks/solid-start.mdx @@ -398,7 +398,7 @@ check: enabled Skill Package Version Installed into prisma-8 @prisma/orm-postgres 8.0.0-rc.13 .claude/skills, .cursor/skills, .agents/skills, .devin/skills -prisma-composer-core-concepts @prisma/composer 0.23.0 .claude/skills, .cursor/skills, .agents/skills, .devin/skills +prisma-composer-core-concepts @prisma/composer 0.25.0 .claude/skills, .cursor/skills, .agents/skills, .devin/skills prisma-platform-core-concepts prisma 8.0.0-rc.19 .claude/skills, .cursor/skills, .agents/skills, .devin/skills ⚠ [INIT.CONFIG_KEPT] prisma.config.ts already exists, so init left it alone instead of writing the skills section. diff --git a/apps/docs/content/docs/guides/integrations/github-actions.mdx b/apps/docs/content/docs/guides/integrations/github-actions.mdx index 69d009a864..f5c8f15dc4 100644 --- a/apps/docs/content/docs/guides/integrations/github-actions.mdx +++ b/apps/docs/content/docs/guides/integrations/github-actions.mdx @@ -126,7 +126,7 @@ npx prisma contract emit ✔ Emitted contract.json and contract.d.ts storageHash: 0c3a18eb65d5a5027c444cf0626d843cdb41dfd5fe8e66d6fce8d2eab460c971 -executionHash: 796fa270d853489edb1c3e0d332d596412292127a259b856b495a498c752882a +executionHash: 4e88564095b5e0ba7904ffdba3c563ef916d87688c8c483c67055f71a9194f0f profileHash: 3916f444a8a17ad749191acf9e08dad97d1a327b88c2f1d45d12f240296aa8b2 ``` 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 c6cefed747..30be48a79e 100644 --- a/apps/docs/content/docs/orm/reference/sql-query-builder.mdx +++ b/apps/docs/content/docs/orm/reference/sql-query-builder.mdx @@ -385,15 +385,9 @@ 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. | +| `options.nulls` | `'first' \| 'last'` | No | Where rows with a null sort value go. Adds `NULLS FIRST` or `NULLS LAST` to the sort key. | -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.task - .select('id', 'description') - .orderBy((f, fns) => fns.raw`CASE WHEN ${f.description} IS NULL THEN 1 ELSE 0 END`.returns('pg/int4@1')) - .orderBy('description', { direction: 'asc' }); -``` +Without `options.nulls`, PostgreSQL puts nulls last for `'asc'` and first for `'desc'`. Any value other than `'first'` or `'last'` makes `orderBy()` throw an error whose `code` is `ORM.ARGUMENT_INVALID`. #### Return type @@ -410,6 +404,20 @@ const plan = db.sql.public.user.select('id', 'email').orderBy('email', { directi const rows = await runtime.query(plan); ``` +##### Put nulls last + +```ts +const plan = db.sql.public.task + .select('id', 'description') + .orderBy('description', { direction: 'desc', nulls: 'last' }) + .build(); +const rows = await runtime.query(plan); +``` + +```sql no-copy +SELECT "id" AS "id", "description" AS "description" FROM "public"."task" ORDER BY "description" DESC NULLS LAST +``` + ##### Sort by a computed value ```ts diff --git a/apps/docs/scripts/lint-versions.ts b/apps/docs/scripts/lint-versions.ts index 8bc3a70d9e..4fa980f9c6 100644 --- a/apps/docs/scripts/lint-versions.ts +++ b/apps/docs/scripts/lint-versions.ts @@ -2,7 +2,9 @@ /** * Every pinned Prisma ORM 8 version in the current docs must be the package's - * `latest` dist-tag on npm. A version mentioned as history ("since 8.0.0-rc.10", + * `latest` dist-tag on npm. `@prisma/cli-engine` is the exception: `prisma` + * installs the exact engine version it depends on, so the expected version is + * the one in the `dependencies` of `prisma@latest`. A version mentioned as history ("since 8.0.0-rc.10", * an "Added in" column) is allowed to be older, and so is a pin to an older * major line, such as a `prisma@6.x` install step for readers staying on 6. * @@ -16,13 +18,9 @@ import { fileURLToPath } from "node:url"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const DOCS_DIR = path.join(__dirname, "../content/docs"); -const TRACKED_PACKAGES = [ - "prisma", - "@prisma/orm-postgres", - "@prisma/orm-mongo", - "@prisma/cli-engine", - "@prisma/prisma7", -]; +const TRACKED_PACKAGES = ["prisma", "@prisma/orm-postgres", "@prisma/orm-mongo", "@prisma/prisma7"]; + +const PACKAGES_PINNED_BY_PRISMA = ["@prisma/cli-engine"]; /** Older releases keep their own content trees and pin their own versions. */ const VERSIONED_TREES = ["(index)/v7", "cli/v7", "guides/v7", "orm/v6", "orm/v7"]; @@ -55,12 +53,23 @@ type Violation = { expected: string; }; -async function fetchLatest(pkg: string): Promise { +type Manifest = { version: string; dependencies?: Record }; + +async function fetchLatestManifest(pkg: string): Promise { const response = await fetch(`https://registry.npmjs.org/${pkg}/latest`); if (!response.ok) { throw new Error(`npm registry returned ${response.status} for ${pkg}`); } - const { version } = (await response.json()) as { version: string }; + return (await response.json()) as Manifest; +} + +function versionPinnedByPrisma(prisma: Manifest, pkg: string): string { + const version = prisma.dependencies?.[pkg]; + if (!version || !/^\d+\.\d+\.\d+(?:-[\w.]+)?$/.test(version)) { + throw new Error( + `prisma@${prisma.version} does not depend on an exact version of ${pkg} (found ${version})`, + ); + } return version; } @@ -151,11 +160,21 @@ function lintFile(file: string, latest: Map): Violation[] { } async function main() { - const latest = new Map( - await Promise.all(TRACKED_PACKAGES.map(async (pkg) => [pkg, await fetchLatest(pkg)] as const)), + const manifests = new Map( + await Promise.all( + TRACKED_PACKAGES.map(async (pkg) => [pkg, await fetchLatestManifest(pkg)] as const), + ), ); + const latest = new Map([...manifests].map(([pkg, { version }]) => [pkg, version])); for (const [pkg, version] of latest) console.log(`${pkg}@${version}`); + const prisma = manifests.get("prisma")!; + for (const pkg of PACKAGES_PINNED_BY_PRISMA) { + const version = versionPinnedByPrisma(prisma, pkg); + latest.set(pkg, version); + console.log(`${pkg}@${version} (installed by prisma@${prisma.version})`); + } + const files = findMdxFiles(DOCS_DIR).filter((file) => !isVersionedTree(file)); const violations = files.flatMap((file) => lintFile(file, latest)); @@ -166,7 +185,7 @@ async function main() { console.error(`\n❌ ${violations.length} stale version reference(s):\n`); for (const { file, line, found, expected } of violations) { - console.error(`${file}:${line}: ${found} (latest is ${expected})`); + console.error(`${file}:${line}: ${found} (expected ${expected})`); } console.error( '\nBump the version, or mark it as history with a phrase like "since" or "before" or an "Added in" column.',