TML-2940: from/to FK relation syntax — clean break, reject legacy fields/references#872
TML-2940: from/to FK relation syntax — clean break, reject legacy fields/references#872tensordreams wants to merge 16 commits into
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR migrates Prisma relation attribute syntax from the legacy ChangesCore relation parsing, lowering, and printing
Documentation updates
Example projects and integration fixtures migration
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
@prisma-next/extension-author-tools
@prisma-next/mongo-runtime
@prisma-next/family-mongo
@prisma-next/sql-runtime
@prisma-next/family-sql
@prisma-next/extension-arktype-json
@prisma-next/middleware-cache
@prisma-next/mongo
@prisma-next/extension-paradedb
@prisma-next/extension-pgvector
@prisma-next/extension-postgis
@prisma-next/postgres
@prisma-next/sql-orm-client
@prisma-next/sqlite
@prisma-next/extension-supabase
@prisma-next/target-mongo
@prisma-next/adapter-mongo
@prisma-next/driver-mongo
@prisma-next/contract
@prisma-next/utils
@prisma-next/config
@prisma-next/errors
@prisma-next/framework-components
@prisma-next/operations
@prisma-next/ts-render
@prisma-next/contract-authoring
@prisma-next/ids
@prisma-next/psl-parser
@prisma-next/psl-printer
@prisma-next/cli
@prisma-next/cli-telemetry
@prisma-next/config-loader
@prisma-next/emitter
@prisma-next/language-server
@prisma-next/migration-tools
prisma-next
@prisma-next/vite-plugin-contract-emit
@prisma-next/mongo-codec
@prisma-next/mongo-contract
@prisma-next/mongo-value
@prisma-next/mongo-contract-psl
@prisma-next/mongo-contract-ts
@prisma-next/mongo-emitter
@prisma-next/mongo-schema-ir
@prisma-next/mongo-query-ast
@prisma-next/mongo-orm
@prisma-next/mongo-query-builder
@prisma-next/mongo-lowering
@prisma-next/mongo-wire
@prisma-next/sql-contract
@prisma-next/sql-errors
@prisma-next/sql-operations
@prisma-next/sql-schema-ir
@prisma-next/sql-contract-psl
@prisma-next/sql-contract-ts
@prisma-next/sql-contract-emitter
@prisma-next/sql-lane-query-builder
@prisma-next/sql-relational-core
@prisma-next/sql-builder
@prisma-next/target-postgres
@prisma-next/target-sqlite
@prisma-next/adapter-postgres
@prisma-next/adapter-sqlite
@prisma-next/driver-postgres
@prisma-next/driver-sqlite
commit: |
size-limit report 📦
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
packages/1-framework/2-authoring/psl-printer/src/ast-to-print-document.ts (1)
272-276: 🎯 Functional Correctness | 🔵 TrivialRelies on explicit
to:which breaks implicit relations.The check
hasFullRelationrequires bothfrom(orfields) andto(orreferences) to establish a FK dependency edge. While thesql-schema-ir-to-psl-astgenerator currently emitsto:explicitly (making the bug latent for this specific path), this logic fails if the AST allows implicitto:(defaulting to the target's@id), as stated in the PR objective.Since the FK-owning side is uniquely identified by the presence of
from/fields, thetocheck is unnecessary for detecting the edge and enforces a stricter requirement than the PSL spec allows.Update the logic to key completeness solely on
hasFrom:- return hasFrom && hasTo; + return hasFrom;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/1-framework/2-authoring/psl-printer/src/ast-to-print-document.ts` around lines 272 - 276, The relation completeness check in hasFullRelation is too strict because it requires both sides of the FK edge, which breaks implicit-to relations. Update the logic in ast-to-print-document’s hasFullRelation so it treats the FK-owning side as sufficient by keying completeness only on the presence of from/fields, while still allowing to/references when explicitly provided.
🤖 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/2-sql/2-authoring/contract-psl/src/psl-relation-resolution.ts`:
- Around line 239-242: The relation argument resolution in
psl-relation-resolution.ts currently falls back from canonical names to legacy
aliases and silently ignores cases where both are present. Update the logic
around getNamedArgument in the relation parser to explicitly detect conflicts
when both from/fields or to/references are supplied, and reject the input with a
validation error instead of preferring one value. Use the existing relation
parsing flow in psl-relation-resolution.ts so the check happens before fromRaw
and toRaw are consumed.
---
Nitpick comments:
In `@packages/1-framework/2-authoring/psl-printer/src/ast-to-print-document.ts`:
- Around line 272-276: The relation completeness check in hasFullRelation is too
strict because it requires both sides of the FK edge, which breaks implicit-to
relations. Update the logic in ast-to-print-document’s hasFullRelation so it
treats the FK-owning side as sufficient by keying completeness only on the
presence of from/fields, while still allowing to/references when explicitly
provided.
🪄 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: fbe45d1e-3b9b-4ccd-80e0-95802f33bb88
⛔ Files ignored due to path filters (6)
projects/psl-relation-syntax/README.mdis excluded by!projects/**projects/psl-relation-syntax/design-notes.mdis excluded by!projects/**projects/psl-relation-syntax/plan.mdis excluded by!projects/**projects/psl-relation-syntax/slices/01-from-to-fk-foundation/plan.mdis excluded by!projects/**projects/psl-relation-syntax/slices/01-from-to-fk-foundation/spec.mdis excluded by!projects/**projects/psl-relation-syntax/spec.mdis excluded by!projects/**
📒 Files selected for processing (14)
packages/1-framework/2-authoring/psl-parser/src/format/canonicalize-relation.tspackages/1-framework/2-authoring/psl-parser/src/format/format.tspackages/1-framework/2-authoring/psl-parser/test/format/relation-keywords.test.tspackages/1-framework/2-authoring/psl-printer/src/ast-to-print-document.tspackages/1-framework/2-authoring/psl-printer/test/print-psl-from-ast.test.tspackages/2-sql/2-authoring/contract-psl/src/interpreter.tspackages/2-sql/2-authoring/contract-psl/src/psl-relation-resolution.tspackages/2-sql/2-authoring/contract-psl/test/interpreter.relations.from-to.test.tspackages/2-sql/2-authoring/contract-psl/test/interpreter.relations.test.tspackages/2-sql/9-family/src/core/psl-contract-infer/sql-schema-ir-to-psl-ast.tspackages/2-sql/9-family/test/psl-contract-infer/print-psl/print-psl.naming-and-constraints.test.tspackages/2-sql/9-family/test/psl-contract-infer/print-psl/print-psl.relations.test.tspackages/2-sql/9-family/test/psl-contract-infer/print-psl/print-psl.single-dialect-gate.test.tspackages/2-sql/9-family/test/psl-contract-infer/sql-schema-ir-to-psl-ast.test.ts
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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/2-mongo-family/2-authoring/contract-psl/src/psl-helpers.ts`:
- Around line 136-153: The relation-argument parsing in `psl-helpers.ts`
currently lets multiple positional args overwrite `relationName`, so mixed forms
like `@relation(from: a, b, to: id)` can be accepted incorrectly. Update the
`relationAttr.args` handling to detect and reject more than one positional
relation-name arg, especially when mixed with named args, and emit a diagnostic
instead of assigning the later value. Add a test around the relation parsing
path that covers `relationName` handling in the `@relation` argument loop and
verifies the new diagnostic for multiple positional or mixed positional/named
arguments.
🪄 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: e337fb3d-7b78-4ae7-96f7-c8bb148ca470
⛔ Files ignored due to path filters (4)
packages/1-framework/3-tooling/cli/test/commands/init/__snapshots__/templates.test.ts.snapis excluded by!**/*.snapprojects/psl-relation-syntax/design-notes.mdis excluded by!projects/**projects/psl-relation-syntax/spec.mdis excluded by!projects/**projects/supabase-integration/example/prisma/schema.prismais excluded by!projects/**
📒 Files selected for processing (51)
docs/architecture docs/adrs/ADR 121 - Contract.d.ts structure and relation typing.mddocs/architecture docs/adrs/ADR 226 - Cross-contract foreign-key references.mddocs/architecture docs/subsystems/2. Contract Emitter & Types.mddocs/architecture docs/subsystems/6. Ecosystem Extensions & Packs.mdexamples/mongo-blog-leaderboard/src/contract.prismaexamples/mongo-demo/migrations/app/20260409T1030_migration/contract.prismaexamples/mongo-demo/migrations/app/20260415_add-posts-author-index/contract.prismaexamples/mongo-demo/src/contract.prismaexamples/prisma-next-cloudflare-worker/src/prisma/contract.prismaexamples/prisma-next-demo/migrations/app/20260422T0720_initial/contract.prismaexamples/prisma-next-demo/migrations/app/20260422T0742_migration/contract.prismaexamples/prisma-next-demo/migrations/app/20260422T0748_migration/contract.prismaexamples/prisma-next-demo/migrations/app/20260605T1145_mti_variant_link_columns/contract.prismaexamples/prisma-next-demo/migrations/app/20260610T0000_add_priority_enum/contract.prismaexamples/prisma-next-demo/migrations/app/20260610T2216_set_priority_default/contract.prismaexamples/prisma-next-demo/src/prisma/contract.prismaexamples/react-router-demo/src/prisma/contract.prismaexamples/retail-store/migrations/app/20260513T0505_initial/contract.prismaexamples/retail-store/migrations/app/20260513T0507_add_product_category_index/contract.prismaexamples/retail-store/migrations/app/20260513T0508_backfill_product_status/contract.prismaexamples/retail-store/src/contract.prismaexamples/supabase/src/contract.prismapackages/1-framework/3-tooling/cli/src/commands/init/templates/code-templates.tspackages/2-mongo-family/2-authoring/contract-psl/src/interpreter.tspackages/2-mongo-family/2-authoring/contract-psl/src/psl-helpers.tspackages/2-mongo-family/2-authoring/contract-psl/test/interpreter.test.tspackages/2-sql/2-authoring/contract-psl/README.mdpackages/2-sql/2-authoring/contract-psl/src/psl-relation-resolution.tspackages/2-sql/2-authoring/contract-psl/test/interpreter.diagnostics.test.tspackages/2-sql/2-authoring/contract-psl/test/interpreter.namespaces.test.tspackages/2-sql/2-authoring/contract-psl/test/interpreter.relations.from-to.test.tspackages/2-sql/2-authoring/contract-psl/test/interpreter.relations.many-to-many.test.tspackages/2-sql/2-authoring/contract-psl/test/interpreter.relations.test.tspackages/2-sql/2-authoring/contract-psl/test/interpreter.test.tspackages/2-sql/2-authoring/contract-psl/test/provider.test.tspackages/2-sql/2-authoring/contract-psl/test/psl-ts-namespace-parity.test.tspackages/2-sql/2-authoring/contract-psl/test/ts-psl-parity.test.tsskills/prisma-next-contract/SKILL.mdtest/integration/test/authoring/diagnostics/ambiguous-backrelation-list/schema.prismatest/integration/test/authoring/diagnostics/unsupported-navigation-list-attribute/schema.prismatest/integration/test/authoring/parity/callback-mode-scalars/schema.prismatest/integration/test/authoring/parity/core-surface/schema.prismatest/integration/test/authoring/parity/map-attributes/schema.prismatest/integration/test/authoring/parity/relation-backrelation-list/schema.prismatest/integration/test/authoring/side-by-side/mongo/contract.prismatest/integration/test/authoring/side-by-side/postgres/contract.prismatest/integration/test/cli.emit-command.additional.test.tstest/integration/test/fixtures/cli/cli-e2e-test-app/fixtures/cli-journeys/contract-composite-pk.prismatest/integration/test/fixtures/cli/cli-integration-test-app/fixtures/emit-command/schema.parity.psltest/integration/test/sql-orm-client/fixtures/mn-psl/contract.prismatest/integration/test/sql-orm-client/fixtures/polymorphism/contract.prisma
✅ Files skipped from review due to trivial changes (4)
- docs/architecture docs/subsystems/6. Ecosystem Extensions & Packs.md
- packages/2-sql/2-authoring/contract-psl/README.md
- docs/architecture docs/adrs/ADR 226 - Cross-contract foreign-key references.md
- skills/prisma-next-contract/SKILL.md
🚧 Files skipped from review as they are similar to previous changes (2)
- packages/2-sql/2-authoring/contract-psl/src/psl-relation-resolution.ts
- packages/2-sql/2-authoring/contract-psl/test/interpreter.relations.from-to.test.ts
| for (const arg of relationAttr.args) { | ||
| if (arg.kind === 'positional') { | ||
| relationName = stripQuotes(arg.value); | ||
| } else if (arg.name === 'name') { | ||
| relationName = stripQuotes(arg.value); | ||
| } else if (arg.name === 'fields') { | ||
| fieldsArg = arg; | ||
| } else if (arg.name === 'references') { | ||
| referencesArg = arg; | ||
| } else if (arg.name === 'fields' || arg.name === 'references') { | ||
| input.diagnostics.push({ | ||
| code: 'PSL_LEGACY_FIELDS_REFERENCES', | ||
| message: `Relation field "${input.modelName}.${input.fieldName}" uses @relation(fields:/references:), which is no longer supported — use from:/to: instead`, | ||
| sourceId: input.sourceId, | ||
| span: arg.span, | ||
| }); | ||
| return undefined; | ||
| } else if (arg.name === 'from') { | ||
| fromRaw = arg.value; | ||
| } else if (arg.name === 'to') { | ||
| toRaw = arg.value; | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Locate relation parser/helper tests and any coverage for unbracketed composite directional args.
rg -n -C3 '`@relation`\(from:\s*[A-Za-z_][A-Za-z0-9_]*\s*,\s*[A-Za-z_][A-Za-z0-9_]*' .
rg -n -C3 'composites must be bracketed|bare single from|to without a from|parseRelationFieldArgument' packages/2-mongo-family/2-authoring/contract-pslRepository: prisma/prisma-next
Length of output: 5882
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '108,210p' packages/2-mongo-family/2-authoring/contract-psl/src/psl-helpers.ts
printf '\n--- TESTS ---\n'
sed -n '690,760p' packages/2-mongo-family/2-authoring/contract-psl/test/interpreter.test.ts
printf '\n--- SQL TESTS ---\n'
sed -n '196,260p' packages/2-sql/2-authoring/contract-psl/test/interpreter.relations.from-to.test.tsRepository: prisma/prisma-next
Length of output: 7631
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the parser/AST shape for `@relation` arguments to see whether a bare second value can appear as a positional arg.
rg -n -C2 'kind: '"'"'positional'"'"'|name === '"'"'from'"'"'|name === '"'"'to'"'"'|parseRelationAttribute' packages/2-mongo-family/2-authoring/contract-psl packages/2-sql/2-authoring/contract-pslRepository: prisma/prisma-next
Length of output: 4805
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the attribute parsing types and any diagnostics around malformed relation args.
ast-grep outline packages/2-mongo-family/2-authoring/contract-psl/src --view expanded | sed -n '1,220p'
printf '\n--- INVALID ARG TESTS ---\n'
rg -n -C3 'PSL_INVALID_ATTRIBUTE_ARGUMENT|parseIdentifierExpr|unknown field|composites must be bracketed|from:' packages/2-mongo-family/2-authoring/contract-psl/test packages/2-mongo-family/2-authoring/contract-psl/srcRepository: prisma/prisma-next
Length of output: 24341
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Find the parser/grammar that constructs ResolvedAttribute args and whether commas can create positional args after named args.
rg -n -C3 'type ResolvedAttribute|interface ResolvedAttribute|kind: '"'"'positional'"'"'|name: .*from|attribute.*args|parse.*attribute' packages/2-mongo-family/2-authoring/contract-psl/src packages/2-mongo-family/2-authoring/contract-psl/testRepository: prisma/prisma-next
Length of output: 156
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the attribute AST/parser types to see how mixed positional and named args are represented.
fd -a '.*' packages/2-mongo-family/2-authoring/contract-psl/src packages/2-mongo-family/2-authoring/contract-psl/test | sed -n '1,200p'
printf '\n--- ResolvedAttribute / parser references ---\n'
rg -n -C3 'interface ResolvedAttribute|type ResolvedAttribute|kind: '"'"'positional'"'"'|kind: '"'"'named'"'"'|args:' packages/2-mongo-family/2-authoring/contract-psl/src packages/2-mongo-family/2-authoring/contract-psl/testRepository: prisma/prisma-next
Length of output: 1206
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the attribute parser/type definitions that produce ResolvedAttribute.args.
rg -n -C3 'ResolvedAttribute|ResolvedAttributeArg|kind: '"'"'positional'"'"'|kind: '"'"'named'"'"'|parse.*Attribute|attribute parser|args' packages . --glob '!**/dist/**' --glob '!**/build/**'Repository: prisma/prisma-next
Length of output: 50375
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C2 'interface ResolvedAttributeArg|type ResolvedAttributeArg|ResolvedAttributeArg =' . \
--glob '!**/dist/**' --glob '!**/build/**' --glob '!**/*.map' \
| head -n 120Repository: prisma/prisma-next
Length of output: 632
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Narrow to parser sources only if the type is re-exported from there.
rg -n -C2 'ResolvedAttributeArg|ResolvedAttribute' packages -g '*psl-parser*' -g '*parser*' --glob '!**/dist/**' --glob '!**/build/**' | head -n 120Repository: prisma/prisma-next
Length of output: 156
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n -C2 'ResolvedAttributeArg|ResolvedAttribute' packages -g '*psl-parser*' -g '*parser*' --glob '!**/dist/**' --glob '!**/build/**' | head -n 120Repository: prisma/prisma-next
Length of output: 156
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1,240p' packages/1-framework/2-authoring/psl-parser/src/resolve.ts
printf '\n--- ATTRIBUTE PARSE SITES ---\n'
rg -n -C3 'ResolvedAttributeArg|positional|named|splitTopLevel|attribute args|parse.*attribute' packages/1-framework/2-authoring/psl-parser/src packages/2-mongo-family/2-authoring/contract-psl/src packages/2-sql/2-authoring/contract-psl/src | head -n 200Repository: prisma/prisma-next
Length of output: 23664
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Search for grammar/tests covering mixed positional and named attribute args.
rg -n -C3 '`@relation`\(from:\s*[^,\]]+,\s*[^,\]]+|from:\s*[^,\]]+,\s*[^,\]]+,\s*to:|named.*positional|positional.*named|multiple arguments|single field may be bare' packages/1-framework/2-authoring/psl-parser packages/2-mongo-family/2-authoring/contract-psl packages/2-sql/2-authoring/contract-psl | head -n 200Repository: prisma/prisma-next
Length of output: 15191
Reject mixed positional relation-name args
relationName is overwritten by every positional arg, so @relation(from: a, b, to: id) can treat b as the relation name instead of failing. Add a diagnostic/test for multiple positional args or mixed positional + named relation arguments.
🤖 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/src/psl-helpers.ts` around
lines 136 - 153, The relation-argument parsing in `psl-helpers.ts` currently
lets multiple positional args overwrite `relationName`, so mixed forms like
`@relation(from: a, b, to: id)` can be accepted incorrectly. Update the
`relationAttr.args` handling to detect and reject more than one positional
relation-name arg, especially when mixed with named args, and emit a diagnostic
instead of assigning the later value. Add a test around the relation parsing
path that covers `relationName` handling in the `@relation` argument loop and
verifies the new diagnostic for multiple positional or mixed positional/named
arguments.
| // Canonical `from:`/`to:` declare the FK dependency edge; legacy | ||
| // `fields:`/`references:` are accepted as an input alias for the same edge. | ||
| const hasFrom = named['from'] !== undefined || named['fields'] !== undefined; | ||
| const hasTo = named['to'] !== undefined || named['references'] !== undefined; | ||
| return hasFrom && hasTo; |
There was a problem hiding this comment.
Why does this still have legacy names?
| span: span(0), | ||
| }; | ||
| const printed = printPslFromAst(ast); | ||
| expect(printed).toContain('@relation(from: [authorId], to: [id])'); |
There was a problem hiding this comment.
Why does it still have square brackets for the single-field case? Or is that implemented in the following PRs next in the stack?
| let references = relation?.references; | ||
| if (relation?.fields && relation.referencesInferred) { |
There was a problem hiding this comment.
Should fields,references and referencesInferred be renamed to fromFields, toFields and toFieldsInferred in this data structure?
| declaringModel: pslModel.name, | ||
| fieldName: field.name, | ||
| targetModel: field.typeName, | ||
| ...(relation.relationName !== undefined ? { relationName: relation.relationName } : {}), |
| /** | ||
| * Parses a single `@relation` directional argument value (`from:`/`to:`). A | ||
| * single field may be bare (`from: userId`) or bracketed (`from: [userId]`); | ||
| * composites must be bracketed (`from: [a, b]`). | ||
| */ | ||
| function parseRelationFieldArgument(raw: string): readonly string[] | undefined { | ||
| const trimmed = raw.trim(); | ||
| const entries = trimmed.startsWith('[') ? parseFieldList(trimmed) : [trimmed]; | ||
| if (entries.length === 0 || entries.some((entry) => entry.length === 0)) { | ||
| return undefined; | ||
| } | ||
| return entries; | ||
| } |
There was a problem hiding this comment.
How come we have to care about this here? Parsing must happen in the parser. We use AST on this level, not raw strings.
tensordreams
left a comment
There was a problem hiding this comment.
Reviewed this slice. I did not find an additional distinct blocking issue beyond the existing unresolved threads already on this PR, mainly around the remaining legacy naming/parser-shape cleanup in the from/to migration.
c8e0273 to
94f373d
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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
`@examples/mongo-demo/migrations/app/20260626T1605_add_user_role_enum/contract.prisma`:
- Around line 3-8: The migration snapshot uses a Prisma enum for `UserRole`, but
`contract.prisma` in Prisma Next MongoDB does not support enums. Replace the
`UserRole` enum definition with a `String` field for `role` in the affected
model, and document the permitted values (`admin`, `author`, `reader`) in the
contract instead of encoding them as an enum. Locate the change by the
`UserRole` symbol and the `role` field in the migration snapshot.
In
`@examples/mongo-demo/migrations/app/20260626T1916_add_posts_indexes/contract.prisma`:
- Around line 3-8: Remove the unsupported UserRole enum from the contract.prisma
snapshot and keep role modeled as a String field in the relevant schema instead.
Update the Prisma Next Mongo example so contract.prisma only uses supported
Mongo snapshot shapes, and if needed document the allowed role values outside
the schema rather than via an enum. Use the UserRole symbol in this snapshot to
locate and replace the enum definition.
🪄 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: da270d36-bc55-4651-99a4-72c9d08dd6f0
⛔ Files ignored due to path filters (1)
packages/1-framework/3-tooling/cli/test/commands/init/__snapshots__/templates.test.ts.snapis excluded by!**/*.snap
📒 Files selected for processing (22)
docs/architecture docs/adrs/ADR 121 - Contract.d.ts structure and relation typing.mddocs/architecture docs/adrs/ADR 226 - Cross-contract foreign-key references.mddocs/architecture docs/subsystems/2. Contract Emitter & Types.mddocs/architecture docs/subsystems/6. Ecosystem Extensions & Packs.mdexamples/mongo-blog-leaderboard/src/contract.prismaexamples/mongo-demo/migrations/app/20260409T1030_migration/contract.prismaexamples/mongo-demo/migrations/app/20260626T1605_add_user_role_enum/contract.prismaexamples/mongo-demo/migrations/app/20260626T1916_add_posts_indexes/contract.prismaexamples/mongo-demo/src/contract.prismaexamples/prisma-next-cloudflare-worker/src/prisma/contract.prismaexamples/prisma-next-demo/migrations/app/20260422T0720_initial/contract.prismaexamples/prisma-next-demo/src/prisma/contract.prismaexamples/react-router-demo/src/prisma/contract.prismaexamples/retail-store/migrations/app/20260513T0505_initial/contract.prismaexamples/retail-store/migrations/app/20260513T0507_add_product_category_index/contract.prismaexamples/retail-store/migrations/app/20260513T0508_backfill_product_status/contract.prismaexamples/retail-store/migrations/app/20260628T0931_add_product_status_order_type_enums/contract.prismaexamples/retail-store/src/contract.prismaexamples/supabase/src/contract.prismapackages/1-framework/2-authoring/psl-printer/src/ast-to-print-document.tspackages/1-framework/2-authoring/psl-printer/test/print-psl-from-ast.test.tspackages/1-framework/3-tooling/cli/src/commands/init/templates/code-templates.ts
💤 Files with no reviewable changes (11)
- packages/1-framework/3-tooling/cli/src/commands/init/templates/code-templates.ts
- examples/react-router-demo/src/prisma/contract.prisma
- examples/supabase/src/contract.prisma
- examples/retail-store/migrations/app/20260513T0508_backfill_product_status/contract.prisma
- examples/retail-store/migrations/app/20260513T0505_initial/contract.prisma
- examples/prisma-next-demo/src/prisma/contract.prisma
- packages/1-framework/2-authoring/psl-printer/test/print-psl-from-ast.test.ts
- examples/retail-store/migrations/app/20260628T0931_add_product_status_order_type_enums/contract.prisma
- examples/retail-store/src/contract.prisma
- packages/1-framework/2-authoring/psl-printer/src/ast-to-print-document.ts
- examples/retail-store/migrations/app/20260513T0507_add_product_category_index/contract.prisma
✅ Files skipped from review due to trivial changes (6)
- examples/mongo-blog-leaderboard/src/contract.prisma
- docs/architecture docs/subsystems/6. Ecosystem Extensions & Packs.md
- docs/architecture docs/subsystems/2. Contract Emitter & Types.md
- docs/architecture docs/adrs/ADR 226 - Cross-contract foreign-key references.md
- examples/prisma-next-demo/migrations/app/20260422T0720_initial/contract.prisma
- docs/architecture docs/adrs/ADR 121 - Contract.d.ts structure and relation typing.md
🚧 Files skipped from review as they are similar to previous changes (3)
- examples/mongo-demo/src/contract.prisma
- examples/mongo-demo/migrations/app/20260409T1030_migration/contract.prisma
- examples/prisma-next-cloudflare-worker/src/prisma/contract.prisma
There was a problem hiding this comment.
Caution
Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.
Actionable comments posted: 2
🤖 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
`@examples/mongo-demo/migrations/app/20260626T1605_add_user_role_enum/contract.prisma`:
- Around line 3-8: The migration snapshot uses a Prisma enum for `UserRole`, but
`contract.prisma` in Prisma Next MongoDB does not support enums. Replace the
`UserRole` enum definition with a `String` field for `role` in the affected
model, and document the permitted values (`admin`, `author`, `reader`) in the
contract instead of encoding them as an enum. Locate the change by the
`UserRole` symbol and the `role` field in the migration snapshot.
In
`@examples/mongo-demo/migrations/app/20260626T1916_add_posts_indexes/contract.prisma`:
- Around line 3-8: Remove the unsupported UserRole enum from the contract.prisma
snapshot and keep role modeled as a String field in the relevant schema instead.
Update the Prisma Next Mongo example so contract.prisma only uses supported
Mongo snapshot shapes, and if needed document the allowed role values outside
the schema rather than via an enum. Use the UserRole symbol in this snapshot to
locate and replace the enum definition.
🪄 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: da270d36-bc55-4651-99a4-72c9d08dd6f0
⛔ Files ignored due to path filters (1)
packages/1-framework/3-tooling/cli/test/commands/init/__snapshots__/templates.test.ts.snapis excluded by!**/*.snap
📒 Files selected for processing (22)
docs/architecture docs/adrs/ADR 121 - Contract.d.ts structure and relation typing.mddocs/architecture docs/adrs/ADR 226 - Cross-contract foreign-key references.mddocs/architecture docs/subsystems/2. Contract Emitter & Types.mddocs/architecture docs/subsystems/6. Ecosystem Extensions & Packs.mdexamples/mongo-blog-leaderboard/src/contract.prismaexamples/mongo-demo/migrations/app/20260409T1030_migration/contract.prismaexamples/mongo-demo/migrations/app/20260626T1605_add_user_role_enum/contract.prismaexamples/mongo-demo/migrations/app/20260626T1916_add_posts_indexes/contract.prismaexamples/mongo-demo/src/contract.prismaexamples/prisma-next-cloudflare-worker/src/prisma/contract.prismaexamples/prisma-next-demo/migrations/app/20260422T0720_initial/contract.prismaexamples/prisma-next-demo/src/prisma/contract.prismaexamples/react-router-demo/src/prisma/contract.prismaexamples/retail-store/migrations/app/20260513T0505_initial/contract.prismaexamples/retail-store/migrations/app/20260513T0507_add_product_category_index/contract.prismaexamples/retail-store/migrations/app/20260513T0508_backfill_product_status/contract.prismaexamples/retail-store/migrations/app/20260628T0931_add_product_status_order_type_enums/contract.prismaexamples/retail-store/src/contract.prismaexamples/supabase/src/contract.prismapackages/1-framework/2-authoring/psl-printer/src/ast-to-print-document.tspackages/1-framework/2-authoring/psl-printer/test/print-psl-from-ast.test.tspackages/1-framework/3-tooling/cli/src/commands/init/templates/code-templates.ts
💤 Files with no reviewable changes (11)
- packages/1-framework/3-tooling/cli/src/commands/init/templates/code-templates.ts
- examples/react-router-demo/src/prisma/contract.prisma
- examples/supabase/src/contract.prisma
- examples/retail-store/migrations/app/20260513T0508_backfill_product_status/contract.prisma
- examples/retail-store/migrations/app/20260513T0505_initial/contract.prisma
- examples/prisma-next-demo/src/prisma/contract.prisma
- packages/1-framework/2-authoring/psl-printer/test/print-psl-from-ast.test.ts
- examples/retail-store/migrations/app/20260628T0931_add_product_status_order_type_enums/contract.prisma
- examples/retail-store/src/contract.prisma
- packages/1-framework/2-authoring/psl-printer/src/ast-to-print-document.ts
- examples/retail-store/migrations/app/20260513T0507_add_product_category_index/contract.prisma
✅ Files skipped from review due to trivial changes (6)
- examples/mongo-blog-leaderboard/src/contract.prisma
- docs/architecture docs/subsystems/6. Ecosystem Extensions & Packs.md
- docs/architecture docs/subsystems/2. Contract Emitter & Types.md
- docs/architecture docs/adrs/ADR 226 - Cross-contract foreign-key references.md
- examples/prisma-next-demo/migrations/app/20260422T0720_initial/contract.prisma
- docs/architecture docs/adrs/ADR 121 - Contract.d.ts structure and relation typing.md
🚧 Files skipped from review as they are similar to previous changes (3)
- examples/mongo-demo/src/contract.prisma
- examples/mongo-demo/migrations/app/20260409T1030_migration/contract.prisma
- examples/prisma-next-cloudflare-worker/src/prisma/contract.prisma
🛑 Comments failed to post (2)
examples/mongo-demo/migrations/app/20260626T1605_add_user_role_enum/contract.prisma (1)
3-8: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Replace the Mongo enum with a string field.
Prisma Next Mongo
contract.prismafiles still do not support enums, soUserRole/role UserRolemakes this migration snapshot invalid. KeeproleasStringand document the allowed values instead.Suggested fix
-enum UserRole { - @@type("mongo/string@1") - Admin = "admin" - Author = "author" - Reader = "reader" -} - model User { id ObjectId `@id` `@map`("_id") name String email String bio String? - role UserRole + // Allowed values: "admin" | "author" | "reader" + role String address Address? posts Post[] @@map("users") }Based on learnings, in Prisma Next MongoDB
contract.prismafiles Prisma enums are not supported yet; constrained values should stay asStringfields with documented allowed values.Also applies to: 22-22
🤖 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 `@examples/mongo-demo/migrations/app/20260626T1605_add_user_role_enum/contract.prisma` around lines 3 - 8, The migration snapshot uses a Prisma enum for `UserRole`, but `contract.prisma` in Prisma Next MongoDB does not support enums. Replace the `UserRole` enum definition with a `String` field for `role` in the affected model, and document the permitted values (`admin`, `author`, `reader`) in the contract instead of encoding them as an enum. Locate the change by the `UserRole` symbol and the `role` field in the migration snapshot.Source: Learnings
examples/mongo-demo/migrations/app/20260626T1916_add_posts_indexes/contract.prisma (1)
3-8: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Avoid Prisma enums in Mongo
contract.prismasnapshots.This file has the same unsupported
UserRoleenum shape. In Prisma Next Mongo schemas,roleneeds to remain aString; otherwise this example snapshot is not valid.Based on learnings, in Prisma Next MongoDB
contract.prismafiles Prisma enums are not supported yet; constrained values should stay asStringfields with documented allowed values.Also applies to: 22-22
🤖 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 `@examples/mongo-demo/migrations/app/20260626T1916_add_posts_indexes/contract.prisma` around lines 3 - 8, Remove the unsupported UserRole enum from the contract.prisma snapshot and keep role modeled as a String field in the relevant schema instead. Update the Prisma Next Mongo example so contract.prisma only uses supported Mongo snapshot shapes, and if needed document the allowed role values outside the schema rather than via an enum. Use the UserRole symbol in this snapshot to locate and replace the enum definition.Source: Learnings
94f373d to
5bfb3ea
Compare
tensordreams
left a comment
There was a problem hiding this comment.
Slice 1 lands the canonical @relation(from:, to:) FK vocabulary end-to-end (SQL + Mongo resolver read with to:-inference, contract-infer printer, CLI templates, psl-printer topological detection) and migrates the repo's schemas. The implementation is sound: legacy fields:/references: is rejected with a guiding PSL_LEGACY_FIELDS_REFERENCES diagnostic in both family resolvers, to:-omission infers the target @id (single + composite), bare-vs-bracketed value forms lower identically, and there are good legacy-rejection / inference / single-dialect-gate tests. No any, bare-as-in-prod, or extension-in-import violations. The substantive issues are doc-staleness: the slice spec/plan/README still describe the rejected "legacy-as-input-alias + backward-compat byte-identical invariant + formatter rewrite" design (the design-notes D1 reversal in this same PR contradicts them), and the parseRelationFieldArgument docstring advertises a Model.-qualifier tolerance that the regression test disproves.
|
|
||
| ## At a glance | ||
|
|
||
| PSL foreign-key relations gain the canonical directional vocabulary `@relation(from:, to:)`. Legacy `@relation(fields:, references:)` keeps parsing as an **input alias** that lowers to the same contract; the toolchain only ever **emits** the canonical spelling. This slice is FK-only (1:N / N:1) — `through:`/`inverse:`/M:N are later slices. Its headline is the **backward-compat invariant**: legacy and canonical spellings lower to byte-identical contracts. |
There was a problem hiding this comment.
[doc] This slice spec still describes the rejected design: "Legacy fields:/references: keeps parsing as an input alias" and the headline "backward-compat invariant: legacy and canonical spellings lower to byte-identical contracts". The implementation does a clean break — both resolvers reject legacy with PSL_LEGACY_FIELDS_REFERENCES (matching projects/psl-relation-syntax/design-notes.md D1 reversal and the project spec.md). The slice done-conditions (line 50 byte-identical legacy-vs-canonical; line 52 formatter idempotence on a legacy input) are untestable as written, and §Scope (line 32) still lists "CST format keyword-only canonicalisation" which isn't implemented (and is unnecessary under clean break). Please rewrite this spec/plan to the clean-break shape so the slice's stated DoD matches what landed.
| # PSL relation syntax | ||
|
|
||
| Rework PSL's relation declaration to a directional `from:`/`to:`/`through:` vocabulary, | ||
| backward-compatible with `fields:`/`references:` (old spelling still parses; the PSL |
There was a problem hiding this comment.
[doc] Stale framing: "backward-compatible with fields:/references: (old spelling still parses; the PSL formatter rewrites it to the canonical form)". Under the D1 clean break landed in this slice, legacy does not parse and the formatter rewrite was dropped (see design-notes.md). Update to the directional-only/reject-legacy description.
| * single field may be bare (`from: userId`) or bracketed (`from: [userId]`); | ||
| * composites must be bracketed (`from: [a, b]`). | ||
| * | ||
| * A redundant model qualifier on `to:` (`to: Post.id`) is stripped to its bare |
There was a problem hiding this comment.
[doc] This docstring is internally contradictory and disagrees with the regression test. Line 54 claims to: Post.id is "stripped to its bare column name so it lowers identically to the unqualified spelling", but lines 57-58 then say the grammar drops .id and to: Post.id reaches the resolver as Post — so stripModelQualifier receives Post (no dot) and returns Post, not id. The test rejects a member-access to: value confirms to: User.id is rejected (unknown field "User.User"), not tolerated. design-notes.md's "tolerated and preserved verbatim" is also wrong here. Either drop the tolerance claim (and the dead lastIndexOf('.') branch, which only fires for the untested bracketed [Post.id] form) or add a test pinning the bracketed form's behaviour and reconcile the wording.
| } | ||
|
|
||
| // An omitted `to:` references the target model's `@id`; an explicit | ||
| // `to:`/`references:` names the referenced fields directly. |
There was a problem hiding this comment.
[nit] Comment says "an explicit to:/references: names the referenced fields directly", but legacy references: is now rejected by parseRelationAttribute with PSL_LEGACY_FIELDS_REFERENCES. Drop the references: mention so the comment reflects that only to: is a valid explicit form.
Scaffold the PSL Directional Relation Syntax project (sibling of sql-orm-many-to-many). Captures the settled grammar design (decisions D1-D5 from drive-discussion): directional from/to/through/inverse relation vocabulary, backward-compatible with fields/references/name (legacy input-only; toolchain emits canonical), with explicit junction naming, pointer disambiguation retiring @relation(name:), implicit M:N synthesis, and an arrow-path form. Linear: TML-2939 (plan anchor); slices TML-2940..2944. Signed-off-by: Alexey Orlenko's AI Agent <agent@prisma.io> Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…oundation) Signed-off-by: Alexey Orlenko's AI Agent <agent@prisma.io> Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
The PSL resolver reads the canonical directional `@relation(from:, to:)`
arguments for foreign-key relations (1:N / N:1). Legacy `fields:`/`references:`
are accepted as input aliases that lower to the identical resolved relation, so
a schema authored either way produces a byte-identical contract.
- `from:`/`to:` parse as generic named args (no grammar change); legacy keys
alias to the same `{ fields, references }` result.
- `to:` may be omitted: the referenced columns default to the target model's
`@id` (inline `@id` or `@@id([...])`), resolved at the FK call site which
holds the target model. Omitting `to:` against a target with no `@id` is an
error.
- A single field is bare (`from: userId`) or bracketed (`from: [userId]`);
composites must be bracketed. `parseRelationFieldArgument` handles both
without weakening the shared `parseFieldList` bracket contract used by
`@@id`/`@@unique`/`@@index`.
- The both-or-neither diagnostic is adapted: `from:` may stand alone (references
inferred); a `to:` (or legacy `references:`) without a `from:` stays an error.
FK relations only — `through:`/`inverse:`/M:N and `@relation(name:)` are
untouched.
Scope note: the spec's redundant-`Model.`-qualifier-on-`to:` tolerance is
deferred. The PSL expression grammar does not carry a member-access argument
value — `parseIdentifierExpr` consumes only the head identifier, so `to: Post.id`
reaches the resolver as `Post` (the `.id` is silently dropped, no diagnostic).
Accepting the qualifier requires a grammar change in @prisma-next/psl-parser,
out of this contract-psl-scoped dispatch. The resolver's qualifier strip is in
place for when the grammar carries the dotted value; a test pins the present
grammar boundary as a regression anchor.
Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
… (I12) M1 found the PSL expression grammar has no member-access value form, so 'to: Post.id' silently drops '.id'. The qualifier is redundant and the member-access grammar is shared with S5 arrow-path; deferred there. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
The CST formatter rewrites legacy @relation argument keys fields->from and references->to, touching the keyword token only. Argument values, brackets, the name: argument, and all comments/trivia are preserved; column alignment is untouched. The rename is a green-tree pre-pass (canonicalizeRelationKeywords): it walks the parsed green tree, rebuilds only the affected @relation arg-key tokens, and carries every other subtree by reference so emitDocument stays generic and trivia is preserved structurally. The rename is scoped to @relation / @@relation attribute argument keys by inspecting the attribute name, so a fields field, a fields argument on @@index, or a @relation(name:) all survive unchanged. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
The @relation FK-keyword canonicalisation bound the rename to the first identifier matching a rename target rather than to the key position, so a bare single-field value spelled `fields`/`references` was rewritten when the key did not itself migrate (e.g. `@relation(from: references, to: id)` became `@relation(from: to, to: id)`). Bind the rename to the key by position: an AttributeArg key exists only when the arg carries a Colon, and is then its first Identifier child. Consider only that identifier and stop, so the value position is never touched. Bracketed values were already safe; this closes the bare-value collision. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
The IR->PSL-AST printer used by `contract infer` now renders the canonical directional vocabulary `@relation(from:, to:)` for FK relations instead of the legacy `fields:`/`references:` keys, keeping the explicit bracketed form (no aggressive inference) and leaving the relationName `name:` argument untouched. The psl-printer's FK-dependency topological sort reads the relation keys to order models (referenced model before the FK owner). Teach `hasFullRelation` to recognise the canonical `from`/`to` keys, with the legacy keys accepted as an input alias, so the dependency edge is still detected and model ordering stays stable. Add a single-dialect gate asserting `contract infer` output carries no legacy `fields:`/`references:` relation keys and does carry `from:`/`to:`, and a printer regression covering canonical-key dependency ordering. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…rom/to only `from`/`to` is now the only FK relation syntax. The resolver reads only `from:`/`to:`, drops `fields`/`references` from the @relation argument allow-list, and emits PSL_LEGACY_FIELDS_REFERENCES when a relation carries `fields:`/`references:` — a clear, actionable rejection pointing at `from:`/`to:`. The psl-parser formatter no longer rewrites relation keywords: the S1·M2 canonicalisation (`canonicalize-relation.ts`) and its test are removed, and `format` reverts to `parse → emitDocument`. S1's from/to unit tests replace the legacy≡canonical equivalence cases with a rejection-diagnostic test (plus lone-`fields:`/lone-`references:` variants), keeping the `from`/`to` inference, value-form, both-or-neither, and self-referential coverage. The member-access `to:` test now pins the actual behaviour: the qualifier drops at the grammar layer, so `to:` is treated as omitted and the target `@id` is inferred. Migrating the remaining legacy `.prisma` and test schemas is a separate dispatch (S1b); the full repo suite is red until then. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…rom/to S1a taught the SQL PSL resolver to accept only @relation(from:, to:) and to reject the legacy fields:/references: spelling. Migrate every SQL-family authored schema, integration fixture, and inline-PSL test to the from/to vocabulary so they parse again. from/to lowers byte-identically to fields/references, so only .prisma source spelling changes — no contract.json, migration metadata, or hash drift (verified via fixtures:emit + migrations:regen:examples producing zero artifact diff). Mongo-family schemas keep fields/references: the Mongo interpreter speaks only that vocabulary and would drop the relation under from/to. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…mber-access grammar The `to: User.id` case is RED on a fresh tml-2940 build: the member-access value grammar that makes `Foo.bar` parse as a qualified argument value arrives in a later slice and is absent here. Without it, `parseIdentifierExpr` reads only the head identifier `User`, the resolver looks for a column `User` on the target model `User`, finds none, and rejects the relation with PSL_INVALID_ATTRIBUTE_ARGUMENT. Assert that real behaviour (failure + the unknown-field diagnostic) instead of the inherited 'qualifier dropped, @id inferred' assertion, which only held against a stale dist still carrying the grammar. The case stays present as the anchor a later slice rewrites once the dotted value is supported. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…ferences S1a/S1b taught the SQL PSL resolver to accept only @relation(from:, to:). The Mongo family has its own PSL relation parser (contract-psl/psl-helpers.ts, parseRelationAttribute) that read only fields:/references: and ignored from:/to:, so Mongo schemas were left on the legacy vocabulary. Mirror the SQL change in the Mongo interpreter: - parseRelationAttribute now reads from:/to:, rejects fields:/references: with the PSL_LEGACY_FIELDS_REFERENCES diagnostic (the same open-string code SQL uses), and supports bare or bracketed values. Omitting to: infers the referenced column(s) from the target model's @id (resolveTargetIdFieldNames); a to: without a from: is rejected. The interpreter caller resolves the inferred references against the target model and routes legacy/invalid relations away from backrelation handling. - Migrate every Mongo-family authored schema, migration snapshot, inline-PSL test, and the Mongo CLI init template from fields:/references: to from:/to:. from/to lowers byte-identically to fields/references, so only .prisma source spelling changes — no contract.json, migration metadata, or hash drift (verified: migrations:regen:examples re-parses the migrated Mongo snapshots and re-emits the start/end contracts with zero artifact diff). @relation(name:) is untouched. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…rences:) in .psl/.ts/.md to from/to Finish the from/to migration for non-.prisma sites the .prisma-focused passes missed: the SQL emit-command parity .psl fixture, the Postgres PSL init template string (and its snapshot), and live authoring examples in the SQL contract-psl README, the prisma-next-contract skill, two subsystem docs, and ADR 121 / ADR 226 worked examples. Deliberate legacy sites are left intact: resolver rejection tests and diagnostic strings, parser/printer fidelity fixtures that never reach the resolver, frozen release/upgrade history (CHANGELOG, releases, upgrade recipe), project design records under projects/, and ADR 226 prose explaining the legacy attribute grammar. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…legacy, no format rewrite D1 reversed (operator decision, 2026-06-26): the directional vocabulary (from/to/through/inverse) is the only accepted @relation syntax. Legacy fields:/references: and @relation(name:) are rejected at parse time with a guiding diagnostic. The format canonicalisation pass was removed. The repo migration is byte-identical; a downstream codemod is deferred (TML-2957). Updates design-notes.md (D1, D3, principles, alternatives, trade-offs) and spec.md (at-a-glance, non-goals, contract-impact, cross-cutting requirements, transitional-shape, project DoD, open questions). Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
Two alignments with changes that landed on main after this branch was cut: - The described-contracts infer test (main #919) asserts printed relation output; this branch retires @relation(fields:/references:) from the infer printer, so the expectation moves to the canonical from:/to: keys. - The single-dialect gate test imported sql-schema-ir-to-psl-ast, which the schema-node tree restructure (main #894) dissolved; it now drives inference through the shared printPslFromFlat fixture helper. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…tput The cross-space FK infer test asserts printed @relation output; the infer printer emits canonical from:/to: keys on this branch, so the expectation and the doc comment move off the legacy fields:/references: spelling. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
5bfb3ea to
e989680
Compare
Linked issue
Refs TML-2940. First of a five-PR stack for the PSL: Directional Relation Syntax project (plan anchor TML-2939); this one bases on
main. Spec / design notes underprojects/psl-relation-syntax/.At a glance
Legacy
@relation(fields:, references:)is rejected at parse time with aPSL_LEGACY_FIELDS_REFERENCESdiagnostic directing authors tofrom:/to:. Theformatcommand no longer rewrites relation keywords (it cannot operate on now-invalid syntax). The repo's own schemas — SQL and Mongo families, including examples, fixtures, and tests — are migrated tofrom:/to:byte-identically (fixtures:checkzero drift). A reusable downstream codemod is deferred (TML-2957).Summary
PSL foreign-key relations gain a directional
@relation(from:, to:)vocabulary — the sole accepted syntax. Legacyfields:/references:is rejected at parse time in both the SQL and Mongo family resolvers. Thecontract inferAST printer emits the canonical spelling. The repo's ~40 legacy sites (SQL + Mongo) are migrated in-stack; migration-history snapshots re-emit byte-identically (the migration hash is overmigration.json/ops.json, not the.prismasource).Decision
Ship
from/toas the only FK relation syntax — read, print — with legacyfields:/references:rejected at parse time. Three faces:from/towith inference: omitto:⇒ the target's@id; a single field is bare, composites bracketed. Legacyfields:/references:produces aPSL_LEGACY_FIELDS_REFERENCESdiagnostic.contract inferemitsfrom/to(theformatrewrite pass was built and then removed when D1 was reversed —formatdoes layout only, no relation-keyword rewrite).from/to(SQL and Mongo). A coupled topological-ordering surface in@prisma-next/psl-printer(hasFullRelation) was taught the canonical keys so inferred-model ordering survives.The
@relationattribute grammar is already generic (named args scanned by string), so no parser/grammar change to accept the new keywords.How it fits together
packages/2-sql/2-authoring/contract-psl/src/psl-relation-resolution.tsandpackages/2-mongo-family/2-authoring/contract-psl/src/psl-helpers.ts) readsfrom/to; legacyfields/referencesis rejected withPSL_LEGACY_FIELDS_REFERENCES. Omit-to:resolves the target@idat the call site (which holds the target model).packages/2-sql/9-family/src/core/psl-contract-infer/sql-schema-ir-to-psl-ast.ts) emits canonical keys;hasFullRelationin@prisma-next/psl-printerwas taught the canonical keys so the FK dependency edge for topological model ordering survives.fields:/references:→from:/to:, a symmetric keyword swap. Migration-history snapshots re-emit byte-identically.Behavior changes & evidence
@relation(from:, to:)authors FK relations;to:inference from the target@id.packages/2-sql/2-authoring/contract-psl/src/psl-relation-resolution.ts— evidence:packages/2-sql/2-authoring/contract-psl/test/interpreter.relations.from-to.test.ts(assertstoEqualon the emittedContract).fields:/references:rejected in both resolvers.psl-relation-resolution.ts(SQL) andpsl-helpers.ts(Mongo) — evidence:interpreter.relations.from-to.test.ts(the rejection diagnostic is asserted).contract inferemits canonical; no legacy keys survive.sql-schema-ir-to-psl-ast.ts— evidence:packages/2-sql/9-family/test/psl-contract-infer/print-psl/print-psl.single-dialect-gate.test.ts.fixtures:checkshows zero contract drift across all migrated schemas (SQL + Mongo).Notes for the reviewer
formatrewrite) and the rework (legacy rejected,formatremoved) — the built-then-deleted formatter commit (feat(psl-parser): canonicalise @relation FK keywords on format) is in the history. Consider squashing at merge so only the final state lands.hasFullRelationchange inpsl-printeris the one cross-package touch; without it, renaming the printer's keys silently drops the FK dependency edge used for topological model ordering.Model.qualifier onto:(to: Post.id) works as of S3 (member-access value grammar); on S1 alone, it's bare-column only (the test assert the S1 boundary).Testing performed
pnpm --filter @prisma-next/sql-contract-psl test·@prisma-next/psl-parser test·@prisma-next/family-sql test·@prisma-next/mongo-family test— green.pnpm typecheck:packages(129/129),pnpm --filter -next/sql-contract-psl test(23 files, 314 tests),pnpm fixtures:check(zero drift), andpnpm lint:deps(0 violations).Skill update
This PR introduces a breaking change to PSL syntax (
fields:/references:→from/to), affectingpackages/3-extensions/and all downstream PSL authors.check:upgrade-coverageexpects achanges:declaration in the in-flight upgrade cycle — flagged as a stack-wide follow-up (the PRs touchingpackages/3-extensions/share it). A user-facing docs note and the deferred codemod (TML-2957) are project close-out items.Checklist
from/toFK foundation + clean break).TML-NNNN: …form.Summary by CodeRabbit