Skip to content

Mongo PSL: bare-member enums fail interpretation unless enum inference codecs are wired explicitly#934

Open
ankur-arch wants to merge 3 commits into
mainfrom
fix/mongo-psl-enum-inference-defaults
Open

Mongo PSL: bare-member enums fail interpretation unless enum inference codecs are wired explicitly#934
ankur-arch wants to merge 3 commits into
mainfrom
fix/mongo-psl-enum-inference-defaults

Conversation

@ankur-arch

@ankur-arch ankur-arch commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

What this fixes

Using an enum in a MongoDB PSL schema failed contract resolution:

■  ✖ Failed to resolve contract source (PN-RUN-3000)
│    Why: PSL to Mongo contract interpretation failed
│      - [PSL_UNSUPPORTED_FIELD_TYPE] Field "WhatsAppMessages.direction" type "WhatsAppMessageDirection"
│        is not supported in Mongo PSL interpreter

Reported on Discord ([NEXT], 2026-07-07). After this PR, a plain enum just works, on every config surface:

enum WhatsAppMessageDirection {
  INBOUND
  OUTBOUND
}

model WhatsAppMessages {
  id        ObjectId @id @map("_id")
  direction WhatsAppMessageDirection
}

What was broken

An enum without @@type(...) infers its codec from its members — but the codec ids to infer to were only wired in one place: @prisma-next/mongo's defineConfig. Any config that used the mongoContract() provider directly hit PSL_ENUM_CANNOT_INFER_TYPE, which then cascaded into the misleading PSL_UNSUPPORTED_FIELD_TYPE on every field typed by the enum — exactly the reported "2 of 2" diagnostics.

The fix

The Mongo PSL interpreter now derives the default inference codecs from the target's own scalar type descriptors (Stringmongo/string@1, Intmongo/int32@1) — the same values defineConfig passed by hand. defineConfig drops its now-redundant wiring, and the explicit enumInferenceCodecs option still overrides.

  • packages/2-mongo-family/2-authoring/contract-psl/src/interpreter.ts — derive the default when the input omits enumInferenceCodecs
  • packages/3-extensions/mongo/src/config/define-config.ts — remove the duplicate wiring
  • skills/extension-author/.../0.14-to-0.15/instructions.md — no-action upgrade declaration (behaviour identical for extension authors)

Testing

Tests were written first and failed for the right reason, then went green:

  • Interpreter unit tests (new interpreter.enums.test.ts): a bare-member enum resolves with no explicit wiring (domain enum + typed field + storage value set); an explicit enumInferenceCodecs still overrides; the original diagnostic pair is still reported when no default can be derived.
  • End-to-end CLI tests: the reporter's schema shape emits successfully through contract emit with both config styles — raw mongoContract() (previously failing) and @prisma-next/mongo defineConfig (new fixture).
  • Regression: full pnpm test:packages sweep, the integration emit suite, pnpm lint:deps, and pnpm fixtures:check (no fixture drift) all pass.

The same Discord thread's second ask — an extendable Collection class for the Mongo ORM — is shipped separately in #936.

🤖 Generated with Claude Code

A bare-member enum (`enum Direction { INBOUND OUTBOUND }`) in a Mongo PSL
schema failed contract interpretation with PSL_ENUM_CANNOT_INFER_TYPE
cascading into PSL_UNSUPPORTED_FIELD_TYPE on every enum-typed field,
unless the config passed enumInferenceCodecs explicitly — only
@prisma-next/mongo's defineConfig did.

The interpreter now derives the default inference codecs from the
target-contributed PSL String/Int scalar type descriptors, so every
config surface resolves classic enums identically; the explicit option
still overrides. defineConfig drops its now-redundant wiring.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Ankur Datta <64993082+ankur-arch@users.noreply.github.com>
@ankur-arch
ankur-arch requested a review from a team as a code owner July 9, 2026 01:01
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The Mongo PSL interpreter now derives default enum inference codec ids from target scalar type descriptors when not explicitly supplied. The change is reflected in config wiring, unit coverage, CLI integration coverage, and upgrade notes.

Changes

Enum inference codec defaulting

Layer / File(s) Summary
Interpreter: derive and thread enumInferenceCodecs
packages/2-mongo-family/2-authoring/contract-psl/src/interpreter.ts, packages/2-mongo-family/2-authoring/contract-psl/src/provider.ts
Adds deriveEnumInferenceCodecs, falls back to it when input.enumInferenceCodecs is absent, threads the result into enum entity context, and expands the option documentation.
Unit tests for enum inference behavior
packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.enums.test.ts
Adds tests for inferred enum codecs, explicit overrides, and diagnostics when inference cannot be derived.
define-config: remove explicit codec wiring
packages/3-extensions/mongo/src/config/define-config.ts
Removes the explicit codec imports and passes only the contract output into mongoContract in the fallback branch.
CLI integration test for bare-member enum emit
test/integration/test/cli.emit-command.additional.test.ts, test/integration/test/fixtures/cli/cli-integration-test-app/fixtures/emit-command/prisma-next.config.mongo-define.ts
Adds an integration test for two Mongo config variants and a new defineConfig fixture, verifying emitted enum output and value sets.
Upgrade instructions note
skills/extension-author/prisma-next-extension-upgrade/upgrades/0.14-to-0.15/instructions.md
Adds a release note describing the new default enum inference behavior and the updated config wiring.

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

Possibly related PRs

Suggested reviewers: wmadden

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately points to the Mongo PSL enum-inference change and the bare-member enum issue addressed by the PR.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/mongo-psl-enum-inference-defaults

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

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

size-limit report 📦

Path Size
postgres / no-emit 153.18 KB (0%)
postgres / emit 128.51 KB (0%)
mongo / no-emit 98.36 KB (0%)
mongo / emit 89.39 KB (0%)
cf-worker / no-emit 180.2 KB (0%)
cf-worker / emit 153.16 KB (0%)

@pkg-pr-new

pkg-pr-new Bot commented Jul 9, 2026

Copy link
Copy Markdown

Open in StackBlitz

@prisma-next/extension-author-tools

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

@prisma-next/mongo-runtime

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

@prisma-next/family-mongo

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

@prisma-next/sql-runtime

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

@prisma-next/family-sql

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

@prisma-next/extension-arktype-json

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

@prisma-next/middleware-cache

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

@prisma-next/mongo

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

@prisma-next/extension-paradedb

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

@prisma-next/extension-pgvector

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

@prisma-next/extension-postgis

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

@prisma-next/postgres

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

@prisma-next/sql-orm-client

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

@prisma-next/sqlite

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

@prisma-next/extension-supabase

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

@prisma-next/target-mongo

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

@prisma-next/adapter-mongo

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

@prisma-next/driver-mongo

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

@prisma-next/contract

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

@prisma-next/utils

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

@prisma-next/config

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

@prisma-next/errors

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

@prisma-next/framework-components

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

@prisma-next/operations

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

@prisma-next/ts-render

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

@prisma-next/contract-authoring

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

@prisma-next/ids

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

@prisma-next/psl-parser

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

@prisma-next/psl-printer

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

@prisma-next/cli

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

@prisma-next/cli-telemetry

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

@prisma-next/config-loader

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

@prisma-next/emitter

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

@prisma-next/language-server

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

@prisma-next/migration-tools

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

prisma-next

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

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

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

@prisma-next/mongo-codec

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

@prisma-next/mongo-contract

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

@prisma-next/mongo-value

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

@prisma-next/mongo-contract-psl

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

@prisma-next/mongo-contract-ts

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

@prisma-next/mongo-emitter

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

@prisma-next/mongo-schema-ir

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

@prisma-next/mongo-query-ast

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

@prisma-next/mongo-orm

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

@prisma-next/mongo-query-builder

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

@prisma-next/mongo-lowering

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

@prisma-next/mongo-wire

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

@prisma-next/sql-contract

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

@prisma-next/sql-errors

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

@prisma-next/sql-operations

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

@prisma-next/sql-schema-ir

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

@prisma-next/sql-contract-psl

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

@prisma-next/sql-contract-ts

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

@prisma-next/sql-contract-emitter

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

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

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

@prisma-next/sql-relational-core

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

@prisma-next/sql-builder

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

@prisma-next/target-postgres

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

@prisma-next/target-sqlite

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

@prisma-next/adapter-postgres

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

@prisma-next/adapter-sqlite

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

@prisma-next/driver-postgres

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

@prisma-next/driver-sqlite

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

commit: 622fb4a

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.enums.test.ts (1)

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

Optional: add coverage for the derived int codec path.

Current tests cover default text-codec inference, explicit override, and failure when Int is missing, but no test exercises an integer bare-member enum successfully resolving via the derived int codec end-to-end.

🤖 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-psl/test/interpreter.enums.test.ts`
around lines 152 - 166, Add a test in interpreter.enums.test.ts to cover the
successful derived int codec path for bare-member enums. Extend the existing
enum inference coverage around interpretPslDocumentToMongoContract by creating
an int-valued enum case that resolves through the derived int codec end-to-end,
then assert the resulting namespace enum codecId matches the expected
mongo/int32@1 codec. Use the existing test helpers and symbols like
bareMemberEnumSchema, mongoScalarTypeDescriptors, mongoCodecLookup,
authoringContributions, and UNBOUND_NAMESPACE_ID to keep it aligned with the
current enum inference tests.
test/integration/test/cli.emit-command.additional.test.ts (1)

383-417: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Split the combined toMatchObject into targeted assertions.

This single toMatchObject bundles targetFamily, target, and deeply nested enum/model/field checks. Based on learnings, this repo's test convention is to prefer separate expect() assertions per field over toMatchObject() for multi-field validation, since it produces clearer, more actionable failure messages pinpointing exactly which field failed.

♻️ Example split
-        expect(contractJson).toMatchObject({
-          targetFamily: 'mongo',
-          target: 'mongo',
-          domain: {
-            namespaces: {
-              __unbound__: {
-                enum: {
-                  WhatsAppMessageDirection: {
-                    codecId: 'mongo/string@1',
-                    members: [
-                      { name: 'INBOUND', value: 'INBOUND' },
-                      { name: 'OUTBOUND', value: 'OUTBOUND' },
-                    ],
-                  },
-                },
-                models: {
-                  WhatsAppMessages: expect.objectContaining({
-                    fields: expect.objectContaining({
-                      direction: {
-                        type: { kind: 'scalar', codecId: 'mongo/string@1' },
-                        nullable: false,
-                        valueSet: {
-                          plane: 'domain',
-                          entityKind: 'enum',
-                          namespaceId: '__unbound__',
-                          entityName: 'WhatsAppMessageDirection',
-                        },
-                      },
-                    }),
-                  }),
-                },
-              },
-            },
-          },
-        });
+        expect(contractJson.targetFamily).toBe('mongo');
+        expect(contractJson.target).toBe('mongo');
+        const enumEntry =
+          contractJson.domain.namespaces.__unbound__.enum.WhatsAppMessageDirection;
+        expect(enumEntry.codecId).toBe('mongo/string@1');
+        expect(enumEntry.members).toEqual([
+          { name: 'INBOUND', value: 'INBOUND' },
+          { name: 'OUTBOUND', value: 'OUTBOUND' },
+        ]);
+        const field =
+          contractJson.domain.namespaces.__unbound__.models.WhatsAppMessages.fields.direction;
+        expect(field.type).toEqual({ kind: 'scalar', codecId: 'mongo/string@1' });
+        expect(field.nullable).toBe(false);
+        expect(field.valueSet).toEqual({
+          plane: 'domain',
+          entityKind: 'enum',
+          namespaceId: '__unbound__',
+          entityName: 'WhatsAppMessageDirection',
+        });
🤖 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 `@test/integration/test/cli.emit-command.additional.test.ts` around lines 383 -
417, The test in the contract JSON assertion is using one large toMatchObject
for multiple unrelated checks, which should be split into focused expect()
assertions. Update the assertions around contractJson to validate targetFamily,
target, and the nested domain/namespace/enum/model/field structure separately,
using the existing WhatsAppMessageDirection and WhatsAppMessages references to
keep each failure message precise.

Source: Learnings

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

Nitpick comments:
In
`@packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.enums.test.ts`:
- Around line 152-166: Add a test in interpreter.enums.test.ts to cover the
successful derived int codec path for bare-member enums. Extend the existing
enum inference coverage around interpretPslDocumentToMongoContract by creating
an int-valued enum case that resolves through the derived int codec end-to-end,
then assert the resulting namespace enum codecId matches the expected
mongo/int32@1 codec. Use the existing test helpers and symbols like
bareMemberEnumSchema, mongoScalarTypeDescriptors, mongoCodecLookup,
authoringContributions, and UNBOUND_NAMESPACE_ID to keep it aligned with the
current enum inference tests.

In `@test/integration/test/cli.emit-command.additional.test.ts`:
- Around line 383-417: The test in the contract JSON assertion is using one
large toMatchObject for multiple unrelated checks, which should be split into
focused expect() assertions. Update the assertions around contractJson to
validate targetFamily, target, and the nested domain/namespace/enum/model/field
structure separately, using the existing WhatsAppMessageDirection and
WhatsAppMessages references to keep each failure message precise.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: d517c9fa-2aeb-423c-b314-080ef7179670

📥 Commits

Reviewing files that changed from the base of the PR and between f44cbcb and ac20aec.

📒 Files selected for processing (6)
  • 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/interpreter.enums.test.ts
  • packages/3-extensions/mongo/src/config/define-config.ts
  • test/integration/test/cli.emit-command.additional.test.ts
  • test/integration/test/fixtures/cli/cli-integration-test-app/fixtures/emit-command/prisma-next.config.mongo-define.ts

@ankur-arch

Copy link
Copy Markdown
Contributor Author

Follow-up for the second issue in the same Discord thread (extendable Collection for the Mongo ORM, ADR 175): #936

ankur-arch and others added 2 commits July 9, 2026 16:24
…nce default wiring

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Ankur Datta <64993082+ankur-arch@users.noreply.github.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Ankur Datta <64993082+ankur-arch@users.noreply.github.com>
@ankur-arch

Copy link
Copy Markdown
Contributor Author

Ponytail review (lazy-senior-dev ladder over the diff):

  • Root cause, not symptom — the fix targets the missing inference-codec default at the interpreter seam instead of patching each config surface; the misleading PSL_UNSUPPORTED_FIELD_TYPE cascade disappears because its trigger does.
  • Deletion over additiondefineConfig loses its hand-wired codec ids (and the import); the net production change is a 12-line derivation helper reusing the target's existing scalar descriptors.
  • Reuse — no new configuration surface: the default derives from scalarTypeDescriptors the target already contributes, and the existing enumInferenceCodecs option is untouched as the override.
  • No speculative abstraction — the SQL interpreter has the same seam but wasn't changed; it isn't part of the reported problem.

→ skipped: same defaulting for the SQL PSL interpreter — add if/when a SQL user hits the equivalent report (its defineConfig paths already wire the ids).

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.

1 participant