Skip to content

Comments

feat: creating zod schemas for zmodel constructs and ORM query input validations#2389

Merged
ymc9 merged 13 commits intodevfrom
feat/zod
Feb 20, 2026
Merged

feat: creating zod schemas for zmodel constructs and ORM query input validations#2389
ymc9 merged 13 commits intodevfrom
feat/zod

Conversation

@ymc9
Copy link
Member

@ymc9 ymc9 commented Feb 20, 2026

Summary by CodeRabbit

  • New Features

    • Expose a Zod-based schema factory via a new client accessor for generating and validating query inputs
    • Add SchemaAccessor API for programmatic schema querying and validation
    • Centralized input validation for CRUD operations and procedure inputs
  • Bug Fixes

    • Stronger type constraints and per-operation typings for improved type safety
  • Tests

    • Large unit and end-to-end test suites covering Zod factory, validator, SchemaAccessor, and ORM integrations

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 20, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds Zod-based schema factories and validators, exposes a client-level $zod accessor, threads ExtQueryArgs typing through CRUD APIs, introduces a SchemaAccessor proxy and InvalidSchemaError, relocates and implements InputValidator, updates package manifests/scripts, and adds extensive unit and e2e tests and vitest configs.

Changes

Cohort / File(s) Summary
Package Manifests & Scripts
packages/orm/package.json, packages/zod/package.json, packages/schema/package.json
Updated workspace dependencies and scripts: added zod workspace deps, testing/watch/pack scripts, vitest devDeps and keywords, and minor manifest edits.
ORM — Client surface & exports
packages/orm/src/client/client-impl.ts, packages/orm/src/client/index.ts, packages/orm/src/client/constants.ts
Added get $zod() getter on client impl and on ClientContract (via contract changes), re-exported zod index from client entry, and removed exported CONTEXT_COMMENT_PREFIX.
ORM — Contract & CRUD typings
packages/orm/src/client/contract.ts, packages/orm/src/client/crud-types.ts
Reworked ExtQueryArgs typing to extend ExtQueryArgsBase, removed/changed ExtractExtQueryArgs usage across many operation and arg types, and propagated ExtQueryArgs generics through operation signatures.
ORM — Zod schema factory (client)
packages/orm/src/client/zod/factory.ts, packages/orm/src/client/zod/index.ts
Added ZodSchemaFactory and factory creators (createQuerySchemaFactory, createSchemaFactory) with plugin support and comprehensive operation schemas; re-exported factory from zod index.
ORM — Input validator relocation
packages/orm/src/client/crud/validator/index.ts, packages/orm/src/client/crud/validator/validator.ts, packages/orm/src/client/crud/operations/find.ts
Moved InputValidator implementation to validator.ts and re-exported via index; added many validateXxxArgs methods including validateProcedureInput; narrowed operation type when calling validateFindArgs.
Zod package — factory, errors, utils, exports
packages/zod/src/factory.ts, packages/zod/src/error.ts, packages/zod/src/index.ts, packages/zod/src/utils.ts, packages/zod/src/types.ts
Introduced a SchemaFactory implementation and ZodSchemaError, reorganized exports (createModelSchemaFactory/ZodUtils), removed older type helpers, and updated utils/error handling and imports.
Schema package — accessor & types
packages/schema/src/accessor.ts, packages/schema/src/index.ts, packages/schema/src/schema.ts
Added SchemaAccessor proxy factory and InvalidSchemaError, exported accessor symbols, and introduced UniqueFieldsInfo with related type refinements.
Tests, fixtures & generated schemas
packages/schema/test/*, packages/zod/test/*, tests/e2e/orm/client-api/*, tests/e2e/vitest.config.ts
Added extensive unit and e2e tests, generated schema fixtures (.zmodel and TS), type-level test files, enabled test typechecking, and removed one trivial createMany test.
Vitest configs & scripts helper
packages/schema/vitest.config.ts, packages/zod/vitest.config.ts, scripts/test-generate.ts
Added vitest config files that merge a shared base and adjusted test-generate invocation to disable model/input generation flags.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

🐇 I nibble types and stitch schemas tight,
I hop through contracts in the soft moonlight,
I guard each input with a Zod-made key,
I braid ExtQueryArgs through every tree,
A little rabbit cheering tests to flight.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.14% 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 clearly and specifically describes the main change: introducing Zod schema generation for both zmodel constructs and ORM query input validation, which is the primary focus of this comprehensive pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/zod

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
Contributor

claude bot commented Feb 20, 2026

Claude finished @ymc9's task —— View job


PR Review: Zod Schemas for ZModel Constructs and ORM Query Input Validations

This is a substantial and well-architected PR that introduces comprehensive Zod integration for ZModel validation. Here's my detailed review:

Code Quality & Architecture

Excellent Design Patterns:

  • Clean separation of concerns: The new @zenstackhq/zod package provides schema creation, while ORM integration handles validation
  • Strong factory pattern: SchemaFactory in packages/zod/src/factory.ts:30-32 provides a clean, type-safe API for generating Zod schemas
  • Proper caching strategy: The ZodSchemaFactory uses method-level @cache() decorators for performance optimization
  • Type-safe implementation: Excellent use of conditional types and mapped types for schema generation

Well-Structured Code Organization:

  • Clear separation between model schema generation (packages/zod/src/factory.ts) and query validation (packages/orm/src/client/zod/zod-schema-factory.ts)
  • The new SchemaAccessor class (packages/schema/src/accessor.ts) provides a clean abstraction for schema navigation
  • Input validation is properly centralized in packages/orm/src/client/crud/validator/validator.ts

Implementation Strengths

Comprehensive Type Safety:

  • The factory methods return properly typed Zod schemas that match ZenStack's CRUD operation signatures
  • Complex field mapping in packages/zod/src/factory.ts:84-103 handles all ZenStack types correctly (primitives, enums, typedefs, relations)
  • Proper handling of nullable and array fields with cardinality helpers

Robust Validation Architecture:

  • The recursive schema generation for relations (packages/zod/src/factory.ts:53-56) properly handles circular references with z.lazy()
  • Custom validation integration (addCustomValidation) preserves existing ZenStack attribute-based validation
  • Proper handling of JSON types including typed JSON with nested validation

Performance Considerations:

  • Extensive use of caching at multiple levels (schema factory cache, method-level caching)
  • Lazy evaluation for recursive schemas prevents infinite loops
  • Efficient schema reuse across operations

Feature Completeness

CRUD Operation Coverage:

  • All major CRUD operations have corresponding schema factories (makeFindManySchema, makeCreateSchema, etc.)
  • Proper integration with existing ZenStack features like slicing, filtering, and plugin query args
  • Support for complex operations like createManyAndReturn, updateManyAndReturn

Advanced Features:

  • Filter slicing support: Integration with ZenStack's model and operation slicing (packages/orm/src/client/zod/zod-schema-factory.ts:1928-2088)
  • Plugin query args: Proper merging of plugin-extended query arguments (packages/orm/src/client/zod/zod-schema-factory.ts:1802-1878)
  • Procedure validation: Support for custom procedure input validation

Testing

Comprehensive Test Coverage:

  • Excellent test suite in tests/e2e/orm/client-api/zod.test.ts with 1414 lines covering all major scenarios
  • Tests cover both positive and negative validation cases
  • Good coverage of edge cases like slicing, plugin integration, and type inference
  • Tests verify both runtime validation and TypeScript type safety

⚠️ Minor Areas for Improvement

Code Organization:

  • The main schema factory file is quite large (2099 lines). Consider splitting into focused modules:
    • Core schema generation
    • Filter/where clause handling
    • Relation handling
    • Plugin integration

Documentation:

  • Some complex type mappings could benefit from additional inline documentation, especially around the recursive relation handling
  • The SchemaAccessor class methods could use more detailed JSDoc comments

Performance Considerations:

  • The cache implementation could potentially benefit from cache size limits or TTL to prevent unbounded memory growth in long-running applications
  • Consider lazy initialization of expensive schema computations

Security & Best Practices

Input Validation:

  • Proper validation of all user inputs before database operations
  • No obvious security vulnerabilities or injection possibilities
  • Follows ZenStack's existing security patterns

Error Handling:

  • Good error handling with descriptive messages (packages/orm/src/client/crud/validator/validator.ts:275-286)
  • Proper error propagation and context preservation

🎯 Specific Technical Observations

  1. Type System Integration (packages/orm/src/client/contract.ts:208): The $zod property integration is clean and follows ZenStack's existing patterns

  2. Schema Mapping Logic (packages/zod/src/factory.ts:84-103): The pattern matching for type resolution is comprehensive and handles all edge cases

  3. Recursive Validation (packages/orm/src/client/zod/zod-schema-factory.ts:53-56): Proper use of z.lazy() prevents circular reference issues

  4. Filter Kind Handling (packages/orm/src/client/zod/zod-schema-factory.ts:2018-2034): The filter slicing implementation correctly respects security boundaries

Overall Assessment

This is an excellent implementation that significantly enhances ZenStack's type safety and validation capabilities. The code is well-architected, thoroughly tested, and follows established patterns. The integration is seamless and maintains backward compatibility.

Recommendation: ✅ APPROVE

The implementation is production-ready and will provide significant value to ZenStack users by enabling compile-time and runtime validation of query inputs.


@claude
Copy link
Contributor

claude bot commented Feb 20, 2026

Claude finished @ymc9's task —— View job


PR Review: Zod Schemas for ZModel Constructs and ORM Query Input Validations

This is a substantial and well-architected PR that introduces comprehensive Zod integration for ZModel validation. Here's my detailed review:

Code Quality & Architecture

Excellent Design Patterns:

  • Clean separation of concerns: The new @zenstackhq/zod package provides schema creation, while ORM integration handles validation
  • Strong factory pattern: SchemaFactory in packages/zod/src/factory.ts:30-32 provides a clean, type-safe API for generating Zod schemas
  • Proper caching strategy: The ZodSchemaFactory uses method-level @cache() decorators for performance optimization
  • Type-safe implementation: Excellent use of conditional types and mapped types for schema generation

Well-Structured Code Organization:

  • Clear separation between model schema generation (packages/zod/src/factory.ts) and query validation (packages/orm/src/client/zod/factory.ts)
  • The new SchemaAccessor class (packages/schema/src/accessor.ts) provides a clean abstraction for schema navigation
  • Input validation is properly centralized in packages/orm/src/client/crud/validator/validator.ts

Implementation Strengths

Comprehensive Type Safety:

  • The factory methods return properly typed Zod schemas that match ZenStack's CRUD operation signatures
  • Complex field mapping in packages/zod/src/factory.ts:84-103 handles all ZenStack types correctly (primitives, enums, typedefs, relations)
  • Proper handling of nullable and array fields with cardinality helpers

Robust Validation Architecture:

  • The recursive schema generation for relations (packages/zod/src/factory.ts:53-56) properly handles circular references with z.lazy()
  • Custom validation integration (addCustomValidation) preserves existing ZenStack attribute-based validation
  • Proper handling of JSON types including typed JSON with nested validation

Performance Considerations:

  • Extensive use of caching at multiple levels (schema factory cache, method-level caching)
  • Lazy evaluation for recursive schemas prevents infinite loops
  • Efficient schema reuse across operations

Feature Completeness

CRUD Operation Coverage:

  • All major CRUD operations have corresponding schema factories (makeFindManySchema, makeCreateSchema, etc.)
  • Proper integration with existing ZenStack features like slicing, filtering, and plugin query args
  • Support for complex operations like createManyAndReturn, updateManyAndReturn

Advanced Features:

  • Filter slicing support: Integration with ZenStack's model and operation slicing (packages/orm/src/client/zod/factory.ts:1928-2088)
  • Plugin query args: Proper merging of plugin-extended query arguments (packages/orm/src/client/zod/factory.ts:1802-1878)
  • Procedure validation: Support for custom procedure input validation

Testing

Comprehensive Test Coverage:

  • Excellent test suite in tests/e2e/orm/client-api/zod.test.ts with 1400+ lines covering all major scenarios
  • Tests cover both positive and negative validation cases
  • Good coverage of edge cases like slicing, plugin integration, and type inference
  • Tests verify both runtime validation and TypeScript type safety

⚠️ Minor Areas for Improvement

Code Organization:

  • The main schema factory file is quite large (2099 lines). Consider splitting into focused modules:
    • Core schema generation
    • Filter/where clause handling
    • Relation handling
    • Plugin integration

Documentation:

  • Some complex type mappings could benefit from additional inline documentation, especially around the recursive relation handling
  • The SchemaAccessor class methods could use more detailed JSDoc comments

Performance Considerations:

  • The cache implementation could potentially benefit from cache size limits or TTL to prevent unbounded memory growth in long-running applications
  • Consider lazy initialization of expensive schema computations

Security & Best Practices

Input Validation:

  • Proper validation of all user inputs before database operations
  • No obvious security vulnerabilities or injection possibilities
  • Follows ZenStack's existing security patterns

Error Handling:

  • Good error handling with descriptive messages (packages/orm/src/client/crud/validator/validator.ts:275-286)
  • Proper error propagation and context preservation

🎯 Specific Technical Observations

  1. Type System Integration (packages/orm/src/client/contract.ts:208): The $zod property integration is clean and follows ZenStack's existing patterns

  2. Schema Mapping Logic (packages/zod/src/factory.ts:84-103): The pattern matching for type resolution is comprehensive and handles all edge cases

  3. Recursive Validation (packages/orm/src/client/zod/factory.ts:53-56): Proper use of z.lazy() prevents circular reference issues

  4. Filter Kind Handling (packages/orm/src/client/zod/factory.ts:2018-2034): The filter slicing implementation correctly respects security boundaries

Overall Assessment

This is an excellent implementation that significantly enhances ZenStack's type safety and validation capabilities. The code is well-architected, thoroughly tested, and follows established patterns. The integration is seamless and maintains backward compatibility.

Recommendation: ✅ APPROVE

The implementation is production-ready and will provide significant value to ZenStack users by enabling compile-time and runtime validation of query inputs.


Copy link
Contributor

@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: 11

🧹 Nitpick comments (9)
packages/zod/src/error.ts (1)

4-7: Missing this.name assignment — error.name will read 'Error' instead of 'ZodSchemaError'.

Without explicitly setting this.name, the inherited Error.prototype.name value ('Error') is used, which makes the class indistinguishable from a plain Error in logs and catch-block error.name checks.

🛡️ Proposed fix
 export class ZodSchemaError extends Error {
     constructor(message: string) {
         super(message);
+        this.name = 'ZodSchemaError';
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/zod/src/error.ts` around lines 4 - 7, The ZodSchemaError constructor
doesn't set this.name so caught errors show as generic "Error"; update the
ZodSchemaError constructor to assign this.name = "ZodSchemaError" (and for
proper subclassing across transpiled targets also call
Object.setPrototypeOf(this, ZodSchemaError.prototype)) so instances report the
correct name in logs and error checks.
packages/orm/src/client/constants.ts (1)

1-4: Dead commented-out code — remove or add a TODO if restoration is planned.

Leaving the entire CONTEXT_COMMENT_PREFIX declaration as a comment block adds noise with no actionable context. If it's permanently removed, delete the lines; if it's temporarily disabled, add a // TODO: re-enable when ... note.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/orm/src/client/constants.ts` around lines 1 - 4, Remove the dead
commented-out declaration of CONTEXT_COMMENT_PREFIX from the file (or, if you
intend to restore it later, replace the block comment with a single-line comment
containing a TODO explaining when/why it will be re-enabled, e.g., "// TODO:
re-enable CONTEXT_COMMENT_PREFIX when query annotation with context is needed");
ensure the symbol name CONTEXT_COMMENT_PREFIX is referenced in the comment/TODO
if kept so future readers know what's being preserved.
scripts/test-generate.ts (1)

24-29: Hardcoded flags override any caller-supplied equivalents.

--generate-models=false --generate-input=false is appended after options.join(' '). For last-wins CLI parsers (yargs, commander), a caller passing --generate-models=true via options will be silently overridden. If this is intentional, a brief comment explaining why model/input generation is always suppressed here would help future maintainers.

✏️ Suggested clarification
     execSync(
-        `${RUNTIME} ${cliPath} generate --schema ${schemaPath} ${options.join(' ')} --generate-models=false --generate-input=false`,
+        // --generate-models=false --generate-input=false are intentionally forced last so
+        // test-generate only produces Zod schema output, never model or input files.
+        `${RUNTIME} ${cliPath} generate --schema ${schemaPath} ${options.join(' ')} --generate-models=false --generate-input=false`,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/test-generate.ts` around lines 24 - 29, The execSync call currently
appends hardcoded flags `--generate-models=false --generate-input=false` after
`options.join(' ')`, which will override any equivalent flags passed in
`options`; update the invocation so it does not silently override
caller-supplied flags: either remove the hardcoded flags from the command and
rely on callers to provide them, or conditionally append them only when
`options` does not already include
`--generate-models`/`--no-generate-models`/`--generate-input`/`--no-generate-input`
(check the `options` array before building the command); use the existing
symbols `execSync`, `RUNTIME`, `cliPath`, `schemaPath`, and `options` to locate
and change the code, and if you intentionally must force these values add a
brief comment above the `execSync` call explaining that model/input generation
must always be suppressed.
packages/orm/src/client/zod/zod-schema-factory.ts (3)

980-980: Inconsistent parameter naming: WithAggregation uses PascalCase.

The parameter WithAggregation should be withAggregation (camelCase) to match the sibling parameter withRelation and project conventions.

Proposed fix
-    private makeOrderBySchema(model: string, withRelation: boolean, WithAggregation: boolean) {
+    private makeOrderBySchema(model: string, withRelation: boolean, withAggregation: boolean) {

And update all usages of WithAggregation within the method body to withAggregation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/orm/src/client/zod/zod-schema-factory.ts` at line 980, Rename the
PascalCase parameter WithAggregation to camelCase withAggregation in the method
signature of makeOrderBySchema and update every usage inside that method body to
reference withAggregation (ensure any internal conditionals, returns, or helper
calls that currently use WithAggregation are changed to withAggregation to
preserve behavior and match project naming conventions).

1471-1487: z.object() (non-strict) used for numeric/array update operators.

Lines 1475 and 1494 use z.object(...) instead of z.strictObject(...). While the .refine() checks enforce Object.keys(v).length === 1, an unknown key alongside a known one (e.g., { set: 1, typo: 2 }) would also have length > 1 and be rejected — so behavior is correct. However, the error message would be about mutual exclusivity rather than "unrecognized key," which may confuse users. Consider using z.strictObject() for a clearer error.

Also applies to: 1489-1500

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/orm/src/client/zod/zod-schema-factory.ts` around lines 1471 - 1487,
Replace the non-strict Zod objects used for update-operator unions with strict
objects so unexpected keys surface with a clearer "unrecognized key" error;
specifically, in the branch that builds the numeric update union (the z.union
that adds the object with keys set/increment/decrement/multiply/divide) and the
corresponding array-update branch, swap z.object(...) for z.strictObject(...),
keeping the same optional/nullability wrappers and the existing refine that
enforces a single operator key.

124-142: Unused debug methods with @ts-ignore and console.log.

Same concern as in accessor.ts: getCache, setCache, and printCacheStats are suppressed with @ts-ignore and appear unused. printCacheStats writes to console.log which could leak internal state if invoked. Consider removing these or, if needed for debugging, guarding behind a debug flag and using @ts-expect-error.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/orm/src/client/zod/zod-schema-factory.ts` around lines 124 - 142,
The three private debug helpers getCache, setCache, and printCacheStats (which
access schemaCache) are unused and suppress TypeScript errors with `@ts-ignore`
and use console.log; either remove them entirely or convert them to a guarded
debug feature: delete the unused methods if not needed, or wrap their bodies
behind a ZOD_SCHEMA_DEBUG (or similar) flag, replace console.log with the
project's logger, and drop `@ts-ignore` in favor of correct typings (or
`@ts-expect-error` if an unavoidable typing gap remains). Update references to
schemaCache access only via approved methods and ensure no leftover `@ts-ignore`
comments remain.
packages/schema/src/accessor.ts (2)

82-86: InvalidSchemaError does not set name.

Without setting this.name, the error's name property defaults to "Error" rather than "InvalidSchemaError", which makes debugging and programmatic error handling harder.

Proposed fix
 export class InvalidSchemaError extends Error {
     constructor(message: string) {
         super(message);
+        this.name = 'InvalidSchemaError';
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/schema/src/accessor.ts` around lines 82 - 86, The InvalidSchemaError
class currently doesn't set this.name, so caught errors report as "Error";
update the constructor of InvalidSchemaError to set this.name =
"InvalidSchemaError" and restore the proper prototype chain (e.g., via
Object.setPrototypeOf(this, InvalidSchemaError.prototype)) and optionally
capture the stack (Error.captureStackTrace(this, InvalidSchemaError)) so the
error's type and stack are correct for debugging and programmatic checks.

124-142: Debug/internal methods suppressed with @ts-ignore.

getCache, setCache, and printCacheStats are all @ts-ignore-suppressed and appear unused by the runtime code. printCacheStats writes to console.log, which is a concern if accidentally invoked in production. Consider removing these or guarding printCacheStats with a debug flag, and replacing @ts-ignore with @ts-expect-error (which will error if the suppression becomes unnecessary).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/schema/src/accessor.ts` around lines 124 - 142, The three
internal/debug methods getCache, setCache, and printCacheStats are suppressed
with `@ts-ignore` and appear unused; remove the unused methods or at minimum
replace the suppression comments with `@ts-expect-error` and prevent console
output in production by guarding printCacheStats behind a debug flag (e.g.,
process.env.DEBUG_SCHEMA or an exported debug boolean) so it only logs when
enabled; update the definitions for getCache, setCache, printCacheStats
accordingly (or delete them) and ensure any `@ts-ignore` near those functions is
changed to `@ts-expect-error` so the build fails if the suppression is no longer
needed.
packages/orm/src/client/crud/validator/validator.ts (1)

186-267: Procedure input validation does not reject extraneous argument keys.

The validateProcedureInput method validates that all required parameters are present and each parameter value matches its Zod schema, but it never checks for unknown/extra keys in the argsPayload object. For example, if a procedure accepts { userId: String } but the caller passes { userId: "x", admin: true }, the extra admin key is silently accepted and forwarded.

This could mask typos in argument names (e.g., userID vs userId) or allow unexpected data to flow through. Consider adding an extraneous-key check after the param loop:

💡 Suggested addition after line 264
+        const knownParams = new Set(params.map((p) => p.name));
+        const extraKeys = Object.keys(obj).filter((k) => !knownParams.has(k));
+        if (extraKeys.length > 0) {
+            throw createInvalidInputError(
+                `Unknown procedure argument(s): ${extraKeys.join(', ')}`,
+                `$procs.${proc}`,
+            );
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/orm/src/client/crud/validator/validator.ts` around lines 186 - 267,
validateProcedureInput currently validates required params and types but doesn't
reject unknown keys in the argsPayload, letting extraneous properties through;
after the loop that iterates params in validateProcedureInput, add a check that
collects Object.keys(obj) and compares against params.map(p => p.name) and if
any keys remain, throw createInvalidInputError with a message like "Unexpected
procedure argument(s): <keys>" and path `$procs.${proc}`; reference the
argsPayload variable (obj), the params array and
this.zodFactory.makeProcedureParamSchema to locate where to insert the check.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/orm/src/client/crud/validator/validator.ts`:
- Around line 29-34: InputValidator is losing the client's Options and
ExtQueryArgs type parameters which narrows zodFactory to generic ClientOptions;
update InputValidator to be generic over Schema, Options, ExtQueryArgs (e.g.,
InputValidator<Schema, Options, ExtQueryArgs>) and use those types when
declaring zodFactory = ZodSchemaFactory<Schema, Options, ExtQueryArgs> and the
constructor ClientContract to preserve the client's precise types (so methods
like makeFindFirstSchema return FindFirstArgs<Schema, Model, Options,
ExtQueryArgs> and client.$zod exposes the narrowed Options/ExtQueryArgs).

In `@packages/orm/src/client/zod/zod-schema-factory.ts`:
- Around line 1722-1735: The refine on schema in zod-schema-factory.ts
incorrectly assumes value.orderBy is an object; update the refinement in the
schema.refine callback (the block that uses bys and AggregateOperators) to
normalize value.orderBy to an array of order objects when it's not already an
array, then iterate each order object to collect its property keys (excluding
AggregateOperators) and ensure every such key is included in bys; keep handling
for value.by (string vs array) as is and return false only if any non-aggregate
order key is missing from bys.

In `@packages/schema/package.json`:
- Line 7: The build script in package.json currently chains "pnpm test:generate"
into the "build" command, creating a dependency on the CLI dist that may not
exist yet; remove "pnpm test:generate" from the "build" script (the "build" npm
script entry) and instead expose "test:generate" as a standalone script (e.g.,
keep the existing "test:generate" npm script) and run it explicitly in CI after
all packages including the CLI are built, or document it as an optional
post-build step to be invoked only after the CLI dist is available.

In `@packages/schema/src/accessor.ts`:
- Around line 172-193: The current typeof guard in getUniqueFields lets null
pass because typeof null === 'object', which leads to uncontrolled errors when
accessing value.type; update the validation to explicitly reject null (e.g.,
check value === null || typeof value !== 'object') and throw InvalidSchemaError
for such cases, and then when branching on the singular unique field guard the
property's presence safely (e.g., verify 'type' in value or value.type !==
undefined before using typeof) so accesses to value.type and Object.keys(value)
are safe; adjust the logic around getUniqueFields, InvalidSchemaError, and the
compound branch that calls _requireModelField to use these stronger null/object
checks.

In `@packages/schema/vitest.config.ts`:
- Around line 1-4: The CI failure is caused because scripts/test-generate.ts
(invoked by the package.json "test:generate" script) expects
packages/cli/dist/index.js (the cliPath variable) to exist; ensure the build
graph requires `@zenstackhq/cli` to be built before packages/schema by either
updating the CI workflow to run the packages/cli build before packages/schema,
or by declaring an explicit build dependency so the package build/task graph
enforces it (e.g., add a turbo/pnpm pipeline dependency or make
packages/schema's build script depend on `@zenstackhq/cli` build) so
scripts/test-generate.ts can find cliPath at runtime.

In `@packages/zod/package.json`:
- Line 39: The package.json has inconsistent versioning: change
"json-stable-stringify" from a careted version to use the catalog alias (e.g.,
"json-stable-stringify": "catalog:json-stable-stringify") and make the
devDependency for "zod" use the same catalog alias as the peerDependency
(replace "^4.1.0" with the same "catalog:zod" entry), then add an entry for
json-stable-stringify to the root pnpm-workspace.yaml catalog so the catalog
reference resolves consistently across peerDependencies and devDependencies.

In `@packages/zod/src/factory.ts`:
- Around line 117-125: applyCardinality currently returns schema.array() early
and thus ignores fieldDef.optional when fieldDef.array is true; update
applyCardinality to handle both flags by returning
schema.array().nullable().optional() (or schema.array().optional().nullable(),
preserving Zod chaining) when both fieldDef.array and fieldDef.optional are
true, keep the existing schema.array() when only array is true and
schema.nullable().optional() when only optional is true; reference the
applyCardinality method, FieldDef.array/optional, and the
schema.array()/schema.nullable().optional() chains when making the change.
- Around line 51-56: The relation branch constructs a z.lazy that already
contains applyCardinality and then wraps that lazy with .optional(), causing
double-optional nesting for optional relations; fix by moving applyCardinality
out of the z.lazy so that you call z.lazy(() =>
this.makeModelSchema(relatedModelName as GetModels<Schema>)) and then apply
this.applyCardinality(...) (and .optional() only when appropriate) to the
resulting lazy schema so applyCardinality is invoked exactly once for
relatedModelName via makeModelSchema and fields[fieldName] gets a single
correctly-cardinalized schema.
- Around line 127-129: The parameter type for makeTypeSchema prevents TypeScript
from inferring the specific Type generic when multiple typedefs exist; change
the signature to accept the generic Type itself (i.e., makeTypeSchema<Type
extends GetTypeDefs<Schema>>(type: Type)) and return
z.ZodObject<GetTypeDefFieldsShape<Schema, Type>, z.core.$strict> so the
parameter is typed as the specific Type rather than the full union; update the
function declaration of makeTypeSchema accordingly so callers like
makeTypeSchema('Address') produce a schema narrowed to the 'Address' fields.

In `@packages/zod/test/factory.test.ts`:
- Around line 63-64: The inline comment above the birthdate assertion is
incorrect: FieldTypeZodMap does include a DateTime key (DateTime:
z.ZodType<Date>) and the test rightly asserts User['birthdate'] is Date | null |
undefined; remove or replace the misleading comment with an accurate one (e.g.,
noting DateTime maps to Date) next to the
expectTypeOf<User['birthdate']>().toEqualTypeOf<Date | null | undefined>() so
the comment matches the implementation and assertion.

In `@tests/e2e/orm/client-api/zod.test.ts`:
- Around line 1397-1402: The test creates a local Prisma test client via
createTestClient(schema) that shadows the outer client and is never
disconnected; update the test so the locally-created client is properly
disconnected after use (e.g., call await client.$disconnect() at the end of the
it block or reuse the outer client instead of creating a new one) so the
connection created by createTestClient is closed; reference the local const
client in this test and ensure its $disconnect() is awaited (or remove the local
creation and use the shared client cleaned up by the existing afterEach).

---

Nitpick comments:
In `@packages/orm/src/client/constants.ts`:
- Around line 1-4: Remove the dead commented-out declaration of
CONTEXT_COMMENT_PREFIX from the file (or, if you intend to restore it later,
replace the block comment with a single-line comment containing a TODO
explaining when/why it will be re-enabled, e.g., "// TODO: re-enable
CONTEXT_COMMENT_PREFIX when query annotation with context is needed"); ensure
the symbol name CONTEXT_COMMENT_PREFIX is referenced in the comment/TODO if kept
so future readers know what's being preserved.

In `@packages/orm/src/client/crud/validator/validator.ts`:
- Around line 186-267: validateProcedureInput currently validates required
params and types but doesn't reject unknown keys in the argsPayload, letting
extraneous properties through; after the loop that iterates params in
validateProcedureInput, add a check that collects Object.keys(obj) and compares
against params.map(p => p.name) and if any keys remain, throw
createInvalidInputError with a message like "Unexpected procedure argument(s):
<keys>" and path `$procs.${proc}`; reference the argsPayload variable (obj), the
params array and this.zodFactory.makeProcedureParamSchema to locate where to
insert the check.

In `@packages/orm/src/client/zod/zod-schema-factory.ts`:
- Line 980: Rename the PascalCase parameter WithAggregation to camelCase
withAggregation in the method signature of makeOrderBySchema and update every
usage inside that method body to reference withAggregation (ensure any internal
conditionals, returns, or helper calls that currently use WithAggregation are
changed to withAggregation to preserve behavior and match project naming
conventions).
- Around line 1471-1487: Replace the non-strict Zod objects used for
update-operator unions with strict objects so unexpected keys surface with a
clearer "unrecognized key" error; specifically, in the branch that builds the
numeric update union (the z.union that adds the object with keys
set/increment/decrement/multiply/divide) and the corresponding array-update
branch, swap z.object(...) for z.strictObject(...), keeping the same
optional/nullability wrappers and the existing refine that enforces a single
operator key.
- Around line 124-142: The three private debug helpers getCache, setCache, and
printCacheStats (which access schemaCache) are unused and suppress TypeScript
errors with `@ts-ignore` and use console.log; either remove them entirely or
convert them to a guarded debug feature: delete the unused methods if not
needed, or wrap their bodies behind a ZOD_SCHEMA_DEBUG (or similar) flag,
replace console.log with the project's logger, and drop `@ts-ignore` in favor of
correct typings (or `@ts-expect-error` if an unavoidable typing gap remains).
Update references to schemaCache access only via approved methods and ensure no
leftover `@ts-ignore` comments remain.

In `@packages/schema/src/accessor.ts`:
- Around line 82-86: The InvalidSchemaError class currently doesn't set
this.name, so caught errors report as "Error"; update the constructor of
InvalidSchemaError to set this.name = "InvalidSchemaError" and restore the
proper prototype chain (e.g., via Object.setPrototypeOf(this,
InvalidSchemaError.prototype)) and optionally capture the stack
(Error.captureStackTrace(this, InvalidSchemaError)) so the error's type and
stack are correct for debugging and programmatic checks.
- Around line 124-142: The three internal/debug methods getCache, setCache, and
printCacheStats are suppressed with `@ts-ignore` and appear unused; remove the
unused methods or at minimum replace the suppression comments with
`@ts-expect-error` and prevent console output in production by guarding
printCacheStats behind a debug flag (e.g., process.env.DEBUG_SCHEMA or an
exported debug boolean) so it only logs when enabled; update the definitions for
getCache, setCache, printCacheStats accordingly (or delete them) and ensure any
`@ts-ignore` near those functions is changed to `@ts-expect-error` so the build
fails if the suppression is no longer needed.

In `@packages/zod/src/error.ts`:
- Around line 4-7: The ZodSchemaError constructor doesn't set this.name so
caught errors show as generic "Error"; update the ZodSchemaError constructor to
assign this.name = "ZodSchemaError" (and for proper subclassing across
transpiled targets also call Object.setPrototypeOf(this,
ZodSchemaError.prototype)) so instances report the correct name in logs and
error checks.

In `@scripts/test-generate.ts`:
- Around line 24-29: The execSync call currently appends hardcoded flags
`--generate-models=false --generate-input=false` after `options.join(' ')`,
which will override any equivalent flags passed in `options`; update the
invocation so it does not silently override caller-supplied flags: either remove
the hardcoded flags from the command and rely on callers to provide them, or
conditionally append them only when `options` does not already include
`--generate-models`/`--no-generate-models`/`--generate-input`/`--no-generate-input`
(check the `options` array before building the command); use the existing
symbols `execSync`, `RUNTIME`, `cliPath`, `schemaPath`, and `options` to locate
and change the code, and if you intentionally must force these values add a
brief comment above the `execSync` call explaining that model/input generation
must always be suppressed.

Copy link
Contributor

@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

🧹 Nitpick comments (1)
packages/orm/src/client/crud/validator/validator.ts (1)

185-186: Consider caching procedure input schemas.

Line 185 notes a TODO to turn procedure input validation into a cached Zod schema. Caching would align with other schema paths and avoid rebuilding per call. If you'd like, I can draft a cached schema helper for this path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/orm/src/client/crud/validator/validator.ts` around lines 185 - 186,
The validateProcedureInput(proc: string, input: unknown) method currently
rebuilds/validates inputs per call; convert this to use a cached Zod schema per
procedure name to avoid repeated schema construction. Implement a small cache
(e.g., Map<string, ZodSchema>) keyed by proc, generate or retrieve the Zod
schema once when validateProcedureInput is first called for that proc, then
reuse it for subsequent validations, and ensure errors are thrown or returned in
the same shape as before; update validateProcedureInput to look up the schema in
the cache, compile it on miss, and validate input using the cached ZodSchema.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/orm/src/client/zod/factory.ts`:
- Around line 1722-1735: The refine validator in schema.refine incorrectly
assumes value.orderBy is an object and calls Object.keys directly, rejecting
valid array forms; update the check to normalize orderBy into an array (e.g.,
const orderBys = Array.isArray(value.orderBy) ? value.orderBy : [value.orderBy])
and then collect field names from each element (e.g., orderBys.flatMap(ob =>
Object.keys(ob))) before filtering against AggregateOperators and comparing
against the bys array (value.by) so groupBy accepts both object and array
orderBy shapes; adjust the logic around value.orderBy, AggregateOperators, bys,
and the final .some(...) accordingly and keep the same error message.
- Around line 1471-1485: The numeric update operator union currently forces
operands to z.number(), rejecting Decimal/bigint/numeric-string types; update
the operator payloads inside the isNumericField branch (the object with keys
set/increment/decrement/multiply/divide) to reuse the field's scalar schema
instead of z.number() — e.g., replace z.number() with the scalar schema produced
by makeScalarSchema for the current fieldDef (respecting existing nullableIf
usage and optional flags) so set/increment/decrement/multiply/divide accept the
same types as the field itself; keep the same single-key refine check and
optional wrapping.

---

Duplicate comments:
In `@packages/orm/src/client/crud/validator/validator.ts`:
- Around line 1-54: InputValidator is currently generic only over Schema which
forces zodFactory to use ClientOptions<Schema> and collapses client-specific
Options/ExtQueryArgs (losing the narrowed types and causing use of any in
validate* return types); fix by making the class generic as
InputValidator<Schema extends SchemaDef, Options, ExtQueryArgs> (or matching the
generics on ClientContract), update the constructor type to
ClientContract<Schema, Options, ExtQueryArgs>, change zodFactory to
ZodSchemaFactory<Schema, ClientOptions<Schema, Options, ExtQueryArgs>> (or the
correct ClientOptions instantiation), and thread Options/ExtQueryArgs through
all validate* signatures (e.g., validateFindArgs, validateCreateArgs, etc.)
replacing the any return-type placeholders with the proper generics so the
methods preserve the client's narrowed types.

In `@packages/schema/package.json`:
- Line 7: The build script "build" in package.json was updated to "tsc --noEmit
&& tsup-node" and no longer chains test:generate; no change required—confirm the
"build" npm script in package.json reflects this new value and proceed with
approval/merge (verify the "build" script string matches "tsc --noEmit &&
tsup-node").

In `@tests/e2e/orm/client-api/zod.test.ts`:
- Around line 1397-1402: The test creates a local client with
createTestClient(schema) that shadows the outer `client` and is never
disconnected, leaking DB connections; update the test (the `it('can be
constructed directly from client'...)` block) to either rename the local
variable (e.g., `localClient`) and call await localClient.disconnect() in a
finally block or explicitly await disconnect on the locally created `client` at
the end of the test to ensure it is closed (referencing createTestClient and the
local `client` variable).

---

Nitpick comments:
In `@packages/orm/src/client/crud/validator/validator.ts`:
- Around line 185-186: The validateProcedureInput(proc: string, input: unknown)
method currently rebuilds/validates inputs per call; convert this to use a
cached Zod schema per procedure name to avoid repeated schema construction.
Implement a small cache (e.g., Map<string, ZodSchema>) keyed by proc, generate
or retrieve the Zod schema once when validateProcedureInput is first called for
that proc, then reuse it for subsequent validations, and ensure errors are
thrown or returned in the same shape as before; update validateProcedureInput to
look up the schema in the cache, compile it on miss, and validate input using
the cached ZodSchema.

@claude
Copy link
Contributor

claude bot commented Feb 20, 2026

Claude finished @ymc9's task —— View job


PR Review: Zod Schemas for ZModel Constructs and ORM Query Input Validations

Todo List:

  • Analyze core architecture and design patterns
  • Review implementation quality and potential bugs
  • Assess performance considerations
  • Check security aspects
  • Evaluate test coverage
  • Provide comprehensive feedback and recommendations

Overview

This is a substantial and well-architected PR that introduces comprehensive Zod integration for ZModel validation. The implementation adds a new @zenstackhq/zod package and integrates it seamlessly into the ORM for runtime validation of query inputs.

✅ Architecture & Design Excellence

Outstanding Design Patterns:

  • Clean separation of concerns: The new @zenstackhq/zod package handles schema generation, while ORM integration manages validation
  • Factory pattern implementation: SchemaFactory provides a clean, type-safe API for Zod schema generation
  • Proper caching strategy: Extensive use of @cache() decorators for performance optimization
  • Type-safe implementation: Excellent use of conditional types and mapped types

Well-Structured Organization:

  • Clear separation between model schema generation (packages/zod/src/factory.ts) and query validation (packages/orm/src/client/zod/factory.ts)
  • The new SchemaAccessor class provides a clean abstraction for schema navigation
  • Input validation centralized in packages/orm/src/client/crud/validator/validator.ts

✅ Implementation Strengths

Comprehensive Type Safety:

  • Factory methods return properly typed Zod schemas matching ZenStack's CRUD operation signatures
  • Complex field mapping handles all ZenStack types correctly (primitives, enums, typedefs, relations)
  • Proper handling of nullable and array fields with cardinality helpers

Robust Validation Architecture:

  • Recursive schema generation for relations properly handles circular references with z.lazy()
  • Custom validation integration preserves existing ZenStack attribute-based validation
  • Proper JSON type handling including typed JSON with nested validation

Performance Considerations:

  • Extensive caching at multiple levels prevents schema regeneration
  • Lazy evaluation for recursive schemas prevents infinite loops
  • Efficient schema reuse across operations

✅ Test Coverage

Comprehensive Testing:

  • Excellent test suite with 1400+ lines covering all major scenarios
  • Tests cover both positive and negative validation cases
  • Good coverage of edge cases like slicing, plugin integration, and type inference
  • Tests verify both runtime validation and TypeScript type safety

⚠️ Issues Identified

🔴 Critical Issues

  1. Type Parameter Loss in InputValidator (packages/orm/src/client/crud/validator/validator.ts:28-32)

    • InputValidator only preserves Schema generic, losing Options and ExtQueryArgs
    • This narrows zodFactory type to generic ClientOptions<Schema> instead of client-specific types
    • Results in loss of type safety for methods like makeFindFirstSchema
  2. GroupBy OrderBy Array Validation Bug (packages/orm/src/client/zod/factory.ts:1723-1737)

    • The code incorrectly assumes value.orderBy is always an object
    • When orderBy is an array, Object.keys() returns indices, causing valid inputs to be rejected
    • Need to normalize to array before validation
  3. Numeric Update Operations Type Restriction (packages/orm/src/client/zod/factory.ts:1471-1487)

    • TODO comment indicates awareness of issue: forces z.number() for Decimal/BigInt fields
    • Rejects valid Decimal instances and numeric strings that the field schema accepts
    • Should reuse the field's scalar schema for consistent type handling

🟡 Minor Issues

  1. Error Class Names Not Set

    • ZodSchemaError and InvalidSchemaError don't set this.name
    • Makes debugging harder when errors show as generic "Error"
  2. Array + Optional Field Handling (packages/zod/src/factory.ts:117-125)

    • applyCardinality returns early for arrays, ignoring optional flag
    • Optional array fields don't get proper nullable().optional() treatment
  3. Resource Leak in Tests (tests/e2e/orm/client-api/zod.test.ts:1401-1404)

    • Local test client created but never disconnected, leaking connections
  4. Build Dependency Issue (packages/schema/package.json:7)

    • test:generate script requires CLI to be built first, causing pipeline failures

🎯 Security Assessment

✅ No Security Concerns Identified:

  • Proper input validation prevents injection attacks
  • No obvious security vulnerabilities
  • Follows established ZenStack security patterns
  • Good error handling with safe error messages

🚀 Performance Analysis

✅ Well Optimized:

  • Extensive caching prevents redundant schema generation
  • Lazy evaluation for recursive schemas
  • Efficient method-level caching with decorators
  • Consider adding cache size limits for long-running applications

📊 Code Quality

✅ High Quality Implementation:

  • Follows project conventions and patterns
  • Good error handling and validation
  • Comprehensive type system integration
  • Clean, readable code with good separation of concerns

🎯 Recommendations

Immediate Actions Required:

  1. Fix InputValidator generics to preserve Options/ExtQueryArgs types
  2. Fix groupBy orderBy validation to handle array inputs correctly
  3. Update numeric operators to accept field-appropriate types (Decimal/BigInt)
  4. Fix resource leak in test by adding client disconnect

Code Quality Improvements:

  1. Set error class names for better debugging
  2. Fix optional array handling in applyCardinality
  3. Remove build dependency on CLI from schema package

✅ Overall Assessment

This is an excellent implementation that significantly enhances ZenStack's validation capabilities. Despite the identified issues, the code is well-architected, thoroughly tested, and follows established patterns. The integration is seamless and maintains backward compatibility.

The issues identified are fixable and don't detract from the overall quality of the work. This PR provides substantial value by enabling both compile-time and runtime validation of query inputs.

Final Recommendation: ✅ APPROVE with requested fixes

The implementation is production-ready after addressing the identified issues.


Copy link
Contributor

@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.

🧹 Nitpick comments (7)
packages/schema/src/accessor.ts (2)

216-232: Consider adding a set trap to prevent silent misuse.

The proxy has no set trap, so assignments like accessor.models = {} would silently succeed on the internal { schema } target object rather than mutating the original schema. If this is intended as a read-only accessor, an explicit set trap (returning false or throwing) would prevent confusing bugs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/schema/src/accessor.ts` around lines 216 - 232, The Proxy returned
by SchemaAccessor currently only defines a get trap, allowing assignments (e.g.
accessor.models = {}) to silently mutate the internal { schema } target; add a
set trap to the Proxy handler in SchemaAccessor that prevents mutation by either
throwing a TypeError or returning false (so strict mode will throw) when any
property is assigned, and ensure it references the same symbols (handler in
SchemaAccessor, the target containing schema, and accessors) so attempts to set
properties on the accessor are blocked rather than applied to the internal
target.

82-86: Set this.name for proper error identification.

Error subclasses default name to "Error". Without setting it, error.name, toString(), and stack traces won't reflect the actual error class.

Proposed fix
 export class InvalidSchemaError extends Error {
     constructor(message: string) {
         super(message);
+        this.name = 'InvalidSchemaError';
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/schema/src/accessor.ts` around lines 82 - 86, The InvalidSchemaError
class doesn't set this.name or restore the prototype chain, so error.name and
stack traces show "Error" and instanceof may fail; in the InvalidSchemaError
constructor set this.name = "InvalidSchemaError" and call
Object.setPrototypeOf(this, InvalidSchemaError.prototype) (or equivalent) after
super(message) to preserve the custom error identity and proper instanceof
behavior.
packages/orm/src/client/zod/factory.ts (3)

980-980: Inconsistent parameter casing: WithAggregation should be withAggregation.

The parameter WithAggregation on line 980 uses PascalCase while all other parameters in this file use camelCase (e.g., withRelation, withAggregations, withoutRelationFields).

Suggested fix
-    private makeOrderBySchema(model: string, withRelation: boolean, WithAggregation: boolean) {
+    private makeOrderBySchema(model: string, withRelation: boolean, withAggregation: boolean) {

And update all references within the method (lines 1018, 1021).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/orm/src/client/zod/factory.ts` at line 980, The parameter name in
makeOrderBySchema is using PascalCase (`WithAggregation`); rename it to
camelCase `withAggregation` and update every reference inside the
makeOrderBySchema function (currently referencing `WithAggregation`) to use
`withAggregation` instead so casing is consistent with other parameters like
`withRelation` and `withAggregations`; ensure both the parameter declaration and
all internal usages (including any conditional checks or passed-through values)
are updated to the new identifier.

124-142: Debug infrastructure uses @ts-ignore and includes console.log.

getCache, setCache, and printCacheStats all suppress TypeScript errors with @ts-ignore. printCacheStats writes to console, which could leak into production. If these are only for the @cache() decorator's internal use, consider making the decorator access the map directly, or at minimum ensure printCacheStats is behind an environment check.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/orm/src/client/zod/factory.ts` around lines 124 - 142, The three
helper methods (getCache, setCache, printCacheStats) suppress TypeScript errors
with `@ts-ignore` and print to console; remove the `@ts-ignore` by giving the
underlying schemaCache a proper type (e.g., declare schemaCache: Map<string,
ZodType> or Record<string, ZodType>) and update getCache(cacheKey: string):
ZodType | undefined and setCache(cacheKey: string, schema: ZodType): this|void
signatures accordingly so TypeScript errors are resolved; then either (a) let
the `@cache`() decorator access the typed schemaCache directly (export or expose a
protected accessor) or (b) keep the methods but gate printCacheStats(detailed =
false) behind a runtime flag (e.g., process.env.NODE_ENV === 'development' or a
DEBUG flag) so console.log is not executed in production. Ensure references to
getCache, setCache, and printCacheStats in the decorator are updated to the new
typed API.

1392-1404: Fragile cast chain: makeUpdateManySchema result → ZodObject for .extend().

makeUpdateManySchema returns ZodType (after casting), and line 1397 casts it back to ZodObject to call .extend(). This works because mergePluginArgsSchema returns .strict() (a ZodObject), but if that implementation changes or a .refine() is added upstream, the cast will silently produce a runtime error when .extend() is called on a non-object schema.

Consider having mergePluginArgsSchema return ZodObject explicitly, or restructure makeUpdateManyAndReturnSchema to build its own base schema rather than extending the updateMany schema.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/orm/src/client/zod/factory.ts` around lines 1392 - 1404, The current
implementation of makeUpdateManyAndReturnSchema casts the result of
makeUpdateManySchema to ZodObject to call .extend(), which is brittle if
makeUpdateManySchema/mergePluginArgsSchema ever return a non-object ZodType;
update the code so you do not blindly cast: either change mergePluginArgsSchema
(the upstream producer used by makeUpdateManySchema) to have a return type of
ZodObject explicitly, or in makeUpdateManyAndReturnSchema validate the returned
ZodType from makeUpdateManySchema is a ZodObject (e.g., check instance/type) and
then safely call .extend(), or build a fresh base object schema instead of
extending the updateMany schema; keep references to
makeUpdateManyAndReturnSchema, makeUpdateManySchema, mergePluginArgsSchema,
makeSelectSchema, makeOmitSchema and refineForSelectOmitMutuallyExclusive when
applying the fix.
packages/zod/src/factory.ts (1)

105-114: makeJsonSchema creates a new schema instance on every call — no caching.

Unlike makeModelSchema/makeTypeSchema, makeJsonSchema is not memoized. Since it's recursive (via z.lazy), each call returns a fresh schema. Consider caching if this is called frequently.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/zod/src/factory.ts` around lines 105 - 114, makeJsonSchema currently
builds a new recursive Zod schema on every call; add memoization similar to
makeModelSchema/makeTypeSchema by introducing a private cache field (e.g.,
jsonSchemaCache or _jsonSchema) on the class, check and return it if present,
otherwise construct the union schema, assign it to that cache, and return it;
ensure the z.lazy closures still reference the cached schema (build and assign
the schema before any external returns so the recursion sees the same instance).
packages/orm/src/client/crud/validator/validator.ts (1)

184-266: validateProcedureInput has a TODO to convert manual validation to Zod schema.

The current implementation manually validates envelope structure, required/optional params, and per-param types. While thorough, it's ~80 lines of imperative checks that could be expressed as a single Zod schema (strict object with optional/required fields, each typed by makeProcedureParamSchema). This would also enable caching.

Would you like me to open an issue to track converting validateProcedureInput to a Zod-schema-based approach?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/orm/src/client/crud/validator/validator.ts` around lines 184 - 266,
The validateProcedureInput implementation should be replaced by a
Zod-schema-based validation: build a cached Zod schema per procedure (keyed by
proc) using zodFactory.makeProcedureParamSchema(param) for each param to compose
a strict object with required vs optional fields, then validate the incoming
envelope (allowing undefined input only when params.length === 0 or all params
optional) by extracting envelope.args and running schema.safeParse; on failure
throw createInvalidInputError with formatError(parsed.error). Implement caching
(e.g., this._procSchemas: Map<string, ZodType>) to avoid rebuilding schemas,
keep special-case handling for procedures with zero params (accept empty args)
and ensure the schema enforces no extra keys (strict). Ensure all references use
validateProcedureInput, zodFactory.makeProcedureParamSchema,
createInvalidInputError, and formatError so reviewers can locate code to change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/orm/src/client/crud/validator/validator.ts`:
- Around line 28-33: InputValidator is only generic over Schema, losing Options
and ExtQueryArgs precision; update the class to forward the client generics so
zodFactory and methods keep correct types. Change InputValidator to accept the
same Options and ExtQueryArgs generics used by ClientContract and
ZodSchemaFactory (e.g., InputValidator<Schema, Options extends
ClientOptions<Schema>, ExtQueryArgs = {}>) and update the zodFactory declaration
to ZodSchemaFactory<Schema, Options, ExtQueryArgs> and the constructor signature
to accept ClientContract<Schema, Options, ExtQueryArgs>, ensuring all validate
methods and any return types use those generics rather than any.

In `@packages/orm/src/client/zod/factory.ts`:
- Around line 1471-1487: The numeric update branch uses z.number() for
set/increment/decrement/multiply/divide which rejects Decimal/BigInt; replace
z.number() with the field's actual scalar schema (the same schema used for the
field's base type) so those operators accept Decimal/BigInt where applicable.
Locate the branch guarded by isNumericField(fieldDef) and use the field's scalar
schema (from whatever helper/method supplies the field's base zod schema for
fieldDef) in nullableIf(...) and for each operator's .optional() type,
preserving the refine(...) single-key check and existing optional/null handling
via nullableIf and fieldDef.optional. Ensure behavior remains identical for
plain numeric fields by falling back to z.number() if the scalar schema is not
available.

In `@packages/zod/src/factory.ts`:
- Around line 41-65: The relation field construction in makeModelSchema creates
a lazy schema then calls applyCardinality(lazySchema, fieldDef).optional(),
which redundantly adds a second .optional() because applyCardinality already
applies nullable/optional for optional relations; update makeModelSchema to use
the result of applyCardinality directly (without appending .optional()) for
relation fields so you don't wrap optionals twice (refer to makeModelSchema and
applyCardinality to locate the change).

---

Nitpick comments:
In `@packages/orm/src/client/crud/validator/validator.ts`:
- Around line 184-266: The validateProcedureInput implementation should be
replaced by a Zod-schema-based validation: build a cached Zod schema per
procedure (keyed by proc) using zodFactory.makeProcedureParamSchema(param) for
each param to compose a strict object with required vs optional fields, then
validate the incoming envelope (allowing undefined input only when params.length
=== 0 or all params optional) by extracting envelope.args and running
schema.safeParse; on failure throw createInvalidInputError with
formatError(parsed.error). Implement caching (e.g., this._procSchemas:
Map<string, ZodType>) to avoid rebuilding schemas, keep special-case handling
for procedures with zero params (accept empty args) and ensure the schema
enforces no extra keys (strict). Ensure all references use
validateProcedureInput, zodFactory.makeProcedureParamSchema,
createInvalidInputError, and formatError so reviewers can locate code to change.

In `@packages/orm/src/client/zod/factory.ts`:
- Line 980: The parameter name in makeOrderBySchema is using PascalCase
(`WithAggregation`); rename it to camelCase `withAggregation` and update every
reference inside the makeOrderBySchema function (currently referencing
`WithAggregation`) to use `withAggregation` instead so casing is consistent with
other parameters like `withRelation` and `withAggregations`; ensure both the
parameter declaration and all internal usages (including any conditional checks
or passed-through values) are updated to the new identifier.
- Around line 124-142: The three helper methods (getCache, setCache,
printCacheStats) suppress TypeScript errors with `@ts-ignore` and print to
console; remove the `@ts-ignore` by giving the underlying schemaCache a proper
type (e.g., declare schemaCache: Map<string, ZodType> or Record<string,
ZodType>) and update getCache(cacheKey: string): ZodType | undefined and
setCache(cacheKey: string, schema: ZodType): this|void signatures accordingly so
TypeScript errors are resolved; then either (a) let the `@cache`() decorator
access the typed schemaCache directly (export or expose a protected accessor) or
(b) keep the methods but gate printCacheStats(detailed = false) behind a runtime
flag (e.g., process.env.NODE_ENV === 'development' or a DEBUG flag) so
console.log is not executed in production. Ensure references to getCache,
setCache, and printCacheStats in the decorator are updated to the new typed API.
- Around line 1392-1404: The current implementation of
makeUpdateManyAndReturnSchema casts the result of makeUpdateManySchema to
ZodObject to call .extend(), which is brittle if
makeUpdateManySchema/mergePluginArgsSchema ever return a non-object ZodType;
update the code so you do not blindly cast: either change mergePluginArgsSchema
(the upstream producer used by makeUpdateManySchema) to have a return type of
ZodObject explicitly, or in makeUpdateManyAndReturnSchema validate the returned
ZodType from makeUpdateManySchema is a ZodObject (e.g., check instance/type) and
then safely call .extend(), or build a fresh base object schema instead of
extending the updateMany schema; keep references to
makeUpdateManyAndReturnSchema, makeUpdateManySchema, mergePluginArgsSchema,
makeSelectSchema, makeOmitSchema and refineForSelectOmitMutuallyExclusive when
applying the fix.

In `@packages/schema/src/accessor.ts`:
- Around line 216-232: The Proxy returned by SchemaAccessor currently only
defines a get trap, allowing assignments (e.g. accessor.models = {}) to silently
mutate the internal { schema } target; add a set trap to the Proxy handler in
SchemaAccessor that prevents mutation by either throwing a TypeError or
returning false (so strict mode will throw) when any property is assigned, and
ensure it references the same symbols (handler in SchemaAccessor, the target
containing schema, and accessors) so attempts to set properties on the accessor
are blocked rather than applied to the internal target.
- Around line 82-86: The InvalidSchemaError class doesn't set this.name or
restore the prototype chain, so error.name and stack traces show "Error" and
instanceof may fail; in the InvalidSchemaError constructor set this.name =
"InvalidSchemaError" and call Object.setPrototypeOf(this,
InvalidSchemaError.prototype) (or equivalent) after super(message) to preserve
the custom error identity and proper instanceof behavior.

In `@packages/zod/src/factory.ts`:
- Around line 105-114: makeJsonSchema currently builds a new recursive Zod
schema on every call; add memoization similar to makeModelSchema/makeTypeSchema
by introducing a private cache field (e.g., jsonSchemaCache or _jsonSchema) on
the class, check and return it if present, otherwise construct the union schema,
assign it to that cache, and return it; ensure the z.lazy closures still
reference the cached schema (build and assign the schema before any external
returns so the recursion sees the same instance).

@claude
Copy link
Contributor

claude bot commented Feb 20, 2026

Claude finished @ymc9's task —— View job


PR Review: Zod Schemas for ZModel Constructs and ORM Query Input Validations

Todo List:

  • Analyze core architecture and design patterns
  • Review implementation quality and potential bugs
  • Assess performance considerations
  • Check security aspects
  • Evaluate test coverage
  • Provide comprehensive feedback and recommendations

Overview

This is a substantial and well-architected PR that introduces comprehensive Zod integration for ZModel validation. The implementation adds a new @zenstackhq/zod package and integrates it seamlessly into the ORM for runtime validation of query inputs.

✅ Architecture & Design Excellence

Outstanding Design Patterns:

  • Clean separation of concerns: The new @zenstackhq/zod package handles schema generation, while ORM integration manages validation
  • Factory pattern implementation: SchemaFactory provides a clean, type-safe API for Zod schema generation
  • Proper caching strategy: Extensive use of @cache() decorators for performance optimization
  • Type-safe implementation: Excellent use of conditional types and mapped types

Well-Structured Organization:

  • Clear separation between model schema generation (packages/zod/src/factory.ts) and query validation (packages/orm/src/client/zod/factory.ts)
  • The new SchemaAccessor class provides a clean abstraction for schema navigation
  • Input validation centralized in packages/orm/src/client/crud/validator/validator.ts

✅ Implementation Strengths

Comprehensive Type Safety:

  • Factory methods return properly typed Zod schemas matching ZenStack's CRUD operation signatures
  • Complex field mapping handles all ZenStack types correctly (primitives, enums, typedefs, relations)
  • Proper handling of nullable and array fields with cardinality helpers

Robust Validation Architecture:

  • Recursive schema generation for relations properly handles circular references with z.lazy()
  • Custom validation integration preserves existing ZenStack attribute-based validation
  • Proper JSON type handling including typed JSON with nested validation

Performance Considerations:

  • Extensive caching at multiple levels prevents schema regeneration
  • Lazy evaluation for recursive schemas prevents infinite loops
  • Efficient schema reuse across operations

✅ Test Coverage

Comprehensive Testing:

  • Excellent test suite with 1400+ lines covering all major scenarios
  • Tests cover both positive and negative validation cases
  • Good coverage of edge cases like slicing, plugin integration, and type inference
  • Tests verify both runtime validation and TypeScript type safety

⚠️ Issues Identified

🔴 Critical Issues

  1. Type Parameter Loss in InputValidator (packages/orm/src/client/crud/validator/validator.ts:28-32)

    • InputValidator only preserves Schema generic, losing Options and ExtQueryArgs
    • This narrows zodFactory type to generic ClientOptions<Schema> instead of client-specific types
    • Results in loss of type safety for methods like makeFindFirstSchema
  2. GroupBy OrderBy Array Validation Bug (packages/orm/src/client/zod/factory.ts:1723-1737)

    • The code incorrectly assumes value.orderBy is always an object
    • When orderBy is an array, Object.keys() returns indices, causing valid inputs to be rejected
    • Need to normalize to array before validation
  3. Numeric Update Operations Type Restriction (packages/orm/src/client/zod/factory.ts:1471-1487)

    • TODO comment indicates awareness of issue: forces z.number() for Decimal/BigInt fields
    • Rejects valid Decimal instances and numeric strings that the field schema accepts
    • Should reuse the field's scalar schema for consistent type handling

🟡 Minor Issues

  1. Error Class Names Not Set

    • ZodSchemaError and InvalidSchemaError don't set this.name
    • Makes debugging harder when errors show as generic "Error"
  2. Array + Optional Field Handling (packages/zod/src/factory.ts:117-125)

    • applyCardinality returns early for arrays, ignoring optional flag
    • Optional array fields don't get proper nullable().optional() treatment
  3. Resource Leak in Tests (tests/e2e/orm/client-api/zod.test.ts:1401-1404)

    • Local test client created but never disconnected, leaking connections
  4. Build Dependency Issue (packages/schema/package.json:7)

    • test:generate script requires CLI to be built first, causing pipeline failures

🎯 Security Assessment

✅ No Security Concerns Identified:

  • Proper input validation prevents injection attacks
  • No obvious security vulnerabilities
  • Follows established ZenStack security patterns
  • Good error handling with safe error messages

🚀 Performance Analysis

✅ Well Optimized:

  • Extensive caching prevents redundant schema generation
  • Lazy evaluation for recursive schemas
  • Efficient method-level caching with decorators
  • Consider adding cache size limits for long-running applications

📊 Code Quality

✅ High Quality Implementation:

  • Follows project conventions and patterns
  • Good error handling and validation
  • Comprehensive type system integration
  • Clean, readable code with good separation of concerns

🎯 Recommendations

Immediate Actions Required:

  1. Fix InputValidator generics to preserve Options/ExtQueryArgs types
  2. Fix groupBy orderBy validation to handle array inputs correctly
  3. Update numeric operators to accept field-appropriate types (Decimal/BigInt)
  4. Fix resource leak in test by adding client disconnect

Code Quality Improvements:

  1. Set error class names for better debugging
  2. Fix optional array handling in applyCardinality
  3. Remove build dependency on CLI from schema package

✅ Overall Assessment

This is an excellent implementation that significantly enhances ZenStack's validation capabilities. Despite the identified issues, the code is well-architected, thoroughly tested, and follows established patterns. The integration is seamless and maintains backward compatibility.

The issues identified are fixable and don't detract from the overall quality of the work. This PR provides substantial value by enabling both compile-time and runtime validation of query inputs.

Final Recommendation: ✅ APPROVE with requested fixes

The implementation is production-ready after addressing the identified issues.


Copy link
Contributor

@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.

🧹 Nitpick comments (1)
tests/e2e/orm/client-api/zod.test-d.ts (1)

208-225: makeExistsSchema lacks a schema-shape test

Every other factory method (makeFindManySchema, makeCreateSchema, … makeGroupBySchema) has a dedicated it block asserting the fields present on its inferred Args type. makeExistsSchema is only verified as a function (line 224). Consider adding a shape test (e.g. asserting the presence of a where property) for completeness and to catch any future regression in the schema's structure.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e/orm/client-api/zod.test-d.ts` around lines 208 - 225, Add a shape
test for makeExistsSchema similar to the other factory method tests: infer the
Args type from client.$zod.makeExistsSchema and assert it contains the expected
properties (at minimum a where property) using expectTypeOf(...).toHaveProperty
or the same pattern used for other tests (e.g., verifying Args["where"] exists);
update the it block for makeExistsSchema to perform this assertion so
regressions to the schema shape are caught.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/e2e/orm/client-api/zod.test-d.ts`:
- Around line 208-225: Add a shape test for makeExistsSchema similar to the
other factory method tests: infer the Args type from
client.$zod.makeExistsSchema and assert it contains the expected properties (at
minimum a where property) using expectTypeOf(...).toHaveProperty or the same
pattern used for other tests (e.g., verifying Args["where"] exists); update the
it block for makeExistsSchema to perform this assertion so regressions to the
schema shape are caught.

@claude
Copy link
Contributor

claude bot commented Feb 20, 2026

Claude finished @ymc9's task —— View job


PR Review: Zod Schemas for ZModel Constructs and ORM Query Input Validations

Todo List:

  • Analyze core architecture and design patterns
  • Review implementation quality and potential bugs
  • Assess performance considerations
  • Check security aspects
  • Evaluate test coverage
  • Provide comprehensive feedback and recommendations

Overview

This is a substantial and well-architected PR that introduces comprehensive Zod integration for ZModel validation. The implementation adds a new @zenstackhq/zod package and integrates it seamlessly into the ORM for runtime validation of query inputs.

✅ Architecture & Design Excellence

Outstanding Design Patterns:

  • Clean separation of concerns: The new @zenstackhq/zod package handles schema generation, while ORM integration manages validation
  • Factory pattern implementation: SchemaFactory provides a clean, type-safe API for Zod schema generation
  • Proper caching strategy: Extensive use of @cache() decorators for performance optimization
  • Type-safe implementation: Excellent use of conditional types and mapped types

Well-Structured Organization:

  • Clear separation between model schema generation (packages/zod/src/factory.ts) and query validation (packages/orm/src/client/zod/factory.ts)
  • The new SchemaAccessor class provides a clean abstraction for schema navigation
  • Input validation centralized in packages/orm/src/client/crud/validator/validator.ts

✅ Implementation Strengths

Comprehensive Type Safety:

  • Factory methods return properly typed Zod schemas matching ZenStack's CRUD operation signatures
  • Complex field mapping handles all ZenStack types correctly (primitives, enums, typedefs, relations)
  • Proper handling of nullable and array fields with cardinality helpers

Robust Validation Architecture:

  • Recursive schema generation for relations properly handles circular references with z.lazy()
  • Custom validation integration preserves existing ZenStack attribute-based validation
  • Proper JSON type handling including typed JSON with nested validation

Performance Considerations:

  • Extensive caching at multiple levels prevents schema regeneration
  • Lazy evaluation for recursive schemas prevents infinite loops
  • Efficient schema reuse across operations

✅ Test Coverage

Comprehensive Testing:

  • Excellent test suite with 1400+ lines covering all major scenarios
  • Tests cover both positive and negative validation cases
  • Good coverage of edge cases like slicing, plugin integration, and type inference
  • Tests verify both runtime validation and TypeScript type safety

⚠️ Issues Identified

🔴 Critical Issues

  1. Type Parameter Loss in InputValidator (packages/orm/src/client/crud/validator/validator.ts:28-32)

    • InputValidator only preserves Schema generic, losing Options and ExtQueryArgs
    • This narrows zodFactory type to generic ClientOptions<Schema> instead of client-specific types
    • Results in loss of type safety for methods like makeFindFirstSchema
  2. GroupBy OrderBy Array Validation Bug (packages/orm/src/client/zod/factory.ts:1723-1737)

    • The code incorrectly assumes value.orderBy is always an object
    • When orderBy is an array, Object.keys() returns indices, causing valid inputs to be rejected
    • Need to normalize to array before validation
  3. Numeric Update Operations Type Restriction (packages/orm/src/client/zod/factory.ts:1471-1487)

    • TODO comment indicates awareness of issue: forces z.number() for Decimal/BigInt fields
    • Rejects valid Decimal instances and numeric strings that the field schema accepts
    • Should reuse the field's scalar schema for consistent type handling

🟡 Minor Issues

  1. Error Class Names Not Set

    • ZodSchemaError and InvalidSchemaError don't set this.name
    • Makes debugging harder when errors show as generic "Error"
  2. Array + Optional Field Handling (packages/zod/src/factory.ts:117-125)

    • applyCardinality returns early for arrays, ignoring optional flag
    • Optional array fields don't get proper nullable().optional() treatment
  3. Resource Leak in Tests (tests/e2e/orm/client-api/zod.test.ts:1401-1404)

    • Local test client created but never disconnected, leaking connections
  4. Build Dependency Issue (packages/schema/package.json:7)

    • test:generate script requires CLI to be built first, causing pipeline failures

🎯 Security Assessment

✅ No Security Concerns Identified:

  • Proper input validation prevents injection attacks
  • No obvious security vulnerabilities
  • Follows established ZenStack security patterns
  • Good error handling with safe error messages

🚀 Performance Analysis

✅ Well Optimized:

  • Extensive caching prevents redundant schema generation
  • Lazy evaluation for recursive schemas
  • Efficient method-level caching with decorators
  • Consider adding cache size limits for long-running applications

📊 Code Quality

✅ High Quality Implementation:

  • Follows project conventions and patterns
  • Good error handling and validation
  • Comprehensive type system integration
  • Clean, readable code with good separation of concerns

🎯 Recommendations

Immediate Actions Required:

  1. Fix InputValidator generics to preserve Options/ExtQueryArgs types
  2. Fix groupBy orderBy validation to handle array inputs correctly
  3. Update numeric operators to accept field-appropriate types (Decimal/BigInt)
  4. Fix resource leak in test by adding client disconnect

Code Quality Improvements:

  1. Set error class names for better debugging
  2. Fix optional array handling in applyCardinality
  3. Remove build dependency on CLI from schema package

✅ Overall Assessment

This is an excellent implementation that significantly enhances ZenStack's validation capabilities. Despite the identified issues, the code is well-architected, thoroughly tested, and follows established patterns. The integration is seamless and maintains backward compatibility.

The issues identified are fixable and don't detract from the overall quality of the work. This PR provides substantial value by enabling both compile-time and runtime validation of query inputs.

Final Recommendation: ✅ APPROVE with requested fixes

The implementation is production-ready after addressing the identified issues.


Copy link
Contributor

@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

🧹 Nitpick comments (2)
packages/orm/src/client/zod/factory.ts (2)

984-985: WithAggregation parameter uses PascalCase — rename to withAggregation.

Inconsistent with the adjacent withRelation parameter (camelCase). Minor but inconsistent across the parameter list.

♻️ Proposed fix
-    private makeOrderBySchema(model: string, withRelation: boolean, WithAggregation: boolean) {
+    private makeOrderBySchema(model: string, withRelation: boolean, withAggregation: boolean) {

All internal references to WithAggregation (lines 995, 996, 1023, 1026) should be updated accordingly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/orm/src/client/zod/factory.ts` around lines 984 - 985, The parameter
name in makeOrderBySchema is inconsistent: rename the PascalCase parameter
WithAggregation to camelCase withAggregation and update all internal references
to that parameter (occurrences of WithAggregation inside makeOrderBySchema and
any callers that pass or reference it) as well as any local variables that
depend on it so the function signature and its internal usage remain consistent
(search for symbols "makeOrderBySchema" and "WithAggregation" and replace with
"withAggregation").

136-143: Remove or gate the printCacheStats debug artifact.

This private method exists purely for debugging, but the console.log calls in it will execute in production if the @cache() decorator ever surfaces this method. Either remove it or guard it with a debug/verbose option.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/orm/src/client/zod/factory.ts` around lines 136 - 143, The private
debug method printCacheStats (which iterates this.schemaCache.keys()) must not
emit console.log in production: either remove the method entirely or replace its
direct console.log calls with gated logging—e.g., check a debug/verbose flag or
environment variable (process.env.DEBUG or an injected logger like
this.logger?.debug/isVerbose) before printing, or switch to using the existing
logger instead of console; ensure no unconditional console.log remains so that
`@cache-decorated` surfaces won't leak debug output.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/orm/src/client/zod/factory.ts`:
- Around line 1033-1038: makeDistinctSchema currently calls
z.enum(nonRelationFields as any) without guarding for the empty-array case,
which will throw in Zod v4; mirror the pattern used in makeGroupBySchema by
checking nonRelationFields.length and return z.never() when there are no
non-relation fields. Update makeDistinctSchema (and keep the
requireModel(this.schema, model) / nonRelationFields calculation) so the
returned schema is nonRelationFields.length > 0 ?
this.orArray(z.enum(nonRelationFields as any), true) : z.never().

In `@tests/e2e/orm/client-api/zod.test-d.ts`:
- Around line 216-233: Add an assertion to the existing type-check block to
verify the missing factory method: the test that ensures client.$zod is a
ZodSchemaFactory should include
expectTypeOf(client.$zod.makeDeleteManySchema).toBeFunction(); so that the
makeDeleteManySchema factory method is covered alongside
makeDeleteSchema/makeUpdateManySchema etc., ensuring its existence is tested;
update the it block that references client.$zod factory methods (the one
containing makeFindManySchema...makeExistsSchema) to include this additional
check.

---

Duplicate comments:
In `@packages/orm/src/client/zod/factory.ts`:
- Around line 1728-1742: The groupBy orderBy handling issue has already been
addressed: in the schema.refine predicate replace any direct access to
value.orderBy with enumerate(value.orderBy) and iterate over each normalized
orderBy object (as done), filtering Object.keys(orderBy) by AggregateOperators
and ensuring every non-aggregate key appears in the enumerate(value.by) list; no
further code changes are required to fix this specific problem in factory.ts
(see schema.refine, enumerate, AggregateOperators).
- Around line 1476-1493: The numeric-update branch in the zod factory uses
z.number() for set/increment/decrement/multiply/divide which breaks
Decimal/BigInt/string numeric fields; in the isNumericField handling inside the
class (see makeScalarSchema, isNumericField, nullableIf and
fieldDef/fieldSchema), replace the hardcoded z.number() with the scalar schema
produced by makeScalarSchema(fieldDef) (and wrap it with
nullableIf/...optional() exactly as done for the original fieldSchema/set
variant) so set and the arithmetic ops accept the same per-field type
(Decimal/bigint/string/number); keep the single-key refine check unchanged.

---

Nitpick comments:
In `@packages/orm/src/client/zod/factory.ts`:
- Around line 984-985: The parameter name in makeOrderBySchema is inconsistent:
rename the PascalCase parameter WithAggregation to camelCase withAggregation and
update all internal references to that parameter (occurrences of WithAggregation
inside makeOrderBySchema and any callers that pass or reference it) as well as
any local variables that depend on it so the function signature and its internal
usage remain consistent (search for symbols "makeOrderBySchema" and
"WithAggregation" and replace with "withAggregation").
- Around line 136-143: The private debug method printCacheStats (which iterates
this.schemaCache.keys()) must not emit console.log in production: either remove
the method entirely or replace its direct console.log calls with gated
logging—e.g., check a debug/verbose flag or environment variable
(process.env.DEBUG or an injected logger like this.logger?.debug/isVerbose)
before printing, or switch to using the existing logger instead of console;
ensure no unconditional console.log remains so that `@cache-decorated` surfaces
won't leak debug output.

@claude
Copy link
Contributor

claude bot commented Feb 20, 2026

Claude finished @ymc9's task —— View job


PR Review: Zod Schemas for ZModel Constructs and ORM Query Input Validations

Todo List:

  • Analyze core architecture and design patterns
  • Review implementation quality and potential bugs
  • Assess performance considerations
  • Check security aspects
  • Evaluate test coverage
  • Provide comprehensive feedback and recommendations

Overview

This is a substantial and well-architected PR that introduces comprehensive Zod integration for ZModel validation. The implementation adds a new @zenstackhq/zod package and integrates it seamlessly into the ORM for runtime validation of query inputs.

✅ Architecture & Design Excellence

Outstanding Design Patterns:

  • Clean separation of concerns: The new @zenstackhq/zod package handles schema generation, while ORM integration manages validation
  • Factory pattern implementation: SchemaFactory provides a clean, type-safe API for Zod schema generation
  • Proper caching strategy: Extensive use of @cache() decorators for performance optimization
  • Type-safe implementation: Excellent use of conditional types and mapped types

Well-Structured Organization:

  • Clear separation between model schema generation (packages/zod/src/factory.ts) and query validation (packages/orm/src/client/zod/factory.ts)
  • The new SchemaAccessor class provides a clean abstraction for schema navigation
  • Input validation centralized in packages/orm/src/client/crud/validator/validator.ts

✅ Implementation Strengths

Comprehensive Type Safety:

  • Factory methods return properly typed Zod schemas matching ZenStack's CRUD operation signatures
  • Complex field mapping handles all ZenStack types correctly (primitives, enums, typedefs, relations)
  • Proper handling of nullable and array fields with cardinality helpers

Robust Validation Architecture:

  • Recursive schema generation for relations properly handles circular references with z.lazy()
  • Custom validation integration preserves existing ZenStack attribute-based validation
  • Proper JSON type handling including typed JSON with nested validation

Performance Considerations:

  • Extensive caching at multiple levels prevents schema regeneration
  • Lazy evaluation for recursive schemas prevents infinite loops
  • Efficient schema reuse across operations

✅ Test Coverage

Comprehensive Testing:

  • Excellent test suite with 1400+ lines covering all major scenarios
  • Tests cover both positive and negative validation cases
  • Good coverage of edge cases like slicing, plugin integration, and type inference
  • Tests verify both runtime validation and TypeScript type safety

⚠️ Issues Identified

🔴 Critical Issues

  1. Type Parameter Loss in InputValidator (packages/orm/src/client/crud/validator/validator.ts:28-32)

    • InputValidator only preserves Schema generic, losing Options and ExtQueryArgs
    • This narrows zodFactory type to generic ClientOptions<Schema> instead of client-specific types
    • Results in loss of type safety for methods like makeFindFirstSchema
  2. GroupBy OrderBy Array Validation Bug (packages/orm/src/client/zod/factory.ts:1723-1737)

    • The code incorrectly assumes value.orderBy is always an object
    • When orderBy is an array, Object.keys() returns indices, causing valid inputs to be rejected
    • Need to normalize to array before validation
  3. Numeric Update Operations Type Restriction (packages/orm/src/client/zod/factory.ts:1471-1487)

    • TODO comment indicates awareness of issue: forces z.number() for Decimal/BigInt fields
    • Rejects valid Decimal instances and numeric strings that the field schema accepts
    • Should reuse the field's scalar schema for consistent type handling

🟡 Minor Issues

  1. Error Class Names Not Set

    • ZodSchemaError and InvalidSchemaError don't set this.name
    • Makes debugging harder when errors show as generic "Error"
  2. Array + Optional Field Handling (packages/zod/src/factory.ts:117-125)

    • applyCardinality returns early for arrays, ignoring optional flag
    • Optional array fields don't get proper nullable().optional() treatment
  3. Resource Leak in Tests (tests/e2e/orm/client-api/zod.test.ts:1401-1404)

    • Local test client created but never disconnected, leaking connections
  4. Build Dependency Issue (packages/schema/package.json:7)

    • test:generate script requires CLI to be built first, causing pipeline failures

🎯 Security Assessment

✅ No Security Concerns Identified:

  • Proper input validation prevents injection attacks
  • No obvious security vulnerabilities
  • Follows established ZenStack security patterns
  • Good error handling with safe error messages

🚀 Performance Analysis

✅ Well Optimized:

  • Extensive caching prevents redundant schema generation
  • Lazy evaluation for recursive schemas
  • Efficient method-level caching with decorators
  • Consider adding cache size limits for long-running applications

📊 Code Quality

✅ High Quality Implementation:

  • Follows project conventions and patterns
  • Good error handling and validation
  • Comprehensive type system integration
  • Clean, readable code with good separation of concerns

🎯 Recommendations

Immediate Actions Required:

  1. Fix InputValidator generics to preserve Options/ExtQueryArgs types
  2. Fix groupBy orderBy validation to handle array inputs correctly
  3. Update numeric operators to accept field-appropriate types (Decimal/BigInt)
  4. Fix resource leak in test by adding client disconnect

Code Quality Improvements:

  1. Set error class names for better debugging
  2. Fix optional array handling in applyCardinality
  3. Remove build dependency on CLI from schema package

✅ Overall Assessment

This is an excellent implementation that significantly enhances ZenStack's validation capabilities. Despite the identified issues, the code is well-architected, thoroughly tested, and follows established patterns. The integration is seamless and maintains backward compatibility.

The issues identified are fixable and don't detract from the overall quality of the work. This PR provides substantial value by enabling both compile-time and runtime validation of query inputs.

Final Recommendation: ✅ APPROVE with requested fixes

The implementation is production-ready after addressing the identified issues.


@claude
Copy link
Contributor

claude bot commented Feb 20, 2026

Claude finished @ymc9's task —— View job


PR Review: Zod Schemas for ZModel Constructs and ORM Query Input Validations

Todo List:

  • Analyze core architecture and design patterns
  • Review implementation quality and potential bugs
  • Assess performance considerations
  • Check security aspects
  • Evaluate test coverage
  • Provide comprehensive feedback and recommendations

Overview

This is a substantial and well-architected PR that introduces comprehensive Zod integration for ZModel validation. The implementation adds a new @zenstackhq/zod package and integrates it seamlessly into the ORM for runtime validation of query inputs.

✅ Architecture & Design Excellence

Outstanding Design Patterns:

  • Clean separation of concerns: The new @zenstackhq/zod package handles schema generation, while ORM integration manages validation
  • Factory pattern implementation: SchemaFactory provides a clean, type-safe API for Zod schema generation
  • Proper caching strategy: Extensive use of @cache() decorators for performance optimization
  • Type-safe implementation: Excellent use of conditional types and mapped types

Well-Structured Organization:

  • Clear separation between model schema generation (packages/zod/src/factory.ts) and query validation (packages/orm/src/client/zod/factory.ts)
  • The new SchemaAccessor class provides a clean abstraction for schema navigation
  • Input validation centralized in packages/orm/src/client/crud/validator/validator.ts

✅ Implementation Strengths

Comprehensive Type Safety:

  • Factory methods return properly typed Zod schemas matching ZenStack's CRUD operation signatures
  • Complex field mapping handles all ZenStack types correctly (primitives, enums, typedefs, relations)
  • Proper handling of nullable and array fields with cardinality helpers

Robust Validation Architecture:

  • Recursive schema generation for relations properly handles circular references with z.lazy()
  • Custom validation integration preserves existing ZenStack attribute-based validation
  • Proper JSON type handling including typed JSON with nested validation

Performance Considerations:

  • Extensive caching at multiple levels prevents schema regeneration
  • Lazy evaluation for recursive schemas prevents infinite loops
  • Efficient schema reuse across operations

✅ Test Coverage

Comprehensive Testing:

  • Excellent test suite with 1400+ lines covering all major scenarios
  • Tests cover both positive and negative validation cases
  • Good coverage of edge cases like slicing, plugin integration, and type inference
  • Tests verify both runtime validation and TypeScript type safety

⚠️ Issues Identified

🔴 Critical Issues

  1. Type Parameter Loss in InputValidator (packages/orm/src/client/crud/validator/validator.ts:28-32)

    • InputValidator only preserves Schema generic, losing Options and ExtQueryArgs
    • This narrows zodFactory type to generic ClientOptions<Schema> instead of client-specific types
    • Results in loss of type safety for methods like makeFindFirstSchema
  2. GroupBy OrderBy Array Validation Bug (packages/orm/src/client/zod/factory.ts:1723-1737)

    • The code incorrectly assumes value.orderBy is always an object
    • When orderBy is an array, Object.keys() returns indices, causing valid inputs to be rejected
    • Need to normalize to array before validation
  3. Numeric Update Operations Type Restriction (packages/orm/src/client/zod/factory.ts:1471-1487)

    • TODO comment indicates awareness of issue: forces z.number() for Decimal/BigInt fields
    • Rejects valid Decimal instances and numeric strings that the field schema accepts
    • Should reuse the field's scalar schema for consistent type handling

🟡 Minor Issues

  1. Error Class Names Not Set

    • ZodSchemaError and InvalidSchemaError don't set this.name
    • Makes debugging harder when errors show as generic "Error"
  2. Array + Optional Field Handling (packages/zod/src/factory.ts:117-125)

    • applyCardinality returns early for arrays, ignoring optional flag
    • Optional array fields don't get proper nullable().optional() treatment
  3. Resource Leak in Tests (tests/e2e/orm/client-api/zod.test.ts:1401-1404)

    • Local test client created but never disconnected, leaking connections
  4. Build Dependency Issue (packages/schema/package.json:7)

    • test:generate script requires CLI to be built first, causing pipeline failures

🎯 Security Assessment

✅ No Security Concerns Identified:

  • Proper input validation prevents injection attacks
  • No obvious security vulnerabilities
  • Follows established ZenStack security patterns
  • Good error handling with safe error messages

🚀 Performance Analysis

✅ Well Optimized:

  • Extensive caching prevents redundant schema generation
  • Lazy evaluation for recursive schemas
  • Efficient method-level caching with decorators
  • Consider adding cache size limits for long-running applications

📊 Code Quality

✅ High Quality Implementation:

  • Follows project conventions and patterns
  • Good error handling and validation
  • Comprehensive type system integration
  • Clean, readable code with good separation of concerns

🎯 Recommendations

Immediate Actions Required:

  1. Fix InputValidator generics to preserve Options/ExtQueryArgs types
  2. Fix groupBy orderBy validation to handle array inputs correctly
  3. Update numeric operators to accept field-appropriate types (Decimal/BigInt)
  4. Fix resource leak in test by adding client disconnect

Code Quality Improvements:

  1. Set error class names for better debugging
  2. Fix optional array handling in applyCardinality
  3. Remove build dependency on CLI from schema package

✅ Overall Assessment

This is an excellent implementation that significantly enhances ZenStack's validation capabilities. Despite the identified issues, the code is well-architected, thoroughly tested, and follows established patterns. The integration is seamless and maintains backward compatibility.

The issues identified are fixable and don't detract from the overall quality of the work. This PR provides substantial value by enabling both compile-time and runtime validation of query inputs.

Final Recommendation: ✅ APPROVE with requested fixes

The implementation is production-ready after addressing the identified issues.


@ymc9 ymc9 merged commit 7a98d41 into dev Feb 20, 2026
8 checks passed
@ymc9 ymc9 deleted the feat/zod branch February 20, 2026 14:31
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.

1 participant