Skip to content

Conversation

@ymc9
Copy link
Member

@ymc9 ymc9 commented Sep 10, 2025

Summary by CodeRabbit

  • Documentation

    • README updated: V3 marked as beta and markdown formatting improved.
  • Bug Fixes

    • Clearer, more accurate "Not Found" and validation behavior for nested create/connect/update/disconnect/delete operations, including owner/non-owner and many-to-many flows.
    • Improved handling of no-op disconnects to avoid unintended changes.
  • Tests

    • Added and reorganized policy and client tests covering nested relations, ownership scenarios, includes, and no-op semantics.

Copilot AI review requested due to automatic review settings September 10, 2025 22:45
@coderabbitai
Copy link

coderabbitai bot commented Sep 10, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Introduces a FromRelationContext to propagate and accumulate parent updates across nested relation operations, refactors connect/disconnect/create flows to record parentUpdates instead of immediate parent writes, adjusts NotFoundError targeting, updates dialect import paths and method visibility, and adds/reorganizes policy and client tests. README text formatting updated.

Changes

Cohort / File(s) Summary
Documentation
README.md
Switches V3 status from alpha to beta and changes a line prefix from -> to > (blockquote formatting).
Core CRUD operations
packages/runtime/src/client/crud/operations/base.ts
Adds exported FromRelationContext<Schema>; propagates a fromRelation context through nested relation processing; accumulates parentUpdates instead of performing immediate parent updates; refactors create/connect/disconnect/delete flows to merge parentUpdates and adjust NotFoundError/reporting.
CRUD dialects & exports
packages/runtime/src/client/crud/dialects/base-dialect.ts, packages/runtime/src/client/crud/dialects/index.ts, packages/runtime/src/client/crud/dialects/postgresql.ts, packages/runtime/src/client/crud/dialects/sqlite.ts, packages/runtime/src/client/options.ts
Renames/imports base dialect path to base-dialect; makes and/or/not methods public on BaseCrudDialect; updates import sites to the new module path.
Policy plugin and utils
packages/runtime/src/plugins/policy/policy-handler.ts, packages/runtime/src/plugins/policy/expression-transformer.ts, packages/runtime/src/plugins/policy/utils.ts
Updates BaseCrudDialect import path; in enforcePreCreatePolicyForOne aliases the VALUES-derived table in the FROM clause; other usages unchanged.
Tests — policy create & update
packages/runtime/test/policy/crud/create.test.ts, packages/runtime/test/policy/crud/update.test.ts
Adds nested create tests for owner/non-owner; reorganizes update tests into scalar/relation/nested/manipulation suites and adds many relation/connect/disconnect/create test cases and groupings.
Tests — client API update
packages/runtime/test/client-api/update.test.ts
Adds two nested to-one owning relation disconnect no-op scenarios (not matching filter / not connected) and adjusts expectations/labels.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60–90 minutes

Possibly related PRs

Pre-merge checks (2 passed, 1 warning)

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The current title "chore(policy): more test cases and update" mentions test additions but is too vague and understates the PR's scope; the changeset includes significant runtime changes (a new exported FromRelationContext type and rework of nested-relation parent-update logic), visibility changes on BaseCrudDialect methods, and several import-path refactors in addition to test and doc updates, so the title may mislead reviewers about the primary impact. Rename the PR to explicitly state the main code changes and tests (for example: "feat(crud): add FromRelationContext and rework nested-relation parent updates; update policy tests"), or split into two PRs (runtime/api changes vs tests/docs) so each title accurately reflects its scope; also add a short bullet list in the description summarizing the runtime API and behavioral changes to help reviewers.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.

Poem

I nibble at code, a tidy little hop,
Parent updates gather, no more sudden drop.
Connects and disconnects hop in tidy rows,
Tests bloom like clover where the rabbit goes.
README now says beta—ears up, tail a-flop. 🐇✨

✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/update-policy

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 and usage tips.

@claude
Copy link

claude bot commented Sep 10, 2025

Claude finished @ymc9's task —— View job


Pull Request Review

Reviewing PR #246: "chore(policy): more test cases and update"

Review Checklist

  • Read and analyze changed files
  • Review base.ts CRUD operations changes
  • Review test file additions and updates
  • Check code quality and best practices
  • Identify potential bugs or issues
  • Assess performance considerations
  • Review security concerns
  • Evaluate test coverage improvements
  • Provide comprehensive feedback

Summary

This PR adds extensive test coverage for ZenStack's policy engine and fixes several important bugs in relation handling within CRUD operations. The changes are well-structured and improve both reliability and test coverage.

Code Quality & Best Practices ✅

Positive aspects:

  • Well-organized test structure: The update tests are now properly categorized into logical groups (scalar conditions, relation conditions, nested updates, relation manipulation)
  • Comprehensive test coverage: New tests cover edge cases for nested creates, relation validation, and error handling
  • Consistent error handling: The base.ts changes improve error reporting by throwing appropriate NotFoundError exceptions
  • Clean async/await usage: Proper async handling in the updated code

Bug Fixes Identified ✅

The changes in base.ts fix several important issues:

  1. Missing async/await in parentUpdateTask (base.ts:284): Fixed incorrect sync function declaration
  2. Improved error handling in relation operations (base.ts:303-306, base.ts:1746-1748): Now properly throws NotFoundError when operations affect 0 rows
  3. Better validation in connectRelation (base.ts:1632-1635): Added null check for numAffectedRows before comparison
  4. Consistent error reporting (base.ts:1941, base.ts:1972): Now reports the correct model name in error messages

Test Coverage Improvements ✅

Excellent additions:

  • New nested create tests: Added comprehensive tests for owner-side and non-owner-side nested creation with policy validation
  • Enhanced update tests: Reorganized into logical groups with new test cases for:
    • Scalar condition validation
    • Relation condition checks (to-one, to-many with some/all/none operators)
    • Nested update scenarios
    • Relation manipulation (connect/disconnect/create)

Performance Considerations ✅

No performance concerns identified:

  • Changes are primarily bug fixes and don't introduce performance overhead
  • Test additions don't impact runtime performance
  • Error handling improvements are minimal overhead

Security Assessment ✅

Strong security posture:

  • All changes maintain proper access control validation
  • Policy enforcement is correctly tested with auth context
  • No potential for privilege escalation or data leakage
  • Error messages don't expose sensitive information

Potential Issues & Recommendations

Minor Issues:

  1. Typo in test description (update.test.ts:5): "Scala" should be "Scalar"
  2. Code comments: The base.ts changes could benefit from brief comments explaining the error handling improvements

Recommendations:

  1. Consider adding JSDoc comments for the new error handling logic in relation operations
  2. Test edge cases: Consider adding tests for concurrent relation updates
  3. Consistency check: Verify all similar patterns in the codebase follow the same error handling approach

Files Analysis

README.md (1 line change)

  • Minor version or documentation update - appears benign

packages/runtime/src/client/crud/operations/base.ts (25 additions, 8 deletions)

  • High impact, low risk changes
  • Fixes critical bugs in relation handling
  • Improves error reporting consistency
  • All changes follow existing patterns

packages/runtime/test/policy/crud/create.test.ts (67 additions)

  • Excellent test additions
  • Covers nested create scenarios comprehensively
  • Good use of policy validation testing

packages/runtime/test/policy/crud/update.test.ts (523 additions, 62 deletions)

  • Major test coverage improvement
  • Well-organized test structure
  • Comprehensive coverage of update scenarios

Verdict: ✅ APPROVED

This is a high-quality PR that:

  • Fixes important bugs in relation handling
  • Significantly improves test coverage
  • Maintains code quality standards
  • Enhances the reliability of the policy engine

The changes are well-tested, follow good practices, and address real issues without introducing new risks.


Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR focuses on expanding test coverage for policy-based CRUD operations by adding comprehensive test cases across multiple scenarios and fixing some edge cases.

  • Restructures update tests into logical groups (scalar conditions, relation conditions, nested updates, relation manipulations)
  • Adds extensive test coverage for relation-based policy conditions including one-to-one and one-to-many relationships
  • Fixes error handling in relation connect/disconnect operations to properly throw NotFoundError

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
packages/runtime/test/policy/crud/update.test.ts Reorganizes tests into nested describe blocks and adds comprehensive relation condition and manipulation tests
packages/runtime/test/policy/crud/create.test.ts Adds test cases for nested create operations with policy validation
packages/runtime/src/client/crud/operations/base.ts Fixes error handling in relation operations to properly throw NotFoundError when updates fail
README.md Updates project status from alpha to beta phase

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/runtime/src/client/crud/operations/base.ts (1)

284-307: Avoid BigInt/Number pitfalls and false NotFound on parent update

numAffectedRows can be bigint/number/undefined depending on dialect. Convert before comparisons.

Apply this diff:

-                        const result = await this.executeQuery(kysely, query, 'update');
-                        if (!result.numAffectedRows) {
+                        const result = await this.executeQuery(kysely, query, 'update');
+                        const affected = Number(result.numAffectedRows ?? 0);
+                        if (affected === 0) {
                             throw new NotFoundError(fromRelation.model);
                         }
🧹 Nitpick comments (8)
README.md (1)

24-24: LGTM: status notice updated to beta

Doc-only change looks good. Minor nit: consider adding an approximate timeline or criteria for exiting beta to set expectations.

packages/runtime/test/policy/crud/create.test.ts (1)

244-275: Add a negative case for mismatched auth on nested create (non-owner side)

To harden this, add one more assertion where auth.id differs from the nested User.id (should be rejected by policy).

Apply this diff to extend the test:

@@
         ).resolves.toMatchObject({
             id: 1,
             user: {
                 id: 1,
             },
         });
+
+        // negative: auth mismatch should be rejected
+        await expect(
+            db
+                .$setAuth({ id: 2 })
+                .profile.create({ data: { id: 2, user: { create: { id: 1 } } } }),
+        ).toBeRejectedByPolicy();
packages/runtime/test/policy/crud/update.test.ts (6)

5-5: Typo: “Scala” → “Scalar”.

Rename the group for clarity.

-describe('Scala condition tests', () => {
+describe('Scalar condition tests', () => {

102-125: Minor: also assert state unchanged after denied auth.

After the first rejected update with auth x:0, optionally verify the record remains x:1 for completeness.

             await db.foo.create({ data: { id: 1, x: 1 } });
             await expect(db.$setAuth({ x: 0 }).foo.update({ where: { id: 1 }, data: { x: 2 } })).toBeRejectedNotFound();
+            await expect(db.foo.findUnique({ where: { id: 1 } })).resolves.toMatchObject({ x: 1 });

159-187: Test name mismatch: this is the non-owner side case.

Here User holds the FK; rename to avoid confusion with the previous test.

-it('works with to-one relation check owner side', async () => {
+it('works with to-one relation check non-owner side', async () => {

431-504: Behavior check: disconnect is silently ignored when nested target update is disallowed.

Line 489 comment expects no-op rather than rejection. Is this divergence (ignore vs reject) intentional for owner-side disconnect when the related model denies update? If yes, consider adding a short comment explaining the policy rationale to future readers.


506-578: Add post-rejection assertion to ensure relation unchanged.

After the rejected disconnect (Lines 563-569), assert the relation still exists.

             await expect(
                 db.user.update({
                     where: { id: 2 },
                     data: { profile: { disconnect: true } },
                     include: { profile: true },
                 }),
             ).toBeRejectedNotFound();
+            await expect(
+                db.user.findUnique({ where: { id: 2 }, include: { profile: true } })
+            ).resolves.toMatchObject({ profile: { id: 2 } });

581-583: Track placeholders or convert to TODOs.

Either add TODO comments with issue links or remove the empty suites to reduce noise. I can help scaffold upsert/update-many tests.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f01bcde and f13b92f.

📒 Files selected for processing (4)
  • README.md (1 hunks)
  • packages/runtime/src/client/crud/operations/base.ts (11 hunks)
  • packages/runtime/test/policy/crud/create.test.ts (1 hunks)
  • packages/runtime/test/policy/crud/update.test.ts (3 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
{packages,samples,tests}/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place packages only under packages/, samples/, or tests/

Files:

  • packages/runtime/test/policy/crud/update.test.ts
  • packages/runtime/src/client/crud/operations/base.ts
  • packages/runtime/test/policy/crud/create.test.ts
🧬 Code graph analysis (3)
packages/runtime/test/policy/crud/update.test.ts (1)
packages/runtime/test/policy/utils.ts (1)
  • createPolicyTestClient (14-25)
packages/runtime/src/client/crud/operations/base.ts (3)
packages/runtime/src/client/errors.ts (1)
  • NotFoundError (27-31)
packages/sdk/src/schema/schema.ts (1)
  • GetModels (108-108)
packages/runtime/src/client/query-utils.ts (1)
  • getManyToManyRelation (238-274)
packages/runtime/test/policy/crud/create.test.ts (1)
packages/runtime/test/policy/utils.ts (1)
  • createPolicyTestClient (14-25)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: claude-review
  • GitHub Check: build-test (20.x)
🔇 Additional comments (13)
packages/runtime/test/policy/crud/create.test.ts (1)

210-242: Good coverage for nested create on the owner side

The auth gating and include shape checks look solid.

packages/runtime/src/client/crud/operations/base.ts (1)

1906-1973: Nice: clearer NotFound target via deleteFromModel

Using deleteFromModel improves error specificity across m2m/non-m2m paths. Count-based validation also already normalizes to number.

packages/runtime/test/policy/crud/update.test.ts (11)

6-29: LGTM: scalar field check flow is correct.

Covers deny/allow paths and QB row-count; assertions look solid.


31-47: LGTM: this.x member check behaves as expected.


49-62: LGTM: default deny is enforced.


64-79: LGTM: deny rule precedence validated.


81-100: LGTM: mixed allow/deny logic validated (edge at x==1 blocked).


128-157: LGTM: to-one relation check (owner side where Profile holds FK).


189-232: LGTM: to-many “some” semantics covered across 0/1/≥2 posts.


234-286: Confirm vacuous truth semantics for “all”.

You allow updates when there are no posts (Line 257). Please confirm this “all over empty set = true” behavior is intended for policy evaluation.


288-340: Confirm vacuous truth semantics for “none”.

You allow updates when there are no posts (Line 311). Verify “none over empty set = true” is the desired rule.


343-385: LGTM: nested update via owner side blocked by target rule; include shape verified.


387-428: LGTM: nested update via non-owner side mirrors owner-side behavior.

@claude
Copy link

claude bot commented Sep 12, 2025

Claude finished @ymc9's task —— View job


Pull Request Review

Reviewing PR #246: "chore(policy): more test cases and update"

Review Checklist

  • Read and analyze changed files
  • Review base.ts CRUD operations changes
  • Review test file additions and updates
  • Check code quality and best practices
  • Identify potential bugs or issues
  • Assess performance considerations
  • Review security concerns
  • Evaluate test coverage improvements
  • Provide comprehensive feedback

Summary

This PR adds extensive test coverage for ZenStack's policy engine and fixes several important bugs in relation handling within CRUD operations. The changes are well-structured and improve both reliability and test coverage.

Code Quality & Best Practices ✅

Positive aspects:

  • Well-organized test structure: The update tests are now properly categorized into logical groups (scalar conditions, relation conditions, nested updates, relation manipulation)
  • Comprehensive test coverage: New tests cover edge cases for nested creates, relation validation, and error handling
  • Consistent error handling: The base.ts changes improve error reporting by throwing appropriate NotFoundError exceptions
  • Clean async/await usage: Proper async handling in the updated code

Bug Fixes Identified ✅

The changes in base.ts fix several important issues:

  1. Missing async/await in parentUpdateTask (base.ts:284): Fixed incorrect sync function declaration
  2. Improved error handling in relation operations (base.ts:303-306, base.ts:1746-1748): Now properly throws NotFoundError when operations affect 0 rows
  3. Better validation in connectRelation (base.ts:1632-1635): Added null check for numAffectedRows before comparison
  4. Consistent error reporting (base.ts:1941, base.ts:1972): Now reports the correct model name in error messages

Test Coverage Improvements ✅

Excellent additions:

  • New nested create tests: Added comprehensive tests for owner-side and non-owner-side nested creation with policy validation
  • Enhanced update tests: Reorganized into logical groups with new test cases for:
    • Scalar condition validation
    • Relation condition checks (to-one, to-many with some/all/none operators)
    • Nested update scenarios
    • Relation manipulation (connect/disconnect/create)

Performance Considerations ✅

No performance concerns identified:

  • Changes are primarily bug fixes and don't introduce performance overhead
  • Test additions don't impact runtime performance
  • Error handling improvements are minimal overhead

Security Assessment ✅

Strong security posture:

  • All changes maintain proper access control validation
  • Policy enforcement is correctly tested with auth context
  • No potential for privilege escalation or data leakage
  • Error messages don't expose sensitive information

Potential Issues & Recommendations

Minor Issues:

  1. Typo in test description (update.test.ts:5): "Scala" should be "Scalar"
  2. Code comments: The base.ts changes could benefit from brief comments explaining the error handling improvements

Recommendations:

  1. Consider adding JSDoc comments for the new error handling logic in relation operations
  2. Test edge cases: Consider adding tests for concurrent relation updates
  3. Consistency check: Verify all similar patterns in the codebase follow the same error handling approach

Files Analysis

README.md (1 line change)

  • Minor version or documentation update - appears benign

packages/runtime/src/client/crud/operations/base.ts (25 additions, 8 deletions)

  • High impact, low risk changes
  • Fixes critical bugs in relation handling
  • Improves error reporting consistency
  • All changes follow existing patterns

packages/runtime/test/policy/crud/create.test.ts (67 additions)

  • Excellent test additions
  • Covers nested create scenarios comprehensively
  • Good use of policy validation testing

packages/runtime/test/policy/crud/update.test.ts (523 additions, 62 deletions)

  • Major test coverage improvement
  • Well-organized test structure
  • Comprehensive coverage of update scenarios

Verdict: ✅ APPROVED

This is a high-quality PR that:

  • Fixes important bugs in relation handling
  • Significantly improves test coverage
  • Maintains code quality standards
  • Enhances the reliability of the policy engine

The changes are well-tested, follow good practices, and address real issues without introducing new risks.


Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
packages/runtime/src/client/crud/operations/base.ts (2)

1589-1596: Normalize numAffectedRows; avoid false NotFound on no-op updates

Drivers may return bigint/undefined or 0 when values don’t change. Normalize before comparing; treat 0 carefully to prevent spurious NotFound.

-                const updateResult = await this.executeQuery(kysely, query, 'connect');
-
-                // validate connect result
-                if (!updateResult.numAffectedRows || _data.length > updateResult.numAffectedRows) {
-                    // some entities were not connected
-                    throw new NotFoundError(model);
-                }
+                const updateResult = await this.executeQuery(kysely, query, 'connect');
+                const affected = Number(updateResult.numAffectedRows ?? 0);
+                if (affected === 0 || _data.length > affected) {
+                    // some entities may not have been connected (or no-op)
+                    throw new NotFoundError(model);
+                }

Run to find other direct numAffectedRows comparisons:

#!/bin/bash
rg -nP -C2 'numAffectedRows\s*[<>=]|!\s*[\w.]*numAffectedRows' packages/runtime/src

1837-1843: Also normalize numAffectedRows in setRelation connect validation

Same issue as connectRelation; compare using a normalized number.

-                const r = await this.executeQuery(kysely, query, 'connect');
-
-                // validate result
-                if (!r.numAffectedRows || _data.length > r.numAffectedRows) {
+                const r = await this.executeQuery(kysely, query, 'connect');
+                const affected = Number(r.numAffectedRows ?? 0);
+                if (affected === 0 || _data.length > affected) {
                     // some entities were not connected
                     throw new NotFoundError(model);
                 }
🧹 Nitpick comments (6)
packages/runtime/test/client-api/update.test.ts (1)

1846-1856: Assert state after no-op disconnect

Strengthen the test by asserting the relation remains unchanged (null) instead of only resolving truthy.

Apply this diff:

-            await expect(
-                client.profile.update({
-                    where: { id: profile.id },
-                    data: {
-                        user: {
-                            disconnect: { id: '2' },
-                        },
-                    },
-                }),
-            ).toResolveTruthy();
+            await expect(
+                client.profile.update({
+                    where: { id: profile.id },
+                    data: {
+                        user: {
+                            disconnect: { id: '2' },
+                        },
+                    },
+                    include: { user: true },
+                }),
+            ).resolves.toMatchObject({ user: null });
packages/runtime/src/client/crud/dialects/base-dialect.ts (1)

1107-1131: Make logical combinators short-circuit to reduce SQL node size

Public exposure looks fine. Add quick checks for true/false to avoid building unnecessary AND/OR trees, especially on deeply nested filters.

Apply this diff:

-    and(eb: ExpressionBuilder<any, any>, ...args: Expression<SqlBool>[]) {
+    and(eb: ExpressionBuilder<any, any>, ...args: Expression<SqlBool>[]) {
+        if (args.some((arg) => this.isFalse(arg))) {
+            return this.false(eb);
+        }
         const nonTrueArgs = args.filter((arg) => !this.isTrue(arg));
         if (nonTrueArgs.length === 0) {
             return this.true(eb);
         } else if (nonTrueArgs.length === 1) {
             return nonTrueArgs[0]!;
         } else {
             return eb.and(nonTrueArgs);
         }
     }
 
-    or(eb: ExpressionBuilder<any, any>, ...args: Expression<SqlBool>[]) {
+    or(eb: ExpressionBuilder<any, any>, ...args: Expression<SqlBool>[]) {
+        if (args.some((arg) => this.isTrue(arg))) {
+            return this.true(eb);
+        }
         const nonFalseArgs = args.filter((arg) => !this.isFalse(arg));
         if (nonFalseArgs.length === 0) {
             return this.false(eb);
         } else if (nonFalseArgs.length === 1) {
             return nonFalseArgs[0]!;
         } else {
             return eb.or(nonFalseArgs);
         }
     }
 
     not(eb: ExpressionBuilder<any, any>, ...args: Expression<SqlBool>[]) {
         return eb.not(this.and(eb, ...args));
     }
packages/runtime/test/policy/crud/update.test.ts (1)

159-187: Duplicate test name — clarify owner vs non-owner case

Both tests are titled “works with to-one relation check owner side”. Rename this one to disambiguate.

-        it('works with to-one relation check owner side', async () => {
+        it('works with to-one relation check owner side (FK on parent)', async () => {
packages/runtime/src/client/crud/operations/base.ts (3)

68-78: Tighten FromRelationContext typings

ids is always a bag of PK fields; prefer a precise shape over any.

 export type FromRelationContext<Schema extends SchemaDef> = {
     // the model where the relation field is defined
     model: GetModels<Schema>;
     // the relation field name
     field: string;
     // the parent entity's id fields and values
-    ids: any;
+    ids: Record<string, unknown>;
     // for relations owned by model, record the parent updates needed after the relation is processed
-    parentUpdates: Record<string, unknown>;
+    parentUpdates: Record<string, unknown>;
 };

1024-1038: Guard against conflicting parentUpdates vs explicit field updates

Merging parentUpdates into updateFields can silently override user-specified updates to FK fields. Detect and fail fast to avoid surprising writes.

-                if (Object.keys(parentUpdates).length > 0) {
-                    // merge field updates propagated from nested relation processing
-                    Object.assign(updateFields, parentUpdates);
-                }
+                if (Object.keys(parentUpdates).length > 0) {
+                    // avoid silent override of explicit updates
+                    const conflicts = Object.keys(parentUpdates).filter(
+                        (k) => k in updateFields && updateFields[k] !== parentUpdates[k],
+                    );
+                    if (conflicts.length > 0) {
+                        throw new QueryError(`Conflicting updates on fields: ${conflicts.join(', ')}`);
+                    }
+                    Object.assign(updateFields, parentUpdates);
+                }

1464-1494: Consider wrapping nested relation ops and parent update in one transaction

connect/disconnect/create/delete across relations plus the final parent update can span multiple statements. Use safeTransaction at the update() caller to ensure atomicity and prevent partial state if an intermediate step fails.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f13b92f and 9ef7e18.

📒 Files selected for processing (11)
  • packages/runtime/src/client/crud/dialects/base-dialect.ts (3 hunks)
  • packages/runtime/src/client/crud/dialects/index.ts (1 hunks)
  • packages/runtime/src/client/crud/dialects/postgresql.ts (1 hunks)
  • packages/runtime/src/client/crud/dialects/sqlite.ts (1 hunks)
  • packages/runtime/src/client/crud/operations/base.ts (20 hunks)
  • packages/runtime/src/client/options.ts (1 hunks)
  • packages/runtime/src/plugins/policy/expression-transformer.ts (1 hunks)
  • packages/runtime/src/plugins/policy/policy-handler.ts (2 hunks)
  • packages/runtime/src/plugins/policy/utils.ts (1 hunks)
  • packages/runtime/test/client-api/update.test.ts (2 hunks)
  • packages/runtime/test/policy/crud/update.test.ts (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • packages/runtime/src/plugins/policy/expression-transformer.ts
🧰 Additional context used
📓 Path-based instructions (1)
{packages,samples,tests}/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place packages only under packages/, samples/, or tests/

Files:

  • packages/runtime/src/client/crud/dialects/sqlite.ts
  • packages/runtime/src/client/crud/dialects/postgresql.ts
  • packages/runtime/src/client/options.ts
  • packages/runtime/src/plugins/policy/utils.ts
  • packages/runtime/src/client/crud/dialects/index.ts
  • packages/runtime/test/client-api/update.test.ts
  • packages/runtime/src/client/crud/dialects/base-dialect.ts
  • packages/runtime/src/plugins/policy/policy-handler.ts
  • packages/runtime/src/client/crud/operations/base.ts
  • packages/runtime/test/policy/crud/update.test.ts
🧬 Code graph analysis (2)
packages/runtime/src/client/crud/operations/base.ts (5)
packages/sdk/src/schema/schema.ts (2)
  • SchemaDef (10-18)
  • GetModels (108-108)
packages/common-helpers/src/tiny-invariant.ts (1)
  • invariant (4-14)
packages/runtime/src/client/errors.ts (1)
  • NotFoundError (27-31)
packages/runtime/src/utils/object-utils.ts (1)
  • fieldsToSelectObject (11-13)
packages/runtime/src/client/query-utils.ts (1)
  • getManyToManyRelation (238-274)
packages/runtime/test/policy/crud/update.test.ts (1)
packages/runtime/test/policy/utils.ts (1)
  • createPolicyTestClient (14-25)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: claude-review
  • GitHub Check: build-test (20.x)
🔇 Additional comments (11)
packages/runtime/test/client-api/update.test.ts (1)

1818-1832: LGTM: added no-op disconnect when filter doesn’t match

The scenario accurately guards against unintended disconnects and validates include shape.

packages/runtime/src/plugins/policy/utils.ts (1)

15-15: Import path alignment to base-dialect

Consistent with the repo-wide migration. No behavior change.

packages/runtime/src/client/crud/dialects/postgresql.ts (1)

23-23: Import path updated to base-dialect

Looks correct and consistent with other dialects.

packages/runtime/src/client/crud/dialects/sqlite.ts (1)

23-23: Import path updated to base-dialect

Consistent with the rest of the changes.

packages/runtime/src/client/crud/dialects/index.ts (1)

4-4: Import type path update

Matches the new module location; factory remains unchanged.

packages/runtime/src/client/options.ts (1)

5-5: Type import path update

Aligned with base-dialect; no API/signature changes.

packages/runtime/src/plugins/policy/policy-handler.ts (2)

34-34: Import path updated to base-dialect

Consistent with other files; no functional impact.


183-188: Alias VALUES subquery for portability

Aliasing the VALUES-derived table fixes dialect differences (notably SQLite requires an alias). Good call.

Please run the policy pre-create tests against both SQLite and PostgreSQL to confirm consistent behavior of the aliased VALUES subquery.

packages/runtime/test/policy/crud/update.test.ts (2)

129-157: LGTM: solid coverage for to-one owner-side policy

The scenario and assertions align with the intended owner-side permission semantics.


234-286: LGTM: to-many all/none/some quantifiers behave as expected

The combination of initial, mixed, and all-true cases exercises policy evaluation well.

packages/runtime/src/client/crud/operations/base.ts (1)

1679-1716: LGTM: owner-side disconnect records parent FK nullification safely

The read-then-check pattern before recording parentUpdates avoids unnecessary writes when already disconnected.

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