fix(orm): handle cyclic JSON typedef references in zod factory (#2654) - #2655

Merged
ymc9 merged 1 commit into
devfrom
fix/issue-2654-cyclic-typedef-zod
May 8, 2026
Merged

fix(orm): handle cyclic JSON typedef references in zod factory (#2654)#2655
ymc9 merged 1 commit into
devfrom
fix/issue-2654-cyclic-typedef-zod

Conversation

@ymc9

@ymc9ymc9 commented May 8, 2026

Copy link
Copy Markdown
Member

Summary

  • makeTypeDefSchema previously recursed forever when JSON typedefs referenced each other (or themselves), throwing RangeError: Maximum call stack size exceeded. The @cache() decorator only populates the cache after the method returns, so the recursive call still saw an empty cache.
  • Wrap nested typedef references in z.lazy(() => …) so the inner lookup defers to validation time, by which point the outer build has finished and cached its result. Same pattern already used by makeJsonValueSchema.
  • Added regression test tests/regression/test/issue-2654.test.ts covering cyclic (AB) and self-referencing (Tree { children Tree[]? }) typedefs.

Fixes#2654

Test plan

  • tests/regression/test/issue-2654.test.ts passes
  • tests/regression/test/issue-{493,558,586}.test.ts (existing JSON typedef coverage) still pass
  • tests/e2e/orm/client-api/typed-json-fields.test.ts still passes

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Resolved infinite recursion errors that occurred during schema generation when working with cyclic or self-referential JSON typedefs, enabling applications to properly create, persist, and retrieve complex nested data structures with circular references.
  • Tests

    • Added comprehensive regression tests verifying the correct behavior and persistence of cyclic and self-referential JSON typedef operations.

Cyclic or self-referencing JSON typedefs caused makeTypeDefSchema to
recurse forever — the @cache() decorator only stores the result after
the method returns, so a recursive call back into the same type still
saw an empty cache. Wrap nested typedef references in z.lazy() so the
inner lookup is deferred to validation time, by which point the outer
build has populated the cache.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented May 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1e7f4c78-2c65-449e-bd56-6ba16ec370db

📥 Commits

Reviewing files that changed from the base of the PR and between d1db37c and 17f44cf.

📒 Files selected for processing (2)
  • packages/orm/src/client/zod/factory.ts
  • tests/regression/test/issue-2654.test.ts

📝 Walkthrough

Walkthrough

The PR fixes a stack overflow when creating data for JSON types with cyclic references by wrapping nested typedef field schemas in z.lazy() to defer evaluation. Two regression tests validate the fix for both mutually recursive and self-referential typedef scenarios.

Changes

Cyclic Typedef Schema Generation

Layer / File(s)Summary
Lazy Schema Construction
packages/orm/src/client/zod/factory.ts
Nested typedef field schemas wrapped in z.lazy() instead of eager construction, deferring evaluation to break cycles in recursive typedef definitions.
Regression Tests
tests/regression/test/issue-2654.test.ts
Two integration tests verify cyclic typedef handling: mutually recursive types (A and B referencing each other) and self-referential types (Tree with optional children), both persisting and returning expected shapes.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 Lazy loops no more shall spin,
When typedefs fold and reference in,
A gentle defer breaks the chain,
Cyclic structures now refrain,
From stack overflow's endless pain! 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately describes the main change: fixing cyclic JSON typedef reference handling in the Zod factory, which is the core issue being addressed.
Linked Issues check✅ PassedThe code changes fully address the linked issue #2654 by wrapping nested typedef references in z.lazy() to defer schema construction, preventing infinite recursion for cyclic/self-referential JSON typedefs.
Out of Scope Changes check✅ PassedAll changes are directly scoped to fixing cyclic JSON typedef handling: modifications to makeTypeDefSchema in factory.ts and a focused regression test for the specific issue.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-2654-cyclic-typedef-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

claudeBot commented May 8, 2026

Copy link
Copy Markdown
Contributor

Claude finished @ymc9's task —— View job


Code Review for PR #2655

Todo List:

  • Read and analyze the factory.ts changes
  • Review the regression test implementation
  • Assess code quality and potential issues
  • Check performance and security considerations
  • Provide comprehensive review feedback

Review Summary

This PR effectively solves the cyclic typedef reference issue by implementing a deferred evaluation pattern using z.lazy(). The fix is well-designed and follows existing patterns in the codebase.

Code Quality & Implementation ✅

Factory.ts Changes (packages/orm/src/client/zod/factory.ts:1013-1015)

The implementation is clean and follows the established pattern already used in makeJsonValueSchema():

letfieldSchema: ZodType=isTypeDef(this.schema,def.type)
? z.lazy(()=>this.makeTypeDefSchema(def.type))
: this.makeScalarSchema(def.type);

Strengths:

  • Consistent pattern: Uses the same z.lazy() approach as makeJsonValueSchema() for handling recursive structures
  • Targeted fix: Only wraps typedef references, not all field types, minimizing performance overhead
  • Clear documentation: Well-written comment explains the purpose and mechanism
  • Preserves caching: The @cache() decorator on makeTypeDefSchema still works effectively since z.lazy() defers the recursive call

Test Coverage ✅

Comprehensive test cases (tests/regression/test/issue-2654.test.ts)

The test file covers both major cyclic scenarios:

  1. Mutual recursion (AB): Tests typedef types that reference each other
  2. Self-recursion (Tree with optional Tree[] children): Tests typedef types that reference themselves

Test quality highlights:

  • Uses realistic nested data structures that would trigger the original stack overflow
  • Tests both creation scenarios that previously failed
  • Follows existing test patterns in the codebase
  • Uses appropriate PostgreSQL provider for JSON field testing

Performance Considerations ✅

  • Minimal overhead: z.lazy() only affects typedef field references, not all schema generation
  • Maintains caching: The @cache() decorator continues to work effectively
  • Deferred evaluation: Schema building overhead is moved to validation time, which is appropriate
  • No breaking changes: Existing non-cyclic typedef schemas work exactly as before

Security Considerations ✅

  • No security implications: This is purely a schema generation fix
  • Input validation preserved: All existing Zod validation rules remain intact
  • No new attack vectors: The fix doesn't introduce any new data handling paths

Potential Issues & Suggestions

Minor Considerations:

  1. Error context: When a lazy-evaluated schema fails validation, the error message might be slightly less clear about which nested typedef caused the issue. However, this is a reasonable tradeoff for fixing the stack overflow.

  2. Memory usage: z.lazy() creates additional function closures, but the impact is negligible compared to fixing the infinite recursion bug.

Code Quality Observations:

The fix is minimal, targeted, and follows established patterns. The implementation correctly identifies that the root cause was the @cache() decorator populating the cache after method completion, not during recursive calls.

Overall Assessment

Excellent fix that:

  • ✅ Solves the reported issue without breaking existing functionality
  • ✅ Uses established patterns from the codebase (makeJsonValueSchema)
  • ✅ Includes comprehensive regression test coverage
  • ✅ Has minimal performance impact
  • ✅ Is well-documented and easy to understand

This PR is ready for merge. The implementation is clean, well-tested, and follows TypeScript/Zod best practices.


@ymc9
ymc9 merged commit 7283d0e into devMay 8, 2026
8 checks passed
@ymc9
ymc9 deleted the fix/issue-2654-cyclic-typedef-zod branch May 8, 2026 03:51
Sign up for freeto 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.

Runtime error when creating data for JSON type with cyclic reference

1 participant

@ymc9
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(orm): handle cyclic JSON typedef references in zod factory (#2654) - #2655

Merged
ymc9 merged 1 commit into
devfrom
fix/issue-2654-cyclic-typedef-zod
May 8, 2026
Merged

fix(orm): handle cyclic JSON typedef references in zod factory (#2654)#2655
ymc9 merged 1 commit into
devfrom
fix/issue-2654-cyclic-typedef-zod

Conversation

@ymc9

@ymc9ymc9 commented May 8, 2026

Copy link
Copy Markdown
Member

Summary

  • makeTypeDefSchema previously recursed forever when JSON typedefs referenced each other (or themselves), throwing RangeError: Maximum call stack size exceeded. The @cache() decorator only populates the cache after the method returns, so the recursive call still saw an empty cache.
  • Wrap nested typedef references in z.lazy(() => …) so the inner lookup defers to validation time, by which point the outer build has finished and cached its result. Same pattern already used by makeJsonValueSchema.
  • Added regression test tests/regression/test/issue-2654.test.ts covering cyclic (AB) and self-referencing (Tree { children Tree[]? }) typedefs.

Fixes#2654

Test plan

  • tests/regression/test/issue-2654.test.ts passes
  • tests/regression/test/issue-{493,558,586}.test.ts (existing JSON typedef coverage) still pass
  • tests/e2e/orm/client-api/typed-json-fields.test.ts still passes

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Resolved infinite recursion errors that occurred during schema generation when working with cyclic or self-referential JSON typedefs, enabling applications to properly create, persist, and retrieve complex nested data structures with circular references.
  • Tests

    • Added comprehensive regression tests verifying the correct behavior and persistence of cyclic and self-referential JSON typedef operations.

Cyclic or self-referencing JSON typedefs caused makeTypeDefSchema to
recurse forever — the @cache() decorator only stores the result after
the method returns, so a recursive call back into the same type still
saw an empty cache. Wrap nested typedef references in z.lazy() so the
inner lookup is deferred to validation time, by which point the outer
build has populated the cache.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented May 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1e7f4c78-2c65-449e-bd56-6ba16ec370db

📥 Commits

Reviewing files that changed from the base of the PR and between d1db37c and 17f44cf.

📒 Files selected for processing (2)
  • packages/orm/src/client/zod/factory.ts
  • tests/regression/test/issue-2654.test.ts

📝 Walkthrough

Walkthrough

The PR fixes a stack overflow when creating data for JSON types with cyclic references by wrapping nested typedef field schemas in z.lazy() to defer evaluation. Two regression tests validate the fix for both mutually recursive and self-referential typedef scenarios.

Changes

Cyclic Typedef Schema Generation

Layer / File(s)Summary
Lazy Schema Construction
packages/orm/src/client/zod/factory.ts
Nested typedef field schemas wrapped in z.lazy() instead of eager construction, deferring evaluation to break cycles in recursive typedef definitions.
Regression Tests
tests/regression/test/issue-2654.test.ts
Two integration tests verify cyclic typedef handling: mutually recursive types (A and B referencing each other) and self-referential types (Tree with optional children), both persisting and returning expected shapes.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 Lazy loops no more shall spin,
When typedefs fold and reference in,
A gentle defer breaks the chain,
Cyclic structures now refrain,
From stack overflow's endless pain! 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately describes the main change: fixing cyclic JSON typedef reference handling in the Zod factory, which is the core issue being addressed.
Linked Issues check✅ PassedThe code changes fully address the linked issue #2654 by wrapping nested typedef references in z.lazy() to defer schema construction, preventing infinite recursion for cyclic/self-referential JSON typedefs.
Out of Scope Changes check✅ PassedAll changes are directly scoped to fixing cyclic JSON typedef handling: modifications to makeTypeDefSchema in factory.ts and a focused regression test for the specific issue.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-2654-cyclic-typedef-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

claudeBot commented May 8, 2026

Copy link
Copy Markdown
Contributor

Claude finished @ymc9's task —— View job


Code Review for PR #2655

Todo List:

  • Read and analyze the factory.ts changes
  • Review the regression test implementation
  • Assess code quality and potential issues
  • Check performance and security considerations
  • Provide comprehensive review feedback

Review Summary

This PR effectively solves the cyclic typedef reference issue by implementing a deferred evaluation pattern using z.lazy(). The fix is well-designed and follows existing patterns in the codebase.

Code Quality & Implementation ✅

Factory.ts Changes (packages/orm/src/client/zod/factory.ts:1013-1015)

The implementation is clean and follows the established pattern already used in makeJsonValueSchema():

letfieldSchema: ZodType=isTypeDef(this.schema,def.type)
? z.lazy(()=>this.makeTypeDefSchema(def.type))
: this.makeScalarSchema(def.type);

Strengths:

  • Consistent pattern: Uses the same z.lazy() approach as makeJsonValueSchema() for handling recursive structures
  • Targeted fix: Only wraps typedef references, not all field types, minimizing performance overhead
  • Clear documentation: Well-written comment explains the purpose and mechanism
  • Preserves caching: The @cache() decorator on makeTypeDefSchema still works effectively since z.lazy() defers the recursive call

Test Coverage ✅

Comprehensive test cases (tests/regression/test/issue-2654.test.ts)

The test file covers both major cyclic scenarios:

  1. Mutual recursion (AB): Tests typedef types that reference each other
  2. Self-recursion (Tree with optional Tree[] children): Tests typedef types that reference themselves

Test quality highlights:

  • Uses realistic nested data structures that would trigger the original stack overflow
  • Tests both creation scenarios that previously failed
  • Follows existing test patterns in the codebase
  • Uses appropriate PostgreSQL provider for JSON field testing

Performance Considerations ✅

  • Minimal overhead: z.lazy() only affects typedef field references, not all schema generation
  • Maintains caching: The @cache() decorator continues to work effectively
  • Deferred evaluation: Schema building overhead is moved to validation time, which is appropriate
  • No breaking changes: Existing non-cyclic typedef schemas work exactly as before

Security Considerations ✅

  • No security implications: This is purely a schema generation fix
  • Input validation preserved: All existing Zod validation rules remain intact
  • No new attack vectors: The fix doesn't introduce any new data handling paths

Potential Issues & Suggestions

Minor Considerations:

  1. Error context: When a lazy-evaluated schema fails validation, the error message might be slightly less clear about which nested typedef caused the issue. However, this is a reasonable tradeoff for fixing the stack overflow.

  2. Memory usage: z.lazy() creates additional function closures, but the impact is negligible compared to fixing the infinite recursion bug.

Code Quality Observations:

The fix is minimal, targeted, and follows established patterns. The implementation correctly identifies that the root cause was the @cache() decorator populating the cache after method completion, not during recursive calls.

Overall Assessment

Excellent fix that:

  • ✅ Solves the reported issue without breaking existing functionality
  • ✅ Uses established patterns from the codebase (makeJsonValueSchema)
  • ✅ Includes comprehensive regression test coverage
  • ✅ Has minimal performance impact
  • ✅ Is well-documented and easy to understand

This PR is ready for merge. The implementation is clean, well-tested, and follows TypeScript/Zod best practices.


@ymc9
ymc9 merged commit 7283d0e into devMay 8, 2026
8 checks passed
@ymc9
ymc9 deleted the fix/issue-2654-cyclic-typedef-zod branch May 8, 2026 03:51
Sign up for freeto 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.

Runtime error when creating data for JSON type with cyclic reference

1 participant

@ymc9
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(orm): handle cyclic JSON typedef references in zod factory (#2654) - #2655

Merged
ymc9 merged 1 commit into
devfrom
fix/issue-2654-cyclic-typedef-zod
May 8, 2026
Merged

fix(orm): handle cyclic JSON typedef references in zod factory (#2654)#2655
ymc9 merged 1 commit into
devfrom
fix/issue-2654-cyclic-typedef-zod

Conversation

@ymc9

@ymc9ymc9 commented May 8, 2026

Copy link
Copy Markdown
Member

Summary

  • makeTypeDefSchema previously recursed forever when JSON typedefs referenced each other (or themselves), throwing RangeError: Maximum call stack size exceeded. The @cache() decorator only populates the cache after the method returns, so the recursive call still saw an empty cache.
  • Wrap nested typedef references in z.lazy(() => …) so the inner lookup defers to validation time, by which point the outer build has finished and cached its result. Same pattern already used by makeJsonValueSchema.
  • Added regression test tests/regression/test/issue-2654.test.ts covering cyclic (AB) and self-referencing (Tree { children Tree[]? }) typedefs.

Fixes#2654

Test plan

  • tests/regression/test/issue-2654.test.ts passes
  • tests/regression/test/issue-{493,558,586}.test.ts (existing JSON typedef coverage) still pass
  • tests/e2e/orm/client-api/typed-json-fields.test.ts still passes

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Resolved infinite recursion errors that occurred during schema generation when working with cyclic or self-referential JSON typedefs, enabling applications to properly create, persist, and retrieve complex nested data structures with circular references.
  • Tests

    • Added comprehensive regression tests verifying the correct behavior and persistence of cyclic and self-referential JSON typedef operations.

Cyclic or self-referencing JSON typedefs caused makeTypeDefSchema to
recurse forever — the @cache() decorator only stores the result after
the method returns, so a recursive call back into the same type still
saw an empty cache. Wrap nested typedef references in z.lazy() so the
inner lookup is deferred to validation time, by which point the outer
build has populated the cache.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented May 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1e7f4c78-2c65-449e-bd56-6ba16ec370db

📥 Commits

Reviewing files that changed from the base of the PR and between d1db37c and 17f44cf.

📒 Files selected for processing (2)
  • packages/orm/src/client/zod/factory.ts
  • tests/regression/test/issue-2654.test.ts

📝 Walkthrough

Walkthrough

The PR fixes a stack overflow when creating data for JSON types with cyclic references by wrapping nested typedef field schemas in z.lazy() to defer evaluation. Two regression tests validate the fix for both mutually recursive and self-referential typedef scenarios.

Changes

Cyclic Typedef Schema Generation

Layer / File(s)Summary
Lazy Schema Construction
packages/orm/src/client/zod/factory.ts
Nested typedef field schemas wrapped in z.lazy() instead of eager construction, deferring evaluation to break cycles in recursive typedef definitions.
Regression Tests
tests/regression/test/issue-2654.test.ts
Two integration tests verify cyclic typedef handling: mutually recursive types (A and B referencing each other) and self-referential types (Tree with optional children), both persisting and returning expected shapes.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 Lazy loops no more shall spin,
When typedefs fold and reference in,
A gentle defer breaks the chain,
Cyclic structures now refrain,
From stack overflow's endless pain! 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately describes the main change: fixing cyclic JSON typedef reference handling in the Zod factory, which is the core issue being addressed.
Linked Issues check✅ PassedThe code changes fully address the linked issue #2654 by wrapping nested typedef references in z.lazy() to defer schema construction, preventing infinite recursion for cyclic/self-referential JSON typedefs.
Out of Scope Changes check✅ PassedAll changes are directly scoped to fixing cyclic JSON typedef handling: modifications to makeTypeDefSchema in factory.ts and a focused regression test for the specific issue.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-2654-cyclic-typedef-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

claudeBot commented May 8, 2026

Copy link
Copy Markdown
Contributor

Claude finished @ymc9's task —— View job


Code Review for PR #2655

Todo List:

  • Read and analyze the factory.ts changes
  • Review the regression test implementation
  • Assess code quality and potential issues
  • Check performance and security considerations
  • Provide comprehensive review feedback

Review Summary

This PR effectively solves the cyclic typedef reference issue by implementing a deferred evaluation pattern using z.lazy(). The fix is well-designed and follows existing patterns in the codebase.

Code Quality & Implementation ✅

Factory.ts Changes (packages/orm/src/client/zod/factory.ts:1013-1015)

The implementation is clean and follows the established pattern already used in makeJsonValueSchema():

letfieldSchema: ZodType=isTypeDef(this.schema,def.type)
? z.lazy(()=>this.makeTypeDefSchema(def.type))
: this.makeScalarSchema(def.type);

Strengths:

  • Consistent pattern: Uses the same z.lazy() approach as makeJsonValueSchema() for handling recursive structures
  • Targeted fix: Only wraps typedef references, not all field types, minimizing performance overhead
  • Clear documentation: Well-written comment explains the purpose and mechanism
  • Preserves caching: The @cache() decorator on makeTypeDefSchema still works effectively since z.lazy() defers the recursive call

Test Coverage ✅

Comprehensive test cases (tests/regression/test/issue-2654.test.ts)

The test file covers both major cyclic scenarios:

  1. Mutual recursion (AB): Tests typedef types that reference each other
  2. Self-recursion (Tree with optional Tree[] children): Tests typedef types that reference themselves

Test quality highlights:

  • Uses realistic nested data structures that would trigger the original stack overflow
  • Tests both creation scenarios that previously failed
  • Follows existing test patterns in the codebase
  • Uses appropriate PostgreSQL provider for JSON field testing

Performance Considerations ✅

  • Minimal overhead: z.lazy() only affects typedef field references, not all schema generation
  • Maintains caching: The @cache() decorator continues to work effectively
  • Deferred evaluation: Schema building overhead is moved to validation time, which is appropriate
  • No breaking changes: Existing non-cyclic typedef schemas work exactly as before

Security Considerations ✅

  • No security implications: This is purely a schema generation fix
  • Input validation preserved: All existing Zod validation rules remain intact
  • No new attack vectors: The fix doesn't introduce any new data handling paths

Potential Issues & Suggestions

Minor Considerations:

  1. Error context: When a lazy-evaluated schema fails validation, the error message might be slightly less clear about which nested typedef caused the issue. However, this is a reasonable tradeoff for fixing the stack overflow.

  2. Memory usage: z.lazy() creates additional function closures, but the impact is negligible compared to fixing the infinite recursion bug.

Code Quality Observations:

The fix is minimal, targeted, and follows established patterns. The implementation correctly identifies that the root cause was the @cache() decorator populating the cache after method completion, not during recursive calls.

Overall Assessment

Excellent fix that:

  • ✅ Solves the reported issue without breaking existing functionality
  • ✅ Uses established patterns from the codebase (makeJsonValueSchema)
  • ✅ Includes comprehensive regression test coverage
  • ✅ Has minimal performance impact
  • ✅ Is well-documented and easy to understand

This PR is ready for merge. The implementation is clean, well-tested, and follows TypeScript/Zod best practices.


@ymc9
ymc9 merged commit 7283d0e into devMay 8, 2026
8 checks passed
@ymc9
ymc9 deleted the fix/issue-2654-cyclic-typedef-zod branch May 8, 2026 03:51
Sign up for freeto 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.

Runtime error when creating data for JSON type with cyclic reference

1 participant

@ymc9
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(orm): handle cyclic JSON typedef references in zod factory (#2654) - #2655

Merged
ymc9 merged 1 commit into
devfrom
fix/issue-2654-cyclic-typedef-zod
May 8, 2026
Merged

fix(orm): handle cyclic JSON typedef references in zod factory (#2654)#2655
ymc9 merged 1 commit into
devfrom
fix/issue-2654-cyclic-typedef-zod

Conversation

@ymc9

@ymc9ymc9 commented May 8, 2026

Copy link
Copy Markdown
Member

Summary

  • makeTypeDefSchema previously recursed forever when JSON typedefs referenced each other (or themselves), throwing RangeError: Maximum call stack size exceeded. The @cache() decorator only populates the cache after the method returns, so the recursive call still saw an empty cache.
  • Wrap nested typedef references in z.lazy(() => …) so the inner lookup defers to validation time, by which point the outer build has finished and cached its result. Same pattern already used by makeJsonValueSchema.
  • Added regression test tests/regression/test/issue-2654.test.ts covering cyclic (AB) and self-referencing (Tree { children Tree[]? }) typedefs.

Fixes#2654

Test plan

  • tests/regression/test/issue-2654.test.ts passes
  • tests/regression/test/issue-{493,558,586}.test.ts (existing JSON typedef coverage) still pass
  • tests/e2e/orm/client-api/typed-json-fields.test.ts still passes

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Resolved infinite recursion errors that occurred during schema generation when working with cyclic or self-referential JSON typedefs, enabling applications to properly create, persist, and retrieve complex nested data structures with circular references.
  • Tests

    • Added comprehensive regression tests verifying the correct behavior and persistence of cyclic and self-referential JSON typedef operations.

Cyclic or self-referencing JSON typedefs caused makeTypeDefSchema to
recurse forever — the @cache() decorator only stores the result after
the method returns, so a recursive call back into the same type still
saw an empty cache. Wrap nested typedef references in z.lazy() so the
inner lookup is deferred to validation time, by which point the outer
build has populated the cache.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented May 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1e7f4c78-2c65-449e-bd56-6ba16ec370db

📥 Commits

Reviewing files that changed from the base of the PR and between d1db37c and 17f44cf.

📒 Files selected for processing (2)
  • packages/orm/src/client/zod/factory.ts
  • tests/regression/test/issue-2654.test.ts

📝 Walkthrough

Walkthrough

The PR fixes a stack overflow when creating data for JSON types with cyclic references by wrapping nested typedef field schemas in z.lazy() to defer evaluation. Two regression tests validate the fix for both mutually recursive and self-referential typedef scenarios.

Changes

Cyclic Typedef Schema Generation

Layer / File(s)Summary
Lazy Schema Construction
packages/orm/src/client/zod/factory.ts
Nested typedef field schemas wrapped in z.lazy() instead of eager construction, deferring evaluation to break cycles in recursive typedef definitions.
Regression Tests
tests/regression/test/issue-2654.test.ts
Two integration tests verify cyclic typedef handling: mutually recursive types (A and B referencing each other) and self-referential types (Tree with optional children), both persisting and returning expected shapes.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 Lazy loops no more shall spin,
When typedefs fold and reference in,
A gentle defer breaks the chain,
Cyclic structures now refrain,
From stack overflow's endless pain! 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately describes the main change: fixing cyclic JSON typedef reference handling in the Zod factory, which is the core issue being addressed.
Linked Issues check✅ PassedThe code changes fully address the linked issue #2654 by wrapping nested typedef references in z.lazy() to defer schema construction, preventing infinite recursion for cyclic/self-referential JSON typedefs.
Out of Scope Changes check✅ PassedAll changes are directly scoped to fixing cyclic JSON typedef handling: modifications to makeTypeDefSchema in factory.ts and a focused regression test for the specific issue.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-2654-cyclic-typedef-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

claudeBot commented May 8, 2026

Copy link
Copy Markdown
Contributor

Claude finished @ymc9's task —— View job


Code Review for PR #2655

Todo List:

  • Read and analyze the factory.ts changes
  • Review the regression test implementation
  • Assess code quality and potential issues
  • Check performance and security considerations
  • Provide comprehensive review feedback

Review Summary

This PR effectively solves the cyclic typedef reference issue by implementing a deferred evaluation pattern using z.lazy(). The fix is well-designed and follows existing patterns in the codebase.

Code Quality & Implementation ✅

Factory.ts Changes (packages/orm/src/client/zod/factory.ts:1013-1015)

The implementation is clean and follows the established pattern already used in makeJsonValueSchema():

letfieldSchema: ZodType=isTypeDef(this.schema,def.type)
? z.lazy(()=>this.makeTypeDefSchema(def.type))
: this.makeScalarSchema(def.type);

Strengths:

  • Consistent pattern: Uses the same z.lazy() approach as makeJsonValueSchema() for handling recursive structures
  • Targeted fix: Only wraps typedef references, not all field types, minimizing performance overhead
  • Clear documentation: Well-written comment explains the purpose and mechanism
  • Preserves caching: The @cache() decorator on makeTypeDefSchema still works effectively since z.lazy() defers the recursive call

Test Coverage ✅

Comprehensive test cases (tests/regression/test/issue-2654.test.ts)

The test file covers both major cyclic scenarios:

  1. Mutual recursion (AB): Tests typedef types that reference each other
  2. Self-recursion (Tree with optional Tree[] children): Tests typedef types that reference themselves

Test quality highlights:

  • Uses realistic nested data structures that would trigger the original stack overflow
  • Tests both creation scenarios that previously failed
  • Follows existing test patterns in the codebase
  • Uses appropriate PostgreSQL provider for JSON field testing

Performance Considerations ✅

  • Minimal overhead: z.lazy() only affects typedef field references, not all schema generation
  • Maintains caching: The @cache() decorator continues to work effectively
  • Deferred evaluation: Schema building overhead is moved to validation time, which is appropriate
  • No breaking changes: Existing non-cyclic typedef schemas work exactly as before

Security Considerations ✅

  • No security implications: This is purely a schema generation fix
  • Input validation preserved: All existing Zod validation rules remain intact
  • No new attack vectors: The fix doesn't introduce any new data handling paths

Potential Issues & Suggestions

Minor Considerations:

  1. Error context: When a lazy-evaluated schema fails validation, the error message might be slightly less clear about which nested typedef caused the issue. However, this is a reasonable tradeoff for fixing the stack overflow.

  2. Memory usage: z.lazy() creates additional function closures, but the impact is negligible compared to fixing the infinite recursion bug.

Code Quality Observations:

The fix is minimal, targeted, and follows established patterns. The implementation correctly identifies that the root cause was the @cache() decorator populating the cache after method completion, not during recursive calls.

Overall Assessment

Excellent fix that:

  • ✅ Solves the reported issue without breaking existing functionality
  • ✅ Uses established patterns from the codebase (makeJsonValueSchema)
  • ✅ Includes comprehensive regression test coverage
  • ✅ Has minimal performance impact
  • ✅ Is well-documented and easy to understand

This PR is ready for merge. The implementation is clean, well-tested, and follows TypeScript/Zod best practices.


@ymc9
ymc9 merged commit 7283d0e into devMay 8, 2026
8 checks passed
@ymc9
ymc9 deleted the fix/issue-2654-cyclic-typedef-zod branch May 8, 2026 03:51
Sign up for freeto 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.

Runtime error when creating data for JSON type with cyclic reference

1 participant

@ymc9
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(orm): handle cyclic JSON typedef references in zod factory (#2654) - #2655

Merged
ymc9 merged 1 commit into
devfrom
fix/issue-2654-cyclic-typedef-zod
May 8, 2026
Merged

fix(orm): handle cyclic JSON typedef references in zod factory (#2654)#2655
ymc9 merged 1 commit into
devfrom
fix/issue-2654-cyclic-typedef-zod

Conversation

@ymc9

@ymc9ymc9 commented May 8, 2026

Copy link
Copy Markdown
Member

Summary

  • makeTypeDefSchema previously recursed forever when JSON typedefs referenced each other (or themselves), throwing RangeError: Maximum call stack size exceeded. The @cache() decorator only populates the cache after the method returns, so the recursive call still saw an empty cache.
  • Wrap nested typedef references in z.lazy(() => …) so the inner lookup defers to validation time, by which point the outer build has finished and cached its result. Same pattern already used by makeJsonValueSchema.
  • Added regression test tests/regression/test/issue-2654.test.ts covering cyclic (AB) and self-referencing (Tree { children Tree[]? }) typedefs.

Fixes#2654

Test plan

  • tests/regression/test/issue-2654.test.ts passes
  • tests/regression/test/issue-{493,558,586}.test.ts (existing JSON typedef coverage) still pass
  • tests/e2e/orm/client-api/typed-json-fields.test.ts still passes

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Resolved infinite recursion errors that occurred during schema generation when working with cyclic or self-referential JSON typedefs, enabling applications to properly create, persist, and retrieve complex nested data structures with circular references.
  • Tests

    • Added comprehensive regression tests verifying the correct behavior and persistence of cyclic and self-referential JSON typedef operations.

Cyclic or self-referencing JSON typedefs caused makeTypeDefSchema to
recurse forever — the @cache() decorator only stores the result after
the method returns, so a recursive call back into the same type still
saw an empty cache. Wrap nested typedef references in z.lazy() so the
inner lookup is deferred to validation time, by which point the outer
build has populated the cache.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented May 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1e7f4c78-2c65-449e-bd56-6ba16ec370db

📥 Commits

Reviewing files that changed from the base of the PR and between d1db37c and 17f44cf.

📒 Files selected for processing (2)
  • packages/orm/src/client/zod/factory.ts
  • tests/regression/test/issue-2654.test.ts

📝 Walkthrough

Walkthrough

The PR fixes a stack overflow when creating data for JSON types with cyclic references by wrapping nested typedef field schemas in z.lazy() to defer evaluation. Two regression tests validate the fix for both mutually recursive and self-referential typedef scenarios.

Changes

Cyclic Typedef Schema Generation

Layer / File(s)Summary
Lazy Schema Construction
packages/orm/src/client/zod/factory.ts
Nested typedef field schemas wrapped in z.lazy() instead of eager construction, deferring evaluation to break cycles in recursive typedef definitions.
Regression Tests
tests/regression/test/issue-2654.test.ts
Two integration tests verify cyclic typedef handling: mutually recursive types (A and B referencing each other) and self-referential types (Tree with optional children), both persisting and returning expected shapes.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 Lazy loops no more shall spin,
When typedefs fold and reference in,
A gentle defer breaks the chain,
Cyclic structures now refrain,
From stack overflow's endless pain! 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately describes the main change: fixing cyclic JSON typedef reference handling in the Zod factory, which is the core issue being addressed.
Linked Issues check✅ PassedThe code changes fully address the linked issue #2654 by wrapping nested typedef references in z.lazy() to defer schema construction, preventing infinite recursion for cyclic/self-referential JSON typedefs.
Out of Scope Changes check✅ PassedAll changes are directly scoped to fixing cyclic JSON typedef handling: modifications to makeTypeDefSchema in factory.ts and a focused regression test for the specific issue.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-2654-cyclic-typedef-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

claudeBot commented May 8, 2026

Copy link
Copy Markdown
Contributor

Claude finished @ymc9's task —— View job


Code Review for PR #2655

Todo List:

  • Read and analyze the factory.ts changes
  • Review the regression test implementation
  • Assess code quality and potential issues
  • Check performance and security considerations
  • Provide comprehensive review feedback

Review Summary

This PR effectively solves the cyclic typedef reference issue by implementing a deferred evaluation pattern using z.lazy(). The fix is well-designed and follows existing patterns in the codebase.

Code Quality & Implementation ✅

Factory.ts Changes (packages/orm/src/client/zod/factory.ts:1013-1015)

The implementation is clean and follows the established pattern already used in makeJsonValueSchema():

letfieldSchema: ZodType=isTypeDef(this.schema,def.type)
? z.lazy(()=>this.makeTypeDefSchema(def.type))
: this.makeScalarSchema(def.type);

Strengths:

  • Consistent pattern: Uses the same z.lazy() approach as makeJsonValueSchema() for handling recursive structures
  • Targeted fix: Only wraps typedef references, not all field types, minimizing performance overhead
  • Clear documentation: Well-written comment explains the purpose and mechanism
  • Preserves caching: The @cache() decorator on makeTypeDefSchema still works effectively since z.lazy() defers the recursive call

Test Coverage ✅

Comprehensive test cases (tests/regression/test/issue-2654.test.ts)

The test file covers both major cyclic scenarios:

  1. Mutual recursion (AB): Tests typedef types that reference each other
  2. Self-recursion (Tree with optional Tree[] children): Tests typedef types that reference themselves

Test quality highlights:

  • Uses realistic nested data structures that would trigger the original stack overflow
  • Tests both creation scenarios that previously failed
  • Follows existing test patterns in the codebase
  • Uses appropriate PostgreSQL provider for JSON field testing

Performance Considerations ✅

  • Minimal overhead: z.lazy() only affects typedef field references, not all schema generation
  • Maintains caching: The @cache() decorator continues to work effectively
  • Deferred evaluation: Schema building overhead is moved to validation time, which is appropriate
  • No breaking changes: Existing non-cyclic typedef schemas work exactly as before

Security Considerations ✅

  • No security implications: This is purely a schema generation fix
  • Input validation preserved: All existing Zod validation rules remain intact
  • No new attack vectors: The fix doesn't introduce any new data handling paths

Potential Issues & Suggestions

Minor Considerations:

  1. Error context: When a lazy-evaluated schema fails validation, the error message might be slightly less clear about which nested typedef caused the issue. However, this is a reasonable tradeoff for fixing the stack overflow.

  2. Memory usage: z.lazy() creates additional function closures, but the impact is negligible compared to fixing the infinite recursion bug.

Code Quality Observations:

The fix is minimal, targeted, and follows established patterns. The implementation correctly identifies that the root cause was the @cache() decorator populating the cache after method completion, not during recursive calls.

Overall Assessment

Excellent fix that:

  • ✅ Solves the reported issue without breaking existing functionality
  • ✅ Uses established patterns from the codebase (makeJsonValueSchema)
  • ✅ Includes comprehensive regression test coverage
  • ✅ Has minimal performance impact
  • ✅ Is well-documented and easy to understand

This PR is ready for merge. The implementation is clean, well-tested, and follows TypeScript/Zod best practices.


@ymc9
ymc9 merged commit 7283d0e into devMay 8, 2026
8 checks passed
@ymc9
ymc9 deleted the fix/issue-2654-cyclic-typedef-zod branch May 8, 2026 03:51
Sign up for freeto 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.

Runtime error when creating data for JSON type with cyclic reference

1 participant

@ymc9
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(orm): handle cyclic JSON typedef references in zod factory (#2654) - #2655

Merged
ymc9 merged 1 commit into
devfrom
fix/issue-2654-cyclic-typedef-zod
May 8, 2026
Merged

fix(orm): handle cyclic JSON typedef references in zod factory (#2654)#2655
ymc9 merged 1 commit into
devfrom
fix/issue-2654-cyclic-typedef-zod

Conversation

@ymc9

@ymc9ymc9 commented May 8, 2026

Copy link
Copy Markdown
Member

Summary

  • makeTypeDefSchema previously recursed forever when JSON typedefs referenced each other (or themselves), throwing RangeError: Maximum call stack size exceeded. The @cache() decorator only populates the cache after the method returns, so the recursive call still saw an empty cache.
  • Wrap nested typedef references in z.lazy(() => …) so the inner lookup defers to validation time, by which point the outer build has finished and cached its result. Same pattern already used by makeJsonValueSchema.
  • Added regression test tests/regression/test/issue-2654.test.ts covering cyclic (AB) and self-referencing (Tree { children Tree[]? }) typedefs.

Fixes#2654

Test plan

  • tests/regression/test/issue-2654.test.ts passes
  • tests/regression/test/issue-{493,558,586}.test.ts (existing JSON typedef coverage) still pass
  • tests/e2e/orm/client-api/typed-json-fields.test.ts still passes

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Resolved infinite recursion errors that occurred during schema generation when working with cyclic or self-referential JSON typedefs, enabling applications to properly create, persist, and retrieve complex nested data structures with circular references.
  • Tests

    • Added comprehensive regression tests verifying the correct behavior and persistence of cyclic and self-referential JSON typedef operations.

Cyclic or self-referencing JSON typedefs caused makeTypeDefSchema to
recurse forever — the @cache() decorator only stores the result after
the method returns, so a recursive call back into the same type still
saw an empty cache. Wrap nested typedef references in z.lazy() so the
inner lookup is deferred to validation time, by which point the outer
build has populated the cache.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented May 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1e7f4c78-2c65-449e-bd56-6ba16ec370db

📥 Commits

Reviewing files that changed from the base of the PR and between d1db37c and 17f44cf.

📒 Files selected for processing (2)
  • packages/orm/src/client/zod/factory.ts
  • tests/regression/test/issue-2654.test.ts

📝 Walkthrough

Walkthrough

The PR fixes a stack overflow when creating data for JSON types with cyclic references by wrapping nested typedef field schemas in z.lazy() to defer evaluation. Two regression tests validate the fix for both mutually recursive and self-referential typedef scenarios.

Changes

Cyclic Typedef Schema Generation

Layer / File(s)Summary
Lazy Schema Construction
packages/orm/src/client/zod/factory.ts
Nested typedef field schemas wrapped in z.lazy() instead of eager construction, deferring evaluation to break cycles in recursive typedef definitions.
Regression Tests
tests/regression/test/issue-2654.test.ts
Two integration tests verify cyclic typedef handling: mutually recursive types (A and B referencing each other) and self-referential types (Tree with optional children), both persisting and returning expected shapes.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 Lazy loops no more shall spin,
When typedefs fold and reference in,
A gentle defer breaks the chain,
Cyclic structures now refrain,
From stack overflow's endless pain! 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately describes the main change: fixing cyclic JSON typedef reference handling in the Zod factory, which is the core issue being addressed.
Linked Issues check✅ PassedThe code changes fully address the linked issue #2654 by wrapping nested typedef references in z.lazy() to defer schema construction, preventing infinite recursion for cyclic/self-referential JSON typedefs.
Out of Scope Changes check✅ PassedAll changes are directly scoped to fixing cyclic JSON typedef handling: modifications to makeTypeDefSchema in factory.ts and a focused regression test for the specific issue.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-2654-cyclic-typedef-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

claudeBot commented May 8, 2026

Copy link
Copy Markdown
Contributor

Claude finished @ymc9's task —— View job


Code Review for PR #2655

Todo List:

  • Read and analyze the factory.ts changes
  • Review the regression test implementation
  • Assess code quality and potential issues
  • Check performance and security considerations
  • Provide comprehensive review feedback

Review Summary

This PR effectively solves the cyclic typedef reference issue by implementing a deferred evaluation pattern using z.lazy(). The fix is well-designed and follows existing patterns in the codebase.

Code Quality & Implementation ✅

Factory.ts Changes (packages/orm/src/client/zod/factory.ts:1013-1015)

The implementation is clean and follows the established pattern already used in makeJsonValueSchema():

letfieldSchema: ZodType=isTypeDef(this.schema,def.type)
? z.lazy(()=>this.makeTypeDefSchema(def.type))
: this.makeScalarSchema(def.type);

Strengths:

  • Consistent pattern: Uses the same z.lazy() approach as makeJsonValueSchema() for handling recursive structures
  • Targeted fix: Only wraps typedef references, not all field types, minimizing performance overhead
  • Clear documentation: Well-written comment explains the purpose and mechanism
  • Preserves caching: The @cache() decorator on makeTypeDefSchema still works effectively since z.lazy() defers the recursive call

Test Coverage ✅

Comprehensive test cases (tests/regression/test/issue-2654.test.ts)

The test file covers both major cyclic scenarios:

  1. Mutual recursion (AB): Tests typedef types that reference each other
  2. Self-recursion (Tree with optional Tree[] children): Tests typedef types that reference themselves

Test quality highlights:

  • Uses realistic nested data structures that would trigger the original stack overflow
  • Tests both creation scenarios that previously failed
  • Follows existing test patterns in the codebase
  • Uses appropriate PostgreSQL provider for JSON field testing

Performance Considerations ✅

  • Minimal overhead: z.lazy() only affects typedef field references, not all schema generation
  • Maintains caching: The @cache() decorator continues to work effectively
  • Deferred evaluation: Schema building overhead is moved to validation time, which is appropriate
  • No breaking changes: Existing non-cyclic typedef schemas work exactly as before

Security Considerations ✅

  • No security implications: This is purely a schema generation fix
  • Input validation preserved: All existing Zod validation rules remain intact
  • No new attack vectors: The fix doesn't introduce any new data handling paths

Potential Issues & Suggestions

Minor Considerations:

  1. Error context: When a lazy-evaluated schema fails validation, the error message might be slightly less clear about which nested typedef caused the issue. However, this is a reasonable tradeoff for fixing the stack overflow.

  2. Memory usage: z.lazy() creates additional function closures, but the impact is negligible compared to fixing the infinite recursion bug.

Code Quality Observations:

The fix is minimal, targeted, and follows established patterns. The implementation correctly identifies that the root cause was the @cache() decorator populating the cache after method completion, not during recursive calls.

Overall Assessment

Excellent fix that:

  • ✅ Solves the reported issue without breaking existing functionality
  • ✅ Uses established patterns from the codebase (makeJsonValueSchema)
  • ✅ Includes comprehensive regression test coverage
  • ✅ Has minimal performance impact
  • ✅ Is well-documented and easy to understand

This PR is ready for merge. The implementation is clean, well-tested, and follows TypeScript/Zod best practices.


@ymc9
ymc9 merged commit 7283d0e into devMay 8, 2026
8 checks passed
@ymc9
ymc9 deleted the fix/issue-2654-cyclic-typedef-zod branch May 8, 2026 03:51
Sign up for freeto 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.

Runtime error when creating data for JSON type with cyclic reference

1 participant

@ymc9
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(orm): handle cyclic JSON typedef references in zod factory (#2654) - #2655

Merged
ymc9 merged 1 commit into
devfrom
fix/issue-2654-cyclic-typedef-zod
May 8, 2026
Merged

fix(orm): handle cyclic JSON typedef references in zod factory (#2654)#2655
ymc9 merged 1 commit into
devfrom
fix/issue-2654-cyclic-typedef-zod

Conversation

@ymc9

@ymc9ymc9 commented May 8, 2026

Copy link
Copy Markdown
Member

Summary

  • makeTypeDefSchema previously recursed forever when JSON typedefs referenced each other (or themselves), throwing RangeError: Maximum call stack size exceeded. The @cache() decorator only populates the cache after the method returns, so the recursive call still saw an empty cache.
  • Wrap nested typedef references in z.lazy(() => …) so the inner lookup defers to validation time, by which point the outer build has finished and cached its result. Same pattern already used by makeJsonValueSchema.
  • Added regression test tests/regression/test/issue-2654.test.ts covering cyclic (AB) and self-referencing (Tree { children Tree[]? }) typedefs.

Fixes#2654

Test plan

  • tests/regression/test/issue-2654.test.ts passes
  • tests/regression/test/issue-{493,558,586}.test.ts (existing JSON typedef coverage) still pass
  • tests/e2e/orm/client-api/typed-json-fields.test.ts still passes

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Resolved infinite recursion errors that occurred during schema generation when working with cyclic or self-referential JSON typedefs, enabling applications to properly create, persist, and retrieve complex nested data structures with circular references.
  • Tests

    • Added comprehensive regression tests verifying the correct behavior and persistence of cyclic and self-referential JSON typedef operations.

Cyclic or self-referencing JSON typedefs caused makeTypeDefSchema to
recurse forever — the @cache() decorator only stores the result after
the method returns, so a recursive call back into the same type still
saw an empty cache. Wrap nested typedef references in z.lazy() so the
inner lookup is deferred to validation time, by which point the outer
build has populated the cache.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented May 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1e7f4c78-2c65-449e-bd56-6ba16ec370db

📥 Commits

Reviewing files that changed from the base of the PR and between d1db37c and 17f44cf.

📒 Files selected for processing (2)
  • packages/orm/src/client/zod/factory.ts
  • tests/regression/test/issue-2654.test.ts

📝 Walkthrough

Walkthrough

The PR fixes a stack overflow when creating data for JSON types with cyclic references by wrapping nested typedef field schemas in z.lazy() to defer evaluation. Two regression tests validate the fix for both mutually recursive and self-referential typedef scenarios.

Changes

Cyclic Typedef Schema Generation

Layer / File(s)Summary
Lazy Schema Construction
packages/orm/src/client/zod/factory.ts
Nested typedef field schemas wrapped in z.lazy() instead of eager construction, deferring evaluation to break cycles in recursive typedef definitions.
Regression Tests
tests/regression/test/issue-2654.test.ts
Two integration tests verify cyclic typedef handling: mutually recursive types (A and B referencing each other) and self-referential types (Tree with optional children), both persisting and returning expected shapes.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 Lazy loops no more shall spin,
When typedefs fold and reference in,
A gentle defer breaks the chain,
Cyclic structures now refrain,
From stack overflow's endless pain! 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately describes the main change: fixing cyclic JSON typedef reference handling in the Zod factory, which is the core issue being addressed.
Linked Issues check✅ PassedThe code changes fully address the linked issue #2654 by wrapping nested typedef references in z.lazy() to defer schema construction, preventing infinite recursion for cyclic/self-referential JSON typedefs.
Out of Scope Changes check✅ PassedAll changes are directly scoped to fixing cyclic JSON typedef handling: modifications to makeTypeDefSchema in factory.ts and a focused regression test for the specific issue.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-2654-cyclic-typedef-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

claudeBot commented May 8, 2026

Copy link
Copy Markdown
Contributor

Claude finished @ymc9's task —— View job


Code Review for PR #2655

Todo List:

  • Read and analyze the factory.ts changes
  • Review the regression test implementation
  • Assess code quality and potential issues
  • Check performance and security considerations
  • Provide comprehensive review feedback

Review Summary

This PR effectively solves the cyclic typedef reference issue by implementing a deferred evaluation pattern using z.lazy(). The fix is well-designed and follows existing patterns in the codebase.

Code Quality & Implementation ✅

Factory.ts Changes (packages/orm/src/client/zod/factory.ts:1013-1015)

The implementation is clean and follows the established pattern already used in makeJsonValueSchema():

letfieldSchema: ZodType=isTypeDef(this.schema,def.type)
? z.lazy(()=>this.makeTypeDefSchema(def.type))
: this.makeScalarSchema(def.type);

Strengths:

  • Consistent pattern: Uses the same z.lazy() approach as makeJsonValueSchema() for handling recursive structures
  • Targeted fix: Only wraps typedef references, not all field types, minimizing performance overhead
  • Clear documentation: Well-written comment explains the purpose and mechanism
  • Preserves caching: The @cache() decorator on makeTypeDefSchema still works effectively since z.lazy() defers the recursive call

Test Coverage ✅

Comprehensive test cases (tests/regression/test/issue-2654.test.ts)

The test file covers both major cyclic scenarios:

  1. Mutual recursion (AB): Tests typedef types that reference each other
  2. Self-recursion (Tree with optional Tree[] children): Tests typedef types that reference themselves

Test quality highlights:

  • Uses realistic nested data structures that would trigger the original stack overflow
  • Tests both creation scenarios that previously failed
  • Follows existing test patterns in the codebase
  • Uses appropriate PostgreSQL provider for JSON field testing

Performance Considerations ✅

  • Minimal overhead: z.lazy() only affects typedef field references, not all schema generation
  • Maintains caching: The @cache() decorator continues to work effectively
  • Deferred evaluation: Schema building overhead is moved to validation time, which is appropriate
  • No breaking changes: Existing non-cyclic typedef schemas work exactly as before

Security Considerations ✅

  • No security implications: This is purely a schema generation fix
  • Input validation preserved: All existing Zod validation rules remain intact
  • No new attack vectors: The fix doesn't introduce any new data handling paths

Potential Issues & Suggestions

Minor Considerations:

  1. Error context: When a lazy-evaluated schema fails validation, the error message might be slightly less clear about which nested typedef caused the issue. However, this is a reasonable tradeoff for fixing the stack overflow.

  2. Memory usage: z.lazy() creates additional function closures, but the impact is negligible compared to fixing the infinite recursion bug.

Code Quality Observations:

The fix is minimal, targeted, and follows established patterns. The implementation correctly identifies that the root cause was the @cache() decorator populating the cache after method completion, not during recursive calls.

Overall Assessment

Excellent fix that:

  • ✅ Solves the reported issue without breaking existing functionality
  • ✅ Uses established patterns from the codebase (makeJsonValueSchema)
  • ✅ Includes comprehensive regression test coverage
  • ✅ Has minimal performance impact
  • ✅ Is well-documented and easy to understand

This PR is ready for merge. The implementation is clean, well-tested, and follows TypeScript/Zod best practices.


@ymc9
ymc9 merged commit 7283d0e into devMay 8, 2026
8 checks passed
@ymc9
ymc9 deleted the fix/issue-2654-cyclic-typedef-zod branch May 8, 2026 03:51
Sign up for freeto 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.

Runtime error when creating data for JSON type with cyclic reference

1 participant

@ymc9
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(orm): handle cyclic JSON typedef references in zod factory (#2654) - #2655

Merged
ymc9 merged 1 commit into
devfrom
fix/issue-2654-cyclic-typedef-zod
May 8, 2026
Merged

fix(orm): handle cyclic JSON typedef references in zod factory (#2654)#2655
ymc9 merged 1 commit into
devfrom
fix/issue-2654-cyclic-typedef-zod

Conversation

@ymc9

@ymc9ymc9 commented May 8, 2026

Copy link
Copy Markdown
Member

Summary

  • makeTypeDefSchema previously recursed forever when JSON typedefs referenced each other (or themselves), throwing RangeError: Maximum call stack size exceeded. The @cache() decorator only populates the cache after the method returns, so the recursive call still saw an empty cache.
  • Wrap nested typedef references in z.lazy(() => …) so the inner lookup defers to validation time, by which point the outer build has finished and cached its result. Same pattern already used by makeJsonValueSchema.
  • Added regression test tests/regression/test/issue-2654.test.ts covering cyclic (AB) and self-referencing (Tree { children Tree[]? }) typedefs.

Fixes#2654

Test plan

  • tests/regression/test/issue-2654.test.ts passes
  • tests/regression/test/issue-{493,558,586}.test.ts (existing JSON typedef coverage) still pass
  • tests/e2e/orm/client-api/typed-json-fields.test.ts still passes

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Resolved infinite recursion errors that occurred during schema generation when working with cyclic or self-referential JSON typedefs, enabling applications to properly create, persist, and retrieve complex nested data structures with circular references.
  • Tests

    • Added comprehensive regression tests verifying the correct behavior and persistence of cyclic and self-referential JSON typedef operations.

Cyclic or self-referencing JSON typedefs caused makeTypeDefSchema to
recurse forever — the @cache() decorator only stores the result after
the method returns, so a recursive call back into the same type still
saw an empty cache. Wrap nested typedef references in z.lazy() so the
inner lookup is deferred to validation time, by which point the outer
build has populated the cache.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitaiBot commented May 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1e7f4c78-2c65-449e-bd56-6ba16ec370db

📥 Commits

Reviewing files that changed from the base of the PR and between d1db37c and 17f44cf.

📒 Files selected for processing (2)
  • packages/orm/src/client/zod/factory.ts
  • tests/regression/test/issue-2654.test.ts

📝 Walkthrough

Walkthrough

The PR fixes a stack overflow when creating data for JSON types with cyclic references by wrapping nested typedef field schemas in z.lazy() to defer evaluation. Two regression tests validate the fix for both mutually recursive and self-referential typedef scenarios.

Changes

Cyclic Typedef Schema Generation

Layer / File(s)Summary
Lazy Schema Construction
packages/orm/src/client/zod/factory.ts
Nested typedef field schemas wrapped in z.lazy() instead of eager construction, deferring evaluation to break cycles in recursive typedef definitions.
Regression Tests
tests/regression/test/issue-2654.test.ts
Two integration tests verify cyclic typedef handling: mutually recursive types (A and B referencing each other) and self-referential types (Tree with optional children), both persisting and returning expected shapes.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 Lazy loops no more shall spin,
When typedefs fold and reference in,
A gentle defer breaks the chain,
Cyclic structures now refrain,
From stack overflow's endless pain! 🌿

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title accurately describes the main change: fixing cyclic JSON typedef reference handling in the Zod factory, which is the core issue being addressed.
Linked Issues check✅ PassedThe code changes fully address the linked issue #2654 by wrapping nested typedef references in z.lazy() to defer schema construction, preventing infinite recursion for cyclic/self-referential JSON typedefs.
Out of Scope Changes check✅ PassedAll changes are directly scoped to fixing cyclic JSON typedef handling: modifications to makeTypeDefSchema in factory.ts and a focused regression test for the specific issue.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-2654-cyclic-typedef-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

claudeBot commented May 8, 2026

Copy link
Copy Markdown
Contributor

Claude finished @ymc9's task —— View job


Code Review for PR #2655

Todo List:

  • Read and analyze the factory.ts changes
  • Review the regression test implementation
  • Assess code quality and potential issues
  • Check performance and security considerations
  • Provide comprehensive review feedback

Review Summary

This PR effectively solves the cyclic typedef reference issue by implementing a deferred evaluation pattern using z.lazy(). The fix is well-designed and follows existing patterns in the codebase.

Code Quality & Implementation ✅

Factory.ts Changes (packages/orm/src/client/zod/factory.ts:1013-1015)

The implementation is clean and follows the established pattern already used in makeJsonValueSchema():

letfieldSchema: ZodType=isTypeDef(this.schema,def.type)
? z.lazy(()=>this.makeTypeDefSchema(def.type))
: this.makeScalarSchema(def.type);

Strengths:

  • Consistent pattern: Uses the same z.lazy() approach as makeJsonValueSchema() for handling recursive structures
  • Targeted fix: Only wraps typedef references, not all field types, minimizing performance overhead
  • Clear documentation: Well-written comment explains the purpose and mechanism
  • Preserves caching: The @cache() decorator on makeTypeDefSchema still works effectively since z.lazy() defers the recursive call

Test Coverage ✅

Comprehensive test cases (tests/regression/test/issue-2654.test.ts)

The test file covers both major cyclic scenarios:

  1. Mutual recursion (AB): Tests typedef types that reference each other
  2. Self-recursion (Tree with optional Tree[] children): Tests typedef types that reference themselves

Test quality highlights:

  • Uses realistic nested data structures that would trigger the original stack overflow
  • Tests both creation scenarios that previously failed
  • Follows existing test patterns in the codebase
  • Uses appropriate PostgreSQL provider for JSON field testing

Performance Considerations ✅

  • Minimal overhead: z.lazy() only affects typedef field references, not all schema generation
  • Maintains caching: The @cache() decorator continues to work effectively
  • Deferred evaluation: Schema building overhead is moved to validation time, which is appropriate
  • No breaking changes: Existing non-cyclic typedef schemas work exactly as before

Security Considerations ✅

  • No security implications: This is purely a schema generation fix
  • Input validation preserved: All existing Zod validation rules remain intact
  • No new attack vectors: The fix doesn't introduce any new data handling paths

Potential Issues & Suggestions

Minor Considerations:

  1. Error context: When a lazy-evaluated schema fails validation, the error message might be slightly less clear about which nested typedef caused the issue. However, this is a reasonable tradeoff for fixing the stack overflow.

  2. Memory usage: z.lazy() creates additional function closures, but the impact is negligible compared to fixing the infinite recursion bug.

Code Quality Observations:

The fix is minimal, targeted, and follows established patterns. The implementation correctly identifies that the root cause was the @cache() decorator populating the cache after method completion, not during recursive calls.

Overall Assessment

Excellent fix that:

  • ✅ Solves the reported issue without breaking existing functionality
  • ✅ Uses established patterns from the codebase (makeJsonValueSchema)
  • ✅ Includes comprehensive regression test coverage
  • ✅ Has minimal performance impact
  • ✅ Is well-documented and easy to understand

This PR is ready for merge. The implementation is clean, well-tested, and follows TypeScript/Zod best practices.


@ymc9
ymc9 merged commit 7283d0e into devMay 8, 2026
8 checks passed
@ymc9
ymc9 deleted the fix/issue-2654-cyclic-typedef-zod branch May 8, 2026 03:51
Sign up for freeto 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.

Runtime error when creating data for JSON type with cyclic reference

1 participant

@ymc9