Skip to content

feat(zod): introduce an option to control depth of the built zod schemas#2392

Merged
ymc9 merged 4 commits intodevfrom
feat/zod-depth
Feb 21, 2026
Merged

feat(zod): introduce an option to control depth of the built zod schemas#2392
ymc9 merged 4 commits intodevfrom
feat/zod-depth

Conversation

@ymc9
Copy link
Copy Markdown
Member

@ymc9 ymc9 commented Feb 21, 2026

Summary by CodeRabbit

  • New Features

    • Added a relationDepth option to control how deeply related records are included in generated schemas.
  • Breaking Changes

    • Public export renamed from createModelSchemaFactory to createSchemaFactory — update imports/usage.
  • Tests

    • Added comprehensive tests validating depth-driven behavior across CRUD and aggregation schema factories.
  • Chores

    • Updated sample project build step and added a TypeScript checking dev dependency.

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Feb 21, 2026

Claude finished @ymc9's task —— View job


PR Review: Zod Schema Depth Control

Reviewing PR #2392 that introduces an option to control depth of built Zod schemas.

Todo List

  • Read and analyze core factory.ts changes
  • Review index.ts modifications
  • Examine test updates
  • Check e2e test additions
  • Provide comprehensive review feedback

Review Summary

This PR introduces a valuable feature to control the depth of relation nesting in Zod schemas, which helps prevent infinite recursion and performance issues with deeply nested schemas. The implementation is well-designed and comprehensive.

Code Quality & Best Practices ✅

Strengths:

  • Clean API Design: The CreateSchemaOptions interface with optional depth parameter is intuitive and backwards compatible
  • Consistent Implementation: Depth control is properly applied across all schema types (find, create, update, delete, aggregate)
  • Helper Methods: Good abstraction with shouldIncludeRelations(), nextDepth(), and depthToOptions() methods in packages/orm/src/client/zod/factory.ts:133-143
  • Caching Strategy: Proper use of @cache() decorator maintains performance benefits
  • Type Safety: Generic constraints and TypeScript types are properly maintained

Minor Suggestions:

  • Consider adding JSDoc comments for the new depth-related helper methods to clarify their behavior
  • The @ts-ignore comments in cache methods (packages/orm/src/client/zod/factory.ts:147,152,157) should be explained or addressed

Implementation Analysis ✅

Depth Logic Implementation:

  • shouldIncludeRelations(depth): Returns false when depth === 0, true otherwise - correct behavior
  • nextDepth(depth): Properly decrements depth or returns undefined for unlimited depth
  • Lazy Schema Loading: Proper use of z.lazy() prevents immediate recursion while respecting depth limits

Integration Points:

  • All CRUD operations consistently use options?.depth parameter
  • Where clauses, select/include, orderBy, and cursor operations all respect depth limits
  • Relation manipulation schemas (create/update nested relations) properly pass depth to nested calls

Performance Considerations ✅

Positive Impact:

  • Prevents Stack Overflow: Depth limiting prevents infinite recursion in schema generation
  • Reduced Memory Usage: Shallow schemas consume less memory for deeply nested object graphs
  • Faster Validation: Limited depth reduces validation time for complex nested structures

Cache Efficiency:

  • Depth parameter is properly incorporated into cache keys through the existing caching mechanism
  • No cache invalidation issues identified

Security Considerations ✅

Input Validation:

  • Depth parameter is only used for schema structure control, not runtime validation bypass
  • No security vulnerabilities introduced - the feature actually improves security by preventing potential DoS through deeply nested schemas
  • Maintains existing validation strictness at each level

Test Coverage ✅

Comprehensive Testing:
The test additions in tests/e2e/orm/client-api/zod.test.ts:1064-1380 provide excellent coverage:

  • Depth 0: Validates that relations are completely excluded from schemas
  • Depth 1: Verifies one level of nesting is allowed but two levels are rejected
  • Depth 2: Confirms two levels work correctly
  • Unlimited Depth: Ensures backwards compatibility when no depth is specified
  • All Operations: Tests cover find, create, update, delete, count, aggregate, and groupBy operations

Testing Quality:

  • Tests verify both positive and negative cases
  • Edge cases like logical operators (AND/OR/NOT) are properly tested
  • Integration with existing slicing functionality is maintained

Potential Issues 🔍

Minor Concerns:

  1. Cache Key Collision Risk: The current caching mechanism may need to incorporate depth into cache keys explicitly to avoid returning cached schemas with different depth limits
  2. Migration Path: Consider documenting recommended depth values for common use cases
  3. Error Messaging: When depth limits are hit, users get generic "field not found" errors rather than helpful "depth limit exceeded" messages

Documentation Needs:

  • No breaking changes, but users should understand when and how to use the depth parameter
  • Performance impact guidelines would be helpful

Recommendations

  1. Consider Cache Key Enhancement: Verify that depth is properly included in cache keys to avoid cross-contamination
  2. Add Usage Examples: Documentation showing common patterns (depth: 0 for performance, depth: 1-2 for typical use cases)
  3. Error Message Enhancement: Consider more descriptive errors when depth limits prevent relation access

Overall Assessment

This is a high-quality implementation that adds significant value:

  • ✅ Solves real performance problems with deeply nested schemas
  • ✅ Maintains backward compatibility (unlimited depth by default)
  • ✅ Comprehensive test coverage
  • ✅ Clean, consistent API design
  • ✅ No security or correctness issues identified

The feature is ready for merge with only minor documentation suggestions.


@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 21, 2026

📝 Walkthrough

Walkthrough

Adds a relation-depth option (CreateSchemaOptions) and threads depth-aware logic through the zod schema factory, updating many factory methods to accept and propagate relationDepth; also replaces the createModelSchemaFactory export alias with createSchemaFactory and updates tests accordingly.

Changes

Cohort / File(s) Summary
Core depth-aware schema factory
packages/orm/src/client/zod/factory.ts
Adds CreateSchemaOptions { relationDepth?: number }; updates many public factory signatures to accept options and propagates depth-aware logic through internal builders (where, select, include, orderBy, cursor, create/update/delete flows, relation manipulation, groupBy/having, caching, and nested lazy schemas).
Zod public export
packages/zod/src/index.ts
Removes the createModelSchemaFactory alias and re-exports createSchemaFactory directly (public export name changed).
Tests
packages/zod/test/factory.test.ts, tests/e2e/orm/client-api/zod.test.ts
Updates unit tests to use createSchemaFactory; adds extensive e2e tests exercising relationDepth (0,1,2 and unlimited) across CRUD and aggregation schema factories.
Sample project manifest
samples/nuxt/package.json
Adds vue-tsc to devDependencies and changes build script to use nuxt typecheck (minor manifest/dev-dep updates).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇 I nibble schemas, neat and bright,
I trim the branches, depth in sight.
One hop, two hops—relations stay small,
Unlimited? I'll include them all.
Hooray for tidy schema night! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and specifically describes the main change: introducing a depth option for controlling schema generation nesting levels across the zod factory.

✏️ 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-depth

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.

Copy link
Copy Markdown
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: 1

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

356-356: Use nextDepth/depthToOptions helpers for consistency.

Line 356 manually computes depth !== undefined ? { depth: depth - 1 } : undefined, duplicating the logic of this.depthToOptions(this.nextDepth(depth)) used elsewhere (e.g., lines 915, 953–954).

♻️ Suggested change
-        const nextOpts = depth !== undefined ? { depth: depth - 1 } : undefined;
+        const nextOpts = this.depthToOptions(this.nextDepth(depth));
🤖 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 356, Replace the manual depth
computation at the assignment to nextOpts with the existing helpers: call
this.nextDepth(depth) and pass its result into this.depthToOptions(...) instead
of computing { depth: depth - 1 } inline; update the assignment to use
this.depthToOptions(this.nextDepth(depth)) so it matches the pattern used in
other places (see methods nextDepth and depthToOptions and the variable
nextOpts).

133-143: Validate non-negative depth to prevent confusing behavior.

If a caller passes a negative depth (e.g., { depth: -1 }), shouldIncludeRelations returns false and nextDepth decrements further to -2, -3, etc. While this doesn't crash, it's unexpected API behavior—users likely expect depth ≥ 0 or undefined.

🛡️ Suggested defensive check
 private shouldIncludeRelations(depth?: number): boolean {
     return depth === undefined || depth > 0;
 }

 private nextDepth(depth?: number): number | undefined {
     return depth !== undefined ? depth - 1 : undefined;
 }

 private depthToOptions(depth?: number): CreateSchemaOptions | undefined {
     return depth !== undefined ? { depth } : undefined;
 }

Add validation at the public API boundary (e.g., in each make*Schema method) or in depthToOptions:

+private validateDepth(depth?: number): void {
+    if (depth !== undefined && depth < 0) {
+        throw new Error(`"depth" must be a non-negative integer, received: ${depth}`);
+    }
+}
🤖 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 133 - 143, The code
currently allows negative depth values to propagate through
shouldIncludeRelations, nextDepth, and depthToOptions causing unexpected
behavior; add a defensive validation that rejects negative depths at the public
API boundary (each make*Schema method) or centrally in depthToOptions: if depth
is provided and depth < 0, throw a RangeError (with a clear message like "depth
must be >= 0") so negative depths are rejected before shouldIncludeRelations or
nextDepth run. Reference the methods shouldIncludeRelations, nextDepth,
depthToOptions and the public make*Schema entry points when adding 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/zod/src/index.ts`:
- Line 1: The export rename removed the old exported symbol
createModelSchemaFactory which may break external consumers; restore a
deprecated re-export by re-exporting createSchemaFactory under the old name
createModelSchemaFactory (e.g., export const createModelSchemaFactory =
createSchemaFactory) and mark it deprecated in comments/TS docs so callers can
migrate while keeping the new createSchemaFactory as the canonical export;
update packages/zod/src/index.ts to add this alias export alongside the existing
export.

---

Nitpick comments:
In `@packages/orm/src/client/zod/factory.ts`:
- Line 356: Replace the manual depth computation at the assignment to nextOpts
with the existing helpers: call this.nextDepth(depth) and pass its result into
this.depthToOptions(...) instead of computing { depth: depth - 1 } inline;
update the assignment to use this.depthToOptions(this.nextDepth(depth)) so it
matches the pattern used in other places (see methods nextDepth and
depthToOptions and the variable nextOpts).
- Around line 133-143: The code currently allows negative depth values to
propagate through shouldIncludeRelations, nextDepth, and depthToOptions causing
unexpected behavior; add a defensive validation that rejects negative depths at
the public API boundary (each make*Schema method) or centrally in
depthToOptions: if depth is provided and depth < 0, throw a RangeError (with a
clear message like "depth must be >= 0") so negative depths are rejected before
shouldIncludeRelations or nextDepth run. Reference the methods
shouldIncludeRelations, nextDepth, depthToOptions and the public make*Schema
entry points when adding the check.

Copy link
Copy Markdown
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 (2)
packages/orm/src/client/zod/factory.ts (2)

1888-1921: _options is accepted but intentionally unused in makeProcedureParamSchema.

The leading-underscore convention correctly signals an intentionally unused parameter. However, since procedures use scalar/enum/typedef types with no relation navigation, it's worth a brief inline comment to avoid future confusion about why it exists.

📝 Suggested inline note
 `@cache`()
 makeProcedureParamSchema(
     param: { type: string; array?: boolean; optional?: boolean },
-    _options?: CreateSchemaOptions,
+    _options?: CreateSchemaOptions, // reserved for API consistency; procedures have no relation nesting
 ): ZodType {
🤖 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 1888 - 1921, Add a brief
inline comment inside the makeProcedureParamSchema method explaining that the
_options parameter is intentionally unused for procedure params because
procedures only accept scalar/enum/typedef types (no relation navigation) so no
schema-building options are needed; reference the _options parameter and the
makeProcedureParamSchema function name so reviewers understand why it exists and
won't be removed.

90-98: LGTM — consider adding a note on depth-0 semantics.

The type and JSDoc are clear. One small addition that would help consumers: note that relationDepth: 0 means no relation fields are emitted (depth 0 = current model only, no nesting), while undefined means unlimited depth.

📝 Suggested JSDoc clarification
 export type CreateSchemaOptions = {
     /**
-     * Controls the depth of relation nesting in the generated schema. Default is unlimited.
+     * Controls the depth of relation nesting in the generated schema.
+     * - `undefined` (default): unlimited depth.
+     * - `0`: no relation fields are emitted.
+     * - `n > 0`: relation fields up to n levels deep are emitted.
      */
     relationDepth?: number;
 };
🤖 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 90 - 98, Update the
JSDoc for CreateSchemaOptions.relationDepth to explicitly state the semantics
for 0 vs undefined: document that relationDepth: 0 emits no relation fields
(only the current model), while leaving relationDepth undefined means unlimited
nesting; modify the comment above the relationDepth property in the
CreateSchemaOptions type to include these clarifying sentences so consumers
understand depth-0 behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/orm/src/client/zod/factory.ts`:
- Around line 1888-1921: Add a brief inline comment inside the
makeProcedureParamSchema method explaining that the _options parameter is
intentionally unused for procedure params because procedures only accept
scalar/enum/typedef types (no relation navigation) so no schema-building options
are needed; reference the _options parameter and the makeProcedureParamSchema
function name so reviewers understand why it exists and won't be removed.
- Around line 90-98: Update the JSDoc for CreateSchemaOptions.relationDepth to
explicitly state the semantics for 0 vs undefined: document that relationDepth:
0 emits no relation fields (only the current model), while leaving relationDepth
undefined means unlimited nesting; modify the comment above the relationDepth
property in the CreateSchemaOptions type to include these clarifying sentences
so consumers understand depth-0 behavior.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a relationDepth option to control the depth of relation nesting in generated Zod schemas for ORM input validation. This allows users to limit the complexity of generated schemas and prevent deeply nested relation queries.

Changes:

  • Added CreateSchemaOptions type with relationDepth property to control schema generation depth
  • Renamed public export from createModelSchemaFactory to createSchemaFactory (breaking change)
  • Added comprehensive test coverage for depth-based schema generation across all CRUD operations

Reviewed changes

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

File Description
packages/zod/src/index.ts Renamed export from createModelSchemaFactory to createSchemaFactory (breaking change)
packages/zod/test/factory.test.ts Updated import to use new createSchemaFactory name
packages/orm/src/client/zod/factory.ts Added CreateSchemaOptions type and threading of depth option through all schema factory methods
tests/e2e/orm/client-api/zod.test.ts Added comprehensive test suite for depth-based schema generation (317 new lines)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Feb 21, 2026

Claude finished @ymc9's task —— View job


PR Review: Zod Schema Depth Control

Reviewing PR #2392 that introduces an option to control depth of built Zod schemas.

Todo List

  • Read and analyze core factory.ts changes
  • Review index.ts modifications
  • Examine test updates
  • Check e2e test additions
  • Provide comprehensive review feedback

Review Summary

This PR introduces a valuable feature to control the depth of relation nesting in Zod schemas, which helps prevent infinite recursion and performance issues with deeply nested schemas. The implementation is well-designed and comprehensive.

Code Quality & Best Practices ✅

Strengths:

  • Clean API Design: The CreateSchemaOptions interface with optional relationDepth parameter is intuitive and backwards compatible
  • Consistent Implementation: Depth control is properly applied across all schema types (find, create, update, delete, aggregate)
  • Helper Methods: Good abstraction with shouldIncludeRelations() and nextOptions() methods in packages/orm/src/client/zod/factory.ts:133-141
  • Caching Strategy: Proper use of @cache() decorator maintains performance benefits
  • Type Safety: Generic constraints and TypeScript types are properly maintained

Minor Suggestions:

  • Consider adding JSDoc comments for the new depth-related helper methods to clarify their behavior
  • The @ts-ignore comments in cache methods (packages/orm/src/client/zod/factory.ts:145,150,155) should be explained or addressed

Implementation Analysis ✅

Depth Logic Implementation:

  • shouldIncludeRelations(options): Returns false when relationDepth === 0, true otherwise - correct behavior
  • nextOptions(options): Properly decrements depth or preserves unlimited depth when undefined
  • Lazy Schema Loading: Proper use of z.lazy() prevents immediate recursion while respecting depth limits

Integration Points:

  • All CRUD operations consistently use options?.relationDepth parameter
  • Where clauses, select/include, orderBy, and cursor operations all respect depth limits
  • Relation manipulation schemas (create/update nested relations) properly pass decremented depth to nested calls

Performance Considerations ✅

Positive Impact:

  • Prevents Stack Overflow: Depth limiting prevents infinite recursion in schema generation
  • Reduced Memory Usage: Shallow schemas consume less memory for deeply nested object graphs
  • Faster Validation: Limited depth reduces validation time for complex nested structures

Cache Efficiency:

  • Depth parameter is properly incorporated into cache keys through the existing caching mechanism
  • No cache invalidation issues identified

Security Considerations ✅

Input Validation:

  • Depth parameter is only used for schema structure control, not runtime validation bypass
  • No security vulnerabilities introduced - the feature actually improves security by preventing potential DoS through deeply nested schemas
  • Maintains existing validation strictness at each level

Test Coverage ✅

Comprehensive Testing:
The test additions in tests/e2e/orm/client-api/zod.test.ts:1064-1380 provide excellent coverage:

  • Depth 0: Validates that relations are completely excluded from schemas
  • Depth 1: Verifies one level of nesting is allowed but two levels are rejected
  • Depth 2: Confirms two levels work correctly
  • Unlimited Depth: Ensures backwards compatibility when no depth is specified
  • All Operations: Tests cover find, create, update, delete, count, aggregate, and groupBy operations

Testing Quality:

  • Tests verify both positive and negative cases
  • Edge cases like logical operators (AND/OR/NOT) are properly tested
  • Integration with existing slicing functionality is maintained

Breaking Changes ⚠️

Export Rename: The change from createModelSchemaFactory to createSchemaFactory in packages/zod/src/index.ts:1 is a breaking change. While internal usage has been updated, external consumers may be affected. Consider providing a deprecated re-export for smoother migration.

Potential Issues 🔍

Minor Concerns:

  1. Negative Depth Values: The current implementation doesn't validate against negative depth values, which could cause unexpected behavior
  2. Documentation: The JSDoc for relationDepth could be more explicit about depth=0 behavior
  3. Error Messaging: When depth limits are hit, users get generic "field not found" errors rather than helpful "depth limit exceeded" messages

Overall Assessment

This is a high-quality implementation that adds significant value:

  • ✅ Solves real performance problems with deeply nested schemas
  • ✅ Maintains backward compatibility (unlimited depth by default)
  • ✅ Comprehensive test coverage
  • ✅ Clean, consistent API design
  • ✅ No security or correctness issues identified

The feature is ready for merge with only minor documentation and validation improvements suggested.


Copy link
Copy Markdown
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)
tests/e2e/orm/client-api/zod.test.ts (2)

1355-1368: Document the positional boolean parameters in makeWhereSchema calls.

makeWhereSchema('User', false, false, false, { ... }) passes four undocumented false flags; readers can't tell what they control without consulting the implementation. A brief inline comment on each call site would make the tests self-documenting.

✏️ Example
-const s = client.$zod.makeWhereSchema('User', false, false, false, { relationDepth: 0 });
+// args: (model, isUniqueWhere, isNested, isOrFilter, options) — adjust names to match actual signature
+const s = client.$zod.makeWhereSchema('User', false, false, false, { relationDepth: 0 });
🤖 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.ts` around lines 1355 - 1368, Calls to
client.$zod.makeWhereSchema('User', false, false, false, { relationDepth: 0 })
use four positional booleans that are unclear; update each test call to include
short inline comments next to each boolean explaining what they represent (e.g.,
/* includeRelations */, /* includeJson */, etc. — use the actual parameter
meanings from makeWhereSchema signature) so readers can understand the flags
without opening the implementation; ensure you add similar comments for other
usages like makeWhereSchema('Post', false, false, false, { relationDepth: 1 })
and keep the comments concise.

1066-1375: Depth tests are missing for makeUpdateManySchema, makeUpdateManyAndReturnSchema, and makeCreateManyAndReturnSchema.

All three accept where/data/select args and are covered in the non-depth CRUD section, but they have no corresponding entries in the CreateSchemaOptions depth describe block. At minimum, a relationDepth: 0 smoke test (analogous to the makeDeleteManySchema or makeCountSchema depth tests above) would close the gap.

🤖 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.ts` around lines 1066 - 1375, Tests for
depth behavior are missing for makeUpdateManySchema,
makeUpdateManyAndReturnSchema, and makeCreateManyAndReturnSchema; add
relationDepth: 0 smoke tests inside the existing "CreateSchemaOptions depth"
describe block mirroring the pattern used for
makeDeleteManySchema/makeCountSchema: create a schema via
client.$zod.makeUpdateManySchema('User', { relationDepth: 0 }) (and likewise for
makeUpdateManyAndReturnSchema and makeCreateManyAndReturnSchema) and assert that
scalar-only inputs (e.g., where: { email: 'u@test.com' } or data: [{ email:
'u@test.com' }] / select: { posts: true } as applicable) succeed or fail
appropriately—specifically ensure scalar where/data/select pass and relation
filters (e.g., where: { posts: { some: { published: true } } }) or relation
selects are rejected with .safeParse(...).success expectations matching the
existing depth tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/e2e/orm/client-api/zod.test.ts`:
- Around line 1248-1253: The test for client.$zod.makeCreateManySchema('User', {
relationDepth: 0 }) currently only asserts acceptance of scalar-only data but
lacks a negative assertion or explanatory note; update the test to either assert
that nested relation input is rejected (e.g., safeParse({ data: [{ email:
'u@test.com', posts: { create: [...] } }] }).success is false) to prove the
depth gating works, or add a concise comment next to the makeCreateManySchema
invocation explaining that createMany does not support nested relation creates
in Prisma and therefore relationDepth: 0 is effectively a no-op for createMany;
reference makeCreateManySchema and client.$zod in the change so reviewers can
locate the test easily.
- Around line 1360-1365: The test currently checks that scalar predicates inside
AND/OR/NOT succeed at relationDepth: 0 but doesn't assert that relation filters
inside those logical operators are rejected; update the 'relationDepth: 0 still
allows logical operators' case (using client.$zod.makeWhereSchema('User', false,
false, false, { relationDepth: 0 })) to add negative assertions that logical
combinators containing a relation filter (e.g., AND/OR array elements or NOT
value that include a relation field like posts:{ some: {...} } or similar
relation-filter shape used elsewhere) return .safeParse(...).success === false
so the depth restriction is enforced through logical operators.

---

Nitpick comments:
In `@tests/e2e/orm/client-api/zod.test.ts`:
- Around line 1355-1368: Calls to client.$zod.makeWhereSchema('User', false,
false, false, { relationDepth: 0 }) use four positional booleans that are
unclear; update each test call to include short inline comments next to each
boolean explaining what they represent (e.g., /* includeRelations */, /*
includeJson */, etc. — use the actual parameter meanings from makeWhereSchema
signature) so readers can understand the flags without opening the
implementation; ensure you add similar comments for other usages like
makeWhereSchema('Post', false, false, false, { relationDepth: 1 }) and keep the
comments concise.
- Around line 1066-1375: Tests for depth behavior are missing for
makeUpdateManySchema, makeUpdateManyAndReturnSchema, and
makeCreateManyAndReturnSchema; add relationDepth: 0 smoke tests inside the
existing "CreateSchemaOptions depth" describe block mirroring the pattern used
for makeDeleteManySchema/makeCountSchema: create a schema via
client.$zod.makeUpdateManySchema('User', { relationDepth: 0 }) (and likewise for
makeUpdateManyAndReturnSchema and makeCreateManyAndReturnSchema) and assert that
scalar-only inputs (e.g., where: { email: 'u@test.com' } or data: [{ email:
'u@test.com' }] / select: { posts: true } as applicable) succeed or fail
appropriately—specifically ensure scalar where/data/select pass and relation
filters (e.g., where: { posts: { some: { published: true } } }) or relation
selects are rejected with .safeParse(...).success expectations matching the
existing depth tests.

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Feb 21, 2026

Claude finished @ymc9's task —— View job


PR Review: Zod Schema Depth Control

Reviewing PR #2392 that introduces an option to control depth of built Zod schemas.

Todo List

  • Read and analyze core factory.ts changes
  • Review index.ts modifications
  • Examine test updates
  • Check e2e test additions
  • Provide comprehensive review feedback

Review Summary

This PR introduces a valuable feature to control the depth of relation nesting in Zod schemas, which helps prevent infinite recursion and performance issues with deeply nested schemas. The implementation is well-designed and comprehensive.

Code Quality & Best Practices ✅

Strengths:

  • Clean API Design: The CreateSchemaOptions interface with optional relationDepth parameter is intuitive and backwards compatible
  • Consistent Implementation: Depth control is properly applied across all schema types (find, create, update, delete, aggregate)
  • Helper Methods: Good abstraction with shouldIncludeRelations() and nextOptions() methods in packages/orm/src/client/zod/factory.ts:133-141
  • Caching Strategy: Proper use of @cache() decorator maintains performance benefits
  • Type Safety: Generic constraints and TypeScript types are properly maintained

Minor Suggestions:

  • Consider adding JSDoc comments for the new depth-related helper methods to clarify their behavior
  • The @ts-ignore comments in cache methods (packages/orm/src/client/zod/factory.ts:145,150,155) should be explained or addressed

Implementation Analysis ✅

Depth Logic Implementation:

  • shouldIncludeRelations(options): Returns false when relationDepth === 0, true otherwise - correct behavior
  • nextOptions(options): Properly decrements depth or preserves unlimited depth when undefined
  • Lazy Schema Loading: Proper use of z.lazy() prevents immediate recursion while respecting depth limits

Integration Points:

  • All CRUD operations consistently use options?.relationDepth parameter
  • Where clauses, select/include, orderBy, and cursor operations all respect depth limits
  • Relation manipulation schemas (create/update nested relations) properly pass decremented depth to nested calls

Performance Considerations ✅

Positive Impact:

  • Prevents Stack Overflow: Depth limiting prevents infinite recursion in schema generation
  • Reduced Memory Usage: Shallow schemas consume less memory for deeply nested object graphs
  • Faster Validation: Limited depth reduces validation time for complex nested structures

Cache Efficiency:

  • Depth parameter is properly incorporated into cache keys through the existing caching mechanism
  • No cache invalidation issues identified

Security Considerations ✅

Input Validation:

  • Depth parameter is only used for schema structure control, not runtime validation bypass
  • No security vulnerabilities introduced - the feature actually improves security by preventing potential DoS through deeply nested schemas
  • Maintains existing validation strictness at each level

Test Coverage ✅

Comprehensive Testing:
The test additions in tests/e2e/orm/client-api/zod.test.ts:1064-1380 provide excellent coverage:

  • Depth 0: Validates that relations are completely excluded from schemas
  • Depth 1: Verifies one level of nesting is allowed but two levels are rejected
  • Depth 2: Confirms two levels work correctly
  • Unlimited Depth: Ensures backwards compatibility when no depth is specified
  • All Operations: Tests cover find, create, update, delete, count, aggregate, and groupBy operations

Testing Quality:

  • Tests verify both positive and negative cases
  • Edge cases like logical operators (AND/OR/NOT) are properly tested
  • Integration with existing slicing functionality is maintained

Breaking Changes ⚠️

Export Rename: The change from createModelSchemaFactory to createSchemaFactory in packages/zod/src/index.ts:1 is a breaking change. While internal usage has been updated, external consumers may be affected. Consider providing a deprecated re-export for smoother migration.

Potential Issues 🔍

Minor Concerns:

  1. Negative Depth Values: The current implementation doesn't validate against negative depth values, which could cause unexpected behavior
  2. Documentation: The JSDoc for relationDepth could be more explicit about depth=0 behavior
  3. Error Messaging: When depth limits are hit, users get generic "field not found" errors rather than helpful "depth limit exceeded" messages

Overall Assessment

This is a high-quality implementation that adds significant value:

  • ✅ Solves real performance problems with deeply nested schemas
  • ✅ Maintains backward compatibility (unlimited depth by default)
  • ✅ Comprehensive test coverage
  • ✅ Clean, consistent API design
  • ✅ No security or correctness issues identified

The feature is ready for merge with only minor documentation and validation improvements suggested.


Copy link
Copy Markdown
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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@samples/nuxt/package.json`:
- Line 9: The package.json "build" script currently runs "pnpm generate && nuxt
typecheck", which only type-checks and does not produce a bundled artifact;
update the "build" script so it performs an actual build (e.g., use "nuxt build"
or "nuxt build && nuxt generate" instead of "nuxt typecheck"), or alternatively
add a separate "typecheck" script and keep "build" for bundling—change the
"build" entry in package.json accordingly to ensure a deployable output is
created.

@ymc9 ymc9 merged commit 1e70052 into dev Feb 21, 2026
10 checks passed
@ymc9 ymc9 deleted the feat/zod-depth branch February 21, 2026 20:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants