Skip to content

TML-2884: Add domain enums to Mongo — author, $jsonSchema enforce, typed read#834

Merged
wmadden merged 45 commits into
mainfrom
tml-2884-slice-mongo-enums-end-to-end-vertical-jsonschema-enforcement
Jun 28, 2026
Merged

TML-2884: Add domain enums to Mongo — author, $jsonSchema enforce, typed read#834
wmadden merged 45 commits into
mainfrom
tml-2884-slice-mongo-enums-end-to-end-vertical-jsonschema-enforcement

Conversation

@wmadden-electric

@wmadden-electric wmadden-electric commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

At a glance

enum Role {
  @@type("mongo/string@1")
  User  = "user"
  Admin = "admin"
}

model Account {
  id   String @id @map("_id")
  role Role
}
await db.orm.accounts.create({ role: 'nope', tags: [] })   // rejected by the $jsonSchema validator
const a = await db.orm.accounts.where({ _id }).first()
a.role                                                      // 'user' | 'admin'  (not string)
db.enums.Role.values                                        // ['user', 'admin']  (ordered, literal-typed)

Decision

Mongo gets domain enums end-to-end — declare them, have the database reject out-of-set writes, read them as the value union, introspect them at runtime via db.enums — as a single vertical. An enum is a domain-plane entity (an ordered map from a member name to a member value); it isn't a target-specific feature. This PR wires the Mongo family onto the framework-level machinery the SQL track already proved.

The only Mongo-shaped piece is how the restriction is realized. SQL stores a named value-set plus a CHECK constraint; Mongo stores nothing — the restriction lives as an enum keyword inside the collection's $jsonSchema validator at validationLevel: 'strict'. No storage entity, no migration-ops parallel — the validator the Mongo family already applies just gains a per-field enum keyword. Mongo has no native enum and no prior PSL enum, so there is no cutover and nothing to retire.

Refs TML-2884 — the Mongo track (R10) of Enums as a domain concept. With the SQL cutover (TML-2853) already merged, this closes Phase 1: one enum concept on SQL and Mongo.

How it fits together

Author. The TS DSL gains a Mongo-bound enumType / member API — bindEnumType<MongoCodecTypes>() mirrors the Postgres binding. The Mongo builder accumulates declared enums into domain.namespaces[__unbound__].enum[Name] (codecId + ordered { name, value } members) and stamps the referencing field's valueSet ref onto the domain field. PSL enum lowers to the same shape via a new mongoFamilyEnumEntityDescriptor (parallel to the SQL family's) and a processEnumDeclarations path in the Mongo PSL interpreter. The Mongo contract schema gains enum? on the domain namespace and valueSet? on RawFieldSchema — both required so the built envelope passes arktype validation.

Enforce. The Mongo JSON-Schema deriver injects $jsonSchema.properties.<field>.enum = [...member values] when a field carries an enum valueSet. With validationLevel: 'strict' (already hardcoded), MongoDB rejects out-of-set writes. Two field-shape corners need care:

  • Array enum fields put enum inside items, not on the outer schema — MongoDB applies a top-level enum to the array value, so it would reject every write.
  • Nullable scalar enum fields include null in the emitted enum array. bsonType and enum are conjunctive in $jsonSchema; a nullable enum without null in enum couldn't store null despite nullable: true.

Read. Mongo's InferFieldBaseType gains a valueSet branch that narrows enum-restricted fields to their literal value union on the no-emit (typeof contract) path. The framework emitter's family-agnostic resolveEnumValues covers the emit path — so consumers of the emitted contract.d.ts see the union too, not just typeof-path consumers. The MongoClient facade gains readonly enums: UnboundEnums<TContract>buildNamespacedEnums(contract.domain) wrapped in the unboundNamespace() projection that sqlite already uses. Mongo and sqlite each have one namespace (__unbound__), so the projection drops the key and consumers see db.enums.Role directly.

Behavior changes & evidence

  • Authordomain.namespaces[__unbound__].enum[Name] populated; field carries the valueSet ref; literal tuples preserved through the handle. Round-trip + literal-tuple tests in enum-type.authoring.test.ts, interpreter.enum.test.ts, enum-type.test-d.ts.
  • Enforce — 8 unit tests in derive-json-schema.test.ts pin the emitted shape per case (scalar / nullable / array / nullable-array), declaration order, and non-enum unchanged.
  • Read — 5 type tests in contract-types.test-d.ts pin the narrowing; 4 type tests + 7 runtime tests on the facade pin db.enums.Role (no namespace key) and the accessor surface.
  • End-to-end against MongoDBmongo.enum.e2e.test.ts drives the loop via MongoMemoryReplSet: out-of-set rejected, in-set round-trips, null into a nullable enum succeeds, array elements validated (in / empty / out-of-set), db.enums.Status.ordinalOf returns declaration order (using an enum whose declaration order differs from lexical, to prove the difference).
  • Through-emit narrowing — the same test file drives the real generateContractDts pipeline and asserts the emitted FieldOutputTypes carries the literal union for scalar / nullable / array enum fields, plus that the domain enum block is present in the emitted namespace type (the mechanism the narrowing rides on — non-vacuity).

Testing performed

  • pnpm build (66/66), pnpm typecheck (138/138), pnpm fixtures:check zero-diff, pnpm lint:casts delta=0.
  • Scoped tests: @prisma-next/mongo 89/89, @prisma-next/mongo-contract 121/121, @prisma-next/mongo-contract-psl 152/152, @prisma-next/family-mongo 149/149.

Skill update

n/a — no public CLI surface or error-code changes. Authoring expands additively: new enumType import + field.namedType(handle) in the TS DSL, and a new PSL enum block keyword on Mongo. The project's user-facing changelog is captured when TML-2853 is published.

Follow-ups

  • Channel-parameterized FieldInputTypes (subsumes R2 F13). MongoContractResult precomputes FieldOutputTypes (D8) but not FieldInputTypes, so the F07 ORM write boundary in the e2e test still casts the accounts collection to a permissive AccountInput shape. R2 F13 flags this cast as the visible cost; R2 A08 names the structural fix (refactor Mongo's BuilderFieldOutputType / FieldOutputTypesFromDefinition to BuilderFieldChannelType<..., Channel> / FieldChannelTypesFromDefinition<Definition, Channel> to match SQL's FieldChannelTypes family, then instantiate twice). Landing A08 simultaneously closes F13 — they are the same gap viewed from the type-builder altitude vs the test-evidence altitude.
  • Combined nullable+array enum corner. The MMS test exercises scalar / nullable scalar / array; nullable+array is skipped because the example model's tags is non-nullable. The deriver behavior for the corner is verified in unit tests. The type-level corner is now exercised by F11's test in contract-builder.types.test-d.ts.
  • enumType shared home. Duplicated between sql-contract-ts and mongo-contract-ts. Lifted to @prisma-next/contract-authoring in D7 (R1 A01 cleared).
  • unboundNamespace duplication. The two-line projection helper is duplicated between sqlite and mongo (same architectural premise, two call sites). Lift when a third unbound-namespace target arrives.
  • Compile-import emit-then-consume. The emit tests use expect(dts).toContain(...) against the real generateContractDts output, matching every other emit test in the codebase. A future hardening pass could write the emitted .d.ts to a temp file and import it for expectTypeOf — a project-wide convention change.

Alternatives considered

  • Wait for a SQL-cutover-style overhaul on Mongo. Rejected: there's nothing to overhaul. Mongo has no native enum and no prior PSL enum, so the domain concept lands directly — author, enforce, and read together in one vertical.
  • Realize the restriction as a separate Mongo entity (a MongoEnumType, parallel to the deleted Postgres native enum). Rejected: a Mongo collection has no schema object the way a SQL schema has named types. The validator is the schema object the family already manages; an enum keyword in $jsonSchema is the realization that already fits.
  • Share enumType source between the two families. Rejected for this slice: architecture.config.json forbids mongo→sql imports, and the file is small. Lifting to a framework-shared location is the right long-term move; out of scope here.
  • Mirror Postgres for the db.enums facade. Initially attempted — exposed db.enums.__unbound__.Role, breaking the spec's specified db.enums.Role surface. Corrected to mirror sqlite (the other unbound-namespace target) via the UnboundEnums projection.
  • Compile-import emit-then-consume test instead of substring assertions. Considered. Rejected as the slice's acceptance test: every other generateContractDts test in the codebase uses substring assertions on the emitter output, and switching one slice to compile-import would diverge from convention without changing what is being verified. Filed as a project-wide hardening follow-up.

Checklist

  • All commits are signed off (git commit -s) per the DCO.
  • I read CONTRIBUTING.md and the change is scoped to one logical concern.
  • Tests are updated.
  • The PR title is in TML-NNNN: <sentence-case title> form.
  • The Skill update section above is filled in.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added end-to-end enum authoring support (TypeScript DSL helpers, enumType/member APIs, and PSL enum blocks) with contract-level enum declarations.
    • MongoDB JSON schema generation now includes enum constraints for scalar, nullable, and array enum fields.
    • Runtime clients now expose an enums facade with enum values, has, nameOf, and ordinalOf, along with enum metadata.
    • SQLite runtime enum handling now uses unbound enum projections for consistent typing.
  • Tests

    • Expanded type-level and integration/e2e coverage for enum lowering, schema enforcement, and runtime behavior.

@wmadden-electric wmadden-electric requested a review from a team as a code owner June 15, 2026 16:23
@coderabbitai

coderabbitai Bot commented Jun 15, 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
📝 Walkthrough

Walkthrough

This PR adds shared enum authoring primitives, lowers enums through Mongo contract building and PSL interpretation, and exposes unbound enum accessors at runtime for Mongo and SQLite.

Changes

Enum authoring pipeline

Layer / File(s) Summary
Shared enum primitives and public re-exports
packages/1-framework/2-authoring/contract/src/enum-type.ts, packages/1-framework/2-authoring/contract/src/index.ts, packages/1-framework/2-authoring/contract/package.json, packages/2-sql/2-authoring/contract-ts/src/enum-type.ts, packages/2-mongo-family/2-authoring/contract-ts/src/enum-type.ts, packages/2-mongo-family/2-authoring/contract-ts/src/exports/contract-builder.ts, packages/3-extensions/mongo/src/exports/contract-builder.ts
enumType, member, EnumTypeHandle, codec typing helpers, and related type/value re-exports are added or forwarded through the framework and contract authoring surfaces.
Unbound enum accessors and typed authoring instantiation
packages/1-framework/0-foundation/contract/src/enum-accessor.ts, packages/1-framework/0-foundation/contract/src/exports/enum-accessor.ts, packages/1-framework/1-core/framework-components/src/shared/framework-authoring.ts, packages/2-sql/2-authoring/contract-psl/src/interpreter.ts
buildUnboundEnums and UnboundEnumsOf are added, the accessor typing logic is updated to select between namespace-derived and built enum accessors, and instantiateAuthoringEntityType becomes generic over its return type.
Mongo contract schema and type inference
packages/2-mongo-family/1-foundation/mongo-contract/src/contract-schema.ts, packages/2-mongo-family/1-foundation/mongo-contract/src/contract-types.ts, packages/2-mongo-family/1-foundation/mongo-contract/test/contract-types.test-d.ts, packages/2-mongo-family/2-authoring/contract-psl/src/derive-json-schema.ts, packages/2-mongo-family/2-authoring/contract-psl/src/exports/index.ts, packages/2-mongo-family/2-authoring/contract-psl/package.json, packages/2-mongo-family/2-authoring/contract-psl/test/derive-json-schema.test.ts
Mongo contract schema validation accepts enum references and enum maps, field/model type inference resolves enum-backed value unions with precomputed row support, and JSON-schema derivation threads enum metadata through to generated validators and tests.
Mongo family enum descriptor and pack wiring
packages/2-mongo-family/9-family/src/core/authoring-entity-types.ts, packages/2-mongo-family/9-family/src/core/control-descriptor.ts, packages/2-mongo-family/9-family/src/exports/pack.ts, packages/2-mongo-family/9-family/test/control.test.ts
An enum authoring entity descriptor is added, the Mongo family descriptor exposes authoring metadata, and the family pack exports the enum authoring descriptors.
Mongo PSL enum lowering and schema derivation
packages/2-mongo-family/2-authoring/contract-psl/src/interpreter.ts, packages/2-mongo-family/2-authoring/contract-psl/src/provider.ts, packages/2-mongo-family/2-authoring/contract-psl/test/derive-json-schema.test.ts
The PSL interpreter now builds enums from top-level enum blocks, threads enum codec ids into field resolution, passes built enums into JSON-schema derivation, and updates provider wiring plus schema tests.
Mongo contract builder and enum DSL
packages/2-mongo-family/2-authoring/contract-ts/src/contract-builder.ts, packages/2-mongo-family/2-authoring/contract-ts/src/enum-type.ts, packages/2-mongo-family/2-authoring/contract-ts/src/exports/contract-builder.ts, packages/3-extensions/mongo/src/contract/enum-type.ts, packages/2-mongo-family/2-authoring/contract-ts/test/*, packages/2-mongo-family/2-authoring/contract-ts/test/*.test-d.ts, packages/3-extensions/mongo/package.json
FieldBuilder gains enum handles and namedType, contracts can declare enums, built contracts emit valueSet references and unbound namespace enum data, and the Mongo-scoped enum factory is re-exported for authoring.
Mongo runtime enum facade and SQLite unbound enums
packages/3-extensions/mongo/src/runtime/mongo.ts, packages/3-extensions/sqlite/src/runtime/sqlite.ts, packages/3-extensions/mongo/src/exports/contract-builder.ts
MongoClient exposes enums from buildUnboundEnums, SQLite switches to the same helper and type alias, and runtime option composition uses ifDefined for mode.
Mongo enum runtime and type tests
packages/3-extensions/mongo/test/mongo.enum.e2e.test.ts, packages/3-extensions/mongo/test/mongo.test.ts, packages/3-extensions/mongo/test/mongo.types.test-d.ts, test/integration/test/mongo/interpreter.enum.test.ts
Integration, facade, and type-level tests cover enum lowering, db.enums, declaration-order accessors, schema enforcement, emitted TypeScript types, and CreateInput narrowing.

Sequence Diagram(s)

sequenceDiagram
  participant Author as Contract author
  participant Builder as buildContractFromDefinition
  participant Interp as interpretPslDocumentToMongoContract
  participant Client as mongo() / MongoClient

  Author->>Builder: defineContract({ enums, models })
  Builder->>Builder: validate enum names and build builtEnums
  Builder-->>Author: contract.domain.namespaces.__unbound__.enum

  Author->>Interp: PSL document + authoringContributions
  Interp->>Interp: processEnumDeclarations
  Interp->>Interp: resolve fields with enumCodecIds
  Interp->>Interp: deriveJsonSchema(builtEnums)
  Interp-->>Author: lowered Mongo contract

  Author->>Client: connect contract
  Client->>Client: buildUnboundEnums(contract.domain)
  Client-->>Author: db.enums.Role accessors
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • prisma/prisma-next#595: The unbound enum projection helper in this PR builds on the enum namespace layout change used by that earlier contract-domain enum update.
  • prisma/prisma-next#750: This PR extends the enum authoring and valueSet groundwork introduced there into Mongo contract building and runtime enum accessors.

Poem

🐇 Hop hop, the enums now bloom,
Through schema, runtime, and contract room.
member() twirls with a typed little grin,
And db.enums lets the rabbit win.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.26% 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 reflects the main Mongo enum support changes, including authoring, JSON schema enforcement, and typed reads.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tml-2884-slice-mongo-enums-end-to-end-vertical-jsonschema-enforcement

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 Jun 15, 2026

Copy link
Copy Markdown

Open in StackBlitz

@prisma-next/extension-author-tools

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

@prisma-next/mongo-runtime

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

@prisma-next/family-mongo

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

@prisma-next/sql-runtime

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

@prisma-next/family-sql

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

@prisma-next/extension-arktype-json

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

@prisma-next/middleware-cache

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

@prisma-next/mongo

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

@prisma-next/extension-paradedb

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

@prisma-next/extension-pgvector

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

@prisma-next/extension-postgis

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

@prisma-next/postgres

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

@prisma-next/sql-orm-client

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

@prisma-next/sqlite

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

@prisma-next/extension-supabase

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

@prisma-next/target-mongo

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

@prisma-next/adapter-mongo

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

@prisma-next/driver-mongo

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

@prisma-next/contract

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

@prisma-next/utils

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

@prisma-next/config

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

@prisma-next/errors

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

@prisma-next/framework-components

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

@prisma-next/operations

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

@prisma-next/ts-render

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

@prisma-next/contract-authoring

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

@prisma-next/ids

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

@prisma-next/psl-parser

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

@prisma-next/psl-printer

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

@prisma-next/cli

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

@prisma-next/cli-telemetry

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

@prisma-next/config-loader

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

@prisma-next/emitter

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

@prisma-next/language-server

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

@prisma-next/migration-tools

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

prisma-next

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

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

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

@prisma-next/mongo-codec

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

@prisma-next/mongo-contract

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

@prisma-next/mongo-value

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

@prisma-next/mongo-contract-psl

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

@prisma-next/mongo-contract-ts

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

@prisma-next/mongo-emitter

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

@prisma-next/mongo-schema-ir

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

@prisma-next/mongo-query-ast

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

@prisma-next/mongo-orm

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

@prisma-next/mongo-query-builder

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

@prisma-next/mongo-lowering

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

@prisma-next/mongo-wire

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

@prisma-next/sql-contract

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

@prisma-next/sql-errors

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

@prisma-next/sql-operations

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

@prisma-next/sql-schema-ir

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

@prisma-next/sql-contract-psl

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

@prisma-next/sql-contract-ts

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

@prisma-next/sql-contract-emitter

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

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

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

@prisma-next/sql-relational-core

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

@prisma-next/sql-builder

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

@prisma-next/target-postgres

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

@prisma-next/target-sqlite

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

@prisma-next/adapter-postgres

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

@prisma-next/adapter-sqlite

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

@prisma-next/driver-postgres

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

@prisma-next/driver-sqlite

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

commit: 2d3f06a

@github-actions

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown

size-limit report 📦

Path Size
postgres / no-emit 160.35 KB (+0.02% 🔺)
postgres / emit 147.55 KB (+0.04% 🔺)
mongo / no-emit 79.58 KB (+2.03% 🔺)
mongo / emit 72.41 KB (+0.44% 🔺)
cf-worker / no-emit 188.11 KB (+0.1% 🔺)
cf-worker / emit 173.57 KB (+0.01% 🔺)

@wmadden-electric wmadden-electric changed the title TML-2884: Mongo enums end-to-end — $jsonSchema enforcement + typed reads + db.enums TML-2884: Add domain enums to Mongo — author, $jsonSchema enforce, typed read Jun 15, 2026
Comment thread packages/2-mongo-family/1-foundation/mongo-contract/src/contract-types.ts Outdated
Comment thread packages/2-mongo-family/2-authoring/contract-psl/src/interpreter.ts Outdated
@wmadden-electric

Copy link
Copy Markdown
Contributor Author

Review-response round

Addressing your two review comments + the local code-review findings that surfaced when I ran /drive-code-review after the original push.

Your review comments

  • InferFieldType too complicated (contract-types.ts:251). Refactored Mongo to use a precomputed field types map, matching SQL's pre-existing FieldChannelTypes<Definition, Channel> pattern. Important honesty correction: my D4 work introduced InferFieldType against that existing pattern — Mongo was the outlier, not SQL. SQL never had this recursion path. Per your "tests first" directive: added a 32-field stress test before refactoring; it compiled cleanly under the old code too, so the stack-space concern is forward-looking (a 100+-field contract with deep value-object nesting would likely cross the limit) rather than currently firing. Refactor still lands for parity + per-call constant-time lookup. — commit 44792fce3
  • ifDefined (interpreter.ts:976). Replaced 8 inline ...(X !== undefined ? { X } : {}) patterns across mongo + SQL PSL interpreters, the mongo runtime, and framework-authoring. Skipped non-applicable matches (!= null semantics, Object.keys().length > 0 patterns). — commit b10ce427b

Other in-scope work since the original push

When I ran /drive-code-review it surfaced ~16 findings across architect + principal-engineer lenses. The 9 in-scope ones landed; the deferrals are listed in the PR body's Follow-ups. Highlights:

Test rigor + integration evidence (D6, 5 commits):

  • mongo.enum.e2e.test.ts now routes writes through deriveJsonSchema() instead of a literal $jsonSchema (so a deriver regression breaks the e2e test).
  • All four MMS rejection sites pin code: 121 (MongoServerError's DocumentFailedValidation).
  • Emit-then-consume tests gained not.toContain negatives for the widened-union false-green class.
  • PSL interpreter test now uses the production mongoFamilyEnumEntityDescriptor, not a stubbed factory.
  • ContractEnumSchema.members requires length ≥ 1.
  • Nullable+array asymmetry is asserted explicitly, not commented away.

Architectural cleanup (D7, 4 commits):

  • enum-type.ts lifted to a new @prisma-next/contract-authoring package — SQL and Mongo authoring re-export. Per-extension bindEnumType<XCodecTypes>() stays put.
  • UnboundEnumsOf<TContract> + buildUnboundEnums() lifted to @prisma-next/contract/enum-accessor — sqlite and Mongo both use the shared helper.
  • instantiateAuthoringEntityType is generic over TOutput; SQL + Mongo PSL interpreters drop their blindCast<EnumTypeHandle, ...>.
  • MMS test writes now go through db.orm.accounts.create(...) (validator rejection proved through the production ORM path, not the native driver). PR body's at-a-glance code updated to match.

Gate

pnpm build 66/66 · pnpm typecheck 138/138 · pnpm fixtures:check zero-diff · pnpm lint:casts delta=0 · scoped tests across the 5 mongo packages all green (671 tests).

Known follow-up not in this PR

The F07 ORM-write test still carries one blindCast because the precomputed map only covers FieldOutputTypes; the where predicate and create input use FieldInputTypes which still narrows enum fields to never. Extending the same precomputed pattern to FieldInputTypes would kill the cast and complete parity. Separate ticket.

The D5 honesty correction: my original framing of "consumers' apps will run out of stack space" overstated immediate urgency. The refactor is the right shape but motivated by parity-with-SQL and forward-looking scaling, not a currently-firing failure.

@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: 4

Caution

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

⚠️ Outside diff range comments (1)
packages/2-mongo-family/2-authoring/contract-ts/src/contract-builder.ts (1)

1894-1899: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Factory-returned enums are currently dropped during contract assembly.

In the factory path, buildBoundContract merges models, valueObjects, and roots, but not enums. Any enums produced by the factory are silently ignored before buildContractFromDefinition(...), so authored enum declarations can be lost at runtime.

💡 Proposed fix
 type ContractFactory<
   Models extends Record<string, AnyModelBuilder> = Record<never, never>,
   ValueObjects extends Record<string, AnyValueObjectBuilder> = Record<never, never>,
   Roots extends Record<string, ModelNameInput> | undefined = undefined,
@@
 > = (helpers: ContractAuthoringHelpers<Family, Target, ExtensionPacks>) => {
   readonly models?: Models;
   readonly valueObjects?: ValueObjects;
   readonly roots?: Roots;
+  readonly enums?: Record<string, EnumTypeHandle>;
 };
@@
   const Built extends {
     readonly models?: Record<string, AnyModelBuilder>;
     readonly valueObjects?: Record<string, AnyValueObjectBuilder>;
     readonly roots?: Record<string, ModelNameInput> | undefined;
+    readonly enums?: Record<string, EnumTypeHandle>;
   },
@@
     return buildContractFromDefinition({
       ...full,
       ...ifDefined('models', built.models),
       ...ifDefined('valueObjects', built.valueObjects),
       ...ifDefined('roots', built.roots),
+      ...ifDefined('enums', built.enums),
     });
🤖 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/2-authoring/contract-ts/src/contract-builder.ts`
around lines 1894 - 1899, The `buildBoundContract` function is not including
`enums` when merging the factory-produced contract properties. In the return
statement of `buildBoundContract`, add a spread operation for `built.enums`
using the same `ifDefined` pattern as the existing spreads for `models`,
`valueObjects`, and `roots`. This will ensure that enum definitions produced by
the factory are preserved and passed to `buildContractFromDefinition` instead of
being silently dropped.
🧹 Nitpick comments (1)
packages/2-mongo-family/1-foundation/mongo-contract/test/contract-types.test-d.ts (1)

16-18: ⚡ Quick win

Remove the decorative section banner comments in this test file.

These headings are redundant with the test names and can be dropped to keep the file aligned with the repo’s TS comment policy.

As per coding guidelines, Don't add comments if avoidable, prefer code that expresses its intent.

Also applies to: 411-416

🤖 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/1-foundation/mongo-contract/test/contract-types.test-d.ts`
around lines 16 - 18, Remove the decorative section banner comments from the
test file that serve only as visual separators and are redundant with the test
names and code structure they precede. Specifically, delete the multi-line
comment blocks (including the dashes) that introduce sections like the enum
value-union narrowing tests. Apply this removal to all such decorative section
headers throughout the file, including the locations mentioned in the review
comment, to align with the coding guideline that avoids unnecessary comments in
favor of code that expresses its own intent.

Source: Coding guidelines

🤖 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/1-framework/1-core/framework-components/src/shared/framework-authoring.ts`:
- Around line 939-943: Replace the bare `as` cast in the factory variable
assignment with `blindCast` to comply with the repository's cast policy. The
factory variable is currently being cast using the bare `as` operator to a
function type that accepts input and ctx parameters; instead, wrap the
descriptor.output.factory expression with a blindCast call (which is already
imported) to perform the type cast, keeping the same function type signature for
the cast target.

In `@packages/1-framework/2-authoring/contract/src/enum-type.ts`:
- Around line 31-40: The `member()` function uses the nullish coalescing
operator `??` which treats both `undefined` and `null` as missing values,
causing a type/runtime mismatch when `null` is explicitly passed. Fix this by
changing the value assignment logic to only use `name` as the default when
`value` is strictly `undefined` (parameter omitted), while allowing `null` to be
passed through unchanged. Replace `value ?? name` with a conditional check like
`value === undefined ? name : value` to distinguish between omitted parameters
and explicitly provided `null` values.

In `@packages/2-mongo-family/2-authoring/contract-ts/src/contract-builder.ts`:
- Line 151: The FieldBuilder interface needs to be made generic to preserve the
EnumTypeHandle's type parameters. Add a type parameter to FieldBuilder (likely
EnumHandle or similar) to carry the Handle's type information, make __enumHandle
non-optional and properly typed as EnumHandle, and ensure that
createFieldBuilder returns FieldBuilder with the correct handle type argument.
Additionally, update the return types of the .optional() and .many() methods on
FieldBuilder to preserve the handle's type parameters when chaining, so that
BuilderEnumValueUnion can correctly extract literal enum values from the generic
__enumHandle constraint.

In
`@packages/2-mongo-family/2-authoring/contract-ts/test/enum-type.authoring.test.ts`:
- Around line 127-143: The test for rejecting empty enum members is not isolated
because the malformed object is missing multiple required contract fields, so
the validation failure could be due to those missing fields rather than the
empty members array specifically. Create a valid baseline contract object that
passes MongoContractSchema validation, then create a second test object that is
identical except with enum.Role.members set to an empty array [], and verify
that only this second object fails validation. This ensures the test
specifically validates the enum-members rule rather than other validation
failures.

---

Outside diff comments:
In `@packages/2-mongo-family/2-authoring/contract-ts/src/contract-builder.ts`:
- Around line 1894-1899: The `buildBoundContract` function is not including
`enums` when merging the factory-produced contract properties. In the return
statement of `buildBoundContract`, add a spread operation for `built.enums`
using the same `ifDefined` pattern as the existing spreads for `models`,
`valueObjects`, and `roots`. This will ensure that enum definitions produced by
the factory are preserved and passed to `buildContractFromDefinition` instead of
being silently dropped.

---

Nitpick comments:
In
`@packages/2-mongo-family/1-foundation/mongo-contract/test/contract-types.test-d.ts`:
- Around line 16-18: Remove the decorative section banner comments from the test
file that serve only as visual separators and are redundant with the test names
and code structure they precede. Specifically, delete the multi-line comment
blocks (including the dashes) that introduce sections like the enum value-union
narrowing tests. Apply this removal to all such decorative section headers
throughout the file, including the locations mentioned in the review comment, to
align with the coding guideline that avoids unnecessary comments in favor of
code that expresses its own intent.
🪄 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: 02e7c6ea-b6bd-4dc7-bcd4-f2fbb945acd3

📥 Commits

Reviewing files that changed from the base of the PR and between f33ef16 and b10ce42.

⛔ Files ignored due to path filters (3)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
  • projects/enums-as-domain-concept/slices/mongo-enums-end-to-end/plan.md is excluded by !projects/**
  • projects/enums-as-domain-concept/slices/mongo-enums-end-to-end/spec.md is excluded by !projects/**
📒 Files selected for processing (36)
  • packages/1-framework/0-foundation/contract/src/enum-accessor.ts
  • packages/1-framework/0-foundation/contract/src/exports/enum-accessor.ts
  • packages/1-framework/1-core/framework-components/src/shared/framework-authoring.ts
  • packages/1-framework/2-authoring/contract/package.json
  • packages/1-framework/2-authoring/contract/src/enum-type.ts
  • packages/1-framework/2-authoring/contract/src/index.ts
  • packages/2-mongo-family/1-foundation/mongo-contract/src/contract-schema.ts
  • packages/2-mongo-family/1-foundation/mongo-contract/src/contract-types.ts
  • packages/2-mongo-family/1-foundation/mongo-contract/test/contract-types.test-d.ts
  • packages/2-mongo-family/2-authoring/contract-psl/package.json
  • packages/2-mongo-family/2-authoring/contract-psl/src/derive-json-schema.ts
  • packages/2-mongo-family/2-authoring/contract-psl/src/exports/index.ts
  • packages/2-mongo-family/2-authoring/contract-psl/src/interpreter.ts
  • packages/2-mongo-family/2-authoring/contract-psl/src/provider.ts
  • packages/2-mongo-family/2-authoring/contract-psl/test/derive-json-schema.test.ts
  • packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.enum.test.ts
  • packages/2-mongo-family/2-authoring/contract-ts/src/contract-builder.ts
  • packages/2-mongo-family/2-authoring/contract-ts/src/enum-type.ts
  • packages/2-mongo-family/2-authoring/contract-ts/src/exports/contract-builder.ts
  • packages/2-mongo-family/2-authoring/contract-ts/test/enum-type.authoring.test.ts
  • packages/2-mongo-family/2-authoring/contract-ts/test/enum-type.test-d.ts
  • packages/2-mongo-family/9-family/src/core/authoring-entity-types.ts
  • packages/2-mongo-family/9-family/src/core/control-descriptor.ts
  • packages/2-mongo-family/9-family/src/exports/pack.ts
  • packages/2-mongo-family/9-family/test/control.test.ts
  • packages/2-sql/2-authoring/contract-psl/src/interpreter.ts
  • packages/2-sql/2-authoring/contract-ts/src/enum-type.ts
  • packages/3-extensions/mongo/package.json
  • packages/3-extensions/mongo/src/contract/enum-type.ts
  • packages/3-extensions/mongo/src/exports/contract-builder.ts
  • packages/3-extensions/mongo/src/runtime/mongo.ts
  • packages/3-extensions/mongo/test/mongo.enum.e2e.test.ts
  • packages/3-extensions/mongo/test/mongo.test.ts
  • packages/3-extensions/mongo/test/mongo.types.test-d.ts
  • packages/3-extensions/postgres/package.json
  • packages/3-extensions/sqlite/src/runtime/sqlite.ts

Comment thread packages/1-framework/2-authoring/contract/src/enum-type.ts Outdated
Comment thread packages/2-mongo-family/2-authoring/contract-ts/src/contract-builder.ts Outdated

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

Caution

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

⚠️ Outside diff range comments (1)
packages/2-mongo-family/2-authoring/contract-ts/src/contract-builder.ts (1)

1360-1368: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject enum-typed fields that reference undeclared enums

Line 1361 stamps valueSet for any enum-handle field, but enum entities are only emitted from definition.enums. There is no validation that all referenced handles are declared, so contracts can contain dangling enum references.

Please validate referenced enum names against the declared enum map during build and throw a deterministic authoring error on missing declarations.

Also applies to: 1757-1788

🤖 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/2-authoring/contract-ts/src/contract-builder.ts`
around lines 1360 - 1368, The buildContractField function creates a valueSet
reference for enum-handle fields without validating that the referenced enum is
actually declared in the contract definition. Add validation in the condition
where builder.__enumHandle exists to check that the enum name
(builder.__enumHandle.enumName) is declared in the available enum map, and throw
a deterministic authoring error if the enum declaration is missing. Only create
and assign the valueSet object if the referenced enum is validated to exist.
♻️ Duplicate comments (1)
packages/2-mongo-family/2-authoring/contract-ts/src/contract-builder.ts (1)

142-154: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Preserve enum handle generics through FieldBuilder so namedType() keeps literal unions

namedType() passes a concrete handle, but createFieldBuilder returns a non-handle-generic FieldBuilder, so the handle’s Values tuple gets erased. BuilderEnumValueUnion then cannot reliably recover the enum value union, and type narrowing falls back to base codec output.

Suggested direction
-export interface FieldBuilder<
+export interface FieldBuilder<
   Type extends ContractFieldType = ContractFieldType,
   Nullable extends boolean = boolean,
   Many extends boolean = boolean,
+  EnumHandle extends EnumTypeHandle | undefined = undefined,
 > {
   readonly __kind: 'field';
   readonly __type: Type;
   readonly __nullable: Nullable;
   readonly __many: Many;
-  readonly __enumHandle?: EnumTypeHandle;
-  optional(): FieldBuilder<Type, true, Many>;
-  many(): FieldBuilder<Type, Nullable, true>;
+  readonly __enumHandle?: EnumHandle;
+  optional(): FieldBuilder<Type, true, Many, EnumHandle>;
+  many(): FieldBuilder<Type, Nullable, true, EnumHandle>;
 }
-function createFieldBuilder<
+function createFieldBuilder<
   Type extends ContractFieldType,
   Nullable extends boolean,
   Many extends boolean,
+  EnumHandle extends EnumTypeHandle | undefined = undefined,
 >(
   spec: FieldBuilderSpec<Type, Nullable, Many>,
-  enumHandle?: EnumTypeHandle,
-): FieldBuilder<Type, Nullable, Many> {
+  enumHandle?: EnumHandle,
+): FieldBuilder<Type, Nullable, Many, EnumHandle> {

Also applies to: 616-623, 838-845, 943-957

🤖 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/2-authoring/contract-ts/src/contract-builder.ts`
around lines 142 - 154, The FieldBuilder interface does not preserve the enum
handle as a generic parameter, causing the handle's Values tuple to be erased
when createFieldBuilder returns a non-handle-generic FieldBuilder. This prevents
BuilderEnumValueUnion from recovering the enum value union reliably. Add a new
generic parameter to the FieldBuilder interface (e.g., EnumHandle) to preserve
the specific enum handle type, update the optional() and many() methods to
propagate this generic parameter through their return types, and ensure that
when namedType() passes a concrete handle, the handle information is retained
through the builder chain. Apply the same generic preservation pattern at the
other affected sites: lines 616-623, 838-845, and 943-957, ensuring consistency
across all FieldBuilder usage.
🤖 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.

Outside diff comments:
In `@packages/2-mongo-family/2-authoring/contract-ts/src/contract-builder.ts`:
- Around line 1360-1368: The buildContractField function creates a valueSet
reference for enum-handle fields without validating that the referenced enum is
actually declared in the contract definition. Add validation in the condition
where builder.__enumHandle exists to check that the enum name
(builder.__enumHandle.enumName) is declared in the available enum map, and throw
a deterministic authoring error if the enum declaration is missing. Only create
and assign the valueSet object if the referenced enum is validated to exist.

---

Duplicate comments:
In `@packages/2-mongo-family/2-authoring/contract-ts/src/contract-builder.ts`:
- Around line 142-154: The FieldBuilder interface does not preserve the enum
handle as a generic parameter, causing the handle's Values tuple to be erased
when createFieldBuilder returns a non-handle-generic FieldBuilder. This prevents
BuilderEnumValueUnion from recovering the enum value union reliably. Add a new
generic parameter to the FieldBuilder interface (e.g., EnumHandle) to preserve
the specific enum handle type, update the optional() and many() methods to
propagate this generic parameter through their return types, and ensure that
when namedType() passes a concrete handle, the handle information is retained
through the builder chain. Apply the same generic preservation pattern at the
other affected sites: lines 616-623, 838-845, and 943-957, ensuring consistency
across all FieldBuilder usage.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: cd6f6a55-4738-4289-814c-12e4ff2eb30b

📥 Commits

Reviewing files that changed from the base of the PR and between b10ce42 and 63cad48.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (9)
  • packages/1-framework/0-foundation/contract/src/enum-accessor.ts
  • packages/1-framework/2-authoring/contract/src/enum-type.ts
  • packages/2-mongo-family/2-authoring/contract-psl/package.json
  • packages/2-mongo-family/2-authoring/contract-ts/src/contract-builder.ts
  • packages/2-mongo-family/2-authoring/contract-ts/test/contract-builder.types.test-d.ts
  • packages/3-extensions/mongo/package.json
  • packages/3-extensions/mongo/test/mongo.enum.e2e.test.ts
  • packages/3-extensions/mongo/test/mongo.types.test-d.ts
  • test/integration/test/mongo/interpreter.enum.test.ts
💤 Files with no reviewable changes (2)
  • packages/2-mongo-family/2-authoring/contract-psl/package.json
  • packages/3-extensions/mongo/package.json
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/3-extensions/mongo/test/mongo.types.test-d.ts
  • packages/3-extensions/mongo/test/mongo.enum.e2e.test.ts

@wmadden-electric

Copy link
Copy Markdown
Contributor Author

R2 review-response round (D9)

R2 surfaced 9 findings (3 architect + 6 PE). 7 fixed in-scope this round; 2 deferred with reasons.

Fixed (5 commits)

  • F11 — BuilderFieldOutputType operator precedence bug. extends binds tighter than |, so nullable+many resolved to (Base|null)[] instead of Base[]|null. Not caught by any test because the e2e fixture had non-nullable tags. Fixed with parens; new type test pins the corner. — a0166572f
  • F14 — TS-DSL domain.namespaces[ns].enum not typed. MongoDomainNamespaceFromDefinition now carries enum? projected from EnumTypeHandle['enumMembers'] (literal-preserving, avoids the JsonValue widening D6 hit). The e2e test's RuntimeNs cast is gone. — a0166572f + e4e104c36
  • F12 — Vacuous parity probe. Replaced const _: T | undefined = undefined with a real expectTypeOf against MongoContractResult<typeof defineContract(…)>. Non-vacuous (fails if enum? slot removed). — e4e104c36
  • F16 — enumAccessors type-required but runtime-absent. Made optional (readonly enumAccessors?: ...) per PE option (a) — accurate to runtime. Nothing reads contract.enumAccessors directly today; db.enums reads from domain.namespaces[ns].enum. — e4e104c36
  • A07 — Dual-carrier rule documented in enum-accessor.ts. — e4e104c36
  • A09 — EnumTypeHandle brand: Symbol() → string-key phantom. Matches the documented pattern at framework-components/src/shared/codec.ts:42 (__codecTraits phantom) and query-ast/src/filter-expressions.ts:6 (__prismaNextMongoFilter__). The D7 workaround — @prisma-next/contract-authoring as a direct dep of postgres and mongo extensions — is reverted. Future extension packs no longer pay this tax. — 864895482
  • F15 — Layer-inverting devDep removed. The PSL interpreter test moved from mongo-contract-psl/test/ (architecture layer 2) to test/integration/test/mongo/ — exactly what the integration tests package exists for. family-mongo devDep deleted from mongo-contract-psl/package.json. — 63cad487a

Deferred (with reasons)

  • A08 — Channel-parameterize BuilderFieldOutputType. Right time is when FieldInputTypes lands (which is itself the structural fix for the F13 cast). Doing it preemptively means refactoring working code that just passed review. Tracked under Follow-ups.
  • F13 — F07 ORM-write boundary cast. Not deferred; subsumed by A08. The cast exists because FieldInputTypes isn't precomputed yet; when A08 channel-parameterizes the map, the cast dies automatically. Folded into Follow-ups.

Honesty corrections from the synthesis

  • I originally tried to defer A09 with "there are likely other Symbol() brands in the codebase, fixing one is inconsistent." The user pushed back. On checking: there are 9+ unique symbol brands, but codec.ts:42 and query-ast/src/filter-expressions.ts:6 both already document the string-key phantom pattern as the right answer. A09 wasn't a cross-cutting refactor; it was bringing EnumTypeHandle into line with an established convention. Lazy synthesis on my part.
  • I originally tried to defer F15 with "test-only layer inversion is workspace-only." The user pushed back. test/integration/ exists precisely for cross-layer test imports; the right move was to move the test, not document the inversion. Also lazy.

Final gate

pnpm build 66/66 · pnpm typecheck 138/138 · pnpm fixtures:check zero-diff · pnpm lint:casts delta=0 · pnpm lint:deps 0 violations (the F15 inversion cleared).

@wmadden-electric

Copy link
Copy Markdown
Contributor Author

Typed write input (R5) — closing the last R10 parity gap (D10)

R10 promises "R4/R5 parity" for the Mongo client. R4 (typed output) landed in D8; R5 (typed input) was unmetdb.orm.accounts.create({ role: 'nope' }) did not fail typecheck, papered over by a permissive AccountsCollection cast in the e2e test (R2's F13). This round closes it.

What landed

  • A08 — channel-parameterized the precomputed map (e9c7805fb). D8's output-only BuilderFieldOutputType became one Channel-parameterized family (BuilderFieldChannelType<…, Channel>), mirroring SQL's FieldChannelTypes. MongoContractResult now carries both fieldOutputTypes and fieldInputTypes. The D9/F11 nullable+many precedence fix (Base[] | null) is inherited on the input channel.
  • R5 — create narrows to the value union; F13 cast deleted (9d9628a3a). create({ role: 'user' }) compiles; create({ role: 'nope' }) is a compile error (@ts-expect-error, verified non-vacuous: widening the union back to string makes tsc report the directive unused + the positive assertions fail). Positive matrix tests cover scalar/nullable/many/nullable+many input. The permissive cast is gone.

Root cause found beyond the brief

D8's "output narrowing works" was partially false on the TS-DSL builder path: the enum handle lived on an optional __enumHandle property whose type field.namedType(Role) erased, so the precomputed map silently resolved enum fields to never from the builder. D8's tests never caught it — they exercised InferModelRow's contract-field path, not the builder path. Fixed by threading the handle as a 4th FieldBuilder type parameter (Mongo-local, no cross-family ripple). Output-side narrowing on the builder path is now actually correct, not just on the contract-field path.

Honest caveat — where is NOT narrowed

where({ role: 'nope' }) is not a compile error yet. MongoWhereFilter resolves per-field types through a different path (ExtractMongoCodecTypes, which is never for a TS-DSL contract) — narrowing it needs the same FieldInputTypes routing applied to the filter, a separate mechanism from create. The e2e test's read predicate uses a small documented byId helper cast as a stopgap. This is a genuine follow-up, not done. R5's letter ("write payloads accept only the value union") is satisfied by create; where-predicate narrowing is the next increment.

Gate

pnpm build 66/66 · pnpm typecheck 138/138 · pnpm fixtures:check zero-diff · pnpm lint:casts delta=0 · pnpm lint:deps clean.

@wmadden-electric wmadden-electric force-pushed the tml-2884-slice-mongo-enums-end-to-end-vertical-jsonschema-enforcement branch from 9d9628a to 73a6929 Compare June 24, 2026 14:02
@wmadden-electric

Copy link
Copy Markdown
Contributor Author

Addressing CodeRabbit review R1 (PRR_kwDOQM0QJc8AAAABDJwahw) sub-items:

  • Inline: framework-authoring.ts bare cast — fixed with blindCast in commit 15b113dd8. Thread resolved.
  • Inline: enum-type.ts ?? null issue — fixed to value === undefined ? name : value in commit 15b113dd8. Thread resolved.
  • Inline: contract-ts/test isolation — test now spreads known-valid contract and mutates only members in commit 29a045e7a. Thread resolved.
  • Outside diff: factory path drops enums (contract-builder.ts:1894-1899) 👎 The factory overload's Built type doesn't include enums, so the type system would reject returning enums from a factory today. This is a latent gap for a pattern not yet exercised by any code or test. Adding it requires threading enums through Built, ContractFactory, all buildBoundContract overloads, and MongoContractResult — a type-level refactor outside this PR's scope. Deferring to a follow-up.
  • FieldBuilder generic (duplicate) 👎 Already implemented — FieldBuilder<Type, Nullable, Many, Handle> with handle propagation through optional()/many() is in the current code at lines 142-155.
  • Nitpick: decorative section comments — removed the redundant banner in contract-types.test-d.ts in commit 29a045e7a.

@wmadden-electric

Copy link
Copy Markdown
Contributor Author

Addressing CodeRabbit review R2 (PRR_kwDOQM0QJc8AAAABDLQDCQ) sub-items:

  • Outside diff: dangling enum ref validation (contract-builder.ts:1360-1368) — Valid finding. buildContractField was stamping valueSet refs without checking the enum was declared. Added authoring-time validation in buildContractFromDefinition: throws when a model field references an enum via namedType() that isn't declared in defineContract({ enums: { ... } }). Commit 29a045e7a.
  • Duplicate: FieldBuilder generic 👎 Same as R1 — already in the current code.

Comment thread packages/1-framework/0-foundation/contract/src/enum-accessor.ts Outdated
Comment thread packages/1-framework/0-foundation/contract/src/enum-accessor.ts Outdated
Comment thread packages/2-mongo-family/1-foundation/mongo-contract/src/contract-types.ts Outdated
Comment thread packages/2-mongo-family/2-authoring/contract-psl/src/interpreter.ts Outdated
Comment thread examples/mongo-demo/migrations/app/20260626_add-user-role-enum/migration.ts Outdated
Comment thread packages/2-mongo-family/9-family/src/core/authoring-entity-types.ts
@wmadden wmadden enabled auto-merge June 28, 2026 07:07
@wmadden-electric wmadden-electric force-pushed the tml-2884-slice-mongo-enums-end-to-end-vertical-jsonschema-enforcement branch from ec1febb to 17df144 Compare June 28, 2026 07:13
@wmadden wmadden added this pull request to the merge queue Jun 28, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Jun 28, 2026
wmadden-electric and others added 10 commits June 28, 2026 09:52
…chema acceptance

- Add `enum-type.ts` to `@prisma-next/mongo-contract-ts` with full
  `enumType`/`member`/`bindEnumType`/`isEnumTypeHandle` implementation
  (mirrors the SQL family's enum-type.ts; mongo domain cannot import sql)
- Export `bindEnumType`, `enumType`, `member`, `EnumTypeHandle`, `EnumMember`,
  `ExtractCodecTypesFromPack` from `@prisma-next/mongo-contract-ts/contract-builder`
- Add `field.namedType(handle)` to the Mongo builder, carrying `__enumHandle`
  through the `optional()`/`many()` chain
- Add `enums?` slot to `ContractDefinition` and `BoundDefinitionInput`
- `buildContractFromDefinition` accumulates enums into
  `domain.namespaces[__unbound__].enum` and stamps the field's domain `valueSet`
  ref (`plane:'domain'`, `entityKind:'enum'`, `namespaceId:'__unbound__'`)
- Add `ContractEnumSchema` and `DomainEnumRefSchema` to the Mongo contract
  schema; add `enum?` to the domain namespace slot and `valueSet?` to
  `RawFieldSchema` so built enum contracts pass arktype validation
- Add `3-extensions/mongo/src/contract/enum-type.ts` binding
  `bindEnumType<MongoCodecTypes>()` from `@prisma-next/mongo-contract-ts`;
  export `enumType` and `member` from `@prisma-next/mongo/contract-builder`
- New round-trip test: author → build → validate → assert enum entity +
  field valueSet ref + MongoContractSchema passes
- New type-test: `enumType` handle `.values` is a literal readonly tuple

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
- New `authoring-entity-types.ts` in `family-mongo` exports
  `mongoFamilyEnumEntityDescriptor`, `mongoFamilyEntityTypes`, and
  `mongoFamilyPslBlockDescriptors` (mirrors SQL family's pattern).
- `pack.ts` and `control-descriptor.ts` now expose the authoring
  section so the PSL provider and CLI can discover enum block descriptors.
- Mongo PSL `interpreter.ts` gains `authoringContributions` input,
  `processEnumDeclarations` (routes enum blocks through the entity
  factory), enum-aware field resolution (stamps `valueSet` ref), and
  includes `enum:` in the returned domain namespace.
- `provider.ts` passes `pslBlockDescriptors` to the parser and
  `authoringContributions` to the interpreter.
- `interpreter.enum.test.ts` proves PSL → contract round-trip parity
  with D1's TS DSL output (enum entity shape + field valueSet ref).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
When a field carries a domain valueSet ref with entityKind:'enum',
fieldToBsonSchema() now adds `enum: [...member values]` (declaration order)
to the derived property schema. The existing validationLevel:'strict' means
the collection validator rejects any out-of-set write.

- derive-json-schema.ts: added `enums` parameter to fieldToBsonSchema /
  deriveObjectSchema / deriveJsonSchema / derivePolymorphicJsonSchema
- interpreter.ts: passes builtEnums through both deriveJsonSchema and
  derivePolymorphicJsonSchema call sites
- derive-json-schema.test.ts: five new tests covering enum array presence,
  absence on non-enum fields, nullable enum, declaration-order preservation,
  and unknown-enum graceful skip

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
- array enum field (many:true): inject `enum` into `items`, not the outer schema
- nullable enum field: include `null` in the emitted `enum` array so null writes pass $jsonSchema
- correct existing nullable enum test to assert the fixed shape
- add tests: array enum, nullable+array enum

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
InferFieldType gains a valueSet branch that resolves enum fields to
their literal value union (nullable / many preserved). MongoClient
interface and facade both carry readonly enums: NamespacedEnums<TContract>,
wired via buildNamespacedEnums + blindCast mirroring Postgres exactly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
db.enums.Role now works directly on MongoClient — no __unbound__ key
needed. Mirrors sqlite's UnboundEnums + unboundNamespace pattern:
- Added UnboundEnums<TContract> type alias (projects out UNBOUND_NAMESPACE_ID)
- Added unboundNamespace() helper (duplicated from sqlite; both are target-
  internal, so no cross-family import and no invasive lift required)
- MongoClient.enums is now UnboundEnums<TContract> instead of NamespacedEnums
- Runtime enums built via unboundNamespace(buildNamespacedEnums(...))
- Type tests and runtime tests updated: __unbound__ indirection removed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…ests

Adds the MMS-backed integration test and emit-then-consume type test that
prove the full vertical: author → $jsonSchema enforcement → typed read →
db.enums. Covers scalar, nullable, and array enum fields. Out-of-set writes
are rejected by the MongoDB collection validator; in-set writes round-trip.
The emit-then-consume tests assert value-union narrowing through the emitted
contract.d.ts (non-vacuous: falls back to codec channel when narrowing is
absent). Adds @prisma-next/emitter and @prisma-next/mongo-emitter as
devDependencies to @prisma-next/mongo.

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

The namedType field builder used a bare object cast to narrow the literal
kind string. Replace with blindCast to satisfy the no-bare-cast ratchet
(delta was +1; now 0).

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Add DefinitionEnums / EnumHandleAccessorType / BuiltEnumAccessors to the
Mongo TS DSL builder and hang `enumAccessors: BuiltEnumAccessors<Definition>`
off MongoContractBaseFromDefinition, mirroring SQL's approach. The slot is
type-only (the blindCast at the builder's return preserves the runtime shape
satisfying MongoContract); BuiltEnumAccessorsOf picks it up so
NamespacedEnums resolves db.enums.<Name>.values to the literal tuple for
TS-DSL-authored contracts.

Add the proof in mongo.types.test-d.ts: a defineContract({enums:{Role}})
contract resolves db.enums.Role.values to readonly ['user','admin'] and
db.enums.Role.members.User to "user" without any cast.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
wmadden-electric and others added 24 commits June 28, 2026 09:52
…ent dual carrier (F12/A07)

F12 replaces the vacuous `const _: T | undefined = undefined` parity probe with
a non-vacuous ExtractMongoFieldOutputTypes reachability assertion and a typed
namespace-enum-slot test that fails if F14's slot is removed. The precise
per-field narrowing through BuilderFieldOutputType is covered by the F11 test
in contract-builder.types.test-d.ts (where the inner defineContract resolves
the typemaps reliably).

A07 rewrites the dual-carrier comment in enum-accessor.ts. With F14 typing the
namespace `enum?` slot, the runtime accessor builds via `buildNamespacedEnums`
read the same shape on both producers. enumAccessors (now optional on
MongoContractBaseFromDefinition) remains as the literal-tuple carrier for
`db.enums.Role.values[0]` — tuple structure can't survive the namespace path
because ContractEnum stores members as an array of {name, value}, not a
positional tuple. NamespaceEnumAccessors selects the enumAccessors carrier
when present, otherwise the namespace enum entries, avoiding the type
intersection that would conflict literal-tuple values against the union.

The F14 cast removal in mongo.enum.e2e.test.ts: the RuntimeNs cast is gone;
`ns?.enum` is read directly and only narrows the codec-value JsonValue
relationship at the deriveJsonSchema boundary.

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

D7's enum-type.ts lift used `Symbol('EnumTypeHandle')` as the nominal brand,
which tsdown inlines as a fresh `unique symbol` into each consumer's .d.mts,
breaking nominal identity across module boundaries. The workaround was to
declare @prisma-next/contract-authoring as a direct dep of postgres and mongo
extension packs.

Replace the Symbol with a string-literal phantom key, mirroring the precedent
in codec.ts (`__codecTraits`) and mongo-query-ast/filter-expressions.ts
(`__prismaNextMongoFilter__`). String equality survives .d.mts de-duplication,
so the workaround direct deps are no longer needed — both extension packages
revert to consuming contract-authoring transitively through their family
contract-ts packages.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The interpreter test imports `mongoFamilyEntityTypes` / `mongoFamilyPslBlockDescriptors`
from @prisma-next/family-mongo (layer 9), but the test lived in mongo-contract-psl
(layer 2). family-mongo already depends on mongo-contract-psl for runtime, so the
test-only devDep on family-mongo created a test-only layer inversion that
contradicted what lint:deps enforces for runtime imports.

Move the test to test/integration/test/mongo/ where cross-layer imports are
legitimate. The test now imports interpretPslDocumentToMongoContract from the
@prisma-next/mongo-contract-psl package (rather than from a relative src path)
since it's outside the package's source tree. Drop the family-mongo devDep from
mongo-contract-psl/package.json.

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Refactor D8's output-only precomputed field-type map into a single
channel-parameterized family, mirroring SQL's FieldChannelTypes:

- BuilderBaseChannelType / BuilderFieldChannelType take a Channel param
  ('output' | 'input'). Enum fields resolve to the value union on both
  channels; scalar fields resolve to the codec's per-channel type.
- FieldChannelTypesFromDefinition instantiated twice (output + input);
  MongoContractResult now carries both fieldOutputTypes and fieldInputTypes
  in its MongoTypeMaps.
- The nullable+many precedence fix (Base[] | null, not (Base|null)[]) is
  inherited by the shared family, so it holds on the input channel too.

Thread the enum handle through FieldBuilder as a 4th type parameter so
BuilderEnumValueUnion can detect it from the builder's type. Previously the
handle lived only on an optional __enumHandle property that field.namedType's
return type erased, so the precomputed map silently resolved enum fields to
never. createFieldBuilder is now generic in the handle.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…3 cast (R5/F13)

With FieldInputTypes now on MongoContractResult (A08), the ORM CreateInput
resolves enum fields to the value union instead of never. Prove R5 and remove
the F13 permissive cast:

- mongo.types.test-d.ts: positive matrix (scalar / nullable / many /
  nullable+many) plus a non-vacuous negative — create({ role: 'nope' }) is a
  compile error and 'nope' is not assignable to the input field type. Verified
  non-vacuous: widening the value union back to string makes tsc flag the
  @ts-expect-error as unused.
- mongo.enum.e2e.test.ts: delete the AccountsCollection permissive cast (F13);
  db.orm.accounts.create(...) typechecks against the real narrowed input.
  Out-of-set/null cases keep their MMS runtime rejection via a local `as never`
  on the value only (test files are cast-exempt).

The `where` filter still narrows its per-field type through
ExtractMongoCodecTypes, which is never for a TS-DSL contract, so where({ _id })
resolves _id to never. Narrowing where needs a separate mechanism (route it
through FieldInputTypes too); until then the read predicate is cast narrowly
via a documented byId helper. Noted as a follow-up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…tension surface

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…3/A-004)

Replace bare `as` cast in framework-authoring.ts:939 with `blindCast` to
comply with the no-bare-casts rule (review thread PRRT_kwDOQM0QJc6J5TS6).

Fix `member()` to use `value === undefined ? name : value` instead of
`value ?? name`, preventing a type/runtime mismatch when an explicit `null`
is passed (review thread PRRT_kwDOQM0QJc6J5TTf).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…cleanup (A-005/A-006c/A-007a)

Tighten the empty-members rejection test to mutate a known-valid contract
(spread + override only enum.Role.members), so the assertion isolates the
enum-members rule rather than failing on unrelated missing required fields
(review thread PRRT_kwDOQM0QJc6J5TTu).

Add authoring-time validation in buildContractFromDefinition: throw when a
model field references an enum via namedType() that is not declared in the
contract's enums map (review thread via PRR_kwDOQM0QJc8AAAABDLQDCQ).

Remove the redundant decorative section-banner comment in
contract-types.test-d.ts (review body PRR_kwDOQM0QJc8AAAABDJwahw nitpick).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The dangling-enum reference guard cast the loop variable to AnyFieldBuilder,
introducing a bare `as` in production code. modelBuilder.__fields is already
typed Record<string, FieldBuilder>, so __enumHandle is accessible directly —
the cast was unnecessary and is dropped (not blindCast'd).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
… into the target facades (revert A02 placement)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…review)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
Adds a UserRole PSL enum (admin/author/reader, mongo/string@1 codec) to
the demo's contract, re-emits contract.json and contract.d.ts, and
consumes the enum through the full product path:

- Typed write in seed.ts: role field required on every user create
- db.enums accessor exposed in db.ts and used via getRoles() in server.ts
- /api/roles endpoint returns db.enums.UserRole.values
- UserList.tsx renders user.role (typed read through emitted FieldOutputTypes)
- App.tsx footer lists the enum + $jsonSchema enforcement as a demo feature
- Migration 20260626_add-user-role-enum updates the users collection
  $jsonSchema validator to enforce the enum constraint via collMod/setValidation
- All test creates updated with the now-required role field

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: Will Madden <madden@prisma.io>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…m the precomputed map (review)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…tract snapshot (not an inlined literal)

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

Replace the hand-authored 20260626_add-user-role-enum migration with
genuine prisma-next migration plan output. The planner diffs from
the prior migration end-contract (2827cbad) to the current contract
(250af57) and emits a collMod on users to add the role enum validator.

Also delete the 20260415_add-posts-author-index self-edge migration
(from === to with no data ops), which was a pre-existing integrity
violation that migration plan correctly rejects. The collMod migration
takes its place as the second step in the chain.

Update manual-migration.test.ts to cover the planner-generated collMod
migration instead of the deleted index migration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…t + value objects, not unknown (regression fix)

Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…ed field maps; delete the production field-resolver fallback

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…the index migration via the planner

Add @@index([authorId]) and @@index([createdAt(sort: Desc), authorId]) to
the Post model in examples/mongo-demo/src/contract.prisma, re-emit the
contract, then run migration plan --from sha256:250af57b (the enum
migration end-hash) to generate a planner-produced createIndex migration
(20260626T1916_add_posts_indexes). The new migration has from != to and
completes the chain: initial → enum → indexes.

The two indexes were previously applied by the hand-authored
add-posts-author-index migration (deleted in commit 6fb1559 because
its from == to was an integrity violation). They are now contract-backed
and schema-driven.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…es + regenerate value-objects d.ts

MongoTypeMaps<TCodecTypes, TFieldOutputTypes, TFieldInputTypes> now requires all
three type arguments. Omitting the field maps is a compile error, so the
silent-fallback-to-unknown regression class cannot recur.

Exported AnyMongoTypeMaps as the widened constraint for call sites that accept
any concrete contract (production generic bounds in query-builder, orm,
mongo-orm, mongo extension runtime).

Updated every fixture that used 1- or 2-arg MongoTypeMaps:
- 6 test fixtures in mongo-contract, mongo-runtime, mongo-orm, integration tests
- 1 generated fixture (value-objects/fixtures/generated/mongo-contract.d.ts)
  — added FieldInputTypes to match what the emitter produces for a scalar-only model
- contract-types.test-d.ts — dropped the "defaults to Record<...>" tests (the
  defaults are removed) and completed 2-arg forms with FieldInputTypes

fixtures:check: zero diff (mongo-contract.d.ts glob not covered — see note below)
typecheck: 143/143 (0 cached)
lint:casts: delta=-1

Note: test/integration/test/value-objects/fixtures/generated/mongo-contract.d.ts
is NOT covered by the fixtures:check glob (**/contract.* etc.). There is no mongo
emit pipeline for this fixture (the prisma-next.config.ts in that directory is
SQL/Postgres-only). The file was hand-maintained before and remains hand-maintained
after; it now carries the correct 3-arg form. Adding it to fixtures:check would
require a new mongo emit config for the value-objects fixture — flagged for follow-up.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…> codecIdByEnumName

- Remove the 3-line "Dual-carrier" block from enum-accessor.ts (lines 78-80):
  it described a contract concern unrelated to the types that followed it.
- Rename `enumCodecIds` to `codecIdByEnumName` in the Mongo PSL interpreter
  (declaration, build site, two call sites) for clarity.
- Tighten the adjacent lookup comment to a plain description of what it does.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…c comment on recursion bound

Add a doc comment above BuilderFieldChannelType clarifying it runs once at
defineContract time (not per query) and that recursion is bounded by VO nesting depth.

Extend contract-builder.types.test-d.ts with the full matrix:
- scalar (plain/nullable/many/nullable+many) on both output AND input channels
- enum (plain/nullable/many/nullable+many) on both channels, including the
  precedence-trap negative (.not.toEqualTypeOf for (union | null)[] vs (union)[] | null)
- value object (plain/nullable/many) on both channels
- nested VO on both channels, with explicit .not.toBeUnknown() to pin
  the recursion in BuilderBaseChannelType (the :660 concern the author raised)

No bugs found during the matrix: nested VO resolves correctly on both channels,
and precedence modifiers work as expected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
…d rename

TML-2891 renamed 'mongo-namespace' → 'mongo-database'. Update the three
migration end/start-contract.d.ts snapshots in our branch accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
… diff

Add incidental-diff note to 0.14→0.15 instructions.md covering the
mongo-demo enum migration additions and the migration snapshot updates.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Signed-off-by: willbot <w.a.madden+machine@gmail.com>
Signed-off-by: Will Madden <madden@prisma.io>
The PSL enum interpreter test was moved to test/integration (to avoid a
layer-inversion devDep), but integration's default timeout is 100ms. PSL
parse + interpret takes longer. Apply timeouts.typeScriptCompilation (8s)
to the describe block.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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 tml-2884-slice-mongo-enums-end-to-end-vertical-jsonschema-enforcement branch from 17df144 to 2d3f06a Compare June 28, 2026 07:56
@wmadden wmadden added this pull request to the merge queue Jun 28, 2026
Merged via the queue into main with commit 7b57bfa Jun 28, 2026
21 checks passed
@wmadden wmadden deleted the tml-2884-slice-mongo-enums-end-to-end-vertical-jsonschema-enforcement branch June 28, 2026 09:03
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