v8.0.0-rc.20
ORM commands run on @prisma/cli-engine 0.9.0, so a piped command prints tab-separated data lines and JSON needs --json. A type alias in PSL can set @default, @onCreate and @onUpdate, and the contract stores each column's real type instead of the alias name. Postgres migrations can rename, move and remove namespaces, and can change a relation's actions or a primary key. A failed migration check now names the object it expected.
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
-
A piped ORM command prints data lines, not JSON, unless you pass
--json. The ORM toolchain now requires@prisma/cli-engine0.9.0, up from 0.7.0, the engine theprismaCLI ships since 8.0.0-rc.25. This engine picks the output format from flags only. When stdout is a pipe, a file,$(…), a CI log or an agent, a command used to print JSON. It now prints the human output on stderr and plain data lines on stdout. Add--jsonto every script, CI step or program that reads JSON fromprisma contract …,prisma db …,prisma migration …orprisma orm init. A project that depends on@prisma/cli-engineitself moves to0.9.0. Data lines are a stable interface: fields are tab-separated in a documented order, and new fields are only added at the end. For example,migration listprints space, name, from, to and operation count, one line per migration.db init,db update,db verify,db schemaandmigration ref deleteprint nothing on stdout; read their exit code, or pass--json. Seepiped-output-needs-jsonandengine-pin-moves-to-0-9-0in the app recipe and the extension recipe. (#30835)Before:
prisma migration status | jq '.envelope.result'
After:
prisma migration status --json | jq '.envelope.result' prisma migration list | cut -f2 # or read the data lines: one migration name per line
-
The contract stores a column's type, not the type alias it was written with. A column typed by a
types {}alias now stores the alias's codec and parameters itself, and the contract no longer has the alias instorage.types. Every contract that uses an alias gets a new storage hash. A stored contract whose columns still name an alias intypeRefno longer loads. Run the app recipe's script, which rewrites stored contracts and migration snapshots, then runprisma contract emit, thenprisma db signagainst every database. The database does not change. In the TypeScript builder, thetypeskey is removed andfield.namedType(...)takes only an enum: write an alias as a constantfield.column(type.VarChar(64)). The Supabase extension's contract changes hash for the same reason, so sign a Supabase database too. Seestored-contracts-expand-type-aliases,ts-types-key-removedandsupabase-contract-expandedin the app recipe. Extension authors run the same script from the package root and re-emit the bundled contract. A contract space that registers a database type writes it underdatabaseTypesinstead oftypes.ColumnTypeDescriptor.typeRef,StorageColumn.typeRefandExecutionContext.typesare removed. Seestored-contracts-expand-type-aliases,types-key-is-database-typesandcolumn-type-ref-removedin the extension recipe. (#30819)Before:
const types = { Embedding1536: type.pgvector.Vector(1536) } as const; const Post = model('Post', { fields: { embedding: field.namedType(types.Embedding1536).optional() }, }); return { types, models: { Post } };
After:
const Embedding1536 = field.column(type.pgvector.Vector(1536)); const Post = model('Post', { fields: { embedding: Embedding1536.optional() }, }); return { models: { Post } };
-
A SQL builder insert must include every column the database requires. In
db.sql.<namespace>.<table>.insert(rows), leaving out a column that is not nullable and has no default is now a type error that names the column. Such an insert used to compile and then fail in the database. A value typedT | undefinedfor such a column is an error too; check it before the insert. In ORMcreateandupsert, a field that is not nullable, has no default and generates a value only on update, such as one with@onUpdate(...)and no@onCreate(...), is now required. Seesql-builder-insert-required-columnsandorm-create-on-update-only-fieldin the app recipe, andsql-builder-insert-required-columnsin the extension recipe. (#30830)Before:
// User.displayName is `String` with no default; this compiled, then failed in the database db.sql.public.user.insert([{ email: 'admin@example.com', kind: 'admin' }]);
After:
db.sql.public.user.insert([{ email: 'admin@example.com', kind: 'admin', displayName: 'Admin' }]);
-
MongoDB enum members and discriminator values are written in the syntax of their data type. A MongoDB PSL enum member is read as a value of its codec's data type, as on SQL, so a quoted member is text. A quoted member on a type that does not take text is refused with
PSL_VALUE_TYPE_INCOMPATIBLE: write numbers,NaNand the infinities unquoted, and dates and binary values with their tags. An Int32 or Int64 member written with a fraction, such as1.0, is refused; write1. A@@discriminatorfield must be required and hold one value, on SQL and MongoDB alike. When an enum types a discriminator, each@@basevalue must be one of its members. A value the receiving type refuses now reportsPSL_VALUE_REFUSED. A Decimal128 value with leading zeros incontract.json, such as"007.5", is refused when the contract loads; re-emit the contract. Seemongo-quoted-enum-members,mongo-integer-enum-members-with-a-fraction,discriminator-optional-or-list,discriminator-base-value-not-a-member,psl-value-refused-codeanddecimal128-spellingin the app recipe. Extension authors:AuthoringEntityContexttakesdataTypesin place ofdataTypeLookup, thereadWrittenValueForCodechook is removed, and an enum block needswrittenValues. Seeentity-context-data-typesandenum-block-members-written-valuesin the extension recipe. (#30832)Before:
enum Ratio { @@type("mongo/double@1") Unknown = "NaN" Half = 0.5 }
After:
enum Ratio { @@type("mongo/double@1") Unknown = NaN Half = 0.5 }
-
Supabase runs every role operation through
forRole(binding), and itspgoption takes a pool only.SupabaseRuntimeno longer hasqueryWithRole,executeWithRole,preparedQueryWithRoleorpreparedExecuteWithRole. Callruntime.forRole(binding)once and use itsquery,executeandconnectionmembers. Thepgoption ofsupabase()takes apg.Pool; apg.Clientfails withSUPABASE.CONFIG_INVALID.db.close()does not end a pool you pass in.RoleBoundDb,ServiceRoleDband the transaction context gainenumsandnativeEnums, so test doubles of them must add those members. Afterdb.close(), a query on any role-bound client rejects withDRIVER.NOT_CONNECTED"Runtime is closed", andclose()waits for open role transactions. Seesupabase-for-role-replaces-with-role-methods,supabase-role-types-gain-members,supabase-pg-takes-a-poolandsupabase-close-waits-for-workin the app recipe. Extension authors:SqlRuntimeBase.acquireRawConnection()now follows the close rule ofconnection(), and a runtime that shares its driver is built withownsDriver: false. Seesupabase-for-role-replaces-with-role-methods,supabase-role-types-gain-members,supabase-pg-takes-a-poolandraw-connection-obeys-closein the extension recipe. (#30828)Before:
const rows = await runtime.queryWithRole(plan, { role: 'anon' }).toArray(); const db = await supabase({ contract, pg: new Client({ connectionString }), jwtSecret });
After:
const rows = await runtime.forRole({ role: 'anon' }).query(plan).toArray(); const db = await supabase({ contract, pg: new Pool({ connectionString }), jwtSecret });
-
A removed Postgres namespace is asked about, and
accessWideningis renamedaccessChanges. When the contract no longer declares a namespace,migration plananddb updatenow ask about each model in it, as for any removed model. They used to leave its tables in place without a word. Answer with--rename <old>:<new>if you renamed it, or--delete <namespace>to drop its tables and the schema. A run without a terminal fails withCLI.CONSENT_REQUIREDuntil you answer. The list of operations that change who can read or write a model's data is nowaccessChanges, indb update --dry-run --jsonand the control API, and the optionacceptAccessWideningis nowacceptAccessChanges. The question ends with "who can read and write its data" in place of "its rows". Seeremoved-namespace-questionsandaccess-changes-renamein the app recipe. Extension authors:SchemaTablesgainshasNamespace,SchemaOwnershipgainsnamespaceDeclaringSpaces, and a resolved rename can name a namespace. Seeaccess-changes-rename,statement-planning-target-namespaces,schema-ownership-namespace-spacesandnamespace-rename-statementin the extension recipe. (#30823)Before:
const result = await client.dbUpdate({ contract, mode: 'apply', migrationsDir, acceptAccessWidening: true }); if (result.ok) console.log(result.value.accessWidening);
After:
const result = await client.dbUpdate({ contract, mode: 'apply', migrationsDir, acceptAccessChanges: true }); if (result.ok) console.log(result.value.accessChanges);
-
db verify --jsonanddb sign --jsonreport schema differences inmeta.schemaIssues. TheCONTRACT.SCHEMA_VERIFICATION_FAILEDdiagnostic carried label strings inmeta.issues. It now carries issue objects inmeta.schemaIssues, the same key and shape asMIGRATION.SCHEMA_VERIFY_FAILED. Each issue has apath, andexpectedandactual. A failed apply also lists each way out as a separate entry innextActions, and some entries change kind. Seeschema-verification-failed-schema-issuesin the app recipe. (#30814)
Features
- A type alias in PSL can set
@default,@onCreateand@onUpdate, and each field typed by it gets them, such astypes { UpdatedAt = TimestampString(3) @onCreate(now()) @onUpdate(now()) }. Postgres, SQLite and MongoDB providetemporal.CreatedAtandtemporal.UpdatedAt, and Postgres also hasStringandJsDatevariants. MongoDB schemas can now use aliases. The TypeScript builder has atype.*helper for every type PSL can write, such astype.VarChar(64). (#30819) - Postgres migrations can rename and move namespaces.
--rename billing:financerenames the schema, and--rename public.Invoice:billing.Invoicemoves a table into another schema with its rows, constraints, indexes and policies.migration.tsgainsthis.renameSchema,this.setTableSchemaandthis.dropSchema. (#30823) prisma migration plancan change a relation'sonDeleteoronUpdate, and can change or remove a primary key, on Postgres. It drops the foreign key or primary key and adds it back, dropping and restoring every foreign key that points at an old primary key. It used to refuse both changes withMIGRATION.PLANNING_FAILED. (#30774)- Prisma 7's
prisma migrate dev,deploy,status,diff,resetandresolveexit withCLI.COMMAND_MOVEDand name the Prisma 8 command to use. (#30774) - A failed migration check names the object it expected, such as
the database already has table "user", in place of a generic sentence. Each check can carry anexpectationinops.json, and a check you write in arawSqloperation can carry one too. Migrations already written keep their hashes and the generic sentence. (#30814) - A failed apply reports
meta.databaseUnchanged, and its message says whether the database changed. On MongoDB, which has no transaction around a migration, it says so once anything was written. (#30814) - A MongoDB discriminator may be a type alias of
String. (#30832)
Fixes
- A Supabase client's internal runtime for Supabase's own tables is now closed by
db.close(), so its queries after close reject with "Runtime is closed" instead of a driver error you could not act on. (#30828) - In the TypeScript builder, passing
undefinedto skip a preset argument no longer loses the arguments after it, sofield.temporal.timestamptz(undefined, 'now')types correctly. (#30830) db migrateno longer says a failed apply changed nothing on MongoDB when operations before the failure stayed applied. (#30814)