prisma v8.0.0-rc.2

v8.0.0-rc.2Pre-release
Added 4
  • Add countBigInt() operation returning a bigint
  • Add sumBigInt() operation returning a bigint
  • Add avgDecimal() operation returning an exact decimal string for PostgreSQL
  • Make CHECK constraints a declared part of the contract
Changed 5
  • Move config file from prisma-next.config.ts to prisma.config.ts with ORM config nested under an orm section
  • Return default aggregates count(), sum() over integer columns, and avg() over integer columns as JavaScript numbers instead of bigint or decimal strings
  • Make count() or integer sum() raise RUNTIME.DECODE_FAILED when value exceeds ±(2^53 − 1) instead of returning a rounded number
  • Make wide-integer codecs reject incorrect JavaScript types with RUNTIME.ENCODE_FAILED
  • Split runtime row queries from non-returning writes
Removed 1
  • Retire the prisma-next binary in favour of the unified prisma CLI

v8.0.0-rc.2

This release retires the prisma-next binary in favour of the unified prisma CLI, returns the default aggregates to plain JavaScript numbers with lossless variants beside them, makes CHECK constraints a declared part of the contract, and splits runtime row queries from non-returning writes. Almost every application will need to re-emit its contract and rename its config file, so read the breaking changes before upgrading.

Two upgrade recipes carry the mechanical translations for this hop: the user recipe and the extension-author recipe.

Breaking changes
  • This repository no longer publishes a CLI; the unified prisma CLI replaces it — nothing published ships a prisma-next bin anymore. @prisma/orm-toolchain exposes the orm command family at @prisma/orm-toolchain/cli and no binary, and the database facades forward no launcher. Install @prisma/cli (the prisma-cli distribution, published under next for the v8 line) and replace prisma-next <command> in package scripts and CI with the unified CLI. The config file moves with it: prisma-next.config.ts is deprecated in favour of prisma.config.ts, and the config value is now engine-shaped, with your existing ORM config nested under an orm section. Both the old filename and the flat shape still load, each printing a deprecation warning on stderr, so the rename and the rewrap can land separately. See the user recipe. (#30005)

    Before:

    // prisma-next.config.ts
    import { defineConfig } from '@prisma/orm-postgres/config';
    
    export default defineConfig({ contract: './contract.ts', output: './generated' });
    

    After:

    // prisma.config.ts
    import { defineConfig } from '@prisma/cli-engine';
    import { defineConfig as ormConfig } from '@prisma/orm-postgres/config';
    
    export default defineConfig({
      orm: ormConfig({ contract: './contract.ts', output: './generated' }),
    });
    
  • The default aggregates are JavaScript numbers again, with lossless variants beside themcount(), sum() over an integer column, and avg() over an integer column all return number. In 8.0.0-rc.1 they returned a bigint, a bigint or decimal string depending on the column's width, and a decimal string respectively. The lossless results moved to three new operations: countBigInt() returns a bigint, sumBigInt() returns a bigint, and avgDecimal() returns an exact decimal string (PostgreSQL only — SQLite has no decimal type and contributes none). A count() or integer sum() whose value passes ±(2^53 − 1) now raises RUNTIME.DECODE_FAILED rather than returning a rounded number, so move those calls to the BigInt variants where the magnitude is real. Unchanged: min/max, sum/avg over a float column, sum over Decimal, sum over UnboundedInt, and the ORM's having(...) operands. The SQL builder's comparison operands do move, because fns.gt(a, b) types both sides from one codec. The same PR also makes the wide-integer codecs refuse the wrong JavaScript type: a BigInt or UnboundedInt column rejects a number and a BigIntNumber column rejects a bigint, with RUNTIME.ENCODE_FAILED naming the type that arrived, where previously a number was accepted and stringified — which let a fractional value reach an integer column unremarked. See the user recipe. (#29930)

    Before:

    const { total } = await db.User.aggregate((a) => ({ total: a.count() }));
    total === 2n; // bigint
    
    const busy = await db.sql.public.user
      .groupBy('kind')
      .having((_f, fns) => fns.gt(fns.count(), 1n)); // bigint literal
    

    After:

    const { total } = await db.User.aggregate((a) => ({ total: a.count() }));
    total === 2; // number — countBigInt() returns the bigint
    
    const busy = await db.sql.public.user
      .groupBy('kind')
      .having((_f, fns) => fns.gt(fns.count(), 1)); // plain number literal
    
  • Which aggregate methods exist is now the contract's answer — the aggregate methods are no longer declared on the ORM and SQL-builder surfaces outright. Each surface is derived from the operation names in the emitted contract.d.ts's AggregateTypes block, so a target or extension can contribute an operation and it appears under its own name with no client change. PostgreSQL now contributes eight operations and SQLite seven. Re-emit your contract with the CLI's contract emit: against a contract with no AggregateTypes block — one authored in code with defineContract(...) and handed straight to the client, or emitted before 8.0.0-rc.1 — every aggregate surface resolves to AggregateOperationsUnavailable, an empty type, and each call becomes a compile error. What this release changes is compile-time only — the separate runtime guard introduced in 8.0.0-rc.1 still stands, rejecting an aggregate whose operation and input codec the composed target does not declare with ORM.AGGREGATE_UNSUPPORTED before the query runs. Separately, count(field) now renders COUNT(<column>) instead of accepting the argument and discarding it, so a call that got past the types — a @ts-expect-error, a count(x as never), or dynamic dispatch — now counts that field's non-null values rather than rows. See the user recipe and the extension-author recipe. (#29922)

  • CHECK constraints are declared in the contract, and introspection now sees all of them — the CHECK shape in contract.json changed from { name, column, valueSet } to { name, prefix, expression }, where expression is the raw SQL predicate and name is a content-addressed wire name (<prefix>_<8hex>, the convention indexes and RLS policies already use). An old-shape contract is rejected on read, so re-emitting is not optional. Three consequences to plan for. Your first migration plan after upgrading drops each old unsuffixed enum constraint and adds the wire-named one, which needs destructive to converge. Every list (many) column gains a declared element-non-null CHECK the planner previously created without declaring. And introspection stopped parsing predicates, so hand-written constraints earlier versions could not see are now visible — and an undeclared check is an extra that db verify --strict reports and a destructive-capable plan drops, so read the first plan for dropCheckConstraint operations naming constraints you wrote yourself, and declare each one you want to keep with @@check(expression: "…", map: "<physical name>"). Two API changes ride along: addCheckConstraint in committed migration files takes an expression instead of a column/values pair, and the typescriptContract options bag now requires createNamespace whenever it passes defaultControlPolicy. An enumType() whose codec is numeric now throws CONTRACT.ENUM_INVALID while the contract is being built rather than failing later at migrate time. See the user recipe and the extension-author recipe. (#29892)

    Before:

    this.addCheckConstraint({ schema, table, constraint, column: 'kind', values: ['admin', 'user'] });
    

    After:

    this.addCheckConstraint({ schema, table, constraint, expression: `"kind" IN ('admin', 'user')` });
    
  • Runtime row queries and non-returning writes are separate callsquery() streams rows and execute() resolves { affectedRows }, which is how a write now reports its affected count without a preceding SELECT. Classify each call site by the result it consumes rather than replacing every execute: a select, a returning write, or any plan whose rows are iterated, indexed, or decoded moves to query, while an insert, update, or delete that returns nothing stays on execute and reads affectedRows. Prepared row consumption moves from target.queryPrepared(prepared, params) to prepared.query(target, params). Runtime middleware splits the same way, into beforeQuery / interceptQuery / afterQuery and beforeExecute / interceptExecute / afterExecute with a shared beforeCompile; query interception returns { rows } and execute interception returns { stats }. There is no operation discriminator, compatibility alias, or generic fallback hook. On Mongo, db.query stays the static builder and the row-executing db.execute facade method is gone — build with db.query, then execute through (await db.runtime()).query(plan). See the user recipe. (#29921)

  • raw is a reserved storage namespace — the SQL surface exposes the whole-query raw statement tag as db.sql.raw, so a storage namespace of that name would be unreachable through the builder while the emitted types still promised its tables. Building the client now raises ORM.NAMESPACE_RESERVED naming the namespace. Rename it in your schema, re-emit the contract, and plan the rename against the database as you would any other namespace rename. Only raw is reserved. (#29997)

    Before:

    model Event {
      id String @id
      @@schema("raw")
    }
    

    After:

    model Event {
      id String @id
      @@schema("ingest")
    }
    
  • Codec ids are checked where you write them — a codec id in a prepared declaration or in a contract-bound raw fragment is now checked against your contract's codec map, so an id the contract does not carry is a compile error instead of an execution-time RUNTIME.PARAM_REF_MISSING_CODEC. The usual cause is an unversioned id. Read the correct spelling off your emitted contract.d.ts — every id it carries now completes at both positions. A raw fragment built through a contract-free lane is unaffected, since it has no map to check against. (#30011)

    Before:

    await db.prepare({ id: 'pg/int4' }, (sql, params) => /* … */);
    const upper = fns.raw`UPPER(${f.email})`.returns('pg/text');
    

    After:

    await db.prepare({ id: 'pg/int4@1' }, (sql, params) => /* … */);
    const upper = fns.raw`UPPER(${f.email})`.returns('pg/text@1');
    
  • db update takes consent by database name, and --yes no longer grants it — a plan that would destroy data is refused until you type the name of the connected database, and the consent binds to that exact plan by hash. --yes never grants it; the CLI style guide has always said a blanket confirmation flag must not stand in for a destructive confirmation. Non-interactive runs grant with --confirm <database>. A dry run, or a plan with nothing destructive in it, never asks. Update any CI invocation that relied on -y to apply a destructive plan. (#29986)

  • The diagnostic commands exit 4 on findings and 2 on errorsdb verify, db sign, and migration check now distinguish "I ran and found problems" (exit 4) from "I could not run" (exit 2). Exit 1 is reserved for a bug in the CLI itself, and exit 0 still means the check ran and found nothing. db verify and db sign previously exited 1 on findings, and migration check exited 2. Scripts that test for any non-zero exit are unaffected; scripts that match a specific code must be updated. (#29984)

  • Four migration status flags are retired--graph, --all, --limit, and --ref moved to their own commands. An old invocation now gets a typed CLI.COMMAND_MOVED error naming the replacement rather than failing as an unknown flag. (#29982)

  • Prepared statements split by their declared resultruntime.prepare() returns one of two handles chosen from the plan the callback builds: a rows plan gives the PreparedStatement you already have, consumed with .query(target, params), while a plan whose declared result is an affected-row count gives a PreparedExecution, consumed with .execute(target, params). This matters to extension authors: a facade that redeclares prepare() changes its return type to PreparedFor with no logic change, and a scope that installs the prepared-query bridge must also install the execute bridge or prepared.execute throws on the bridge invariant. See the extension-author recipe. (#30006)

Features
  • Whole-query raw SQL replaces the classic $queryRaw / $executeRaw use case. A whole statement is authored with the same tagged template the fragment mechanism already used, terminated with .returnsRow(rowSpec) for decoded, typed rows or .affectedCount() for a mutation count, and built into an ordinary query plan that flows through the existing lowering, codec, guardrail, and execution machinery — no new query lane and no new execution surface. Row-returning raw queries interpolate into other raw templates as subqueries, which gives CTEs, including data-modifying ones, for free. (#29997)
  • @@check(expression: "…") declares a CHECK constraint in the schema, and contract infer adopts the ones your database already has. Use name: for a wire-name prefix, so the physical constraint is name_<8hex> hashed over the predicate and compared by name — which means Postgres reprinting the expression never causes drift. Use map: to adopt a constraint under its existing physical name, comparing the predicate byte-for-byte. Pulling a database now emits @@check for every live check Prisma Next did not derive, so a hand-written constraint is declared from the first pull instead of reading as an undeclared extra. (#29972)
  • @noCheck opts a column out of the CHECK constraints Prisma Next derives for it, per kind: @noCheck suppresses all of them, @noCheck(membership) keeps the element-non-null check on a list column while dropping the membership check, and @noCheck(elementNotNull) does the reverse. The TypeScript builder equivalent is .noCheck(...). contract infer emits the attribute too, so a pulled schema passes db verify --schema-only immediately instead of needing one migration first. (#29928)
  • Two new column types make integer representation a per-column choice without changing the lossless BigInt default. BigIntNumber reads and writes as a JavaScript number, throwing outside ±(2^53 − 1) instead of rounding. UnboundedInt uses PostgreSQL unconstrained numeric storage and round-trips integral values as exact bigint values at arbitrary magnitude. PostgreSQL contributes both; SQLite contributes BigIntNumber. (#29902)
  • The minimum supported PostgreSQL version drops from 17 to 15, the oldest version CI has been exercising all along. init scaffolds and the --probe-db warning threshold follow the new floor. The reasoning is recorded in ADR 244. (#29971)
  • Renaming a model or column whose CHECK constraint content is unchanged now plans a single ALTER TABLE … RENAME CONSTRAINT, classed widening, instead of a drop plus an add. A cosmetic rename no longer needs a destructive-capable plan or a full table revalidation. (#29894)
  • Errors carry typed next actions. A failure that has a remedy now ships it as structured data — nextActions, each naming a command to run — raised at the site that holds the arguments rather than spelled out in English prose a caller would have to parse. The binary name is templated at the raise site and substituted when rendered, so the suggestion stays correct as the CLI is renamed. (#29977)
  • CLI failures report their real error code. envelope.code is the stable surface consumers branch on, and a dozen failures previously reported CONTRACT.VERIFY_FAILED while hiding the true code in metadata. Every construction site now declares its code explicitly, fourteen new codes were added for the failures that had none, and the generic error path gained cause support. (#29919)
  • Config loading reports diagnostics per section instead of throwing on the first problem it finds. A command fails only when a section it actually reads is broken, so a malformed formatter section no longer blocks db init. Each diagnostic is tagged with the config section and field it concerns. (#29936)
Fixes
  • init no longer fails at its contract-emit step against the published packages. The step now runs the scaffolded project's own CLI binary as a subprocess rather than loading the new config in-process with the running CLI's bundled loader, and its failure message carries the child's stderr so a real cause is visible. The schema-path prompt also shows its default as placeholder text instead of looking blank until a keypress. (#30018)
  • contract emit picks the import specifier for the emitted contract.d.ts by reading the nearest package.json above the file it is writing, rather than falling back to the process working directory. Running the command from the wrong directory previously wrote an unresolvable internal specifier into the generated file. (#29981)
  • Synthesized foreign-key-backing index name prefixes are truncated to fit PostgreSQL's 63-byte identifier limit, so a mapped explicit join table no longer fails contract emit before the content hash can be appended. User-authored over-budget index prefixes still fail loudly. (#30025)
  • A raw row spec column named __proto__ is now refused loudly instead of silently vanishing — bracket assignment onto an object literal hit the inherited setter, so the key never became an own property and the record was quietly re-parented. constructor and prototype create ordinary own properties and round-trip faithfully. (#30014)
  • The PostgreSQL direct driver no longer ends a caller's transaction. A driver-level read issued while its connection held an open transaction reported no transaction in progress, took the cursor portal-protection path, and wrapped itself in BEGIN/COMMIT — and that COMMIT ended the caller's transaction, so later statements ran autocommit and ROLLBACK undid nothing. The driver and its connection now share the transaction-open flag. (#29920)
  • ORM mutation reloads encode Bytes identities through the column codec, so a repeated upsert keyed on a Bytes column no longer raises ORM.MUTATION_ROW_MISSING. Every unbound literal entering a select through raw collection state now becomes a typed parameter. (#29910)
New contributors
View original

Upgraded? How did it go?

Discussion