Skip to content

TML-2952: type SQL enum columns through the codec instead of printing their stored values#896

Merged
wmadden-electric merged 5 commits into
mainfrom
worktree/wonderful-feistel-cb6888
Jul 1, 2026
Merged

TML-2952: type SQL enum columns through the codec instead of printing their stored values#896
wmadden-electric merged 5 commits into
mainfrom
worktree/wonderful-feistel-cb6888

Conversation

@wmadden-electric

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

Copy link
Copy Markdown
Contributor

An enum column/field's TypeScript type is the union of its allowed values ('low' | 'high' | 'urgent'). Today that union is built by printing the stored, codec-encoded values straight to TS literals in two separate emit-path spots — which bypasses the codec, the one component that knows a stored value's real TypeScript type. It's correct only by coincidence: today's enums use text/int codecs where the encoded form happens to equal the output type. This routes emit typing through the codec, on one path, and removes the duplication. SQL only; Mongo is the sibling TML-2953.

No user-visible change for today's enums — correctness + de-duplication.

What changed

  • New codec seam. renderValueType(encodedValue, channel) on CodecDescriptor and renderValueTypeFor on CodecLookup — the value-keyed counterpart of the existing renderOutputType. It renders a single codec-encoded value as its TS output literal, or undefined when the codec can't (e.g. a Date-output codec — no base-class default, so non-identity codecs opt out). Implemented for the Postgres and Mongo primitive codecs.
  • SQL column emit now renders each value-set value through the codec via a shared renderValueSetType helper, instead of printing the stored values directly (renderValueSetUnionBase/renderValueSetLiteral deleted).
  • Framework field emit no longer special-cases enums. The hardcoded domain-enum override (DomainEnumLookup, renderEnumValueUnion, renderEnumMemberLiteral) is deleted; a new family-supplied EmissionSpi.resolveFieldValueSet hook supplies a field's permitted values, rendered through the same codec helper. The domain enum is no longer a typing input — it keeps only the runtime db.enums dictionary.

Keeping Mongo byte-identical (the one design call worth flagging)

FieldOutputTypes is generated once for both families, and the framework emitter must not read storage. So deleting the shared override bare would regress Mongo's emitted enum fields (the demo role: 'admin' | 'author' | 'reader' would widen to string). The resolver hook resolves this cleanly:

  • SQL sources permitted values from the storage value set (field → column → value set) — the domain enum stops being a SQL typing input.
  • Mongo supplies an interim resolver reading domain.enum (it has no value-set entity yet), keeping its output byte-identical. This is removed by TML-2953, which brings Mongo onto the value-set model.

So the acceptance criterion "domain enum is not a typing input" holds for SQL now, and repo-wide once TML-2953 lands.

The no-emit (typeof contract) path is unchanged — it already propagated the authored, typed values, and is the reference the emit path now matches.

Acceptance evidence

  • A1 — every emit-path enum type goes through the codec; a structural guard keeps the deleted direct-render helpers gone.
  • A2 — the guard pins the only domain-enum typing readers to the db.enums dictionary block and the Mongo interim resolver; the SQL emitter reads none.
  • A4 — byte-identical for identity codecs, both families: empty contract.json diff, fixtures:check clean, the mongo-demo contract.d.ts/.json git-unchanged across every commit.
  • A5 — a non-identity test codec (encodes 0|1|2, outputs 'low'|'high'|'urgent') types as the codec output, not the raw encoded literal — on both the column and field surfaces.
  • A6 — the emitted field type equals the no-emit typeof contract union.
  • A7 — no new casts (lint:casts delta 0), lint:deps clean; build, typecheck, and the full test suites (packages/integration/e2e) green.

Notes

  • Shaping artifacts (spec + dispatch plan) live under projects/refactor-enum-typing-via-codec/. The spec was corrected during shaping: the family-resolver design (the framework must not reach into storage) and the SQL-scoping of A2.
  • The change is additive at the framework layer (the new SPI hook is optional), so no downstream extension needed updating; one Mongo test harness updated its mock lookup to mirror production.
  • Verified on main including Exercise Mongo enums in retail-store; add raw/execute/mongoEnums to the Mongo client #880 (Mongo enums in retail-store), which re-emits byte-identically under this change.

Upgrade instructions (0.14 → 0.15)

  • skills/extension-author/prisma-next-extension-upgrade/upgrades/0.14-to-0.15/ — a real entry: an extension that types a value-set-restricted (enum) column via a custom CodecDescriptorImpl must implement renderValueLiteral (and expose renderValueLiteralFor on a hand-built CodecLookup), or the column widens to the codec output type. Framework-built lookups already supply it. Validated by execution (reverting packages/3-extensions/ reddens the mongo enum e2e; re-applying reproduces the branch, tests green).
  • skills/upgrade/prisma-next-upgrade/upgrades/0.14-to-0.15/ — user side is incidental (changes: []); the only examples/ touch is a type test and the emitted contract is byte-identical.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Value-restricted fields now emit more precise literal unions instead of broad string types.
    • Type generation now uses codec-based literal rendering across supported databases, improving output for enums and similar restricted values.
  • Bug Fixes

    • Fixed cases where restricted field types could fall back to overly generic types.
    • Improved handling for non-identity codecs so emitted types stay aligned with stored values.
  • Tests

    • Added coverage for literal rendering, value-set narrowing, and fallback behavior.

@wmadden-electric wmadden-electric requested a review from a team as a code owner July 1, 2026 08:49
@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

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: 6bf76e06-57e3-47a5-b4f5-0a41d589ee7a

📥 Commits

Reviewing files that changed from the base of the PR and between 5548108 and 613bcf2.

⛔ Files ignored due to path filters (1)
  • projects/refactor-enum-typing-via-codec/spec.md is excluded by !projects/**
📒 Files selected for processing (5)
  • packages/1-framework/1-core/framework-components/src/exports/codec.ts
  • packages/1-framework/1-core/framework-components/src/shared/render-ts-literal.ts
  • packages/2-sql/3-tooling/emitter/test/value-set-codec-lookups.ts
  • packages/3-mongo-target/2-mongo-adapter/src/core/codecs.ts
  • packages/3-targets/3-targets/postgres/src/core/codecs.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/1-framework/1-core/framework-components/src/exports/codec.ts

📝 Walkthrough

Walkthrough

This PR replaces the DomainEnumLookup/ContractEnum-based enum type narrowing with a codec-driven renderValueLiteral/renderValueLiteralFor mechanism. CodecDescriptor and CodecLookup gain optional literal-rendering hooks, EmissionSpi.resolveFieldValueSet returns codec-encoded values, and SQL/Mongo emitters and Postgres/Mongo codec descriptors implement the new seam, with a new renderTsLiteral helper and extensive test/doc updates.

Changes

Value-literal codec narrowing

Layer / File(s) Summary
Codec descriptor and lookup contracts
.../codec-descriptor.ts, .../codec-types.ts, .../emission-types.ts
CodecDescriptor, CodecDescriptorImpl, and CodecLookup gain optional renderValueLiteral/renderValueLiteralFor; EmissionSpi.resolveFieldValueSet? return type updated to { encodedValues, codecId } | undefined with expanded docs.
extractCodecLookup value renderer wiring
.../control-stack.ts, .../control-stack.test.ts
Registers per-codec renderValueLiteral functions and exposes renderValueLiteralFor(id, value, side) on the codec lookup; tests cover delegation, unknown codec ids, and missing renderer fallback.
Domain type generation resolveFieldValueSet/renderValueSetType
.../domain-type-generation.ts, .../domain-type-generation.test.ts
Removes DomainEnumLookup/ContractEnum/ValueSetRef plumbing; adds ResolvedFieldValueSet, FieldValueSetResolver, renderValueSetType; resolveFieldType and map generators thread resolved value sets through codec-based literal rendering.
generate-contract-dts wiring and integration tests
generate-contract-dts.ts, emitter.integration.test.ts, generate-contract-dts.spi-branches.test.ts
generateContractDts passes an emitter-provided resolveFieldValueSet hook instead of building a domain enum lookup; integration and SPI-branch tests use local literal codec lookups and resolvers to validate narrowing and fallback.
Mongo emitter resolveFieldValueSet
packages/2-mongo-family/.../index.ts, mongo.enum.e2e.test.ts
mongoEmission resolves enum-referenced value sets into encoded values and codec id; Mongo enum E2E tests add literal rendering to the codec lookup mock and pass an extra positional argument.
SQL emitter resolveFieldValueSet and renderValueSetType usage
packages/2-sql/.../index.ts, emitter-hook.storage-column-types.test.ts, value-set-codec-lookups.ts
sqlEmission resolves storage value sets via table/column lookups and renders column types through renderValueSetType; removes local union-rendering helpers; adds identity/non-identity codec lookup fixtures and tests for enum column/field narrowing and fallback behavior.
Target codec renderValueLiteral implementations
render-ts-literal.ts, exports/codec.ts, mongo-adapter codecs.ts, postgres codecs.ts, codec-render-value-literal.test.ts
Adds renderTsLiteral helper and exports it; Mongo and Postgres codec descriptors implement renderValueLiteral using it; Postgres test suite validates literal rendering across text, numeric, boolean, and timestamp codecs.
Upgrade guidance updates
skills/.../0.14-to-0.15/instructions.md (extension-author, upgrade)
Documents the migration from domain-enum override to codec-based renderValueLiteral/renderValueLiteralFor narrowing.
Demo type-level assertion
examples/prisma-next-demo/test/demo-dx.types.test.ts
Adds a type test confirming emitted Post.priority type matches the direct enum value union.

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

Possibly related PRs

  • prisma/prisma-next#797: Both PRs modify scalar field typing in domain-type-generation.ts's resolveFieldType, extending codec/lookup rendering capabilities.
  • prisma/prisma-next#807: Both PRs modify extractCodecLookup's return surface in control-stack.ts, at the codec-lookup construction layer.

Suggested reviewers: wmadden, aqrln

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.41% 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 accurately summarizes the main change: enum/value-set columns are now typed through codec rendering instead of direct stored-value printing.
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 worktree/wonderful-feistel-cb6888

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.

@pkg-pr-new

pkg-pr-new Bot commented Jul 1, 2026

Copy link
Copy Markdown

Open in StackBlitz

@prisma-next/extension-author-tools

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

@prisma-next/mongo-runtime

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

@prisma-next/family-mongo

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

@prisma-next/sql-runtime

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

@prisma-next/family-sql

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

@prisma-next/extension-arktype-json

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

@prisma-next/middleware-cache

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

@prisma-next/mongo

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

@prisma-next/extension-paradedb

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

@prisma-next/extension-pgvector

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

@prisma-next/extension-postgis

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

@prisma-next/postgres

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

@prisma-next/sql-orm-client

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

@prisma-next/sqlite

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

@prisma-next/extension-supabase

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

@prisma-next/target-mongo

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

@prisma-next/adapter-mongo

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

@prisma-next/driver-mongo

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

@prisma-next/contract

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

@prisma-next/utils

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

@prisma-next/config

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

@prisma-next/errors

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

@prisma-next/framework-components

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

@prisma-next/operations

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

@prisma-next/ts-render

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

@prisma-next/contract-authoring

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

@prisma-next/ids

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

@prisma-next/psl-parser

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

@prisma-next/psl-printer

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

@prisma-next/cli

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

@prisma-next/cli-telemetry

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

@prisma-next/config-loader

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

@prisma-next/emitter

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

@prisma-next/language-server

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

@prisma-next/migration-tools

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

prisma-next

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

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

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

@prisma-next/mongo-codec

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

@prisma-next/mongo-contract

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

@prisma-next/mongo-value

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

@prisma-next/mongo-contract-psl

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

@prisma-next/mongo-contract-ts

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

@prisma-next/mongo-emitter

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

@prisma-next/mongo-schema-ir

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

@prisma-next/mongo-query-ast

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

@prisma-next/mongo-orm

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

@prisma-next/mongo-query-builder

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

@prisma-next/mongo-lowering

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

@prisma-next/mongo-wire

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

@prisma-next/sql-contract

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

@prisma-next/sql-errors

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

@prisma-next/sql-operations

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

@prisma-next/sql-schema-ir

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

@prisma-next/sql-contract-psl

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

@prisma-next/sql-contract-ts

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

@prisma-next/sql-contract-emitter

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

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

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

@prisma-next/sql-relational-core

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

@prisma-next/sql-builder

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

@prisma-next/target-postgres

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

@prisma-next/target-sqlite

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

@prisma-next/adapter-postgres

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

@prisma-next/adapter-sqlite

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

@prisma-next/driver-postgres

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

@prisma-next/driver-sqlite

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

commit: 613bcf2

@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

size-limit report 📦

Path Size
postgres / no-emit 160.58 KB (+0.15% 🔺)
postgres / emit 147.77 KB (+0.15% 🔺)
mongo / no-emit 79.96 KB (+0.12% 🔺)
mongo / emit 72.77 KB (+0.13% 🔺)
cf-worker / no-emit 188.34 KB (+0.12% 🔺)
cf-worker / emit 173.77 KB (+0.12% 🔺)

Comment thread examples/prisma-next-demo/test/demo-dx.types.test.ts Outdated
Comment thread packages/1-framework/1-core/framework-components/src/shared/codec-descriptor.ts Outdated
Comment thread packages/1-framework/1-core/framework-components/src/shared/codec-types.ts Outdated
Comment thread packages/1-framework/3-tooling/emitter/src/domain-type-generation.ts Outdated
Comment thread packages/1-framework/3-tooling/emitter/src/domain-type-generation.ts Outdated
… + plan

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…ing stored values (TML-2952)

An enum column/field TypeScript type is produced by the codec, from the storage
value set, instead of being printed straight from the stored values in two
separate emit paths (which bypassed the codec and only worked by coincidence for
text/int codecs).

- Add a codec renderValueType seam (CodecDescriptor + CodecLookup); implement for
  the Postgres and Mongo primitive codecs.
- Route the SQL column emit through it via a shared renderValueSetType helper,
  relocated to the framework emitter so column and field emit share one path.
- Replace the hardcoded domain-enum field override with a family-supplied
  EmissionSpi.resolveFieldValueSet hook. SQL sources permitted values from the
  storage value set; Mongo supplies an interim resolver reading domain.enum,
  removed once Mongo stores enum values as a value set (TML-2953). The domain enum
  is no longer a typing input; it keeps only the runtime db.enums dictionary.

Byte-identical for text/int enums on both families (empty contract diff,
fixtures:check clean). A non-identity test codec proves the emitted type is the
codec output, not the raw encoded literal; emit and no-emit agree. Structural
guards keep the deleted direct-render helpers gone and pin the only domain-enum
typing reader to the Mongo interim resolver. No new casts; lint:deps clean.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
@wmadden-electric wmadden-electric force-pushed the worktree/wonderful-feistel-cb6888 branch from e6fa3cc to 1d473db Compare July 1, 2026 09:14

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/3-targets/3-targets/postgres/src/core/codecs.ts (1)

1001-1016: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Avoid reusing the identity literal renderer for PgCharDescriptor. SqlCharCodec.decode() trims trailing spaces, so CHAR(n) is not identity-preserving in the same way as VARCHAR. renderValueType should apply the same normalization path or otherwise account for padded values to avoid mismatches on pg/char@1.

🤖 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/codecs.ts` around lines 1001 -
1016, PgCharDescriptor is incorrectly using the identity literal renderer, which
assumes values round-trip unchanged; update PgCharDescriptor.renderValueType to
follow the same normalization path used by SqlCharCodec.decode() for CHAR
values, since trailing-space padding is trimmed and pg/char@1 is not
identity-preserving. Use the PgCharDescriptor and SqlCharCodec symbols to locate
the fix, and ensure the rendered value type accounts for padded CHAR semantics
instead of delegating to renderPrimitiveLiteral directly.
🧹 Nitpick comments (6)
packages/2-mongo-family/3-tooling/emitter/src/index.ts (1)

262-278: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Correct interim implementation; minor type-duplication nit.

The logic correctly gates on entityKind === 'enum', resolves the domain enum, and returns { encodedValues, codecId } matching the FieldValueSetResolver contract. The inline return type { readonly encodedValues: readonly JsonValue[]; readonly codecId: string } | undefined duplicates the ResolvedFieldValueSet type already exported from domain-type-generation.ts (and is duplicated again in packages/2-sql/3-tooling/emitter/src/index.ts's resolveFieldValueSet). Importing and reusing ResolvedFieldValueSet would keep both family emitters structurally locked to the same contract instead of independently redeclaring it.

♻️ Suggested fix
-import type {
-  Contract,
-  ContractModel,
-  ContractModelBase,
-  JsonValue,
-} from '`@prisma-next/contract/types`';
+import type { Contract, ContractModel, ContractModelBase } from '`@prisma-next/contract/types`';
+import type { ResolvedFieldValueSet } from '`@prisma-next/emitter/domain-type-generation`';
...
-  resolveFieldValueSet(
-    _modelName: string,
-    fieldName: string,
-    model: ContractModelBase,
-    contract: Contract,
-  ): { readonly encodedValues: readonly JsonValue[]; readonly codecId: string } | undefined {
+  resolveFieldValueSet(
+    _modelName: string,
+    fieldName: string,
+    model: ContractModelBase,
+    contract: Contract,
+  ): ResolvedFieldValueSet | undefined {
🤖 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/2-mongo-family/3-tooling/emitter/src/index.ts` around lines 262 -
278, The interim logic in resolveFieldValueSet is correct, but the return type
is being redeclared inline instead of reusing the shared ResolvedFieldValueSet
type. Update this emitter’s resolveFieldValueSet signature to import and
reference ResolvedFieldValueSet from domain-type-generation.ts, and apply the
same cleanup in the matching resolveFieldValueSet implementation in the SQL
family emitter so both stay aligned with the single exported contract type.
packages/2-sql/3-tooling/emitter/src/index.ts (1)

318-357: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Correct implementation; duplicate value-set lookup with computeColumnType.

The table/column/value-set resolution chain is correct and matches the fixtures exercised in emitter-hook.storage-column-types.test.ts. However, the entityAt<StorageValueSet>(storage, { namespaceId: column.valueSet.namespaceId, entityKind: column.valueSet.entityKind, entityName: column.valueSet.entityName }) call here is identical to the one in computeColumnType (Line 457-461). Extracting a small resolveColumnValueSet(storage, column) helper would remove the duplication and keep the lookup logic in one place.

Also, similar to the Mongo emitter, the inline return type duplicates ResolvedFieldValueSet from @prisma-next/emitter/domain-type-generation rather than importing it.

♻️ Suggested helper extraction
+function resolveColumnValueSet(
+  storage: SqlStorage,
+  column: StorageColumn,
+): StorageValueSet | undefined {
+  if (!column.valueSet) return undefined;
+  return entityAt<StorageValueSet>(storage, {
+    namespaceId: column.valueSet.namespaceId,
+    entityKind: column.valueSet.entityKind,
+    entityName: column.valueSet.entityName,
+  });
+}

Then in both resolveFieldValueSet and computeColumnType, replace the inline entityAt calls with resolveColumnValueSet(storage, column).

🤖 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/2-sql/3-tooling/emitter/src/index.ts` around lines 318 - 357,
`resolveFieldValueSet` duplicates the same `entityAt<StorageValueSet>(...)`
lookup already performed in `computeColumnType`; extract that shared logic into
a small `resolveColumnValueSet(storage, column)` helper and have both methods
call it. While touching `resolveFieldValueSet`, replace the inline return shape
with the shared `ResolvedFieldValueSet` type imported from
`@prisma-next/emitter/domain-type-generation` so the emitter uses the canonical
type instead of redefining it.
packages/1-framework/3-tooling/emitter/test/enum-typing-codec-seam.guard.test.ts (1)

79-83: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

DOMAIN_ENUM_READ regex has a blind spot for dot-property access.

The pattern only catches .enum(, .enum[, and .enum?.[. A future read like ns.enum.Priority (plain dot-name access) would silently bypass this guard, defeating its purpose of catching domain.enum typing regressions.

🛡️ Suggested widening
-const DOMAIN_ENUM_READ = /\.enum\s*(\?\.\[|\[|\))/;
+const DOMAIN_ENUM_READ = /\.enum\s*(\?\.\[|\[|\)|\?\.[A-Za-z_$]|\.[A-Za-z_$])/;
🤖 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/1-framework/3-tooling/emitter/test/enum-typing-codec-seam.guard.test.ts`
around lines 79 - 83, The DOMAIN_ENUM_READ guard regex is too narrow because it
misses plain dot-property access after `.enum`, so update the matcher in the
enum-typing-codec-seam test to also recognize reads like `ns.enum.Priority`.
Keep the existing intent of matching only real access syntax in the test, but
widen the pattern in DOMAIN_ENUM_READ so it covers dot-name property reads in
addition to the current paren/bracket forms.
packages/1-framework/3-tooling/emitter/test/domain-type-generation.test.ts (1)

41-54: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Test-only codec-lookup mock duplicated 3x with a subtle quoting inconsistency.

literalCodecLookup() here properly escapes strings via serializeValue, but the near-identical helpers in emitter.integration.test.ts (lines 12-25) and generate-contract-dts.spi-branches.test.ts (lines 14-27) build the string literal with a raw template (`'${value}'`) that doesn't escape embedded quotes. Consider extracting one shared test helper (e.g. in a shared test-utils module) so all three suites stay in sync and use the safer escaping.

🤖 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/1-framework/3-tooling/emitter/test/domain-type-generation.test.ts`
around lines 41 - 54, The test-only literal codec lookup mock is duplicated
across multiple test suites, and two copies build string literals without
escaping embedded quotes. Extract a shared test helper based on
literalCodecLookup()/renderValueTypeFor() and use serializeValue for string
values so emitter.integration.test.ts and
generate-contract-dts.spi-branches.test.ts stay consistent with this safer
implementation.
packages/3-targets/3-targets/postgres/src/core/codecs.ts (1)

88-105: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

renderPrimitiveLiteral is duplicated verbatim across the Mongo and Postgres target packages (and a test helper).

The exact same function body exists in packages/3-mongo-target/2-mongo-adapter/src/core/codecs.ts. Given the architecture separates per-family target implementations, this may be an intentional isolation choice, but if the two copies ever diverge (e.g., one gets the escaping fix above and the other doesn't), correctness silently drifts between families. Consider hoisting a shared renderPrimitiveLiteral/escaping utility into a common location (e.g. framework-components/codec) that both target packages import.

Also applies to: 163-171, 246-254, 288-296, 330-338, 372-380, 414-422, 456-464, 1001-1016, 1025-1040, 1054-1066, 1075-1087

🤖 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/codecs.ts` around lines 88 -
105, The `renderPrimitiveLiteral` implementation in `codecs.ts` is duplicated
across the Postgres and Mongo target packages, so update the shared
literal-rendering logic by extracting it into a common utility (for example
under `framework-components/codec`) and have both target `codecs.ts` files
import it. Make sure any callers of `renderPrimitiveLiteral` and the related
`renderValueType` paths keep using the shared helper so escaping and literal
formatting stay consistent across families and test helpers.
packages/3-targets/3-targets/postgres/test/codec-render-value-type.test.ts (1)

1-94: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

LGTM! Solid coverage of quoting/escaping, numeric/boolean literals, and the non-narrowable fallback case.

One minor gap: the alias-based descriptors (pgCharDescriptor, pgVarcharDescriptor, pgIntDescriptor, pgFloatDescriptor) that also gained renderValueType aren't exercised here — low risk since they delegate to the same tested renderPrimitiveLiteral, but worth adding for completeness.

🤖 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/test/codec-render-value-type.test.ts`
around lines 1 - 94, Add test coverage in codec-render-value-type.test.ts for
the alias-based descriptors that also implement renderValueType:
pgCharDescriptor, pgVarcharDescriptor, pgIntDescriptor, and pgFloatDescriptor.
Use valueRendererFor alongside the existing
pgTextDescriptor/pgInt4Descriptor-style tests to verify they produce the same
primitive literal rendering as their underlying codecs, ensuring the alias
exports are exercised directly.
🤖 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-mongo-target/2-mongo-adapter/src/core/codecs.ts`:
- Around line 88-102: renderPrimitiveLiteral currently only escapes backslashes
and apostrophes, so string values containing line terminators can generate
invalid TS in the .d.ts output. Update renderPrimitiveLiteral to also escape raw
newline, carriage return, and Unicode line/paragraph separators before wrapping
the value in single quotes, while keeping the existing handling for numbers and
booleans unchanged.

In `@packages/3-targets/3-targets/postgres/src/core/codecs.ts`:
- Around line 88-105: The renderPrimitiveLiteral helper in codecs.ts is missing
control-character escaping for string literals, so update its string handling to
escape newline, carriage return, and Unicode line-separator characters in
addition to backslashes and apostrophes. Keep the fix localized to
renderPrimitiveLiteral so any callers that rely on its TS-literal output get
valid generated source.

---

Outside diff comments:
In `@packages/3-targets/3-targets/postgres/src/core/codecs.ts`:
- Around line 1001-1016: PgCharDescriptor is incorrectly using the identity
literal renderer, which assumes values round-trip unchanged; update
PgCharDescriptor.renderValueType to follow the same normalization path used by
SqlCharCodec.decode() for CHAR values, since trailing-space padding is trimmed
and pg/char@1 is not identity-preserving. Use the PgCharDescriptor and
SqlCharCodec symbols to locate the fix, and ensure the rendered value type
accounts for padded CHAR semantics instead of delegating to
renderPrimitiveLiteral directly.

---

Nitpick comments:
In `@packages/1-framework/3-tooling/emitter/test/domain-type-generation.test.ts`:
- Around line 41-54: The test-only literal codec lookup mock is duplicated
across multiple test suites, and two copies build string literals without
escaping embedded quotes. Extract a shared test helper based on
literalCodecLookup()/renderValueTypeFor() and use serializeValue for string
values so emitter.integration.test.ts and
generate-contract-dts.spi-branches.test.ts stay consistent with this safer
implementation.

In
`@packages/1-framework/3-tooling/emitter/test/enum-typing-codec-seam.guard.test.ts`:
- Around line 79-83: The DOMAIN_ENUM_READ guard regex is too narrow because it
misses plain dot-property access after `.enum`, so update the matcher in the
enum-typing-codec-seam test to also recognize reads like `ns.enum.Priority`.
Keep the existing intent of matching only real access syntax in the test, but
widen the pattern in DOMAIN_ENUM_READ so it covers dot-name property reads in
addition to the current paren/bracket forms.

In `@packages/2-mongo-family/3-tooling/emitter/src/index.ts`:
- Around line 262-278: The interim logic in resolveFieldValueSet is correct, but
the return type is being redeclared inline instead of reusing the shared
ResolvedFieldValueSet type. Update this emitter’s resolveFieldValueSet signature
to import and reference ResolvedFieldValueSet from domain-type-generation.ts,
and apply the same cleanup in the matching resolveFieldValueSet implementation
in the SQL family emitter so both stay aligned with the single exported contract
type.

In `@packages/2-sql/3-tooling/emitter/src/index.ts`:
- Around line 318-357: `resolveFieldValueSet` duplicates the same
`entityAt<StorageValueSet>(...)` lookup already performed in
`computeColumnType`; extract that shared logic into a small
`resolveColumnValueSet(storage, column)` helper and have both methods call it.
While touching `resolveFieldValueSet`, replace the inline return shape with the
shared `ResolvedFieldValueSet` type imported from
`@prisma-next/emitter/domain-type-generation` so the emitter uses the canonical
type instead of redefining it.

In `@packages/3-targets/3-targets/postgres/src/core/codecs.ts`:
- Around line 88-105: The `renderPrimitiveLiteral` implementation in `codecs.ts`
is duplicated across the Postgres and Mongo target packages, so update the
shared literal-rendering logic by extracting it into a common utility (for
example under `framework-components/codec`) and have both target `codecs.ts`
files import it. Make sure any callers of `renderPrimitiveLiteral` and the
related `renderValueType` paths keep using the shared helper so escaping and
literal formatting stay consistent across families and test helpers.

In `@packages/3-targets/3-targets/postgres/test/codec-render-value-type.test.ts`:
- Around line 1-94: Add test coverage in codec-render-value-type.test.ts for the
alias-based descriptors that also implement renderValueType: pgCharDescriptor,
pgVarcharDescriptor, pgIntDescriptor, and pgFloatDescriptor. Use
valueRendererFor alongside the existing pgTextDescriptor/pgInt4Descriptor-style
tests to verify they produce the same primitive literal rendering as their
underlying codecs, ensuring the alias exports are exercised directly.
🪄 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: 29642cbd-cce6-407a-baa5-d472d72cf35b

📥 Commits

Reviewing files that changed from the base of the PR and between d987376 and 1d473db.

⛔ Files ignored due to path filters (2)
  • projects/refactor-enum-typing-via-codec/plan.md is excluded by !projects/**
  • projects/refactor-enum-typing-via-codec/spec.md is excluded by !projects/**
📒 Files selected for processing (20)
  • examples/prisma-next-demo/test/demo-dx.types.test.ts
  • packages/1-framework/1-core/framework-components/src/control/control-stack.ts
  • packages/1-framework/1-core/framework-components/src/control/emission-types.ts
  • packages/1-framework/1-core/framework-components/src/shared/codec-descriptor.ts
  • packages/1-framework/1-core/framework-components/src/shared/codec-types.ts
  • packages/1-framework/1-core/framework-components/test/control-stack.test.ts
  • packages/1-framework/3-tooling/emitter/src/domain-type-generation.ts
  • packages/1-framework/3-tooling/emitter/src/generate-contract-dts.ts
  • packages/1-framework/3-tooling/emitter/test/domain-type-generation.test.ts
  • packages/1-framework/3-tooling/emitter/test/emitter.integration.test.ts
  • packages/1-framework/3-tooling/emitter/test/enum-typing-codec-seam.guard.test.ts
  • packages/1-framework/3-tooling/emitter/test/generate-contract-dts.spi-branches.test.ts
  • packages/2-mongo-family/3-tooling/emitter/src/index.ts
  • packages/2-sql/3-tooling/emitter/src/index.ts
  • packages/2-sql/3-tooling/emitter/test/emitter-hook.storage-column-types.test.ts
  • packages/2-sql/3-tooling/emitter/test/value-set-codec-lookups.ts
  • packages/3-extensions/mongo/test/mongo.enum.e2e.test.ts
  • packages/3-mongo-target/2-mongo-adapter/src/core/codecs.ts
  • packages/3-targets/3-targets/postgres/src/core/codecs.ts
  • packages/3-targets/3-targets/postgres/test/codec-render-value-type.test.ts

Comment thread packages/3-mongo-target/2-mongo-adapter/src/core/codecs.ts Outdated
Comment thread packages/3-targets/3-targets/postgres/src/core/codecs.ts Outdated
…b; fix literal escaping

Review feedback on PR #896 — naming/doc only, byte-identical (fixtures:check
clean), plus one real escaping fix.

- Rename the codec SPI for what it does: renderValueType -> renderValueLiteral
  and renderValueTypeFor -> renderValueLiteralFor (it renders a stored value as
  a TS literal). Rewrite the descriptor doc to lead with the plain statement.
- Rename the opaque `channel` param to `side` (matching the existing
  convention) and document it: output = the read/SELECT type, input = the
  create/update type. A non-identity codec can render a stored value to a
  different literal per side.
- De-enum the value-set vocabulary: a value set is a generic set of a
  field/column permitted values, independent of enums (an enum is one
  producer). Purge "enum" from the value-set docs, and strip the downstream
  SQL reference from framework-emitter code (it must not name a consumer).
- Escape newlines and line/paragraph separators in renderPrimitiveLiteral
  (both the Postgres and Mongo copies) so a value containing a line terminator
  cannot emit invalid TypeScript in the generated .d.ts; add a test.

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

@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.

🧹 Nitpick comments (1)
packages/3-targets/3-targets/postgres/src/core/codecs.ts (1)

95-113: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: collapse repeated override boilerplate.

All 12 renderValueLiteral overrides are identical 3-line delegations to renderPrimitiveLiteral. Since its signature is structurally compatible with the required (value, side) shape, these can be a single-line class field instead.

♻️ Example collapse (repeat per descriptor)
-  override renderValueLiteral(value: JsonValue): string | undefined {
-    return renderPrimitiveLiteral(value);
-  }
+  override renderValueLiteral = renderPrimitiveLiteral;

Also applies to: 178-180, 261-263, 303-305, 345-347, 387-389, 429-431, 471-473, 1019-1021, 1043-1045, 1069-1071, 1090-1092

🤖 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/codecs.ts` around lines 95 -
113, Collapse the repeated renderValueLiteral override boilerplate by replacing
the identical multi-line delegations with single-line class field assignments
where the descriptor classes call renderPrimitiveLiteral directly. Use the
existing renderPrimitiveLiteral helper and update each renderValueLiteral
override mentioned in the diff (for example in the descriptor classes around
those symbols) to preserve the same behavior with less duplication.
🤖 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.

Nitpick comments:
In `@packages/3-targets/3-targets/postgres/src/core/codecs.ts`:
- Around line 95-113: Collapse the repeated renderValueLiteral override
boilerplate by replacing the identical multi-line delegations with single-line
class field assignments where the descriptor classes call renderPrimitiveLiteral
directly. Use the existing renderPrimitiveLiteral helper and update each
renderValueLiteral override mentioned in the diff (for example in the descriptor
classes around those symbols) to preserve the same behavior with less
duplication.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: dd7c50e3-98b5-4f85-8d01-8f8db7aa859a

📥 Commits

Reviewing files that changed from the base of the PR and between 1d473db and 037e7c2.

⛔ Files ignored due to path filters (1)
  • projects/refactor-enum-typing-via-codec/spec.md is excluded by !projects/**
📒 Files selected for processing (17)
  • packages/1-framework/1-core/framework-components/src/control/control-stack.ts
  • packages/1-framework/1-core/framework-components/src/control/emission-types.ts
  • packages/1-framework/1-core/framework-components/src/shared/codec-descriptor.ts
  • packages/1-framework/1-core/framework-components/src/shared/codec-types.ts
  • packages/1-framework/1-core/framework-components/test/control-stack.test.ts
  • packages/1-framework/3-tooling/emitter/src/domain-type-generation.ts
  • packages/1-framework/3-tooling/emitter/test/domain-type-generation.test.ts
  • packages/1-framework/3-tooling/emitter/test/emitter.integration.test.ts
  • packages/1-framework/3-tooling/emitter/test/enum-typing-codec-seam.guard.test.ts
  • packages/1-framework/3-tooling/emitter/test/generate-contract-dts.spi-branches.test.ts
  • packages/2-mongo-family/3-tooling/emitter/src/index.ts
  • packages/2-sql/3-tooling/emitter/test/emitter-hook.storage-column-types.test.ts
  • packages/2-sql/3-tooling/emitter/test/value-set-codec-lookups.ts
  • packages/3-extensions/mongo/test/mongo.enum.e2e.test.ts
  • packages/3-mongo-target/2-mongo-adapter/src/core/codecs.ts
  • packages/3-targets/3-targets/postgres/src/core/codecs.ts
  • packages/3-targets/3-targets/postgres/test/codec-render-value-literal.test.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/3-targets/3-targets/postgres/test/codec-render-value-literal.test.ts
🚧 Files skipped from review as they are similar to previous changes (10)
  • packages/2-sql/3-tooling/emitter/test/value-set-codec-lookups.ts
  • packages/3-extensions/mongo/test/mongo.enum.e2e.test.ts
  • packages/1-framework/3-tooling/emitter/test/emitter.integration.test.ts
  • packages/1-framework/3-tooling/emitter/test/enum-typing-codec-seam.guard.test.ts
  • packages/2-mongo-family/3-tooling/emitter/src/index.ts
  • packages/1-framework/3-tooling/emitter/test/generate-contract-dts.spi-branches.test.ts
  • packages/1-framework/1-core/framework-components/src/control/emission-types.ts
  • packages/1-framework/3-tooling/emitter/src/domain-type-generation.ts
  • packages/2-sql/3-tooling/emitter/test/emitter-hook.storage-column-types.test.ts
  • packages/1-framework/3-tooling/emitter/test/domain-type-generation.test.ts

…um typing

Extension-author entry: a value-set-restricted (e.g. enum) field/column now
narrows its TS literal union through the codec, not the deleted domain-enum
override. A custom CodecDescriptorImpl typing such a column must implement
renderValueLiteral(value, side) or the column widens to the codec output type;
a hand-built CodecLookup driving generateContractDts must expose
renderValueLiteralFor (framework-built lookups already supply it). Validated by
execution: reverting packages/3-extensions/ makes the mongo enum e2e go red,
re-applying the entry reproduces the branch state, tests green 92/92.

User entry: incidental (changes: [] unchanged) — the only examples/ touch is a
type test asserting emit-vs-no-emit agreement; emitted contract byte-identical.

Unblocks the check:upgrade-coverage Lint gate on PR #896.

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

@wmadden wmadden 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.

Pre-emptively approving

Comment thread packages/1-framework/3-tooling/emitter/test/enum-typing-codec-seam.guard.test.ts Outdated
Comment thread packages/3-mongo-target/2-mongo-adapter/src/core/codecs.ts Outdated
Comment thread packages/3-targets/3-targets/postgres/src/core/codecs.ts Outdated
…are renderTsLiteral

- Delete the source-scanning guard test (enum-typing-codec-seam.guard): a
  grep-over-source vitest is not a real test; its A1/A2 properties hold by
  design and the behavioral tests. Soften the spec line that prescribed it.
- Use the ifDefined helper for the Mongo-adapter descriptor conditional
  property spreads (renderOutputType / renderValueLiteral).
- Extract the duplicated renderPrimitiveLiteral (Postgres + Mongo adapter) into
  a shared renderTsLiteral in @prisma-next/framework-components — it renders a
  TS literal (a framework concern both targets already depend on), not data.
  Both targets and the emitter test fixture import the one helper.

Refactor-only; fixtures:check clean, lint:deps/casts clean.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
@wmadden-electric wmadden-electric added this pull request to the merge queue Jul 1, 2026
Merged via the queue into main with commit 4d8ab61 Jul 1, 2026
21 checks passed
@wmadden-electric wmadden-electric deleted the worktree/wonderful-feistel-cb6888 branch July 1, 2026 14:57
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