v8.0.0-rc.19
@default now means a database default, and values the client generates are written @onCreate(...) and @onUpdate(...). Every default is stored in one form owned by its column's data type, so date, time and bytes defaults are written with tags. Clients take the contract as a typed value, can render any plan as SQL without a database, and run each ORM call as one activity that middleware and the cache can see. A column of any database type can be declared with Raw(sql`...`), and Prisma 7 schemas with Unsupported(...) fields now load.
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.
Breaking changes
-
Client generators are written
@onCreate(...)and@onUpdate(...), and@defaultmeans a database default.@default(uuid()),@default(cuid(2)),@default(ulid())and@default(nanoid())are refused withPSL_GENERATOR_IN_DEFAULT. Write@onCreate(uuid())and so on instead. The app recipe ships a script that rewrites your Prisma 8 schema files. The contract does not change, so there is nothing to re-emit, sign or migrate.@default(now())and@default(autoincrement())are unchanged.contract printnow writes generated values as@onCreate(...)and@onUpdate(...). Seeclient-functions-move-from-default-to-on-createandcontract-print-writes-generator-attributesin the app recipe. (#30780)Before:
model Post { id String @id @default(uuid(7)) slug String @default(nanoid(12)) }
After:
model Post { id String @id @onCreate(uuid(7)) slug String @onCreate(nanoid(12)) }
-
A client takes the contract as a typed value. The
contractJsonoption and the type argument on the client call are removed frompostgres(),postgresServerless(),sqlite(),supabase(),mongo(),postgresStatic(),sqliteStatic(),mongoStatic()andmongoQuery(). Turn the emitted JSON into a value once withcontractFromJson<Contract>(contractJson)from the facade's/staticentry, and pass{ contract }. The app recipe ships a script that moves your source files; move test files by hand. The*OptionsWithContractand*OptionsWithContractJsontypes are replaced byPostgresOptions<Contract>and the matching types of the other facades. Seeclient-takes-contract-valuein the app recipe. (#30748)Before:
export const db = postgres<Contract>({ contractJson, url: process.env['DATABASE_URL']! });
After:
import { contractFromJson } from '@prisma/orm-postgres/static'; export const contract = contractFromJson<Contract>(contractJson); export const db = postgres({ contract, url: process.env['DATABASE_URL']! });
-
Date, time, interval and bytes defaults are written with tags, and each default has one stored form.
contract emitrefuses a quoted date, time, interval or bytes default and names the tag to write. Abyteadefault takes hexadecimal, not base64. A PostgreSQL contract that stores a default in another form than its column's type gives, such as"1.5"onnumeric(10,2), is refused by the CLI: runprisma contract emit, thenprisma db sign. On SQLite, datetime and JSON columns get their own data types; run the app recipe's script before the next CLI command, because the CLI refuses the old contract. Reading a value that its column's type cannot hold now throwsRUNTIME.DECODE_FAILED. A SQLiteDateTimetext with no UTC offset is read as UTC. Runprisma db signagainst every database after the upgrade. Seedate-time-interval-and-bytes-defaults-use-tags,sqlite-contracts-name-datetime-and-json-data-types,postgres-defaults-stored-in-another-spelling,prisma7-datetime-default-finer-than-column,stored-values-a-column-type-does-not-hold,sqlite-datetime-without-offset-read-as-utc,text-array-min-max-return-arraysandsign-databases-after-data-type-valuesin the app recipe. (#30750)Before:
at DateTime @default("2024-01-01T00:00:00Z") day Date @default("2024-03-15") payload Bytes @default("AQID")
After:
at DateTime @default(timestamptz`2024-01-01T00:00:00Z`) day Date @default(date`2024-03-15`) payload Bytes @default(bytea`\x010203`)
-
@noCheckis removed, and a list that allows null elements is writtenT?[]. A stored contract with anoCheckkey no longer loads. First run the app recipe's script, which rewrites stored contracts and migration snapshots, then runprisma db signagainst every database. Then replaceT[] @noCheck(elementNotNull)withT?[]and delete@noCheck(membership); in TypeScript, use.many({ elementsNullable: true }). A contract read from a Prisma 7 schema now types list elements asT | null, and the Supabase extension's fourtext[]columns allow null elements; both change the storage hash, so sign your databases. Seestored-contracts-drop-nocheck,psl-nocheck-removed,ts-nocheck-method-removed,prisma7-lists-allow-null-elementsandsupabase-contract-lists-allow-null-elementsin the app recipe. Extension authors run the same script from the extension package root, then re-emit the bundled contract; seestored-contracts-drop-nocheck,psl-nocheck-removedandts-nocheck-method-removedin the extension recipe. (#30741)Before:
tags String[] @noCheck(elementNotNull)After:
tags String?[]
-
Type-constructor arguments are values. A quoted number, such as
VarChar("255")orTimestamp("3"), is refused withPSL_VALUE_TYPE_INCOMPATIBLE; write the number without quotes. On Postgres a whole number written with a fraction, such asVarChar(255.0), is refused too, and so is a number the type cannot hold. The same applies to a field preset's precision, such astemporal.timestamp("3"). Seetype-constructor-arguments-are-valuesin the app recipe. (#30801)Before:
name VarChar("255")After:
name VarChar(255)
-
Enum members are values of their data type. A PSL enum member is read as a value of the enum codec's data type, as a
@defaultis. A quoted member is refused on a codec whose type is not text, such aspg/numeric@1orpg/int8@1; write numbers unquoted and dates and times with their tag. Enums typed bypg/jsonb@1,pg/timetz@1,pg/enum@1,arktype/json@1,pg/vector@1orpg/geometry@1are refused; use a text enum, or a native enum instead ofpg/enum@1. Members of apg/numeric@1,pg/interval@1ormongo/decimal128@1enum compare as values, so0.5and0.50are duplicates. Columns of more enum codecs are now typed as the union of their members; re-emit the contract. Seepsl-enum-members-read-by-the-cast-rule,more-codecs-refused-for-enums,enum-members-compare-by-their-data-typeandenum-columns-typed-by-member-literalsin the app recipe. (#30790)Before:
enum Ratio { @@type("pg/numeric@1") Half = "0.5" }
After:
enum Ratio { @@type("pg/numeric@1") Half = 0.5 }
-
Generator arguments are checked, and a generator must fit its column. An argument
uuid,cuidornanoiddoes not take, such asuuid(5), is now refused, in Prisma 8 PSL withPSL_INVALID_GENERATOR_ARGUMENTand in a Prisma 7 schema withPSL.PRISMA7_UNKNOWN_DEFAULT; it used to be read as something else. A number with a fraction, such asnanoid(8.0), is refused too. A generator on a Postgres column too short for what it writes, such asVarChar(5) @onCreate(uuid()), is refused when the contract is built instead of failing at the first insert. A SQL@@discriminatorthat is not text has a new message. Seeprisma7-default-function-arguments-checked,generator-function-argument-diagnostics,generator-function-argument-with-fraction,generator-fits-columnanddiscriminator-must-be-textin the app recipe. (#30792) -
Re-emit
contract.d.ts, and expect more precise types fromdefineContract.prisma contract emitwrites field and column types as generics applied to the contract, so a SQLcontract.d.tsemitted by an earlier release no longer compiles; re-emit it. A contract built withdefineContractnow gets the same ORM types as an emitted one: typedincluderows, aggregates, query operations, parameterized types such asVarchar<50>, index names inwhereandorderBycallbacks, unique keys inwhereUnique, and its enums. A MongodefineContractcontract types lists and value objects asreadonly, and its factory form no longer types every field asnever. Casts that only worked because a type wasunknownornevermay now fail to compile. Seetypescript-contract-include-row-types,typescript-contract-aggregates-and-operations,typescript-contract-parameterized-output-types,typescript-contract-index-references,typescript-contract-storage-and-domain-types,typescript-contract-enums-in-domain,emitted-contract-types-from-generics,emitted-field-types-from-shared-generic,mongo-define-contract-readonly-rows,mongo-define-contract-factory-field-types,value-object-enum-fields-narrowandmongo-update-accessors-take-input-typesin the app recipe. (#30709, #30747) -
A MongoDB field keeps its schema name in code. A field declared
id ObjectId @id @map("_id")is the fieldidin the contract, in ORM rows and inwhere,select,orderByand write data, where it was_id. The contract records the stored name inmodel.storage.fields, and a contract without it is refused withCONTRACT.VALIDATION_FAILED. Runprisma contract emit, then the app recipe's script, which rewrites migration snapshots. Storage hashes do not change.--renameand--deletename fields by their schema names.@mapon a field of a composite type is now refused, because it was always ignored. In the TypeScript builder, set a stored name with.mongo({ field: '_id' }). Seemongo-contracts-record-stored-field-names,mongo-orm-uses-field-names,mongo-statements-name-fields,mongo-composite-field-map-refusedandmongo-typescript-stored-field-namesin the app recipe. (#30779)Before:
await db.orm.users.where({ _id: user._id }).update({ name: 'Ada' });
After:
await db.orm.users.where({ id: user.id }).update({ name: 'Ada' });
-
MongoDB contracts written in TypeScript get a collection validator. A
defineContractcontract now gets the same closed$jsonSchemavalidator as a PSL contract, and its storage hash changes once.defineContractrefuses what a validator cannot describe, such as an_idthat can hold an array or null. MongoDB does not check existing documents when a validator is added, so fix documents the validator would refuse, then apply it withprisma db update, or withprisma migration planandprisma db migrate. A collection shared by several models now gets a validator that accepts each of them; apply it the same way. Seemongo-typescript-contract-validatorandmongo-shared-collection-validatorin the app recipe. (#30773) -
Each SQL ORM call is one
orm-callactivity, and ORM annotations live on it. The statements an ORM call sends carry no user annotations, so a middleware that reads an annotation from a statement plan finds nothing; read it inactivityStartedfrom theorm-callactivity. The cache middleware caches an annotated ORM read as a whole, keyed bycacheAnnotation({ key })or by the call's content hash;deriveKeyapplies only to SQL builder queries. If yourderiveKeyseparates tenants or users, give each annotated ORM read a tenant-prefixedkey, or one tenant's cached ORM read can be served to another. A custom cache store holdsCachedValueinstead ofCachedRows. A hand-built object passed toorm()that copies onlyqueryandexecutenow fails withORM.ACTIVITY_SOURCE_MISSING. An ORM call still pending atclose()now completes instead of rejecting. Seeorm-annotations-on-the-call,cached-orm-reads-key-on-the-call,cache-store-holds-cached-values,orm-needs-a-runtime-queryableandclose-lets-open-orm-calls-finishin the app recipe. (#30749)Before:
async beforeEncode(plan) { const actor = auditAnnotation.read(plan); if (actor) log(actor); },
After:
async activityStarted(activity) { if (activity.kind !== 'orm-call') return; const actor = auditAnnotation.read(activity); if (actor) log(actor); },
-
Migration checks require the full effect of an operation, so plan unapplied migrations again. The runner skips an operation only when its checks show the whole effect is already in place. On PostgreSQL, SQLite and MongoDB these checks now compare the full definition, not just a name. An
ops.jsonwritten by an earlier release keeps its old checks: runmigration.tsagain for each migration that is not applied yet. On PostgreSQL,this.setDefaultno longer takesoperationClass; delete the option. Widening anautoincrement()column now also widens its sequence; for a migration already applied, runALTER SEQUENCE … AS bigintyourself.this.alterColumnTypewithusingon a column that already has the new type is refused; write the conversion as adataTransform. Seesetdefault-operation-class-derived,setdefault-has-no-postcheck, thepostgres-postcheck-*entries,postgres-alter-column-type-unchanged-refused,postgres-autoincrement-sequence-follows-column-type,sqlite-postchecks-check-full-definitionandmongo-postchecks-check-full-definitionin the app recipe. (#30752) -
A failed migration reports the runner's own error code.
MIGRATION.RUNNER_FAILEDno longer exists. A failed apply reports codes such asMIGRATION.SCHEMA_VERIFY_FAILED,MIGRATION.PRECHECK_FAILEDorMIGRATION.POSTCHECK_FAILED, with typedmeta.schemaIssuesandmeta.failedCheck. On a control client,dbInit,dbUpdateandmigratetake a requiredinvocationoption, and their failures areCliStructuredErrors. Seerunner-failures-report-own-codesandcontrol-client-apply-takes-invocationin the app recipe. (#30791) -
prisma db schema --jsonnames a column's typetypeText. It holds the type as the database reports it, such asinteger, in place ofnativeType, andresolvedNativeTypeis gone. The tree output shows the same text.CONTRACT.DEFAULT_INVALIDcarriesmeta.typeTextinstead ofmeta.nativeType. Seedb-schema-json-names-type-textanddefault-invalid-meta-type-textin the app recipe. (#30777) -
String timestamp columns are filled with text. The Postgres
temporal.*Stringpresets, and every@updatedAtfield aprisma7Schema(...)contract reads, now fill the column with text instead of aDate. Re-emit the contract; only the execution hash changes. Thepg/timestamp-string@1andpg/timestamptz-string@1codecs refuse aDateat runtime withRUNTIME.ENCODE_FAILED. Seestring-timestamp-presets-write-textandstring-timestamp-codecs-take-text-onlyin the app recipe. (#30742) -
usingis a reserved member of every ORM collection. A custom collection class with its own member namedusingno longer compiles; rename it. Seeorm-collection-member-using-reservedin the app recipe. (#30717) -
Objects that implement client or transaction types need the new members. Clients and transactions gain
query,execute,toSqland other members (see Features). Code that only uses these types needs no change. Code that builds its own object of one of them, such as a test double, must add the new members.supabase()now creates its pool on the first query. Seeclient-query-and-to-sqlin the app recipe andtransaction-query-membersin the extension recipe. (#30802, #30789) -
Extension authors: activities, codecs and generator functions.
RuntimeActivityis renamedActivityand now includes client activities, which an extension starts withstartActivityOn; queryables and transactions carry an activity source. A codec implementstoWire,fromWire,toDataTypeValueandfromDataTypeValueinstead ofencode,decode,encodeJsonanddecodeJson, and a data type'sreadreplacestoCanonicalForm. A default function can no longer generate a value in the client; contribute a generator function instead, and stop registering the identifier generators the SQL family now registers. A SQL target declares theuuid,cuidandnanoidsignatures withsqlIntegerArgumentGeneratorFunctions. Seeruntime-activity-is-activity,activity-includes-client-activities,start-a-client-activity,runtime-connections-carry-an-activity-source,begin-transaction-on-takes-a-parent,transaction-activity-takes-a-parent,orm-runtime-objects-carry-an-activity-source,orm-annotations-on-the-call,cache-store-holds-cached-values,queryable-carries-the-activity-source,codecs-read-wire-and-data-type-values,data-types-own-parameter-limits,json-helpers-renamed,stored-form-helpers-renamed,projections-read-by-from-wire,empty-result-json-renamed-empty-result-wire,default-functions-lower-to-database-defaults-only,applicable-codec-ids-and-build-phases-removed,sql-family-registers-identifier-generators,sql-target-declares-integer-argument-generators,generator-signature-from-data-types,generator-function-field-length,build-contract-definition-takes-lookups,arg-binding-spec-label,one-of-refuses-data-type-valueandtext-data-typesin the extension recipe. (#30706, #30749, #30750, #30780, #30742, #30792) -
Extension authors: type constructors, column types, contract types and MongoDB. A type constructor's value argument is declared
{ kind: 'value', dataType }, and thestring,booleanandstringArraykinds are removed. Columns are compared by data type, so the type-name normalisers andSqlTypeLookupsandMongoTypeLookupsare removed, schema IR columns havetypeText, and the Postgres and SQLite control adapters take the stack's data types. A codec whose output type depends on its type parameters declares a type function instead ofrenderOutputType, andFieldTypesFromDomainmoves to@prisma/orm-framework/contract/types. A MongoDB model's storage records each field's stored name, andderiveJsonSchemais replaced byderiveCollectionValidators. Seevalue-arguments-name-a-data-type,value-argument-types-in-typescript,psl-and-print-helpers-take-data-types,composed-helpers-take-no-data-type-lookup,helper-codec-invariants,type-lookups-one-framework-type,type-name-normalisers-removed,introspected-type-text,schema-ir-column-type-text,default-resolver-takes-the-column,alter-type-postcheck-by-data-type,control-adapters-take-declared-data-types,resolved-sql-type-carries-the-data-type,typescript-contract-pack-type-slots,parameterized-output-types-declared-once,psl-interpreter-contract-capabilities,field-types-expression-removed,field-types-generic-moved,resolve-field-value-set-renamed,mongo-parameterized-output-types,family-codec-templates-render-no-literals,enum-block-written-values,mongo-model-storage-fields,mongo-validator-derivation-moves-to-mongo-contract,mongo-derive-json-schema-replaced-by-derive-collection-validatorsandaggregate-operation-using-reservedin the extension recipe. The app-facing changes above also apply to extensions that build clients, write migrations or ship contracts. (#30801, #30777, #30709, #30747, #30790, #30779, #30733, #30773, #30717)
Features
@onCreate(fn())and@onUpdate(fn())set the values the client generates, and each function resolves for the column's codec:uuid()works onString,VarChar(n),Char(n)andUuid. A column may have both a database@defaultand a generator. The TypeScript builder has.onCreate(gen.uuid(7))and.onUpdate(gen.now()). (#30780)- Every Postgres date and time type has a
now()generator, includingdate,timeandtimetz, so@updatedAtworks on@db.Date,@db.Timeand@db.Timetz. A generator on an optional field generates a value the client leaves out and stores an explicitnull. (#30742) Raw(sql`ltree`)declares a column of a database type no data type in the stack knows, on Postgres and SQLite. Migrations create it from the text,db verifycompares that text with the database, and the ORM reads and writes its values asunknown. The TypeScript builder hastype.Raw(sql`...`)andrawColumn(sql`...`). (#30745, #30801)- A Prisma 7 schema with
Unsupported(...)fields now loads throughprisma7Schema(). Each such field becomes a raw column with no field on the model, as in Prisma 7's client. (#30788) - A Prisma 7 schema with
@updatedAtbeside@default(now()), or with a generator on an optional field, now loads, signs and verifies against the database Prisma 7 built. (#30742) - A client can be created with no database.
db.toSql(plan)renders any plan as{ sql, params }, anddb.query(plan)anddb.execute(plan)run a plan directly.postgres(),postgresServerless(),sqlite()andsupabase()takemode: 'strict' | 'permissive'. (#30802) - Every SQL client takes custom collection classes as the
collectionsoption, and uses them ondb.orm, on every transaction'stx.orm, on serverless connections and on Supabase role-bound clients. (#30776) - A transaction offers the client's query members (
sql,orm,raw,prepare,query,execute) on every SQL client. A Supabase role-bound client gainsprepare, and SQLite'sdb.ormgainsfragment. (#30789) - Packages contribute collection methods computed from a model, under
using. The Postgres package adds a search method for each full-text index, such asdb.orm.public.Post.using.post_search(query), and pgvector adds a nearest-neighbour search for each vector field. The methods chain withwhere,orderByandlimit. (#30717) - Middleware sees each SQL ORM call as one
orm-callactivity, with its statements as children, and can answer the whole call throughintercept.onActivity. The cache middleware uses this to cache an annotated ORM read as a whole, so a cache hit sends no statement. (#30706, #30749) - ORM filters and writes take each field's input type, so a
Varchar<50>,Char<36>orNumeric<10, 2>field accepts a plain string inwhere,create,updateand the other write methods. (#30709) - A
pg.enumcolumn can name anative_enumdeclared in another namespace, such aspg.enum(auth.OrderStatus). (#30721) prisma contract inferreads each column's type from the data types of the configured target and extensions. It writes the shortest matching type constructor, and when columns have types no pack declares, it fails once and lists every one of them instead of writing a schema that cannot emit. NewBit,VarBitandIntervaltype constructors let those columns infer. (#30735)prisma db verifyand the migration planner compare column types as data types with their parameters, sonumeric(10)matchesnumeric(10,0). A database column of a type no configured pack declares gets a message that says so. (#30777)- A SQL
@@discriminatorfield may be aChar(n)orVarChar(n)column on Postgres, or asql.String(n)column on SQLite. (#30792)
Fixes
- A hand-written
setDefaultthat changes an existing default is applied instead of skipped, and the runner no longer skips an operation because an object of the same name but another definition exists. (#30752) - Widening a Postgres
autoincrement()column fromint4toint8now also widens its sequence, which used to stop at 2147483647. (#30752) - When a raw SQL default fails the schema check because the database stores it in another form, the error shows both texts and the
sqlliteral to write. (#30740) - The MongoDB ORM's typed
whereacceptsnull, an empty list and a whole list on list fields, andnullon a nullable field. Anullfilter on a list field matches only a null or missing list, not a list that holds a null item. (#30769) - A MongoDB PSL contract with a composite type that contains itself now emits, where it failed with
Maximum call stack size exceeded. (#30733) - A MongoDB collection that stores several unrelated models gets a validator that accepts all of them, where it described only the last model. (#30773)
- An enum field inside a value object is typed by its enum's members, as an enum field of a model is. (#30747)
- The language server no longer leaves an unhandled rejection when it removes a file watcher after the editor connection has closed. (#30787)
- A cache store's
getandunsettake optional fields, so code that calls a store directly passes only the fields it needs, where it had to passundefinedfor the others.cache.invalidateandcacheAnnotationacceptundefinedfor their optional fields underexactOptionalPropertyTypes. Stores written against the old shape still fit. (#30822)