v8.0.0-rc.21
migration plan and db update can rename or delete an enum value on Postgres and SQLite, and update the rows that hold it. MongoDB contracts written in TypeScript, PSL or Prisma 6 now build through one builder and follow the same rules. A closed client's members report the failure through the promise or result they return. MySQL gets its first public packages, which can plan and apply migrations. PostgreSQL's date and time types now name the kind of value they read, and the text date and time types read ISO 8601 on every server. Field presets are removed: each is written with a type constructor or type alias that stores the same contract. When a database is at a contract outside the migration graph, db migrate explains why and gives recoveries that work, none of which resets the database. The upgrade guides for rc.18 and rc.19 now work on real projects.
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. 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
-
A client member that returns a promise or a result rejects instead of throwing at the call. On
postgres(),sqlite(), asupabase()role-bound client and a transaction,query,execute,prepare,transaction, ORM calls and a prepared statement'squeryandexecutenow report a closed client, a failed connection or an ended transaction through what they return. The promise rejects, or the result rejects when it is awaited or read. Error codes and messages do not change. So.catch(handle)andPromise.allSettlednow see the error, but atry/catcharound a call withoutawaitno longer catches it: addawait.db.runtime()still throws at the call, and so does a mistake in the call itself. Extension authors:lazyActivitySourceanddelegatingActivitySourcenow reject fromstartActivityandplanContentHashwhen their source throws. Seeclient-members-reject-instead-of-throwingin the app recipe, andclient-members-reject-instead-of-throwingandlazy-activity-source-rejectsin the extension recipe. (#30840)Before:
try { db.transaction(work); // threw at the call after close(), so the catch ran } catch (error) { handle(error); }
After:
try { await db.transaction(work); // the returned promise rejects; await it to catch the error } catch (error) { handle(error); }
-
Removing an enum value is now asked about, like removing a model or a field. On Postgres and SQLite, when the contract removes an enum member or changes the value it stores,
migration plananddb updateask what the change means and rewrite the rows that hold the old value. A script that runs without a terminal fails withCLI.CONSENT_REQUIREDuntil you answer with--rename Status.ARCHIVED:Status.RETIREDor--delete Status.ARCHIVED. Before, Postgres failed at apply when a row held the old value, and SQLite planned nothing. Native Postgres enums (pg.enum(...)) are unchanged. Extension authors: renames and migration subjects gain anenumValuekind, and three optional fields let a target say how to keep the data a removed value would lose. Seeremoved-enum-value-questionsin the app recipe, andenum-value-rename-statementandrows-holding-value-and-keep-data-editin the extension recipe. (#30849) -
MongoDB contract type names and
mongoContractoptions change.mongoContract(schemaPath, options)now requiresoptions.target, the MongoDB target pack; a config written withdefineConfigfrom@prisma/orm-mongo/configneeds no change. The type of the object you pass todefineContractis renamed fromContractDefinitiontoContractInput, as in SQL.MongoModelDefinitionis removed: writeContractModel<MongoModelStorage>. Extension authors:loadContractSourceno longer takesonWarningand returns{ contract, warnings }, and a failed emit carriessourceWarnings. Seemongo-contract-target-option,mongo-contract-inputandmongo-model-definition-removedin the app recipe, and the same ids pluscontract-source-warningsin the extension recipe. (#30846)Before:
import type { ContractDefinition } from '@prisma/orm-mongo/contract-builder'; const contract = mongoContract('./prisma/schema.prisma');
After:
import type { ContractInput } from '@prisma/orm-mongo/contract-builder'; import mongoTargetPack from '@prisma/orm-mongo/target/pack'; const contract = mongoContract('./prisma/schema.prisma', { target: mongoTargetPack });
-
Every MongoDB contract source applies the same rules, so some contracts are now refused.
defineContractnow refuses what PSL refused: a discriminator that is optional, a list or not text; a variant value the discriminator cannot hold; a listed variant no model declares; and a variant stored in another collection than its base. Every source refuses an index MongoDB would refuse, a value object field typed by an undeclared enum, and a variant field that overwrites or restates a base field differently. PSL also refuses a duplicate index in one collection. Some contracts are newly accepted, such as an optional field that a type alias or generator fills. A Prisma 6 schema with two identical indexes in one collection now gives one index, so its storage hash changes: runprisma contract emit, thenprisma db sign. Seemongo-ts-polymorphism-rules,mongo-index-shape-rules,mongo-value-object-enum-check,mongo-newly-acceptedandmongo-prisma6-merged-indexesin the app recipe and the extension recipe. (#30846) -
close()closes the client's runtime even when you passed in the pool, and a SQLite file that cannot be opened is a connection error. When you pass your ownpg.Poolorpg.Clienttopostgres()orsupabase(),close()now also closes the client's runtime, so a runtime captured withdb.runtime()beforeclose()refuses new work. Your pool or client stays open; end it yourself. A SQLite database file that cannot be opened, such as a missing directory or a file that is not a database, is now aSqlConnectionErrorinstead of aSqlQueryError. Seeclose-ends-the-client-not-your-poolandsqlite-open-failure-is-a-connection-errorin the app recipe. (#30833) -
Each target names where its contract markers live.
verifyContractSpacestakes a requiredmarkerLocation. Extension authors:SqlControlAdapter,MongoControlAdapterandControlFamilyInstancegain a requiredmarkerLocation,SqlControlAdaptergains an optionalholdMarkerLock, andSqlPlanTargetDetails.schemais optional, with SQLite operations no longer carryingschema: 'main'. Seeverify-contract-spaces-marker-locationin the app recipe, andverify-contract-spaces-marker-location,control-adapter-marker-location,control-adapter-hold-marker-lockandplan-target-details-schema-optionalin the extension recipe. (#30841) -
PostgreSQL date and time types that read more than one kind of value are renamed. In PSL,
TimestamptzbecomesTimestamptzTemporal,TimestampbecomesTimestampTemporalandIntervalbecomesIntervalParts; the old names are errors. In TypeScript,type.Timestamptz(...),type.Timestamp(...)andtype.Interval(...)are renamed the same way, andintervalColumn(...)becomesintervalPartsColumn(...). Each new name reads the same value with the same codec, so the contract and its hashes do not change.@db.Timestampand@db.Timestamptzin a Prisma 7 schema are not affected. AnIntervalParts(p)column is now typed asPgInterval, the{ months, days, micros }object it reads, instead of a string. The string typesTimestamp,Timestamptz,TimeandIntervalare no longer exported from@prisma/orm-postgres/target/codec-types, so remove them from imports, including those in migration snapshots'contract.d.ts.Temporalfields are now typed with thetemporal-specpackage instead of a global declaration; a type test that compares a field with the globalTemporal.InstantimportsTemporalfromtemporal-spec. Seepsl-temporal-type-renames,ts-temporal-type-helpers-renamed,interval-with-precision-typed-as-parts,branded-time-types-removed,temporal-types-from-temporal-specandcontract-types-list-new-aggregatesin the app recipe and the extension recipe. (#30834)Before:
model Event { id Int @id at Timestamptz(3) span Interval }
After:
model Event { id Int @id at TimestamptzTemporal(3) span IntervalParts }
-
PostgreSQL text date and time values are ISO 8601, and every connection must use
DateStyle=ISO.TimestamptzString,TimestampString,DateString,TimeStringandTimetzread and write ISO 8601, the form the contract stores, instead of the text PostgreSQL prints in the session's time zone and date style. ATimestamptzStringthat read2024-01-01 01:00:00+01on a server inEurope/Berlinnow reads2024-01-01T00:00:00Zon every server. A literal default or enum member on these types must be written in that form, orcontract emitrefuses it withCONTRACT.DEFAULT_INVALID. Theirnow()values round down to the column's precision, so the execution hash changes where such a column has anow()generator. The PostgreSQL driver ensuresDateStyle=ISOon every connection, including apg.Poolorpg.Clientyou pass in; a connection that prints another style makes every date and time read fail withRUNTIME.DATE_STYLE_NOT_ISO. Extension authors: a target can declaresessionSettings, and a driver receives them as the second argument ofcreateor in its connect options and must ensure them on every connection. Seeiso-string-date-time-valuesanddate-style-iso-requiredin the app recipe, anddriver-ensures-target-session-settingsin the extension recipe. (#30834)Before:
scheduledAt: field.column(timestamptzStringColumn).default('2024-01-15 10:30:00+00'),
After:
scheduledAt: field.column(timestamptzStringColumn).default('2024-01-15T10:30:00Z'),
-
A Prisma 7 schema's
@default(now())is filled by the client on create. A contract fromprisma7Schema(...)now reads@default(now())as@default(now()) @onCreate(now()), so a create through Prisma 8 writes the current time in UTC, as Prisma 7's client did, instead of leaving the column to the database's clock in the session's time zone. Re-emit the contract and commit it: only the execution hash changes, so no migration and nodb signis needed. The reader also refuses two schemas Prisma 7 refuses:now()on a field that is not a date or time, and a function default other thandbgeneratedon a list field. Seeprisma7-schema-now-default-writes-on-createin the app recipe. (#30834) -
The control API checks a database's marker against the migration graph itself.
refuseMarkerOutsideGraphis no longer exported from@prisma/orm-toolchain/cli/control-api: the control client'smigrateandexecuteMigrateShowPlannow check the marker of every contract space and fail withMIGRATION.MARKER_MISMATCHwhen it is outside the space's migration graph.executeMigrateShowPlantakes a requiredinvocation,{ cwd, configPath, dbGiven }, asmigratedoes. The error'smetano longer hasreachableHashes; it hasspaceId,markerHash,targetHash, andlastGraphHashandmissingMigrationswhen the database's ledger shows them. Projects that only run theprismaCLI are not affected. Seerefuse-marker-outside-graph-removed,execute-migrate-show-plan-requires-invocationandmarker-mismatch-metain the app recipe. (#30866)Before:
const refusal = refuseMarkerOutsideGraph({ markerHash, graph }); if (refusal) return notOk(refusal); const plan = await executeMigrateShowPlan({ ...options });
After:
const plan = await executeMigrateShowPlan({ ...options, invocation }); if (!plan.ok && plan.failure.code === 'MIGRATION.MARKER_MISMATCH') { // plan.failure.meta: spaceId, markerHash, targetHash, lastGraphHash?, missingMigrations? }
-
Field presets are removed; each is written with a type constructor or type alias that stores the same contract. A field preset set a field's column, identity and generated values in one call, such as
id.uuidv4String()andtemporal.createdAt()in PSL, orfield.id.uuidv4String(),field.text()andfield.temporal.createdAt()in TypeScript. Every preset, andfield.generated(...)with theuuidv4(),ulid()and other helpers of@prisma/orm-framework/ids, is removed. PSL refuses a preset withPSL_UNRESOLVED_REFERENCEand a message that gives the exact rewrite; TypeScript reports a missing property, and an emit of a contract that was not type-checked fails withCONTRACT.FIELD_HELPER_UNKNOWN. The replacement stores the same contract with the same hashes, so nothing is re-emitted, signed or migrated. MongoDB'sfield.date(),field.string()and other codec helpers are not presets and stay. Extension authors: theauthoring.fieldnamespace, thefieldPresetkind, theoption,numberandobjectargument kinds and the helpers that built presets are removed, a pack that still contributesauthoring.fieldis refused withCONTRACT.PACK_CONTRIBUTION_INVALID, andPSL_PRESET_ON_VARIANT_FIELDandPSL_PRESET_CONFLICTare renamedPSL_GENERATOR_ON_VARIANT_FIELDandPSL_GENERATOR_CONFLICT. Seepsl-field-presets-removed,ts-field-presets-removedandts-field-generated-removedin the app recipe, andauthoring-field-namespace-removed,argument-kinds-removed,preset-builders-removed,preset-diagnostics-removedandfield-presets-in-extension-codein the extension recipe. (#30845)Before:
model Post { id id.ulid() authorId ulid() createdAt temporal.createdAt() }
After:
model Post { id Ulid @id @onCreate(ulid()) authorId Ulid createdAt temporal.CreatedAt }
Features
migration plananddb updatecan rename or delete an enum value on Postgres and SQLite.--rename Status.ARCHIVED:Status.RETIREDchanges the stored value in every column and list that holds it, and--delete Status.ARCHIVEDsets it to NULL in optional columns and takes it out of lists. On a required column that still holds the value, the apply stops before changing anything and says how to keep the data.migration.tsgainsthis.replaceStoredValueandthis.removeStoredValue. (#30849)- MySQL has its first public packages,
@prisma/orm-mysqland@prisma/orm-target-mysql, as an early preview. They can emit a contract, andmigration plan,db migrate,db verifyanddb signwork against MySQL 8.4 for creating and dropping tables, columns, indexes, unique constraints and foreign keys. Other schema changes are refused with a message that they are not supported on MySQL yet, and queries are not available yet. The newprisma-8-demo-mysqlexample shows the workflow. (#30841, #30856) - A failed migration on a target whose schema changes cannot be rolled back, such as MySQL or MongoDB, says which contract space failed and what stayed applied. Running
db migrateagain skips what was applied and finishes the rest. (#30841) - A contract source's warnings now reach
contract emit,contract printand the control client even when the source then fails. Before, they were dropped. (#30846) - PostgreSQL has three new explicit date and time types:
TimestampJsDate(p?)reads atimestampcolumn as a JavaScriptDate,TimestampInstant(p?)reads it as aTemporal.Instant, both as UTC, andIntervalString(p?)reads anintervalas an ISO 8601 duration such asP1Y2M.DateTemporalandTimeTemporalare added as the explicit names of whatDateandTimeread today. An enum onTimestampString,TimestamptzStringorTimetzis now allowed. (#30834) - Every SQL target has a type alias for each generated identifier's column:
sql.UuidString,sql.Ulid,sql.Nanoid,sql.Cuid2andsql.Ksuid, which Postgres also names at the top level, such asUlid. In TypeScript they aretype.sql.Ulid()and, on Postgres,type.Ulid(). Every SQL target also hassql.Char(n), which SQLite had no way to write before.@prisma/orm-postgres/contract-builderexportstypebesidefieldandgen, so a query fragment declares a field asfield.column(type.TimestamptzTemporal()). Seeidentifier-type-aliases,sqlite-sql-charandpostgres-contract-builder-exports-typein the app recipe. (#30845)
Fixes
- When a database's marker names a contract that no migration starts or ends at, for example after a migration it applied was deleted or planned again,
db migratefails withMIGRATION.MARKER_MISMATCHand now says why: the last contract of the migration graph the database reached, and the migrations it applied after that which are no longer on disk or were planned again. Its next actions give two recoveries that work:db update, for a database whose migration history does not need to be kept, and new migrations, for one that changes only through migrations, such as production. Before, it suggestedmigration plan --fromandmigration ref set, which the CLI refuses.db migrate,db migrate --showand the control API now make this check for every contract space and return the same error, and no error, document or skill recommends resetting a database. (#30866) - The upgrade guides and scripts for rc.18 → rc.19 and rc.19 → rc.20 now work on real projects, so users who upgrade from rc.18 or rc.19 get working guides through
prisma skills sync. The main fixes: (#30855)- Every script skips hidden directories, such as
.next/and other worktrees. contract-from-jsonmoves a client call whose result is destructured.remove-nocheckrewrites stored PostgreSQL defaults into the rc.19 form, and takes--defaultfor a default it cannot convert.- Both guides say how to recover a database that is behind on migrations:
prisma db sign --contract <last applied migration>, thenprisma db migrate. - Detection patterns no longer stall on large generated files.
- Every script skips hidden directories, such as
- On SQLite, a SQL builder or ORM query with parameters in a join or derived table and also in
WHERE, or inGROUP BYand also inHAVINGorORDER BY, bound its values to the wrong placeholders and returned wrong rows. Each value now binds to its own numbered placeholder. (#30853) - The Postgres driver recognizes a lost connection from the error's code and the driver's own state, not from its message. Errors such as SQLSTATE class
08,57P01–57P05,EPIPEandEAI_AGAINare now connection errors. For apg.Clientyou pass in, two queries arriving at once share one connection attempt, and a failed connection attempt is reported on every later use instead of hanging. (#30833) - Postgres messages about contract markers named the table
_prisma_marker; they now nameprisma_contract.marker, where the markers are. SQLite messages that name an object sayuserwhere they saidmain.user. (#30841) Temporalfields were typedanyin a project without aTemporalpolyfill's global declarations. They are now typed through thetemporal-specpackage. (#30834)- In a PSL schema with relations, a field whose type does not resolve was dropped, and the contract build then failed with an unrelated relation error such as
CONTRACT.RELATION_INVALID. The build now stops first and reports the field's own diagnostic. On SQLite, an unknown bare type name such asUlidsuggests the name in a namespace:Did you mean sql.Ulid?. (#30845)