TML-2787: M:N slice 3 — nested writes through the junction#683
TML-2787: M:N slice 3 — nested writes through the junction#683tensordreams wants to merge 9 commits into
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ 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 |
size-limit report 📦
|
@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/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/emitter
@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: |
92d1741 to
25a3c7e
Compare
7cbb4a0 to
de78282
Compare
25a3c7e to
06fc270
Compare
de78282 to
3e1c908
Compare
|
Updated: the type-level |
06fc270 to
bc2d4b9
Compare
308b48d to
bb3e246
Compare
3982e13 to
b1dadcf
Compare
bb3e246 to
47cf59e
Compare
b1dadcf to
df900cd
Compare
47cf59e to
b04a59c
Compare
df900cd to
ee44053
Compare
b04a59c to
04522c0
Compare
ee44053 to
65d4fe3
Compare
04522c0 to
4d8dad2
Compare
Write slice: connect/disconnect/create through the junction + required-payload .create disable (types+runtime). 4 dispatches (write path / required-payload fixture / type+runtime disable / integration). Type-disable feasibility risk pre-named with a halt. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
Replace the partitionByOwnership N:M rejection guard with a junctionOwned bucket. connect/disconnect/create over a through relation now resolve to junction INSERT/DELETE (create = target-insert then link) in both the create() and update() graph flows, after the parent PK is known. Composite keys are AND-ed across all parent/child column pairs; disconnect stays update()-only. Flip the rejection unit test to a positive junction-DML assertion and add connect/disconnect/create coverage for both flows. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…umns exist Throw a clear runtime error when a nested `.create()` targets an M:N relation whose junction table has non-FK NOT-NULL columns with no default (i.e. `requiredPayloadColumns` is non-empty). The error names the relation, the offending column(s), and points to the junction model / SQL builder as the supported alternative. `connect` and `disconnect` are unaffected — they only touch the FK pair. Pure junctions (no required payload) pass through the create path as before. Adds `requiredPayloadColumns` to the local `JunctionThrough` interface and copies it from the already-resolved `relation.through` in `getRelationDefinitions`. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…finish write integration tests Both `connect` and `create` write a bare junction row the M:N sugar can't complete when the junction has required non-FK columns (e.g. `user_roles.level NOT NULL`). Extend the runtime guard in `applyJunctionOwnedMutation` to cover `connect` in addition to `create`; `disconnect` (DELETE path) is unaffected. Guard message is unified: "Cannot `<op>` on relation `<rel>`: its junction `<table>` has required column(s) `<col>` the relation API can't populate. Use the `<Model>` model directly or the SQL builder." Unit test that previously asserted connect-on-User.roles succeeds is flipped to assert rejection. Integration tests are fully enabled (no it.skip); the new test asserts connect on User.roles throws the guard, while pure-junction (User.tags) connect/create/disconnect paths continue to work end-to-end. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…sted-write test; re-emit fixtures with through blocks Plain `string` constants failed the integration typecheck (tsc) because Tag.id / Role.id are `Char<36>`. Brand TAG_RUST, TAG_TS, ROLE_ADMIN, ROLE_EDITOR at declaration (matching the pattern from create.test.ts). Runtime values are unchanged. Also commits the re-emitted contract.d.ts fixtures carrying the new `through` blocks on the N:M relations (User.tags / User.roles). Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…elations at the type level Nested `.create` on an N:M relation whose junction carries a required non-FK payload column now resolves its argument to `never`, surfacing a compile-time error instead of only the runtime guard in `mutation-executor.ts`. `connect`/`disconnect` typing is unchanged. The derivation resolves the relation `through` to its junction model, treats any non-join column as a payload column, and disables create when any payload column is required (not nullable, no default). `User.roles` (junction `user_roles` with required `level`) disables create; `User.tags` (pure junction) keeps it. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
Renames the `CreateDisabled` flag on `RelationMutator` to `LinkWritesDisabled` and applies the same `never`-arg guard to both `create` and `connect` overloads. `disconnect` is unaffected (it issues a DELETE, not an INSERT, so no payload column is needed). `HasRequiredJunctionPayload` is already threaded as the flag via `RelationMutationCallback` — no change required there. The runtime guard in `mutation-executor.ts` already blocks both operations; the type system now matches. Test file renamed to `junction-link-write-disable.test-d.ts`; the former "connect remains available" positive test is replaced with a `@ts-expect-error` assertion, and a new pure-positive disconnect test preserves coverage of the allowed path. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…ard tests The type-level .create/.connect disable (now in dist after the main rebase) makes those calls compile errors; cast the args to `never` to still exercise the runtime guard on a real DB (defense-in-depth). Surfaced by rebuilding against origin/main. Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
65d4fe3 to
bd47c00
Compare
4d8dad2 to
16dc690
Compare
Slice 3 (final) of the SQL ORM: Many-to-Many End to End project (Linear project). Nested
connect/disconnect/createthrough the junction + the required-payload safety rail.Overview
db.orm.User.update/create({ tags: (t) => t.connect/disconnect/create(...) })now routes to theUserTagjunction (INSERT / DELETE / target-insert+link), under bothcreate()andupdate(). TheN:M not supported yetguard is gone. Junctions with a required non-FK payload column cant be written through the sugar, socreateandconnecton them are disabled with a clear error (disconnect stays).Changes (5 commits)
74a778816— runtime junction write path:partitionByOwnershipgains ajunctionOwnedbucket (keyed onthroughpresence); connect→INSERT, disconnect→DELETE, create→target-insert+link, both flows, composite-key AND-ed; the rejection unit test flipped positive. (getRelationDefinitionsnow carriesthrough.)926bdc849— required-payload fixture:User ↔ RoleviaUserRole(user_id, role_id, level NOT NULL)(canonical CLI emit).3bccd80b3— runtime guard: nestedcreateon a required-payload junction throws.e6c641811— design correction (decision Remove SQL -> Runtime dependency #9): extended the guard toconnecttoo (connect also INSERTs a junction row it cant complete → DB NOT-NULL violation), flipped the unit test, finished the 10 write integration tests.Integration tests (per the project standard)
mn-nested-write.test.ts— 10 tests, no skips: connect/create on the pureUser.tagsjunction with whole-row readback viainclude(tags), bothcreate()+update()flows;disconnect; connect AND create onUser.rolesthrow the guard;disconnectonUser.rolesworks. Whole-rowtoEqual, explicit.selectin most, ≥1 implicit/default selection.AC status
create+connecton required-payload junctions: ✅ done + tested. Type-level disable: deferred — see below.⚠ Open decision (blocks marking this slice done)
The type-level
.createdisable cant be built as specified: the generatedcontract.d.tsrelation type carries onlyto/cardinality/on, notthrough— so a conditional type cant see which model is the junction or that it has a required column.requiredPayloadColumnsexists only at runtime. Honouring the type-level disable needs the contract.d.tstype emitter to carrythrough(a contract-surface change reaching into slice-0 territory). Options (full detail inwip/unattended-decisions.md#8):.d.tsemitter to emitthrough, then a follow-up adds the conditional-type disable + negative type test;requiredPayloadColumns/hasRequiredPayloadmarker into the typed relation;I shipped the runtime guard and left this for you. Once you pick (a)/(b), a small follow-up dispatch completes the type-level disable.
Notes
connect-disabled-on-required-payload was a mid-flight spec correction (the original spec wrongly assumed connect was FK-pair-only safe). The reverseTag.users/Role.usersdirections are deferred (one-directional fixture, decision #3). Refs: TML-2787.