Commit c793885
authored
TML-2787: M:N slice 3 — nested writes through the junction (#683)
Slice 3 (final) of the **SQL ORM: Many-to-Many End to End** project
([Linear
project](https://linear.app/prisma-company/project/sql-orm-many-to-many-end-to-end-c178df40ca3a)).
Nested `connect`/`disconnect`/`create` through the junction + the
required-payload safety rail.
> **Stacked PR.** Base `tml-2786` (#680) → `tml-2785` (#679) →
`tml-2784` (#678) → `tml-2597` (#673) → `tml-2729` (#667) → `main`.
Review/merge bottom-up.
## Overview
`db.orm.User.update/create({ tags: (t) =>
t.connect/disconnect/create(...) })` now routes to the `UserTag`
junction (INSERT / DELETE / target-insert+link), under both `create()`
and `update()`. The `N:M not supported yet` guard is gone. Junctions
with a **required non-FK payload column** cant be written through the
sugar, so `create` **and** `connect` on them are disabled at both the
type level and runtime with a clear error (disconnect stays).
## Changes
- **Runtime junction write path:** `partitionByOwnership` gains a
`junctionOwned` bucket (keyed on `through` presence); connect→INSERT,
disconnect→DELETE, create→target-insert+link, both flows, composite-key
AND-ed; the rejection unit test flipped positive.
(`getRelationDefinitions` now carries `through`.)
- **Required-payload fixture:** `User ↔ Role` via `UserRole(user_id,
role_id, level NOT NULL)` (canonical CLI emit).
- **Runtime guard:** nested `create` **and** `connect` on a
required-payload junction throw (connect also INSERTs a junction row it
cant complete → DB NOT-NULL violation). Disconnect stays allowed.
- **Type-level gate:** `HasRequiredJunctionPayload` +
`HasJunctionThrough` compose `LinkWritesDisabled` and
`BareDisconnectDisabled` on `RelationMutator`, disabling
`create`/`connect` and bare `disconnect()` at the type level for
required-payload junctions. The `.d.ts` emitter now carries `through`
(namespace-scoped), so the gate derives required-payload columns from
the junction model's field types without `any`. Covered by type tests
(`junction-link-write-disable.test-d.ts`).
- **Error handling:** `isUniqueConstraintViolation` uses
driver-normalized `SqlQueryError.sqlState`; duplicate-connect wraps with
a domain error naming the junction; shared-column conflicts detected on
both INSERT and DELETE sides; metadata-length assertions fail fast;
preflight detects duplicate resolved targets.
- **Namespace scoping:** `MutationDefaultsOptions` carries `namespace`;
`ModelNameForTable` and `JunctionPayloadFieldNames` are namespace-aware;
execution-default and storage-default columns are excluded from
`requiredPayloadColumns`.
- **Transaction safety:** ORM mutations run inside a transaction via
`withMutationScope`.
## Integration tests (per the project standard)
`mn-nested-write.test.ts` — connect/create/disconnect on the pure
`User.tags` junction and the required-payload `User.roles` junction,
both `create()` and `update()` flows; duplicate-connect rejection;
missing-target rejection; parent-insert-failure-after-connect-preflight
(no partial writes); execution-defaulted junction payload column; bare
`disconnect()` type error on junctions; whole-row `toEqual`, explicit
`.select` in most, ≥1 implicit/default selection.
## AC status
- ✅ **AC-1** — connect/disconnect/create route to junction DML, both
flows, guard removed, rejection test flipped (unit + integration).
- ✅ **AC-2** — Runtime disable of `create`+`connect` on required-payload
junctions + type-level disable (`create`/`connect` → `never`, bare
`disconnect()` → type error on junctions). Both implemented and tested.
- ✅ **AC-3** — write integration tests per the standard.
## Notes
`connect`-disabled-on-required-payload was a mid-flight spec correction
(the original spec wrongly assumed connect was FK-pair-only safe).
Duplicate `connect` errors deliberately rather than silently succeeding
(intentional divergence from Prisma-classic implicit-M:N `connect`,
which is idempotent via `ON CONFLICT DO NOTHING`). The reverse
`Tag.users`/`Role.users` directions are deferred (one-directional
fixture, decision #3). Refs: TML-2787.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Enabled many-to-many nested mutations via junction tables when
junction metadata is available, including `create`, `connect`, and
`disconnect`.
* Added compile-time and runtime protections to prevent
creating/connecting junction links when the junction has required
payload fields.
* **Bug Fixes**
* Improved unique-constraint error detection for more consistent
handling (based on SQLSTATE).
* **Tests**
* Expanded unit and integration coverage for junction-based M:N nested
writes, junction payload validation, and correct junction DML behavior.
* **Chores**
* Updated generated contract and example artifacts to use
namespace-qualified execution-default references.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>1 parent 69b6e74 commit c793885
121 files changed
Lines changed: 7304 additions & 342 deletions
File tree
- .agents/rules
- examples
- bundle-size/src/postgres/generated
- prisma-next-cloudflare-worker/src/prisma
- prisma-next-demo-sqlite/src/prisma
- prisma-next-demo
- migrations/app
- 20260422T0720_initial
- 20260422T0742_migration
- 20260422T0748_migration
- 20260605T1145_mti_variant_link_columns
- 20260610T0000_add_priority_enum
- 20260610T2216_set_priority_default
- src/prisma
- prisma-next-postgis-demo
- migrations/app/20260512T1309_migration
- src/prisma
- react-router-demo/src/prisma
- supabase/src
- packages
- 1-framework/0-foundation/contract
- src
- test
- 2-sql
- 1-core
- contract/src
- errors
- src
- exports
- test
- 2-authoring
- contract-psl/test
- contract-ts
- src
- test
- 3-tooling/emitter/test
- 4-lanes
- relational-core/src
- sql-builder
- src/runtime
- test
- fixtures/generated
- runtime
- 5-runtime
- src
- test
- 9-family/test
- 3-extensions/sql-orm-client
- src
- test
- fixtures
- generated
- junction-namespaces/generated
- projects/sql-orm-many-to-many
- slices/03-nested-write-through-junction
- dispatches
- skills
- extension-author/prisma-next-extension-upgrade/upgrades
- 0.13-to-0.14
- 0.14-to-0.15
- upgrade/prisma-next-upgrade/upgrades/0.14-to-0.15
- test
- e2e/framework/test/fixtures/generated
- integration/test
- authoring/parity
- default-cuid-2
- default-nanoid-16
- default-nanoid
- default-pack-slugid
- default-ulid
- default-uuid-v4
- default-uuid-v7
- sql-builder/fixtures/generated
- sql-orm-client
- fixtures
- execution-defaulted-tags
- generated
- generated
- junction-namespaces
- generated
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
11 | | - | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
12 | 25 | | |
13 | 26 | | |
14 | 27 | | |
| |||
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 16 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 4 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 11 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 16 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 4 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 16 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
0 commit comments