feat(contract-ts): a cross-space relation never guesses its target table; a foreign key to an unreadable one is an authoring error - #30323
Conversation
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…must not fabricate a lowercased target table A cross-space handle whose .sql() stage is a factory function carries no statically readable table name. The relation lowering currently fills the gap with modelName.toLowerCase(), so OrderItem becomes orderitem, a case transform the DSL offers nowhere else. This test is red until the lowering leaves the target table unset. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…e leaves the table unset instead of lowercasing the model name The TS DSL never transforms identifier case, yet the cross-space relation lowering filled a missing target table with modelName.toLowerCase(), turning OrderItem into orderitem. The relation node now leaves toTable and on.childTable undefined for a cross-space handle whose .sql() stage is a factory function; the planner resolves the table from the remote contract, exactly as it already does for the field-to-column mapping. RelationNode types both fields as string | undefined, and the one reader in build-contract.ts checks the table only when it is known. Local relations are unchanged. The cross-space foreign-key lowering had the same lowercase fallback. Its target table reaches the storage IR, where the table name is required, so it falls back to the model name unchanged, which is the DSL identity default. buildContractDefinition now declares that extensions may be a pack record; it always read them at runtime but its signature said undefined. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…lback Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…name must fail instead of guessing The cross-space foreign-key target table is carried verbatim into contract.json and the REFERENCES clause; nothing resolves it against the remote contract. When the handle's .sql() stage is a factory function there is no table to read, so any fallback emits a reference to a table that may not exist. This test is red until the lowering throws CONTRACT.FOREIGN_KEY_INVALID. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
…name throws instead of guessing The cross-space foreign-key target table is written verbatim into contract.json and the REFERENCES clause, and nothing resolves it against the remote contract. Any fallback for a handle whose .sql() stage is a factory function therefore emits a reference to a table that may not exist. The lowering now throws CONTRACT.FOREIGN_KEY_INVALID naming the source model, the target model, the contract space, and the fix: declare the target model's .sql() stage with a static object carrying table. The local-relation reader in build-contract.ts asserts the table is present instead of silently skipping the mismatch check, and LoweringInput documents why it widens ContractInput. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
… cross-space relation table Nothing resolves or reads an undefined cross-space toTable; the comments described a step that does not exist. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: willbot <w.a.madden+machine@gmail.com> Signed-off-by: Will Madden <madden@prisma.io>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Advanced Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (5)
Included review availability: Your plan provides up to 10 included reviews per hour; 6 remain after this review. 📝 WalkthroughWalkthroughCross-space relation lowering no longer derives table names from model names. Missing static tables remain unset for relations and cause foreign-key validation errors. Local relations now require a target table before validation. Input types accept SQL extension-pack records. ChangesCross-space table metadata
Priority: ⬇️ Low Estimated code review effort: 2 (Simple) | ~15 minutes Change: Bug fix Suggested reviewers: Merge Risk: ⚪ Minimal · up to The changed cross-space relation behavior preserves unavailable static table metadata without affecting local validation, and foreign keys reject unsupported declarations. No actionable merge-blocking risk remains. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Comment |
@prisma/orm-extension-arktype-json
@prisma/orm-extension-middleware-cache
@prisma/orm-extension-paradedb
@prisma/orm-extension-pgvector
@prisma/orm-extension-postgis
@prisma/orm-extension-supabase
@prisma/orm-family-mongo
@prisma/orm-family-sql
@prisma/orm-framework
@prisma/orm-mongo
@prisma/orm-postgres
@prisma/orm-sqlite
@prisma/orm-target-mongo
@prisma/orm-target-postgres
@prisma/orm-target-sqlite
@prisma/orm-toolchain
commit: |
size-limit report 📦
|
At a glance
In the TypeScript authoring DSL, this relation to a model in another contract space:
used to resolve the target table as
OrderItemlowercased toorderitemwhenever the target model's own.sql()stage was a factory function, which hides itstablefrom static reading. Now the relation node carries no table for that case, and a foreign key to such a target is an authoring error instead of aREFERENCES "orderitem"clause to a table that does not exist.The decision
The DSL never guesses a table name. This is slice 3 of the project that removed the PSL interpreter's lowered-first-letter default (#30317): the same class of defect, an implicit case transform, in a different surface. Two code paths carried it.
belongsTowith no statically readable target table,toTableandon.childTableare nowundefined. Their only reader inbuild-contract.tsalready skipped cross-space relations, and the emitted cross-space relation is deliberately non-navigable, so nothing downstream changes. The local-relation path now asserts the table is present instead of skipping its check when it is not.contract.jsonand into the DDL'sREFERENCESclause, and nothing resolves it against the remote contract afterwards. Any guess there is a broken constraint. So when a cross-space foreign key targets a handle whose table cannot be read statically, lowering throwsCONTRACT.FOREIGN_KEY_INVALIDnaming the source model, the target model and the space, and telling the author to declare the target model's.sql()stage with a static object carryingtable. That is the only way a cross-space handle can carry its table; there is no per-relation table option.What changes for authors
A cross-space foreign key to a factory-form handle no longer compiles a contract. Code that worked only because the remote table happened to equal the lowercased model name now fails at authoring time with the error above. Nothing in this repository relied on it, and the upgrade coverage check demands no fragment, but this is a behaviour change worth a release-note line. The fix on the author's side is one edit: give the target model a static
.sql({ table: '...' }).RelationNode.toTableandon.childTable, which extension packages re-export, are now typedstring | undefined. Every producer in the repo still sets them for local relations; only cross-space consumers, of which there are none, would see the widening.Tests
Both new tests were red on
mainfor the stated reason before the change: the relation-node test saworderitemwhere it expectedundefined, and the foreign-key test saw no error where it expected one.test:packages,lint:deps, and the cast ratchet pass.Alternatives considered
tableoption so authors can name the remote table on the relation. A new DSL surface; noted as a possible follow-up, not needed for correctness.projects/psl-verbatim-table-names/slices/ts-dsl-relation-fallback/spec.mdrecords the slice, including the retraction of an earlier claim that the planner resolved the foreign-key table.🤖 Generated with Claude Code
Summary by CodeRabbit
Bug Fixes
Tests