github prisma/orm v8.0.0-rc.23

pre-release2 hours ago

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 in index.users.create(email_1). Two indexes of one collection that would get one name, such as a unique and a sparse index on email, are refused; name one of them. A contract read with prisma6Schema() names each index as Prisma 6 did, such as User_email_key. Run prisma contract emit, run the script the app recipe ships to name the indexes of your migration snapshots, then run prisma db sign or plan a migration, which has no operations. Extension authors: MongoIndex, MongoSchemaIndex and their input types take a required name, CreateIndexCall and DropIndexCall take the index's name and options, and code that calls buildMongoContractFromDefinition calls nameIndexes first. See mongo-contracts-record-index-names, mongo-unnamed-indexes-with-one-name-refused, mongo-prisma6-index-names and mongo-index-operations-by-name in the app recipe, and mongo-index-name-required, mongo-index-calls-take-name and mongo-build-refusal-index-name-duplicate in 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 with PSL.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; run prisma db sign again. Prisma 8 PSL and TypeScript still merge identical and redundant indexes. Extension authors: the MongoDB contract builder no longer merges indexes, BuildRefusal has the kind indexDuplicate again, and mergeRedundantIndexes merges before building. See mongo-prisma6-indexes-as-prisma6-reads-them in the app recipe, and mongo-build-refusal-index-duplicate-restored in 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 sign and db verify accept the extension in any schema on the connection's search path, such as Supabase's extensions. A project that composes one of these packs runs prisma contract emit, which writes the pack's new migration, and then prisma db migrate, which applies it without changing the database. An application may no longer declare an extension that a composed pack declares; remove its own extension vector {} block or extension('vector') handle. Extension authors: a pack declares its Postgres extension in the unbound namespace, or the configuration is refused with CONTRACT.PACK_CONTRIBUTION_INVALID. See pack-extension-found-on-search-path and remove-pack-extension-declaration in the app recipe, and pack-declares-extension-unbound in the extension recipe. (#30956)

  • mongo() and mongoStatic() refuse a contract whose extension packs they are not given. They take an extensions option, as postgres() does. A MongoDB project whose prisma.config.ts composes extension packs passes each pack's runtime descriptor to mongo({ extensions }) and mongoStatic({ extensions }); otherwise they fail with RUNTIME.MISSING_EXTENSION_PACK. A MongoDB contract.json must have "target"; every contract prisma contract emit writes has it. Extension authors: createMongoExecutionContext checks the contract against the stack, a MongoDB descriptor declares each codec once as a codec descriptor instead of in a codecs() slot, and mongoStandardCodecs and buildStandardCodecRegistry are removed. See mongo-client-takes-extensions and mongo-contract-requires-target in the app recipe, and mongo-execution-context-checks-contract-requirements, mongo-codecs-declared-once and mongo-standard-codecs-removed in 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() and postgres() check their database input when they are called. A wrong input fails with RUNTIME.BINDING_INVALID at once, not on the first query. Passing both url and pg to supabase() fails; before, pg won silently. An empty url, or one that is not postgres:// or postgresql://, fails; before, an empty one connected with the PG* environment variables or to localhost. null as pg, url or binding fails, even beside another input, so leave the option out instead. A binding of an unknown kind fails. supabase() refuses a pg.Client with the message "Supabase takes a pg.Pool or a url, not a pg.Client". See supabase-one-binding-input and postgres-checks-database-input in 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, VarChar and VarBit still work but report the warning PSL_DEPRECATED_SCALAR_NAME. Write the convenience type that gives the same type instead, such as String(storage: varchar, length: 12) for VarChar(12); the contract does not change. The same TypeScript helpers, such as type.VarChar(...), are deprecated too. The column helpers int8Column and numericColumn are renamed int8BigIntColumn and numericStringColumn, and the old names are removed. Extension authors: postgresScalarAuthoringTypes, postgresNativeAuthoringTypes and postgresScalarAndNativeTypeConstructors are replaced by postgresScalarTypes and postgresExplicitTypes. See psl-renamed-postgres-scalar-types, ts-renamed-postgres-scalar-helpers and ts-renamed-postgres-column-helpers in the app recipe, and the same ids plus ts-renamed-postgres-type-groupings in 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-polyfill is a peer dependency of the MySQL and SQLite packages. The MySQL and SQLite control planes, such as the prisma commands, now import it. npm, pnpm and bun install it for you. A project that installs with Yarn adds temporal-polyfill (^1.0.4) to its dependencies. A MySQL DateTime field now refuses to write a year before 1000 or after 9999 with RUNTIME.ENCODE_FAILED. On SQLite, db verify reads a database default of date('now') as the current date, so it matches only a Date @default(now()) field. Extension authors: mysqlCreateNamespace, MysqlDatabase, sqliteCreateNamespace and SqliteUnboundDatabase move from the target's /target/control entry to its /target/types entry. See temporal-polyfill-is-a-peer-dependency-of-mysql, mysql-datetime-writes-are-checked and temporal-polyfill-is-a-peer-dependency-of-sqlite in the app recipe, and the same ids plus mysql-create-namespace-moved-to-target-types and sqlite-create-namespace-moved-to-target-types in the extension recipe. (#30922, #30950)

Features

  • On PostgreSQL, Int, BigInt, Float, Decimal, String, Boolean and Bytes are convenience types, as DateTime is. Each is named for what a field means, and its optional storage argument (and as, for BigInt) chooses an explicit type named after the database type, such as Int(storage: int2) for Int2 or BigInt(as: number) for Int8Number. 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, Time and Duration. as: chooses the value the application reads, such as a JavaScript Date, a string or a Temporal value, and on MySQL storage: chooses the column, such as DateTime(storage: timestamp). A bare DateTime is the type DateTime was before, so every existing SQLite and MySQL contract emits the same. (#30922, #30950)
  • ORM create, update, upsert and delete work on MySQL and return the written rows, as on PostgreSQL. MySQL has no RETURNING, 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 plan and db update take --rename GeoPoint.lat:GeoPoint.latitude, --delete GeoPoint.lng and --backfill GeoPoint.country for a value object field, rename or delete an enum value a value object holds, and take --convert Bug or --delete Bug for a variant whose discriminator value changed or that was removed. Each statement acts on every column that holds the value object. A failed migration's rowsHoldingValue detail is now a list of every removal whose check failed. (#30929)
  • supabase() takes the options postgres() takes, including cursor, which streams rows from a server-side cursor, and binding. (#30920)
  • A Supabase database whose pgvector, PostGIS, ParadeDB or citext extension lives in schema extensions adopts Prisma 8 with prisma contract emit and prisma 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)

Don't miss a new orm release

NewReleases is sending notifications on new releases.