TML-2953: type Mongo enum fields from a stored value set (like SQL)#900
Conversation
…TML-2953) Bring Mongo enum typing onto the storage value set, mirroring the shipped SQL work: add a value-set entity to Mongo storage (same shape as SQL), source the $jsonSchema validator and the emit typing from it instead of domain.enum, and delete the interim resolver. Uniformity + value-set-driven typing, not a storageHash fix (the validator is already materialized in storage). Field to value-set link stays by-name in the emitter; the explicit ref + storage-only query builder are deferred to TML-2961. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Mirror SQL StorageValueSet in the Mongo family: MongoValueSet
({ kind: 'valueSet', values }, codec-encoded, no codecId), a valueSet slot on
the namespace entries (contract.storage.namespaces[ns].entries.valueSet[Name]),
serializer/hydration via the entity-kind registry, and the arktype schema.
Round-trips and validates. Nothing reads it yet — authoring populates it next,
typing/validator source from it after.
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…-2953) Both Mongo authoring paths (TS builder + PSL interpreter) now emit the storage value set into entries.valueSet (values = codec-encoded member values), mirroring SQL build-contract, and thread it through the hashed storage body. The Mongo target serializer, which serialized only entries.collection, now carries valueSet on both the serialize and hydrate paths; widen the namespace entries input type so authoring places a value set without a cast. Example contracts (mongo-demo, retail-store) regenerate to carry the value set; the physical migration ops are unchanged (ops.json byte-identical) — the planner walks only entries.collection, so the value set is inherently non-physical (zero migration ops), verified by test. Typing/validator still read domain.enum here; re-sourced next. Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…t (TML-2953) Re-source the Mongo emit typing and the $jsonSchema validator from the storage value set instead of domain.enum, and delete the interim resolver. resolveFieldValueSet reads storage.namespaces[ns].entries.valueSet[name] (codecId from the field); derive-json-schema takes a value-set map and sources the enum keyword from it, so it structurally cannot read domain.enum. The interpreter threads the value sets to the deriver and still emits domain.enum for the db.enums runtime dictionary (the keep). Byte-identical: fixtures:check clean, zero example contract changes (the value set values equal what domain.enum produced), FieldOutputTypes and the validator unchanged. No Mongo emitter or validator path reads domain.enum; only db.enums does. Document the identity guarantee at the two value-set-build sites (enum member values are JsonValue, so the codec encodeJson is identity by construction). Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Emit/no-emit type-parity test (real emitted contract.d.ts): a Mongo enum field types as the value union on FieldOutputTypes, the ORM row, and typeof contract, matching SQL. A codec-through-path test with a non-identity mock lookup proves the emit typing flows through renderValueSetType/renderValueLiteralFor (codec output, not the encoded value), not a raw print. Record the 0.14->0.15 upgrade entries: user side incidental (contracts regenerate to carry the value set; emitted types + validator byte-identical; db.enums unchanged; no user action); extension-author entry for the deriveJsonSchema / derivePolymorphicJsonSchema signature change (domain-enum map -> value-set map). Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
|
Warning Review limit reached
Next review available in: 23 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (20)
📝 WalkthroughWalkthroughAdds Mongo ChangesMongo valueSet feature
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
size-limit report 📦
|
@prisma-next/extension-author-tools
@prisma-next/mongo-runtime
@prisma-next/family-mongo
@prisma-next/sql-runtime
@prisma-next/family-sql
@prisma-next/extension-arktype-json
@prisma-next/middleware-cache
@prisma-next/mongo
@prisma-next/extension-paradedb
@prisma-next/extension-pgvector
@prisma-next/extension-postgis
@prisma-next/postgres
@prisma-next/sql-orm-client
@prisma-next/sqlite
@prisma-next/extension-supabase
@prisma-next/target-mongo
@prisma-next/adapter-mongo
@prisma-next/driver-mongo
@prisma-next/contract
@prisma-next/utils
@prisma-next/config
@prisma-next/errors
@prisma-next/framework-components
@prisma-next/operations
@prisma-next/ts-render
@prisma-next/contract-authoring
@prisma-next/ids
@prisma-next/psl-parser
@prisma-next/psl-printer
@prisma-next/cli
@prisma-next/cli-telemetry
@prisma-next/config-loader
@prisma-next/emitter
@prisma-next/language-server
@prisma-next/migration-tools
prisma-next
@prisma-next/vite-plugin-contract-emit
@prisma-next/mongo-codec
@prisma-next/mongo-contract
@prisma-next/mongo-value
@prisma-next/mongo-contract-psl
@prisma-next/mongo-contract-ts
@prisma-next/mongo-emitter
@prisma-next/mongo-schema-ir
@prisma-next/mongo-query-ast
@prisma-next/mongo-orm
@prisma-next/mongo-query-builder
@prisma-next/mongo-lowering
@prisma-next/mongo-wire
@prisma-next/sql-contract
@prisma-next/sql-errors
@prisma-next/sql-operations
@prisma-next/sql-schema-ir
@prisma-next/sql-contract-psl
@prisma-next/sql-contract-ts
@prisma-next/sql-contract-emitter
@prisma-next/sql-lane-query-builder
@prisma-next/sql-relational-core
@prisma-next/sql-builder
@prisma-next/target-postgres
@prisma-next/target-sqlite
@prisma-next/adapter-postgres
@prisma-next/adapter-sqlite
@prisma-next/driver-postgres
@prisma-next/driver-sqlite
commit: |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/3-mongo-target/1-mongo-target/src/core/mongo-target-contract-serializer.ts (1)
25-31: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate inline type for
dbInput.entries.This intersection type duplicates
MongoTargetDatabaseInput['entries']frommongo-target-database.ts. If that type changes, this local copy can silently drift out of sync.♻️ Proposed fix to reuse the shared type
const dbInput: { id: string; - entries?: Readonly<Record<string, Readonly<Record<string, unknown>>>> & { - readonly collection?: Readonly<Record<string, MongoCollectionInput>>; - readonly valueSet?: Readonly<Record<string, MongoValueSetInput>>; - }; + entries?: MongoTargetDatabaseInput['entries']; } = { id: nsData.id };🤖 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-mongo-target/1-mongo-target/src/core/mongo-target-contract-serializer.ts` around lines 25 - 31, The inline type for dbInput.entries in mongo-target-contract-serializer should reuse the shared MongoTargetDatabaseInput['entries'] type instead of duplicating the same intersection locally. Update the dbInput declaration in mongoTargetContractSerializer to reference the existing type from mongo-target-database, and keep the surrounding MongoCollectionInput and MongoValueSetInput usage aligned with that shared contract so the serializer stays in sync automatically.
🤖 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-mongo-target/1-mongo-target/src/core/mongo-target-contract-serializer.ts`:
- Around line 25-31: The inline type for dbInput.entries in
mongo-target-contract-serializer should reuse the shared
MongoTargetDatabaseInput['entries'] type instead of duplicating the same
intersection locally. Update the dbInput declaration in
mongoTargetContractSerializer to reference the existing type from
mongo-target-database, and keep the surrounding MongoCollectionInput and
MongoValueSetInput usage aligned with that shared contract so the serializer
stays in sync automatically.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: CHILL
Plan: Pro
Run ID: 7bd36911-355d-414e-89a5-0bfccdcce0b8
⛔ Files ignored due to path filters (4)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yamlprojects/refactor-enum-typing-via-codec/mongo-value-set-design.mdis excluded by!projects/**projects/refactor-enum-typing-via-codec/mongo-value-set-plan.mdis excluded by!projects/**projects/refactor-enum-typing-via-codec/mongo-value-set-spec.mdis excluded by!projects/**
📒 Files selected for processing (42)
examples/mongo-demo/migrations/app/20260626T1605_add_user_role_enum/end-contract.d.tsexamples/mongo-demo/migrations/app/20260626T1605_add_user_role_enum/end-contract.jsonexamples/mongo-demo/migrations/app/20260626T1605_add_user_role_enum/migration.jsonexamples/mongo-demo/migrations/app/20260626T1916_add_posts_indexes/end-contract.d.tsexamples/mongo-demo/migrations/app/20260626T1916_add_posts_indexes/end-contract.jsonexamples/mongo-demo/migrations/app/20260626T1916_add_posts_indexes/migration.jsonexamples/mongo-demo/migrations/app/20260626T1916_add_posts_indexes/start-contract.d.tsexamples/mongo-demo/migrations/app/20260626T1916_add_posts_indexes/start-contract.jsonexamples/mongo-demo/src/contract.d.tsexamples/mongo-demo/src/contract.jsonexamples/mongo-demo/test/enum-value-set.types.test-d.tsexamples/retail-store/migrations/app/20260628T0931_add_product_status_order_type_enums/end-contract.d.tsexamples/retail-store/migrations/app/20260628T0931_add_product_status_order_type_enums/end-contract.jsonexamples/retail-store/migrations/app/20260628T0931_add_product_status_order_type_enums/migration.jsonexamples/retail-store/src/contract.d.tsexamples/retail-store/src/contract.jsonpackages/2-mongo-family/1-foundation/mongo-contract/src/contract-schema.tspackages/2-mongo-family/1-foundation/mongo-contract/src/entity-kinds.tspackages/2-mongo-family/1-foundation/mongo-contract/src/exports/entity-kinds.tspackages/2-mongo-family/1-foundation/mongo-contract/src/exports/index.tspackages/2-mongo-family/1-foundation/mongo-contract/src/ir/mongo-storage.tspackages/2-mongo-family/1-foundation/mongo-contract/src/ir/mongo-value-set.tspackages/2-mongo-family/1-foundation/mongo-contract/test/entity-kinds.test.tspackages/2-mongo-family/1-foundation/mongo-contract/test/ir/mongo-value-set.test.tspackages/2-mongo-family/1-foundation/mongo-contract/test/mongo-storage.test.tspackages/2-mongo-family/1-foundation/mongo-contract/test/value-set-schema.test.tspackages/2-mongo-family/2-authoring/contract-psl/src/derive-json-schema.tspackages/2-mongo-family/2-authoring/contract-psl/src/exports/index.tspackages/2-mongo-family/2-authoring/contract-psl/src/interpreter.tspackages/2-mongo-family/2-authoring/contract-psl/test/derive-json-schema.test.tspackages/2-mongo-family/2-authoring/contract-ts/src/contract-builder.tspackages/2-mongo-family/2-authoring/contract-ts/test/enum-type.authoring.test.tspackages/2-mongo-family/3-tooling/emitter/package.jsonpackages/2-mongo-family/3-tooling/emitter/src/index.tspackages/2-mongo-family/3-tooling/emitter/test/emitter-hook.resolve-field-value-set.test.tspackages/2-mongo-family/3-tooling/emitter/test/emitter-hook.value-set-codec-path.test.tspackages/3-extensions/mongo/test/mongo.enum.e2e.test.tspackages/3-mongo-target/1-mongo-target/src/core/mongo-target-contract-serializer.tspackages/3-mongo-target/1-mongo-target/src/core/mongo-target-database.tspackages/3-mongo-target/1-mongo-target/test/mongo-planner.test.tsskills/extension-author/prisma-next-extension-upgrade/upgrades/0.14-to-0.15/instructions.mdskills/upgrade/prisma-next-upgrade/upgrades/0.14-to-0.15/instructions.md
wmadden
left a comment
There was a problem hiding this comment.
Pre-emptively approved. Address the comments then merge
bd5b6fd to
e2e254f
Compare
Pull request was closed
…et pack (TML-2953) The value set must hold codec-encoded values (spec R1). The Mongo TS builder was storing raw member values because it had no codec. Fix: the Mongo target owns its codec descriptors (mongo-target descriptor-meta carries types.codecTypes.codecDescriptors, imported from adapter-mongo/codecs), and both authoring paths resolve the codec by id from definition.target via extractCodecLookup and encode the value set + domain enum member values through codec.encodeJson. The builder receives the pack — it does not import the adapter, so no layering inversion. A codec not in the contract pack stack is a structured, user-facing error (PN-CON-4016), thrown identically by the TS builder (encodeEnumValue) and the PSL interpreter — never a raw fallback. Runtime encode/decode is unaffected: it resolves through the adapter codecs() registry, so the target pack is the sole contributor of codec descriptors to the composed control stack (adapter descriptor registration removed; the duplicate-owner guard stays plain). Also: the planner test builds the value set through real IR factories; a direct serializer round-trip test for entries.valueSet. Byte-identical for shipped identity codecs (fixtures:check zero diff). A non-identity test codec proves real encoding. retail-store client build green (no bson leak). Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
e2e254f to
0d187b4
Compare
At a glance
A Mongo model with an enum field:
Reading
roleback gives you the union of its allowed values — notstring:That already worked. What this PR changes is where that union comes from. Previously Mongo read the allowed values straight off the domain
enum. Now they live once, in the storage block, as a named value set — and the field's type and the collection's validator are both built from it:The decision
A field's type is determined by its value set — the union of the codec-rendered literals of its allowed values — not the plain codec type. That is the primitive SQL already uses (shipped in #896); this PR brings Mongo onto it. The domain
enumstops being a typing or enforcement input and becomes runtime-only: it still powersdb.enums, and nothing else.Why this is worth doing, beyond removing a SQL/Mongo divergence: a "value set" is a generic restriction — "the permitted values here are
x, y, z". A domain enum is one thing that produces one; a check constraint, a range, or a native database enum could produce one too, and each would be typed through the identical path with no enum-specific code. This closes the last place Mongo typed a field by reaching for the enum directly.What it does
{ kind: 'valueSet', values }, codec-encoded, atstorage.namespaces[ns].entries.valueSet[Name]).$jsonSchemaenumkeyword is built from it. The code that readdomain.enumfor these is deleted;db.enumsis the only reader ofdomain.enumleft.Why it's safe
fixtures:checkis clean across it.db verifyand migration behaviour are unchanged (pinned by a test).storageHashcovers, so an enum change was already tracked.How it's verified
contract.d.ts(both the ORM row andFieldOutputTypes) and ontypeof contract, matching SQL's shape — a typecheck-enforced test.0|1|2, reads back'low'|'high'|'urgent') proves the type is produced through the codec, not by printing the stored value.fixtures:check, upgrade-coverage, and the Mongo suites are green; no new casts.Upgrade (0.14 → 0.15)
db.enumsis unchanged.deriveJsonSchema/derivePolymorphicJsonSchema(public@prisma-next/mongo-contract-pslexports) now take a value-set map instead of a domain-enum map.Alternatives considered
FieldOutputTypes, and the feature that would drive storage-only typing — native Postgres enums — is Postgres-only and never touches Mongo. This PR links a field to its value set by name in the emitter instead, which is enough for every consumer that needs it today.typeof contractpath to read the value set too. Unnecessary. That path is handed the authored literals directly and is already the value union — the same posture SQL keeps.Follow-up: TML-2961 — the explicit Mongo document-structure projection.
🤖 Generated with Claude Code
Summary by CodeRabbit
codecsentry point.