Skip to content

TML-3037: contract infer output round-trips through contract emit - #1011

Merged
wmadden merged 33 commits into
mainfrom
tml-3037-infer-emit-roundtrip-recut
Jul 21, 2026
Merged

TML-3037: contract infer output round-trips through contract emit#1011
wmadden merged 33 commits into
mainfrom
tml-3037-infer-emit-roundtrip-recut

Conversation

@wmadden-electric

@wmadden-electric wmadden-electric commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Linked issue

Refs TML-3037 · Closes TML-3024 · Supersedes #998 (closed unmerged — see Alternatives considered for why).

At a glance

Here is a fragment of packages/3-extensions/supabase/scripts/generate-contract.ts, on main, before this PR:

const DOUBLE_PLURALIZED_FIELD_NAMES: ReadonlySet<string> = new Set([
  'icebergNamespaceses', 'icebergTableses', 'identitieses', 'mfaAmrClaimses',
  'mfaChallengeses', 'mfaFactorses', 'oauthAuthorizationses', 'oauthConsentses',
  'objectses', 'oneTimeTokenses', 'refreshTokenses', 's3MultipartUploadsPartses',
  // …twenty in total
]);

We ship a script that repairs contract infer's output before our own contract emit will accept it. Alongside that list are tables of column, default, and index omissions — each with a careful comment explaining which part of our own pipeline rejects our own output. This PR deletes 170 lines of that script and fixes what it was working around.

Decision

  1. A round-trip instrument exists: introspect → infer → emit → db verify --schema-only against a live database, plus a runtime instrument that builds a real ExecutionContext from the emitted contract. Every defect below was reproduced by it before its fix.
  2. Seven infer→emit round-trip defects are fixed (pluralization, 1:1 back-relations, Decimal-on-postgres, identity columns, index types, list literal defaults, dbgenerated default drift), and dropped dangling FKs are now explained in infer's output instead of vanishing silently.
  3. The eighth defect — date columns — is fixed halfway, deliberately. The missing pg/date@1 codec lands (with strict parsing: 2024-02-31 is rejected, not normalized into March), but nothing binds the @db.Date spelling to it. That binding belongs to remove-db-attributes, a parallel in-flight project that replaces PSL's @db.* attributes with first-class scalar types (slice 2 of it is open as TML-2986: postgres native types as bare PSL scalar types; Json→pg/json, new Jsonb #975); its future bare Date type is where the pin goes. Wiring the binding through @db.* machinery here would extend exactly what that project deletes. The runtime instrument pins the still-broken .include() decode so the future binding flips a red assertion instead of landing silently.

Why the work exists

A user sent in a 260-line fix-inferred-contract.ts they run after every contract infer. Auditing it claim-by-claim against the source turned up the defects. They had written our pack's repair script independently, against a different database, without ever seeing ours. Two people arriving at the same workaround is the finding: contract infer writes PSL that contract emit rejects, or that db verify reports as drift — and no test anywhere did introspect → infer → emit, which is why all of it shipped. So the first commit is the instrument, not a fix: a round-trip journey carrying already-plural table names, 1:1 / 1:N / self-referencing FKs, both identity variants, serial, bounded and unbounded numeric, date, text[], jsonb, GIN and hash indexes, and an FK pointing out of scope.

What was broken

Back-relation names double-pluralized. pluralize() appended es to anything ending in s, so already-plural table names — most real schemas — came out sessionses. Now uses the maintained pluralize library. Closes TML-3024, whose bar was "the Supabase generator override is removed and the regenerated contract is unchanged by its removal". It is, and it is.

Infer printed a 1:1 back-relation emit couldn't parse. A bare profile Profile?. The uniqueness detection producing it was correct; the interpreter collected back-relation candidates only if (field.list). We had a snapshot test asserting we print PSL our own emitter rejects.

Decimal never worked on Postgres. Not an infer bug: Decimal maps to pg/numeric@1 on the base-scalar path with no typeParams, so any schema with a plain amount Decimal field threw RUNTIME.CODEC_PARAMETERIZATION_MISMATCH at connect. NumericParams.precision was required while every sibling temporal codec's param is optional, and three other layers already handled a missing precision. Infer is simply the first thing that generates a Decimal.

date columns break through .include(). @db.Date inherits DateTime's pg/timestamptz@1, whose decodeJson rejects the bare YYYY-MM-DD that json_agg renders. This PR ships the pg/date@1 codec but leaves it unbound; see Decision 3.

Non-btree indexes could never emit. Infer prints @@index(…, type: "gin"); the Postgres target registered zero index types, so emit threw unregistered index type "gin". The target now registers its built-ins (btree, hash, gin, gist, spgist, brin) via the same IndexTypeRegistry mechanism ParadeDB uses for bm25; an unregistered type is still rejected.

Identity columns lost their default. The columns query joined pg_attribute but never selected attidentity; serial worked only because it sets a real nextval(...) default. Identity maps onto autoincrement() symmetrically — infer emits it and the verify normalizer resolves a live identity column to it, so neither side drifts. PSL doesn't model GENERATED ALWAYS vs BY DEFAULT; a fresh db init from such a contract creates serial (pre-existing gap, TML-3044).

dbgenerated literal defaults drifted forever. Emit kept '{}'::jsonb as kind: 'function'; introspection parsed the same literal to kind: 'literal'; resolvedDefaultsEqual compares kind first, so db verify reported such columns not-equal permanently. Normalization now happens once, at SchemaIR construction.

Dangling FKs dropped silently. Infer correctly drops an FK whose target is outside the introspected scope, but said nothing — a user loses every auth.users relationship with no indication. It now says so, and points at the likely cause.

Reviewer notes

  • Prior review is absorbed, not pending. The bulk of this branch carried two review rounds on TML-3037: contract infer output round-trips through contract emit #998 (a pre-open pass and a round of four architectural findings, all fixed at the root), and this PR itself had a three-lens local review whose artifacts ship in projects/infer-emit-roundtrip/reviews/pr-1011/; its findings are fixed in the last three commits (strict date parsing made real, PSL_*_BACKRELATION_LIST diagnostic codes renamed to drop the now-false _LIST suffix, deferred-binding marker + spec correction).
  • The date-deferral delta is one commitfix(contract-psl): defer the @db.Date -> pg/date@1 binding to remove-db-attributes — and it returns the four type-channel files (psl-column-resolution.ts, psl-named-type-resolution.ts, the postgres adapter's control-mutation-defaults.ts, scripts/lint-framework-vocabulary.config.json) byte-identical to main.
  • The .include() decode failure is bigger than dates: TML-3054 records that the timestamp codecs reject Postgres's own json_agg renderings and numeric/int8 lose precision in the envelope parse. Out of scope here; the pinned date scenario is the first recorded instance of that class.
  • The largest commits are the instrument and the pack regeneration. The pack's contract.prisma diff is the proof-of-done: regenerating against this branch produces zero diff.
  • projects/infer-emit-roundtrip/ (spec, plan, reproduction record, review artifacts) stays on disk for review; close-out deletes it after merge. reproduction.md records the pre-fix state verbatim and is deliberately not updated.

Breaking changes

Upgrade instructions are recorded in skills/upgrade/prisma-next-upgrade/upgrades/0.15-to-0.16/ and the extension-author mirror.

  • dbgenerated(...) literal defaults resolve differently on the next emit — storageHash changes.
  • Back-relation names change on a future contract infer re-run (sessionsessessions). These are public field names consumers type.
  • Identity columns need an explicit default under db verify --strict only; non-strict verify filters an undeclared live default out entirely.

@db.Date columns do not change codecId in this PR — that entry rides with the future binding. Index-type registration and the Decimal fix are not breaking: both previously threw unconditionally.

Testing performed

On the final HEAD (after the review-rework commits):

  • pnpm build · pnpm typecheck · pnpm lint (incl. lint:deps, lint:casts, lint:framework-vocabulary) · pnpm fixtures:check · pnpm check:upgrade-coverage — all green
  • pnpm test:packages — 995 files / 13185 passed (3 expected-fail, 1 skipped)
  • pnpm test:integration — 204 files / 1173 passed
  • pnpm test:e2e — 20 files / 109 passed
  • Both round-trip instruments in isolation — 2 files / 13 passed
  • pnpm --filter @prisma-next/extension-supabase run contract:generate — zero diff against the committed contract

Skill update

skills/upgrade/prisma-next-upgrade/upgrades/0.15-to-0.16/instructions.md and the extension-author mirror carry the three breaking-change entries above. No CLI/API surface changed beyond what those entries describe.

Follow-ups

  • Date binding: asked of the remove-db-attributes project (pin its bare Date to pg/date@1, via codecId — the descriptor carries a marker); the runtime instrument's pinned red assertion flips when it lands.
  • Deferred round-trip gaps, each filed: TML-3041 (@default(null)), TML-3042 (nullable lists), TML-3043 (FK-less relations), TML-3044 (identity DDL), TML-3045 (array-returning function defaults), TML-3048 (named-sequence nextval), TML-3054 (.include() decode class).

Alternatives considered

Merge #998 as-is. This branch's first life. Its final review round bound @db.Date to pg/date@1 through a double-keyed scalarTypeDescriptors map (scalar names and attribute names sharing one map) to keep the codec id out of family-layer code. It worked, but it extended the exact bespoke @db.* channel that remove-db-attributes deletes — new machinery with a planned demolition date. Closing #998 and re-cutting with the binding deferred cost one commit; merging would have cost that project a migration.

Bind the date spelling ourselves via a namespaced constructor (pg.Date()). The existing pack-contribution channel supports it today, but remove-db-attributes already claims the date native type for its bare Date; shipping a second spelling now means two spellings with two bindings colliding at that project's printer handoff.

Eight separate PRs. Every defect is an instance of one class, they share one instrument and one acceptance bar, and the evidence they're worth fixing is collective — the pack script shrinking wouldn't appear in any of them.

Make infer emit lists for 1:1 back-relations. Would have made emit pass by discarding real information. The contract already supports '1:1'; the gap was PSL-side only.

Relax the list-default check to permit storage function defaults. Admits tags DateTime[] @default(now()), whose DDL Postgres refuses — moving the error from authoring time to apply time. Review caught it; the literal-default fix makes the array case work without it.

Checklist

  • All commits are signed off (git commit -s) per the DCO. The DCO status check will block merge if any commit is missing a Signed-off-by: trailer.
  • I read CONTRIBUTING.md and the change is scoped to one logical concern.
  • Tests are updated (or n/a if the change is doc-only / refactor with no behavioural delta).
  • The PR title is in TML-NNNN: <sentence-case title> form (Linear ticket prefix + concise title naming the concrete deliverable). See .claude/skills/create-pr/SKILL.md for the full convention.
  • The Skill update section above is filled in (or stated n/a — internal only).

Summary by CodeRabbit

  • New Features
    • Added PostgreSQL date support with reliable JSON and runtime encoding/decoding.
    • Added support for PostgreSQL identity columns and autoincrement() defaults.
    • Added PostgreSQL index-type support, including hash and GIN indexes.
    • Improved array default handling and support for unbounded numeric columns.
  • Bug Fixes
    • Improved one-to-one relation and backrelation detection, cardinality, and diagnostics.
    • Prevented double-pluralized inferred relation fields.
    • Added warnings for foreign keys referencing unavailable tables.
  • Documentation
    • Updated upgrade guidance for identity defaults and relation naming changes.

Spec and plan for TML-3037. An external user's post-processing script,
audited claim-by-claim, surfaced eight defects — all instances of one
class: contract infer writes PSL that contract emit rejects, or that
db verify then reports as drift.

Our own extension-supabase generate-contract.ts is independently the
same workaround script, which is the evidence this is worth fixing at
the source rather than papering over per-consumer.

No test anywhere does introspect → infer → emit. That is why all eight
shipped, and building that instrument is the first dispatch.

Refs: TML-3037
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…against a live database

`contract infer` output does not survive `contract emit`, and no test anywhere
drives introspect -> infer -> emit, which is why eight defects shipped. This
adds the instrument that reproduces all of them, and the failure record it
produced.

Two files, because the CLI path cannot observe runtime decode:

- `cli-journeys/infer-roundtrip-fidelity.e2e.test.ts` drives infer -> emit ->
  `db verify --schema-only` over a schema carrying already-plural table names,
  a 1:1 and a 1:N FK, both `GENERATED ... AS IDENTITY` variants plus a `serial`
  control, bounded and unbounded `numeric`, a `date`, array/jsonb defaults, GIN
  and `USING hash` indexes, and an FK pointing out of the introspected scope.
- `infer-roundtrip-runtime.integration.test.ts` builds a real `ExecutionContext`
  against an emitted contract and reads a `date` through `.include()` — the only
  way to see the numeric-connect and date-decode defects.

All ten assertions fail today; each asserts the fixed behaviour, so each flips
green as its fix lands. Each `it()` isolates one defect, repairing the unrelated
emit-blockers so a single run reports every defect independently.

`projects/infer-emit-roundtrip/reproduction.md` records, per defect, the command
that surfaces it and the verbatim error. Two findings correct the spec: the
unbounded-numeric crash arrives via the bare `Decimal` base scalar, not via
`@db.Numeric` with no args (infer never prints that attribute), so the affected
surface is every `Decimal` field on postgres; and only the to-many side of a
relation double-pluralizes, so this fixture reproduces `sessionses` but not
`identitieses`.

Refs: TML-3037
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…gres

D1 reproduced the numeric crash and showed the spec had the mechanism
wrong. It does not arrive via @db.Numeric with no arguments; infer never
prints that. It arrives via the base-scalar path, where
control-mutation-defaults maps Decimal to pg/numeric@1 with no
typeParams — so a plain `amount Decimal` field crashes at connect
regardless of any attribute.

This is not an infer defect that happens to touch Decimal. Decimal has
never worked on postgres; infer is just the first thing that generates
one. No .prisma file in the repo declares a Decimal field, which is why
it went unnoticed.

The chosen fix is unchanged, but D4 must test the bare scalar — an
attribute-only test would pass while the defect ships.

Refs: TML-3037
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
`pluralize()` appended `es` to any word ending in s/x/z/ch/sh, so an
already-plural table name like `sessions` doubled to `sessionses` in
inferred back-relation field names.

Replaced the hand-rolled suffix rule with the `pluralize` package
(real inflection: irregular/uncountable word lists plus ordered
regex rules, not a trailing-character heuristic), per TML-3024 and
the spec's preference for a maintained library over a hand-rolled
table. Verified it satisfies every row of the acceptance table,
including the two cases that pull in opposite directions: already
plural (sessions, identities, mfaAmrClaims unchanged) and
singular-but-s-ending (status -> statuses, bus -> buses, class ->
classes).

Added `@types/pluralize` as a dev dependency since the library ships
untyped. Import as a default import (`import pluralizeLib from
'pluralize'`), not a namespace import: pluralize assigns its whole
CJS module.exports in one statement, so cjs-module-lexer-based
interop (Node's native ESM loader, used when the CLI runs the built
dist/psl-infer.mjs) cannot statically detect `.plural` as a named
export and leaves the namespace with only `default`. A default
import always resolves to the full module.exports value, so
`pluralizeLib.plural` works reliably. Vitest hides this because Vite
pre-bundles CJS deps with esbuild, which copies the CJS exports'
own properties onto the namespace at runtime; confirmed the failure
and the fix against a real Node ESM import, not just the test
runner.

Refs: TML-3037
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…ults infer prints

The interpreter only collected back-relation candidates for list-typed
fields, so a bare, `@relation`-less model-typed field (the back side of
a 1:1) fell through to scalar resolution and died with
PSL_UNSUPPORTED_FIELD_TYPE. A model-typed, non-list field with no
`@relation` can only be the back side — the owning side always carries
`@relation(fields, references)` to declare its FK columns — so this is
never ambiguous. ModelBackrelationCandidate now carries `isList`, and
applyBackrelationCandidates resolves cardinality to `1:1` for a
singular candidate (`1:N` unchanged for list) and skips many-to-many
junction matching for singular candidates, since that concept only
applies to lists.

Separately, the list-default check conflated a storage-level SQL
default (`dbgenerated(...)`, `now()`, lowered as
`defaultValue.kind: "function"`) with a genuine per-element execution
default (`executionDefaults.onCreate`, e.g. `uuid()`), rejecting both
on list fields. Only the latter has no list semantics; Postgres accepts
`DEFAULT (expression)` on an array column regardless of `many`, and
buildColumnDefaultSql already renders it that way. The check now only
rejects `executionDefaults.onCreate` on a list field.

Refs: TML-3037
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Permitting storage-level function defaults on lists (the defect-7 fix)
also let `Int[] @default(autoincrement())` through, which the postgres
DDL builder miscompiles: buildColumnTypeSql keys on the
`autoincrement()` sentinel to emit SERIAL and drops the array suffix
entirely. The old broad `kind === "function"` rule blocked that shape,
but for the wrong reason — it called every storage default an
execution default. Restoring it would undo defect 7, so this adds a
narrow guard instead: a sequence yields one scalar per row, which an
array column cannot be.

The check keys on the lowered `autoincrement()` sentinel rather than
the authored call name. Every target lowers the call to that exact
expression and keys its DDL rendering off it, so rejecting the
sentinel keeps the shape out of the DDL builders however it was
spelled.

Both sides of the line are tested: Int[] @default(autoincrement())
errors, String[] @default(dbgenerated("{}::text[]")) still lowers to a
storage function default. Scalar @default(autoincrement()) is
untouched.

Also regenerates the print-psl inline snapshot invalidated by the
pluralize fix: `child` now pluralizes to `children`, the correct
irregular plural, where the hand-rolled rule produced `childs`.

Refs: TML-3037
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
… date

`NumericParams.precision` was required while every sibling temporal codec
(`PrecisionParams`) declares its param optional. A bare `amount Decimal`
field — the base-scalar path `control-mutation-defaults.ts` maps every
postgres `Decimal` field through, with or without `@db.Numeric` — produced a
`pg/numeric@1` ref with no `typeParams` and crashed `createExecutionContext`
with `RUNTIME.CODEC_PARAMETERIZATION_MISMATCH`. Make `precision` optional on
`NumericParams`/`numericParamsSchema`, matching the sibling pattern;
`assertColumnCodecIntegrity` is untouched. A bounded `numeric(10,2)` still
carries and enforces its params.

No `pg/date@1` codec existed: `@db.Date` inherited `DateTime`s
`pg/timestamptz@1`. Top-level `SELECT` survived because `decode()` was a
passthrough over the driver-parsed `Date`, but `.include()` (`json_agg` ->
`decodeJson()`) rejected a bare `YYYY-MM-DD` string. Add a real `pg/date@1`
codec and point `@db.Date` at it. A Postgres `date` has no timezone, so the
codec canonicalizes its JS-level value as a `Date` at UTC midnight:
`decode()` re-derives the calendar date from the pg drivers local-midnight
`Date` via local getters and reconstructs it via `Date.UTC`; `encode()`
formats via UTC getters directly to `YYYY-MM-DD`, bypassing the pg drivers
own local-getter-based `Date` serialization (which would shift the day near
midnight under a negative UTC offset). `decodeJson`/`encodeJson` share the
same `YYYY-MM-DD` representation.

BREAKING: `@db.Date` columns now emit `codecId: "pg/date@1"` instead of
inheriting `"pg/timestamptz@1"`, which changes the contract hash and
therefore signed markers for any existing `@db.Date` column. Upgrade
instructions are dispatch D9s job, not covered here.

RT.01 (unbounded numeric via infer) and RT.02 (date via .include()) in
test/integration/test/infer-roundtrip-runtime.integration.test.ts now pass.
RT.02s top-level assertion is tightened from toBeInstanceOf(Date) to the
exact UTC instant, now that pg/date@1 owns the conversion and the round trip
no longer depends on the process timezone. Added RT.03, authoring
"amount Decimal" directly (no @db.Numeric, bypassing infer) so the
base-scalar fix is proven independently of infers own behavior.

Refs: TML-3037
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…dangling FKs

The postgres target registered zero index types, so validateIndexTypes
rejected any @@index(..., type: "gin"/"hash") contract infer produced for a
non-default access method. Register the six access methods Postgres ships
(btree, hash, gin, gist, spgist, brin) via IndexTypeRegistry, following the
same wiring ParadeDB already uses for its own bm25 type. Options schemas stay
permissive; per-method option validation is a later slice. btree is
Postgres default access method already normalized to `undefined` by
introspection, so registering it does not change what gets emitted for an
ordinary index.

Separately, resolveForeignKeys correctly drops a foreign key whose target is
outside the introspected schema, but did so silently: the scalar column
survived with no comment and no warning, so a user loses every such
relationship (e.g. every auth.users FK in a Supabase database) with no
indication. Track dropped FKs per table and emit an explanatory comment on
the model, matching the existing missing-primary-key comment mechanism and
voice.

Refs: TML-3037
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
… both sides

`GENERATED ALWAYS AS IDENTITY` and `GENERATED BY DEFAULT AS IDENTITY` columns
report no `column_default` at all — Postgres tracks generation via
`pg_attribute.attidentity`, not a default expression — so infer emitted a
bare column with no default, and even once printed, the introspected side
had nothing to compare a `@default(autoincrement())` contract against.

Fixed symmetrically, on both sides:

- `SqlColumnIR` gains an `identity` boolean (introspection-only; a
  contract-derived column never sets it, since PSL has no identity
  vocabulary).
- The postgres columns query selects `a.attidentity` and threads it onto the
  column; the control adapter computes `resolvedDefault` from the identity
  flag when set, since there is no raw expression to parse.
- `parsePostgresDefault` takes an `identity` option that resolves straight to
  `autoincrement()`, independent of `rawDefault`.
- Postgres infer (`buildScalarField`) prints `@default(autoincrement())` for
  an identity column, the same as it already does for `serial`.

Deliberate boundary: at the contract's altitude, `autoincrement()` means "the
database generates this value" — identity and `serial` are both that, and
PSL has no syntax to distinguish `GENERATED ALWAYS` from `GENERATED BY
DEFAULT`. This maps both identity variants onto `autoincrement()` rather
than modelling the distinction. Consequence: a fresh `db init` from such a
contract creates a `serial` column, not an identity one — a pre-existing gap
(identity has never been authorable), unchanged by this fix.

Verified against a live database: a new introspection integration test
proves both identity variants thread through as `identity: true` with
`resolvedDefault: autoincrement()`, that `serial` is unaffected, and that a
plain column gets no default. The TML-3037 D1 instrument's IF.06 assertion
(both `GENERATED` variants print `@default(autoincrement())`; `serial`
keeps working) now passes, and a manual reduction of the same journey
(stripping only the two known non-identity default mismatches) shows `db
verify --schema-only` reports zero drift for the identity columns.

Refs: TML-3037
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
D6 exposed a second instance: dbgenerated("'{}'::text[]") drifts
identically to the jsonb case. D1 could not reproduce it because emit
failed before verify ever ran; clearing the emit blockers uncovered it.

The defect is any literal default the authoring and introspection paths
resolve differently — kind:function on one side, kind:literal on the
other, and resolvedDefaultsEqual compares kind first. jsonb and text[]
are two instances; enumerate the rest from parsePostgresDefault rather
than from a list.

The chosen fix already covers the class, because reusing
parsePostgresDefault (rather than copying it) means a form it learns to
parse later is consistent on both sides automatically.

Refs: TML-3037
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…aults, not just function text

Emit kept every `@default(dbgenerated("..."))` as `kind: 'function'`, even when the
raw SQL text was actually a literal (`'{}'::jsonb`, `'{}'::text[]`, NULL, true/false,
a bare number, a plain string). Introspection parses the same text through
`parsePostgresDefault` and gets `kind: 'literal'` for all of those forms.
`resolvedDefaultsEqual` compares `kind` before content, so any dbgenerated
literal drifted forever in `db verify`, even when the database matched the
contract exactly. jsonb and text[] were the two instances TML-3037 caught,
but the mismatch is generic to every literal form the normalizer recognizes.

Fix the class: `lowerDbgenerated` (adapter-postgres) now resolves the raw
text through the same `parsePostgresDefault` introspection already uses,
instead of keeping it as opaque text. A form the normalizer does not
recognize as a literal falls through to its own function fallback, so
genuine functions (`gen_random_uuid()`, `nextval(...)`, arbitrary
expressions) are unaffected.

`parsePostgresDefault` needs the column's native type to parse array and
jsonb literals correctly, so `DefaultFunctionLoweringContext` gains an
optional `nativeType` field, populated by the SQL-family authoring layer
(`lowerDefaultForField`) with the `[]`-suffixed form for list columns —
matching the format introspection's `resolvedNativeType` already uses.

Covers every literal form `parsePostgresDefault` handles (array, null,
boolean, numeric, string, jsonb-parsed-object, out-of-range bigint) and
proves three genuine functions stay functions, including a dbgenerated
nextval(...) and an enum-cast default whose qualified type name defeats
the string-literal pattern.

No fixture moved: the only existing dbgenerated usages in the repo
(gen_random_uuid(), enum casts, an interval expression) all already
resolve to `kind: 'function'` under the new path, unchanged.

Refs: TML-3037
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…ate contract

D2-D7 fixed the eight infer/emit round-trip defects TML-3037 set out to fix.
This dispatch is the proof: delete the workarounds from generate-contract.ts
and confirm the regenerated contract carries the fix instead of the patch.

DOUBLE_PLURALIZED_FIELD_NAMES + applyDoublePluralizationFix: deleted.
pluralize() (D2) is now correct for already-plural table names, so the
override and the fix cancel out exactly - no back-relation name changed in
the regenerated contract.prisma.

INDEX_OMISSIONS + applyIndexOmissions + indexOmissionNameOf: deleted. The
postgres target now registers hash as a built-in index type (D5), so
auth.one_time_tokens' two USING hash indexes come back declared as
@@index(..., type: "hash") instead of silently dropped.

DEFAULT_OMISSIONS: reduced from 7 entries to 1. The jsonb/array
dbgenerated(...) literal-default disagreement (D7) is fixed generically -
authoring and introspection now resolve the same literal to the same
`kind: 'literal'` shape via parsePostgresDefault - so the six
custom_oauth_providers/webauthn_credentials/iceberg_namespaces entries are
gone and those columns' defaults are declared again. Only auth.users.phone
remains: `DEFAULT NULL` on a nullable column round-trips to an explicit
`@default(null)`, which the interpreter rejects (PSL_INVALID_DEFAULT_VALUE) -
a different, still-open defect this slice did not fix.

COLUMN_OMISSIONS is untouched: nullable text[] columns still have no PSL
form. CONTRACT-FIDELITY.md's audit summary is updated to match.

Regenerated packages/3-extensions/supabase/src/contract/contract.prisma via
`contract:generate`. The diff is exactly the two classes above (jsonb/array
defaults restored, two hash indexes declared) plus the resulting
contract.json/contract.d.ts changes - nothing else moved.
reference-fixture-verify.integration.test.ts passes against the regenerated,
workaround-free contract.

Refs: TML-3037
Closes: TML-3024
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Four consumer-visible breaking changes from this slice need an upgrade
entry, in both the user-skill and the extension-author-skill packages
(both substrates hit the same postgres target/interpreter machinery):

- pg/date@1: an existing @db.Date column's codecId changes on the next
  contract emit, changing storageHash, with no source change required.
- dbgenerated literal defaults (jsonb/array/null/bool/numeric/string) now
  resolve to kind: 'literal' on both the authoring and introspection side;
  an existing dbgenerated(...) literal default's resolved shape changes on
  the next emit, changing storageHash.
- Identity columns (GENERATED ... AS IDENTITY) now resolve to
  autoincrement() on introspection too; db verify --strict newly reports
  drift for an identity column whose contract predates this fix and
  declares no default. Non-strict verify is unaffected (undeclared
  auxiliary defaults are tolerated below --strict).
- pluralize no longer double-pluralizes an already-plural table name in
  contract infer output; only affects a future infer run, not an
  already-generated .prisma file, but a consumer who re-infers and gets a
  renamed back-relation field must update the application/pack code that
  references it.

The extension-author entry additionally explains the packages/3-extensions/supabase diff itself (the D8 workaround-deletion regeneration): it demonstrates the dbgenerated-literal-defaults fix directly, plus two additive/non-breaking changes (postgres index-type registration, the Decimal base-scalar fix) that need no entry.

pnpm check:upgrade-coverage passes with these entries committed.

Refs: TML-3037
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…-blind

D7's dbgenerated fix added a nativeType field directly to the shared
framework DefaultFunctionLoweringContext type, which lint:framework-vocabulary
correctly rejected: nativeType is explicit family vocabulary the framework
domain must not carry (per no-family-vocabulary-in-framework.mdc, which names
nativeType as the paradigm example).

Replace the concrete field with an opaque fieldContext?: unknown, the same
pattern ControlMutationDefaultEntry.signature already uses one property
over: the SQL authoring layer (contract-psl's lowerDefaultForField) populates
it with a plain object, and the postgres adapter's lowerDbgenerated narrows
it back via blindCast against a locally-declared shape, since core cannot
name a postgres-specific type and the authoring layer has no reason to
export one. Guards the case where fieldContext is entirely absent so no
other lower handler that never reads it breaks.

lint:framework-vocabulary is back at the committed threshold (836).

Refs: TML-3037
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…d comments

Per no-transient-project-ids-in-code.mdc, source and tests must never carry
transient project-plan IDs — only durable TML-#### references. This slice's
own reproduction.md and plan.md track defects/dispatches by D1-D9 and each
integration test by its own IF.NN / RT.NN row; those IDs leaked into the
actual test suite:

- Dispatch IDs (D1, D4, D5, D6) in doc-comment headers across
  index-types.test.ts, identity-column-introspection.integration.test.ts,
  infer-roundtrip-fidelity.e2e.test.ts, and
  infer-roundtrip-runtime.integration.test.ts.
- IF.01-IF.08 / RT.01-RT.03 test-case IDs prefixing every it() description
  and expect() message in the two new infer-roundtrip integration suites.

Removed all of them, keeping the durable TML-3037 references and rewording
the two prose mid-sentence D4 references in
infer-roundtrip-runtime.integration.test.ts so the paragraph still reads
correctly without the dispatch letter. Test behavior is unchanged - only
names/comments moved; both files re-run green.

Refs: TML-3037
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…efault rejection

D3's relaxation of the list-default check rejected only
executionDefaults.onCreate on a list field, permitting any storage-level
function default through. With D7 landed, the case that motivated the
relaxation - dbgenerated("'{}'::text[]") - already resolves to
kind: 'literal' before this check runs (parsePostgresDefault
recognizes the array-literal pattern), so the relaxation's only live
effect was a hole: a genuine function default on a list
(DateTime[] @default(now()), an array-returning dbgenerated(...)) now
emits a contract whose DDL Postgres rejects at apply time instead of
being caught at authoring time.

Restores the original check: isListField && (loweredOnCreate ||
loweredFunctionDefault). This also makes the narrow
PSL_LIST_AUTOINCREMENT_UNSUPPORTED guard D3 added afterward redundant -
it existed only to re-plug the hole for one function (autoincrement());
the restored broad check covers it along with every other function-kind
default, so the guard and its error code are deleted. Restores the
now()-on-a-list rejection test D3 deleted and replaces the two
D3-added "accepts" tests (dbgenerated array literal, now()) and the
narrow autoincrement rejection test with the original three
rejects-an-execution-default tests (now, uuid, autoincrement).

Verified empirically via the CLI journey (infer -> emit -> db verify):
String[] @default(dbgenerated("'{}'::text[]")) still emits and
round-trips clean, confirming D7's literal resolution is what makes
this case work, not the now-reverted relaxation.

A genuine array-returning function default
(dbgenerated("ARRAY[gen_random_uuid()]")) stays rejected - pre-existing
behaviour, not a regression, and a candidate for a future authoring
feature rather than something to fix here.

Refs: TML-3037
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…n too

The 1:1 back-relation guard added by D3 skipped every singular
model-typed field carrying @relation, under a comment claiming "it
declares fields/references" - which it never actually checked. Infer
prints a named singular back relation (@relation(name: "..."), no
fields/references) whenever two FKs connect the same table pair, or a
unique FK self-references its own table - relation-inference.ts sets
relationName whenever needsRelationName is true, independent of
whether the candidate is a list. That shape still hit
PSL_INVALID_RELATION_ATTRIBUTE ("requires fields and references
arguments"), because both the backrelation-candidate collector and the
owning-side FK-building loop keyed off "has @relation", not off
"declares fields/references".

Adds relationAttributeDeclaresOwningSide(), which checks for the
fields/references named arguments the owning side actually declares,
and uses it in both places: the collector now only skips a genuine
owning-side field, and the FK-building loop now skips a back-relation
field (named or bare) instead of trying to validate fields/references
it never has.

The single-FK-per-pair fixture couldn't see this - it never produced
a relationName. Extends the round-trip fixture with `accounts` (two
FKs to `users`, one unique) and a self-referencing `categories.parent_id
unique`, and adds two isolated tests proving both shapes now emit,
alongside the existing tests proving the genuine owning side (fields +
references) is still treated as such.

Refs: TML-3037
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…t function rewrites

D7 made lowerDbgenerated() reuse parsePostgresDefault() so a literal
default (e.g. '{}'::jsonb) resolves the same way on both the
authoring and introspection sides. But parsePostgresDefault() also
rewrites some function forms for introspection's benefit - notably
nextval(...) -> autoincrement() - and the authoring side was adopting
those rewrites wholesale. dbgenerated("nextval('my_seq')") silently
lowered to autoincrement(), whose DDL renders SERIAL - creating a
fresh table_col_seq sequence and discarding the user's named one.

Authoring now adopts the parser's result only when it resolves to
kind: 'literal'; a kind: 'function' resolution (or no resolution)
keeps the original raw expression text unchanged. This keeps the class
fix intact for the case it was meant for - both sides still agree on
literals - while authoring no longer rewrites function expressions it
was only ever meant to read.

Corrects the "dbgenerated keeps genuine functions as functions" test:
it previously asserted the nextval() rewrite as the *expected*
behaviour ("normalized to autoincrement(), not demoted to a literal"),
which is exactly the bug. It now asserts the expression is preserved
verbatim. Also drops the transient "F13" dispatch-finding label from
that describe block's name, missed by the commit that swept the
others (23cb5d7).

A contract authored with dbgenerated("nextval('my_seq')") will still
drift against a live column under `db verify`, since introspection
maps a live nextval default to autoincrement() regardless - that
mismatch pre-dates this slice and is not chased here.

Corrects the upgrade note in both 0.15-to-0.16/instructions.md copies
(user-facing and extension-author): "nextval(...) is unaffected" was
true of `kind` but not of the expression text before this fix: the
note now explains authoring only adopts literal resolutions, so a
named sequence is preserved rather than silently becoming
autoincrement().

Refs: TML-3037
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…), not just no-throw

The include() date-decode test only checked that nothing threw and
discarded the row, so a decode that produced a *wrong* date would
still pass. That assertion is the only coverage for the defect
pg/date@1 was built for - the top-level select() path never had the
bug (decode() there is a passthrough over an already-parsed Date), so
this is the one path that actually exercises decodeJson()'s
YYYY-MM-DD handling.

Captures the include() result and asserts it equals the exact expected
row, matching the top-level assertion's style immediately above it.

Refs: TML-3037
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Review caught that the prescribed remedy was unsound. Relaxing the list
check to permit storage function defaults admits DateTime[] @default(now()),
whose DDL postgres refuses — trading an authoring-time error for a
DDL-apply-time one. Nothing in the authoring layer can tell whether a
function returns an array, so the relaxation cannot be made safe.

The tell was in the implementation: it needed a special case for
autoincrement() to plug one instance of that hole.

Once defect 8 resolves literal defaults through parsePostgresDefault,
dbgenerated("{}::text[]") is a literal and never reaches the list check.
The check stays as it was and the case works anyway.

Refs: TML-3037
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
… at introspection

Review comments 1 and 3 on PR #998: `identity` on SqlColumnIR was postgres
vocabulary in sql core, and every consumer (default-normalizer option,
infer's identity check) existed only to re-derive what the postgres control
adapter already knows from `attidentity` and already stamps into
`resolvedDefault`.

The adapter now stamps `resolvedDefault: { kind: function, expression:
autoincrement() }` directly for an identity column, with no intermediate
option or field. `parsePostgresDefault` loses its `identity` option. Infer
recognizes the same shape by its only distinguishing trait: a resolvedDefault
of autoincrement() with no raw default. `SqlColumnIR.identity` is deleted
entirely; the postgres adapter is the only place identity is recognized.

Refs: TML-3037
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…ce at SchemaIR construction

Review comment 4 on PR #998: DefaultFunctionLoweringContext.fieldContext
was an opaque unknown channel smuggling postgres nativeType from SQL
authoring into the adapter, held together by a blindCast and a comment.
That field is deleted, and lowerDbgenerated reverts to keeping a
dbgenerated(...) default verbatim as { kind: function, expression }, with
no parsePostgresDefault call.

The jsonb/text[] literal-default drift this was compensating for
(resolvedDefaultsEqual comparing kind before content, so a dbgenerated
literal never matched its introspected counterpart) is fixed once
instead, at SchemaIR construction of the EXPECTED (contract-derived)
side: contractToSchemaIR gains a target-supplied resolveDefault hook,
mirroring the existing expandNativeType/renderDefault IoC pattern, and
the postgres target wires postgresResolveDefault — reusing the same
parsePostgresDefault introspection already calls — into both db verify
and migration planning's expected-tree derivation.

Consequence carried through: a dbgenerated(...) default on a *list*
field still fails PSL_LIST_EXECUTION_DEFAULT_UNSUPPORTED at emit time,
because the interpreter's check has no authoring-time signal left to
distinguish a literal-shaped dbgenerated call from a genuine function —
that signal only lived in the now-deleted fieldContext-driven
resolution. auth.custom_oauth_providers.{acceptable_client_ids,scopes}
(text[] literal defaults) go back into DEFAULT_OMISSIONS with the
reason recorded; the D1 journey's matching list-default assertions are
left unmodified, and reproduce this as a known regression relative to
the branch this round reviewed, not a design this round endorses.

The pack's jsonb dbgenerated defaults (attribute_mapping,
authorization_params, transports, metadata) round-trip clean under the
new compare-time fix, proven by
extension-supabase/test/reference-fixture-verify.integration.test.ts.
Upgrade instructions for the now-reverted authoring-side resolution are
removed from both 0.15-to-0.16 instructions.md files — the fix is
invisible to contract.json/storageHash, so no downstream action is
needed.

Refs: TML-3037
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…ql literal

Review comment 2 on PR #998: this branch added codecId: pg/date@1 directly
to db.Date's NATIVE_TYPE_SPECS row in psl-column-resolution.ts (2-sql), a
postgres codec id literal in family code.

db.Date goes back to codecId: null (matching origin/main). The noArgs case
in resolveDbNativeTypeAttribute now falls back through the already-threaded
scalarTypeDescriptors map (target-contributed, keyed everywhere else by PSL
base type name) by the full attribute name ("db.Date") before falling back
to the base type's own descriptor -- reusing the existing IoC seam instead
of inventing one, since baseDescriptor alone would resolve to DateTime's
pg/timestamptz@1, the original defect. The postgres adapter contributes
the db.Date -> pg/date@1 mapping under that same key in
postgresScalarTypeDescriptors (control-mutation-defaults.ts), so the
concrete codec id lives in target-owned code.

Extends lint:framework-vocabulary with a second scope: packages/2-sql,
forbidden ["pg/"], threshold 61 -- the measured count after removing this
branch's one new literal, unchanged from origin/main (every other pg/
occurrence in the scope is pre-existing debt, grandfathered per
TML-2986/2987, not touched here). The ~30-literal db.* attribute table
itself is not relocated; that is the existing remove-db-attributes
project's scope.

Refs: TML-3037
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…dDefault

Two designs were tried and abandoned for defect 7 (array columns cant
keep their default): relaxing the interpreters list-default check
admitted DateTime[] @default(now()), which Postgres DDL refuses; riding
defect 8s literal resolution at lowering time required smuggling
postgres nativeType through a framework type and was rejected in
review. Defect 8 landed at compare time instead, so it never rescues
emit for a list column.

The actual defect was narrower: PSL already has literal list syntax
(@default([...])) and the interpreter already lowers it to
kind: literal without touching the list check. Infer just never
printed it — any raw default it could not otherwise map fell back to
dbgenerated(<raw text>), which the interpreter always rejects on a
list column. The postgres control adapter already resolves a list
columns raw default to a structured literal at introspection time
(resolvedDefault, the same mechanism defect 3 uses for identity
columns), so infer now prints PSL literal-list syntax from that
resolved value instead of the raw-text fallback.

Deletes the acceptable_client_ids/scopes entries from
DEFAULT_OMISSIONS and regenerates the Supabase pack; both columns now
carry @default([]).

Refs: TML-3037
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…er-output-round-trips-through-contract-emit

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>

# Conflicts:
#	skills/extension-author/prisma-next-extension-upgrade/upgrades/0.15-to-0.16/instructions.md
#	skills/upgrade/prisma-next-upgrade/upgrades/0.15-to-0.16/instructions.md
…db-attributes

The remove-db-attributes project (spec on tml-2985-unify-type-channel; slice
2 = PR #975) replaces the whole @db.* channel with target-contributed bare
scalar types, and its Date type pins its codec explicitly. Binding date
columns to pg/date@1 through the @db.* machinery — first as a pg/ literal in
2-sql, then as a double-keyed scalarTypeDescriptors entry — extends exactly
the mechanism that project deletes, so the binding moves there.

This commit reverts the double-keyed map (82ffdd2) and returns db.Date to
codecId: null (inherit DateTime's pg/timestamptz@1), matching main. The
pg/date@1 codec itself stays registered on the postgres target, unbound;
the remove-db-attributes Date pin is one line once it lands.

Consequences carried in the same commit: the runtime instrument's date
scenario pins the still-broken include() decode (RUNTIME.DECODE_FAILED)
instead of asserting the fixed value, so the binding change flips a red
assertion rather than silently landing; the 0.15-to-0.16 upgrade entry for
the codecId change is withdrawn (the contract no longer changes in this
slice); the slice spec records the deferral.

Refs: TML-3037
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 3c1df7dc-dc02-4e5d-aa0b-e1aa5ef773eb

📥 Commits

Reviewing files that changed from the base of the PR and between 20e5b61 and 1547a33.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (7)
  • packages/2-sql/9-family/src/exports/control.ts
  • skills/extension-author/prisma-next-extension-upgrade/upgrades/0.15-to-0.16/instructions.md
  • skills/upgrade/prisma-next-upgrade/upgrades/0.15-to-0.16/instructions.md
  • test/integration/test/authoring/diagnostics/ambiguous-backrelation/expected-diagnostics.json
  • test/integration/test/authoring/diagnostics/ambiguous-backrelation/schema.prisma
  • test/integration/test/authoring/diagnostics/orphaned-backrelation/expected-diagnostics.json
  • test/integration/test/authoring/diagnostics/orphaned-backrelation/schema.prisma
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/2-sql/9-family/src/exports/control.ts
  • skills/upgrade/prisma-next-upgrade/upgrades/0.15-to-0.16/instructions.md
  • skills/extension-author/prisma-next-extension-upgrade/upgrades/0.15-to-0.16/instructions.md

📝 Walkthrough

Walkthrough

This PR reworks PSL 1:1 backrelation resolution and diagnostics, switches back-relation pluralization to the pluralize library, adds a default-resolution hook threaded through schema IR and Postgres normalization, introduces identity-column/autoincrement introspection and inference, adds a Postgres date codec, unbounded numeric params, and index-type registration, regenerates the Supabase contract, and adds round-trip integration tests plus upgrade docs.

Estimated code review effort: 4 (Complex) | ~75 minutes

Changes

Relation inference and naming

Layer / File(s) Summary
1:1 backrelation resolution
packages/2-sql/2-authoring/contract-psl/src/interpreter.ts, psl-field-resolution.ts, psl-relation-resolution.ts, test/interpreter.relations.test.ts
Owning-side vs back-side 1:1 relations are distinguished via relationAttributeDeclaresOwningSide, backrelation candidates track isList for cardinality, and skipped fields avoid duplicate lowering.
Relation naming normalization
packages/2-sql/9-family/src/core/psl-contract-infer/name-transforms.ts, packages/2-sql/9-family/package.json, tests
pluralize now delegates to the pluralize library instead of custom suffix rules, avoiding double-pluralization.
Diagnostic code consolidation
packages/2-sql/2-authoring/contract-psl/test/*, packages/1-framework/3-tooling/cli/test/output.errors.test.ts, test/integration/test/authoring/diagnostics/*
List-specific diagnostic codes are replaced with generic PSL_ORPHANED_BACKRELATION/PSL_AMBIGUOUS_BACKRELATION, and fixtures/tests are updated to match.

Estimated code review effort: 4 (Complex) | ~75 minutes

Default normalization and identity inference

Layer / File(s) Summary
Default resolver projection
packages/2-sql/9-family/src/core/migrations/contract-to-schema-ir.ts, src/exports/control.ts, test
An optional DefaultResolver hook normalizes contract defaults into resolved schema-IR defaults.
Postgres default normalization wiring
packages/3-targets/3-targets/postgres/src/core/default-normalizer.ts, migrations/diff-database-schema.ts, src/exports/*, tests
postgresResolveDefault re-parses function-shaped defaults for diff and control export flows.
Identity-column introspection and inference
packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts, psl-infer/infer-psl-contract.ts, packages/2-sql/1-core/schema-ir/test/*, tests
Postgres introspection reads attidentity to resolve autoincrement() defaults; PSL inference emits @default(autoincrement()), list-literal defaults, and dangling-FK warning comments.

Estimated code review effort: 4 (Complex) | ~75 minutes

Postgres codecs and index metadata

Layer / File(s) Summary
Postgres date codec
packages/3-targets/3-targets/postgres/src/core/codec-helpers.ts, codec-ids.ts, codecs.ts, src/exports/codecs.ts, tests
A new pg/date@1 codec encodes/decodes UTC calendar dates with JSON round-trip support and validation.
Unbounded numeric parameters
packages/3-targets/3-targets/postgres/src/core/codec-helpers.ts, codecs.ts, tests
NumericParams.precision becomes optional, allowing pgNumericColumn() with no arguments.
Postgres index type registration
packages/3-targets/3-targets/postgres/src/core/index-types.ts, descriptor-meta.ts, tests
postgresIndexTypes registers built-in access methods (btree, hash, gin, gist, spgist, brin) and validates their PSL @@index(type: ...) usage.

Estimated code review effort: 3 (Moderate) | ~30 minutes

Supabase contract regeneration

Layer / File(s) Summary
Contract generation rules
packages/3-extensions/supabase/scripts/generate-contract.ts, src/contract/CONTRACT-FIDELITY.md
DEFAULT_OMISSIONS is narrowed to auth.users.phone; index-omission and double-pluralization workarounds are removed.
Generated Supabase contract artifacts
packages/3-extensions/supabase/src/contract/contract.{d.ts,json,prisma}
Regenerated artifacts add supported defaults for array/JSONB columns, hash indexes on one_time_tokens, and an updated storage hash.

Estimated code review effort: 2 (Simple) | ~15 minutes

Round-trip validation and upgrade documentation

Layer / File(s) Summary
Infer emit verify journeys
test/integration/test/cli-journeys/infer-roundtrip-fidelity.e2e.test.ts, packages/3-targets/3-targets/postgres/test/psl-infer/*
A new CLI journey validates infer→emit→verify round trips for relations, defaults, indexes, identity columns, and dangling FKs.
Runtime round-trip execution
test/integration/test/infer-roundtrip-runtime.integration.test.ts
New runtime integration tests cover unbounded numeric, bare Decimal, and date decoding via live execution contexts.
0.15 to 0.16 upgrade notes
skills/extension-author/.../instructions.md, skills/upgrade/.../instructions.md
Upgrade docs describe identity-default strict-verify impact and pluralize-based back-relation naming.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI as contract infer/emit CLI
  participant Introspect as control-adapter (introspection)
  participant Infer as infer-psl-contract
  participant Interpreter as contract-psl interpreter
  participant Runtime as ExecutionContext

  CLI->>Introspect: introspect Postgres schema
  Introspect->>Introspect: read attidentity, column_default
  Introspect-->>CLI: columns with resolvedDefault (autoincrement/date/etc)
  CLI->>Infer: buildPslDocumentAst(columns, danglingForeignKeysByTable)
  Infer->>Infer: buildScalarField (default/list-literal), buildDanglingForeignKeyWarning
  Infer-->>CLI: contract.prisma
  CLI->>Interpreter: interpretPslDocumentToSqlContract
  Interpreter->>Interpreter: resolve owning-side/back-side relations, backrelation candidates
  Interpreter-->>CLI: SQL contract with relations/defaults/indexes
  CLI->>Runtime: createExecutionContext(contract)
  Runtime->>Runtime: decode date/numeric columns via pg/date, pg/numeric codecs
  Runtime-->>CLI: query results
Loading

Possibly related issues

Possibly related PRs

  • prisma/prisma-next#998: Overlaps directly on SqlColumnIR identity/default handling tests and the same contract-psl relation interpretation files (interpreter.ts, psl-field-resolution.ts, psl-relation-resolution.ts) for 1:1 backrelation handling.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 39.39% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly captures the main goal: making contract infer output round-trip through contract emit.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tml-3037-infer-emit-roundtrip-recut

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
packages/3-targets/6-adapters/postgres/test/control-mutation-defaults.test.ts (1)

166-196: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer separate assertions over toMatchObject for multiple fields.

Based on learnings, in test files, prefer separate expect() assertions for each field instead of combining checks with toMatchObject() when validating multiple fields. This yields clearer, more actionable failure messages that pinpoint exactly which field failed (e.g., if ok is false, the error is immediately obvious without digging through a deep object diff).

♻️ Proposed refactor
     it('keeps a jsonb literal expression as a function, unresolved', () => {
       const expression = "'{}'::jsonb";
-      expect(lower(expression)).toMatchObject({
-        ok: true,
-        value: { kind: 'storage', defaultValue: { kind: 'function', expression } },
-      });
+      const result = lower(expression);
+      expect(result.ok).toBe(true);
+      expect((result as any).value).toEqual({
+        kind: 'storage',
+        defaultValue: { kind: 'function', expression },
+      });
     });
 
     it('keeps a text[] literal expression as a function, unresolved', () => {
       const expression = "'{}'::text[]";
-      expect(lower(expression)).toMatchObject({
-        ok: true,
-        value: { kind: 'storage', defaultValue: { kind: 'function', expression } },
-      });
+      const result = lower(expression);
+      expect(result.ok).toBe(true);
+      expect((result as any).value).toEqual({
+        kind: 'storage',
+        defaultValue: { kind: 'function', expression },
+      });
     });
 
     it('keeps gen_random_uuid() a function', () => {
       const expression = 'gen_random_uuid()';
-      expect(lower(expression)).toMatchObject({
-        ok: true,
-        value: { kind: 'storage', defaultValue: { kind: 'function', expression } },
-      });
+      const result = lower(expression);
+      expect(result.ok).toBe(true);
+      expect((result as any).value).toEqual({
+        kind: 'storage',
+        defaultValue: { kind: 'function', expression },
+      });
     });
 
     it("keeps nextval(...) a function, unchanged (doesn't adopt the normalizer's autoincrement() rewrite)", () => {
       const expression = "nextval('seq'::regclass)";
-      expect(lower(expression)).toMatchObject({
-        ok: true,
-        value: { kind: 'storage', defaultValue: { kind: 'function', expression } },
-      });
+      const result = lower(expression);
+      expect(result.ok).toBe(true);
+      expect((result as any).value).toEqual({
+        kind: 'storage',
+        defaultValue: { kind: 'function', expression },
+      });
     });

(Note: If result is a discriminated union like a Result type, if (!result.ok) throw new Error(...) can be used instead of casting as any if you prefer stricter typing, or simply rely on result.value if the compiler understands it.)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/3-targets/6-adapters/postgres/test/control-mutation-defaults.test.ts`
around lines 166 - 196, Refactor the four tests around lower so each expected
field is asserted separately instead of using toMatchObject. After validating
ok, assert the storage kind, defaultValue kind, and expression independently
while preserving the existing expected values and test coverage.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/3-extensions/supabase/src/contract/CONTRACT-FIDELITY.md`:
- Around line 22-23: The “Column defaults (3)” audit entry in
CONTRACT-FIDELITY.md is stale: only auth.users.phone remains omitted. Update the
count to one and remove the acceptable_client_ids/scopes omission claim,
optionally incorporating them into the explanation that these defaults are now
declared as literal list defaults.

In `@packages/3-targets/3-targets/postgres/src/core/codec-helpers.ts`:
- Around line 137-138: Replace Date.UTC construction in pgDateDecode with a
zero-initialized Date followed by setUTCFullYear using the wire year, month, and
date. Apply the same pattern in pgDateDecodeJson at
packages/3-targets/3-targets/postgres/src/core/codec-helpers.ts lines 150-151,
preserving UTC-midnight behavior and correct years 0-99.

---

Nitpick comments:
In
`@packages/3-targets/6-adapters/postgres/test/control-mutation-defaults.test.ts`:
- Around line 166-196: Refactor the four tests around lower so each expected
field is asserted separately instead of using toMatchObject. After validating
ok, assert the storage kind, defaultValue kind, and expression independently
while preserving the existing expected values and test coverage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 78942b14-00d8-48c2-b0c1-545c04d83bd2

📥 Commits

Reviewing files that changed from the base of the PR and between 8d3f112 and 8a0efe3.

⛔ Files ignored due to path filters (4)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • projects/infer-emit-roundtrip/plan.md is excluded by !projects/**
  • projects/infer-emit-roundtrip/reproduction.md is excluded by !projects/**
  • projects/infer-emit-roundtrip/spec.md is excluded by !projects/**
📒 Files selected for processing (45)
  • packages/2-sql/1-core/schema-ir/test/sql-column-ir.test.ts
  • packages/2-sql/2-authoring/contract-psl/src/interpreter.ts
  • packages/2-sql/2-authoring/contract-psl/src/psl-field-resolution.ts
  • packages/2-sql/2-authoring/contract-psl/src/psl-relation-resolution.ts
  • packages/2-sql/2-authoring/contract-psl/test/interpreter.relations.test.ts
  • packages/2-sql/9-family/package.json
  • packages/2-sql/9-family/src/core/migrations/contract-to-schema-ir.ts
  • packages/2-sql/9-family/src/core/psl-contract-infer/name-transforms.ts
  • packages/2-sql/9-family/src/exports/control.ts
  • packages/2-sql/9-family/test/contract-to-schema-ir.test.ts
  • packages/2-sql/9-family/test/psl-contract-infer/name-transforms.test.ts
  • packages/3-extensions/supabase/scripts/generate-contract.ts
  • packages/3-extensions/supabase/src/contract/CONTRACT-FIDELITY.md
  • packages/3-extensions/supabase/src/contract/contract.d.ts
  • packages/3-extensions/supabase/src/contract/contract.json
  • packages/3-extensions/supabase/src/contract/contract.prisma
  • packages/3-targets/3-targets/postgres/src/core/codec-helpers.ts
  • packages/3-targets/3-targets/postgres/src/core/codec-ids.ts
  • packages/3-targets/3-targets/postgres/src/core/codecs.ts
  • packages/3-targets/3-targets/postgres/src/core/default-normalizer.ts
  • packages/3-targets/3-targets/postgres/src/core/descriptor-meta.ts
  • packages/3-targets/3-targets/postgres/src/core/index-types.ts
  • packages/3-targets/3-targets/postgres/src/core/migrations/diff-database-schema.ts
  • packages/3-targets/3-targets/postgres/src/core/psl-infer/infer-psl-contract.ts
  • packages/3-targets/3-targets/postgres/src/exports/codecs.ts
  • packages/3-targets/3-targets/postgres/src/exports/control.ts
  • packages/3-targets/3-targets/postgres/src/exports/default-normalizer.ts
  • packages/3-targets/3-targets/postgres/test/codecs-class.test.ts
  • packages/3-targets/3-targets/postgres/test/codecs-class.types.test-d.ts
  • packages/3-targets/3-targets/postgres/test/codecs-runtime-and-helpers.test.ts
  • packages/3-targets/3-targets/postgres/test/codecs.test.ts
  • packages/3-targets/3-targets/postgres/test/default-normalizer.test.ts
  • packages/3-targets/3-targets/postgres/test/index-types.test.ts
  • packages/3-targets/3-targets/postgres/test/psl-infer/infer-psl-contract-described-contracts.test.ts
  • packages/3-targets/3-targets/postgres/test/psl-infer/infer-psl-contract.test.ts
  • packages/3-targets/3-targets/postgres/test/psl-infer/print-psl/print-psl.enums.test.ts
  • packages/3-targets/3-targets/postgres/test/psl-infer/print-psl/print-psl.relations.test.ts
  • packages/3-targets/6-adapters/postgres/src/core/control-adapter.ts
  • packages/3-targets/6-adapters/postgres/test/control-mutation-defaults.test.ts
  • packages/3-targets/6-adapters/postgres/test/migrations/identity-column-introspection.integration.test.ts
  • packages/3-targets/6-adapters/postgres/test/migrations/planner-ddl-builders.test.ts
  • skills/extension-author/prisma-next-extension-upgrade/upgrades/0.15-to-0.16/instructions.md
  • skills/upgrade/prisma-next-upgrade/upgrades/0.15-to-0.16/instructions.md
  • test/integration/test/cli-journeys/infer-roundtrip-fidelity.e2e.test.ts
  • test/integration/test/infer-roundtrip-runtime.integration.test.ts

Comment on lines +22 to 23
**Column defaults (3):** `auth.users.phone` (`DEFAULT NULL` is a no-op, but round-trips through the raw-default parser as an explicit `@default(null)`, which the interpreter rejects); `auth.custom_oauth_providers.acceptable_client_ids` and `.scopes` (both `text[]` with `DEFAULT '{}'::text[]`, printed as `@default(dbgenerated("'{}'::text[]"))` — the interpreter rejects any function-kind default on a list field, and a `dbgenerated(...)` default is always function-kind at authoring time). Column type is declared in full for all three; only the `@default` is dropped. The jsonb `dbgenerated(...)` defaults that used to widen this list (TML-3037) are declared again — `db verify`'s permanent-drift disagreement is fixed generically, at the postgres target's `SchemaIR` construction, so it needs no authoring-side omission.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Stale "Column defaults (3)" entry — acceptable_client_ids/scopes are no longer omitted.

The doc states Machine-readable versions of these lists live in scripts/generate-contract.ts (COLUMN_OMISSIONS / DEFAULT_OMISSIONS), each with the full reasoning; this is the audit summary. and claims three column defaults are omitted, specifically citing auth.custom_oauth_providers.acceptable_client_ids and .scopes as rejected because they're function-kind dbgenerated(...) defaults on list fields.

However, generate-contract.ts's DEFAULT_OMISSIONS now contains only auth.users: ['phone'] — no entry for custom_oauth_providers — and the regenerated contract.d.ts/contract.prisma/contract.json show acceptable_client_ids and scopes now carry an explicit literal [] default (not function-kind/dbgenerated). The list-literal-default inference fix apparently resolved this omission, but this section wasn't updated to reflect it.

Update the count to reflect only auth.users.phone remaining omitted, and either drop the acceptable_client_ids/.scopes bullet or fold it into the "declared again" explanation used for the jsonb defaults later in the same paragraph.

As per coding guidelines, **/*.{md,mdc}: "Keep docs current (READMEs, rules, links)."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/3-extensions/supabase/src/contract/CONTRACT-FIDELITY.md` around
lines 22 - 23, The “Column defaults (3)” audit entry in CONTRACT-FIDELITY.md is
stale: only auth.users.phone remains omitted. Update the count to one and remove
the acceptable_client_ids/scopes omission claim, optionally incorporating them
into the explanation that these defaults are now declared as literal list
defaults.

Source: Coding guidelines

Comment on lines +137 to +138
export const pgDateDecode = (wire: Date): Date =>
new Date(Date.UTC(wire.getFullYear(), wire.getMonth(), wire.getDate()));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Date.UTC corrupts years 0-99. Both decoding sites use Date.UTC, which maps year arguments between 0 and 99 to 1900 through 1999, silently corrupting historical dates from the first century AD.

  • packages/3-targets/3-targets/postgres/src/core/codec-helpers.ts#L137-L138: instantiate new Date(0) and call setUTCFullYear(wire.getFullYear(), wire.getMonth(), wire.getDate()) to safely construct the UTC-midnight instant in pgDateDecode.
  • packages/3-targets/3-targets/postgres/src/core/codec-helpers.ts#L150-L151: apply the same setUTCFullYear pattern to safely construct the parsed JSON date in pgDateDecodeJson.
📍 Affects 1 file
  • packages/3-targets/3-targets/postgres/src/core/codec-helpers.ts#L137-L138 (this comment)
  • packages/3-targets/3-targets/postgres/src/core/codec-helpers.ts#L150-L151
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/3-targets/3-targets/postgres/src/core/codec-helpers.ts` around lines
137 - 138, Replace Date.UTC construction in pgDateDecode with a zero-initialized
Date followed by setUTCFullYear using the wire year, month, and date. Apply the
same pattern in pgDateDecodeJson at
packages/3-targets/3-targets/postgres/src/core/codec-helpers.ts lines 150-151,
preserving UTC-midnight behavior and correct years 0-99.

Date.UTC(y, m, d) normalizes out-of-range components (e.g. "2024-02-31"
becomes March 2, and two-digit years like "0024" map into 1900+y) rather
than producing NaN, so the previous Number.isNaN(getTime()) check was
unreachable for these inputs. decodeJson now compares the constructed
date's UTC year/month/day back against the parsed components and throws
if they diverge.

Found in PR #1011 review (F01).

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…or singular fields too

PSL_ORPHANED_BACKRELATION_LIST and PSL_AMBIGUOUS_BACKRELATION_LIST kept
their "_LIST" suffix after the messages were de-listed to also cover
singular back-relations, so the codes no longer described what they
fire on. Renamed to PSL_ORPHANED_BACKRELATION and
PSL_AMBIGUOUS_BACKRELATION everywhere: source, tests, integration
diagnostic fixtures, and project docs. No alias kept.

PR #1011 review (M1).

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…y IR bullet in spec

The pg/date@1 codec descriptor had no note explaining why it sits in
codecDescriptors unbound to any PSL type; a one-line comment states the
binding is deferred to remove-db-attributes' Date type and must be
pinned via codecId, not a second targetTypes activation.

The spec's Contract-impact section and the identity-columns writeup
both still claimed SqlColumnIR gains an identity field. Commit
a2b9fb7 deleted that plan: identity is recognized once, at
introspection, by the postgres control adapter stamping resolvedDefault
directly, with no new IR surface. Rewrote both bullets to match.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
@wmadden-electric

Copy link
Copy Markdown
Contributor Author

Review-rework round for the local review in projects/infer-emit-roundtrip/reviews/pr-1011/:

  • F01 (correctness)bfb89de11: pg/date@1's decodeJson now genuinely rejects calendar-invalid dates. The previous Number.isNaN(Date.UTC(...)) check was unreachable (Date.UTC normalizes, never NaNs), so "2024-02-31" silently decoded to March 2 and "0024-01-15" to 1924. The fix round-trips the constructed date's UTC components against the parsed ones; tests written failing-first for 2024-02-31, 2024-13-01, 0024-01-15. The PR body's "strict parsing" claim is now true (it wasn't at open — the TML-3037: contract infer output round-trips through contract emit #998 CodeRabbit finding had not actually been fixed).
  • M1 (naming)981929d36: PSL_ORPHANED_BACKRELATION_LIST / PSL_AMBIGUOUS_BACKRELATION_LISTPSL_ORPHANED_BACKRELATION / PSL_AMBIGUOUS_BACKRELATION — the codes fire for singular back-relations since this branch's 1:1 fix, so the _LIST suffix over-claimed. Swept repo-wide (source, CLI error tests, integration diagnostic fixtures, docs); no alias kept.
  • M2 + spec drift (docs)9636c4dc0: one-line marker at pgDateDescriptor that the PSL binding is deferred to remove-db-attributes and must pin via codecId (not a second targetTypes activation — review F02); spec's stale "SqlColumnIR gains an identity field" bullets corrected to the shipped shape (identity compensated once at introspection, no new IR surface).
  • F02: no code change, by design — the constraint is recorded at the descriptor and in the remove-db-attributes handoff.

Full gate set re-run green on the new head: packages 13185, integration 1173, e2e 109, plus lints/fixtures/upgrade-coverage.

@wmadden-electric wmadden-electric changed the title TML-3037: contract infer output round-trips through contract emit (re-cut) TML-3037: contract infer output round-trips through contract emit Jul 21, 2026
Comments across the branch narrated dead system states ("used to throw",
"the shape emit rejected before this fix", "no longer resolves") and the
work that removed them. A comment survives the change it rode in on; the
next reader has no "before". Rewritten in present tense to state the
invariant or constraint each site actually relies on, and compressed where
the narration was the bulk. The runtime instrument header shrinks from 28
lines to 10.

Refs: TML-3037
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Refs: TML-3037
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
* target-agnostic. Omitted entirely, the contract's raw default is the
* resolved default unchanged (today's behavior).
*/
export type DefaultResolver = (def: ColumnDefault, resolvedNativeType: string) => ColumnDefault;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this?

pgFloat4Descriptor,
pgFloat8Descriptor,
pgNumericDescriptor,
// PSL `@db.Date` binding is deferred to remove-db-attributes' `Date` type; pin it via codecId, not a second targetTypes activation here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No TODOs without a Linear ticket ID

…d codes

Refs: TML-3037
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…oundtrip-recut

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>

# Conflicts:
#	pnpm-lock.yaml
@pkg-pr-new

pkg-pr-new Bot commented Jul 21, 2026

Copy link
Copy Markdown

Open in StackBlitz

@prisma-next/extension-author-tools

npm i https://pkg.pr.new/@prisma-next/extension-author-tools@1011

@prisma-next/mongo-runtime

npm i https://pkg.pr.new/@prisma-next/mongo-runtime@1011

@prisma-next/family-mongo

npm i https://pkg.pr.new/@prisma-next/family-mongo@1011

@prisma-next/sql-runtime

npm i https://pkg.pr.new/@prisma-next/sql-runtime@1011

@prisma-next/family-sql

npm i https://pkg.pr.new/@prisma-next/family-sql@1011

@prisma-next/extension-arktype-json

npm i https://pkg.pr.new/@prisma-next/extension-arktype-json@1011

@prisma-next/middleware-cache

npm i https://pkg.pr.new/@prisma-next/middleware-cache@1011

@prisma-next/mongo

npm i https://pkg.pr.new/@prisma-next/mongo@1011

@prisma-next/extension-paradedb

npm i https://pkg.pr.new/@prisma-next/extension-paradedb@1011

@prisma-next/extension-pgvector

npm i https://pkg.pr.new/@prisma-next/extension-pgvector@1011

@prisma-next/extension-postgis

npm i https://pkg.pr.new/@prisma-next/extension-postgis@1011

@prisma-next/postgres

npm i https://pkg.pr.new/@prisma-next/postgres@1011

@prisma-next/sql-orm-client

npm i https://pkg.pr.new/@prisma-next/sql-orm-client@1011

@prisma-next/sqlite

npm i https://pkg.pr.new/@prisma-next/sqlite@1011

@prisma-next/extension-supabase

npm i https://pkg.pr.new/@prisma-next/extension-supabase@1011

@prisma-next/target-mongo

npm i https://pkg.pr.new/@prisma-next/target-mongo@1011

@prisma-next/adapter-mongo

npm i https://pkg.pr.new/@prisma-next/adapter-mongo@1011

@prisma-next/driver-mongo

npm i https://pkg.pr.new/@prisma-next/driver-mongo@1011

@prisma-next/contract

npm i https://pkg.pr.new/@prisma-next/contract@1011

@prisma-next/utils

npm i https://pkg.pr.new/@prisma-next/utils@1011

@prisma-next/config

npm i https://pkg.pr.new/@prisma-next/config@1011

@prisma-next/errors

npm i https://pkg.pr.new/@prisma-next/errors@1011

@prisma-next/framework-components

npm i https://pkg.pr.new/@prisma-next/framework-components@1011

@prisma-next/operations

npm i https://pkg.pr.new/@prisma-next/operations@1011

@prisma-next/ts-render

npm i https://pkg.pr.new/@prisma-next/ts-render@1011

@prisma-next/contract-authoring

npm i https://pkg.pr.new/@prisma-next/contract-authoring@1011

@prisma-next/ids

npm i https://pkg.pr.new/@prisma-next/ids@1011

@prisma-next/psl-parser

npm i https://pkg.pr.new/@prisma-next/psl-parser@1011

@prisma-next/psl-printer

npm i https://pkg.pr.new/@prisma-next/psl-printer@1011

@prisma-next/cli

npm i https://pkg.pr.new/@prisma-next/cli@1011

@prisma-next/cli-telemetry

npm i https://pkg.pr.new/@prisma-next/cli-telemetry@1011

@prisma-next/config-loader

npm i https://pkg.pr.new/@prisma-next/config-loader@1011

@prisma-next/emitter

npm i https://pkg.pr.new/@prisma-next/emitter@1011

@prisma-next/language-server

npm i https://pkg.pr.new/@prisma-next/language-server@1011

@prisma-next/migration-tools

npm i https://pkg.pr.new/@prisma-next/migration-tools@1011

prisma-next

npm i https://pkg.pr.new/prisma-next@1011

@prisma-next/vite-plugin-contract-emit

npm i https://pkg.pr.new/@prisma-next/vite-plugin-contract-emit@1011

@prisma-next/mongo-codec

npm i https://pkg.pr.new/@prisma-next/mongo-codec@1011

@prisma-next/mongo-contract

npm i https://pkg.pr.new/@prisma-next/mongo-contract@1011

@prisma-next/mongo-value

npm i https://pkg.pr.new/@prisma-next/mongo-value@1011

@prisma-next/mongo-contract-psl

npm i https://pkg.pr.new/@prisma-next/mongo-contract-psl@1011

@prisma-next/mongo-contract-ts

npm i https://pkg.pr.new/@prisma-next/mongo-contract-ts@1011

@prisma-next/mongo-emitter

npm i https://pkg.pr.new/@prisma-next/mongo-emitter@1011

@prisma-next/mongo-schema-ir

npm i https://pkg.pr.new/@prisma-next/mongo-schema-ir@1011

@prisma-next/mongo-query-ast

npm i https://pkg.pr.new/@prisma-next/mongo-query-ast@1011

@prisma-next/mongo-orm

npm i https://pkg.pr.new/@prisma-next/mongo-orm@1011

@prisma-next/mongo-query-builder

npm i https://pkg.pr.new/@prisma-next/mongo-query-builder@1011

@prisma-next/mongo-lowering

npm i https://pkg.pr.new/@prisma-next/mongo-lowering@1011

@prisma-next/mongo-wire

npm i https://pkg.pr.new/@prisma-next/mongo-wire@1011

@prisma-next/sql-contract

npm i https://pkg.pr.new/@prisma-next/sql-contract@1011

@prisma-next/sql-errors

npm i https://pkg.pr.new/@prisma-next/sql-errors@1011

@prisma-next/sql-operations

npm i https://pkg.pr.new/@prisma-next/sql-operations@1011

@prisma-next/sql-schema-ir

npm i https://pkg.pr.new/@prisma-next/sql-schema-ir@1011

@prisma-next/sql-contract-psl

npm i https://pkg.pr.new/@prisma-next/sql-contract-psl@1011

@prisma-next/sql-contract-ts

npm i https://pkg.pr.new/@prisma-next/sql-contract-ts@1011

@prisma-next/sql-contract-emitter

npm i https://pkg.pr.new/@prisma-next/sql-contract-emitter@1011

@prisma-next/sql-lane-query-builder

npm i https://pkg.pr.new/@prisma-next/sql-lane-query-builder@1011

@prisma-next/sql-relational-core

npm i https://pkg.pr.new/@prisma-next/sql-relational-core@1011

@prisma-next/sql-builder

npm i https://pkg.pr.new/@prisma-next/sql-builder@1011

@prisma-next/target-postgres

npm i https://pkg.pr.new/@prisma-next/target-postgres@1011

@prisma-next/target-sqlite

npm i https://pkg.pr.new/@prisma-next/target-sqlite@1011

@prisma-next/adapter-postgres

npm i https://pkg.pr.new/@prisma-next/adapter-postgres@1011

@prisma-next/adapter-sqlite

npm i https://pkg.pr.new/@prisma-next/adapter-sqlite@1011

@prisma-next/driver-postgres

npm i https://pkg.pr.new/@prisma-next/driver-postgres@1011

@prisma-next/driver-sqlite

npm i https://pkg.pr.new/@prisma-next/driver-sqlite@1011

commit: 1547a33

@github-actions

Copy link
Copy Markdown

size-limit report 📦

Path Size
postgres / no-emit 160.18 KB (+0.29% 🔺)
postgres / emit 143.57 KB (+0.23% 🔺)
mongo / no-emit 99.3 KB (0%)
mongo / emit 89.43 KB (0%)
cf-worker / no-emit 186.47 KB (+0.27% 🔺)
cf-worker / emit 167.82 KB (+0.23% 🔺)

@wmadden
wmadden added this pull request to the merge queue Jul 21, 2026
Merged via the queue into main with commit 649e805 Jul 21, 2026
22 checks passed
@wmadden
wmadden deleted the tml-3037-infer-emit-roundtrip-recut branch July 21, 2026 08:59
@coderabbitai coderabbitai Bot mentioned this pull request Jul 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants