v8.0.0-rc.23
MongoDB indexes carry their names, and migrations create and drop each index by its name. On PostgreSQL, Int, BigInt, Float, Decimal, String, Boolean and Bytes become convenience types, and SQLite and MySQL get the convenience date and time types. The pgvector, PostGIS, ParadeDB and citext packs find their extension through the search path, so db sign adopts a Supabase database. MySQL gets ORM writes. mongo() takes extension packs, and supabase() takes the options postgres() takes. Prisma ORM no longer refuses to start when it is loaded from more than one copy.
The upgrade recipes for this hop: the app recipe and the extension recipe. Each breaking change below names the change ids to look for in them. The app recipe opens with the order to do the steps in. After upgrading, run prisma contract emit once, so a contract that an extension such as Supabase stamps with its version matches the installed package (contract-artifacts-restamp in the app recipe).
Breaking changes
-
MongoDB indexes carry their names. Every index of a MongoDB contract records the name MongoDB stores it under, so every MongoDB contract with an index gets a new storage hash. An index written without a name is named after its keys, such as
email_1, the name earlier releases already gave it in the database, so no database changes. Migrations create and drop each index by that name, and an operation id names the index, as inindex.users.create(email_1). Two indexes of one collection that would get one name, such as a unique and a sparse index onemail, are refused; name one of them. A contract read withprisma6Schema()names each index as Prisma 6 did, such asUser_email_key. Runprisma contract emit, run the script the app recipe ships to name the indexes of your migration snapshots, then runprisma db signor plan a migration, which has no operations. Extension authors:MongoIndex,MongoSchemaIndexand their input types take a requiredname,CreateIndexCallandDropIndexCalltake the index's name and options, and code that callsbuildMongoContractFromDefinitioncallsnameIndexesfirst. Seemongo-contracts-record-index-names,mongo-unnamed-indexes-with-one-name-refused,mongo-prisma6-index-namesandmongo-index-operations-by-namein the app recipe, andmongo-index-name-required,mongo-index-calls-take-nameandmongo-build-refusal-index-name-duplicatein the extension recipe. (#30952)Before:
createIndex('users', [{ field: 'email', direction: 1 }], { unique: true }),
After:
createIndex('users', [{ field: 'email', direction: 1 }], { name: 'email_1', unique: true }),
-
The Prisma 6 MongoDB reader keeps indexes as Prisma 6 wrote them.
prisma6Schema()no longer merges indexes. Two indexes of one model with the same fields and the same written sort are refused withPSL.PRISMA6_MONGO_INDEX_ALREADY_EXISTS, as Prisma 6 refuses them. A unique and a plain index on the same keys that Prisma 6 accepts are both kept, so the contract gets a new storage hash; runprisma db signagain. Prisma 8 PSL and TypeScript still merge identical and redundant indexes. Extension authors: the MongoDB contract builder no longer merges indexes,BuildRefusalhas the kindindexDuplicateagain, andmergeRedundantIndexesmerges before building. Seemongo-prisma6-indexes-as-prisma6-reads-themin the app recipe, andmongo-build-refusal-index-duplicate-restoredin the extension recipe. (#30921) -
The pgvector, PostGIS, ParadeDB and citext packs find their extension through the search path. Each pack declares its extension in the unbound namespace, so
db signanddb verifyaccept the extension in any schema on the connection's search path, such as Supabase'sextensions. A project that composes one of these packs runsprisma contract emit, which writes the pack's new migration, and thenprisma db migrate, which applies it without changing the database. An application may no longer declare an extension that a composed pack declares; remove its ownextension vector {}block orextension('vector')handle. Extension authors: a pack declares its Postgres extension in the unbound namespace, or the configuration is refused withCONTRACT.PACK_CONTRIBUTION_INVALID. Seepack-extension-found-on-search-pathandremove-pack-extension-declarationin the app recipe, andpack-declares-extension-unboundin the extension recipe. (#30956) -
mongo()andmongoStatic()refuse a contract whose extension packs they are not given. They take anextensionsoption, aspostgres()does. A MongoDB project whoseprisma.config.tscomposes extension packs passes each pack's runtime descriptor tomongo({ extensions })andmongoStatic({ extensions }); otherwise they fail withRUNTIME.MISSING_EXTENSION_PACK. A MongoDBcontract.jsonmust have"target"; every contractprisma contract emitwrites has it. Extension authors:createMongoExecutionContextchecks the contract against the stack, a MongoDB descriptor declares each codec once as a codec descriptor instead of in acodecs()slot, andmongoStandardCodecsandbuildStandardCodecRegistryare removed. Seemongo-client-takes-extensionsandmongo-contract-requires-targetin the app recipe, andmongo-execution-context-checks-contract-requirements,mongo-codecs-declared-onceandmongo-standard-codecs-removedin the extension recipe. (#30943)Before:
export const db = mongo({ contract, url });
After:
import myPack from 'my-mongo-pack/runtime'; export const db = mongo({ contract, url, extensions: [myPack] });
-
supabase()andpostgres()check their database input when they are called. A wrong input fails withRUNTIME.BINDING_INVALIDat once, not on the first query. Passing bothurlandpgtosupabase()fails; before,pgwon silently. An emptyurl, or one that is notpostgres://orpostgresql://, fails; before, an empty one connected with thePG*environment variables or to localhost.nullaspg,urlorbindingfails, even beside another input, so leave the option out instead. Abindingof an unknown kind fails.supabase()refuses apg.Clientwith the message "Supabase takes a pg.Pool or a url, not a pg.Client". Seesupabase-one-binding-inputandpostgres-checks-database-inputin the app recipe. (#30920)Before:
postgres({ contract, url: process.env['DATABASE_URL'] ?? null });
After:
const url = process.env['DATABASE_URL']; postgres({ contract, ...(url === undefined ? {} : { url }) });
-
On PostgreSQL, seven scalar type names are deprecated, and two column helpers are renamed.
SmallInt,BigIntNumber,Numeric,UnboundedInt,Real,VarCharandVarBitstill work but report the warningPSL_DEPRECATED_SCALAR_NAME. Write the convenience type that gives the same type instead, such asString(storage: varchar, length: 12)forVarChar(12); the contract does not change. The same TypeScript helpers, such astype.VarChar(...), are deprecated too. The column helpersint8ColumnandnumericColumnare renamedint8BigIntColumnandnumericStringColumn, and the old names are removed. Extension authors:postgresScalarAuthoringTypes,postgresNativeAuthoringTypesandpostgresScalarAndNativeTypeConstructorsare replaced bypostgresScalarTypesandpostgresExplicitTypes. Seepsl-renamed-postgres-scalar-types,ts-renamed-postgres-scalar-helpersandts-renamed-postgres-column-helpersin the app recipe, and the same ids plusts-renamed-postgres-type-groupingsin the extension recipe. (#30942)Before:
model Account { id Int @id visits SmallInt price Numeric(10, 2) code VarChar(12) }
After:
model Account { id Int @id visits Int(storage: int2) price Decimal(precision: 10, scale: 2) code String(storage: varchar, length: 12) }
-
temporal-polyfillis a peer dependency of the MySQL and SQLite packages. The MySQL and SQLite control planes, such as theprismacommands, now import it. npm, pnpm and bun install it for you. A project that installs with Yarn addstemporal-polyfill(^1.0.4) to its dependencies. A MySQLDateTimefield now refuses to write a year before 1000 or after 9999 withRUNTIME.ENCODE_FAILED. On SQLite,db verifyreads a database default ofdate('now')as the current date, so it matches only aDate @default(now())field. Extension authors:mysqlCreateNamespace,MysqlDatabase,sqliteCreateNamespaceandSqliteUnboundDatabasemove from the target's/target/controlentry to its/target/typesentry. Seetemporal-polyfill-is-a-peer-dependency-of-mysql,mysql-datetime-writes-are-checkedandtemporal-polyfill-is-a-peer-dependency-of-sqlitein the app recipe, and the same ids plusmysql-create-namespace-moved-to-target-typesandsqlite-create-namespace-moved-to-target-typesin the extension recipe. (#30922, #30950)
Features
- On PostgreSQL,
Int,BigInt,Float,Decimal,String,BooleanandBytesare convenience types, asDateTimeis. Each is named for what a field means, and its optionalstorageargument (andas, forBigInt) chooses an explicit type named after the database type, such asInt(storage: int2)forInt2orBigInt(as: number)forInt8Number. Written bare, each gives the same type as before, so every existing schema emits the same contract. (#30942) - SQLite and MySQL get the convenience types
DateTime,Date,TimeandDuration.as:chooses the value the application reads, such as a JavaScriptDate, a string or aTemporalvalue, and on MySQLstorage:chooses the column, such asDateTime(storage: timestamp). A bareDateTimeis the typeDateTimewas before, so every existing SQLite and MySQL contract emits the same. (#30922, #30950) - ORM
create,update,upsertanddeletework on MySQL and return the written rows, as on PostgreSQL. MySQL has noRETURNING, so the ORM writes and then reads the rows back by their key in the same transaction. (#30927) - Migration statements cover value objects and variants on PostgreSQL and SQLite.
prisma migration plananddb updatetake--rename GeoPoint.lat:GeoPoint.latitude,--delete GeoPoint.lngand--backfill GeoPoint.countryfor a value object field, rename or delete an enum value a value object holds, and take--convert Bugor--delete Bugfor a variant whose discriminator value changed or that was removed. Each statement acts on every column that holds the value object. A failed migration'srowsHoldingValuedetail is now a list of every removal whose check failed. (#30929) supabase()takes the optionspostgres()takes, includingcursor, which streams rows from a server-side cursor, andbinding. (#30920)- A Supabase database whose pgvector, PostGIS, ParadeDB or citext extension lives in schema
extensionsadopts Prisma 8 withprisma contract emitandprisma db sign. (#30956)
Fixes
- Prisma ORM no longer refuses to start when it is loaded from more than one copy.
RUNTIME.MULTIPLE_ORM_COPIES, added in 8.0.0-rc.22, is removed for now, because a host that loads the ORM in two module instances, such as Prisma Composer's boot wrapper beside the app bundle, cannot satisfy it. A narrower check may return later. Installing one version of every@prisma/orm-*package is still required: values made by one copy are not recognised by another. (#30961) - A released Supabase role session resets its role. Before, its connection went back to the pool still running as the session's role, such as
authenticated, so the next user of that connection ran under that role. (#30920) - On MySQL, stopping a stream early on a large result no longer ends the process with an unhandled rejection. The driver interrupts the statement itself before it closes the connection. (#30931)