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 10 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
3e1c908 to
308b48d
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>
… with required payload column; re-emit contract
Adds a Role model (id, name) and UserRole junction (userId, roleId,
level NOT NULL no-default) to the sql-orm-client integration fixture,
plus a User.roles manyToMany relation through UserRole. The required
non-FK column `level` in UserRole exercises the requiredPayloadColumns
path in resolveThrough, which D3 uses to disable direct .create for
junctions with required payload.
Re-emits contract.json + contract.d.ts for both the integration test
copy and the package-local copy (pgvector refs stripped). Change is
additive: existing models and User.tags relation are unchanged.
Verified: resolveThrough('user_roles') → requiredPayloadColumns=['level']
(NOT NULL ∧ no default ∧ not FK).
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>
308b48d to
bb3e246
Compare
bc2d4b9 to
3982e13
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.