Skip to content

feat(zod): experimental support zod v4#462

Merged
dinwwwh merged 13 commits intomainfrom
feat/zod/zod4
May 5, 2025
Merged

feat(zod): experimental support zod v4#462
dinwwwh merged 13 commits intomainfrom
feat/zod/zod4

Conversation

@dinwwwh
Copy link
Copy Markdown
Member

@dinwwwh dinwwwh commented May 3, 2025

Closes: https://github.com/unnoq/orpc/issues/428

Summary by CodeRabbit

  • New Features

    • Added experimental support for Zod v4, including a new JSON Schema converter and smart coercion plugin.
    • Introduced advanced JSON Schema customization registries for Zod v4.
    • Dual support for Zod v3 and v4 now available with explicit exports and dependency declarations.
  • Documentation

    • Enhanced OpenAPI and plugin documentation to clearly distinguish Zod v3 and v4 usage, features, and import instructions.
  • Tests

    • Added comprehensive test suites for Zod v4 covering schema conversion, smart coercion, and registry integration.
  • Chores

    • Updated package configuration to support Zod v4 and its dependencies.
    • Added new exports and peer dependencies for Zod v4 support.

@vercel
Copy link
Copy Markdown

vercel Bot commented May 3, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
orpc ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 5, 2025 9:20am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 3, 2025

Warning

Rate limit exceeded

@unnoq has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 8 minutes and 35 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between d00b442 and aa7ac21.

📒 Files selected for processing (5)
  • packages/zod/src/zod4/coercer.combination.test.ts (1 hunks)
  • packages/zod/src/zod4/coercer.native.test.ts (1 hunks)
  • packages/zod/src/zod4/coercer.rest.test.ts (1 hunks)
  • packages/zod/src/zod4/coercer.structure.test.ts (1 hunks)
  • packages/zod/tests/shared.ts (1 hunks)

Walkthrough

This update introduces comprehensive support for Zod version 4 (currently in beta) across the codebase. It adds a new zod4 submodule with dedicated coercion and JSON schema conversion logic, as well as registries for customizing schema output. The package configuration is updated to include Zod 4 and its dependencies. Extensive test suites are added to validate coercion and conversion behaviors for Zod 4 schemas. Documentation is enhanced to clarify usage differences between Zod v3 and v4, including new import paths and plugin usage. No existing exported APIs are removed; new exports are introduced for Zod 4 support.

Changes

File(s) Change Summary
apps/content/docs/openapi/openapi-specification.md, apps/content/docs/openapi/plugins/zod-smart-coercion.md Documentation updated to clarify Zod v3 vs v4 usage, new import paths, metadata support, coercion plugin limitations, and registry usage for JSON Schema customization.
packages/zod/package.json Added Zod v4 submodule exports, updated peer and dev dependencies to include Zod v4, and added alias for Zod 4 beta.
packages/openapi/src/schema.ts Export updated to include JSONSchemaContentEncoding alongside JSONSchemaFormat.
packages/zod/src/zod4/index.ts New index file re-exporting coercer, converter, and registries modules for Zod 4.
packages/zod/src/zod4/coercer.ts Added experimental_ZodSmartCoercionPlugin class implementing input coercion for Zod 4 schemas.
packages/zod/src/zod4/converter.ts Added experimental_ZodToJsonSchemaConverter class for converting Zod 4 schemas to JSON Schema, with options and interceptors.
packages/zod/src/zod4/registries.ts Added three registries for customizing JSON Schema generation for Zod 4: general, input, and output registries.
packages/zod/src/zod4/*.test.ts Added extensive test suites for coercion and conversion behaviors: native types, combinations, structures, meta, rest cases, number/string handling, processed/lazy schemas, and plugin behavior.
packages/zod/tests/shared.ts Added shared test utilities for schema conversion and smart coercion test cases.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Plugin as ZodSmartCoercionPlugin (v4)
    participant ZodSchema
    User->>Plugin: Provide input and Zod 4 schema
    Plugin->>Plugin: Recursively coerce input based on schema type
    Plugin->>ZodSchema: Check schema type and constraints
    Plugin->>User: Return coerced input
Loading
sequenceDiagram
    participant User
    participant Converter as ZodToJsonSchemaConverter (v4)
    participant ZodSchema
    User->>Converter: Request JSON Schema for Zod 4 schema
    Converter->>Converter: Recursively convert schema nodes
    Converter->>ZodSchema: Read schema properties and metadata
    Converter->>User: Return JSON Schema object
Loading

Assessment against linked issues

Objective Addressed Explanation
Add support for Zod 4, including new peer dependencies, submodule exports, and API integration (#428)
Provide separate documentation and usage guidance for Zod v3 and v4 (#428)
Implement coercion and JSON Schema conversion for Zod 4 schemas (#428)
Add test coverage for Zod 4 coercion and conversion logic (#428)

Possibly related PRs

  • feat(zod): rewrite @orpc/zod #206: Broad rewrite of the @orpc/zod package updating dependencies and restructuring tests and converter logic for Zod v3; related as foundational work extended by this PR for Zod v4 support.

Poem

In fields of code where Zod now grows,
A rabbit hops where the new wind blows.
With v4 in tow, the schemas bloom,
Coercion and conversion chase away gloom.
Registries sparkle, tests abound—
In this warren, robust support is found!
🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

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

Support

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

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

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

@codecov
Copy link
Copy Markdown

codecov Bot commented May 3, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

📢 Thoughts on this report? Let us know!

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 3, 2025

More templates

@orpc/arktype

npm i https://pkg.pr.new/@orpc/arktype@462

@orpc/client

npm i https://pkg.pr.new/@orpc/client@462

@orpc/contract

npm i https://pkg.pr.new/@orpc/contract@462

@orpc/openapi

npm i https://pkg.pr.new/@orpc/openapi@462

@orpc/openapi-client

npm i https://pkg.pr.new/@orpc/openapi-client@462

@orpc/react

npm i https://pkg.pr.new/@orpc/react@462

@orpc/server

npm i https://pkg.pr.new/@orpc/server@462

@orpc/react-query

npm i https://pkg.pr.new/@orpc/react-query@462

@orpc/shared

npm i https://pkg.pr.new/@orpc/shared@462

@orpc/solid-query

npm i https://pkg.pr.new/@orpc/solid-query@462

@orpc/standard-server

npm i https://pkg.pr.new/@orpc/standard-server@462

@orpc/standard-server-fetch

npm i https://pkg.pr.new/@orpc/standard-server-fetch@462

@orpc/standard-server-node

npm i https://pkg.pr.new/@orpc/standard-server-node@462

@orpc/svelte-query

npm i https://pkg.pr.new/@orpc/svelte-query@462

@orpc/valibot

npm i https://pkg.pr.new/@orpc/valibot@462

@orpc/vue-colada

npm i https://pkg.pr.new/@orpc/vue-colada@462

@orpc/vue-query

npm i https://pkg.pr.new/@orpc/vue-query@462

@orpc/zod

npm i https://pkg.pr.new/@orpc/zod@462

commit: aa7ac21

@dinwwwh dinwwwh marked this pull request as ready for review May 5, 2025 08:57
Copy link
Copy Markdown

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

🧹 Nitpick comments (15)
packages/zod/src/zod4/converter.rest.test.ts (1)

4-20: Add comments explaining conversion expectations

The test structure is sound, but it would be helpful to add explanatory comments for why these particular schemas are expected to convert to { not: {} } JSON schemas, as this conversion pattern isn't immediately intuitive.

 testSchemaConverter([
   {
     name: 'z.symbol()',
     schema: z.symbol(),
     input: [true, { not: {} }],
+    // Symbols cannot be represented in JSON Schema, so we use a negation schema
   },
   {
     name: 'z.promise(z.string())',
     schema: z.promise(z.string()),
     input: [true, { not: {} }],
+    // Promises cannot be represented in JSON Schema, so we use a negation schema
   },
   {
     name: 'z.custom(() => false)',
     schema: z.custom(() => false),
     input: [true, { not: {} }],
+    // Custom validators cannot be represented in JSON Schema, so we use a negation schema
   },
 ])
apps/content/docs/openapi/plugins/zod-smart-coercion.md (1)

44-47: Clear import examples for different Zod versions

The import examples clearly show how to use the plugin with both Zod v3 and Zod v4. The use of the experimental_ prefix appropriately indicates this is not yet a stable API.

Consider adding a note about potential API changes to set user expectations about stability:

import { ZodSmartCoercionPlugin } from '@orpc/zod' // <-- zod v3
import {
  experimental_ZodSmartCoercionPlugin as ZodSmartCoercionPlugin
} from '@orpc/zod/zod4' // <-- zod v4
+
+// Note: The Zod v4 API is experimental and may change in future releases
packages/zod/src/zod4/coercer.rest.test.ts (1)

1-17: Union type coercion tests look good.

The tests verify that union schemas correctly maintain string values when they could be coerced to other types. This is important behavior for ensuring that smart coercion doesn't overaggressively convert values that could be valid in multiple formats.

However, consider adding more test cases to cover:

  1. Unions with more than two types
  2. Unions with complex types (objects, arrays)
  3. Edge cases where coercion might be ambiguous
packages/zod/src/zod4/converter.combination.test.ts (2)

30-34: Remove duplicate test case.

There's a duplicate test case for intersection(z.string().optional(), z.number().optional()) with identical schema and expected output.

-  {
-    name: 'intersection(z.string().optional(), z.number().optional())',
-    schema: z.intersection(z.string().optional(), z.number().optional()),
-    input: [false, { allOf: [{ type: 'string' }, { type: 'number' }] }],
-  },

1-29: Expand union and intersection test coverage.

The tests effectively verify basic union and intersection schema conversion to JSON Schema. Consider adding:

  1. Tests with more complex schemas (nested objects, arrays)
  2. Discriminated unions
  3. Unions with literals and enums
  4. Branded types in intersections

These additions would ensure comprehensive coverage for combination types.

packages/zod/src/zod4/coercer.structure.test.ts (1)

1-179: Strong test coverage for structure types.

The test suite provides comprehensive coverage for array, tuple, set, object, record, and map types, including handling of undefined inputs, optional schemas, and type coercion scenarios.

Consider adding tests for:

  1. Nested object structures
  2. Objects with optional fields
  3. Deep arrays with mixed types
  4. Records with more specific key types
packages/zod/src/zod4/converter.string.test.ts (2)

66-69: Remove duplicate test case for uuid.

There's a duplicate test case for z.uuid() - the same test appears earlier at lines 36-39.

-  {
-    name: 'uuid',
-    schema: z.uuid(),
-    input: [true, { type: 'string', format: 'uuid' }],
-  },

1-146: Comprehensive string schema test coverage.

The test suite provides excellent coverage for string schemas, including length constraints, regex patterns, and specialized formats. It tests all the string validation methods and ISO formats.

Consider adding tests for:

  1. Custom string validations via .refine()
  2. Locale-specific string validations
  3. More complex regex patterns
  4. String transformations with .transform()
packages/zod/src/zod4/coercer.test.ts (1)

7-8: Consider using a more type-safe approach for options initialization

Instead of using type assertion with as any, consider creating a mock object that matches the expected interface structure. This would make the test more robust against future interface changes.

- const options = {} as any
+ const options = {
+   clientInterceptors: [] as Array<any>
+ }
packages/zod/src/zod4/converter.processed.test.ts (1)

1-1: Consider using consistent import style for zod4

This file uses the default import style (import z from 'zod4') while other files in the package use the namespace import style (import * as z from 'zod4'). Consider standardizing the import approach across all files to maintain consistency.

- import z from 'zod4'
+ import * as z from 'zod4'
packages/zod/src/zod4/converter.number.test.ts (1)

4-46: Well-structured test suite covering numeric schema conversions.

This test suite provides comprehensive coverage of numeric types in Zod v4, including basic numbers, integers, bounded numbers, multiplicity constraints, exclusive bounds, bigints, and nan values. Each test case properly defines the expected JSON Schema representation according to specification.

Consider adding test cases for additional numeric constraints that might be available in Zod v4, such as:

+ {
+   name: 'number.nonnegative()',
+   schema: z.number().nonnegative(),
+   input: [true, { type: 'number', minimum: 0 }],
+ },
+ {
+   name: 'number.nonpositive()',
+   schema: z.number().nonpositive(),
+   input: [true, { type: 'number', maximum: 0 }],
+ },
+ {
+   name: 'number.safe()',
+   schema: z.number().safe(),
+   input: [true, { type: 'number', maximum: 9007199254740991, minimum: -9007199254740991 }],
+ },
packages/zod/src/zod4/registries.ts (2)

24-41: Documentation error in JSON_SCHEMA_INPUT_REGISTRY example

There's a mistake in the example code - it references JSON_SCHEMA_REGISTRY instead of JSON_SCHEMA_INPUT_REGISTRY in the example.

-JSON_SCHEMA_REGISTRY.add(user, {
+JSON_SCHEMA_INPUT_REGISTRY.add(user, {
   examples: [{ name: 'John', age: "20" }],
 })

43-60: Incorrect documentation for JSON_SCHEMA_OUTPUT_REGISTRY

The JSDoc comment incorrectly states this registry is "only useful for .input" but it should be "only useful for .output", as this is the OUTPUT_REGISTRY.

-* Zod registry for customizing generated JSON schema, only useful for .input
+* Zod registry for customizing generated JSON schema, only useful for .output
packages/zod/src/zod4/coercer.ts (1)

316-319: "1.0" is not coerced although it is a valid number

Number("1.0") gives 1, whose string representation is "1", therefore
the current equality check rejects it. If this is unintended, compare
against Number(value).toString() === String(parseFloat(value))
or use a regular expression to allow numeric postfixes.

packages/zod/tests/shared.ts (1)

34-56: Leaking interceptor across test cases can cause false positives

options is a shared mutable object; plugin.init(options) appends an
interceptor once, but if testSchemaSmartCoercion is invoked multiple times
in different test files the interceptor list grows unbounded and the first
interceptor will run N times.

Create a fresh options object per invocation:

-  const plugin = new ZodSmartCoercionPlugin()
-  const options = {} as any
-  plugin.init(options)
+  const plugin   = new ZodSmartCoercionPlugin()
+  const options  = {} as any
+  plugin.init(options)

to

+  const coerce = (schema: AnySchema, input: unknown) => {
+    const opts = {} as any         // fresh each call
+    plugin.init(opts)
+    ...
+  }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 95b67ec and aa2f202.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (23)
  • apps/content/docs/openapi/openapi-specification.md (3 hunks)
  • apps/content/docs/openapi/plugins/zod-smart-coercion.md (2 hunks)
  • packages/openapi/src/schema.ts (1 hunks)
  • packages/zod/package.json (3 hunks)
  • packages/zod/src/zod4/coercer.combination.test.ts (1 hunks)
  • packages/zod/src/zod4/coercer.native.test.ts (1 hunks)
  • packages/zod/src/zod4/coercer.rest.test.ts (1 hunks)
  • packages/zod/src/zod4/coercer.structure.test.ts (1 hunks)
  • packages/zod/src/zod4/coercer.test.ts (1 hunks)
  • packages/zod/src/zod4/coercer.ts (1 hunks)
  • packages/zod/src/zod4/converter.combination.test.ts (1 hunks)
  • packages/zod/src/zod4/converter.meta.test.ts (1 hunks)
  • packages/zod/src/zod4/converter.native.test.ts (1 hunks)
  • packages/zod/src/zod4/converter.number.test.ts (1 hunks)
  • packages/zod/src/zod4/converter.processed.test.ts (1 hunks)
  • packages/zod/src/zod4/converter.rest.test.ts (1 hunks)
  • packages/zod/src/zod4/converter.string.test.ts (1 hunks)
  • packages/zod/src/zod4/converter.structure.test.ts (1 hunks)
  • packages/zod/src/zod4/converter.test.ts (1 hunks)
  • packages/zod/src/zod4/converter.ts (1 hunks)
  • packages/zod/src/zod4/index.ts (1 hunks)
  • packages/zod/src/zod4/registries.ts (1 hunks)
  • packages/zod/tests/shared.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (9)
packages/zod/src/zod4/converter.string.test.ts (1)
packages/zod/tests/shared.ts (1)
  • testSchemaConverter (15-25)
packages/zod/src/zod4/converter.processed.test.ts (1)
packages/zod/tests/shared.ts (1)
  • testSchemaConverter (15-25)
packages/zod/src/zod4/converter.number.test.ts (1)
packages/zod/tests/shared.ts (1)
  • testSchemaConverter (15-25)
packages/zod/src/zod4/converter.combination.test.ts (1)
packages/zod/tests/shared.ts (1)
  • testSchemaConverter (15-25)
packages/zod/src/zod4/converter.rest.test.ts (1)
packages/zod/tests/shared.ts (1)
  • testSchemaConverter (15-25)
packages/zod/src/zod4/converter.native.test.ts (1)
packages/zod/tests/shared.ts (1)
  • testSchemaConverter (15-25)
packages/zod/src/zod4/converter.structure.test.ts (1)
packages/zod/tests/shared.ts (1)
  • testSchemaConverter (15-25)
packages/zod/src/zod4/coercer.native.test.ts (1)
packages/zod/tests/shared.ts (1)
  • testSchemaSmartCoercion (34-60)
packages/zod/src/zod4/converter.meta.test.ts (1)
packages/zod/tests/shared.ts (1)
  • testSchemaConverter (15-25)
🪛 GitHub Check: test
packages/zod/src/zod4/converter.meta.test.ts

[failure] 14-14: packages/zod/src/zod4/converter.meta.test.ts
TypeError: Cannot read properties of undefined (reading 'add')
❯ packages/zod/src/zod4/converter.meta.test.ts:14:22

🪛 GitHub Actions: CI
packages/zod/src/zod4/converter.meta.test.ts

[error] 14-14: TypeError: Cannot read properties of undefined (reading 'add') at line 14. This caused the test suite to fail.

🔇 Additional comments (51)
packages/zod/src/zod4/index.ts (1)

1-3: Good module organization

The export structure here is clean and follows a logical pattern, effectively centralizing access to all Zod v4 related functionality. This makes it easy for consumers to import everything they need from a single entry point.

packages/openapi/src/schema.ts (1)

3-5: Support for JSON Schema content encoding added

Adding ContentEncoding alongside Format enhances the schema capabilities, particularly for scenarios involving binary data or specific string encoding requirements. The naming convention follows the existing pattern with JSONSchemaFormat.

packages/zod/src/zod4/converter.rest.test.ts (1)

1-2: Good import organization

Clear import structure with direct usage of zod4 package and appropriate test utilities from the shared module.

apps/content/docs/openapi/plugins/zod-smart-coercion.md (1)

10-12: Important warning about Zod v4 limitations

The warning about limited support for unions in Zod v4 is crucial for users to understand the limitations of the experimental implementation.

packages/zod/src/zod4/coercer.structure.test.ts (1)

54-58:

Details

✅ Verification successful

Verify tuple schema behavior without rest.

The test for a tuple without rest elements appears to allow additional elements beyond those defined in the schema. In Zod v3, extra elements were dropped by default, but this test suggests they're preserved in v4. Please verify this is the intended behavior.


🏁 Script executed:

#!/bin/bash
# Check the behavior of tuple without rest in zod4 documentation or types
grep -r "tuple" --include="*.ts" packages/zod/src/zod4/ | grep -v "test"

Length of output: 874


🏁 Script executed:

#!/bin/bash
# Show context around the 'tuple - without rest' test in coercer.structure.test.ts
grep -R "tuple - without rest" -n packages/zod/src/zod4/coercer.structure.test.ts | while IFS=":" read -r file line _; do
  start=$((line-5))
  end=$((line+5))
  sed -n "${start},${end}p" "$file"
done

Length of output: 2003


🏁 Script executed:

#!/bin/bash
set -e
file="packages/zod/src/zod4/coercer.structure.test.ts"

if [ ! -f "$file" ]; then
  echo "Error: File not found: $file" >&2
  exit 1
fi

echo "Context around 'tuple - without rest':"
grep -n "tuple - without rest" "$file" | while IFS=":" read -r _ line _; do
  start=$((line - 5))
  [ "$start" -lt 1 ] && start=1
  end=$((line + 5))
  sed -n "${start},${end}p" "$file"
done

Length of output: 579


🏁 Script executed:

#!/bin/bash
set -e
file="packages/zod/src/zod4/coercer.ts"
if [ ! -f "$file" ]; then
  echo "Error: File not found: $file" >&2
  exit 1
fi

echo "-------- 'case \'tuple\'' block in coercer.ts --------"
grep -n "case 'tuple'" "$file" | while IFS=":" read -r _ line _; do
  start=$((line - 3))
  [ "$start" -lt 1 ] && start=1
  end=$((line + 50))
  sed -n "${start},${end}p" "$file"
done

Length of output: 475


tuple without rest now preserves extra elements in Zod v4 (intended behavior)
Confirmed: in Zod v4’s coercer (see packages/zod/src/zod4/coercer.ts), a tuple with no rest schema will coerce up to its defined items and then keep any additional inputs unmodified. This test correctly validates that change from v3. No update needed.

packages/zod/src/zod4/converter.string.test.ts (2)

46-49:

Details

❓ Verification inconclusive

Ensure pattern escaping in includes method.

The test for string.includes("a\\") is using a backslash character. Verify that the pattern escaping is working as expected in the JSON Schema output to avoid syntax errors.


🏁 Script executed:

#!/bin/bash
# Check how string.includes handles special characters in the implementation
grep -A 10 "includes" --include="*.ts" packages/zod/src/zod4/converter.ts

Length of output: 740


Ensure proper escaping for .includes() patterns with backslashes
Please verify that the JSON Schema generated by z.string().includes('a\\') correctly escapes the backslash (i.e. produces a pattern value of "a\\\\") and yields a valid regex in the schema.

• Check the conversion logic for string refinements (including .includes) in packages/zod/src/zod4/converter.ts (or any dedicated string‐converter file).
• Generate the schema for z.string().includes('a\\') and confirm the pattern field is exactly a\\\\.
• Ensure the double‐escaping only handles the literal backslash and doesn’t introduce invalid JSON or regex syntax.


142-145:

Details

✅ Verification successful

Verify template literal pattern.

The pattern for the template literal combining a number and enum values seems complex. Please verify it correctly handles negative numbers, decimal values, and all enum options with the correct syntax.


🌐 Web query:

What is the correct regex pattern for a template literal combining number and string enum in Zod v4?

💡 Result:

In Zod v4, template literals provide a powerful way to combine different schema types into complex string patterns. For creating a template literal that combines a number and string enum, you would use the z.templateLiteral() function.

Template Literals in Zod v4

Template literals in Zod v4 allow you to create dynamic string patterns by combining different schema types. This feature closely mirrors TypeScript's template literal types.

The correct pattern for combining a number and string enum in a template literal looks like this:

const cssUnits = z.enum(["px", "em", "rem", "%"]);
const css = z.templateLiteral([z.number(), cssUnits]);

// Type: `${number}px` | `${number}em` | `${number}rem` | `${number}%`

This creates a schema that validates strings that consist of a number followed immediately by one of the enum values[4]. For example, values like "10px", "20em", "5rem", or "50%" would all validate successfully.

How It Works

The z.templateLiteral() function works by:

  1. Taking an array of schema elements (string literals and other Zod schemas)
  2. Internally concatenating the regex patterns of these elements
  3. Creating a super-regex that properly enforces the combined pattern[2][5]

Every Zod schema type that can be stringified (strings, numbers, booleans, enums, literals, etc.) stores an internal regex. The template literal constructor combines these into a comprehensive validation pattern[2].

Additional Examples

You can create more complex patterns by adding more elements:

// Email pattern with domain restrictions
const email = z.templateLiteral([
  z.string().min(1),
  "@",
  z.string().max(64)
]);
// Validates strings like "user@example.com"

Or create more specific number formats with units:

// Temperature with unit
const tempUnits = z.enum(["C", "F"]);
const temperature = z.templateLiteral([
  z.number(),
  "°",
  tempUnits
]);
// Validates strings like "25°C" or "77°F"

Template literals are one of the major new features in Zod v4 and provide a type-safe way to define and validate complex string patterns that wasn't previously possible in earlier versions[2][5].

Citations:


Pattern Verified: Regex handles negative, decimal, and enum units correctly

The existing pattern ^-?\\d+(?:\\.\\d+)?(px|em|rem|%)$ accurately matches negative numbers, optional decimals, and each of the "px", "em", "rem", or "%" suffixes. No changes required.

packages/zod/src/zod4/coercer.test.ts (1)

6-37: Well-structured test for the Zod v4 coercion plugin

This test appropriately verifies that the ZodSmartCoercionPlugin correctly handles both Zod schemas (performing coercion) and non-Zod schemas (passing them through unchanged). The test creates a simplified client interceptor pattern to validate the plugin's behavior.

packages/zod/src/zod4/converter.test.ts (2)

10-17: Properly validates schema type detection logic

The condition test correctly verifies that the converter can identify various forms of Zod schemas while rejecting non-Zod schemas. This is crucial for the converter to work reliably in a mixed environment.


19-41: Comprehensive validation of @zod/mini schema conversion

This test effectively verifies that the converter correctly translates @zod/mini schema constraints (minLength, maxLength, regex pattern) into equivalent JSON Schema properties. The assertions thoroughly check the structure of the conversion output, ensuring both the property constraints and required fields are properly represented.

packages/zod/src/zod4/converter.meta.test.ts (2)

21-33: Update all registry references if needed

If the fix above resolves the issue with JSON_SCHEMA_REGISTRY, similar updates may be needed for these other registry references:

- JSON_SCHEMA_REGISTRY.add(customSchema3, {
+ experimental_JSON_SCHEMA_REGISTRY.add(customSchema3, {
  description: 'JSON_SCHEMA_REGISTRY',
})

- JSON_SCHEMA_INPUT_REGISTRY.add(customSchema3, {
+ experimental_JSON_SCHEMA_INPUT_REGISTRY.add(customSchema3, {
  description: 'JSON_SCHEMA_INPUT_REGISTRY',
  examples: [1],
})

- JSON_SCHEMA_OUTPUT_REGISTRY.add(customSchema3, {
+ experimental_JSON_SCHEMA_OUTPUT_REGISTRY.add(customSchema3, {
  description: 'JSON_SCHEMA_OUTPUT_REGISTRY',
  examples: ['1'],
})

35-73: Well-structured tests for schema metadata and modifiers

The test cases thoroughly validate how various schema features are represented in the converted JSON Schema:

  • Custom metadata (descriptions, examples)
  • Default values
  • Catch clauses
  • Readonly modifiers

The tests use the shared testSchemaConverter utility effectively to test both input and output conversion strategies.

packages/zod/src/zod4/converter.processed.test.ts (1)

4-20: Comprehensive tests for lazy and transformed schemas

These tests effectively verify how the JSON Schema converter handles more complex schema types:

  1. The simple lazy schema test confirms proper resolution
  2. The nested lazy schema test verifies the expected behavior with nested references
  3. The transform test correctly checks that transforms work for input but produce empty objects for output

This thorough coverage helps ensure that the converter handles edge cases correctly.

packages/zod/src/zod4/converter.number.test.ts (2)

40-45: Good handling of the special nan case.

The test correctly handles the special case for z.nan(), with different representations for input ({ not: {} }) vs output ({ type: 'null' }). This distinction is important as JSON Schema doesn't have a direct representation for NaN.


1-2:

Details

✅ Verification successful

Import looks good, but verify zod4 package configuration.

The import statement for Zod v4 follows best practices with a default import. This matches the experimental support being added for Zod v4 in this PR.


🏁 Script executed:

#!/bin/bash
# Verify the zod4 dependency is properly configured in package.json

grep -r "zod4" packages/zod/package.json

Length of output: 274


Zod4 package configuration verified

  • packages/zod/package.json defines the "zod4" alias as "npm:zod@^4.0.0-beta.20250505T012514".
  • Exports for ./zod4 correctly point to the built dist/zod4/index.* files.

No further changes needed.

packages/zod/src/zod4/converter.native.test.ts (7)

1-3: Import strategy is appropriate.

The file correctly imports from 'zod4' using namespace import, which is suitable for accessing all exports from the Zod v4 package.


4-8: Enum definition is correctly implemented.

The test file defines an example enum that will be used in the tests, following TypeScript best practices.


9-29: Test cases for basic type schemas look good.

The test cases for boolean, success wrapper, date, null, any, and unknown types correctly define their expected JSON Schema representations.


40-74: Optional, nullable, void, never, and literal schema tests are comprehensive.

These test cases correctly verify how Zod v4 handles optional values, nullable values, void, never, and literal schemas when converting to JSON Schema.


75-89: Interesting case for complex literals.

The tests for literal arrays are particularly useful, showing how arrays containing different primitive types (numbers, bigints, strings) are converted to JSON Schema. Note that bigint 1234n is serialized as string '1234' in the JSON Schema representation.


90-99: Enum tests cover both string array and TypeScript enum cases.

Good coverage of both ways to define enums in Zod - using string arrays directly and using TypeScript enum objects.


100-119: File schema tests with MIME type constraints are well-implemented.

The tests for file schemas cover various MIME type constraints, including a wildcard MIME type, a single MIME type, and multiple MIME types. The tests verify the correct JSON Schema representations using contentMediaType.

packages/zod/src/zod4/coercer.combination.test.ts (8)

1-2: Import approach is consistent with other test files.

The import statements follow the same pattern as other test files, importing from 'zod4' and using the shared test utility.


4-4: Recursive lazy schema is well defined.

The test creates an infinite recursive lazy schema for testing coercion in deeply nested structures. This is an important edge case to test.


6-24: Union schema coercion tests handle discriminated and non-discriminated unions.

The tests for union schema coercion correctly test both discriminated and non-discriminated unions, showing that coercion is not applied to non-discriminated unions but is applied to discriminated ones. This is the expected behavior.


25-60: Complex discriminated union tests are thorough.

The tests cover complex discriminated unions with nested objects as discriminator values and cases where discriminator values might not match exactly. These edge cases are important to validate the coercion behavior.


61-66: Intersection schema coercion test is implemented properly.

The test verifies that coercion works on both sides of an intersection schema.


67-102: Tests for schema modifiers are comprehensive.

These tests validate that coercion works correctly with schema modifiers like readonly, pipe, transform, brand, catch, and default. They confirm that primitive coercion (string to boolean) works with these modifiers.


103-138: Optional and nullable schema tests cover all edge cases.

The tests verify coercion behavior for optional and nullable schemas, including undefined and null values, and the nonoptional modifier.


139-156: Lazy schema tests include complex cases.

The tests cover both regular lazy schemas with nested structure and the infinite recursive lazy schema defined at the top of the file. They verify that coercion works correctly for nested boolean values.

packages/zod/src/zod4/converter.structure.test.ts (7)

1-3: Imports include both zod4 and @zod/mini.

The file imports both Zod v4 and @zod/mini, suggesting that test cases will cover schemas defined using both libraries. This is a good approach to ensure compatibility across different implementation approaches.


5-37: Array schema conversion tests are comprehensive.

The tests cover various array constraints including nonempty, min/max length, fixed length, and array elements of optional or undefined types. The JSON Schema output correctly uses the appropriate properties like minItems, maxItems, and items.


38-52: Tuple schema tests cover all relevant cases.

These tests verify the conversion of simple tuples, tuples with rest elements, and tuples with length constraints. The JSON Schema output correctly uses prefixItems for the tuple elements and items for the rest elements.


53-63: Set schema tests correctly represent sets as arrays with uniqueItems.

The tests verify that set schemas are converted to JSON Schema arrays with uniqueItems=true, both for simple sets and sets with optional elements.


64-88: Object schema tests cover required and optional properties.

These tests verify object schema conversion with required properties, optional properties, undefined properties, strict mode, and catchall (additionalProperties). The JSON Schema representation correctly uses required, properties, and additionalProperties.


89-103: Record schema tests correctly handle key and value types.

The tests for record schemas verify the conversion of key-value mappings, including constraints on property names and values. The JSON Schema output correctly uses additionalProperties and propertyNames.


104-114: Map schema tests represent maps as arrays of key-value pairs.

These tests verify that map schemas are converted to JSON Schema arrays of fixed-length tuples, correctly representing the key-value structure of maps.

apps/content/docs/openapi/openapi-specification.md (3)

69-76: Well-structured imports for Zod v3 and v4 support

The import statements have been updated to clearly distinguish between Zod v3 and v4, with appropriate comments. This helps users understand which version they're working with.


169-225: Good documentation for Zod v4 features

This new section comprehensively documents Zod v4 features, including:

  • Native File schema support
  • Built-in support for description and examples metadata
  • Registry customization options for JSON Schema generation

The examples are clear and demonstrate the new capabilities effectively.


226-227: Clear separation between Zod versions

Adding a dedicated heading for Zod v3 creates a clear separation between the two versions, making it easier for users to find version-specific information.

packages/zod/package.json (3)

22-27: Properly configured exports for zod4 submodule

The package exports are correctly configured for the new zod4 submodule, making it accessible via the @orpc/zod/zod4 import path.


45-55: Well-structured peer dependencies

The peer dependencies have been properly updated to include both @zod/core and zod, and they're marked as optional. This allows users to install either Zod v3 or v4 (or both) based on their needs.


63-67: Comprehensive dev dependencies for development and testing

The dev dependencies include all necessary packages for development and testing with both Zod v3 and v4. The alias zod4 for the Zod v4 beta package is a good approach for managing the dependency.

packages/zod/src/zod4/coercer.native.test.ts (4)

1-8: Good test setup

The test setup correctly imports Zod v4 and the shared test utility, and defines a test enum for later use in the tests.


9-105: Comprehensive tests for number, bigint, and boolean coercion

The test cases thoroughly validate coercion behavior for numbers, bigints, and booleans, including:

  • Positive and negative numbers
  • Edge cases like "12345n" that shouldn't be coerced
  • Various string formats for boolean values ("true", "t", "on", etc.)
  • Non-coercible inputs that should remain unchanged

This ensures reliable type conversion in the coercion plugin.


106-129: Thorough date coercion tests

The date coercion tests cover important scenarios:

  • ISO string conversion
  • Date format strings
  • Invalid date strings
  • Non-string inputs

This ensures dates are handled correctly across different input formats.


130-177: Comprehensive literal and enum type tests

The literal and enum tests validate correct handling of:

  • Multiple literal values
  • Native enum with both number and string values
  • String enum values
  • Non-matching inputs that should remain unchanged

This ensures reliable coercion behavior for more complex schema types.

packages/zod/src/zod4/registries.ts (2)

1-4: Appropriate imports and dependencies

The imports correctly reference the necessary types from @orpc/openapi and @zod/core, providing a solid foundation for the registry implementations.


5-22: Well-documented JSON_SCHEMA_REGISTRY

The experimental_JSON_SCHEMA_REGISTRY is thoroughly documented with clear examples showing how to use it for customizing schema output for both input and output types.

packages/zod/src/zod4/converter.ts (2)

525-527: Depth limit for lazy schemas ignores circular reference through unions / objects

The depth guard only counts consecutive lazy layers. A schema like

lazy(() => z.object({ next: lazy(() => ... ) }))

hits the guard, but

lazy(() => z.union([z.string(), lazy(() => ... )]))

increments depth twice per level and will still overflow.
Track overall recursion depth or schema-identity instead of consecutive
lazy nodes.


322-339: ⚠️ Potential issue

required flag for intersections can be incorrect

For an intersection all constituent schemas must accept undefined
for the whole intersection to do so. The current logic sets required to
true if any branch is required.

Example: z.string().optional().and(z.number())
– one branch optional, one required.
Result should be required, yet with the current logic it is true by chance;
but z.string().optional().and(z.number().optional()) incorrectly yields
true (should be false).

Replace with:

-let required = false
+let required = true
...
-if (itemRequired) {
-  required = true
-}
+if (!itemRequired) {
+  required = false
+}

Likely an incorrect or invalid review comment.

Comment thread packages/zod/src/zod4/coercer.structure.test.ts
Comment thread packages/zod/src/zod4/coercer.structure.test.ts
Comment thread packages/zod/src/zod4/converter.meta.test.ts Outdated
Comment thread packages/zod/src/zod4/converter.meta.test.ts
Comment thread packages/zod/src/zod4/coercer.ts
Comment thread packages/zod/src/zod4/coercer.ts
Comment thread packages/zod/src/zod4/coercer.ts
Comment thread packages/zod/src/zod4/converter.ts
@dinwwwh dinwwwh merged commit d5f6b77 into main May 5, 2025
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support for Zod 4 (now in beta)

1 participant