prisma-8

Use when working in a project that depends on @prisma/orm-postgres, @prisma/orm-sqlite, or @prisma/orm-mongo (Prisma 8, formerly Prisma Next): editing contract.prisma or a contract.ts builder, running `prisma contract emit`, planning or applying migrations, editing migration.ts, writing db.orm / db.sql / db.query queries, wiring db.ts or middleware, integrating a build tool, using the Supabase extension or RLS, or reading a dotted error code such as MIGRATION.HASH_MISMATCH. Use when the user asks "what is Prisma 8", "where do I start", or compares it to another ORM. Use when the user asks to upgrade or bump Prisma 8 in an app or an extension package. Use when you see @internal/* or @prisma/orm-* imports, prisma.config.ts with definePrismaConfig, or contract.json / contract.d.ts. Do not use for Prisma ORM 7 or earlier (schema.prisma + @prisma/client).

Install
npx skills add 'https://github.com/prisma/orm/tree/main/skills/prisma-8'
Download bundle ↓
main · fac8604Scanned 2026-09-17

Contributors

GitHub-linked commit authors for this SKILL.md at the saved revision. Co-authors and history before file renames are not included.

File history ↗

upgrading/extension/upgrades/0.13-to-0.14/instructions.md

upgrading/extension/upgrades/0.13-to-0.14/instructions.mdBrowse 76 files
View on GitHub
← Back to SKILL.md

from: "0.13" to: "0.14" changes:

  • id: uuid-preset-rename summary: | The uuid field presets are renamed: field.uuid()field.uuidString(), field.id.uuidv4()field.id.uuidv4String(), field.id.uuidv7()field.id.uuidv7String(). These names now describe the storage encoding (char(36) string). Postgres-native uuid storage uses the new field.uuidNative() / field.id.uuidv4Native() / field.id.uuidv7Native() presets from @internal/postgres/contract-builder. detection: glob: "**/*.ts" contains: - "field.uuid()" - "field.id.uuidv4()" - "field.id.uuidv7()" anyMatch: true script: uuid-preset-rename.ts
  • id: qualify-flat-builder-accessors summary: | The builder-layer flat accessors are removed: @internal/sql-builder's sql() and @internal/sql-orm-client's orm() now expose per-namespace facets only. Extension code that builds queries by accessing a bare table/model on the builder output (sql.<table> / orm.<Model>) must name the namespace the table/model is declared in: sql.<namespace>.<table>, orm.<namespace>.<Model> (public for a standard single-schema SQL contract; the late-bound __unbound__ namespace for an unbound/SQLite contract). There is no codemod — the correct namespace is the one each table/model is declared in, which is call-site-specific. Extensions that only contribute codecs, types, or migrations (and never build queries through sql/orm) are unaffected. detection: glob: "**/*.{ts,tsx}" contains: - "@internal/sql-builder" - "@internal/sql-orm-client" anyMatch: true
  • id: create-runtime-removed summary: | createRuntime is removed from @internal/sql-runtime. Extension code that constructed a runtime via createRuntime(...) must switch to the target class constructor directly: new PostgresRuntimeImpl({...}) from @internal/postgres/runtime, or new SqliteRuntimeImpl({...}) from @internal/sqlite/runtime. Pass the same options minus the stackInstance unpacking — supply adapter directly instead of stackInstance.adapter. detection: glob: "**/*.{ts,tsx}" contains: - "createRuntime"
  • id: migration-op-factories-to-methods summary: | The bare migration op factory functions are removed from @internal/postgres/migration (and the deprecated @internal/target-postgres/migration alias). Replace each import and call-site with the corresponding method on this inside your Migration subclass. The option shapes changed from positional arguments to a single options object. detection: glob: "**/migration.ts" contains: - "from '@internal/postgres/migration'" - "from '@internal/target-postgres/migration'" anyMatch: true script: migration-op-factories-to-methods.ts
  • id: namespace-entries-open-dict summary: | SqlNamespace.entries is now an open dictionary typed Readonly<Record<string, Readonly<Record<string, unknown>>>>. The previously closed shape ({ table?: ..., valueSet?: ... }) is gone — dot-access like .entries.table or .entries.collection no longer compiles. Read tables via the namespaceTables(ns) helper from @internal/sql-contract/types, or via bracket notation entries['table']. For typed getter access on the concrete class instances use the non-enumerable getters (ns.table, db.collection). Annotations and type constraints that hard-code the closed shape must be widened to the open dict. detection: glob: "**/*.{ts,tsx}" contains: - ".entries.table" - ".entries.collection" - ".entries.valueSet" anyMatch: true
  • id: enum-becomes-domain-concept summary: | The native Postgres enum surface is deleted from the SPI. PostgresEnumStorageEntry no longer exists in @internal/sql-contract/types — the SqlStorage.types slot now holds codec-instance entries only (StorageTypeInstance). The pg/enum@1 codec surface is deleted from @internal/target-postgres (PgEnumDescriptor, pgEnumColumn, PG_ENUM_CODEC_ID, the enum codec-type-map entry), as are the native enumType / enumColumn helpers from @internal/adapter-postgres/column-types. Enums are domain entities plus a storage valueSet enforced by a CHECK constraint; columns reference them as pg/text@1 (or another codec) with a valueSet ref. Extensions that referenced PostgresEnumStorageEntry in type constraints drop it (use StorageTypeInstance alone); fixtures that used pg/enum@1 as a codec id must switch to a live codec or an inert fixture id. detection: glob: "**/*.{ts,tsx}" contains: - "PostgresEnumStorageEntry" - "pg/enum@1" - "pgEnumColumn" - "PgEnumDescriptor" - "PG_ENUM_CODEC_ID" anyMatch: true
  • id: namespaced-type-resolution summary: | Per-namespace type resolution. The emitted TypeMaps ExtractFieldOutputTypes / ExtractFieldInputTypes (from @internal/sql-contract) now nest by namespace — { [namespace]: { [model]: { [field] } } } — and TableProxy (from @internal/sql-builder) takes a required namespace coordinate: TableProxy<C, Name> becomes TableProxy<C, NsId, Name>. Extension code that indexes those TypeMaps or constructs TableProxy types directly must thread the namespace coordinate (ExtractFieldOutputTypes<C>[namespace][Model][Field], TableProxy<C, namespace, Name>): public for a standard single-schema contract, the late-bound __unbound__ namespace for an unbound/SQLite contract. Extensions that only contribute codecs, native types, or migrations and never reference these types are unaffected. detection: glob: "**/*.{ts,tsx}" contains: - "ExtractFieldOutputTypes" - "ExtractFieldInputTypes" - "TableProxy<" anyMatch: true
  • id: contract-model-definitions-removed summary: | ContractModelDefinitions is removed from @internal/contract (and its /types export), along with the second TModels type parameter on Contract (now Contract<TStorage>). The flat cross-namespace model union it produced is gone; resolve models per-namespace instead. Replace ContractModelDefinitions<C> with C['domain']['namespaces'][<ns>]['models'] (use [keyof C['domain']['namespaces']] for the sole-namespace case). Family wrappers like MongoContract<S, M> become single-arg MongoContract<S>; carry precise per-model types via an explicit per-namespace domain override. detection: glob: "**/*.{ts,tsx}" contains: - "ContractModelDefinitions" - "Contract<" - "MongoContract<" anyMatch: true

0.13 → 0.14 — Extension-author upgrade instructions

uuid-preset-rename

The uuid field preset names now include the storage encoding suffix:

BeforeAfter
field.uuid()field.uuidString()
field.id.uuidv4()field.id.uuidv4String()
field.id.uuidv7()field.id.uuidv7String()

Apply the rename in any extension test files, contract fixture files, or documentation that uses these presets. The rename is mechanical — run the colocated script or apply the substitutions directly:

// Before
id: field.id.uuidv7(),
userId: field.id.uuidv4(),
externalId: field.uuid(),

// After
id: field.id.uuidv7String(),
userId: field.id.uuidv4String(),
externalId: field.uuidString(),

No change to emitted contract.json — both old and new preset names emit the same codec (sql/char@1).

qualify-flat-builder-accessors

The query builder (@internal/sql-builder) and ORM client (@internal/sql-orm-client) are now always qualified by namespace. The flat by-bare-name accessors are gone: the value returned by sql({ … }) / orm({ … }) is a map of per-namespace facets, so there is no sql.<table> and no orm.<Model> at the top level. You reach a table or model by naming its namespace.

This affects extension code that builds queries through these packages. Extensions that only contribute codecs, native types, or migration operations — and never construct a sql/orm query — need no change.

Migrate query-building call sites

Insert the namespace segment after the builder output, naming the namespace each table/model is declared in:

// Before
const plan = sql.user.select('id', 'email').build();
const row  = await orm.User.find({ where: { id } });

// After — name the namespace (`public` for a standard single-schema SQL contract)
const plan = sql.public.user.select('id', 'email').build();
const row  = await orm.public.User.find({ where: { id } });

For an unbound contract (e.g. SQLite, or any target whose entities live in the late-bound namespace) the namespace segment is __unbound__ — import UNBOUND_NAMESPACE_ID from @internal/framework-components/ir and index with it (sql[UNBOUND_NAMESPACE_ID].user) rather than hard-coding the string. For a multi-namespace contract, name the specific namespace each table/model sits in.

Validation

This is a type-level change — pnpm typecheck (or pnpm build) pinpoints every remaining flat access as a compile error (Property '<table>' does not exist on type 'Db<…>'). Fix each by inserting the namespace segment, then run your extension's standard pnpm test.

migration-op-factories-to-methods

The bare op factory functions previously exported from @internal/postgres/migration (and the deprecated @internal/target-postgres/migration alias) are removed. Each function is now a protected method on the PostgresMigration base class — call it as this.<method>(...) inside your extension's Migration subclass.

The option shapes also changed: positional arguments are replaced by a single options object.

Remove the bare names from your import and replace each call-site:

Before (bare function)After (method)
dropColumn(schema, table, column)this.dropColumn({ schema, table, column })
setNotNull(schema, table, column)this.setNotNull({ schema, table, column })
setDefault(schema, table, column, defaultSql)this.setDefault({ schema, table, column, defaultSql })
addPrimaryKey(schema, table, name, columns)this.addPrimaryKey({ schema, table, constraint: name, columns })
addForeignKey(schema, table, { name, columns, references, onDelete })this.addForeignKey({ schema, table, foreignKey: { name, columns, references, onDelete } })
addCheckConstraint(schema, table, name, column, values)this.addCheckConstraint({ schema, table, constraint: name, column, values })
createIndex(schema, table, indexName, columns)this.createIndex({ schema, table, index: indexName, columns })
installExtension({ id, extensionName, invariantId })this.installExtension({ id, extensionName, invariantId })

Example (extension migration):

// Before
import { installExtension, Migration, MigrationCLI } from '@internal/target-postgres/migration';

override get operations() {
  return [
    installExtension({
      id: 'my-ext.install',
      extensionName: 'my_extension',
      invariantId: MY_INVARIANTS.install,
    }),
  ];
}

// After
import { Migration, MigrationCLI } from '@internal/target-postgres/migration';

override get operations() {
  return [
    this.installExtension({
      id: 'my-ext.install',
      extensionName: 'my_extension',
      invariantId: MY_INVARIANTS.install,
    }),
  ];
}

The colocated script applies this transformation automatically. Run it from your extension root:

pnpm exec tsx .claude/skills/prisma-8/upgrading/extension/upgrades/0.13-to-0.14/migration-op-factories-to-methods.ts

create-runtime-removed

createRuntime is removed from @internal/sql-runtime. Construct the target runtime class directly instead.

// Before
import { createRuntime } from '@internal/sql-runtime';
const runtime = createRuntime({ stackInstance, context, driver, ...opts });

// After — Postgres
import { PostgresRuntimeImpl } from '@internal/postgres/runtime';
const runtime = new PostgresRuntimeImpl({ adapter: stackInstance.adapter, context, driver, ...opts });

// After — SQLite
import { SqliteRuntimeImpl } from '@internal/sqlite/runtime';
const runtime = new SqliteRuntimeImpl({ adapter: stackInstance.adapter, context, driver, ...opts });

The options are identical except stackInstance is no longer passed: supply adapter from stackInstance.adapter directly. Depend on the bare-name interfaces (PostgresRuntime, SqliteRuntime) for type annotations, not the Impl classes.

namespace-entries-open-dict

The entries property on every namespace class is now an open dictionary:

// The type is now:
entries: Readonly<Record<string, Readonly<Record<string, unknown>>>>

// Previously it was a closed shape:
entries: {
  table?: Readonly<Record<string, StorageTable>>;
  valueSet?: Readonly<Record<string, StorageValueSet>>;
}

Dot-access like .entries.table or .entries.collection no longer compiles. Migrate to one of the two canonical read styles:

Generic/walker code — bracket notation:

// Before
const tables = ns.entries.table;

// After — bracket notation
const tables = ns.entries['table'] as Record<string, StorageTable> | undefined;

Typed family/target code — use the exported family helpers or the class getters:

// Using the namespaceTables() helper (for SqlNamespace values)
import { namespaceTables, namespaceValueSets } from '@internal/sql-contract/types';
const tables = namespaceTables(ns);    // Record<string, StorageTable>
const vsets  = namespaceValueSets(ns); // Record<string, StorageValueSet> | undefined

// Using the namespaceCollections() helper (for MongoNamespace values)
import { namespaceCollections } from '@internal/mongo-contract';
const collections = namespaceCollections(ns); // Record<string, MongoCollection>

Type annotations — widen any closed-shape annotation to the open dict:

// Before
const ns = namespaces[id] as { entries: { table: Record<string, StorageTable> } };

// After — open dict annotation
const ns = namespaces[id] as { entries: Record<string, Record<string, unknown>> };
// then narrow via the helper:
const tables = namespaceTables(ns);

This is a compile-time-only change when using the helpers — no runtime behavior differs. Run pnpm typecheck to find all remaining dot-access sites.

enum-becomes-domain-concept

Native Postgres enums are removed from the framework. Enums are now a domain concept: a domain enum entity plus a storage valueSet entity, with member values stored through an ordinary codec (typically pg/text@1 → a text column) and the value set enforced by a planner-generated CHECK constraint. The whole native surface is deleted:

  • PostgresEnumStorageEntry is gone from @internal/sql-contract/types. The polymorphic SqlStorage.types slot now carries codec-instance entries only. Type constraints that accepted both narrow to StorageTypeInstance:

    // Before
    import type { PostgresEnumStorageEntry, StorageTypeInstance } from '@internal/sql-contract/types';
    type TypesConstraint = Record<string, StorageTypeInstance | PostgresEnumStorageEntry>;
    
    // After
    import type { StorageTypeInstance } from '@internal/sql-contract/types';
    type TypesConstraint = Record<string, StorageTypeInstance>;
  • The pg/enum@1 codec and its registry surface are deleted from @internal/target-postgres: PgEnumDescriptor, pgEnumColumn, PG_ENUM_CODEC_ID, and the enum entry in the codec type map. Test fixtures that used 'pg/enum@1' as an opaque codec id must switch to a live codec id or an inert fixture id (e.g. app/test-enum@1) — the id no longer resolves to a registered codec.

  • The native enumType(name, values[]) / enumColumn(...) authoring helpers are deleted from @internal/adapter-postgres/column-types. The domain authoring surface is enumType(name, codecRef, ...member(name, value)) + member from the target contract-builder, returned under the contract's enums key.

  • Introspection no longer adopts native enum types: the adapter records detected native enum type names under annotations.pg.nativeEnumTypeNames (names only), and contract infer refuses with a diagnostic naming them. The old annotations.pg.enumTypes structure is gone.

Columns restricted to an enum now carry codecId: 'pg/text@1' (or another codec), nativeType: 'text', and a valueSet reference; the owning table carries a check entry. If your extension reads storage.types looking for enum shapes, read the namespace's valueSet entries instead.

Validation

pnpm typecheck flags every deleted-symbol reference. After fixing, run your extension's standard pnpm test.

namespaced-type-resolution

The SQL/ORM type machinery now resolves columns, fields, and models by namespace coordinate rather than by bare name across all namespaces. Two type-shape changes affect extension code that depends on these types directly:

  1. Emitted TypeMaps nest by namespace. ExtractFieldOutputTypes<C> / ExtractFieldInputTypes<C> (from @internal/sql-contract) now return { [namespace]: { [model]: { [field]: <type> } } } instead of the flat { [model]: { [field] } }. Index the namespace first:
// Before
type Row = ExtractFieldOutputTypes<C>['User'];
// After — name the namespace the model is declared in
type Row = ExtractFieldOutputTypes<C>['public']['User'];
  1. TableProxy takes a required namespace coordinate. TableProxy<C, Name> (from @internal/sql-builder) becomes TableProxy<C, NsId, Name>:
// Before
let p: TableProxy<C, 'users'>;
// After
let p: TableProxy<C, 'public', 'users'>;

Use public for a standard single-schema SQL contract; for an unbound/SQLite contract use the late-bound namespace (UNBOUND_NAMESPACE_ID from @internal/framework-components/ir); for a multi-namespace contract, name the namespace each model/table actually sits in. There is no codemod — the correct namespace is call-site-specific. pnpm typecheck pins every remaining flat access (Property '<model>' does not exist on type '{ public: ... }'). Extensions that only contribute codecs, native types, or migrations — and never reference ExtractFieldOutputTypes / ExtractFieldInputTypes / TableProxy directly — need no change.

contract-model-definitions-removed

ContractModelDefinitions is removed from @internal/contract (and the @internal/contract/types re-export), and the Contract interface loses its second TModels type parameter — Contract<TStorage, TModels> becomes Contract<TStorage>. The flat, first-name-wins cross-namespace model union is gone; models resolve per-namespace from the domain plane.

Replace any ContractModelDefinitions<C> use with a read of a namespace's models:

// Before
import type { Contract, ContractModelDefinitions } from '@internal/contract/types';
type Models = ContractModelDefinitions<C>;
type UserModel = Models['User'];

// After — read the sole namespace's models (or name a specific namespace)
type Models = C['domain']['namespaces'][keyof C['domain']['namespaces']]['models'];
type UserModel = Models['User'];

If you need a bare model shape rather than the contract's own models, use ContractModelBase from @internal/contract/types. Family contract aliases drop their model parameter too — MongoContract<S, M> becomes MongoContract<S>. When you build a contract type that must carry precise per-model shapes (e.g. a test fixture or a defineContract result type), override the domain explicitly:

type MyContract = Omit<Contract<MyStorage>, 'domain'> & {
  readonly domain: {
    readonly namespaces: {
      readonly public: { readonly models: MyModels };
    };
  };
};