Skip to content

TML-2787: M:N slice 3 — nested writes through the junction#683

Merged
aqrln merged 45 commits into
mainfrom
tml-2787-slice-3-write
Jun 17, 2026
Merged

TML-2787: M:N slice 3 — nested writes through the junction#683
aqrln merged 45 commits into
mainfrom
tml-2787-slice-3-write

Conversation

@tensordreams

@tensordreams tensordreams commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Slice 3 (final) of the SQL ORM: Many-to-Many End to End project (Linear project). 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/connectnever, 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.

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.

@coderabbitai

coderabbitai Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@tensordreams, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 59 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: af506d3b-191f-4ad6-bc4d-39fe36b5d9d8

📥 Commits

Reviewing files that changed from the base of the PR and between 8d6393f and 334108d.

📒 Files selected for processing (2)
  • packages/2-sql/2-authoring/contract-ts/src/contract-lowering.ts
  • packages/2-sql/2-authoring/contract-ts/test/contract-builder.cross-namespace-same-table.test.ts
📝 Walkthrough

Walkthrough

Adds M:N nested mutation support to the SQL ORM client by routing junction/through relations through a new applyJunctionOwnedMutation execution path supporting create, connect, and disconnect. Introduces compile-time gating via HasRequiredJunctionPayload to disable link writes when a junction requires payload columns. Extends test infrastructure with contract builders for M:N scenarios, unit tests validating junction routing, and integration tests for both pure and required-payload junctions. Also broadens ExecutionMutationDefault.ref to include namespace, propagated across all generated example contracts and authoring layers.

Changes

M:N Nested Mutations with Junction Tables

Layer / File(s) Summary
Junction routing and executor infrastructure
packages/3-extensions/sql-orm-client/src/mutation-executor.ts
Introduces JunctionThrough interface and hasThrough guard; extends relation typing with junction-aware through field; refactors partitionByOwnership to produce a junctionOwned bucket separate from parent/child; adds applyJunctionOwnedMutation handling create/connect/disconnect against the junction table after the parent row is inserted or updated, including preflight duplicate detection and unique-constraint error wrapping.
Type-level junction analysis and link-write gating
packages/3-extensions/sql-orm-client/src/types.ts
Adds RelationThrough, HasRequiredJunctionPayload, ModelNameForTable, and JunctionPayloadFieldNames type utilities for introspecting junction tables and required payload columns; extends RelationMutator with a LinkWritesDisabled generic flag that types create/connect as never when required junction payload columns are present; wires detection into RelationMutationCallback.
Type assertion tests for junction payload validation
packages/3-extensions/sql-orm-client/test/junction-link-write-disable.test-d.ts
New .test-d.ts file with Vitest type assertions confirming required-payload junctions reject create/connect at compile time, pure junctions allow all three operations, and disconnect is always available.
Test contract builders for M:N scenarios
packages/3-extensions/sql-orm-client/test/helpers.ts
Extends test infrastructure with buildTestContextFromContract helper constructing an ExecutionContext from a provided contract with optional mutation-default generators; adds four contract builders: buildManyToManyContract for configurable M:N model setup with custom local fields and optional extra junction columns, buildManyToManyContractWithTargetRelation adding owner relations, buildExecutionDefaultJunctionContract for execution-time generated junction payload, and buildCustomPrimaryKeyContract for custom primary-key scenarios.
Unit tests for junction mutation routing and execution
packages/3-extensions/sql-orm-client/test/mutation-executor.test.ts
Replaces the prior M:N rejection test with targeted tests asserting junction INSERT/DELETE routing, composite-key column coverage, parameter ordering, disconnect where-clause literals, error handling for duplicate/conflicting junctions, and payload-requirement accept/reject rules for create/update flows.
Integration test schema and seeding infrastructure
test/integration/test/sql-orm-client/runtime-helpers.ts
Extends the Postgres test schema with roles and user_roles tables (roles with id/name uniqueness; user_roles as composite PK junction with required level column); adds SeedRole, SeedUserRole seed data types; exports seedRoles and seedUserRoles helper functions; refactors runtime to use single Client instead of Pool with execution recording through shared recordAndDelegate wrapper.
Integration tests for M:N nested writes
test/integration/test/sql-orm-client/mn-nested-write.test.ts
End-to-end suite covering pure junction (User.tags) and required-payload junction (User.roles) scenarios. Tags tests verify create() with connect, multi-connect, update() with connect/disconnect, and multi-create() nested target writes. Roles tests assert runtime rejection of create()/connect() when level is required and allowance of disconnect() during update.
Agent testing rule update
.agents/rules/no-contract-data-patching-in-tests.mdc
Expands agent rule to explicitly forbid hand-authored contract data and coincidentally-shaped object literals; adds "No cast-smuggling" section against escape-casting patterns; introduces stop-work directives for unattended vs interactive failure scenarios.

ExecutionMutationDefault ref namespace field

Layer / File(s) Summary
ref type definition and unit tests
packages/1-framework/0-foundation/contract/src/types.ts, packages/1-framework/0-foundation/contract/test/contract-factories.test.ts, packages/1-framework/0-foundation/contract/test/hashing.test.ts
ExecutionMutationDefault.ref broadened from {table, column} to {namespace, table, column}; contract hashing and factory unit tests updated to supply the new field in fixture data.
Ref namespace validation, runtime filtering, and helpers
packages/2-sql/1-core/contract/src/validators.ts, packages/2-sql/1-core/errors/src/errors.ts, packages/2-sql/1-core/errors/src/exports/index.ts, packages/2-sql/1-core/errors/test/errors.test.ts, packages/2-sql/4-lanes/relational-core/src/query-lane-context.ts, packages/2-sql/5-runtime/src/sql-context.ts, packages/2-sql/5-runtime/test/mutation-default-generators.test.ts, packages/2-sql/5-runtime/test/sql-context.test.ts
Arktype validators require namespace in ref; MutationDefaultsOptions extended with optional namespace field; applyMutationDefaults filters generators by namespace; unique-constraint violation constant UNIQUE_VIOLATION_SQLSTATE and predicate isUniqueConstraintViolation added to support junction error handling; all test fixtures updated with namespaced refs ('public' or '__unbound__').
Contract authoring builders and namespace-qualified defaults
packages/2-sql/2-authoring/contract-ts/src/build-contract.ts, packages/2-sql/2-authoring/contract-ts/src/contract-lowering.ts, packages/2-sql/2-authoring/contract-psl/test/composed-mutation-defaults.test.ts, packages/2-sql/2-authoring/contract-psl/test/interpreter.defaults.test.ts, packages/2-sql/2-authoring/contract-psl/test/provider.test.ts, packages/2-sql/2-authoring/contract-ts/test/*.test.ts, packages/2-sql/3-tooling/emitter/test/emitter-hook.generation.basic.test.ts
SQL contract builder includes namespace in execution default refs; M:N through descriptor conditionally includes namespaceId; table-name collision detection updated to be namespace-scoped via [namespaceId, tableName] key; all PSL/DSL/emitter test expectations updated to assert namespace in generated refs for defaults and temporal lowering functions; two new DSL tests verify namespace isolation for table names and M:N junction namespace resolution.
Generated example contracts
examples/*/src/prisma/contract.*, examples/prisma-next-demo/migrations/app/*/*-contract.*, skills/extension-author/prisma-next-extension-upgrade/upgrades/0.14-to-0.15/instructions.md, skills/upgrade/prisma-next-upgrade/upgrades/0.14-to-0.15/instructions.md
All generated .d.ts and .json contract files updated to include namespace in each mutation default ref object with regenerated executionHash values across all example apps (Cloudflare, SQLite, PostGIS, React Router, Supabase) and migration archives; Supabase extension pack version bumped to 0.14.0; upgrade documentation added for 0.140.15 migration notes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • prisma/prisma-next#678: Adds the through contract and resolver foundation plus initial N:M cardinality canonicalization in sql-orm-client that this PR builds directly on top of to implement actual junction-table execution and type gating.

Poem

🐇 Through the junction I hop, left and right,
Linking tags and roles with gleeful might!
create, connect, disconnect in a row—
Required payload? Type says "no"!
The executor routes, the tables align,
Many-to-many at last, by design! ✨

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch tml-2787-slice-3-write

@github-actions

github-actions Bot commented Jun 1, 2026

Copy link
Copy Markdown

size-limit report 📦

Path Size
postgres / no-emit 154.55 KB (+0.82% 🔺)
postgres / emit 142.13 KB (+0.86% 🔺)
mongo / no-emit 78 KB (0%)
mongo / emit 72.09 KB (0%)
cf-worker / no-emit 181.82 KB (+0.05% 🔺)
cf-worker / emit 167.78 KB (+0.02% 🔺)

@pkg-pr-new

pkg-pr-new Bot commented Jun 1, 2026

Copy link
Copy Markdown

Open in StackBlitz

@prisma-next/extension-author-tools

npm i https://pkg.pr.new/@prisma-next/extension-author-tools@683

@prisma-next/mongo-runtime

npm i https://pkg.pr.new/@prisma-next/mongo-runtime@683

@prisma-next/family-mongo

npm i https://pkg.pr.new/@prisma-next/family-mongo@683

@prisma-next/sql-runtime

npm i https://pkg.pr.new/@prisma-next/sql-runtime@683

@prisma-next/family-sql

npm i https://pkg.pr.new/@prisma-next/family-sql@683

@prisma-next/extension-arktype-json

npm i https://pkg.pr.new/@prisma-next/extension-arktype-json@683

@prisma-next/middleware-cache

npm i https://pkg.pr.new/@prisma-next/middleware-cache@683

@prisma-next/mongo

npm i https://pkg.pr.new/@prisma-next/mongo@683

@prisma-next/extension-paradedb

npm i https://pkg.pr.new/@prisma-next/extension-paradedb@683

@prisma-next/extension-pgvector

npm i https://pkg.pr.new/@prisma-next/extension-pgvector@683

@prisma-next/extension-postgis

npm i https://pkg.pr.new/@prisma-next/extension-postgis@683

@prisma-next/postgres

npm i https://pkg.pr.new/@prisma-next/postgres@683

@prisma-next/sql-orm-client

npm i https://pkg.pr.new/@prisma-next/sql-orm-client@683

@prisma-next/sqlite

npm i https://pkg.pr.new/@prisma-next/sqlite@683

@prisma-next/extension-supabase

npm i https://pkg.pr.new/@prisma-next/extension-supabase@683

@prisma-next/target-mongo

npm i https://pkg.pr.new/@prisma-next/target-mongo@683

@prisma-next/adapter-mongo

npm i https://pkg.pr.new/@prisma-next/adapter-mongo@683

@prisma-next/driver-mongo

npm i https://pkg.pr.new/@prisma-next/driver-mongo@683

@prisma-next/contract

npm i https://pkg.pr.new/@prisma-next/contract@683

@prisma-next/utils

npm i https://pkg.pr.new/@prisma-next/utils@683

@prisma-next/config

npm i https://pkg.pr.new/@prisma-next/config@683

@prisma-next/errors

npm i https://pkg.pr.new/@prisma-next/errors@683

@prisma-next/framework-components

npm i https://pkg.pr.new/@prisma-next/framework-components@683

@prisma-next/operations

npm i https://pkg.pr.new/@prisma-next/operations@683

@prisma-next/ts-render

npm i https://pkg.pr.new/@prisma-next/ts-render@683

@prisma-next/contract-authoring

npm i https://pkg.pr.new/@prisma-next/contract-authoring@683

@prisma-next/ids

npm i https://pkg.pr.new/@prisma-next/ids@683

@prisma-next/psl-parser

npm i https://pkg.pr.new/@prisma-next/psl-parser@683

@prisma-next/psl-printer

npm i https://pkg.pr.new/@prisma-next/psl-printer@683

@prisma-next/cli

npm i https://pkg.pr.new/@prisma-next/cli@683

@prisma-next/cli-telemetry

npm i https://pkg.pr.new/@prisma-next/cli-telemetry@683

@prisma-next/emitter

npm i https://pkg.pr.new/@prisma-next/emitter@683

@prisma-next/migration-tools

npm i https://pkg.pr.new/@prisma-next/migration-tools@683

prisma-next

npm i https://pkg.pr.new/prisma-next@683

@prisma-next/vite-plugin-contract-emit

npm i https://pkg.pr.new/@prisma-next/vite-plugin-contract-emit@683

@prisma-next/mongo-codec

npm i https://pkg.pr.new/@prisma-next/mongo-codec@683

@prisma-next/mongo-contract

npm i https://pkg.pr.new/@prisma-next/mongo-contract@683

@prisma-next/mongo-value

npm i https://pkg.pr.new/@prisma-next/mongo-value@683

@prisma-next/mongo-contract-psl

npm i https://pkg.pr.new/@prisma-next/mongo-contract-psl@683

@prisma-next/mongo-contract-ts

npm i https://pkg.pr.new/@prisma-next/mongo-contract-ts@683

@prisma-next/mongo-emitter

npm i https://pkg.pr.new/@prisma-next/mongo-emitter@683

@prisma-next/mongo-schema-ir

npm i https://pkg.pr.new/@prisma-next/mongo-schema-ir@683

@prisma-next/mongo-query-ast

npm i https://pkg.pr.new/@prisma-next/mongo-query-ast@683

@prisma-next/mongo-orm

npm i https://pkg.pr.new/@prisma-next/mongo-orm@683

@prisma-next/mongo-query-builder

npm i https://pkg.pr.new/@prisma-next/mongo-query-builder@683

@prisma-next/mongo-lowering

npm i https://pkg.pr.new/@prisma-next/mongo-lowering@683

@prisma-next/mongo-wire

npm i https://pkg.pr.new/@prisma-next/mongo-wire@683

@prisma-next/sql-contract

npm i https://pkg.pr.new/@prisma-next/sql-contract@683

@prisma-next/sql-errors

npm i https://pkg.pr.new/@prisma-next/sql-errors@683

@prisma-next/sql-operations

npm i https://pkg.pr.new/@prisma-next/sql-operations@683

@prisma-next/sql-schema-ir

npm i https://pkg.pr.new/@prisma-next/sql-schema-ir@683

@prisma-next/sql-contract-psl

npm i https://pkg.pr.new/@prisma-next/sql-contract-psl@683

@prisma-next/sql-contract-ts

npm i https://pkg.pr.new/@prisma-next/sql-contract-ts@683

@prisma-next/sql-contract-emitter

npm i https://pkg.pr.new/@prisma-next/sql-contract-emitter@683

@prisma-next/sql-lane-query-builder

npm i https://pkg.pr.new/@prisma-next/sql-lane-query-builder@683

@prisma-next/sql-relational-core

npm i https://pkg.pr.new/@prisma-next/sql-relational-core@683

@prisma-next/sql-builder

npm i https://pkg.pr.new/@prisma-next/sql-builder@683

@prisma-next/target-postgres

npm i https://pkg.pr.new/@prisma-next/target-postgres@683

@prisma-next/target-sqlite

npm i https://pkg.pr.new/@prisma-next/target-sqlite@683

@prisma-next/adapter-postgres

npm i https://pkg.pr.new/@prisma-next/adapter-postgres@683

@prisma-next/adapter-sqlite

npm i https://pkg.pr.new/@prisma-next/adapter-sqlite@683

@prisma-next/driver-postgres

npm i https://pkg.pr.new/@prisma-next/driver-postgres@683

@prisma-next/driver-sqlite

npm i https://pkg.pr.new/@prisma-next/driver-sqlite@683

commit: 334108d

@tensordreams tensordreams force-pushed the tml-2786-slice-2-filter branch from 92d1741 to 25a3c7e Compare June 2, 2026 12:40
@tensordreams tensordreams force-pushed the tml-2787-slice-3-write branch from 7cbb4a0 to de78282 Compare June 2, 2026 12:40
@tensordreams tensordreams force-pushed the tml-2786-slice-2-filter branch from 25a3c7e to 06fc270 Compare June 2, 2026 13:07
@tensordreams tensordreams force-pushed the tml-2787-slice-3-write branch from de78282 to 3e1c908 Compare June 2, 2026 13:07
@tensordreams tensordreams marked this pull request as ready for review June 2, 2026 13:08
@tensordreams tensordreams requested a review from a team as a code owner June 2, 2026 13:08
@tensordreams tensordreams changed the title TML-2787: M:N slice 3 — nested writes through the junction (DRAFT — type-level disable pending decision) TML-2787: M:N slice 3 — nested writes through the junction Jun 2, 2026
@tensordreams

Copy link
Copy Markdown
Contributor Author

Updated: the type-level .create/.connect disable is now implemented (escalation #8 resolved via the chosen approach — the .d.ts emitter now carries through, landed in slice 0; the type gate derives required-payload purely at the type level, no any). AC-2 is closed at both the runtime and type levels. The whole stack has also been rebased onto the latest origin/main (clean — no fixture re-emit cascade). Negative type test: junction-link-write-disable.test-d.ts. No longer a draft.

@tensordreams tensordreams force-pushed the tml-2786-slice-2-filter branch from 06fc270 to bc2d4b9 Compare June 3, 2026 08:51
@tensordreams tensordreams force-pushed the tml-2787-slice-3-write branch 2 times, most recently from 308b48d to bb3e246 Compare June 3, 2026 11:32
@tensordreams tensordreams force-pushed the tml-2786-slice-2-filter branch 2 times, most recently from 3982e13 to b1dadcf Compare June 4, 2026 15:12
@tensordreams tensordreams force-pushed the tml-2787-slice-3-write branch from bb3e246 to 47cf59e Compare June 4, 2026 15:12
@tensordreams tensordreams force-pushed the tml-2786-slice-2-filter branch from b1dadcf to df900cd Compare June 4, 2026 15:41
@tensordreams tensordreams force-pushed the tml-2787-slice-3-write branch from 47cf59e to b04a59c Compare June 4, 2026 15:41
@tensordreams tensordreams force-pushed the tml-2786-slice-2-filter branch from df900cd to ee44053 Compare June 5, 2026 12:44
@tensordreams tensordreams force-pushed the tml-2787-slice-3-write branch from b04a59c to 04522c0 Compare June 5, 2026 12:44
@tensordreams tensordreams force-pushed the tml-2786-slice-2-filter branch from ee44053 to 65d4fe3 Compare June 5, 2026 14:21
@tensordreams tensordreams force-pushed the tml-2787-slice-3-write branch from 04522c0 to 4d8dad2 Compare June 5, 2026 14:21
@tensordreams tensordreams force-pushed the tml-2786-slice-2-filter branch from 65d4fe3 to bd47c00 Compare June 5, 2026 15:02
@tensordreams tensordreams force-pushed the tml-2787-slice-3-write branch from 4d8dad2 to 16dc690 Compare June 5, 2026 15:02
@tensordreams tensordreams force-pushed the tml-2787-slice-3-write branch from 16dc690 to f3a7a5c Compare June 8, 2026 10:19
@tensordreams tensordreams force-pushed the tml-2786-slice-2-filter branch from bd47c00 to 40b8cbe Compare June 8, 2026 10:19
@tensordreams tensordreams force-pushed the tml-2787-slice-3-write branch from f3a7a5c to b772b54 Compare June 8, 2026 11:19
…no-op

main moved under this branch: the enum refactor dropped
PostgresEnumStorageEntry from sql-contract/types, and per-namespace
resolution renamed ModelsOf -> NamespaceModelsOf(<ns>). Update the test
contract-builder import and the junction-payload ModelNameForTable type
accordingly. Record the namespace-scoped execution-default ref change as an
incidental substrate diff in the end-user 0.13->0.14 upgrade notes (re-emit
picks it up; no user action) so check:upgrade-coverage passes.

Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
These two integration tests append an execution-default to the contract at
runtime (a tags.updated_at @updatedat default; a user_tags.created_at onCreate
default). After the ref gained a required namespace, the structural validator
rejects the namespace-less refs. Add namespace: "public".

Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
The flat sql-builder write path called applyMutationDefaults with no
namespace, so a multi-namespace contract with two same-named tables could
apply the wrong namespace's execution default (or none at all). The ORM
path already threaded it everywhere; only the lane did not (F01).

Forward `namespace: namespaceId` from buildParamValues/buildSetExpressions,
and make MutationDefaultsOptions.namespace required so a forgotten namespace
is a type error rather than a silent degrade to table-name-only matching
(F02) — the runtime matcher now filters on namespace unconditionally.

Regression test: two same-named `users` tables in `public`/`auth` with
per-namespace execution defaults; each insert/update now picks up its own
namespace's default and not the collision twin's.

Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
The guard's remediation advice told users to write the related model (e.g.
`Role`) directly, but the unfillable required column lives on the junction
table (`user_roles`), not the related model. Writing `Role` would never
populate the junction payload. Point the advice at the junction `through.table`
instead, and pin the wording in the create/connect guard tests.

Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
The cast-smuggling section listed `blindAs`, which does not exist in
`@prisma-next/utils/casts` — the only exported helpers are `blindCast` and
`castAs`. Reference the real helpers so the forbidden-pattern enumeration is
accurate.

Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…resolution

Table names are unique per namespace, not globally — the storage IR and runtime
already key tables by namespace, and the low-level buildSqlContractFromDefinition
path already accepts same-named tables in different namespaces. The DSL/emit path
did not:

- The table-uniqueness check keyed `tableOwners` by bare table name, so two models
  mapping `public.users` and `shadow.users` were rejected. Key it by
  `(namespace, table)`.
- M:N junction resolution lost the junction's namespace: the lowered `through`
  node carried only a table name, and `buildThroughDescriptor` re-derived the
  namespace by bare table name (last-wins). With two `user_roles` junctions this
  emitted the wrong `through.namespaceId` for both. Carry the junction's namespace
  on the `through` node and resolve `through.namespaceId ?? defaultNamespaceId`.

Value-identical for existing single-namespace contracts (no fixture drift).

Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…ate-disconnect

Two type-level corrections to the nested-write mutators, mirroring the runtime:

- The junction-payload required-column gate (`HasRequiredJunctionPayload`) matched
  execution defaults by `(table, column)` only, dropping the junction namespace
  even though `ExecutionMutationDefault.ref` is namespace-scoped (runtime twin:
  the sql-builder/applyMutationDefaults namespace fix). In a multi-namespace
  contract an execution default for `public.user_roles.token` could make
  `shadow.user_roles.token` look optional, wrongly enabling create/connect on the
  shadow relation. Thread the junction's namespace through the payload-field
  helpers and include `ref.namespace` in `HasExecutionCreateDefault`.
- `MutationCreateInput` typed junction `disconnect` as available, but `createGraph`
  rejects any nested disconnect during `create()`. Add a create/update context
  axis to `RelationMutator` so disconnect is unavailable in create input; it stays
  available in update input.

Tested against a new emitted two-namespace fixture (`junction-namespaces`) where
`public`/`shadow` share the `user_roles` junction but only `public` carries the
execution default.

Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
`withMutationScope` only opened a transaction when the runtime exposed a
top-level `transaction()`, but no runtime does — `transaction()` lives on a
connection (`connection().transaction()`). So every ORM mutation, including
multi-statement nested-write graphs, ran without a transaction: a failure after
the first write left a partial write behind. Open a connection and run the whole
graph inside its transaction (falling back to a bare scope only when the runtime
exposes no connection, e.g. unit-test stubs).

The integration harness is reworked to match: the `@prisma/dev` server is
PGlite-backed (one concurrent connection), so the wrapper now drives a single
`pg.Client` (not a pool) through the direct driver, and its `connection()` /
`transaction()` keep recording executions so the per-test execution-count
assertions still see reads and writes. New regression test: a nested M:N tag
create that fails (duplicate pk) after the parent insert now rolls back the
parent and the junction.

Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…n emitted fixture

The execution-default junction-payload test hand-cloned the emitted contract and
mutated `storage`/`execution` directly (dropping `user_tags.created_at`'s storage
default and pushing an execution default by hand) — exactly the pattern the
no-contract-data-patching rule forbids. Replace it with a dedicated emitted
fixture (`execution-defaulted-tags`) authored through the DSL, where
`user_tags.created_at` is NOT NULL with an execution-time onCreate default and no
storage default. The test now builds its runtime/context from that emitted
contract; the scenario comes from a shape the authoring surface can produce.

Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
The sql-orm-client integration runtime now drives a single long-lived client so
transactions hold one connection on the single-backend PGlite server. The dev
server's default 1s idle timeout reaps that client during brief idle windows
under full-suite load (a pool would reconnect; a lone client cannot), causing
intermittent "connection terminated" flakes. Raise the idle timeout to 30s for
these tests — no test holds the connection idle anywhere near that long.

Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…main

Rebasing onto main picked up TML-2916 (un-namespaced PG models default to
`public`, dropping the spurious empty `__unbound__` storage slot). Re-emit the
slice's own fixtures (`junction-namespaces`, `execution-defaulted-tags`) so they
match the new emitter output; storage hashes update accordingly. Lockfile records
the `@prisma-next/sql-errors` dependency at the 0.14.0 workspace version.

Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
@tensordreams tensordreams force-pushed the tml-2787-slice-3-write branch from ffb6131 to 39562fd Compare June 16, 2026 18:46
Now that main is 0.14.0, the upgrade-coverage gate keys the in-flight transition
to 0.14→0.15. Move the namespace-scoped execution-default-ref note out of the
released 0.13-to-0.14 upgrade and into new 0.14-to-0.15 directories for both the
user and extension-author skills. The change is an incidental substrate diff
(re-emit picks up the new contract ref shape; no consumer action), so each ships
`changes: []`.

Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
…ack version

The supabase example contract embeds the supabase extension pack version, which
the 0.14.0 release bump left at the stale `0.13.0` — `fixtures:check` re-emits it
to `0.14.0`. Re-emit so the committed contract matches; no schema change.

Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
Comment thread packages/2-sql/2-authoring/contract-ts/src/contract-lowering.ts Outdated
Comment thread packages/2-sql/2-authoring/contract-ts/src/contract-lowering.ts Outdated
Comment thread packages/2-sql/2-authoring/contract-ts/src/contract-lowering.ts Outdated
Comment thread packages/2-sql/2-authoring/contract-ts/test/contract-builder.dsl.test.ts Outdated
Comment thread packages/2-sql/2-authoring/contract-ts/test/contract-builder.dsl.test.ts Outdated
Comment thread packages/3-extensions/sql-orm-client/src/mutation-executor.ts
Comment thread test/integration/test/sql-orm-client/runtime-helpers.ts Outdated
Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
Comment thread packages/2-sql/2-authoring/contract-ts/src/contract-lowering.ts Outdated
Signed-off-by: Alexey Orlenko's AI Agent <robot@aqrln.net>
@aqrln aqrln enabled auto-merge June 17, 2026 13:51
@aqrln aqrln added this pull request to the merge queue Jun 17, 2026
Merged via the queue into main with commit c793885 Jun 17, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants