github prisma/orm v8.0.0-rc.20

pre-release4 hours ago

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-engine 0.9.0, up from 0.7.0, the engine the prisma CLI 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 --json to every script, CI step or program that reads JSON from prisma contract …, prisma db …, prisma migration … or prisma orm init. A project that depends on @prisma/cli-engine itself moves to 0.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 list prints space, name, from, to and operation count, one line per migration. db init, db update, db verify, db schema and migration ref delete print nothing on stdout; read their exit code, or pass --json. See piped-output-needs-json and engine-pin-moves-to-0-9-0 in 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 in storage.types. Every contract that uses an alias gets a new storage hash. A stored contract whose columns still name an alias in typeRef no longer loads. Run the app recipe's script, which rewrites stored contracts and migration snapshots, then run prisma contract emit, then prisma db sign against every database. The database does not change. In the TypeScript builder, the types key is removed and field.namedType(...) takes only an enum: write an alias as a constant field.column(type.VarChar(64)). The Supabase extension's contract changes hash for the same reason, so sign a Supabase database too. See stored-contracts-expand-type-aliases, ts-types-key-removed and supabase-contract-expanded in 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 under databaseTypes instead of types. ColumnTypeDescriptor.typeRef, StorageColumn.typeRef and ExecutionContext.types are removed. See stored-contracts-expand-type-aliases, types-key-is-database-types and column-type-ref-removed in 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 typed T | undefined for such a column is an error too; check it before the insert. In ORM create and upsert, 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. See sql-builder-insert-required-columns and orm-create-on-update-only-field in the app recipe, and sql-builder-insert-required-columns in 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, NaN and the infinities unquoted, and dates and binary values with their tags. An Int32 or Int64 member written with a fraction, such as 1.0, is refused; write 1. A @@discriminator field must be required and hold one value, on SQL and MongoDB alike. When an enum types a discriminator, each @@base value must be one of its members. A value the receiving type refuses now reports PSL_VALUE_REFUSED. A Decimal128 value with leading zeros in contract.json, such as "007.5", is refused when the contract loads; re-emit the contract. See mongo-quoted-enum-members, mongo-integer-enum-members-with-a-fraction, discriminator-optional-or-list, discriminator-base-value-not-a-member, psl-value-refused-code and decimal128-spelling in the app recipe. Extension authors: AuthoringEntityContext takes dataTypes in place of dataTypeLookup, the readWrittenValueForCodec hook is removed, and an enum block needs writtenValues. See entity-context-data-types and enum-block-members-written-values in 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 its pg option takes a pool only. SupabaseRuntime no longer has queryWithRole, executeWithRole, preparedQueryWithRole or preparedExecuteWithRole. Call runtime.forRole(binding) once and use its query, execute and connection members. The pg option of supabase() takes a pg.Pool; a pg.Client fails with SUPABASE.CONFIG_INVALID. db.close() does not end a pool you pass in. RoleBoundDb, ServiceRoleDb and the transaction context gain enums and nativeEnums, so test doubles of them must add those members. After db.close(), a query on any role-bound client rejects with DRIVER.NOT_CONNECTED "Runtime is closed", and close() waits for open role transactions. See supabase-for-role-replaces-with-role-methods, supabase-role-types-gain-members, supabase-pg-takes-a-pool and supabase-close-waits-for-work in the app recipe. Extension authors: SqlRuntimeBase.acquireRawConnection() now follows the close rule of connection(), and a runtime that shares its driver is built with ownsDriver: false. See supabase-for-role-replaces-with-role-methods, supabase-role-types-gain-members, supabase-pg-takes-a-pool and raw-connection-obeys-close in 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 accessWidening is renamed accessChanges. When the contract no longer declares a namespace, migration plan and db update now 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 with CLI.CONSENT_REQUIRED until you answer. The list of operations that change who can read or write a model's data is now accessChanges, in db update --dry-run --json and the control API, and the option acceptAccessWidening is now acceptAccessChanges. The question ends with "who can read and write its data" in place of "its rows". See removed-namespace-questions and access-changes-rename in the app recipe. Extension authors: SchemaTables gains hasNamespace, SchemaOwnership gains namespaceDeclaringSpaces, and a resolved rename can name a namespace. See access-changes-rename, statement-planning-target-namespaces, schema-ownership-namespace-spaces and namespace-rename-statement in 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 --json and db sign --json report schema differences in meta.schemaIssues. The CONTRACT.SCHEMA_VERIFICATION_FAILED diagnostic carried label strings in meta.issues. It now carries issue objects in meta.schemaIssues, the same key and shape as MIGRATION.SCHEMA_VERIFY_FAILED. Each issue has a path, and expected and actual. A failed apply also lists each way out as a separate entry in nextActions, and some entries change kind. See schema-verification-failed-schema-issues in the app recipe. (#30814)

Features

  • A type alias in PSL can set @default, @onCreate and @onUpdate, and each field typed by it gets them, such as types { UpdatedAt = TimestampString(3) @onCreate(now()) @onUpdate(now()) }. Postgres, SQLite and MongoDB provide temporal.CreatedAt and temporal.UpdatedAt, and Postgres also has String and JsDate variants. MongoDB schemas can now use aliases. The TypeScript builder has a type.* helper for every type PSL can write, such as type.VarChar(64). (#30819)
  • Postgres migrations can rename and move namespaces. --rename billing:finance renames the schema, and --rename public.Invoice:billing.Invoice moves a table into another schema with its rows, constraints, indexes and policies. migration.ts gains this.renameSchema, this.setTableSchema and this.dropSchema. (#30823)
  • prisma migration plan can change a relation's onDelete or onUpdate, 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 with MIGRATION.PLANNING_FAILED. (#30774)
  • Prisma 7's prisma migrate dev, deploy, status, diff, reset and resolve exit with CLI.COMMAND_MOVED and 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 an expectation in ops.json, and a check you write in a rawSql operation 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 undefined to skip a preset argument no longer loses the arguments after it, so field.temporal.timestamptz(undefined, 'now') types correctly. (#30830)
  • db migrate no longer says a failed apply changed nothing on MongoDB when operations before the failure stayed applied. (#30814)

Don't miss a new orm release

NewReleases is sending notifications on new releases.