prisma-8

Use when working in a project that depends on @prisma/orm-postgres, @prisma/orm-sqlite, or @prisma/orm-mongo (Prisma 8, formerly Prisma Next): editing contract.prisma or a contract.ts builder, running `prisma contract emit`, planning or applying migrations, editing migration.ts, writing db.orm / db.sql / db.query queries, wiring db.ts or middleware, integrating a build tool, using the Supabase extension or RLS, or reading a dotted error code such as MIGRATION.HASH_MISMATCH. Use when the user asks "what is Prisma 8", "where do I start", or compares it to another ORM. Use when the user asks to upgrade or bump Prisma 8 in an app or an extension package. Use when you see @internal/* or @prisma/orm-* imports, prisma.config.ts with definePrismaConfig, or contract.json / contract.d.ts. Do not use for Prisma ORM 7 or earlier (schema.prisma + @prisma/client).

Install
npx skills add 'https://github.com/prisma/orm/tree/main/skills/prisma-8'
Download bundle ↓
main · fac8604Scanned 2026-09-17

Contributors

GitHub-linked commit authors for this SKILL.md at the saved revision. Co-authors and history before file renames are not included.

File history ↗

upgrading/app/upgrades/0.17-to-8.0.0-rc.1/instructions.md

upgrading/app/upgrades/0.17-to-8.0.0-rc.1/instructions.mdBrowse 76 files
View on GitHub
← Back to SKILL.md

from: "0.17" to: "8.0.0-rc.1" changes:

  • id: aggregate-results-carry-their-target-s-codec summary: | Aggregates read back through the codec their target declares for the result, so their application types change. count() is a bigint on both targets — a count is a cardinality and is not capped at 2^53. On PostgreSQL, sum over int2/int4 widens to int8 and reads as a bigint, while sum(int8) and avg over any integer are numeric and read as decimal strings; min/max keep the column's own type, except over varchar, where PostgreSQL returns text. On SQLite, sum over an integer column is a bigint and avg is always a number (real). The two targets genuinely diverge on avg — a portable query must handle both, and there is no shim that flattens them. Include reducers (.include('posts', (p) => p.count())) decode the same way, so an included count is a bigint too, and an empty relation reads 0n rather than 0. Comparisons are where this bites quietly: count === 2 is false where count is 2n, and JSON.stringify throws on a bigint. Sweep for toBe(<number>) / === <number> / arithmetic against an aggregate result and switch to 2n or the decimal string, and render bigints explicitly (String(value)) wherever you serialise. having(...) operands are the exception and stay numbers — they are compared inside SQL and never cross a codec. Regenerate your contracts (prisma-next contract emit): the emitted contract.d.ts gains an AggregateTypes block that types every aggregate per operation and input codec, and the ORM and SQL builder both resolve their result types from it — against a contract emitted before 8.0.0-rc.1 an aggregate resolves to never in the ORM and to unknown in the SQL builder. detection: glob: "**/*.{ts,tsx,mts,cts}" contains: - "aggregate(" - ".count()" - "groupBy(" anyMatch: true

0.17 → 8.0.0-rc.1 — User upgrade instructions

aggregate-results-carry-their-target-s-codec

An aggregate's result is a value the database computes, and 8.0.0-rc.1 reads it back through the codec its target declares for that result rather than through whatever the driver happened to hand over. What each aggregate returns is now the target's answer, stated in the contract and honoured by the runtime:

TargetAggregateReads as
PostgreSQLcount() (with or without an argument)bigint
PostgreSQLsum over int2 / int4bigint (the sum widens to int8)
PostgreSQLsum over int8, avg over any integerdecimal string (the result is numeric)
PostgreSQLsum / avg over float4 / float8number
PostgreSQLmin / maxthe column's own type — except over varchar, which returns text
SQLitecount()bigint
SQLitesum over an integer columnbigint
SQLiteavg over anythingnumber (SQLite's avg is always real)
SQLitemin / maxthe column's own type

The targets diverge on avg, and deliberately: PostgreSQL computes it as numeric, SQLite as a float. A query written against both handles both.

Include reducers follow the same rules — .include('posts', (posts) => posts.count()) yields a bigint, and a parent with no related rows reads 0n where it read 0.

Two failure modes are worth sweeping for, because neither announces itself:

  • Equality against a number. row.count === 2 is false when row.count is 2n, and expect(count).toBe(2) fails the same way. Change the literal (2n), or compare through Number(...) where the magnitude is known to be small.
  • Serialisation. JSON.stringify throws TypeError: Do not know how to serialize a BigInt. Render explicitly — String(count), or a replacer that maps bigints to strings.

Arithmetic mixing a bigint with a number also throws (2n + 1 is a TypeError); convert one side deliberately.

having(...) is the one place that does not move. A HAVING operand is compared inside SQL against the aggregate the database is computing, so it never crosses a codec: having.count().gte(2) keeps the plain number it always took. Only the aggregate's result — the value that reaches your code — carries its target's type.

Finally, regenerate your contracts:

prisma-next contract emit

The emitted contract.d.ts gains an AggregateTypes block — the settled result identity per operation and per input codec — and both the ORM client and the SQL builder resolve their aggregate result types from it. Against a contract emitted before 8.0.0-rc.1 the block is absent, so an aggregate resolves to never in the ORM and to unknown in the SQL builder: a type error at the call site in the first case, an untyped value in the second, rather than a wrong runtime value in either.