v0.15.0
v0.15.0
This release ships Postgres row-level security end-to-end (policies for every operation, explicit @@rls enablement, role declarations — authored in PSL or TypeScript, planned by migration plan, drift caught by db verify), native Postgres enums (external adoption and a managed lifecycle), the complete introspected Supabase contract, a PSL language server (prisma-next lsp) with formatting, completions, and semantic highlighting, native scalar-list columns, PSL many-to-many authoring, and one unified schema differ behind db verify and migration planning. SQL ORM includes now decode through codecs, matching top-level reads.
Breaking changes
-
SQL ORM includes decode through codecs — every scalar field of an included relation now decodes through its contract-bound codec, matching top-level query results. Code that relied on included fields keeping the database's raw JSON representation must be updated: Postgres
byteainclude fields returnUint8Arrayinstead of\x-prefixed hex text, timestamp fields returnDateinstead of strings, and custom codec-backed fields return whatever the codec'sdecodeJsonproduces. Custom SQL codec authors:encodeJson/decodeJsonnow use the exact scalar shape the database produces inside JSON values — see the extension-author recipe for the built-in representation changes. (#942)Before:
const [post] = await db.orm.public.Post.find({ include: { author: true } }); post.author.avatar; // '\\x89504e…' (raw hex text) post.author.createdAt; // '2026-07-01T12:00:00' (string)
After:
post.author.avatar; // Uint8Array post.author.createdAt; // Date
-
db verify --jsonreports a singleschema.issueslist — the splitschema.issues/schema.schemaDiffIssuespair collapses into oneschema.issuesarray of{ path, reason, message, expected?, actual? }, and the retiredoutcomefield is replaced byreason('missing'→'not-found','extra'→'not-expected','mismatch'→'not-equal'). The same collapse applies toschema.warnings. Update scripts or CI steps that readschemaDiffIssuesor compare.outcome. See the 0.14→0.15 upgrade recipe. (#921)Before:
{ "schema": { "issues": [], "schemaDiffIssues": [{ "outcome": "missing", "message": "…" }] } }After:
{ "schema": { "issues": [{ "reason": "not-found", "path": ["…"], "message": "…" }] } } -
RLS policies require
@@rlson the target model — RLS enablement is an explicit, authored table attribute. Apolicy_*block'stargetmodel must declare@@rls, orcontract emitfails withPSL_EXTENSION_TARGET_MODEL_MISSING_ATTRIBUTE. Plan semantics follow the marker: a marked table with RLS off plansENABLE ROW LEVEL SECURITY, removing every policy keeps RLS enabled (fail-closed deny-all), and removing@@rlsplansDISABLE ROW LEVEL SECURITY(requires the destructive allowance). Renaming only a policy's name plans a singleALTER POLICY … RENAME TOinstead of drop+create. Extension authors constructingPostgresTableSchemaNodeby hand must supply the now-requiredrlsEnabledboolean. (#945)Before:
model Profile { id Uuid @id userId Uuid @unique }
After:
model Profile { id Uuid @id userId Uuid @unique @@rls }
-
Extension authors: SQL contract authoring requires a target
createNamespace— the SQL family no longer materialises a placeholder namespace, soprismaContract(...)/defineContract(...)from@prisma-next/sql-contract-psl/@prisma-next/sql-contract-tsneed the target's namespace factory (postgresCreateNamespace/sqliteCreateNamespace); target-packdefineContractwrappers already supply it, so app authors are unaffected.SqlNamespaceis now an abstract class;buildSqlNamespace,buildSqlNamespaceMap,SqlBoundNamespace, andSqlUnboundNamespaceare removed, and hand-written namespace literals carry the targetkind(e.g.'postgres-schema') instead of'sql-namespace'. See the extension-author recipe. (#864) -
Extension authors: the coordinate-based schema-diff SPI is retired — the migration planner and
db verifynow run on one generic node differ.collectSqlSchemaIssues/collectSqlSchemaIssuesPerNamespace,diffPostgresDatabaseSchema, andSqlControlTargetDescriptor.diffDatabaseSchemaare removed (usediffSchemasor a target'sbuildXPlanDiff);MigrationPlanner.plan()'skeepDiffIssuepredicate is replaced by anownershiporacle; the issue typesBaseSchemaIssue/SchemaIssue/EnumValuesChangedIssueare gone —SchemaDiffIssueis the single issue shape everywhere, including the codecverifyTypehook; andgraphWalkStrategyis renamedresolveRecordedPathin@prisma-next/migration-tools/aggregate. See the extension-author recipe. (#921, #894) -
Extension authors: restricted-column typing goes through the codec — a column restricted to a value set derives its TS literal union by rendering each stored value through the codec's
renderValueLiteral(value, side), replacing the framework's deleted domain-enum override. Custom codec descriptors used by enum/restricted columns must implement it, or the column widens to the codec's output type. (#896) -
Extension authors: Mongo
deriveJsonSchemasources enums from value sets — the fourth argument ofderiveJsonSchema/derivePolymorphicJsonSchemachanges from a domain-enum map to a value-set map (contract.storage.namespaces[<ns>].entries.valueSet). Callers throughmongoContract(...)/defineContract(...)need no change. (#900) -
Extension authors:
ScalarFieldState's first generic is the column descriptor —ScalarFieldState<'pg/text@1', …>becomesScalarFieldState<ColumnTypeDescriptor<'pg/text@1'>, …>, so field states preserve the whole descriptor type (including native-enum member tuples). Built contract types also keep literalnativeType/typeParamsinstead of widening tostring. (#958) -
Extension authors:
native_enumentities serialize intocontract.json, keyed by physical type name — packs declaring native Postgres enums must re-emit their bundled contract so theentries.native_enummaps land in the published artifacts (this is what lets a consumer'scontract infersubtract pack-owned enum types). Code addressing an entry by key switches from the PascalCase name to the physical Postgres type name (entries.native_enum.aal_level, not.AalLevel). (#946, #954)
Features
-
Postgres row-level security, end-to-end — PSL gains
policy_select,policy_insert,policy_update,policy_delete, andpolicy_allblocks (withusing/withCheckpredicates and per-role targeting), the@@rlsenablement attribute, and standaloneroledeclarations insidenamespace unbound { }.migration planplans the full lifecycle (ENABLE/DISABLE ROW LEVEL SECURITY, policy create/drop, rename viaALTER POLICY), anddb verifyfails on policy drift and on declared roles the live cluster lacks. The same surface is authorable in the TypeScript DSL (policySelect(...),rlsEnabled(model),role(name)), producing wire-name-identical contracts. (#771, #868, #945, #950, #957, #959) -
Native Postgres enums —
CREATE TYPE … AS ENUMtypes are first-class again, this time as explicit entities. External types the database already owns (e.g. Supabase'sauth.aal_level) are declared vianative_enumblocks, typed as member-value literal unions, adopted bycontract infer, and read at runtime through the new Postgres-onlydb.nativeEnumsaccessor. Managed native enums get a migration lifecycle: create/delete, and member addition viaALTER TYPE … ADD VALUE(other member changes are refused with a converting-migration hint). Also authorable in the TypeScript DSL vianativeEnum(name, ...values)+field.column(pg.enum(handle)), with the member union visible intypeof contractwithout an emit. (#906, #944, #949, #970, #935, #958) -
The complete Supabase contract —
@prisma-next/extension-supabasenow ships the full introspected description of everything Supabase owns: everyauthandstoragetable, all native enum types, and the three platform roles (anon,authenticated,service_role), up from the previous 5-table minimum. A secondarydb.asServiceRole().supabase.{sql,orm}admin root reads Supabase-internal tables asservice_role, and the extension ships with docs, a real-Supabase acceptance harness, and a user-facingprisma-next-supabaseskill. (#845, #960, #985, #987) -
PSL language server — a new
prisma-next lspsubcommand serves diagnostics, formatting, completions (types and block templates), semantic highlighting, folding regions, and symbol-table diagnostics over LSP, backed by the fault-tolerant CST parser (which now fully replaces the legacy parser).prisma formatformats PSL from the CLI, and a browser playground wires a Monaco editor to the language server. (#852, #851, #850, #857, #862, #871, #878, #869, #856, #887, #972) -
PSL native scalar lists — scalar-list fields (
String[],Int[], …) lower to native array storage columns instead of a JSONB fallback, end-to-end: author, migrate, and infer, gated on the adapter-reportedscalarListcapability. (#870, #846) -
PSL authors many-to-many — an
N:Mrelation with athroughjunction is now authorable in PSL, completing the M:N surface whose read side landed in 0.14. (#819) -
Per-migration contract snapshots — each applied migration persists its contract snapshot in a 1:1 ledger companion table, and the
Migrationbase class takes typed start/end contract JSON, exposingthis.startContract/this.endContractviews for data-transform migrations. (#908, #879) -
Client-safe static surface — new
@prisma-next/{postgres,sqlite,mongo}/staticentrypoints export<target>Static({ contractJson }), a driver-freeExecutionContextplus derivedenums, query builder,raw, andcontract— safe to import in client bundles. The runtime facades also exposedb.contextanddb.contract. (#888) -
Mongo enums, end-to-end — enums are authorable for MongoDB in PSL and the TypeScript builder, enforced at the database layer via a planner-generated
$jsonSchemavalidator, and typed from a stored value set the same way SQL enums are. The Mongo client also gainsdb.rawanddb.execute(plan). (#834, #900, #880) -
Extension-aware
contract infer—contract inferomits database elements a stack extension pack's contract already describes, and resolves a foreign key into pack-owned space as a qualified cross-space relation (e.g.supabase:auth.AuthUser) instead of re-declaring the pack's tables. (#919) -
Variant-declared relations in the ORM — the
.variant('X')-narrowed accessor surfaces relations the variant model declares (filterable and includable), alongside the base model's relations. (#933, #976) -
Enum
@@typeinference — a PSLenumblock may omit@@type; the codec is inferred from the member values (text for string members, int for integers). (#905) -
@relation(index: false)andinetcolumns — PSL's@relationgains an optionalindexargument for foreign keys whose columns genuinely have no backing index (contract inferemits it automatically), and the Postgres target gains apg/inet@1codec soinetcolumns are authorable asString @db.Inetand inferrable. (#960)
Fixes
-
@default(false)survives emission — the contract canonicalizer no longer stripsvalue: falsefrom resolved defaults, so a boolean-falsecolumn default is present in the emittedcontract.jsonand round-trips against live introspection. Re-emitting an affected contract changes its storage hash. (#904) -
Mongo reshaping reads decode through codecs — aggregation reads through
$project/$addFieldsstages decode their output fields instead of returning raw BSON (a projected_idnow comes back decoded, not as a rawObjectId). (#897) -
pgbindings resolve by structure — a caller-supplied Pool/Client from a duplicatedpgcopy in a bundle now resolves correctly instead of throwingUnable to determine pg binding typeat boot; newisPgPool/isPgClientguards are exported from@prisma-next/postgres/runtime. (#969) -
Array columns verify cleanly — a scalar-list column's derived schema IR keeps the bare element type with
many: true(previously every list column verifiednot-equalagainst live introspection); Postgres introspection also excludes expression-keyed indexes and no longer collides unique and non-unique indexes over identical columns. (#960) -
Stack-missing migration errors name the failing operation — the error raised when a migration references an operation the stack doesn't provide now says which operation. (#953)