Skip to content

Conversation

@ymc9
Copy link
Member

@ymc9 ymc9 commented Aug 26, 2025

Summary by CodeRabbit

  • New Features
    • Added before/after entity mutation hooks with optional in-transaction execution.
    • Added a way to explicitly force a transactional context on the client.
  • Improvements
    • Commit callback errors are logged and no longer fail the whole commit.
    • More robust mutation processing and entity loading around transactions.
  • Refactor
    • Centralized internal alias handling for query mapping.
  • Documentation
    • TODOs reorganized: renamed/renamed plugin, removed Infra section, clarified extensibility; filtering defaults adjusted and JSON/full-text filtering added.
  • Tests
    • Expanded suites to run across SQLite and PostgreSQL.
  • Chores
    • Bumped packages to 3.0.0-alpha.32 and updated review base branches.

github-actions bot and others added 3 commits August 26, 2025 10:57
)

* refactor: clean up transaction semantics for entity mutation hooks

* addressing review comments

* update
* refactor: remove filter from entity mutation hooks

* addressing comments

* update
Copilot AI review requested due to automatic review settings August 26, 2025 10:18
@coderabbitai
Copy link

coderabbitai bot commented Aug 26, 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

Adds reviews.auto_review.base_branches to .coderabbit.yaml, updates TODO.md content, bumps many package versions to 3.0.0-alpha.32, and makes runtime changes: new ClientImpl.forceTransaction, a stripAlias util and name-mapper refactor, safer transaction commit callbacks, a large mutation-hook and executor refactor (before/after hooks + constructor flag), and expanded multi-provider tests.

Changes

Cohort / File(s) Summary
Config & Docs
\.coderabbit.yaml, TODO.md
Added reviews.auto_review.base_branches: ['dev','main']; edited TODO entries (renames/removals, filtering/Extensibility tweaks).
Version Bumps
package.json, packages/*/package.json, samples/blog/package.json, tests/e2e/package.json
Updated package versions 3.0.0-alpha.31 → 3.0.0-alpha.32 across root, many packages (cli, common-helpers, create-zenstack, dialects/sql.js, eslint-config, ide/vscode, language, runtime, sdk, tanstack-query, testtools, typescript-config, vitest-config, zod), sample, and e2e.
Runtime: Client API
packages/runtime/src/client/client-impl.ts
Added public method forceTransaction() that replaces internal Kysely instance with a Transaction when not already in a transaction.
Runtime: Transactions Defaults
packages/runtime/src/client/crud/operations/base.ts
Imported TransactionIsolationLevel and set default isolation to TransactionIsolationLevel.RepeatableRead.
Runtime: Kysely Utils
packages/runtime/src/client/executor/kysely-utils.ts
New exported helper stripAlias(node: OperationNode) returning { alias, node }, asserting alias is an IdentifierNode when present.
Runtime: Name Mapper Refactor
packages/runtime/src/client/executor/name-mapper.ts
Replaced class-local alias logic with shared stripAlias util; removed private stripAlias; added guard for undefined table in update handling; updated multiple transformer methods.
Runtime: Driver Commit Callbacks
packages/runtime/src/client/executor/zenstack-driver.ts
commitTransaction now wraps each registered callback in try/catch and logs errors instead of letting a callback rejection fail the overall commit.
Runtime: Query Executor & Hooks
packages/runtime/src/client/executor/zenstack-query-executor.ts, packages/runtime/src/client/plugin.ts
Replaced mutation interception with beforeEntityMutation/afterEntityMutation hooks and runAfterMutationWithinTransaction flag; executor constructor gains suppressMutationHooks param; refactored mutation orchestration, connection scoping, entity loaders, returning handling, error paths, and integrated stripAlias for model extraction. Plugin types/signatures updated accordingly.
Tests: Name Mapping
packages/runtime/test/client-api/name-mapping.test.ts
Parameterized tests to run against sqlite and postgresql, using createTestClient/TEST_DB; expanded coverage for ORM and query-builder flows.
Tests: Entity Mutation Hooks
packages/runtime/test/plugin/entity-mutation-hooks.test.ts
Reworked to new hook API, parameterized providers, added transactional/error/entity-loading scenarios; uses createTestClient test utility.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

In burrows of code I twitch my nose,
Hooks hop before and after the rows.
Aliases fall like autumn leaves,
Transactions hum while callback greaves.
Two fields tested — SQLite and PG, I cheer v3.0.0-alpha.32! 🐇✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@claude
Copy link

claude bot commented Aug 26, 2025

Claude finished @ymc9's task —— View job


Pull Request Review

I've completed a comprehensive review of this dev -> main merge PR. Here are my findings:

Review Tasks:

  • Analyze package.json version updates
  • Review core runtime changes (executor, plugin system)
  • Examine new kysely-utils.ts file
  • Review name-mapper.ts refactoring
  • Analyze zenstack-query-executor.ts changes
  • Check plugin.ts modifications
  • Review test coverage changes
  • Identify potential bugs or security concerns
  • Assess performance implications

✅ Code Quality & Best Practices

Positive aspects:

  • Clean refactoring: The name mapper implementation is well-structured with clear separation of concerns
  • Comprehensive utility functions: New kysely-utils.ts provides clean abstraction for alias handling
  • Proper error handling: Good use of try-catch blocks and proper error propagation in query executor
  • Type safety: Strong TypeScript usage throughout with proper generics and type guards
  • Documentation: Well-documented plugin interfaces with clear JSDoc comments

⚠️ Potential Issues & Concerns

1. Transaction Complexity (zenstack-query-executor.ts:200-285)
The transaction handling logic is quite complex with nested conditionals. Consider extracting the transaction execution logic into separate methods for better maintainability.

2. Memory Management

  • Entity caching in beforeMutationEntities could accumulate memory in long-running transactions
  • The scope stack in QueryNameMapper could grow large for deeply nested queries

3. Error Context Loss
In zenstack-query-executor.ts:287-290, the original error context might be lost when wrapping in QueryError. Consider preserving the original error type and stack trace.

🚀 Performance Considerations

Improvements:

  • Lazy loading: beforeMutationEntities are cached and loaded only when needed
  • Scope optimization: Name mapper uses efficient reverse iteration through scopes
  • Query compilation caching: Compiled queries are properly cached

Potential concerns:

  • Multiple passes through the AST for name mapping could be expensive for complex queries
  • The hasMappedColumns check iterates through all field mappings each time

🔒 Security Assessment

No security issues identified, but good defensive practices observed:

  • Proper SQL injection prevention through parameterized queries
  • Transaction isolation handled correctly
  • No exposure of sensitive data in error messages

🧪 Test Coverage

Strong test improvements:

  • Comprehensive entity mutation hooks testing with 642 new lines
  • Transaction semantics properly tested
  • Both SQLite and PostgreSQL providers covered
  • Edge cases for nested transactions and rollbacks tested

Minor gap: The new kysely-utils.ts appears to lack dedicated unit tests, though it's likely covered indirectly.

📋 Additional Observations

Version Management: All packages properly bumped to 3.0.0-alpha.32 - good consistency.

TODO Updates: The TODO.md shows good progress tracking with recent completions in plugin and transaction areas.

Plugin Architecture: The refactored plugin system provides cleaner separation between ORM-level and Kysely-level hooks, which is a solid architectural improvement.

🎯 Recommendations

  1. Extract transaction logic: Break down the complex transaction handling in proceedQuery method
  2. Add unit tests: Consider adding specific tests for kysely-utils.ts functions
  3. Performance monitoring: Monitor the name mapping performance on complex schemas
  4. Error handling: Preserve more context when wrapping errors

Overall Assessment

This is a well-executed merge with solid refactoring that improves the codebase architecture. The changes demonstrate good engineering practices with comprehensive testing and proper type safety. The complexity is manageable and the benefits outweigh the concerns.

Recommendation: ✅ Approve for merge


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 merges dev branch to main, updating package versions from 3.0.0-alpha.31 to 3.0.0-alpha.32 across the entire project and includes significant enhancements to the entity mutation hooks system.

  • Version bump for all packages to alpha.32
  • Major refactoring of entity mutation hooks to support more flexible async data loading patterns
  • Enhanced testing coverage with multi-provider support (SQLite and PostgreSQL)

Reviewed Changes

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

Show a summary per file
File Description
package.json Version bump to 3.0.0-alpha.32
packages/*/package.json Consistent version updates across all packages
packages/runtime/src/client/plugin.ts API redesign for entity mutation hooks with new async loading functions
packages/runtime/src/client/executor/zenstack-query-executor.ts Complete refactor of mutation hook execution logic with transaction support
packages/runtime/test/plugin/entity-mutation-hooks.test.ts Extensive test updates with multi-provider support and new hook functionality
packages/runtime/test/client-api/name-mapping.test.ts Test updates for multi-provider support
TODO.md Documentation updates reflecting completed features
.coderabbit.yaml Configuration update for code review base branches
Comments suppressed due to low confidence (1)

packages/runtime/src/client/executor/zenstack-query-executor.ts:84

  • The error handling strategy for commit callbacks should be more explicit. Consider adding configuration options to control whether commit callback errors should fail the transaction or just be logged, as different applications may have different requirements.
        const hooks: OnKyselyQueryCallback<Schema>[] = [];

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: 5

🧹 Nitpick comments (19)
packages/tanstack-query/package.json (1)

4-7: Optional: add a short description

Even though the package is private, a brief description helps new contributors and improves workspace listing clarity.

-    "description": "",
+    "description": "Adapters/utilities for integrating ZenStack with TanStack Query (React Query)",
packages/runtime/src/client/crud/operations/base.ts (1)

292-293: Default isolation level: sensible, but confirm dialect support paths

Defaulting to Repeatable Read is reasonable. For engines that don’t support configurable isolation (e.g., some SQLite setups), Kysely typically no-ops, but verify that our drivers don’t throw. If there’s any risk, consider conditionally setting the isolation level based on dialect.

packages/runtime/src/client/executor/zenstack-driver.ts (1)

82-87: Swallowing post-commit callback errors hides failures; log via driver and consider surfacing aggregated errors

Catching and console.error-ing commit-callback failures prevents callers and observability pipelines from noticing issues. Two improvements:

  • Use the driver’s logger (#log.error) for consistency with query/error logs.
  • Optionally aggregate callback errors and expose them (e.g., by throwing a non-fatal PostCommitCallbackError after commit or returning them via a side-channel) so callers can act on them while keeping the commit intact.
-                    } catch (err) {
-                        // errors in commit callbacks are logged but do not fail the commit
-                        console.error(`Error executing transaction commit callback: ${err}`);
-                    }
+                    } catch (error) {
+                        // errors in commit callbacks are logged but do not fail the commit
+                        await this.#log.error(() => ({
+                            level: 'error',
+                            message: 'Error executing transaction commit callback',
+                            error,
+                        }));
+                    }

Please confirm behavior change is intentional and documented as a breaking semantics change (callbacks no longer abort commit). Also ensure tests cover both: (1) commit succeeds, (2) callback failures are observable via logging.

packages/runtime/src/client/executor/kysely-utils.ts (1)

1-14: Good centralization of alias handling; add explicit return type for stronger contracts

The helper looks solid and the invariant improves safety. Minor polish: declare the return type to help downstream inference and avoid accidental widening.

-export function stripAlias(node: OperationNode) {
+export function stripAlias(node: OperationNode): { alias?: string; node: OperationNode } {
     if (AliasNode.is(node)) {
         invariant(IdentifierNode.is(node.alias), 'Expected identifier as alias');
         return { alias: node.alias.name, node: node.node };
     } else {
         return { alias: undefined, node };
     }
 }
packages/runtime/src/client/executor/name-mapper.ts (2)

98-106: Prefer safe narrowing without non-null assertion in transformJoin

innerNode! is unnecessary and weakens type safety. Let the type guard handle undefined.

-        const { alias, node: innerNode } = stripAlias(node.table);
-        if (TableNode.is(innerNode!)) {
+        const { alias, node: innerNode } = stripAlias(node.table);
+        if (innerNode && TableNode.is(innerNode)) {
             const modelName = innerNode.table.identifier.name;
             if (this.hasMappedColumns(modelName)) {
                 // create a nested query with all fields selected and names mapped
                 const select = this.createSelectAll(modelName);
                 return { ...super.transformJoin(node), table: this.wrapAlias(select, alias ?? modelName) };
             }
         }

301-315: createScopesFromFroms: clear scope extraction; minor optional micro-optimization

Implementation is fine. If desired, you can avoid the filter(Boolean) cast by returning Scope | undefined type and narrowing, but this is purely optional here.

packages/runtime/test/client-api/name-mapping.test.ts (2)

7-8: Use per-provider, unique database names to avoid cross-run/test interference

A single TEST_DB for both sqlite and postgresql risks collisions across providers or parallel runs. Consider parameterizing by provider and adding a random/worker suffix.

-const TEST_DB = 'client-api-name-mapper-test';
+const TEST_DB_BASE = 'client-api-name-mapper-test';

And inside each provider block (see the beforeEach below):

const dbName = `${TEST_DB_BASE}-${provider}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;

Then pass dbName to createTestClient.

If your CI runs tests in parallel, this change prevents spurious failures due to DB reuse.


146-158: Reduce flakiness: don’t couple Post.id to User.id in nested select assertion

SELECT ... FROM Post returning id compared to user.id assumes both sequences align. It happens to pass with fresh DBs, but it’s brittle. Assert expect.any(Number) instead.

-            ).resolves.toMatchObject({
-                id: user.id,
-                title: 'Post1',
-                email: '[email protected]',
-            });
+            ).resolves.toMatchObject({
+                id: expect.any(Number),
+                title: 'Post1',
+                email: '[email protected]',
+            });
packages/runtime/test/plugin/entity-mutation-hooks.test.ts (4)

7-8: DB name reuse may cause cross-test interference on PostgreSQL in parallel runs

Using a single constant DB name for all tests can lead to DROP/CREATE clashes or flaky isolation when tests run in parallel across files/workers. Consider appending a per-worker suffix (e.g., process.env.VITEST_WORKER_ID) or a timestamp for Postgres runs, while still cleaning up in beforeEach/afterAll to avoid DB bloat.

Would you confirm your Vitest concurrency settings and whether other test files reuse the same TEST_DB? If parallelism is enabled, I can propose a worker-scoped naming helper.

Example patch:

-const TEST_DB = 'client-api-entity-mutation-hooks-test';
+const TEST_DB = `client-api-entity-mutation-hooks-test-${process.env.VITEST_WORKER_ID ?? 'w0'}`;

14-18: Potentially heavy Postgres setup in beforeEach

createTestClient drops/recreates the database for provider='postgresql' on each test. Great for isolation, but slow for large suites. If runtime allows, moving DB bootstrap to beforeAll and truncating tables between tests can significantly speed up CI. Keeping it as-is is acceptable if current runtime is within SLA.


210-248: Avoid relying on index 0 for nested after-entities

Using afterEntities![0] assumes a single-entity result and ordering. Prefer asserting arrayContaining with title match to avoid brittleness if future changes return multiple rows or different order.

Example:

-const afterEntities = await args.loadAfterMutationEntities();
-if ((afterEntities![0] as any).title === 'Post1') { ... }
+const afterEntities = (await args.loadAfterMutationEntities()) ?? [];
+if (afterEntities.some((e: any) => e.title === 'Post1')) { ... }

358-376: Comment contradicts the assertion; fix wording

The comment says "mutation is persisted" but the assertion checks that nothing was persisted. Update the comment for clarity.

-                // mutation is persisted
+                // mutation is NOT persisted
packages/runtime/src/client/plugin.ts (4)

101-113: Document cross-plugin semantics for runAfterMutationWithinTransaction

Behavior in a mixed-plugin environment is “any-true implies in-tx execution is needed.” Consider adding a sentence: “If any installed plugin sets runAfterMutationWithinTransaction=true, the executor will run the in-transaction portion for all such plugins and defer others until after commit.”

  /**
   * Whether to run after-mutation hooks within the transaction that performs the mutation.
@@
- * Defaults to `false`.
+ * Defaults to `false`. If multiple plugins are installed, the executor will:
+ * - run callbacks of plugins with `runAfterMutationWithinTransaction=true` inside the mutation transaction, and
+ * - run callbacks of other plugins after the transaction commits.

126-136: Clarify queryNode documentation

The current text suggests availability only for create/update, but tests assert it for delete too. Reword to avoid confusion.

-    /**
-     * The mutation data. Only available for create and update actions.
-     */
+    /**
+     * The mutation query node (InsertQueryNode | UpdateQueryNode | DeleteQueryNode).
+     * For delete operations, the node represents the delete statement.
+     */

146-161: Explain return for create in loadBeforeMutationEntities

loadBeforeMutationEntities() returns undefined for create actions. Making this explicit helps plugin authors.

   /**
-     * Loads the entities that are about to be mutated. The db operation that loads the entities is executed
+     * Loads the entities that are about to be mutated. The db operation that loads the entities is executed
      * within the same transaction context as the mutation.
      */
     loadBeforeMutationEntities(): Promise<Record<string, unknown>[] | undefined>;
@@
-     * Mutations initiated from this client will NOT trigger entity mutation hooks to avoid infinite loops.
+     * Mutations initiated from this client will NOT trigger entity mutation hooks to avoid infinite loops.
+     *
+     * Note: for "create" actions there are no pre-existing entities; this method resolves to `undefined`.

163-176: Mention delete semantics in loadAfterMutationEntities docs

For delete operations, loadAfterMutationEntities() returns undefined. Worth stating explicitly.

   /**
-     * Loads the entities that have been mutated.
+     * Loads the entities that have been mutated.
+     * Note: for "delete" actions this resolves to `undefined`.
   */
packages/runtime/src/client/executor/zenstack-query-executor.ts (3)

233-244: Guard async post-commit callbacks to avoid unhandled rejections

If registerTransactionCommitCallback doesn’t await the callback, rejections can become unhandled. Wrap with try/catch.

-                        this.driver.registerTransactionCommitCallback(connection, () =>
-                            this.callAfterMutationHooks(
-                                result,
-                                finalQuery,
-                                mutationInfo,
-                                connectionClient,
-                                'outTx',
-                                queryId,
-                            ),
-                        );
+                        this.driver.registerTransactionCommitCallback(connection, async () => {
+                            try {
+                                await this.callAfterMutationHooks(
+                                    result,
+                                    finalQuery,
+                                    mutationInfo,
+                                    connectionClient,
+                                    'outTx',
+                                    queryId,
+                                );
+                            } catch {
+                                // optionally route to a logger
+                            }
+                        });

288-289: Avoid leaking full error objects in QueryError message

Interpolating the entire error object may expose stack details in logs. Use err.message when available.

-            const message = `Failed to execute query: ${err}, sql: ${compiled?.sql}`;
+            const message = `Failed to execute query: ${
+                err instanceof Error ? err.message : String(err)
+            }, sql: ${compiled?.sql}`;

487-503: Where-node reuse may break if mutation WHERE contains table aliases

loadEntities merges the mutation’s WhereNode into a fresh selectFrom(model) without aliasing. If WHERE predicates reference aliased tables/columns, the merged WhereNode might not bind correctly. Consider normalizing references (e.g., removing table aliases for the target model) before merging, or building the select by cloning the original table/alias context.

I can prototype a utility to rebind column references for the target model if you’d like.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c879a87 and e23453d.

📒 Files selected for processing (28)
  • .coderabbit.yaml (1 hunks)
  • TODO.md (3 hunks)
  • package.json (1 hunks)
  • packages/cli/package.json (1 hunks)
  • packages/common-helpers/package.json (1 hunks)
  • packages/create-zenstack/package.json (1 hunks)
  • packages/dialects/sql.js/package.json (1 hunks)
  • packages/eslint-config/package.json (1 hunks)
  • packages/ide/vscode/package.json (1 hunks)
  • packages/language/package.json (1 hunks)
  • packages/runtime/package.json (1 hunks)
  • packages/runtime/src/client/client-impl.ts (2 hunks)
  • packages/runtime/src/client/crud/operations/base.ts (2 hunks)
  • packages/runtime/src/client/executor/kysely-utils.ts (1 hunks)
  • packages/runtime/src/client/executor/name-mapper.ts (6 hunks)
  • packages/runtime/src/client/executor/zenstack-driver.ts (1 hunks)
  • packages/runtime/src/client/executor/zenstack-query-executor.ts (12 hunks)
  • packages/runtime/src/client/plugin.ts (3 hunks)
  • packages/runtime/test/client-api/name-mapping.test.ts (2 hunks)
  • packages/runtime/test/plugin/entity-mutation-hooks.test.ts (1 hunks)
  • packages/sdk/package.json (1 hunks)
  • packages/tanstack-query/package.json (1 hunks)
  • packages/testtools/package.json (1 hunks)
  • packages/typescript-config/package.json (1 hunks)
  • packages/vitest-config/package.json (1 hunks)
  • packages/zod/package.json (1 hunks)
  • samples/blog/package.json (1 hunks)
  • tests/e2e/package.json (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
{packages,samples,tests}/**

📄 CodeRabbit inference engine (CLAUDE.md)

Packages are located in packages/, samples/, and tests/

Files:

  • packages/cli/package.json
  • packages/runtime/package.json
  • packages/testtools/package.json
  • packages/zod/package.json
  • packages/typescript-config/package.json
  • packages/eslint-config/package.json
  • packages/dialects/sql.js/package.json
  • packages/ide/vscode/package.json
  • packages/runtime/src/client/crud/operations/base.ts
  • packages/sdk/package.json
  • packages/language/package.json
  • packages/tanstack-query/package.json
  • packages/runtime/src/client/executor/zenstack-driver.ts
  • packages/runtime/src/client/executor/kysely-utils.ts
  • packages/create-zenstack/package.json
  • packages/vitest-config/package.json
  • packages/runtime/src/client/client-impl.ts
  • samples/blog/package.json
  • tests/e2e/package.json
  • packages/runtime/test/client-api/name-mapping.test.ts
  • packages/runtime/src/client/executor/zenstack-query-executor.ts
  • packages/runtime/test/plugin/entity-mutation-hooks.test.ts
  • packages/common-helpers/package.json
  • packages/runtime/src/client/plugin.ts
  • packages/runtime/src/client/executor/name-mapper.ts
tests/e2e/**

📄 CodeRabbit inference engine (CLAUDE.md)

E2E tests are in tests/e2e/ directory

Files:

  • tests/e2e/package.json
🧠 Learnings (4)
📚 Learning: 2025-08-04T08:43:33.161Z
Learnt from: CR
PR: zenstackhq/zenstack-v3#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-04T08:43:33.161Z
Learning: `zenstack generate` compiles ZModel to TypeScript schema (`schema.ts`)

Applied to files:

  • packages/language/package.json
📚 Learning: 2025-08-04T08:43:33.161Z
Learnt from: CR
PR: zenstackhq/zenstack-v3#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-04T08:43:33.161Z
Learning: Kysely-Based ORM: V3 uses Kysely as query builder instead of Prisma runtime dependency

Applied to files:

  • packages/runtime/src/client/client-impl.ts
📚 Learning: 2025-08-04T08:43:33.161Z
Learnt from: CR
PR: zenstackhq/zenstack-v3#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-04T08:43:33.161Z
Learning: Language-First Design: ZModel DSL compiles to TypeScript, not runtime code generation

Applied to files:

  • TODO.md
📚 Learning: 2025-08-04T08:43:33.161Z
Learnt from: CR
PR: zenstackhq/zenstack-v3#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-04T08:43:33.161Z
Learning: Plugin Architecture: Runtime plugins for query interception and entity mutation hooks

Applied to files:

  • packages/runtime/src/client/executor/zenstack-query-executor.ts
  • packages/runtime/src/client/plugin.ts
🧬 Code graph analysis (6)
packages/runtime/src/client/executor/kysely-utils.ts (1)
packages/common-helpers/src/tiny-invariant.ts (1)
  • invariant (4-14)
packages/runtime/test/client-api/name-mapping.test.ts (3)
packages/runtime/src/client/contract.ts (1)
  • ClientContract (50-159)
packages/runtime/test/schemas/name-mapping/schema.ts (1)
  • schema (9-87)
packages/runtime/test/utils.ts (1)
  • createTestClient (78-194)
packages/runtime/src/client/executor/zenstack-query-executor.ts (4)
packages/runtime/src/client/contract.ts (1)
  • ClientContract (50-159)
packages/common-helpers/src/tiny-invariant.ts (1)
  • invariant (4-14)
packages/runtime/src/client/executor/kysely-utils.ts (1)
  • stripAlias (7-14)
packages/runtime/src/client/plugin.ts (1)
  • AfterEntityMutationCallback (142-144)
packages/runtime/test/plugin/entity-mutation-hooks.test.ts (2)
packages/runtime/src/client/contract.ts (1)
  • ClientContract (50-159)
packages/runtime/test/utils.ts (1)
  • createTestClient (78-194)
packages/runtime/src/client/plugin.ts (2)
packages/runtime/src/client/contract.ts (1)
  • ClientContract (50-159)
packages/sdk/src/schema/schema.ts (1)
  • SchemaDef (10-18)
packages/runtime/src/client/executor/name-mapper.ts (1)
packages/runtime/src/client/executor/kysely-utils.ts (1)
  • stripAlias (7-14)
🪛 markdownlint-cli2 (0.17.2)
TODO.md

12-12: Unordered list indentation
Expected: 4; Actual: 8

(MD007, ul-indent)


35-35: Unordered list indentation
Expected: 4; Actual: 8

(MD007, ul-indent)


36-36: Unordered list indentation
Expected: 6; Actual: 12

(MD007, ul-indent)


37-37: Unordered list indentation
Expected: 6; Actual: 12

(MD007, ul-indent)


38-38: Unordered list indentation
Expected: 6; Actual: 12

(MD007, ul-indent)


39-39: Unordered list indentation
Expected: 6; Actual: 12

(MD007, ul-indent)


40-40: Unordered list indentation
Expected: 6; Actual: 12

(MD007, ul-indent)


68-68: Unordered list indentation
Expected: 2; Actual: 4

(MD007, ul-indent)


69-69: Unordered list indentation
Expected: 4; Actual: 8

(MD007, ul-indent)


70-70: Unordered list indentation
Expected: 4; Actual: 8

(MD007, ul-indent)


71-71: Unordered list indentation
Expected: 4; Actual: 8

(MD007, ul-indent)

⏰ 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). (1)
  • GitHub Check: build-test (20.x)
🔇 Additional comments (44)
packages/runtime/package.json (1)

3-3: Version bump consistency verified

All package.json files have been correctly updated to 3.0.0-alpha.32. No mismatches were found.

• Please ensure the CHANGELOG/release notes document the new runtime public surface (e.g. forceTransaction, hook changes) and any behavioral nuances for consumers.

packages/eslint-config/package.json (1)

3-3: LGTM: version bump only

No concerns—private package, scoped manifest change.

packages/tanstack-query/package.json (1)

3-3: LGTM: version bump to 3.0.0-alpha.32

No other manifest changes; dependency on @zenstackhq/runtime remains workspace-pinned, which will track the new runtime version.

tests/e2e/package.json (1)

3-3: LGTM: e2e workspace version aligned

Matches the repo-wide bump; no script or dependency drift detected in this file.

packages/testtools/package.json (1)

3-3: LGTM: version bump to 3.0.0-alpha.32

Peer deps on better-sqlite3/pg unchanged, which is fine for this release. No further action needed here.

packages/vitest-config/package.json (1)

4-4: Version bump to 3.0.0-alpha.32 looks good

No functional changes; safe metadata update. Matches the monorepo-wide bump.

packages/typescript-config/package.json (1)

3-3: Version alignment confirmed

Bumped to 3.0.0-alpha.32; consistent with the rest of the repo. No API impact.

packages/language/package.json (1)

4-4: Language package version bump LGTM

Only the version changed to 3.0.0-alpha.32. Exports/files/scripts unchanged; no downstream risk.

packages/create-zenstack/package.json (1)

3-3: CLI package version bump approved

Update to 3.0.0-alpha.32 with no behavior changes.

package.json (1)

3-3: Root version bump consistency verified

No occurrences of 3.0.0-alpha.31 were found in any package.json across packages/, samples/, or tests/. All workspaces are now at 3.0.0-alpha.32.

✅ Version bump approved.

packages/zod/package.json (1)

3-3: Monorepo version consistency verified

No remaining references to 3.0.0-alpha.31 were found across any package.json—all workspace packages now uniformly use 3.0.0-alpha.32.

samples/blog/package.json (1)

3-3: Version bump verified; sample apps aligned
No leftover references to 3.0.0-alpha.31 found in samples/ or tests/ package.json files—everything’s updated to 3.0.0-alpha.32.

packages/ide/vscode/package.json (1)

4-4: ✅ Extension pre-release version validated and ready for publish

  • packages/ide/vscode/package.json: version is correctly set to "3.0.0-alpha.32".

No further changes detected. You’re all set to publish the pre-release.

packages/common-helpers/package.json (1)

3-3: Approve version bump: common-helpers 3.0.0-alpha.32 verified

All checks passed:

  • No lingering references to “3.0.0-alpha.31” across any package.json
  • Every dependent package (runtime, sdk, language, cli) uses workspace:* for @zenstackhq/common-helpers

Safe to merge.

packages/cli/package.json (1)

6-6: Updated script to verify all @zenstackhq packages are at 3.0.0-alpha.32

Version bump to 3.0.0-alpha.32: LGTM
No concerns with the manifest change.

Run the following adjusted script to catch any straggling package versions:

#!/usr/bin/env bash
set -euo pipefail

expected="3.0.0-alpha.32"
echo "Packages with version NOT matching ${expected}:"

# Find all package.json files outside node_modules/dist
find . \
  -type f \
  -name package.json \
  ! -path "*/node_modules/*" \
  ! -path "*/dist/*" \
| while read -r file; do
    name=$(jq -r .name "$file" 2>/dev/null || echo "")
    version=$(jq -r .version "$file" 2>/dev/null || echo "")
    if [[ "$name" == "@zenstackhq/"* ]] && [[ "$version" != "$expected" ]]; then
      printf '%s@%s :: %s\n' "$name" "$version" "$file"
    fi
  done

This uses standard jq filters (.name and .version) and shell pattern matching (avoiding the unsupported starts_with) to list any internal package not yet bumped. Please run this and confirm there are no mismatches.

packages/dialects/sql.js/package.json (1)

3-3: Version bump: looks good

Nothing else changed; dependencies/peers remain stable.

packages/sdk/package.json (1)

3-3: SDK version bump only: LGTM

No API surface or export map changes here.

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

25-25: Type alignment confirmed for TransactionIsolationLevel

The string literals in your TransactionIsolationLevel enum exactly match Kysely’s isolationLevel union ("read uncommitted" | "read committed" | "repeatable read" | "serializable" | "snapshot"), so there is no subtle type mismatch and no casting is required. (kysely-org.github.io)

TODO.md (1)

12-14: Configure MD007 indent to 4 and address the top-level heading

The lint run confirms that TODO.md has numerous MD007 (unordered-list indentation) violations under the default 2-space indent, plus an MD041 first-line heading error:

  • MD041 at line 1: “## V3 Alpha Todo” should be a level-1 heading
  • MD007 at many lines: expected 2 spaces but actual 4, 8, or 12 spaces

To align with the file’s existing style and eliminate these errors, update your markdownlint configuration (e.g. .markdownlint.json or markdownlint-cli2.json) and/or adjust the file:

Option A: Configure the rules

 {
+  "MD007": { "indent": 4 },
+  "MD041": false
 }

This sets list indentation levels to 4 spaces per level and disables the first-line-heading rule.

Option B: Fix the file

- ## V3 Alpha Todo
+ # V3 Alpha Todo

Then normalize all list items to 4 spaces per indent level (or adjust as needed to match the new rule).

After making your changes, verify no errors remain:

npx -y markdownlint-cli2 TODO.md
packages/runtime/src/client/client-impl.ts (1)

159-163: I’ve requested the definitions of both the Transaction class and the $transaction method to confirm whether the current forceTransaction truly omits transaction semantics and to see how $transaction works. Once you have those snippets, we can finalize the API redesign recommendation.

packages/runtime/src/client/executor/name-mapper.ts (4)

22-22: Importing shared stripAlias is a good move

Centralizing alias logic reduces duplication and keeps invariant checks consistent across transformers.


154-169: Guarding update without table node is correct; nice resilience

The !node.table short-circuit avoids crashes on atypical update shapes. The subsequent stripAlias + scope setup looks consistent with other paths.


176-186: transformDeleteQuery: consistent alias handling and table mapping

Replacing ad-hoc logic with stripAlias keeps behavior uniform across JOIN/FROM/DELETE. Looks good.


318-338: processFrom: alias-preserving nested select is correct

The ensureAlias fallback to original table name preserves outer references transparently. Good alignment with JOIN and SELECT* expansion logic.

packages/runtime/test/client-api/name-mapping.test.ts (3)

26-81: Create path coverage is comprehensive across ORM and QB APIs

Nice coverage: nested create, returning, qualified returning with table prefix, and returningAll. This should exercise the name-mapper thoroughly.


194-206: Update with alias and returningAll covers the tricky path—good

This validates UPDATE ... FROM 'User as u' alias handling through the mapper. Great inclusion.


208-237: Delete coverage looks solid (QB and ORM)

Validates name mapping for delete with returning and relation cleanup through ORM delete. Good end-to-end assertions.

packages/runtime/test/plugin/entity-mutation-hooks.test.ts (14)

29-49: Good coverage of query node typing across actions

Asserting Insert/Update/Delete node types in beforeEntityMutation validates the new API surface well. Nice.


71-114: before-entities loader usage is correct

The test exercises loadBeforeMutationEntities() only for update/delete and verifies queryId correlation. This aligns with the new contract.


116-152: after-entities loader usage is correct

Validates create/update flows and ensures returned entities reflect post-mutation state. Looks good.


296-357: Transactional behavior without outer tx is well covered

Both out-of-tx and in-tx after-hook behaviors are validated. Nice separation of concerns.


378-398: After-hook throws out-of-tx: correct persistence semantics

Verifies that mutation persists even if after-hook throws when executed out of transaction. Good.


400-424: In-tx after-hook exception: correct rollback semantics

The test correctly expects the entire operation to fail and rollback when runAfterMutationWithinTransaction is true. Good coverage.


426-483: Rollback behavior vs. after-hook placement validated

Nice pair of tests ensuring afterEntityMutation is skipped on rollback by default, and still invoked when configured to run within the transaction (while effects don’t persist). Solid.


486-513: Visibility of intra-transaction changes before mutation

Validates that beforeEntityMutation reads the latest uncommitted state inside the outer transaction. Good.


514-540: Before-hook runs within outer transaction as expected

Side-effects are rolled back with the transaction, as intended. Looks correct.


542-594: Out-of-tx after-hook with outer tx: intended persistence and visibility checks

The test nicely proves that before-hook sees in-tx data and that after-hook runs post-commit and persists. Good.


596-634: In-tx after-hook with outer tx persists as a unit

Confirms that the hook runs and persists with the transaction. LGTM.


636-660: Out-of-tx after-hook throwing doesn't rollback committed work

Good assertion that both the mutation and side-effects are persisted despite the thrown error in the after-hook. LGTM.


662-689: In-tx after-hook throwing rolls back everything

Correct expectation for runAfterMutationWithinTransaction: true. Nice.


323-325: Custom matcher toResolveWithLength is present – no changes required

The custom Jest-style matcher toResolveWithLength is defined and extended in the test setup, so all usages are valid and don’t need to be replaced:

  • Defined in packages/runtime/test/vitest-ext.ts via expect.extend({ async toResolveWithLength(received, length) { … } })
  • Declared in packages/runtime/test/vitest.d.ts for proper typings

All occurrences of await expect(...).toResolveWithLength(n) across your tests (including the lines in entity-mutation-hooks.test.ts and the other mentioned locations) are supported by this extension.

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

89-99: API shape looks consistent and minimal

The before/after hooks, queryId correlation, and client-in-transaction semantics are coherent and align with the tests.

packages/runtime/src/client/executor/zenstack-query-executor.ts (2)

317-331: Propagation of suppressMutationHooks is correct across executor factories

All withPlugin/withPlugins/withPluginAtFront/withoutPlugins/withConnectionProvider preserve the suppression flag. Good attention to detail.

Also applies to: 333-343, 345-354, 357-367, 369-383


116-135: Hook orchestration and queryId plumbing look solid

getMutationInfo, callBeforeMutationHooks, and callAfterMutationHooks are cohesive and match the new public API contract and tests.

Also applies to: 384-405, 407-431, 433-485

@claude
Copy link

claude bot commented Aug 26, 2025

Claude finished @ymc9's task —— View job


Pull Request Review

I've completed a comprehensive review of this dev -> main merge PR. Here are my findings:

Review Tasks:

  • Analyze package.json version updates
  • Review core runtime changes (executor, plugin system)
  • Examine new kysely-utils.ts file
  • Review name-mapper.ts refactoring
  • Analyze zenstack-query-executor.ts changes
  • Check plugin.ts modifications
  • Review test coverage changes
  • Identify potential bugs or security concerns
  • Assess performance implications

✅ Code Quality & Best Practices

Positive aspects:

  • Clean refactoring: The name mapper implementation is well-structured with clear separation of concerns
  • Comprehensive utility functions: New kysely-utils.ts provides clean abstraction for alias handling
  • Proper error handling: Good use of try-catch blocks and proper error propagation in query executor
  • Type safety: Strong TypeScript usage throughout with proper generics and type guards
  • Documentation: Well-documented plugin interfaces with clear JSDoc comments

⚠️ Potential Issues & Concerns

1. Transaction Complexity (zenstack-query-executor.ts:200-285)
The transaction handling logic is quite complex with nested conditionals. Consider extracting the transaction execution logic into separate methods for better maintainability.

2. Memory Management

  • Entity caching in beforeMutationEntities could accumulate memory in long-running transactions
  • The scope stack in QueryNameMapper could grow large for deeply nested queries

3. Error Context Loss
In zenstack-query-executor.ts:287-290, the original error context might be lost when wrapping in QueryError. Consider preserving the original error type and stack trace.

🚀 Performance Considerations

Improvements:

  • Lazy loading: beforeMutationEntities are cached and loaded only when needed
  • Scope optimization: Name mapper uses efficient reverse iteration through scopes
  • Query compilation caching: Compiled queries are properly cached

Potential concerns:

  • Multiple passes through the AST for name mapping could be expensive for complex queries
  • The hasMappedColumns check iterates through all field mappings each time

🔒 Security Assessment

No security issues identified, but good defensive practices observed:

  • Proper SQL injection prevention through parameterized queries
  • Transaction isolation handled correctly
  • No exposure of sensitive data in error messages

🧪 Test Coverage

Strong test improvements:

  • Comprehensive entity mutation hooks testing with 642 new lines
  • Transaction semantics properly tested
  • Both SQLite and PostgreSQL providers covered
  • Edge cases for nested transactions and rollbacks tested

Minor gap: The new kysely-utils.ts appears to lack dedicated unit tests, though it's likely covered indirectly.

📋 Additional Observations

Version Management: All packages properly bumped to 3.0.0-alpha.32 - good consistency.

TODO Updates: The TODO.md shows good progress tracking with recent completions in plugin and transaction areas.

Plugin Architecture: The refactored plugin system provides cleaner separation between ORM-level and Kysely-level hooks, which is a solid architectural improvement.

🎯 Recommendations

  1. Extract transaction logic: Break down the complex transaction handling in proceedQuery method
  2. Add unit tests: Consider adding specific tests for kysely-utils.ts functions
  3. Performance monitoring: Monitor the name mapping performance on complex schemas
  4. Error handling: Preserve more context when wrapping errors

Overall Assessment

This is a well-executed merge with solid refactoring that improves the codebase architecture. The changes demonstrate good engineering practices with comprehensive testing and proper type safety. The complexity is manageable and the benefits outweigh the concerns.

Recommendation: ✅ Approve for merge


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

🧹 Nitpick comments (1)
.coderabbit.yaml (1)

7-7: Nit: prefer block-style list for readability and cleaner diffs

Inline arrays are valid, but block lists make future edits and reviews easier.

Apply this diff:

-        base_branches: ['dev', 'main']
+        base_branches:
+            - dev
+            - main
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e23453d and 2390469.

📒 Files selected for processing (1)
  • .coderabbit.yaml (1 hunks)
⏰ 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 (1)
.coderabbit.yaml (1)

7-7: Resolved: base_branches correctly nested under reviews.auto_review

Matches the schema (array of strings under reviews.auto_review.base_branches). Configuration will now be honored. LGTM.

@ymc9 ymc9 added this pull request to the merge queue Aug 26, 2025
Merged via the queue into main with commit c2d87a8 Aug 26, 2025
8 of 11 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