v0.14.0
v0.14.0
This release reshapes the enum surface (PSL enum is now a domain concept backed by a value-set CHECK constraint, not a native Postgres type), makes the SQL builder always-qualified by namespace, adds native UUID storage on Postgres, ships a new fault-tolerant PSL parser, completes the read side of many-to-many (correlated includes plus some / every / none filters through the junction), and adds a Supabase façade alongside several runtime-class renamings. Most breaking changes have a matching codemod or upgrade recipe.
Breaking changes
-
PSL
enumbecomes the domain enum — anenumblock now authors a text-class column whose value set is enforced by a CHECK constraint, not a nativeCREATE TYPE … AS ENUM. Each block must declare@@type("<codec-id>")(typicallypg/text@1) and map members to database values withName = "value". The transitionalenum2keyword is retired (rename toenum— emitted contract is identical). Native enum machinery is deleted:enumType(name, values[])/enumColumnfrom@prisma-next/adapter-postgres/column-types, thepg/enum@1codec, and adoption of native enum types incontract inferare all gone. Databases carrying a native enum type need a one-time converting migration (ALTER column totextUSING::text, add the value-set CHECK,DROP TYPE) —contract inferrefuses native enum types and names them. See the 0.13→0.14 upgrade recipe and the extension-author recipe. (#817)Before:
enum user_type { admin user }
After:
enum user_type { @@type("pg/text@1") admin = "admin" user = "user" }
-
Query builder and ORM are always qualified by namespace — the flat by-bare-name accessors are removed at the builder layer; the Postgres facade exposes the namespaced surface. On Postgres,
db.sql.<table>becomesdb.sql.<namespace>.<table>anddb.orm.<Model>becomesdb.orm.<namespace>.<Model>(publicfor a standard single-schema project). Direct builder calls (sql.<table>,orm.<Model>) migrate the same way. SQLite and Mongo are unaffected — their single-namespace facade keeps the flat surface working. No codemod: the correct namespace is the one each table/model is declared in. The generatedcontract.d.tsalso drops the flat top-levelexport type Models— read models per-namespace asContract['domain']['namespaces']['<namespace>']['models']and re-emit. See the 0.13→0.14 upgrade recipe. (#778)Before:
const users = await db.sql.user.select('id', 'email').build().execute(); const alice = await db.orm.User.find({ where: { id } });
After:
const users = await db.sql.public.user.select('id', 'email').build().execute(); const alice = await db.orm.public.User.find({ where: { id } });
-
UUID field presets renamed by storage encoding —
field.uuid()→field.uuidString(),field.id.uuidv4()→field.id.uuidv4String(),field.id.uuidv7()→field.id.uuidv7String(). The new names describe thechar(36)storage encoding (the emitted codec,sql/char@1, is unchanged). Postgres-nativeuuidcolumns use the newfield.uuidNative()/field.id.uuidv4Native()/field.id.uuidv7Native()presets from@prisma-next/postgres/contract-builder. The rename is mechanical — a colocated codemod ships in the 0.13→0.14 upgrade recipe. (#810)Before:
id: field.id.uuidv7(), externalId: field.uuid(),
After:
id: field.id.uuidv7String(), externalId: field.uuidString(),
-
Postgres migration op factories become methods on
Migration— the bare op factory functions previously exported from@prisma-next/postgres/migration(and the@prisma-next/target-postgres/migrationalias) are removed. Each is now a protected method on thePostgresMigrationbase class — call it asthis.<op>(...). Positional arguments are replaced by a single options object. A codemod ships in the 0.13→0.14 upgrade recipe. (#813)Before:
import { addForeignKey, dropColumn } from '@prisma-next/postgres/migration'; override get operations() { return [ dropColumn('public', 'user', 'legacyName'), addForeignKey('public', 'post', { name: 'post_userId_fkey', columns: ['userId'], references: { schema: 'public', table: 'user', columns: ['id'] } }), ]; }
After:
override get operations() { return [ this.dropColumn({ schema: 'public', table: 'user', column: 'legacyName' }), this.addForeignKey({ schema: 'public', table: 'post', foreignKey: { name: 'post_userId_fkey', columns: ['userId'], references: { schema: 'public', table: 'user', columns: ['id'] } } }), ]; }
-
SQL runtime class renames —
@prisma-next/sql-runtimeexportsabstract class SqlRuntimeBase(previouslySqlRuntime). The bare namesPostgresRuntimeandSqliteRuntimeare now interfaces — the types to depend on in extension and app code. The concrete classes arePostgresRuntimeImpl(from@prisma-next/postgres/runtime) andSqliteRuntimeImpl(from@prisma-next/sqlite/runtime). Code that referenced the class names to subclass them switches to theImplnames. Code using the facade factories (postgres(...),sqlite(...)) is unaffected. (#806) -
createRuntimeremoved from@prisma-next/sql-runtime— use the target facade factory (postgres(...)/sqlite(...)) or construct the target class directly (new PostgresRuntimeImpl({...})/new SqliteRuntimeImpl({...})). The constructor options match whatcreateRuntimeaccepted, exceptstackInstanceis not taken — passadapterdirectly. App code using the facade factories is unaffected. (#806) -
SqlContractSerializerno longer accepts Postgres contracts — the family serializer's entries registry only knows SQL-family built-ins (table,valueSet) and rejects the Postgres-specifictypekey that every Postgres namespace carries. Migration files and app code that deserialize a Postgres-emitted contract must usePostgresContractSerializerfrom@prisma-next/target-postgres/runtime. SQLite and family-only contracts are unaffected. (#812)Before:
import { SqlContractSerializer } from '@prisma-next/family-sql/ir'; const contract = new SqlContractSerializer().deserializeContract(json) as Contract;
After:
import { PostgresContractSerializer } from '@prisma-next/target-postgres/runtime'; const contract = new PostgresContractSerializer().deserializeContract(json) as Contract;
-
Extension authors:
SqlNamespace.entriesis an open dictionary — the closed shape ({ table?, valueSet? }) is gone.entriesis nowReadonly<Record<string, Readonly<Record<string, unknown>>>>, so dot-access like.entries.tableno longer compiles. Read tables via thenamespaceTables(ns)helper from@prisma-next/sql-contract/types, or via bracket notationentries['table']; the concrete class instances still expose typed getters (ns.table). See the extension-author recipe. (#812)
Features
-
Postgres-native UUID storage —
field.uuidNative()/field.id.uuidv4Native()/field.id.uuidv7Native()from@prisma-next/postgres/contract-builderauthor columns backed by the nativeuuidtype. The cross-target*String()presets continue to emitchar(36). (#810) -
Many-to-many reads land —
N:Mrelations through athroughjunction can now be eagerly loaded viainclude()(correlated reads, slice 1) and filtered withsome/every/nonethrough the junction (slice 2). M:N validation arrived in 0.13; the runtime read surface is wired up in this release. (#679, #680) -
Supabase façade —
@prisma-next/extension-supabaseships asupabase()façade andSupabaseRuntimethat composes the cross-contract foreign keys introduced in 0.13 into a runnable extension. (#792) -
Fault-tolerant PSL parser — a new recursive-descent parser produces a full syntax tree (
SourceFile) even when the input contains errors, so editor integrations can report diagnostics and surface partial structure without bailing on the first failure. (#795) -
Custom and parameterized codecs in control-path queries — adapters now honor custom and parameterized codecs when encoding values on the control path (catalog reads, schema-verification queries, migration-state lookups), matching how user-data queries already handled them. (#807)
-
contract inferwrites apragmaheader — inferred PSL contracts now carry apragmablock recording the inference source and options, so re-running infer or auditing a generated schema is unambiguous. (#801) -
Per-namespace typed resolution in the builder — the emitted
contract.d.tsTypeMaps nest by namespace, so the query builder and ORM client resolve each namespace's own columns and fields — fixing same-bare-name models declared in more than one namespace. Re-emit picks up the new shape. (#803) -
Enum input types are exhaustively typed in the emitted
.d.ts— an enum-restricted field's input type renders as the literal member union (matching the output side), so create/update calls are exhaustiveness-checked at compile time. Re-emit picks up the new shape. (#797) -
Typed
db.enums.<namespace>.<Name>accessor — the emitter generates adomainblock incontract.d.tsthat exposes each PSL-authored enum as a literal-typedContractEnumAccessor(values,names,members).contract.jsonis unchanged; re-emit picks up the new types. (#809) -
Enum member defaults via
@default(EnumType.Member)— the PSL interpreter and contract-ts authoring surface resolve a member default to the corresponding database value literal. (#808)
Fixes
-
sql-orm-clientmodel accessors typed by selected variant — accessing a model on the ORM client narrows the result type to the selected variant rather than the union of all variants. (#790) -
Emitter emits enum input literals — fixes a hole where enum-restricted input types fell back to the codec's broad input type instead of the literal member union. (#797)
-
Un-namespaced Postgres models default to
public— un-namespaced models in a Postgres contract correctly default to thepublicnamespace per ADR 223; the spurious empty__unbound__storage slot is gone. Re-emit picks up the shape change. (#838)