Skip to content

fix(orm): variance-annotate ToManyRelationUpdateInput to avoid excessively deep instantiation - #2855

Merged
ymc9 merged 1 commit into
devfrom
fix/issue-2778-to-many-update-variance
Sep 25, 2026
Merged

ymc9 merged 1 commit into
devfrom
fix/issue-2778-to-many-update-variance

Conversation

@ymc9

@ymc9 ymc9 commented Sep 25, 2026 •

Copy link
Copy Markdown
Member

Fixes #2778

Problem

Passing a generated XxxUpdateArgs / XxxUpsertArgs type (from input.ts, or UpdateArgs<Schema, Model> directly) to the matching client method fails on a schema with a many-to-many relation:

export function upsertTag(data: TagUpsertArgs) {
    return db.tag.upsert(data); // TS2589: Type instantiation is excessively deep and possibly infinite
}

On TS 5/6 tsc runs out of memory instead; the language service becomes unusable.

Root cause

The generated args type uses the default QueryOptions<Schema>, while the client's methods use the projected QueryRelevantOptions<Schema, Options>. Checking the argument therefore compares two instantiations of ToManyRelationUpdateInput with different Options. That alias had no variance annotations, so TypeScript measured its variance structurally through the recursive nested update/upsert types (NestedUpdateInput, NestedUpsertInput, XOR, ...). A trace shows that single getVariancesWorker call taking ~63s and ending "unmeasurable", after which the structural fallback explodes.

ToOneRelationUpdateInput was already in out-annotated for exactly this reason; the to-many variant was missed.

Fix

Add the same in out annotations to ToManyRelationUpdateInput.

Verification

  • Reporter's repro project: TS2589 / OOM before; compiles in <1s (TS 7) and ~2s (TS 5) after.
  • New regression test tests/regression/test/issue-2778 covers UpsertArgs and UpdateArgs passed straight into upsert / update on a many-to-many schema (type-check + runtime).
  • tsc for tests/regression and tests/e2e passes; tests/e2e/orm/client-api passes on SQLite (the only failures are two MySQL timezone tests that need a local MySQL and fail identically on dev).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved type compatibility for nested relation updates, allowing generated upsert and update arguments to be used with the corresponding client operations.
    • Verified that nested product creation works correctly during these operations.

…ively deep instantiation

Passing a generated `XxxUpdateArgs`/`XxxUpsertArgs` type directly to the
corresponding client method (e.g. `db.tag.upsert(args)`) on a schema with a
many-to-many relation made TypeScript report TS2589 ("Type instantiation is
excessively deep and possibly infinite") or run out of memory. The generated
type uses the default `QueryOptions` while the client uses the projected
`QueryRelevantOptions`, so TypeScript had to compare two instantiations of
`ToManyRelationUpdateInput` with different `Options`. Without variance
annotations it measured the variance structurally through the recursive
nested update/upsert types, which is what blew up. `ToOneRelationUpdateInput`
was already annotated for this reason; this applies the same to the to-many
variant.

Fixes #2778

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: zenstackhq/zenstack/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 58186097-4ae3-4e39-99a5-452591b0fe05

📥 Commits

Reviewing files that changed from the base of the PR and between aa89ff5 and 8660072.

📒 Files selected for processing (4)
  • packages/orm/src/client/crud-types.ts
  • tests/regression/test/issue-2778/regression.test.ts
  • tests/regression/test/issue-2778/schema.ts
  • tests/regression/test/issue-2778/schema.zmodel

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

The ORM update input type now marks four parameters invariant. A regression test uses generated upsert and update argument types with a SQLite schema containing a many-to-many relation.

Changes

Many-to-many type recursion

Layer / File(s) Summary
Update input type variance
packages/orm/src/client/crud-types.ts
ToManyRelationUpdateInput marks Schema, Model, Field, and Options invariant. Its update operations and create restrictions are unchanged.
Typed relation regression
tests/regression/test/issue-2778/schema.zmodel, tests/regression/test/issue-2778/schema.ts, tests/regression/test/issue-2778/regression.test.ts
The SQLite fixture defines related Tag and Product models. The regression test passes generated argument types to Tag upsert and update methods, then checks returned names and that the database contains two products.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 86600

The regression test checks the intended typed update and upsert calls. No actionable issue remains before merge.

Security Architecture Review

Security architecture risk: ⚪ Minimal · up to 86600

The change affects how TypeScript compares many-to-many update types. It leaves the available relation operations and their create restrictions unchanged, and no new runtime security risk was identified.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The changed annotation affects compile-time comparison of ORM input types; the examined production diff adds no runtime path for attacker-controlled input.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main code change and its purpose: adding variance annotations to ToManyRelationUpdateInput to prevent excessively deep TypeScript instantiation.
Linked Issues check ✅ Passed Issue #2778 requires generated upsert arguments to pass directly to the matching client method without TS2589. The PR adds in out annotations to all type parameters of ToManyRelationUpdateInput, m…
Out of Scope Changes check ✅ Passed The production change directly addresses the recursive type comparison in issue #2778. The added schema and regression test provide the many-to-many reproduction and verify the required upsert and upd…
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 3…
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Warning

Some tools did not complete. Review the errors below.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

packages/orm/src/client/crud-types.ts

ESLint skipped: missing config or dependency (missing-dependency). The ESLint configuration references a package that is not available in the sandbox.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ymc9
ymc9 merged commit f239b08 into dev Sep 25, 2026
8 checks passed
@ymc9
ymc9 deleted the fix/issue-2778-to-many-update-variance branch September 25, 2026 15:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant