v8.0.0-rc.13
This release brings MongoDB closer to Postgres: a Mongo schema can declare Int64, Decimal128, Binary, Json and Bson fields and automatic timestamps, and a Prisma 6 MongoDB project can use its existing schema.prisma as the contract source. The ORM client can order by a related row's column, by a relation count, and with explicit null placement. The new prisma contract print command writes any configured contract as Prisma 8 PSL. A TypeScript contract now encodes every literal default through the column's codec, and the generated defaults in contract.json name their target with new keys, so re-emit your contract after upgrading.
The upgrade recipes for this hop: the app recipe and the extension recipe. Each breaking change below names the change id to look for in them.
Breaking changes
-
Generated defaults in the contract name an
entryand afield. Each entry underexecution.mutations.defaultsincontract.jsonused to name its target asref: { namespace, table, column }. It now usesref: { namespace, entry, field }with the same values. Runprisma contract emitafter upgrading; the runtime refuses a contract that still has the old keys. Contract snapshots undermigrations/snapshots/need the same rename by hand. TheexecutionHashchanges, but no migration ordb signis needed. Seeexecution-ref-entry-fieldin the app recipe. (#30399)Before:
{ "ref": { "namespace": "public", "table": "user", "column": "updated_at" }, "onUpdate": { "kind": "generator", "id": "timestampNow" } }After:
{ "ref": { "entry": "user", "field": "updated_at", "namespace": "public" }, "onUpdate": { "kind": "generator", "id": "timestampNow" } } -
A literal
.default(value)in a TypeScript contract must be the codec's input type.defineContractfrom the Postgres and SQLite packages now encodes every literal default through the column's codec. A value of the wrong type is a type error for fields built inside thedefineContractfactory, and a value the codec refuses fails the build withCONTRACT.DEFAULT_INVALID. Pass a value of the codec's input type, or choose the field preset whose codec takes the value you have.bigintand bytes defaults are stored in a different form, which changes the storage hash of a contract that has one. PSL contracts,now(),autoincrement()andsqltagged defaults are not affected. Seets-defaults-encoded-by-codecin the app recipe. (#30433)Before:
createdAt: field.dateTime().default('2024-01-01T00:00:00Z'), views: field.bigint().default(1),
After:
createdAt: field.temporal.timestamptzString().default('2024-01-01T00:00:00Z'), views: field.bigint().default(1n),
-
cursor()refuses an order that is not a plain column.cursor()throwsORM.ARGUMENT_INVALIDwhen an activeorderByitem is an extension-operation result (such as a vector distance), a relation field, a relation count, or an order with null placement. Before, an extension-operation order was left out of the keyset without an error, which returned wrong pages. Paginate such queries withlimit()andoffset(), or order by plain columns only.distinctOn()throws the same error when one of its leading orders is not a plain column. Seecursor-rejects-expression-ordersin the app recipe. (#30402) -
A hand-written contract source in
prisma.config.tsmust declare itsformat. A config that buildscontract.sourceitself, as an object with aloadfunction, must give itformat: 'psl'orformat: 'typescript'. Without it, every command that reads the config fails withCONFIG.VALIDATION_FAILED. Sources made bydefineConfig,prisma7Schema(),prismaContract()and the TypeScript contract helpers already declare one. Seeconfig-contract-source-requires-formatin the app recipe. (#30315) -
prisma contract formatformats a Prisma 7 schema, and policy expressions decode every JSON escape. A project whose contract isprisma7Schema(...)used to be skipped byprisma contract format; the command now formats that file with the Prisma 8 formatter. Do not run it on a schema that must keep Prisma 7's formatting. AusingorwithCheckexpression in a PSLpolicy_*block now also decodes\t,\b,\f,\/and\uXXXX; write the backslash twice if you mean the backslash and the letter. Seecontract-format-formats-prisma7-schemaandpolicy-expression-json-escapesin the app recipe. (#30315) -
The Mongo codec subpaths moved from the adapter to the target. The
adapter/codec-types,adapter/codecs,adapter/codec-idsandadapter/data-typessubpaths of@prisma/orm-mongoand@prisma/orm-target-mongoare nowtarget/.... An emitted Mongocontract.d.tsimportsadapter/codec-types, so re-emit the contract and rewrite the import in eachcontract.d.tsundermigrations/snapshots/.createMongoRunnerDeps(...)is removed,MongoRunnerDependenciesandMarkerOperationsmoved to@prisma/orm-mongo/family/control-adapter, and a Mongo family instance must be created from a control stack that includes the adapter. The contract JSON and every hash stay the same. See themongo-*entries in the app recipe and the extension recipe. (#30396)Before:
import type { CodecTypes } from '@prisma/orm-mongo/adapter/codec-types';
After:
import type { CodecTypes } from '@prisma/orm-mongo/target/codec-types';
-
A Mongo
Jsonfield holds only JSON values, and fields of a variant model go through their codecs. AJsonfield now refuses aDate, anObjectId, aDecimal128, aBinaryor any other value that is not JSON, at any depth: a read fails withRUNTIME.DECODE_FAILEDand a write fails withRUNTIME.ENCODE_FAILED. Change the type of a field that holds such values toBson. A Mongo contract written in PSL with aJsonfield gets a new collection validator and a new storage hash, so runprisma contract emitand thenprisma db update, or plan a migration. Through.variant(...), a field declared only on the variant model is now written and read through its codec, so remove any code that converted such values by hand. Seemongo-json-field-semanticsandmongo-variant-field-codecsin the app recipe, andmongo-bson-codec-addedin the extension recipe. (#30439)Before:
model Event { id ObjectId @id @map("_id") payload Json }
After, when
payloadholds values that are not JSON:model Event { id ObjectId @id @map("_id") payload Bson }
-
Four Mongo PSL scalar names are deprecated. A Mongo schema now names each scalar after the BSON type it stores:
IntbecomesInt32,FloatbecomesDouble,BooleanbecomesBoolandDateTimebecomesDate. The old names still work and produce the same contract, but each use reports aPSL_DEPRECATED_SCALAR_NAMEwarning, and a later release removes them. Postgres and SQLite schemas do not change. Seemongo-psl-scalar-namesin the app recipe. (#30396)Before:
model Post { id ObjectId @id @map("_id") views Int rating Float? published Boolean createdAt DateTime }
After:
model Post { id ObjectId @id @map("_id") views Int32 rating Double? published Bool createdAt Date }
-
Extension authors: mutation defaults and temporal presets moved to the framework.
GeneratorStability,RuntimeMutationDefaultGenerator,MutationDefaultsOptions,AppliedMutationDefaultandMutationDefaultsOpare now exported from@prisma/orm-framework/components/runtime.applyMutationDefaultstakesentryinstead oftable, and each applied default names itsfieldinstead of itscolumn.TIMESTAMP_NOW_GENERATOR_ID,temporalAuthoringPresetsandtemporalCodecPresetmoved to@prisma/orm-framework/components/authoring, andtimestampNowControlDescriptormoved to@prisma/orm-framework/components/control.MongoExecutionContexthas a new requiredapplyMutationDefaultsmethod. See the extension recipe. (#30406, #30403)Before:
const applied = context.applyMutationDefaults({ op: 'create', table: tableName, namespace, values }); for (const def of applied) row[def.column] = def.value;
After:
const applied = context.applyMutationDefaults({ op: 'create', entry: tableName, namespace, values }); for (const def of applied) row[def.field] = def.value;
-
Extension authors:
OrderByItemcarries a null placement, andIncludeExprcarries key column lists. TheOrderByItemconstructor takes a required third argument,nulls, and a renderer that writesORDER BYitself must writeNULLS FIRSTorNULLS LASTafter the direction.IncludeExpr.localColumnandIncludeExpr.targetColumnare now the arrayslocalColumnsandtargetColumns, paired by index. Seeorder-by-item-nullsandinclude-expr-join-column-listsin the extension recipe. (#30402, #30107)
Features
- Order by a related row's column, a relation count, and null placement. Inside
orderBy, a to-one relation offers the related model's columns (post.author.name.asc()), a to-many relation offerscount()with an optional filter (user.posts.count().desc()), and everyasc()anddesc()accepts{ nulls: 'first' | 'last' }. (#30402) prisma contract printwrites the configured contract as Prisma 8 PSL. The command loads whatevercontractnames in the config (a Prisma 7 schema, a TypeScript contract or a PSL contract) and prints it as a Prisma 8 PSL file that emits the same contract, including its hashes. It refuses, by name, any part of the contract that PSL cannot express. Use--output <path>to write a file. (#30315)- A Prisma 6 MongoDB project can use its existing
schema.prismaas the contract source. Setcontract: prisma6Schema('prisma/schema.prisma')withprisma6Schemafrom@prisma/orm-mongo/config. Anything the reader cannot express is reported as an error with aPSL.PRISMA6_MONGO_*code. (#30405) - Mongo schemas can declare
Int64,Decimal128,Binary,JsonandBsonfields. The ORM reads the first four asbigint, decimal text,Uint8Arrayand a JSON value. ABsonfield holds any BSON value. The TypeScript helpers arefield.int64(),field.decimal128(),field.binary(),field.json()andfield.bson(). (#30396, #30439) - Mongo schemas can declare automatic timestamps.
temporal.createdAt()andtemporal.updatedAt()fill the field on create, andtemporal.updatedAt()advances it on every update that writes something. (#30403) prisma contract inferprints Postgres array defaults as literal lists. A default such as'{a,b}'::text[]on a text, varchar, enum, date or boolean array column now prints as@default(["a", "b"])instead of a rawsqlexpression. (#30436)
Fixes
- An ORM
include()across a composite foreign key matches on every key column. It used to match on the first column only, which returned related rows that belonged to other parents. Nested writes and multi-table variants use the whole key too. (#30107) - A Mongo ORM query that combines
select()withinclude()returns the included relations. (#30170) mongo()accepts a connection string that lists several hosts, and the CLI masks the credentials of such a string in its output. (#30354)- A Postgres migration that removes a column and a row-level security policy that references it drops the policy first, so the migration applies. (#30232)
limit()andoffset()refuse a value that is not a non-negative integer, such asNaN, withRUNTIME.AST_INVALID, instead of writing it into the SQL. (#30133)- The Postgres warning for an identifier that is too long measures the name in bytes, as Postgres does, so it now fires for a long name written in non-ASCII characters. (#30127)
- A number inside a
json[]orjsonb[]array default, such as'{1,true}'::jsonb[], is read as a JSON number, not as text. (#30455) createAll(rows, { onConflict: 'skip' })on a variant stored in its own table reports an error that names the unsupported option, and the help forprisma migration new --fromnames the correct default, thedbref. (#30427)
New contributors
- @rajat12826 made their first contribution in #30170
- @xia-chao made their first contribution in #30133
- @MahathirMohammadShuvo made their first contribution in #30127
- @Punisheroot made their first contribution in #30232