Skip to content

Conversation

@ymc9
Copy link
Member

@ymc9 ymc9 commented Jan 16, 2026

Summary by CodeRabbit

  • New Features

    • Plugins can declare per-operation extended query args with validation; args flow into hooks and are validated per-operation.
  • API

    • Client methods and plugin hooks expose operation-specific ctx (client, model, operation, args, options, proceed). Client reinitializes input validation when plugins/options change.
  • Types

    • New operation group and ext-args types; exact-shape utility added; omit config renamed.
  • Tests

    • Added e2e tests for extended query-args, validation, and propagation.
  • Samples

    • Sample procedure and sample deps updated.

✏️ Tip: You can customize this high-level summary in your review settings.

Copilot AI review requested due to automatic review settings January 16, 2026 07:57
@coderabbitai
Copy link

coderabbitai bot commented Jan 16, 2026

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

Walkthrough

Refactors CRUD operation types into const-backed categorized sets, threads per-operation ExtQueryArgs through client/plugin/validator APIs, moves input validation to a per-client InputValidator, updates handler and plugin hook signatures to be operation-aware, and adds E2E tests and generated types for ext-query-args.

Changes

Cohort / File(s) Summary
CRUD operation type refactor
packages/orm/src/client/crud/operations/base.ts, packages/orm/src/client/index.ts
Replace union types with const-backed sets (CoreCrudOperations, CoreReadOperations, CoreWriteOperations, AllCrudOperations, AllReadOperations) and update exported names and BaseOperationHandler signature.
Client contract & ext query args
packages/orm/src/client/contract.ts
Add ExtractExtQueryArgs and ExtQueryArgs generic; thread ExtQueryArgs through ClientContract, AllModelOperations, ModelOperations; make $use generic to merge plugin ext args; update transaction client type.
Validator & per-operation schemas
packages/orm/src/client/crud/validator/index.ts
Make validation operation-aware (accept CoreCrudOperations), introduce per-instance schemaCache, merge plugin-provided per-operation extQueryArgs schemas, and adjust GetSchemaFunc/validate signatures.
Find operation & handlers
packages/orm/src/client/crud/operations/find.ts, packages/orm/src/client/crud/operations/*
Use CoreCrudOperations in handler signatures, validate by operation, enforce single-result semantics for find-one ops (set take = 1), and simplify validator invocation.
Client implementation & validator lifecycle
packages/orm/src/client/client-impl.ts
Add inputValidator field on ClientImpl; reinitialize InputValidator on $use/$unuse/$unuseAll/$setOptions; pass client.inputValidator into model CRUD handlers and plugin contexts.
Plugin typing, extQueryArgs & definePlugin
packages/orm/src/client/plugin.ts
Add ExtQueryArgsBase, make RuntimePlugin generic over ext args with extQueryArgs.getValidationSchema(operation), update definePlugin overloads and hook contexts (OnQueryHookContext) to use new operation types.
Options, utils & minor API
packages/orm/src/client/options.ts, packages/orm/src/utils/type-utils.ts
Broaden ClientOptions.plugins to RuntimePlugin<any, any>[], rename OmitOptionsOmitConfig, add Exact<T,Shape> utility type.
Plugin implementations & typing tweaks
packages/plugins/policy/src/plugin.ts, packages/plugins/policy/src/expression-transformer.ts
Remove generics from PolicyPlugin instantiation, cast plugin functions to ZModelFunction in transformer, minor signature adjustments.
Public exports & small reflows
packages/orm/src/client/crud-types.ts, packages/orm/src/client/index.ts
Export new operation type groups; minor non-functional reflow/indentation changes.
Tests, generated types, schema, samples
tests/e2e/orm/plugin-infra/*, tests/e2e/package.json, samples/orm/*
Add E2E tests for ext-query-args, generated schema/models/input files, add zod dependency to samples/tests, and small sample/test adjustments.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hop through consts and threaded args so bright,
Validators giggle, plugins lend schemas light,
Handlers whisper "one" when a single row's due,
Tests and types now dance in a tidy queue,
A rabbit claps its paws — hooray for the new!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: plugin-extended query args' clearly and concisely summarizes the main change: introducing support for plugins to extend query arguments in the ORM system.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings


📜 Recent 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 23d59a7 and 0752557.

📒 Files selected for processing (4)
  • packages/orm/src/client/contract.ts
  • packages/orm/src/client/crud/validator/index.ts
  • packages/orm/src/client/plugin.ts
  • tests/e2e/orm/plugin-infra/ext-query-args.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/orm/src/client/plugin.ts
🧰 Additional context used
📓 Path-based instructions (1)
tests/e2e/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

E2E tests should validate real-world schema compatibility with established projects

Files:

  • tests/e2e/orm/plugin-infra/ext-query-args.test.ts
🧠 Learnings (8)
📓 Common learnings
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to packages/zenstackhq/orm/**/*.{ts,tsx} : Implement plugin hooks at ORM, Kysely, and entity mutation levels for query interception and customization
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to packages/zenstackhq/orm/**/*.test.{ts,tsx} : ORM package tests should include comprehensive client API tests and policy tests

Applied to files:

  • tests/e2e/orm/plugin-infra/ext-query-args.test.ts
  • packages/orm/src/client/crud/validator/index.ts
  • packages/orm/src/client/contract.ts
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to packages/zenstackhq/orm/**/*.{ts,tsx} : Implement plugin hooks at ORM, Kysely, and entity mutation levels for query interception and customization

Applied to files:

  • tests/e2e/orm/plugin-infra/ext-query-args.test.ts
  • packages/orm/src/client/crud/validator/index.ts
  • packages/orm/src/client/contract.ts
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to tests/e2e/**/*.{ts,tsx} : E2E tests should validate real-world schema compatibility with established projects

Applied to files:

  • tests/e2e/orm/plugin-infra/ext-query-args.test.ts
  • packages/orm/src/client/contract.ts
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to tests/**/type*.{ts,tsx} : Ensure TypeScript inference and type coverage are validated through type coverage tests

Applied to files:

  • tests/e2e/orm/plugin-infra/ext-query-args.test.ts
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to packages/zenstackhq/orm/**/*.{ts,tsx} : Use Kysely as the query builder interface for low-level database queries, avoiding raw SQL when possible

Applied to files:

  • tests/e2e/orm/plugin-infra/ext-query-args.test.ts
  • packages/orm/src/client/crud/validator/index.ts
  • packages/orm/src/client/contract.ts
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to **/*.zmodel : ZModel schema files should define database structure and policies that compile to TypeScript via `zenstack generate`

Applied to files:

  • tests/e2e/orm/plugin-infra/ext-query-args.test.ts
  • packages/orm/src/client/crud/validator/index.ts
  • packages/orm/src/client/contract.ts
📚 Learning: 2025-12-30T15:07:06.254Z
Learnt from: mwillbanks
Repo: zenstackhq/zenstack-v3 PR: 550
File: packages/orm/src/client/crud/operations/base.ts:158-159
Timestamp: 2025-12-30T15:07:06.254Z
Learning: Do not use ts-expect-error in production code within the zenstackhq/zenstack-v3 repo (e.g., packages/*). Use explicit type annotations, targeted type assertions, or refactor to resolve the type error. ts-expect-error may be acceptable only in test files for stubbing or temporary silencing. Ensure production code is type-safe and maintainable.

Applied to files:

  • packages/orm/src/client/crud/validator/index.ts
  • packages/orm/src/client/contract.ts
🧬 Code graph analysis (1)
packages/orm/src/client/crud/validator/index.ts (1)
packages/orm/src/client/crud/operations/base.ts (2)
  • CoreCrudOperations (60-77)
  • CoreCrudOperations (82-82)
⏰ 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). (3)
  • GitHub Check: build-test (22.x, postgresql)
  • GitHub Check: build-test (22.x, sqlite)
  • GitHub Check: claude-review
🔇 Additional comments (12)
tests/e2e/orm/plugin-infra/ext-query-args.test.ts (6)

1-6: LGTM!

Imports are well-organized, bringing in the necessary types (CoreReadOperations, CoreWriteOperations, definePlugin, ClientContract) from the ORM package along with test utilities and Zod for schema validation.


27-34: LGTM!

Proper test lifecycle management with database cleanup before each test and safe disconnection after.


36-163: Comprehensive test coverage for extending all operations.

This test thoroughly validates the plugin-extended query args feature:

  • Optional cache and ttl parameters
  • TTL propagation through onQuery hook
  • Validation errors for invalid values and unrecognized keys
  • Coverage across all CRUD operation types (read, create, update, delete)
  • Transactional and plugin boundary usage ($transaction, $use, $setOptions, $setAuth)

This aligns well with the coding guidelines for E2E tests validating real-world schema compatibility. Based on learnings, the comprehensive client API coverage is appropriate for ORM tests.


165-193: LGTM!

Good test for operation-specific extensions. Validates that only read operations accept the cache extension while write operations correctly reject it. The runtime check using (CoreReadOperations as readonly string[]).includes(operation) properly aligns compile-time types with runtime behavior.


195-247: LGTM!

Excellent test for per-operation schema differentiation. The test correctly validates:

  • Read operations accept ttl but reject bust
  • Write operations accept bust but reject ttl
  • Proper propagation of extended args through the onQuery hook

This demonstrates the flexibility of the plugin system for operation-aware extensions.


249-272: LGTM!

Critical test for validating schema isolation between clients. The double verification (lines 264-266 and 268-271) ensures that plugin extensions don't leak across client instances and that internal caching doesn't cause cross-contamination.

packages/orm/src/client/crud/validator/index.ts (3)

59-63: Solid: per-operation schema caching and validation flow.

Per-client schemaCache plus operation-aware cache keys/validation avoids cross-op schema collisions and keeps validation deterministic.

Also applies to: 192-203, 331-363


365-415: Nice: operation-aware find schema with plugin extensions.

The operation-derived findUnique/findFirst semantics combined with plugin arg merging keeps validation aligned with the new per-op ext args model.


1148-1174: Good: consistent plugin-arg merging across CRUD schemas.

The per-operation mergePluginArgsSchema integration is applied uniformly across create/update/delete/count/aggregate/groupBy, which should reduce drift in validation behavior.

Also applies to: 1433-1482, 1594-1670, 1705-1771

packages/orm/src/client/contract.ts (3)

51-58: Nice: ExtractExtQueryArgs adds a clear per-operation fallback.

This keeps ext args scoped to each operation while still honoring a shared all shape.


74-218: Good: ExtQueryArgs is preserved across client APIs and plugin installs.

The updated signatures for $setAuth, $setOptions, $setInputValidation, $transaction, $use, and $unuse keep extended args flowing through client instances.


274-863: Good: per-operation ext args are threaded through model operations.

The operation signatures consistently intersect core args with ExtractExtQueryArgs, matching the new per-op ext args contract.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.


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 Jan 16, 2026

Claude finished @ymc9's task —— View job


Code Review Complete

I've conducted a thorough review of the "WIP: plugin custom options" PR. This feature implements extended query arguments for plugins, allowing them to add custom options to ORM operations while maintaining type safety and validation.

Todo List:

  • Analyze core ORM client changes
  • Review plugin infrastructure modifications
  • Examine new extended query args functionality
  • Check test coverage and quality
  • Assess code quality and best practices
  • Look for potential bugs and security issues
  • Review performance considerations
  • Provide comprehensive feedback

📋 Summary

This PR introduces a powerful plugin extension mechanism that allows plugins to add custom query arguments to any ORM operation. The implementation is well-architected with proper type safety, validation, and comprehensive test coverage.

✅ Strengths

🏗️ Excellent Architecture

  • Type-safe design: Uses conditional types and generics to maintain full type safety
  • Clean separation: Plugin args are properly isolated from core ORM functionality
  • Flexible targeting: Supports extending all operations or specific ones via operation-specific schemas

🔒 Robust Input Validation

  • Zod integration: Plugins provide validation schemas that are merged with core schemas
  • Strict validation: Uses strict() mode to reject unrecognized properties
  • Operation-specific: Different operations can have different extended arg schemas

🧪 Comprehensive Test Coverage

  • Complete operation coverage: Tests all CRUD operations with extensions
  • Validation testing: Proper error handling for invalid inputs
  • Type safety verification: Uses TypeScript errors as tests (@ts-expect-error)
  • Multiple scenarios: Tests universal extensions, operation-specific extensions, and mixed scenarios

📊 Performance Conscious

  • Schema caching: Validation schemas are cached to avoid repeated creation
  • Lazy evaluation: Plugin validation only runs when extensions are present
  • Efficient merging: Smart merging of plugin schemas with core schemas

⚠️ Areas for Improvement

🏷️ Type Safety Concerns

Issue: The ExtractExtQueryArgs type uses a complex conditional that could fail silently:

type ExtractExtQueryArgs<ExtQueryArgs, Operation extends CoreCrudOperations> = 
    Operation extends keyof ExtQueryArgs
        ? NonNullable<ExtQueryArgs[Operation]>
        : 'all' extends keyof ExtQueryArgs
          ? NonNullable<ExtQueryArgs['all']>
          : {};

Recommendation: Add runtime validation to ensure type-level guarantees hold at runtime.

🛡️ Security Considerations

Issue: Plugin validation schemas are trusted without additional verification:

// packages/orm/src/client/crud/validator/index.ts:375-385
private mergePluginArgsSchema(schema: ZodObject, operation: CoreCrudOperations) {
    let result = schema;
    for (const plugin of this.options.plugins ?? []) {
        if (plugin.extQueryArgs) {
            const pluginSchema = plugin.extQueryArgs.getValidationSchema(operation);
            if (pluginSchema) {
                result = result.extend(pluginSchema.shape); // ⚠️ No validation of plugin schema
            }
        }
    }
    return result.strict();
}

Recommendation: Add validation to ensure plugin schemas don't override core fields or contain malicious patterns.

Performance Optimizations

Issue: Schema caching key could be more efficient:

// packages/orm/src/client/crud/validator/index.ts:349-360
const cacheKey = stableStringify({
    type: 'model',
    model,
    operation,
    extraValidationsEnabled: this.extraValidationsEnabled, // ⚠️ Could cause cache misses
});

Recommendation: Consider more granular caching that accounts for plugin configurations.

📝 Documentation & Examples

Issue: The sample code suggests this is an experimental feature:

// samples/orm/main.ts:8-10
const myPlugin = definePlugin<'foo', typeof schema, { bar: string }>({
    id: 'foo',
});

Recommendation: Add comprehensive documentation with real-world examples (caching, logging, metrics, etc.).

🐛 Potential Issues

🔄 Client Method Signature

Location: packages/orm/src/client/client-impl.ts:519-530

Issue: The findMany method accepts variable arguments which could be confusing:

findMany: (...allArgs: unknown[]) => {
    const [args, pluginOptions] = allArgs; // ⚠️ Unclear API

Impact: Medium - Could lead to developer confusion
Recommendation: Consider a more explicit API design or better documentation.

🔧 Type Inference Edge Cases

Location: packages/orm/src/client/contract.ts:54-58

Issue: Complex type extraction could fail with deeply nested plugin args
Impact: Low - Mainly affects development experience
Recommendation: Add unit tests for complex type scenarios.

🎯 Recommendations

🚀 Short Term (Before Merge)

  1. Add plugin schema validation to prevent core field overrides
  2. Improve documentation with practical examples
  3. Consider API clarity for the extended args parameter passing

📈 Long Term (Future Enhancements)

  1. Plugin composition - Allow multiple plugins to extend the same operation
  2. Runtime type checking - Add development-time warnings for type mismatches
  3. Performance monitoring - Add metrics for plugin overhead

🏆 Overall Assessment

Grade: A-

This is a well-implemented feature that adds significant value to the plugin ecosystem. The type-safe design, comprehensive validation, and thorough testing demonstrate excellent engineering practices. The architectural decisions support extensibility while maintaining core ORM performance and safety.

The identified issues are minor and primarily related to documentation and edge case handling rather than fundamental design problems. This feature will enable powerful plugin capabilities while maintaining ZenStack's commitment to type safety and developer experience.


@ymc9 ymc9 changed the title WIP: plugin custom options feat: plugin-extended query args Jan 16, 2026
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 introduces a plugin infrastructure feature that allows plugins to extend query arguments for CRUD operations. The changes enable plugins to define custom options (like cache TTL settings) that can be passed to database operations and validated using Zod schemas.

Changes:

  • Added ExtQueryArgs type system to allow plugins to extend query arguments for specific or all CRUD operations
  • Refactored CRUD operation types from string unions to const arrays with exported values
  • Updated the input validator to merge plugin-provided Zod schemas with base operation schemas
  • Added comprehensive test coverage for the extended query args feature

Reviewed changes

Copilot reviewed 18 out of 20 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
tests/e2e/package.json Added zod dependency for testing plugin validation schemas
tests/e2e/orm/plugin-infra/ext-query-args/* New test schema, models, and input types for testing extended query args
tests/e2e/orm/plugin-infra/ext-query-args.test.ts Comprehensive tests for plugin extended query args feature
samples/orm/package.json Added zod dependency to sample project
samples/orm/main.ts Example usage of definePlugin with custom options (incomplete implementation)
pnpm-lock.yaml Updated lock file with new dependencies including zod 4.1.12
packages/plugins/policy/src/plugin.ts Updated PolicyPlugin to match new RuntimePlugin signature
packages/orm/src/utils/type-utils.ts Added Exact utility type for type-level validation
packages/orm/src/client/plugin.ts Added ExtQueryArgsBase type and updated RuntimePlugin to support extended query args
packages/orm/src/client/options.ts Renamed OmitOptions to OmitConfig and updated plugin type to accept any ExtQueryArgs
packages/orm/src/client/index.ts Exported CRUD operation constant arrays for external use
packages/orm/src/client/crud/validator/index.ts Added mergePluginArgsSchema method to integrate plugin validation schemas
packages/orm/src/client/crud/operations/find.ts Updated to pass operation name to validator instead of options object
packages/orm/src/client/crud/operations/base.ts Converted operation types to const arrays with exported values
packages/orm/src/client/crud-types.ts Minor formatting changes to ModelResult type
packages/orm/src/client/contract.ts Added ExtractExtQueryArgs utility and updated all operation signatures to accept extended args
packages/orm/src/client/client-impl.ts Updated findMany to accept plugin options parameter (incomplete for other operations)
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. 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: 2

Caution

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

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

6-21: Don’t clobber caller-provided take for find-first semantics.
Right now take is always forced to 1, which discards explicit take values (e.g., negative take to reverse ordering). Consider defaulting only when it’s undefined.

🔧 Suggested fix
-        if (findOne) {
-            // ensure "limit 1"
-            parsedArgs = parsedArgs ?? {};
-            parsedArgs.take = 1;
-        }
+        if (findOne) {
+            // ensure "limit 1" when not explicitly set
+            parsedArgs = parsedArgs ?? {};
+            if (parsedArgs.take === undefined) {
+                parsedArgs.take = 1;
+            }
+        }
🤖 Fix all issues with AI agents
In `@samples/orm/main.ts`:
- Line 47: The call to db.user.findMany is returning a promise that is not
awaited and its result is discarded; either await the promise (e.g., prepend
await to db.user.findMany in an async context) or remove the call, or if this is
intentional demo code add a clear inline comment explaining the discarded
result/extended-args demonstration; locate the invocation of db.user.findMany in
samples/orm/main.ts and update it accordingly.
- Line 28: The anonymous resolver listPublicPosts currently uses an empty object
destructuring pattern "({})" which triggers the linter; change its parameter to
either no params "()" or a single unused placeholder like "(_)" in the
listPublicPosts arrow function to silence the linter and indicate the parameter
is intentionally unused.
🧹 Nitpick comments (4)
samples/orm/main.ts (1)

32-40: Remove or document commented-out code.

This commented block appears to be leftover from a previous implementation. Consider removing it or adding a TODO comment explaining why it's kept.

packages/orm/src/client/client-impl.ts (1)

452-467: Context object typed as any bypasses type safety.

The ctx object is explicitly typed as any on line 453, which bypasses TypeScript's type checking. Based on learnings, ts-expect-error and loose typing should be avoided in production code. Consider using a proper interface for the context object.

Suggested approach

Define a typed context interface that includes the optional options property:

interface OnQueryContext {
    client: ClientContract<any>;
    model: string;
    operation: AllCrudOperations;
    args: unknown;
    proceed: (nextArgs: unknown) => Promise<unknown>;
    options?: unknown;
}

Then use this type instead of any:

-                    proceed = (_args: unknown) => {
-                        const ctx: any = {
+                    proceed = (_args: unknown) => {
+                        const ctx: OnQueryContext = {
packages/orm/src/client/plugin.ts (2)

75-83: Consider documenting the second overload or removing dead code path.

The definePlugin implementation accepts pluginOrSchema and plugin parameters, but:

  1. Only one overload signature is declared (lines 75-77)
  2. The implementation checks plugin ?? pluginOrSchema, suggesting a second overload pattern that isn't exposed

If a second overload (schema, plugin) was removed, the plugin parameter and conditional logic may be dead code. If it's intended for future use, consider adding the overload signature or a comment explaining the intent.

If the second overload is needed, add the signature:
 export function definePlugin<ExtQueryArgs extends ExtQueryArgsBase = {}, Schema extends SchemaDef = SchemaDef>(
     plugin: RuntimePlugin<Exact<ExtQueryArgs, ExtQueryArgsBase>, Schema>,
 ): RuntimePlugin<ExtQueryArgs, Schema>;
+
+export function definePlugin<ExtQueryArgs extends ExtQueryArgsBase = {}, Schema extends SchemaDef = SchemaDef>(
+    schema: Schema,
+    plugin: RuntimePlugin<Exact<ExtQueryArgs, ExtQueryArgsBase>, Schema>,
+): RuntimePlugin<ExtQueryArgs, Schema>;

 export function definePlugin(pluginOrSchema: unknown, plugin?: unknown) {
     // If plugin is provided, it's the second overload (schema, plugin)
     // Otherwise, it's the first overload (plugin only)
     return plugin ?? pluginOrSchema;
 }

2-2: Use explicit generic parameters for ZodObject type.

The ZodObject type from Zod is generic and should specify type parameters for the object shape and optional unknown-key behavior (e.g., ZodObject<any> or ZodObject<Record<string, any>>). Bare ZodObject usage without parameters reduces type specificity on line 68.

📜 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 154ba29 and 3d289d4.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (19)
  • packages/orm/src/client/client-impl.ts
  • packages/orm/src/client/contract.ts
  • packages/orm/src/client/crud-types.ts
  • packages/orm/src/client/crud/operations/base.ts
  • packages/orm/src/client/crud/operations/find.ts
  • packages/orm/src/client/crud/validator/index.ts
  • packages/orm/src/client/index.ts
  • packages/orm/src/client/options.ts
  • packages/orm/src/client/plugin.ts
  • packages/orm/src/utils/type-utils.ts
  • packages/plugins/policy/src/plugin.ts
  • samples/orm/main.ts
  • samples/orm/package.json
  • tests/e2e/orm/plugin-infra/ext-query-args.test.ts
  • tests/e2e/orm/plugin-infra/ext-query-args/input.ts
  • tests/e2e/orm/plugin-infra/ext-query-args/models.ts
  • tests/e2e/orm/plugin-infra/ext-query-args/schema.ts
  • tests/e2e/orm/plugin-infra/ext-query-args/schema.zmodel
  • tests/e2e/package.json
🧰 Additional context used
📓 Path-based instructions (2)
tests/e2e/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

E2E tests should validate real-world schema compatibility with established projects

Files:

  • tests/e2e/orm/plugin-infra/ext-query-args/models.ts
  • tests/e2e/orm/plugin-infra/ext-query-args.test.ts
  • tests/e2e/orm/plugin-infra/ext-query-args/schema.ts
  • tests/e2e/orm/plugin-infra/ext-query-args/input.ts
**/*.zmodel

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.zmodel: Always run zenstack generate after modifying ZModel schemas
ZModel schema files should define database structure and policies that compile to TypeScript via zenstack generate

Files:

  • tests/e2e/orm/plugin-infra/ext-query-args/schema.zmodel
🧠 Learnings (10)
📓 Common learnings
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to packages/zenstackhq/orm/**/*.{ts,tsx} : Implement plugin hooks at ORM, Kysely, and entity mutation levels for query interception and customization
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to packages/zenstackhq/orm/**/*.{ts,tsx} : Implement plugin hooks at ORM, Kysely, and entity mutation levels for query interception and customization

Applied to files:

  • tests/e2e/orm/plugin-infra/ext-query-args/models.ts
  • samples/orm/package.json
  • tests/e2e/orm/plugin-infra/ext-query-args.test.ts
  • packages/orm/src/client/index.ts
  • tests/e2e/orm/plugin-infra/ext-query-args/schema.ts
  • packages/orm/src/client/options.ts
  • tests/e2e/orm/plugin-infra/ext-query-args/schema.zmodel
  • packages/orm/src/client/crud/operations/find.ts
  • packages/orm/src/client/crud/operations/base.ts
  • packages/orm/src/client/crud/validator/index.ts
  • samples/orm/main.ts
  • tests/e2e/orm/plugin-infra/ext-query-args/input.ts
  • packages/orm/src/client/plugin.ts
  • packages/orm/src/client/client-impl.ts
  • packages/orm/src/client/contract.ts
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to **/*.zmodel : ZModel schema files should define database structure and policies that compile to TypeScript via `zenstack generate`

Applied to files:

  • tests/e2e/orm/plugin-infra/ext-query-args/models.ts
  • samples/orm/package.json
  • tests/e2e/orm/plugin-infra/ext-query-args/schema.ts
  • tests/e2e/orm/plugin-infra/ext-query-args/schema.zmodel
  • packages/orm/src/client/crud/validator/index.ts
  • samples/orm/main.ts
  • tests/e2e/orm/plugin-infra/ext-query-args/input.ts
  • packages/orm/src/client/plugin.ts
  • packages/orm/src/client/contract.ts
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to tests/e2e/**/*.{ts,tsx} : E2E tests should validate real-world schema compatibility with established projects

Applied to files:

  • tests/e2e/orm/plugin-infra/ext-query-args/models.ts
  • tests/e2e/orm/plugin-infra/ext-query-args.test.ts
  • tests/e2e/orm/plugin-infra/ext-query-args/schema.ts
  • tests/e2e/orm/plugin-infra/ext-query-args/schema.zmodel
  • tests/e2e/package.json
  • tests/e2e/orm/plugin-infra/ext-query-args/input.ts
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to packages/zenstackhq/orm/**/*.test.{ts,tsx} : ORM package tests should include comprehensive client API tests and policy tests

Applied to files:

  • tests/e2e/orm/plugin-infra/ext-query-args/models.ts
  • samples/orm/package.json
  • tests/e2e/orm/plugin-infra/ext-query-args.test.ts
  • packages/orm/src/client/index.ts
  • packages/orm/src/client/options.ts
  • packages/orm/src/client/crud/operations/base.ts
  • packages/orm/src/client/crud/validator/index.ts
  • samples/orm/main.ts
  • tests/e2e/orm/plugin-infra/ext-query-args/input.ts
  • packages/orm/src/client/plugin.ts
  • packages/orm/src/client/client-impl.ts
  • packages/orm/src/client/contract.ts
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to packages/zenstackhq/orm/**/*.{ts,tsx} : Use Kysely as the query builder interface for low-level database queries, avoiding raw SQL when possible

Applied to files:

  • tests/e2e/orm/plugin-infra/ext-query-args/models.ts
  • samples/orm/package.json
  • tests/e2e/orm/plugin-infra/ext-query-args.test.ts
  • packages/orm/src/client/crud/operations/find.ts
  • packages/orm/src/client/crud/validator/index.ts
  • samples/orm/main.ts
  • tests/e2e/orm/plugin-infra/ext-query-args/input.ts
  • packages/orm/src/client/plugin.ts
  • packages/orm/src/client/client-impl.ts
  • packages/orm/src/client/contract.ts
📚 Learning: 2025-12-30T15:07:06.254Z
Learnt from: mwillbanks
Repo: zenstackhq/zenstack-v3 PR: 550
File: packages/orm/src/client/crud/operations/base.ts:158-159
Timestamp: 2025-12-30T15:07:06.254Z
Learning: Do not use ts-expect-error in production code within the zenstackhq/zenstack-v3 repo (e.g., packages/*). Use explicit type annotations, targeted type assertions, or refactor to resolve the type error. ts-expect-error may be acceptable only in test files for stubbing or temporary silencing. Ensure production code is type-safe and maintainable.

Applied to files:

  • packages/orm/src/utils/type-utils.ts
  • packages/orm/src/client/crud-types.ts
  • packages/orm/src/client/index.ts
  • packages/plugins/policy/src/plugin.ts
  • packages/orm/src/client/options.ts
  • packages/orm/src/client/crud/operations/find.ts
  • packages/orm/src/client/crud/operations/base.ts
  • packages/orm/src/client/crud/validator/index.ts
  • packages/orm/src/client/plugin.ts
  • packages/orm/src/client/client-impl.ts
  • packages/orm/src/client/contract.ts
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to tests/**/type*.{ts,tsx} : Ensure TypeScript inference and type coverage are validated through type coverage tests

Applied to files:

  • tests/e2e/orm/plugin-infra/ext-query-args.test.ts
📚 Learning: 2025-10-21T16:09:31.218Z
Learnt from: ymc9
Repo: zenstackhq/zenstack-v3 PR: 319
File: packages/runtime/src/client/executor/zenstack-query-executor.ts:63-72
Timestamp: 2025-10-21T16:09:31.218Z
Learning: In ZenStack, TypeDefs can be inherited by models. When a TypeDef contains fields with `map` attributes, those mapped field names need to be processed by the QueryNameMapper since they become part of the inheriting model's schema. Therefore, when checking if a schema has mapped names (e.g., in `schemaHasMappedNames`), both `schema.models` and `schema.typeDefs` must be inspected for `@map` and `map` attributes.

Applied to files:

  • tests/e2e/orm/plugin-infra/ext-query-args/schema.ts
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Database migrations should use Prisma CLI under the hood via ZenStack commands

Applied to files:

  • samples/orm/main.ts
🧬 Code graph analysis (6)
tests/e2e/orm/plugin-infra/ext-query-args.test.ts (5)
packages/orm/src/client/contract.ts (1)
  • ClientContract (74-217)
packages/orm/src/client/crud/validator/index.ts (1)
  • schema (66-68)
packages/testtools/src/client.ts (1)
  • createTestClient (101-248)
packages/orm/src/client/plugin.ts (1)
  • definePlugin (79-83)
packages/orm/src/client/index.ts (2)
  • CoreReadOperations (10-10)
  • CoreWriteOperations (11-11)
packages/orm/src/client/options.ts (2)
packages/orm/src/client/plugin.ts (1)
  • RuntimePlugin (16-70)
packages/schema/src/schema.ts (1)
  • SchemaDef (11-19)
packages/orm/src/client/crud/operations/find.ts (3)
packages/orm/src/client/crud/operations/base.ts (2)
  • CoreCrudOperations (60-77)
  • CoreCrudOperations (82-82)
packages/orm/src/client/index.ts (1)
  • CoreCrudOperations (9-9)
packages/language/src/validators/function-invocation-validator.ts (1)
  • validateArgs (129-150)
samples/orm/main.ts (4)
packages/orm/src/client/plugin.ts (1)
  • definePlugin (79-83)
samples/next.js/zenstack/schema-lite.ts (1)
  • schema (120-120)
samples/orm/zenstack/schema.ts (1)
  • schema (260-260)
samples/next.js/lib/db.ts (1)
  • db (6-25)
packages/orm/src/client/client-impl.ts (3)
packages/orm/src/client/plugin.ts (1)
  • RuntimePlugin (16-70)
packages/orm/src/client/crud/operations/base.ts (4)
  • CoreCrudOperations (60-77)
  • CoreCrudOperations (82-82)
  • AllCrudOperations (125-125)
  • AllCrudOperations (130-130)
packages/orm/src/client/crud/operations/find.ts (1)
  • FindOperationHandler (5-29)
packages/orm/src/client/contract.ts (5)
packages/orm/src/client/constants.ts (1)
  • TRANSACTION_UNSUPPORTED_METHODS (14-14)
packages/orm/src/client/crud/operations/base.ts (2)
  • CoreCrudOperations (60-77)
  • CoreCrudOperations (82-82)
packages/schema/src/schema.ts (2)
  • SchemaDef (11-19)
  • GetModels (122-122)
packages/orm/src/client/plugin.ts (1)
  • RuntimePlugin (16-70)
packages/orm/src/client/crud-types.ts (2)
  • Subset (905-907)
  • ExistsArgs (1091-1091)
🪛 Biome (2.1.2)
samples/orm/main.ts

[error] 28-28: Unexpected empty object pattern.

(lint/correctness/noEmptyPattern)

⏰ 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). (3)
  • GitHub Check: Agent
  • GitHub Check: CodeQL analysis (javascript-typescript)
  • GitHub Check: claude-review
🔇 Additional comments (33)
samples/orm/package.json (1)

21-22: Both zod and kysely are properly declared in the workspace catalog (pnpm-workspace.yaml), so the "catalog:" references in this file are valid and require no changes.

packages/orm/src/client/crud-types.ts (1)

217-221: LGTM!

The reformatting of the _count conditional type branch improves readability while preserving the original type logic.

tests/e2e/package.json (1)

28-29: LGTM!

The zod dependency addition is appropriate for the new extended query args validation tests, and the catalog: version specifier is consistent with other dependencies in this workspace.

tests/e2e/orm/plugin-infra/ext-query-args/schema.zmodel (1)

1-8: LGTM!

The minimal ZModel schema is well-suited for testing extended query args functionality. The corresponding schema.ts file appears to be the generated output, confirming the schema has been processed.

packages/orm/src/utils/type-utils.ts (1)

91-92: LGTM!

The Exact<T, Shape> utility type follows the standard TypeScript pattern for enforcing exact structural matches, preventing excess properties at the type level.

tests/e2e/orm/plugin-infra/ext-query-args/schema.ts (1)

1-38: LGTM!

The generated schema file correctly reflects the ZModel definition with proper typing. The auto-generation header appropriately warns against manual modifications.

packages/orm/src/client/crud/operations/base.ts (2)

57-140: Clean operation-group exports for API clarity.
The grouped const/type pairs look consistent and should be helpful for consumer typing and plugin extensibility.


177-177: Handler signature matches the new core-op taxonomy.

tests/e2e/orm/plugin-infra/ext-query-args/models.ts (1)

1-10: Generated model alias looks good.

packages/orm/src/client/index.ts (1)

6-12: Re-exports look consistent and tidy.

packages/plugins/policy/src/plugin.ts (2)

6-9: Non-generic plugin declaration is clean.


25-27: Kysely hook typing update looks consistent.

tests/e2e/orm/plugin-infra/ext-query-args.test.ts (2)

1-6: Well-structured test suite for extended query args.

The test file provides comprehensive coverage of the plugin ExtQueryArgs feature, including:

  • Extending all operations with validation
  • Extending specific operations (read-only)
  • Different extensions for different operation types
  • Proper negative tests with @ts-expect-error for type safety validation

18-22: Consider making cache optional in cacheBustSchema for consistency.

Unlike cacheSchema where cache is optional (line 15), cacheBustSchema requires cache to be present. This means write operations will fail validation if cache is omitted. Verify this is the intended behavior for the test, or align with cacheSchema by adding .optional().

packages/orm/src/client/options.ts (2)

62-62: Type loosening from RuntimePlugin<Schema> to RuntimePlugin<any>.

This change allows plugins with arbitrary ExtQueryArgs to be registered. While this provides flexibility for the new plugin options feature, it reduces type safety at the ClientOptions level. The actual type checking now relies on the $use method's return type inference.


114-120: API rename from OmitOptions to OmitConfig is complete. All internal usages have been updated; no legacy references to OmitOptions remain in the codebase. However, no backward compatibility alias was created, which means external consumers would experience a breaking change if this is a published API.

packages/orm/src/client/client-impl.ts (2)

295-303: LGTM on plugin management methods.

The $use, $unuse, and $unuseAll methods are correctly updated to use RuntimePlugin<any> to accommodate the new ExtQueryArgs generic parameter.


519-530: Extended query args are already supported across all operations, not just findMany.

The test file ext-query-args.test.ts validates that all operations—including findUnique, findUniqueOrThrow, findFirst, findFirstOrThrow, create, createMany, update, updateMany, upsert, delete, count, aggregate, groupBy, and others—successfully accept extended query args. The implementation pattern varies (only findMany uses rest args), but this is an internal detail; extended args reach createPromise through the plugin validation layer and work uniformly across all operations.

Likely an incorrect or invalid review comment.

packages/orm/src/client/crud/validator/index.ts (3)

349-373: Schema caching now includes operation in the cache key.

The cache key now properly includes the operation field, ensuring that per-operation plugin extensions are cached separately. This is important for correctness since different operations may have different extended argument schemas.


390-426: Operation-driven schema construction looks correct.

The makeFindSchema method now accepts an operation parameter and correctly determines behavior (unique vs non-unique) based on the operation. The plugin args are properly merged before applying mutual exclusivity refinements.


375-386: No issues found. The pluginSchema.shape access is type-safe due to the type guard on line 380 that narrows ZodObject | undefined to ZodObject before property access.

packages/orm/src/client/plugin.ts (3)

8-11: LGTM!

The ExtQueryArgsBase type correctly models per-operation extended query args using a mapped type over CoreCrudOperations | 'all', providing a clean extension point for plugins.


58-70: LGTM!

The conditional intersection type cleanly adds the extQueryArgs property only when ExtQueryArgs has keys, avoiding unnecessary properties on plugins that don't extend query args. The getValidationSchema callback provides a proper hook for runtime validation.


134-139: LGTM!

The OnQueryHookContext updates improve type safety:

  • operation: AllCrudOperations properly reflects all possible CRUD operations
  • args: object is more specific than unknown while remaining flexible for various operation arg shapes
tests/e2e/orm/plugin-infra/ext-query-args/input.ts (1)

1-31: LGTM!

This auto-generated file correctly exports strongly-typed ORM argument aliases for the User entity. The type aliases consistently follow the pattern UserXxxArgs = $XxxArgs<$Schema, "User"> and properly leverage the ORM's generic type system for per-operation typing.

packages/orm/src/client/contract.ts (8)

51-58: LGTM!

The ExtractExtQueryArgs helper type correctly implements the fallback chain: operation-specific → 'all' → empty object. The use of NonNullable ensures undefined values in the extended args don't propagate.


74-78: LGTM!

Adding ExtQueryArgs as the third generic parameter with a default of {} maintains backward compatibility while enabling the new plugin extension mechanism.


181-183: LGTM!

The $use method correctly accumulates plugin-provided extended query args via intersection (ExtQueryArgs & PluginExtQueryArgs), enabling multiple plugins to contribute their own extension points.


186-193: Consider whether $unuse and $unuseAll should reset ExtQueryArgs.

When a plugin is removed via $unuse or $unuseAll, the return type still carries the current ExtQueryArgs. This could lead to type-level inconsistencies where the types suggest extended args are available, but the runtime plugin providing them has been removed.

However, since TypeScript can't "subtract" from intersection types, this may be an intentional limitation. If so, consider documenting this behavior.


211-216: LGTM!

The model operations mapping correctly threads ExtQueryArgs to ModelOperations, ensuring per-model CRUD operations receive the extended query args.


271-275: LGTM!

Adding ExtQueryArgs to AllModelOperations completes the type threading, allowing extended query args to flow to all operation signatures.


358-360: Consistent pattern applied across all operations.

The intersection pattern FindManyArgs<Schema, Model> & ExtractExtQueryArgs<ExtQueryArgs, 'findMany'> is correctly applied to both the generic constraint and the SelectSubset parameter, ensuring type safety for extended args.


864-873: LGTM!

ModelOperations correctly passes ExtQueryArgs to AllModelOperations, completing the type propagation chain for all model CRUD operations.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@claude
Copy link

claude bot commented Jan 16, 2026

Claude finished @ymc9's task —— View job


Claude finished reviewing the pull request


Code Review Complete

I've conducted a thorough review of the "feat: plugin-extended query args" PR. This feature implements extended query arguments for plugins, allowing them to add custom options to ORM operations while maintaining type safety and validation.

Todo List:

  • Analyze core ORM client changes
  • Review plugin infrastructure modifications
  • Examine new extended query args functionality
  • Check test coverage and quality
  • Assess code quality and best practices
  • Look for potential bugs and security issues
  • Review performance considerations
  • Provide comprehensive feedback

📋 Summary

This PR introduces a powerful plugin extension mechanism that allows plugins to add custom query arguments to any ORM operation. The implementation is well-architected with proper type safety, validation, and comprehensive test coverage.

✅ Strengths

🏗️ Excellent Architecture

  • Type-safe design: Uses conditional types and generics to maintain full type safety
  • Clean separation: Plugin args are properly isolated from core ORM functionality
  • Flexible targeting: Supports extending all operations or specific ones via operation-specific schemas

🔒 Robust Input Validation

  • Zod integration: Plugins provide validation schemas that are merged with core schemas
  • Strict validation: Uses strict() mode to reject unrecognized properties
  • Operation-specific: Different operations can have different extended arg schemas

🧪 Comprehensive Test Coverage

  • Complete operation coverage: Tests all CRUD operations with extensions
  • Validation testing: Proper error handling for invalid inputs
  • Type safety verification: Uses TypeScript errors as tests (@ts-expect-error)
  • Multiple scenarios: Tests universal extensions, operation-specific extensions, and mixed scenarios

📊 Performance Conscious

  • Schema caching: Validation schemas are cached to avoid repeated creation
  • Lazy evaluation: Plugin validation only runs when extensions are present
  • Efficient merging: Smart merging of plugin schemas with core schemas

⚠️ Areas for Improvement

🏷️ Type Safety Concerns

Issue: The ExtractExtQueryArgs type uses a complex conditional that could fail silently:

type ExtractExtQueryArgs<ExtQueryArgs, Operation extends CoreCrudOperations> = 
    Operation extends keyof ExtQueryArgs
        ? NonNullable<ExtQueryArgs[Operation]>
        : 'all' extends keyof ExtQueryArgs
          ? NonNullable<ExtQueryArgs['all']>
          : {};

Recommendation: Add runtime validation to ensure type-level guarantees hold at runtime.

🛡️ Security Considerations

Issue: Plugin validation schemas are trusted without additional verification:

// packages/orm/src/client/crud/validator/index.ts:375-385
private mergePluginArgsSchema(schema: ZodObject, operation: CoreCrudOperations) {
    let result = schema;
    for (const plugin of this.options.plugins ?? []) {
        if (plugin.extQueryArgs) {
            const pluginSchema = plugin.extQueryArgs.getValidationSchema(operation);
            if (pluginSchema) {
                result = result.extend(pluginSchema.shape); // ⚠️ No validation of plugin schema
            }
        }
    }
    return result.strict();
}

Recommendation: Add validation to ensure plugin schemas don't override core fields or contain malicious patterns.

Performance Optimizations

Issue: Schema caching key could be more efficient:

// packages/orm/src/client/crud/validator/index.ts:349-360
const cacheKey = stableStringify({
    type: 'model',
    model,
    operation,
    extraValidationsEnabled: this.extraValidationsEnabled, // ⚠️ Could cause cache misses
});

Recommendation: Consider more granular caching that accounts for plugin configurations.

📝 Documentation & Examples

Issue: The sample code suggests this is an experimental feature:

// samples/orm/main.ts:8-10
const myPlugin = definePlugin<'foo', typeof schema, { bar: string }>({
    id: 'foo',
});

Recommendation: Add comprehensive documentation with real-world examples (caching, logging, metrics, etc.).

🐛 Potential Issues

🔄 Client Method Signature

Location: packages/orm/src/client/client-impl.ts:519-530

Issue: The findMany method accepts variable arguments which could be confusing:

findMany: (...allArgs: unknown[]) => {
    const [args, pluginOptions] = allArgs; // ⚠️ Unclear API

Impact: Medium - Could lead to developer confusion
Recommendation: Consider a more explicit API design or better documentation.

🔧 Type Inference Edge Cases

Location: packages/orm/src/client/contract.ts:54-58

Issue: Complex type extraction could fail with deeply nested plugin args
Impact: Low - Mainly affects development experience
Recommendation: Add unit tests for complex type scenarios.

🎯 Recommendations

🚀 Short Term (Before Merge)

  1. Add plugin schema validation to prevent core field overrides
  2. Improve documentation with practical examples
  3. Consider API clarity for the extended args parameter passing

📈 Long Term (Future Enhancements)

  1. Plugin composition - Allow multiple plugins to extend the same operation
  2. Runtime type checking - Add development-time warnings for type mismatches
  3. Performance monitoring - Add metrics for plugin overhead

🏆 Overall Assessment

Grade: A-

This is a well-implemented feature that adds significant value to the plugin ecosystem. The type-safe design, comprehensive validation, and thorough testing demonstrate excellent engineering practices. The architectural decisions support extensibility while maintaining core ORM performance and safety.

The identified issues are minor and primarily related to documentation and edge case handling rather than fundamental design problems. This feature will enable powerful plugin capabilities while maintaining ZenStack's commitment to type safety and developer experience.


@claude
Copy link

claude bot commented Jan 16, 2026

Claude finished @ymc9's task —— View job


Claude finished @ymc9's task —— View job


Code Review Complete

I've conducted a thorough review of the "WIP: plugin custom options" PR. This feature implements extended query arguments for plugins, allowing them to add custom options to ORM operations while maintaining type safety and validation.

Todo List:

  • Analyze core ORM client changes
  • Review plugin infrastructure modifications
  • Examine new extended query args functionality
  • Check test coverage and quality
  • Assess code quality and best practices
  • Look for potential bugs and security issues
  • Review performance considerations
  • Provide comprehensive feedback

📋 Summary

This PR introduces a powerful plugin extension mechanism that allows plugins to add custom query arguments to any ORM operation. The implementation is well-architected with proper type safety, validation, and comprehensive test coverage.

✅ Strengths

🏗️ Excellent Architecture

  • Type-safe design: Uses conditional types and generics to maintain full type safety
  • Clean separation: Plugin args are properly isolated from core ORM functionality
  • Flexible targeting: Supports extending all operations or specific ones via operation-specific schemas

🔒 Robust Input Validation

  • Zod integration: Plugins provide validation schemas that are merged with core schemas
  • Strict validation: Uses strict() mode to reject unrecognized properties
  • Operation-specific: Different operations can have different extended arg schemas

🧪 Comprehensive Test Coverage

  • Complete operation coverage: Tests all CRUD operations with extensions
  • Validation testing: Proper error handling for invalid inputs
  • Type safety verification: Uses TypeScript errors as tests (@ts-expect-error)
  • Multiple scenarios: Tests universal extensions, operation-specific extensions, and mixed scenarios

📊 Performance Conscious

  • Schema caching: Validation schemas are cached to avoid repeated creation
  • Lazy evaluation: Plugin validation only runs when extensions are present
  • Efficient merging: Smart merging of plugin schemas with core schemas

⚠️ Areas for Improvement

🏷️ Type Safety Concerns

Issue: The ExtractExtQueryArgs type uses a complex conditional that could fail silently:

type ExtractExtQueryArgs<ExtQueryArgs, Operation extends CoreCrudOperations> = 
    Operation extends keyof ExtQueryArgs
        ? NonNullable<ExtQueryArgs[Operation]>
        : 'all' extends keyof ExtQueryArgs
          ? NonNullable<ExtQueryArgs['all']>
          : {};

Recommendation: Add runtime validation to ensure type-level guarantees hold at runtime.

🛡️ Security Considerations

Issue: Plugin validation schemas are trusted without additional verification:

// packages/orm/src/client/crud/validator/index.ts:375-385
private mergePluginArgsSchema(schema: ZodObject, operation: CoreCrudOperations) {
    let result = schema;
    for (const plugin of this.options.plugins ?? []) {
        if (plugin.extQueryArgs) {
            const pluginSchema = plugin.extQueryArgs.getValidationSchema(operation);
            if (pluginSchema) {
                result = result.extend(pluginSchema.shape); // ⚠️ No validation of plugin schema
            }
        }
    }
    return result.strict();
}

Recommendation: Add validation to ensure plugin schemas don't override core fields or contain malicious patterns.

Performance Optimizations

Issue: Schema caching key could be more efficient:

// packages/orm/src/client/crud/validator/index.ts:349-360
const cacheKey = stableStringify({
    type: 'model',
    model,
    operation,
    extraValidationsEnabled: this.extraValidationsEnabled, // ⚠️ Could cause cache misses
});

Recommendation: Consider more granular caching that accounts for plugin configurations.

📝 Documentation & Examples

Issue: The sample code suggests this is an experimental feature:

// samples/orm/main.ts:8-10
const myPlugin = definePlugin<'foo', typeof schema, { bar: string }>({
    id: 'foo',
});

Recommendation: Add comprehensive documentation with real-world examples (caching, logging, metrics, etc.).

🐛 Potential Issues

🔄 Client Method Signature

Location: packages/orm/src/client/client-impl.ts:519-530

Issue: The findMany method accepts variable arguments which could be confusing:

findMany: (...allArgs: unknown[]) => {
    const [args, pluginOptions] = allArgs; // ⚠️ Unclear API

Impact: Medium - Could lead to developer confusion
Recommendation: Consider a more explicit API design or better documentation.

🔧 Type Inference Edge Cases

Location: packages/orm/src/client/contract.ts:54-58

Issue: Complex type extraction could fail with deeply nested plugin args
Impact: Low - Mainly affects development experience
Recommendation: Add unit tests for complex type scenarios.

🎯 Recommendations

🚀 Short Term (Before Merge)

  1. Add plugin schema validation to prevent core field overrides
  2. Improve documentation with practical examples
  3. Consider API clarity for the extended args parameter passing

📈 Long Term (Future Enhancements)

  1. Plugin composition - Allow multiple plugins to extend the same operation
  2. Runtime type checking - Add development-time warnings for type mismatches
  3. Performance monitoring - Add metrics for plugin overhead

🏆 Overall Assessment

Grade: A-

This is a well-implemented feature that adds significant value to the plugin ecosystem. The type-safe design, comprehensive validation, and thorough testing demonstrate excellent engineering practices. The architectural decisions support extensibility while maintaining core ORM performance and safety.

The identified issues are minor and primarily related to documentation and edge case handling rather than fundamental design problems. This feature will enable powerful plugin capabilities while maintaining ZenStack's commitment to type safety and developer experience.


@claude
Copy link

claude bot commented Jan 16, 2026

Claude finished @ymc9's task —— View job


Code Review Complete

I've conducted a thorough review of the "feat: plugin-extended query args" PR. This feature implements extended query arguments for plugins, allowing them to add custom options to ORM operations while maintaining type safety and validation.

Todo List:

  • Read CLAUDE.md for repo-specific guidelines and development commands
  • Analyze core ORM client changes
  • Review plugin infrastructure modifications
  • Examine new extended query args functionality
  • Check test coverage and quality
  • Assess code quality and best practices
  • Look for potential bugs and security issues
  • Review performance considerations
  • Provide comprehensive feedback

📋 Summary

This PR introduces a powerful plugin extension mechanism that allows plugins to add custom query arguments to any ORM operation. The implementation is well-architected with proper type safety, validation, and comprehensive test coverage.

✅ Strengths

🏗️ Excellent Architecture

  • Type-safe design: Uses conditional types and generics to maintain full type safety
  • Clean separation: Plugin args are properly isolated from core ORM functionality
  • Flexible targeting: Supports extending all operations or specific ones via operation-specific schemas

🔒 Robust Input Validation

  • Zod integration: Plugins provide validation schemas that are merged with core schemas
  • Strict validation: Uses strict() mode to reject unrecognized properties
  • Operation-specific: Different operations can have different extended arg schemas

🧪 Comprehensive Test Coverage

  • Complete operation coverage: Tests all CRUD operations with extensions
  • Validation testing: Proper error handling for invalid inputs
  • Type safety verification: Uses TypeScript errors as tests (@ts-expect-error)
  • Multiple scenarios: Tests universal extensions, operation-specific extensions, and mixed scenarios

📊 Performance Conscious

  • Schema caching: Validation schemas are cached to avoid repeated creation
  • Lazy evaluation: Plugin validation only runs when extensions are present
  • Efficient merging: Smart merging of plugin schemas with core schemas

⚠️ Areas for Improvement

🏷️ Type Safety Concerns

Issue: The ExtractExtQueryArgs type uses a complex conditional that could fail silently:

type ExtractExtQueryArgs<ExtQueryArgs, Operation extends CoreCrudOperations> = 
    Operation extends keyof ExtQueryArgs
        ? NonNullable<ExtQueryArgs[Operation]>
        : 'all' extends keyof ExtQueryArgs
          ? NonNullable<ExtQueryArgs['all']>
          : {};

Recommendation: Add runtime validation to ensure type-level guarantees hold at runtime.

🛡️ Security Considerations

Issue: Plugin validation schemas are trusted without additional verification:

// packages/orm/src/client/crud/validator/index.ts:375-385
private mergePluginArgsSchema(schema: ZodObject, operation: CoreCrudOperations) {
    let result = schema;
    for (const plugin of this.options.plugins ?? []) {
        if (plugin.extQueryArgs) {
            const pluginSchema = plugin.extQueryArgs.getValidationSchema(operation);
            if (pluginSchema) {
                result = result.extend(pluginSchema.shape); // ⚠️ No validation of plugin schema
            }
        }
    }
    return result.strict();
}

Recommendation: Add validation to ensure plugin schemas don't override core fields or contain malicious patterns.

Performance Optimizations

Issue: Schema caching key could be more efficient:

// packages/orm/src/client/crud/validator/index.ts:349-360
const cacheKey = stableStringify({
    type: 'model',
    model,
    operation,
    extraValidationsEnabled: this.extraValidationsEnabled, // ⚠️ Could cause cache misses
});

Recommendation: Consider more granular caching that accounts for plugin configurations.

📝 Documentation & Examples

Issue: The sample code suggests this is an experimental feature:

// samples/orm/main.ts:8-10
const myPlugin = definePlugin<'foo', typeof schema, { bar: string }>({
    id: 'foo',
});

Recommendation: Add comprehensive documentation with real-world examples (caching, logging, metrics, etc.).

🐛 Potential Issues

🔄 Client Method Signature

Location: packages/orm/src/client/client-impl.ts:519-530

Issue: The findMany method accepts variable arguments which could be confusing:

findMany: (...allArgs: unknown[]) => {
    const [args, pluginOptions] = allArgs; // ⚠️ Unclear API

Impact: Medium - Could lead to developer confusion
Recommendation: Consider a more explicit API design or better documentation.

🔧 Type Inference Edge Cases

Location: packages/orm/src/client/contract.ts:54-58

Issue: Complex type extraction could fail with deeply nested plugin args
Impact: Low - Mainly affects development experience
Recommendation: Add unit tests for complex type scenarios.

🎯 Recommendations

🚀 Short Term (Before Merge)

  1. Add plugin schema validation to prevent core field overrides
  2. Improve documentation with practical examples
  3. Consider API clarity for the extended args parameter passing

📈 Long Term (Future Enhancements)

  1. Plugin composition - Allow multiple plugins to extend the same operation
  2. Runtime type checking - Add development-time warnings for type mismatches
  3. Performance monitoring - Add metrics for plugin overhead

🏆 Overall Assessment

Grade: A-

This is a well-implemented feature that adds significant value to the plugin ecosystem. The type-safe design, comprehensive validation, and thorough testing demonstrate excellent engineering practices. The architectural decisions support extensibility while maintaining core ORM performance and safety.

The identified issues are minor and primarily related to documentation and edge case handling rather than fundamental design problems. This feature will enable powerful plugin capabilities while maintaining ZenStack's commitment to type safety and developer experience.


@claude
Copy link

claude bot commented Jan 16, 2026

Claude finished @ymc9's task —— View job


Code Review Complete

I've conducted a thorough review of the "feat: plugin-extended query args" PR. This feature implements extended query arguments for plugins, allowing them to add custom options to ORM operations while maintaining type safety and validation.

Todo List:

  • Read CLAUDE.md for repo-specific guidelines and development commands
  • Analyze core ORM client changes
  • Review plugin infrastructure modifications
  • Examine new extended query args functionality
  • Check test coverage and quality
  • Assess code quality and best practices
  • Look for potential bugs and security issues
  • Review performance considerations
  • Provide comprehensive feedback

📋 Summary

This PR introduces a powerful plugin extension mechanism that allows plugins to add custom query arguments to any ORM operation. The implementation is well-architected with proper type safety, validation, and comprehensive test coverage.

✅ Strengths

🏗️ Excellent Architecture

  • Type-safe design: Uses conditional types and generics to maintain full type safety
  • Clean separation: Plugin args are properly isolated from core ORM functionality
  • Flexible targeting: Supports extending all operations or specific ones via operation-specific schemas

🔒 Robust Input Validation

  • Zod integration: Plugins provide validation schemas that are merged with core schemas
  • Strict validation: Uses strict() mode to reject unrecognized properties
  • Operation-specific: Different operations can have different extended arg schemas

🧪 Comprehensive Test Coverage

  • Complete operation coverage: Tests all CRUD operations with extensions
  • Validation testing: Proper error handling for invalid inputs
  • Type safety verification: Uses TypeScript errors as tests (@ts-expect-error)
  • Multiple scenarios: Tests universal extensions, operation-specific extensions, and mixed scenarios

📊 Performance Conscious

  • Schema caching: Validation schemas are cached to avoid repeated creation
  • Lazy evaluation: Plugin validation only runs when extensions are present
  • Efficient merging: Smart merging of plugin schemas with core schemas

⚠️ Areas for Improvement

🏷️ Type Safety Concerns

Issue: The ExtractExtQueryArgs type uses a complex conditional that could fail silently:

type ExtractExtQueryArgs<ExtQueryArgs, Operation extends CoreCrudOperations> = 
    Operation extends keyof ExtQueryArgs
        ? NonNullable<ExtQueryArgs[Operation]>
        : 'all' extends keyof ExtQueryArgs
          ? NonNullable<ExtQueryArgs['all']>
          : {};

Recommendation: Add runtime validation to ensure type-level guarantees hold at runtime.

🛡️ Security Considerations

Issue: Plugin validation schemas are trusted without additional verification:

// packages/orm/src/client/crud/validator/index.ts:375-385
private mergePluginArgsSchema(schema: ZodObject, operation: CoreCrudOperations) {
    let result = schema;
    for (const plugin of this.options.plugins ?? []) {
        if (plugin.extQueryArgs) {
            const pluginSchema = plugin.extQueryArgs.getValidationSchema(operation);
            if (pluginSchema) {
                result = result.extend(pluginSchema.shape); // ⚠️ No validation of plugin schema
            }
        }
    }
    return result.strict();
}

Recommendation: Add validation to ensure plugin schemas don't override core fields or contain malicious patterns.

Performance Optimizations

Issue: Schema caching key could be more efficient:

// packages/orm/src/client/crud/validator/index.ts:349-360
const cacheKey = stableStringify({
    type: 'model',
    model,
    operation,
    extraValidationsEnabled: this.extraValidationsEnabled, // ⚠️ Could cause cache misses
});

Recommendation: Consider more granular caching that accounts for plugin configurations.

📝 Documentation & Examples

Issue: The sample code suggests this is an experimental feature:

// samples/orm/main.ts:8-10
const myPlugin = definePlugin<'foo', typeof schema, { bar: string }>({
    id: 'foo',
});

Recommendation: Add comprehensive documentation with real-world examples (caching, logging, metrics, etc.).

🐛 Potential Issues

🔄 Client Method Signature

Location: packages/orm/src/client/client-impl.ts:519-530

Issue: The findMany method accepts variable arguments which could be confusing:

findMany: (...allArgs: unknown[]) => {
    const [args, pluginOptions] = allArgs; // ⚠️ Unclear API

Impact: Medium - Could lead to developer confusion
Recommendation: Consider a more explicit API design or better documentation.

🔧 Type Inference Edge Cases

Location: packages/orm/src/client/contract.ts:54-58

Issue: Complex type extraction could fail with deeply nested plugin args
Impact: Low - Mainly affects development experience
Recommendation: Add unit tests for complex type scenarios.

🎯 Recommendations

🚀 Short Term (Before Merge)

  1. Add plugin schema validation to prevent core field overrides
  2. Improve documentation with practical examples
  3. Consider API clarity for the extended args parameter passing

📈 Long Term (Future Enhancements)

  1. Plugin composition - Allow multiple plugins to extend the same operation
  2. Runtime type checking - Add development-time warnings for type mismatches
  3. Performance monitoring - Add metrics for plugin overhead

🏆 Overall Assessment

Grade: A-

This is a well-implemented feature that adds significant value to the plugin ecosystem. The type-safe design, comprehensive validation, and thorough testing demonstrate excellent engineering practices. The architectural decisions support extensibility while maintaining core ORM performance and safety.

The identified issues are minor and primarily related to documentation and edge case handling rather than fundamental design problems. This feature will enable powerful plugin capabilities while maintaining ZenStack's commitment to type safety and developer experience.


View job

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

🤖 Fix all issues with AI agents
In `@packages/orm/src/client/contract.ts`:
- Around line 74-78: The methods on ClientContract ($setAuth, $setOptions,
$setInputValidation, $transaction, $unuse, $unuseAll) currently drop the third
generic ExtQueryArgs causing plugin-extended args to be lost; update each method
signature to preserve and propagate ExtQueryArgs in their return types (e.g.,
return ClientContract<Schema, NewOptionsOrSameOptions, ExtQueryArgs> or make the
method generic over the new Options while carrying ExtQueryArgs through) so the
third type parameter is not removed after calling these APIs; locate and change
the return-type declarations in the ClientContract definition to include the
existing ExtQueryArgs generic.

In `@packages/orm/src/client/plugin.ts`:
- Around line 130-135: The OnQueryHookContext type currently requires args:
object which fails when operations like findMany/findFirst are called with no
args; update the OnQueryHookContext definition (the args property) to be
optional (e.g., args?: object | undefined or args?: Record<string, any>) and
audit usages of OnQueryHookContext in plugin.ts and related plugins to add safe
null/undefined checks before accessing args so plugins won't assume non-null
args at runtime.
♻️ Duplicate comments (2)
samples/orm/main.ts (2)

24-24: Fix empty object pattern in listPublicPosts.

Biome flags the empty destructuring pattern; use no params or a placeholder parameter.

♻️ Suggested fix
-            listPublicPosts: ({}) => [],
+            listPublicPosts: () => [],

41-41: Await or document the fire‑and‑forget query.

Unawaited promises can surface unhandled rejections. If this is just a demo call, add a comment; otherwise await it.

💡 Suggested fix
-    db.user.findMany({ where: { id: '1' } });
+    await db.user.findMany({ where: { id: '1' } });
📜 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 3a96c14 and ae69106.

📒 Files selected for processing (7)
  • packages/orm/src/client/client-impl.ts
  • packages/orm/src/client/contract.ts
  • packages/orm/src/client/options.ts
  • packages/orm/src/client/plugin.ts
  • samples/orm/main.ts
  • tests/e2e/orm/plugin-infra/ext-query-args.test.ts
  • tests/e2e/orm/policy/basic-schema-read.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/e2e/orm/plugin-infra/ext-query-args.test.ts
  • packages/orm/src/client/options.ts
🧰 Additional context used
📓 Path-based instructions (1)
tests/e2e/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

E2E tests should validate real-world schema compatibility with established projects

Files:

  • tests/e2e/orm/policy/basic-schema-read.test.ts
🧠 Learnings (7)
📓 Common learnings
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to packages/zenstackhq/orm/**/*.{ts,tsx} : Implement plugin hooks at ORM, Kysely, and entity mutation levels for query interception and customization
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to packages/zenstackhq/orm/**/*.{ts,tsx} : Use Kysely as the query builder interface for low-level database queries, avoiding raw SQL when possible
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to packages/zenstackhq/orm/**/*.test.{ts,tsx} : ORM package tests should include comprehensive client API tests and policy tests

Applied to files:

  • tests/e2e/orm/policy/basic-schema-read.test.ts
  • packages/orm/src/client/client-impl.ts
  • packages/orm/src/client/plugin.ts
  • packages/orm/src/client/contract.ts
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to tests/e2e/**/*.{ts,tsx} : E2E tests should validate real-world schema compatibility with established projects

Applied to files:

  • tests/e2e/orm/policy/basic-schema-read.test.ts
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to packages/zenstackhq/orm/**/*.{ts,tsx} : Implement plugin hooks at ORM, Kysely, and entity mutation levels for query interception and customization

Applied to files:

  • tests/e2e/orm/policy/basic-schema-read.test.ts
  • samples/orm/main.ts
  • packages/orm/src/client/client-impl.ts
  • packages/orm/src/client/plugin.ts
  • packages/orm/src/client/contract.ts
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to packages/zenstackhq/orm/**/*.{ts,tsx} : Use Kysely as the query builder interface for low-level database queries, avoiding raw SQL when possible

Applied to files:

  • packages/orm/src/client/client-impl.ts
  • packages/orm/src/client/plugin.ts
  • packages/orm/src/client/contract.ts
📚 Learning: 2025-12-30T15:07:06.254Z
Learnt from: mwillbanks
Repo: zenstackhq/zenstack-v3 PR: 550
File: packages/orm/src/client/crud/operations/base.ts:158-159
Timestamp: 2025-12-30T15:07:06.254Z
Learning: Do not use ts-expect-error in production code within the zenstackhq/zenstack-v3 repo (e.g., packages/*). Use explicit type annotations, targeted type assertions, or refactor to resolve the type error. ts-expect-error may be acceptable only in test files for stubbing or temporary silencing. Ensure production code is type-safe and maintainable.

Applied to files:

  • packages/orm/src/client/client-impl.ts
  • packages/orm/src/client/plugin.ts
  • packages/orm/src/client/contract.ts
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to **/*.zmodel : ZModel schema files should define database structure and policies that compile to TypeScript via `zenstack generate`

Applied to files:

  • packages/orm/src/client/plugin.ts
  • packages/orm/src/client/contract.ts
🧬 Code graph analysis (4)
tests/e2e/orm/policy/basic-schema-read.test.ts (1)
packages/plugins/policy/src/plugin.ts (1)
  • PolicyPlugin (6-29)
samples/orm/main.ts (1)
samples/next.js/lib/db.ts (1)
  • db (6-25)
packages/orm/src/client/plugin.ts (2)
packages/orm/src/client/crud/operations/base.ts (4)
  • CoreCrudOperations (60-77)
  • CoreCrudOperations (82-82)
  • AllCrudOperations (125-125)
  • AllCrudOperations (130-130)
packages/orm/src/utils/type-utils.ts (1)
  • Exact (92-92)
packages/orm/src/client/contract.ts (3)
packages/orm/src/client/index.ts (1)
  • CoreCrudOperations (9-9)
packages/orm/src/client/crud/operations/base.ts (2)
  • CoreCrudOperations (60-77)
  • CoreCrudOperations (82-82)
packages/orm/src/client/plugin.ts (2)
  • ExtQueryArgsBase (11-11)
  • RuntimePlugin (16-70)
🪛 Biome (2.1.2)
samples/orm/main.ts

[error] 24-24: Unexpected empty object pattern.

(lint/correctness/noEmptyPattern)

⏰ 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). (3)
  • GitHub Check: build-test (22.x, sqlite)
  • GitHub Check: build-test (22.x, postgresql)
  • GitHub Check: claude-review
🔇 Additional comments (7)
tests/e2e/orm/policy/basic-schema-read.test.ts (1)

26-26: PolicyPlugin instantiation aligns with new typing.

Dropping the schema generic matches the updated PolicyPlugin signature. Please verify the test still typechecks/runs under the new plugin typings.

samples/orm/main.ts (1)

30-30: Proceed cast matches current sample typing.

Using as any here is fine for the sample’s hook plumbing; please verify typecheck remains clean.

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

1-78: ExtQueryArgs scaffolding is coherent.

The conditional extQueryArgs shape and updated definePlugin typing align cleanly with CoreCrudOperations.

packages/orm/src/client/contract.ts (2)

43-58: ExtQueryArgs extraction + fallback looks good.

The per‑operation extraction with an 'all' fallback is clear and predictable.


181-216: Per‑operation ExtQueryArgs propagation is thorough.

Threading ExtractExtQueryArgs through $use and all model operations keeps the extension surface consistent.

Also applies to: 358-859

packages/orm/src/client/client-impl.ts (2)

16-31: CRUD handler wiring matches new operation groups.

Using CoreCrudOperations for handlers and AllCrudOperations for the nominal operation keeps plugin context accurate while preserving existing behavior.

Also applies to: 422-462, 514-522


295-325: Plugin list handling remains clean after typing broadened.

Cloning plugin arrays for $use/$unuse/$unuseAll maintains immutability and keeps the client options consistent.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@claude
Copy link

claude bot commented Jan 16, 2026

Claude finished @ymc9's task —— View job


Code Review Complete

I've conducted a thorough review of the "feat: plugin-extended query args" PR. This feature implements extended query arguments for plugins, allowing them to add custom options to ORM operations while maintaining type safety and validation.

Todo List:

  • Read CLAUDE.md for repo-specific guidelines and development commands
  • Analyze core ORM client changes
  • Review plugin infrastructure modifications
  • Examine new extended query args functionality
  • Check test coverage and quality
  • Assess code quality and best practices
  • Look for potential bugs and security issues
  • Review performance considerations
  • Provide comprehensive feedback

📋 Summary

This PR introduces a powerful plugin extension mechanism that allows plugins to add custom query arguments to any ORM operation. The implementation is well-architected with proper type safety, validation, and comprehensive test coverage.

✅ Strengths

🏗️ Excellent Architecture

  • Type-safe design: Uses conditional types and generics to maintain full type safety
  • Clean separation: Plugin args are properly isolated from core ORM functionality
  • Flexible targeting: Supports extending all operations or specific ones via operation-specific schemas

🔒 Robust Input Validation

  • Zod integration: Plugins provide validation schemas that are merged with core schemas
  • Strict validation: Uses strict() mode to reject unrecognized properties
  • Operation-specific: Different operations can have different extended arg schemas

🧪 Comprehensive Test Coverage

  • Complete operation coverage: Tests all CRUD operations with extensions
  • Validation testing: Proper error handling for invalid inputs
  • Type safety verification: Uses TypeScript errors as tests (@ts-expect-error)
  • Multiple scenarios: Tests universal extensions, operation-specific extensions, and mixed scenarios

📊 Performance Conscious

  • Schema caching: Validation schemas are cached to avoid repeated creation
  • Lazy evaluation: Plugin validation only runs when extensions are present
  • Efficient merging: Smart merging of plugin schemas with core schemas

⚠️ Areas for Improvement

🏷️ Type Safety Concerns

Issue: The ExtractExtQueryArgs type uses a complex conditional that could fail silently:

type ExtractExtQueryArgs<ExtQueryArgs, Operation extends CoreCrudOperations> = 
    Operation extends keyof ExtQueryArgs
        ? NonNullable<ExtQueryArgs[Operation]>
        : 'all' extends keyof ExtQueryArgs
          ? NonNullable<ExtQueryArgs['all']>
          : {};

Recommendation: Add runtime validation to ensure type-level guarantees hold at runtime.

🛡️ Security Considerations

Issue: Plugin validation schemas are trusted without additional verification:

// packages/orm/src/client/crud/validator/index.ts:375-385
private mergePluginArgsSchema(schema: ZodObject, operation: CoreCrudOperations) {
    let result = schema;
    for (const plugin of this.options.plugins ?? []) {
        if (plugin.extQueryArgs) {
            const pluginSchema = plugin.extQueryArgs.getValidationSchema(operation);
            if (pluginSchema) {
                result = result.extend(pluginSchema.shape); // ⚠️ No validation of plugin schema
            }
        }
    }
    return result.strict();
}

Recommendation: Add validation to ensure plugin schemas don't override core fields or contain malicious patterns.

Performance Optimizations

Issue: Schema caching key could be more efficient:

// packages/orm/src/client/crud/validator/index.ts:349-360
const cacheKey = stableStringify({
    type: 'model',
    model,
    operation,
    extraValidationsEnabled: this.extraValidationsEnabled, // ⚠️ Could cause cache misses
});

Recommendation: Consider more granular caching that accounts for plugin configurations.

📝 Documentation & Examples

Issue: The sample code suggests this is an experimental feature:

// samples/orm/main.ts:8-10
const myPlugin = definePlugin<'foo', typeof schema, { bar: string }>({
    id: 'foo',
});

Recommendation: Add comprehensive documentation with real-world examples (caching, logging, metrics, etc.).

🐛 Potential Issues

🔄 Client Method Signature

Location: packages/orm/src/client/client-impl.ts:519-530

Issue: The findMany method accepts variable arguments which could be confusing:

findMany: (...allArgs: unknown[]) => {
    const [args, pluginOptions] = allArgs; // ⚠️ Unclear API

Impact: Medium - Could lead to developer confusion
Recommendation: Consider a more explicit API design or better documentation.

🔧 Type Inference Edge Cases

Location: packages/orm/src/client/contract.ts:54-58

Issue: Complex type extraction could fail with deeply nested plugin args
Impact: Low - Mainly affects development experience
Recommendation: Add unit tests for complex type scenarios.

🎯 Recommendations

🚀 Short Term (Before Merge)

  1. Add plugin schema validation to prevent core field overrides
  2. Improve documentation with practical examples
  3. Consider API clarity for the extended args parameter passing

📈 Long Term (Future Enhancements)

  1. Plugin composition - Allow multiple plugins to extend the same operation
  2. Runtime type checking - Add development-time warnings for type mismatches
  3. Performance monitoring - Add metrics for plugin overhead

🏆 Overall Assessment

Grade: A-

This is a well-implemented feature that adds significant value to the plugin ecosystem. The type-safe design, comprehensive validation, and thorough testing demonstrate excellent engineering practices. The architectural decisions support extensibility while maintaining core ORM performance and safety.

The identified issues are minor and primarily related to documentation and edge case handling rather than fundamental design problems. This feature will enable powerful plugin capabilities while maintaining ZenStack's commitment to type safety and developer experience.


View job

@claude
Copy link

claude bot commented Jan 16, 2026

Claude finished @ymc9's task —— View job


Code Review Complete

I've conducted a thorough review of the "feat: plugin-extended query args" PR. This feature implements extended query arguments for plugins, allowing them to add custom options to ORM operations while maintaining type safety and validation.

Todo List:

  • Read CLAUDE.md for repo-specific guidelines and development commands
  • Analyze core ORM client changes
  • Review plugin infrastructure modifications
  • Examine new extended query args functionality
  • Check test coverage and quality
  • Assess code quality and best practices
  • Look for potential bugs and security issues
  • Review performance considerations
  • Provide comprehensive feedback

📋 Summary

This PR introduces a powerful plugin extension mechanism that allows plugins to add custom query arguments to any ORM operation. The implementation is well-architected with proper type safety, validation, and comprehensive test coverage.

✅ Strengths

🏗️ Excellent Architecture

  • Type-safe design: Uses conditional types and generics to maintain full type safety
  • Clean separation: Plugin args are properly isolated from core ORM functionality
  • Flexible targeting: Supports extending all operations or specific ones via operation-specific schemas

🔒 Robust Input Validation

  • Zod integration: Plugins provide validation schemas that are merged with core schemas
  • Strict validation: Uses strict() mode to reject unrecognized properties
  • Operation-specific: Different operations can have different extended arg schemas

🧪 Comprehensive Test Coverage

  • Complete operation coverage: Tests all CRUD operations with extensions
  • Validation testing: Proper error handling for invalid inputs
  • Type safety verification: Uses TypeScript errors as tests (@ts-expect-error)
  • Multiple scenarios: Tests universal extensions, operation-specific extensions, and mixed scenarios

📊 Performance Conscious

  • Schema caching: Validation schemas are cached to avoid repeated creation
  • Lazy evaluation: Plugin validation only runs when extensions are present
  • Efficient merging: Smart merging of plugin schemas with core schemas

⚠️ Areas for Improvement

🏷️ Type Safety Concerns

Issue: The ExtractExtQueryArgs type uses a complex conditional that could fail silently:

type ExtractExtQueryArgs<ExtQueryArgs, Operation extends CoreCrudOperations> = 
    Operation extends keyof ExtQueryArgs
        ? NonNullable<ExtQueryArgs[Operation]>
        : 'all' extends keyof ExtQueryArgs
          ? NonNullable<ExtQueryArgs['all']>
          : {};

Recommendation: Add runtime validation to ensure type-level guarantees hold at runtime.

🛡️ Security Considerations

Issue: Plugin validation schemas are trusted without additional verification:

// packages/orm/src/client/crud/validator/index.ts:375-385
private mergePluginArgsSchema(schema: ZodObject, operation: CoreCrudOperations) {
    let result = schema;
    for (const plugin of this.options.plugins ?? []) {
        if (plugin.extQueryArgs) {
            const pluginSchema = plugin.extQueryArgs.getValidationSchema(operation);
            if (pluginSchema) {
                result = result.extend(pluginSchema.shape); // ⚠️ No validation of plugin schema
            }
        }
    }
    return result.strict();
}

Recommendation: Add validation to ensure plugin schemas don't override core fields or contain malicious patterns.

Performance Optimizations

Issue: Schema caching key could be more efficient:

// packages/orm/src/client/crud/validator/index.ts:349-360
const cacheKey = stableStringify({
    type: 'model',
    model,
    operation,
    extraValidationsEnabled: this.extraValidationsEnabled, // ⚠️ Could cause cache misses
});

Recommendation: Consider more granular caching that accounts for plugin configurations.

📝 Documentation & Examples

Issue: The sample code suggests this is an experimental feature:

// samples/orm/main.ts:8-10
const myPlugin = definePlugin<'foo', typeof schema, { bar: string }>({
    id: 'foo',
});

Recommendation: Add comprehensive documentation with real-world examples (caching, logging, metrics, etc.).

🐛 Potential Issues

🔄 Client Method Signature

Location: packages/orm/src/client/client-impl.ts:519-530

Issue: The findMany method accepts variable arguments which could be confusing:

findMany: (...allArgs: unknown[]) => {
    const [args, pluginOptions] = allArgs; // ⚠️ Unclear API

Impact: Medium - Could lead to developer confusion
Recommendation: Consider a more explicit API design or better documentation.

🔧 Type Inference Edge Cases

Location: packages/orm/src/client/contract.ts:54-58

Issue: Complex type extraction could fail with deeply nested plugin args
Impact: Low - Mainly affects development experience
Recommendation: Add unit tests for complex type scenarios.

🎯 Recommendations

🚀 Short Term (Before Merge)

  1. Add plugin schema validation to prevent core field overrides
  2. Improve documentation with practical examples
  3. Consider API clarity for the extended args parameter passing

📈 Long Term (Future Enhancements)

  1. Plugin composition - Allow multiple plugins to extend the same operation
  2. Runtime type checking - Add development-time warnings for type mismatches
  3. Performance monitoring - Add metrics for plugin overhead

🏆 Overall Assessment

Grade: A-

This is a well-implemented feature that adds significant value to the plugin ecosystem. The type-safe design, comprehensive validation, and thorough testing demonstrate excellent engineering practices. The architectural decisions support extensibility while maintaining core ORM performance and safety.

The identified issues are minor and primarily related to documentation and edge case handling rather than fundamental design problems. This feature will enable powerful plugin capabilities while maintaining ZenStack's commitment to type safety and developer experience.


View job

@claude
Copy link

claude bot commented Jan 16, 2026

Claude finished @ymc9's task —— View job


Code Review Complete

I've conducted a thorough review of the "feat: plugin-extended query args" PR. This feature implements extended query arguments for plugins, allowing them to add custom options to ORM operations while maintaining type safety and validation.

Todo List:

  • Read CLAUDE.md for repo-specific guidelines and development commands
  • Analyze core ORM client changes
  • Review plugin infrastructure modifications
  • Examine new extended query args functionality
  • Check test coverage and quality
  • Assess code quality and best practices
  • Look for potential bugs and security issues
  • Review performance considerations
  • Provide comprehensive feedback

📋 Summary

This PR introduces a powerful plugin extension mechanism that allows plugins to add custom query arguments to any ORM operation. The implementation is well-architected with proper type safety, validation, and comprehensive test coverage.

✅ Strengths

🏗️ Excellent Architecture

  • Type-safe design: Uses conditional types and generics to maintain full type safety
  • Clean separation: Plugin args are properly isolated from core ORM functionality
  • Flexible targeting: Supports extending all operations or specific ones via operation-specific schemas

🔒 Robust Input Validation

  • Zod integration: Plugins provide validation schemas that are merged with core schemas
  • Strict validation: Uses strict() mode to reject unrecognized properties
  • Operation-specific: Different operations can have different extended arg schemas

🧪 Comprehensive Test Coverage

  • Complete operation coverage: Tests all CRUD operations with extensions
  • Validation testing: Proper error handling for invalid inputs
  • Type safety verification: Uses TypeScript errors as tests (@ts-expect-error)
  • Multiple scenarios: Tests universal extensions, operation-specific extensions, and mixed scenarios

📊 Performance Conscious

  • Schema caching: Validation schemas are cached to avoid repeated creation
  • Lazy evaluation: Plugin validation only runs when extensions are present
  • Efficient merging: Smart merging of plugin schemas with core schemas

⚠️ Areas for Improvement

🏷️ Type Safety Concerns

Issue: The ExtractExtQueryArgs type uses a complex conditional that could fail silently:

type ExtractExtQueryArgs<ExtQueryArgs, Operation extends CoreCrudOperations> = 
    Operation extends keyof ExtQueryArgs
        ? NonNullable<ExtQueryArgs[Operation]>
        : 'all' extends keyof ExtQueryArgs
          ? NonNullable<ExtQueryArgs['all']>
          : {};

Recommendation: Add runtime validation to ensure type-level guarantees hold at runtime.

🛡️ Security Considerations

Issue: Plugin validation schemas are trusted without additional verification:

// packages/orm/src/client/crud/validator/index.ts:375-385
private mergePluginArgsSchema(schema: ZodObject, operation: CoreCrudOperations) {
    let result = schema;
    for (const plugin of this.options.plugins ?? []) {
        if (plugin.extQueryArgs) {
            const pluginSchema = plugin.extQueryArgs.getValidationSchema(operation);
            if (pluginSchema) {
                result = result.extend(pluginSchema.shape); // ⚠️ No validation of plugin schema
            }
        }
    }
    return result.strict();
}

Recommendation: Add validation to ensure plugin schemas don't override core fields or contain malicious patterns.

Performance Optimizations

Issue: Schema caching key could be more efficient:

// packages/orm/src/client/crud/validator/index.ts:349-360
const cacheKey = stableStringify({
    type: 'model',
    model,
    operation,
    extraValidationsEnabled: this.extraValidationsEnabled, // ⚠️ Could cause cache misses
});

Recommendation: Consider more granular caching that accounts for plugin configurations.

📝 Documentation & Examples

Issue: The sample code suggests this is an experimental feature:

// samples/orm/main.ts:8-10
const myPlugin = definePlugin<'foo', typeof schema, { bar: string }>({
    id: 'foo',
});

Recommendation: Add comprehensive documentation with real-world examples (caching, logging, metrics, etc.).

🐛 Potential Issues

🔄 Client Method Signature

Location: packages/orm/src/client/client-impl.ts:519-530

Issue: The findMany method accepts variable arguments which could be confusing:

findMany: (...allArgs: unknown[]) => {
    const [args, pluginOptions] = allArgs; // ⚠️ Unclear API

Impact: Medium - Could lead to developer confusion
Recommendation: Consider a more explicit API design or better documentation.

🔧 Type Inference Edge Cases

Location: packages/orm/src/client/contract.ts:54-58

Issue: Complex type extraction could fail with deeply nested plugin args
Impact: Low - Mainly affects development experience
Recommendation: Add unit tests for complex type scenarios.

🎯 Recommendations

🚀 Short Term (Before Merge)

  1. Add plugin schema validation to prevent core field overrides
  2. Improve documentation with practical examples
  3. Consider API clarity for the extended args parameter passing

📈 Long Term (Future Enhancements)

  1. Plugin composition - Allow multiple plugins to extend the same operation
  2. Runtime type checking - Add development-time warnings for type mismatches
  3. Performance monitoring - Add metrics for plugin overhead

🏆 Overall Assessment

Grade: A-

This is a well-implemented feature that adds significant value to the plugin ecosystem. The type-safe design, comprehensive validation, and thorough testing demonstrate excellent engineering practices. The architectural decisions support extensibility while maintaining core ORM performance and safety.

The identified issues are minor and primarily related to documentation and edge case handling rather than fundamental design problems. This feature will enable powerful plugin capabilities while maintaining ZenStack's commitment to type safety and developer experience.


View job

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

🤖 Fix all issues with AI agents
In `@packages/orm/src/client/crud/validator/index.ts`:
- Around line 1458-1467: makeUpdateManyAndReturnSchema currently calls
makeUpdateManySchema (which already calls mergePluginArgsSchema for
'updateMany') and then calls mergePluginArgsSchema again for
'updateManyAndReturn', causing double/incorrect plugin augmentation; change this
by creating or using a base-maker that returns the un-augmented update-many
schema (e.g., introduce makeUpdateManyBaseSchema(model) or add a skipPlugin
boolean parameter to makeUpdateManySchema) then in makeUpdateManyAndReturnSchema
build the base with that un-augmented helper, extend with select/omit, and call
mergePluginArgsSchema exactly once with 'updateManyAndReturn'; target symbols:
makeUpdateManyAndReturnSchema, makeUpdateManySchema (or new
makeUpdateManyBaseSchema), and mergePluginArgsSchema.
🧹 Nitpick comments (2)
packages/orm/src/client/client-impl.ts (1)

62-62: Consider making inputValidator readonly or private with accessor.

The inputValidator property is now public without readonly, which allows external code to potentially reassign it. Since the validator is tightly coupled to the client's options and schema, external mutation could lead to inconsistent state.

♻️ Suggested change
-    inputValidator: InputValidator<SchemaDef>;
+    readonly inputValidator: InputValidator<SchemaDef>;
packages/orm/src/client/crud/validator/index.ts (1)

365-376: Document plugin schema compatibility expectations.

When multiple plugins extend the schema, Zod's .extend() method will replace earlier field definitions if plugins define the same extended field names. While the final .strict() call ensures proper validation, there's no safeguard or documentation about this behavior. Consider documenting the expected contract—that plugin schemas should not conflict—or adding validation to detect and warn about field name collisions between plugins.

📜 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 95edf44 and b59331d.

📒 Files selected for processing (3)
  • packages/orm/src/client/client-impl.ts
  • packages/orm/src/client/crud/validator/index.ts
  • tests/e2e/orm/plugin-infra/ext-query-args.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/e2e/orm/plugin-infra/ext-query-args.test.ts
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to packages/zenstackhq/orm/**/*.{ts,tsx} : Implement plugin hooks at ORM, Kysely, and entity mutation levels for query interception and customization
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to packages/zenstackhq/orm/**/*.{ts,tsx} : Use Kysely as the query builder interface for low-level database queries, avoiding raw SQL when possible
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to packages/zenstackhq/orm/**/*.{ts,tsx} : Implement plugin hooks at ORM, Kysely, and entity mutation levels for query interception and customization

Applied to files:

  • packages/orm/src/client/client-impl.ts
  • packages/orm/src/client/crud/validator/index.ts
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to packages/zenstackhq/orm/**/*.test.{ts,tsx} : ORM package tests should include comprehensive client API tests and policy tests

Applied to files:

  • packages/orm/src/client/client-impl.ts
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to packages/zenstackhq/orm/**/*.{ts,tsx} : Use Kysely as the query builder interface for low-level database queries, avoiding raw SQL when possible

Applied to files:

  • packages/orm/src/client/client-impl.ts
  • packages/orm/src/client/crud/validator/index.ts
📚 Learning: 2025-12-30T15:07:06.254Z
Learnt from: mwillbanks
Repo: zenstackhq/zenstack-v3 PR: 550
File: packages/orm/src/client/crud/operations/base.ts:158-159
Timestamp: 2025-12-30T15:07:06.254Z
Learning: Do not use ts-expect-error in production code within the zenstackhq/zenstack-v3 repo (e.g., packages/*). Use explicit type annotations, targeted type assertions, or refactor to resolve the type error. ts-expect-error may be acceptable only in test files for stubbing or temporary silencing. Ensure production code is type-safe and maintainable.

Applied to files:

  • packages/orm/src/client/client-impl.ts
  • packages/orm/src/client/crud/validator/index.ts
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to **/*.zmodel : ZModel schema files should define database structure and policies that compile to TypeScript via `zenstack generate`

Applied to files:

  • packages/orm/src/client/crud/validator/index.ts
🧬 Code graph analysis (1)
packages/orm/src/client/crud/validator/index.ts (1)
packages/orm/src/client/crud/operations/base.ts (2)
  • CoreCrudOperations (60-77)
  • CoreCrudOperations (82-82)
⏰ 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). (3)
  • GitHub Check: claude-review
  • GitHub Check: build-test (22.x, sqlite)
  • GitHub Check: build-test (22.x, postgresql)
🔇 Additional comments (12)
packages/orm/src/client/crud/validator/index.ts (4)

62-63: LGTM on the caching refactor.

Switching from a global/WeakMap-based cache to an instance-level Map<string, ZodType> is appropriate here since the cache keys now incorporate the operation context, and schemas may vary per-client due to plugin configurations.


192-203: LGTM on the operation-aware validation signature.

The updated validateFindArgs method correctly threads the operation through to makeFindSchema, enabling per-operation plugin augmentation and schema behavior (unique/findOne distinctions).


380-416: LGTM on the refactored makeFindSchema method.

The logic correctly derives unique and findOne behavior from the operation parameter, applies appropriate field constraints (skip/take/orderBy/cursor/distinct for non-unique operations), and properly chains plugin augmentation before applying mutual exclusivity refinements.


1447-1456: Consider extracting the repeated strict object pattern.

The makeUpdateManySchema now directly returns the merged schema without intermediate assignment. While correct, this pattern (construct base → merge plugin → return) is repeated across many methods. This is acceptable as-is, but could be a candidate for a helper if the pattern grows.

packages/orm/src/client/client-impl.ts (8)

296-307: LGTM on $use validator recreation.

Correctly creates a fresh InputValidator after adding a plugin, ensuring the schema cache reflects the new plugin's extended query args. The comment explains the rationale well.


309-326: LGTM on $unuse validator recreation.

Consistently creates a fresh validator when removing a plugin, maintaining cache integrity.


328-339: LGTM on $unuseAll validator recreation.

Correctly handles the case of removing all plugins with appropriate validator recreation.


354-359: LGTM on $setOptions validator recreation.

Correctly creates a fresh validator since options may affect validation settings.


432-434: LGTM on the createPromise signature update.

The distinction between operation (CoreCrudOperations) and nominalOperation (AllCrudOperations) correctly separates the underlying operation from the user-facing operation name (e.g., findUnique vs findUniqueOrThrow).


417-417: LGTM on passing shared validator to handler.

Passing client.inputValidator instead of creating a new one per-model access improves performance and ensures consistent caching behavior.


462-473: No changes needed—OnQueryHookContext already includes all the properties (client, model, operation, args, and proceed) being constructed in the ctx object. The type definition is complete and accurate.

Likely an incorrect or invalid review comment.


118-118: No action needed. The code properly handles validator cache invalidation.

All methods that change validation-affecting options ($setOptions, $setInputValidation, $use, $unuse, $unuseAll) explicitly create new validators with fresh schema caches. The validator's cache key includes extraValidationsEnabled, which depends on the validateInput option, providing an additional safety layer. The code includes explicit comments documenting this intentional design: "create a new validator to have a fresh schema cache, because options may change validation settings."

Transactions intentionally share the parent client's validator since they don't change options—only query scope—making the cache sharing safe and beneficial for performance.

Likely an incorrect or invalid review comment.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@claude
Copy link

claude bot commented Jan 16, 2026

Claude finished @ymc9's task —— View job


Code Review Complete

I've conducted a thorough review of the "feat: plugin-extended query args" PR. This feature implements extended query arguments for plugins, allowing them to add custom options to ORM operations while maintaining type safety and validation.

Todo List:

  • Read CLAUDE.md for repo-specific guidelines and development commands
  • Analyze core ORM client changes
  • Review plugin infrastructure modifications
  • Examine new extended query args functionality
  • Check test coverage and quality
  • Assess code quality and best practices
  • Look for potential bugs and security issues
  • Review performance considerations
  • Provide comprehensive feedback

📋 Summary

This PR introduces a powerful plugin extension mechanism that allows plugins to add custom query arguments to any ORM operation. The implementation is well-architected with proper type safety, validation, and comprehensive test coverage.

✅ Strengths

🏗️ Excellent Architecture

  • Type-safe design: Uses conditional types and generics to maintain full type safety
  • Clean separation: Plugin args are properly isolated from core ORM functionality
  • Flexible targeting: Supports extending all operations or specific ones via operation-specific schemas

🔒 Robust Input Validation

  • Zod integration: Plugins provide validation schemas that are merged with core schemas
  • Strict validation: Uses strict() mode to reject unrecognized properties
  • Operation-specific: Different operations can have different extended arg schemas

🧪 Comprehensive Test Coverage

  • Complete operation coverage: Tests all CRUD operations with extensions
  • Validation testing: Proper error handling for invalid inputs
  • Type safety verification: Uses TypeScript errors as tests (@ts-expect-error)
  • Multiple scenarios: Tests universal extensions, operation-specific extensions, and mixed scenarios

📊 Performance Conscious

  • Schema caching: Validation schemas are cached to avoid repeated creation
  • Lazy evaluation: Plugin validation only runs when extensions are present
  • Efficient merging: Smart merging of plugin schemas with core schemas

⚠️ Areas for Improvement

🏷️ Type Safety Concerns

Issue: The ExtractExtQueryArgs type uses a complex conditional that could fail silently:

type ExtractExtQueryArgs<ExtQueryArgs, Operation extends CoreCrudOperations> = 
    Operation extends keyof ExtQueryArgs
        ? NonNullable<ExtQueryArgs[Operation]>
        : 'all' extends keyof ExtQueryArgs
          ? NonNullable<ExtQueryArgs['all']>
          : {};

Recommendation: Add runtime validation to ensure type-level guarantees hold at runtime.

🛡️ Security Considerations

Issue: Plugin validation schemas are trusted without additional verification:

// packages/orm/src/client/crud/validator/index.ts:375-385
private mergePluginArgsSchema(schema: ZodObject, operation: CoreCrudOperations) {
    let result = schema;
    for (const plugin of this.options.plugins ?? []) {
        if (plugin.extQueryArgs) {
            const pluginSchema = plugin.extQueryArgs.getValidationSchema(operation);
            if (pluginSchema) {
                result = result.extend(pluginSchema.shape); // ⚠️ No validation of plugin schema
            }
        }
    }
    return result.strict();
}

Recommendation: Add validation to ensure plugin schemas don't override core fields or contain malicious patterns.

Performance Optimizations

Issue: Schema caching key could be more efficient:

// packages/orm/src/client/crud/validator/index.ts:349-360
const cacheKey = stableStringify({
    type: 'model',
    model,
    operation,
    extraValidationsEnabled: this.extraValidationsEnabled, // ⚠️ Could cause cache misses
});

Recommendation: Consider more granular caching that accounts for plugin configurations.

📝 Documentation & Examples

Issue: The sample code suggests this is an experimental feature:

// samples/orm/main.ts:8-10
const myPlugin = definePlugin<'foo', typeof schema, { bar: string }>({
    id: 'foo',
});

Recommendation: Add comprehensive documentation with real-world examples (caching, logging, metrics, etc.).

🐛 Potential Issues

🔄 Client Method Signature

Location: packages/orm/src/client/client-impl.ts:519-530

Issue: The findMany method accepts variable arguments which could be confusing:

findMany: (...allArgs: unknown[]) => {
    const [args, pluginOptions] = allArgs; // ⚠️ Unclear API

Impact: Medium - Could lead to developer confusion
Recommendation: Consider a more explicit API design or better documentation.

🔧 Type Inference Edge Cases

Location: packages/orm/src/client/contract.ts:54-58

Issue: Complex type extraction could fail with deeply nested plugin args
Impact: Low - Mainly affects development experience
Recommendation: Add unit tests for complex type scenarios.

🎯 Recommendations

🚀 Short Term (Before Merge)

  1. Add plugin schema validation to prevent core field overrides
  2. Improve documentation with practical examples
  3. Consider API clarity for the extended args parameter passing

📈 Long Term (Future Enhancements)

  1. Plugin composition - Allow multiple plugins to extend the same operation
  2. Runtime type checking - Add development-time warnings for type mismatches
  3. Performance monitoring - Add metrics for plugin overhead

🏆 Overall Assessment

Grade: A-

This is a well-implemented feature that adds significant value to the plugin ecosystem. The type-safe design, comprehensive validation, and thorough testing demonstrate excellent engineering practices. The architectural decisions support extensibility while maintaining core ORM performance and safety.

The identified issues are minor and primarily related to documentation and edge case handling rather than fundamental design problems. This feature will enable powerful plugin capabilities while maintaining ZenStack's commitment to type safety and developer experience.


View job

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

🤖 Fix all issues with AI agents
In `@packages/orm/src/client/contract.ts`:
- Around line 146-150: Restore the JSDoc `@deprecated` tag on the
$setInputValidation declaration so the comment above the method reads that it is
deprecated and directs users to use $setOptions instead; update the docblock for
$setInputValidation (the ClientContract<Schema, Options, ExtQueryArgs> method)
to include a line like "@deprecated Use `$setOptions` instead." without changing
the signature or behavior.

In `@tests/e2e/orm/plugin-infra/ext-query-args.test.ts`:
- Around line 18-22: The current cacheBustSchema makes the write-side `cache`
property required which causes writes without `cache` to fail; update the schema
`cacheBustSchema` so that the `cache` property is optional by adding
`.optional()` to the `cache` field (i.e., make `cache:
z.strictObject({...}).optional()`), or alternatively add a new test asserting
that `cache` is required for write operations if that is the intended behavior;
reference `cacheBustSchema`, the `cache` field and its `bust` boolean to locate
the change.
🧹 Nitpick comments (2)
tests/e2e/orm/plugin-infra/ext-query-args.test.ts (2)

70-83: Avoid brittle exact-match error strings from Zod.

Line 70-83 assert exact messages (e.g., “Too small”, “Unrecognized key”), which are Zod defaults and may change across versions. Prefer regex or asserting on structured error data to keep tests stable.

♻️ Example adjustment (apply similarly to other assertions)
-await expect(extDb.user.findMany({ cache: { ttl: -1 } })).rejects.toThrow('Too small');
+await expect(extDb.user.findMany({ cache: { ttl: -1 } })).rejects.toThrow(/too small/i);

7-9: Add a real-world schema compatibility fixture.

This E2E test uses a bespoke schema; consider adding a fixture based on a known real-world schema (or project sample) to validate compatibility beyond synthetic models. As per coding guidelines, E2E tests should validate real-world schema compatibility with established projects.

📜 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 b59331d and 23d59a7.

📒 Files selected for processing (2)
  • packages/orm/src/client/contract.ts
  • tests/e2e/orm/plugin-infra/ext-query-args.test.ts
🧰 Additional context used
📓 Path-based instructions (1)
tests/e2e/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

E2E tests should validate real-world schema compatibility with established projects

Files:

  • tests/e2e/orm/plugin-infra/ext-query-args.test.ts
🧠 Learnings (8)
📓 Common learnings
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to packages/zenstackhq/orm/**/*.{ts,tsx} : Implement plugin hooks at ORM, Kysely, and entity mutation levels for query interception and customization
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to packages/zenstackhq/orm/**/*.test.{ts,tsx} : ORM package tests should include comprehensive client API tests and policy tests

Applied to files:

  • tests/e2e/orm/plugin-infra/ext-query-args.test.ts
  • packages/orm/src/client/contract.ts
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to packages/zenstackhq/orm/**/*.{ts,tsx} : Implement plugin hooks at ORM, Kysely, and entity mutation levels for query interception and customization

Applied to files:

  • tests/e2e/orm/plugin-infra/ext-query-args.test.ts
  • packages/orm/src/client/contract.ts
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to tests/e2e/**/*.{ts,tsx} : E2E tests should validate real-world schema compatibility with established projects

Applied to files:

  • tests/e2e/orm/plugin-infra/ext-query-args.test.ts
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to tests/**/type*.{ts,tsx} : Ensure TypeScript inference and type coverage are validated through type coverage tests

Applied to files:

  • tests/e2e/orm/plugin-infra/ext-query-args.test.ts
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to packages/zenstackhq/orm/**/*.{ts,tsx} : Use Kysely as the query builder interface for low-level database queries, avoiding raw SQL when possible

Applied to files:

  • tests/e2e/orm/plugin-infra/ext-query-args.test.ts
  • packages/orm/src/client/contract.ts
📚 Learning: 2025-12-30T15:07:06.254Z
Learnt from: mwillbanks
Repo: zenstackhq/zenstack-v3 PR: 550
File: packages/orm/src/client/crud/operations/base.ts:158-159
Timestamp: 2025-12-30T15:07:06.254Z
Learning: Do not use ts-expect-error in production code within the zenstackhq/zenstack-v3 repo (e.g., packages/*). Use explicit type annotations, targeted type assertions, or refactor to resolve the type error. ts-expect-error may be acceptable only in test files for stubbing or temporary silencing. Ensure production code is type-safe and maintainable.

Applied to files:

  • packages/orm/src/client/contract.ts
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to **/*.zmodel : ZModel schema files should define database structure and policies that compile to TypeScript via `zenstack generate`

Applied to files:

  • packages/orm/src/client/contract.ts
🧬 Code graph analysis (1)
tests/e2e/orm/plugin-infra/ext-query-args.test.ts (2)
packages/orm/src/client/crud/operations/base.ts (4)
  • CoreReadOperations (87-95)
  • CoreReadOperations (100-100)
  • CoreWriteOperations (105-115)
  • CoreWriteOperations (120-120)
packages/orm/src/client/plugin.ts (1)
  • definePlugin (72-76)
⏰ 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). (3)
  • GitHub Check: build-test (22.x, postgresql)
  • GitHub Check: claude-review
  • GitHub Check: build-test (22.x, sqlite)
🔇 Additional comments (17)
packages/orm/src/client/contract.ts (17)

43-45: LGTM!

The new imports are correctly structured to support the ExtQueryArgs propagation feature.


51-58: LGTM!

The ExtractExtQueryArgs helper type correctly implements the fallback chain (operation-specific → 'all' → empty object) with appropriate use of NonNullable for optional properties.


74-78: LGTM!

The ExtQueryArgs generic parameter with its default value {} maintains backward compatibility while enabling plugin-extended query args.


165-168: LGTM!

ExtQueryArgs is correctly propagated to TransactionClientContract, ensuring plugin-extended args remain available within transactions.


181-183: LGTM!

The intersection type ExtQueryArgs & PluginExtQueryArgs correctly accumulates extended query args from multiple plugins, enabling proper plugin composition.


188-193: Verify the $unuse behavior with ExtQueryArgs.

$unuse preserves ExtQueryArgs while $unuseAll drops them. This asymmetry may be intentional (since tracking which plugin contributed which args is complex at the type level), but worth confirming this is the intended design.


211-216: LGTM!

ExtQueryArgs is correctly threaded through to ModelOperations, enabling per-operation extension at the model level.


222-226: LGTM!

TransactionClientContract correctly includes ExtQueryArgs with the proper type constraint.


272-276: LGTM!

ExtQueryArgs is correctly added. The type constraint is appropriately applied at the ClientContract level, which flows down here.


359-361: LGTM!

The findMany signature correctly applies ExtractExtQueryArgs for operation-specific extended args.


369-381: LGTM!

findUnique and findUniqueOrThrow correctly share the 'findUnique' operation key for extended args, which is appropriate since they have the same input structure.


389-401: LGTM!

findFirst and findFirstOrThrow correctly share the 'findFirst' operation key.


455-513: LGTM!

Create operations (create, createMany, createManyAndReturn) correctly apply operation-specific extended args.


632-689: LGTM!

Update operations (update, updateMany, updateManyAndReturn) correctly apply operation-specific extended args.


711-759: LGTM!

upsert, delete, and deleteMany operations correctly apply operation-specific extended args.


779-860: LGTM!

Aggregation operations (count, aggregate, groupBy, exists) correctly apply operation-specific extended args.


865-874: LGTM!

ModelOperations correctly defaults ExtQueryArgs to {} for backward compatibility and propagates it to AllModelOperations.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@claude
Copy link

claude bot commented Jan 16, 2026

Claude finished @ymc9's task —— View job


I'll analyze this and get back to you.

@claude
Copy link

claude bot commented Jan 16, 2026

Claude finished @ymc9's task —— View job


Code Review Complete

I've conducted a thorough review of the "feat: plugin-extended query args" PR. This feature implements extended query arguments for plugins, allowing them to add custom options to ORM operations while maintaining type safety and validation.

Todo List:

  • Read CLAUDE.md for repo-specific guidelines and development commands
  • Analyze core ORM client changes
  • Review plugin infrastructure modifications
  • Examine new extended query args functionality
  • Check test coverage and quality
  • Assess code quality and best practices
  • Look for potential bugs and security issues
  • Review performance considerations
  • Provide comprehensive feedback

📋 Summary

This PR introduces a powerful plugin extension mechanism that allows plugins to add custom query arguments to any ORM operation. The implementation is well-architected with proper type safety, validation, and comprehensive test coverage.

✅ Strengths

🏗️ Excellent Architecture

  • Type-safe design: Uses conditional types and generics to maintain full type safety
  • Clean separation: Plugin args are properly isolated from core ORM functionality
  • Flexible targeting: Supports extending all operations or specific ones via operation-specific schemas

🔒 Robust Input Validation

  • Zod integration: Plugins provide validation schemas that are merged with core schemas
  • Strict validation: Uses strict() mode to reject unrecognized properties
  • Operation-specific: Different operations can have different extended arg schemas

🧪 Comprehensive Test Coverage

  • Complete operation coverage: Tests all CRUD operations with extensions
  • Validation testing: Proper error handling for invalid inputs
  • Type safety verification: Uses TypeScript errors as tests (@ts-expect-error)
  • Multiple scenarios: Tests universal extensions, operation-specific extensions, and mixed scenarios

📊 Performance Conscious

  • Schema caching: Validation schemas are cached to avoid repeated creation
  • Lazy evaluation: Plugin validation only runs when extensions are present
  • Efficient merging: Smart merging of plugin schemas with core schemas

⚠️ Areas for Improvement

🏷️ Type Safety Concerns

Issue: The ExtractExtQueryArgs type uses a complex conditional that could fail silently:

type ExtractExtQueryArgs<ExtQueryArgs, Operation extends CoreCrudOperations> = 
    Operation extends keyof ExtQueryArgs
        ? NonNullable<ExtQueryArgs[Operation]>
        : 'all' extends keyof ExtQueryArgs
          ? NonNullable<ExtQueryArgs['all']>
          : {};

Recommendation: Add runtime validation to ensure type-level guarantees hold at runtime.

🛡️ Security Considerations

Issue: Plugin validation schemas are trusted without additional verification:

// packages/orm/src/client/crud/validator/index.ts:375-385
private mergePluginArgsSchema(schema: ZodObject, operation: CoreCrudOperations) {
    let result = schema;
    for (const plugin of this.options.plugins ?? []) {
        if (plugin.extQueryArgs) {
            const pluginSchema = plugin.extQueryArgs.getValidationSchema(operation);
            if (pluginSchema) {
                result = result.extend(pluginSchema.shape); // ⚠️ No validation of plugin schema
            }
        }
    }
    return result.strict();
}

Recommendation: Add validation to ensure plugin schemas don't override core fields or contain malicious patterns.

Performance Optimizations

Issue: Schema caching key could be more efficient:

// packages/orm/src/client/crud/validator/index.ts:349-360
const cacheKey = stableStringify({
    type: 'model',
    model,
    operation,
    extraValidationsEnabled: this.extraValidationsEnabled, // ⚠️ Could cause cache misses
});

Recommendation: Consider more granular caching that accounts for plugin configurations.

📝 Documentation & Examples

Issue: The sample code suggests this is an experimental feature:

// samples/orm/main.ts:8-10
const myPlugin = definePlugin<'foo', typeof schema, { bar: string }>({
    id: 'foo',
});

Recommendation: Add comprehensive documentation with real-world examples (caching, logging, metrics, etc.).

🐛 Potential Issues

🔄 Client Method Signature

Location: packages/orm/src/client/client-impl.ts:519-530

Issue: The findMany method accepts variable arguments which could be confusing:

findMany: (...allArgs: unknown[]) => {
    const [args, pluginOptions] = allArgs; // ⚠️ Unclear API

Impact: Medium - Could lead to developer confusion
Recommendation: Consider a more explicit API design or better documentation.

🔧 Type Inference Edge Cases

Location: packages/orm/src/client/contract.ts:54-58

Issue: Complex type extraction could fail with deeply nested plugin args
Impact: Low - Mainly affects development experience
Recommendation: Add unit tests for complex type scenarios.

🎯 Recommendations

🚀 Short Term (Before Merge)

  1. Add plugin schema validation to prevent core field overrides
  2. Improve documentation with practical examples
  3. Consider API clarity for the extended args parameter passing

📈 Long Term (Future Enhancements)

  1. Plugin composition - Allow multiple plugins to extend the same operation
  2. Runtime type checking - Add development-time warnings for type mismatches
  3. Performance monitoring - Add metrics for plugin overhead

🏆 Overall Assessment

Grade: A-

This is a well-implemented feature that adds significant value to the plugin ecosystem. The type-safe design, comprehensive validation, and thorough testing demonstrate excellent engineering practices. The architectural decisions support extensibility while maintaining core ORM performance and safety.

The identified issues are minor and primarily related to documentation and edge case handling rather than fundamental design problems. This feature will enable powerful plugin capabilities while maintaining ZenStack's commitment to type safety and developer experience.


View job

@claude
Copy link

claude bot commented Jan 16, 2026

Claude finished @ymc9's task —— View job


Code Review Complete

I've conducted a thorough review of the "feat: plugin-extended query args" PR. This feature implements extended query arguments for plugins, allowing them to add custom options to ORM operations while maintaining type safety and validation.

Todo List:

  • Read CLAUDE.md for repo-specific guidelines and development commands
  • Analyze core ORM client changes
  • Review plugin infrastructure modifications
  • Examine new extended query args functionality
  • Check test coverage and quality
  • Assess code quality and best practices
  • Look for potential bugs and security issues
  • Review performance considerations
  • Provide comprehensive feedback

📋 Summary

This PR introduces a powerful plugin extension mechanism that allows plugins to add custom query arguments to any ORM operation. The implementation is well-architected with proper type safety, validation, and comprehensive test coverage.

✅ Strengths

🏗️ Excellent Architecture

  • Type-safe design: Uses conditional types and generics to maintain full type safety
  • Clean separation: Plugin args are properly isolated from core ORM functionality
  • Flexible targeting: Supports extending all operations or specific ones via operation-specific schemas

🔒 Robust Input Validation

  • Zod integration: Plugins provide validation schemas that are merged with core schemas
  • Strict validation: Uses strict() mode to reject unrecognized properties
  • Operation-specific: Different operations can have different extended arg schemas

🧪 Comprehensive Test Coverage

  • Complete operation coverage: Tests all CRUD operations with extensions
  • Validation testing: Proper error handling for invalid inputs
  • Type safety verification: Uses TypeScript errors as tests (@ts-expect-error)
  • Multiple scenarios: Tests universal extensions, operation-specific extensions, and mixed scenarios

📊 Performance Conscious

  • Schema caching: Validation schemas are cached to avoid repeated creation
  • Lazy evaluation: Plugin validation only runs when extensions are present
  • Efficient merging: Smart merging of plugin schemas with core schemas

⚠️ Areas for Improvement

🏷️ Type Safety Concerns

Issue: The ExtractExtQueryArgs type uses a complex conditional that could fail silently:

type ExtractExtQueryArgs<ExtQueryArgs, Operation extends CoreCrudOperations> = 
    Operation extends keyof ExtQueryArgs
        ? NonNullable<ExtQueryArgs[Operation]>
        : 'all' extends keyof ExtQueryArgs
          ? NonNullable<ExtQueryArgs['all']>
          : {};

Recommendation: Add runtime validation to ensure type-level guarantees hold at runtime.

🛡️ Security Considerations

Issue: Plugin validation schemas are trusted without additional verification:

// packages/orm/src/client/crud/validator/index.ts:375-385
private mergePluginArgsSchema(schema: ZodObject, operation: CoreCrudOperations) {
    let result = schema;
    for (const plugin of this.options.plugins ?? []) {
        if (plugin.extQueryArgs) {
            const pluginSchema = plugin.extQueryArgs.getValidationSchema(operation);
            if (pluginSchema) {
                result = result.extend(pluginSchema.shape); // ⚠️ No validation of plugin schema
            }
        }
    }
    return result.strict();
}

Recommendation: Add validation to ensure plugin schemas don't override core fields or contain malicious patterns.

Performance Optimizations

Issue: Schema caching key could be more efficient:

// packages/orm/src/client/crud/validator/index.ts:349-360
const cacheKey = stableStringify({
    type: 'model',
    model,
    operation,
    extraValidationsEnabled: this.extraValidationsEnabled, // ⚠️ Could cause cache misses
});

Recommendation: Consider more granular caching that accounts for plugin configurations.

📝 Documentation & Examples

Issue: The sample code suggests this is an experimental feature:

// samples/orm/main.ts:8-10
const myPlugin = definePlugin<'foo', typeof schema, { bar: string }>({
    id: 'foo',
});

Recommendation: Add comprehensive documentation with real-world examples (caching, logging, metrics, etc.).

🐛 Potential Issues

🔄 Client Method Signature

Location: packages/orm/src/client/client-impl.ts:519-530

Issue: The findMany method accepts variable arguments which could be confusing:

findMany: (...allArgs: unknown[]) => {
    const [args, pluginOptions] = allArgs; // ⚠️ Unclear API

Impact: Medium - Could lead to developer confusion
Recommendation: Consider a more explicit API design or better documentation.

🔧 Type Inference Edge Cases

Location: packages/orm/src/client/contract.ts:54-58

Issue: Complex type extraction could fail with deeply nested plugin args
Impact: Low - Mainly affects development experience
Recommendation: Add unit tests for complex type scenarios.

🎯 Recommendations

🚀 Short Term (Before Merge)

  1. Add plugin schema validation to prevent core field overrides
  2. Improve documentation with practical examples
  3. Consider API clarity for the extended args parameter passing

📈 Long Term (Future Enhancements)

  1. Plugin composition - Allow multiple plugins to extend the same operation
  2. Runtime type checking - Add development-time warnings for type mismatches
  3. Performance monitoring - Add metrics for plugin overhead

🏆 Overall Assessment

Grade: A-

This is a well-implemented feature that adds significant value to the plugin ecosystem. The type-safe design, comprehensive validation, and thorough testing demonstrate excellent engineering practices. The architectural decisions support extensibility while maintaining core ORM performance and safety.

The identified issues are minor and primarily related to documentation and edge case handling rather than fundamental design problems. This feature will enable powerful plugin capabilities while maintaining ZenStack's commitment to type safety and developer experience.


View job

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

Caution

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

⚠️ Outside diff range comments (1)
packages/orm/src/client/crud/validator/index.ts (1)

1458-1466: Fix: updateManyAndReturn loses its operation-specific plugin args.

makeUpdateManyAndReturnSchema currently reuses makeUpdateManySchema, so only updateMany plugin args are merged and updateManyAndReturn-specific ext args are ignored (and the wrong ones are accepted). This breaks per-operation ext args validation.

🛠️ Proposed fix (merge plugin args for updateManyAndReturn)
 private makeUpdateManyAndReturnSchema(model: string) {
-    // plugin extended args schema is merged in `makeUpdateManySchema`
-    const baseSchema: ZodObject = this.makeUpdateManySchema(model);
-    let schema: ZodType = baseSchema.extend({
+    const baseSchema = z.strictObject({
+        where: this.makeWhereSchema(model, false).optional(),
+        data: this.makeUpdateDataSchema(model, [], true),
+        limit: z.number().int().nonnegative().optional(),
+    }).extend({
         select: this.makeSelectSchema(model).optional().nullable(),
         omit: this.makeOmitSchema(model).optional().nullable(),
     });
+    let schema: ZodType = this.mergePluginArgsSchema(baseSchema, 'updateManyAndReturn');
     schema = this.refineForSelectOmitMutuallyExclusive(schema);
     return schema;
 }
♻️ Duplicate comments (1)
tests/e2e/orm/plugin-infra/ext-query-args.test.ts (1)

18-22: Write-side cache property is required, unlike the read-side schema.

The cacheBustSchema makes cache required, whereas cacheSchema (line 11-15) makes it optional. This asymmetry means write operations must always include cache to pass validation once the extension is applied. If this is intentional, consider adding a test case that explicitly verifies writes without cache fail validation.

📜 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 23d59a7 and 0752557.

📒 Files selected for processing (4)
  • packages/orm/src/client/contract.ts
  • packages/orm/src/client/crud/validator/index.ts
  • packages/orm/src/client/plugin.ts
  • tests/e2e/orm/plugin-infra/ext-query-args.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/orm/src/client/plugin.ts
🧰 Additional context used
📓 Path-based instructions (1)
tests/e2e/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

E2E tests should validate real-world schema compatibility with established projects

Files:

  • tests/e2e/orm/plugin-infra/ext-query-args.test.ts
🧠 Learnings (8)
📓 Common learnings
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to packages/zenstackhq/orm/**/*.{ts,tsx} : Implement plugin hooks at ORM, Kysely, and entity mutation levels for query interception and customization
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to packages/zenstackhq/orm/**/*.test.{ts,tsx} : ORM package tests should include comprehensive client API tests and policy tests

Applied to files:

  • tests/e2e/orm/plugin-infra/ext-query-args.test.ts
  • packages/orm/src/client/crud/validator/index.ts
  • packages/orm/src/client/contract.ts
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to packages/zenstackhq/orm/**/*.{ts,tsx} : Implement plugin hooks at ORM, Kysely, and entity mutation levels for query interception and customization

Applied to files:

  • tests/e2e/orm/plugin-infra/ext-query-args.test.ts
  • packages/orm/src/client/crud/validator/index.ts
  • packages/orm/src/client/contract.ts
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to tests/e2e/**/*.{ts,tsx} : E2E tests should validate real-world schema compatibility with established projects

Applied to files:

  • tests/e2e/orm/plugin-infra/ext-query-args.test.ts
  • packages/orm/src/client/contract.ts
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to tests/**/type*.{ts,tsx} : Ensure TypeScript inference and type coverage are validated through type coverage tests

Applied to files:

  • tests/e2e/orm/plugin-infra/ext-query-args.test.ts
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to packages/zenstackhq/orm/**/*.{ts,tsx} : Use Kysely as the query builder interface for low-level database queries, avoiding raw SQL when possible

Applied to files:

  • tests/e2e/orm/plugin-infra/ext-query-args.test.ts
  • packages/orm/src/client/crud/validator/index.ts
  • packages/orm/src/client/contract.ts
📚 Learning: 2025-11-26T01:55:04.540Z
Learnt from: CR
Repo: zenstackhq/zenstack-v3 PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-26T01:55:04.540Z
Learning: Applies to **/*.zmodel : ZModel schema files should define database structure and policies that compile to TypeScript via `zenstack generate`

Applied to files:

  • tests/e2e/orm/plugin-infra/ext-query-args.test.ts
  • packages/orm/src/client/crud/validator/index.ts
  • packages/orm/src/client/contract.ts
📚 Learning: 2025-12-30T15:07:06.254Z
Learnt from: mwillbanks
Repo: zenstackhq/zenstack-v3 PR: 550
File: packages/orm/src/client/crud/operations/base.ts:158-159
Timestamp: 2025-12-30T15:07:06.254Z
Learning: Do not use ts-expect-error in production code within the zenstackhq/zenstack-v3 repo (e.g., packages/*). Use explicit type annotations, targeted type assertions, or refactor to resolve the type error. ts-expect-error may be acceptable only in test files for stubbing or temporary silencing. Ensure production code is type-safe and maintainable.

Applied to files:

  • packages/orm/src/client/crud/validator/index.ts
  • packages/orm/src/client/contract.ts
🧬 Code graph analysis (1)
packages/orm/src/client/crud/validator/index.ts (1)
packages/orm/src/client/crud/operations/base.ts (2)
  • CoreCrudOperations (60-77)
  • CoreCrudOperations (82-82)
⏰ 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). (3)
  • GitHub Check: build-test (22.x, postgresql)
  • GitHub Check: build-test (22.x, sqlite)
  • GitHub Check: claude-review
🔇 Additional comments (12)
tests/e2e/orm/plugin-infra/ext-query-args.test.ts (6)

1-6: LGTM!

Imports are well-organized, bringing in the necessary types (CoreReadOperations, CoreWriteOperations, definePlugin, ClientContract) from the ORM package along with test utilities and Zod for schema validation.


27-34: LGTM!

Proper test lifecycle management with database cleanup before each test and safe disconnection after.


36-163: Comprehensive test coverage for extending all operations.

This test thoroughly validates the plugin-extended query args feature:

  • Optional cache and ttl parameters
  • TTL propagation through onQuery hook
  • Validation errors for invalid values and unrecognized keys
  • Coverage across all CRUD operation types (read, create, update, delete)
  • Transactional and plugin boundary usage ($transaction, $use, $setOptions, $setAuth)

This aligns well with the coding guidelines for E2E tests validating real-world schema compatibility. Based on learnings, the comprehensive client API coverage is appropriate for ORM tests.


165-193: LGTM!

Good test for operation-specific extensions. Validates that only read operations accept the cache extension while write operations correctly reject it. The runtime check using (CoreReadOperations as readonly string[]).includes(operation) properly aligns compile-time types with runtime behavior.


195-247: LGTM!

Excellent test for per-operation schema differentiation. The test correctly validates:

  • Read operations accept ttl but reject bust
  • Write operations accept bust but reject ttl
  • Proper propagation of extended args through the onQuery hook

This demonstrates the flexibility of the plugin system for operation-aware extensions.


249-272: LGTM!

Critical test for validating schema isolation between clients. The double verification (lines 264-266 and 268-271) ensures that plugin extensions don't leak across client instances and that internal caching doesn't cause cross-contamination.

packages/orm/src/client/crud/validator/index.ts (3)

59-63: Solid: per-operation schema caching and validation flow.

Per-client schemaCache plus operation-aware cache keys/validation avoids cross-op schema collisions and keeps validation deterministic.

Also applies to: 192-203, 331-363


365-415: Nice: operation-aware find schema with plugin extensions.

The operation-derived findUnique/findFirst semantics combined with plugin arg merging keeps validation aligned with the new per-op ext args model.


1148-1174: Good: consistent plugin-arg merging across CRUD schemas.

The per-operation mergePluginArgsSchema integration is applied uniformly across create/update/delete/count/aggregate/groupBy, which should reduce drift in validation behavior.

Also applies to: 1433-1482, 1594-1670, 1705-1771

packages/orm/src/client/contract.ts (3)

51-58: Nice: ExtractExtQueryArgs adds a clear per-operation fallback.

This keeps ext args scoped to each operation while still honoring a shared all shape.


74-218: Good: ExtQueryArgs is preserved across client APIs and plugin installs.

The updated signatures for $setAuth, $setOptions, $setInputValidation, $transaction, $use, and $unuse keep extended args flowing through client instances.


274-863: Good: per-operation ext args are threaded through model operations.

The operation signatures consistently intersect core args with ExtractExtQueryArgs, matching the new per-op ext args contract.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@ymc9 ymc9 merged commit 233b47d into dev Jan 16, 2026
6 checks passed
@ymc9 ymc9 deleted the feat/plugin-options branch January 16, 2026 12:52
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