github prisma/orm v8.0.0-rc.19

pre-release6 hours ago

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 @default means a database default. @default(uuid()), @default(cuid(2)), @default(ulid()) and @default(nanoid()) are refused with PSL_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 print now writes generated values as @onCreate(...) and @onUpdate(...). See client-functions-move-from-default-to-on-create and contract-print-writes-generator-attributes in 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 contractJson option and the type argument on the client call are removed from postgres(), postgresServerless(), sqlite(), supabase(), mongo(), postgresStatic(), sqliteStatic(), mongoStatic() and mongoQuery(). Turn the emitted JSON into a value once with contractFromJson<Contract>(contractJson) from the facade's /static entry, and pass { contract }. The app recipe ships a script that moves your source files; move test files by hand. The *OptionsWithContract and *OptionsWithContractJson types are replaced by PostgresOptions<Contract> and the matching types of the other facades. See client-takes-contract-value in 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 emit refuses a quoted date, time, interval or bytes default and names the tag to write. A bytea default takes hexadecimal, not base64. A PostgreSQL contract that stores a default in another form than its column's type gives, such as "1.5" on numeric(10,2), is refused by the CLI: run prisma contract emit, then prisma 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 throws RUNTIME.DECODE_FAILED. A SQLite DateTime text with no UTC offset is read as UTC. Run prisma db sign against every database after the upgrade. See date-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-arrays and sign-databases-after-data-type-values in 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`)
  • @noCheck is removed, and a list that allows null elements is written T?[]. A stored contract with a noCheck key no longer loads. First run the app recipe's script, which rewrites stored contracts and migration snapshots, then run prisma db sign against every database. Then replace T[] @noCheck(elementNotNull) with T?[] and delete @noCheck(membership); in TypeScript, use .many({ elementsNullable: true }). A contract read from a Prisma 7 schema now types list elements as T | null, and the Supabase extension's four text[] columns allow null elements; both change the storage hash, so sign your databases. See stored-contracts-drop-nocheck, psl-nocheck-removed, ts-nocheck-method-removed, prisma7-lists-allow-null-elements and supabase-contract-lists-allow-null-elements in the app recipe. Extension authors run the same script from the extension package root, then re-emit the bundled contract; see stored-contracts-drop-nocheck, psl-nocheck-removed and ts-nocheck-method-removed in the extension recipe. (#30741)

    Before:

    tags String[] @noCheck(elementNotNull)

    After:

    tags String?[]
  • Type-constructor arguments are values. A quoted number, such as VarChar("255") or Timestamp("3"), is refused with PSL_VALUE_TYPE_INCOMPATIBLE; write the number without quotes. On Postgres a whole number written with a fraction, such as VarChar(255.0), is refused too, and so is a number the type cannot hold. The same applies to a field preset's precision, such as temporal.timestamp("3"). See type-constructor-arguments-are-values in 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 @default is. A quoted member is refused on a codec whose type is not text, such as pg/numeric@1 or pg/int8@1; write numbers unquoted and dates and times with their tag. Enums typed by pg/jsonb@1, pg/timetz@1, pg/enum@1, arktype/json@1, pg/vector@1 or pg/geometry@1 are refused; use a text enum, or a native enum instead of pg/enum@1. Members of a pg/numeric@1, pg/interval@1 or mongo/decimal128@1 enum compare as values, so 0.5 and 0.50 are duplicates. Columns of more enum codecs are now typed as the union of their members; re-emit the contract. See psl-enum-members-read-by-the-cast-rule, more-codecs-refused-for-enums, enum-members-compare-by-their-data-type and enum-columns-typed-by-member-literals in 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, cuid or nanoid does not take, such as uuid(5), is now refused, in Prisma 8 PSL with PSL_INVALID_GENERATOR_ARGUMENT and in a Prisma 7 schema with PSL.PRISMA7_UNKNOWN_DEFAULT; it used to be read as something else. A number with a fraction, such as nanoid(8.0), is refused too. A generator on a Postgres column too short for what it writes, such as VarChar(5) @onCreate(uuid()), is refused when the contract is built instead of failing at the first insert. A SQL @@discriminator that is not text has a new message. See prisma7-default-function-arguments-checked, generator-function-argument-diagnostics, generator-function-argument-with-fraction, generator-fits-column and discriminator-must-be-text in the app recipe. (#30792)

  • Re-emit contract.d.ts, and expect more precise types from defineContract. prisma contract emit writes field and column types as generics applied to the contract, so a SQL contract.d.ts emitted by an earlier release no longer compiles; re-emit it. A contract built with defineContract now gets the same ORM types as an emitted one: typed include rows, aggregates, query operations, parameterized types such as Varchar<50>, index names in where and orderBy callbacks, unique keys in whereUnique, and its enums. A Mongo defineContract contract types lists and value objects as readonly, and its factory form no longer types every field as never. Casts that only worked because a type was unknown or never may now fail to compile. See typescript-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-narrow and mongo-update-accessors-take-input-types in the app recipe. (#30709, #30747)

  • A MongoDB field keeps its schema name in code. A field declared id ObjectId @id @map("_id") is the field id in the contract, in ORM rows and in where, select, orderBy and write data, where it was _id. The contract records the stored name in model.storage.fields, and a contract without it is refused with CONTRACT.VALIDATION_FAILED. Run prisma contract emit, then the app recipe's script, which rewrites migration snapshots. Storage hashes do not change. --rename and --delete name fields by their schema names. @map on 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' }). See mongo-contracts-record-stored-field-names, mongo-orm-uses-field-names, mongo-statements-name-fields, mongo-composite-field-map-refused and mongo-typescript-stored-field-names in 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 defineContract contract now gets the same closed $jsonSchema validator as a PSL contract, and its storage hash changes once. defineContract refuses what a validator cannot describe, such as an _id that 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 with prisma db update, or with prisma migration plan and prisma db migrate. A collection shared by several models now gets a validator that accepts each of them; apply it the same way. See mongo-typescript-contract-validator and mongo-shared-collection-validator in the app recipe. (#30773)

  • Each SQL ORM call is one orm-call activity, 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 in activityStarted from the orm-call activity. The cache middleware caches an annotated ORM read as a whole, keyed by cacheAnnotation({ key }) or by the call's content hash; deriveKey applies only to SQL builder queries. If your deriveKey separates tenants or users, give each annotated ORM read a tenant-prefixed key, or one tenant's cached ORM read can be served to another. A custom cache store holds CachedValue instead of CachedRows. A hand-built object passed to orm() that copies only query and execute now fails with ORM.ACTIVITY_SOURCE_MISSING. An ORM call still pending at close() now completes instead of rejecting. See orm-annotations-on-the-call, cached-orm-reads-key-on-the-call, cache-store-holds-cached-values, orm-needs-a-runtime-queryable and close-lets-open-orm-calls-finish in 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.json written by an earlier release keeps its old checks: run migration.ts again for each migration that is not applied yet. On PostgreSQL, this.setDefault no longer takes operationClass; delete the option. Widening an autoincrement() column now also widens its sequence; for a migration already applied, run ALTER SEQUENCE … AS bigint yourself. this.alterColumnType with using on a column that already has the new type is refused; write the conversion as a dataTransform. See setdefault-operation-class-derived, setdefault-has-no-postcheck, the postgres-postcheck-* entries, postgres-alter-column-type-unchanged-refused, postgres-autoincrement-sequence-follows-column-type, sqlite-postchecks-check-full-definition and mongo-postchecks-check-full-definition in the app recipe. (#30752)

  • A failed migration reports the runner's own error code. MIGRATION.RUNNER_FAILED no longer exists. A failed apply reports codes such as MIGRATION.SCHEMA_VERIFY_FAILED, MIGRATION.PRECHECK_FAILED or MIGRATION.POSTCHECK_FAILED, with typed meta.schemaIssues and meta.failedCheck. On a control client, dbInit, dbUpdate and migrate take a required invocation option, and their failures are CliStructuredErrors. See runner-failures-report-own-codes and control-client-apply-takes-invocation in the app recipe. (#30791)

  • prisma db schema --json names a column's type typeText. It holds the type as the database reports it, such as integer, in place of nativeType, and resolvedNativeType is gone. The tree output shows the same text. CONTRACT.DEFAULT_INVALID carries meta.typeText instead of meta.nativeType. See db-schema-json-names-type-text and default-invalid-meta-type-text in the app recipe. (#30777)

  • String timestamp columns are filled with text. The Postgres temporal.*String presets, and every @updatedAt field a prisma7Schema(...) contract reads, now fill the column with text instead of a Date. Re-emit the contract; only the execution hash changes. The pg/timestamp-string@1 and pg/timestamptz-string@1 codecs refuse a Date at runtime with RUNTIME.ENCODE_FAILED. See string-timestamp-presets-write-text and string-timestamp-codecs-take-text-only in the app recipe. (#30742)

  • using is a reserved member of every ORM collection. A custom collection class with its own member named using no longer compiles; rename it. See orm-collection-member-using-reserved in the app recipe. (#30717)

  • Objects that implement client or transaction types need the new members. Clients and transactions gain query, execute, toSql and 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. See client-query-and-to-sql in the app recipe and transaction-query-members in the extension recipe. (#30802, #30789)

  • Extension authors: activities, codecs and generator functions. RuntimeActivity is renamed Activity and now includes client activities, which an extension starts with startActivityOn; queryables and transactions carry an activity source. A codec implements toWire, fromWire, toDataTypeValue and fromDataTypeValue instead of encode, decode, encodeJson and decodeJson, and a data type's read replaces toCanonicalForm. 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 the uuid, cuid and nanoid signatures with sqlIntegerArgumentGeneratorFunctions. See runtime-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-value and text-data-types in 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 the string, boolean and stringArray kinds are removed. Columns are compared by data type, so the type-name normalisers and SqlTypeLookups and MongoTypeLookups are removed, schema IR columns have typeText, 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 of renderOutputType, and FieldTypesFromDomain moves to @prisma/orm-framework/contract/types. A MongoDB model's storage records each field's stored name, and deriveJsonSchema is replaced by deriveCollectionValidators. See value-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-validators and aggregate-operation-using-reserved in 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 on String, VarChar(n), Char(n) and Uuid. A column may have both a database @default and 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, including date, time and timetz, so @updatedAt works on @db.Date, @db.Time and @db.Timetz. A generator on an optional field generates a value the client leaves out and stores an explicit null. (#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 verify compares that text with the database, and the ORM reads and writes its values as unknown. The TypeScript builder has type.Raw(sql`...`) and rawColumn(sql`...`). (#30745, #30801)
  • A Prisma 7 schema with Unsupported(...) fields now loads through prisma7Schema(). 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 @updatedAt beside @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 }, and db.query(plan) and db.execute(plan) run a plan directly. postgres(), postgresServerless(), sqlite() and supabase() take mode: 'strict' | 'permissive'. (#30802)
  • Every SQL client takes custom collection classes as the collections option, and uses them on db.orm, on every transaction's tx.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 gains prepare, and SQLite's db.orm gains fragment. (#30789)
  • Packages contribute collection methods computed from a model, under using. The Postgres package adds a search method for each full-text index, such as db.orm.public.Post.using.post_search(query), and pgvector adds a nearest-neighbour search for each vector field. The methods chain with where, orderBy and limit. (#30717)
  • Middleware sees each SQL ORM call as one orm-call activity, with its statements as children, and can answer the whole call through intercept.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> or Numeric<10, 2> field accepts a plain string in where, create, update and the other write methods. (#30709)
  • A pg.enum column can name a native_enum declared in another namespace, such as pg.enum(auth.OrderStatus). (#30721)
  • prisma contract infer reads 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. New Bit, VarBit and Interval type constructors let those columns infer. (#30735)
  • prisma db verify and the migration planner compare column types as data types with their parameters, so numeric(10) matches numeric(10,0). A database column of a type no configured pack declares gets a message that says so. (#30777)
  • A SQL @@discriminator field may be a Char(n) or VarChar(n) column on Postgres, or a sql.String(n) column on SQLite. (#30792)

Fixes

  • A hand-written setDefault that 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 from int4 to int8 now 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 sql literal to write. (#30740)
  • The MongoDB ORM's typed where accepts null, an empty list and a whole list on list fields, and null on a nullable field. A null filter 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 get and unset take optional fields, so code that calls a store directly passes only the fields it needs, where it had to pass undefined for the others. cache.invalidate and cacheAnnotation accept undefined for their optional fields under exactOptionalPropertyTypes. Stores written against the old shape still fit. (#30822)

Don't miss a new orm release

NewReleases is sending notifications on new releases.