Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: TypeScript Enhancements for Zod Types #4013

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

valentinpolitov
Copy link

@valentinpolitov valentinpolitov commented Mar 1, 2025

This PR introduces several TypeScript improvements that enhance type inference and developer experience without affecting runtime functionality.

Summary of Changes

1. ZodString Enhancements

  • Generic Case Inference:
    ZodString is now generic over its output type, enabling propagation of case transformations.

  • Case Matching for Methods:
    Introduced the inferCase helper type so that methods like includes, startsWith, and endsWith only accept values that match the current output case.

  • Enhanced Transformations:
    Updated toLowerCase and toUpperCase to return Lowercase<Output> and Uppercase<Output>, respectively, by reusing the _addCheck helper.

  • Generic length Method:
    Made the .length method generic, returning Output & { length: StringLength }.

  • Tests:
    Updated tests to validate the correct behavior of these enhancements.

2. ZodArray Enhancements

  • Exact Array Cardinality:
    Implemented tuple-based type inference for fixed-length arrays using the .length method.

  • TypeScript Limitation Enforcement:
    Enforced a maximum inferred tuple length of 100. For numbers beyond this limit or for generic number inputs, a simple array type is returned.

  • Documentation:
    Updated the Arrays documentation to reflect these changes.

3. ZodType Enhancements

  • Type Narrowing Helper:
    Introduced a generic, no-op method _assertParsedDataType that forces TypeScript to narrow the type of parsed input data inside the _parse method.

  • Overloads for Input Types:
    Supports overloads for both ParseInput and ParseContext, defaulting the generic type parameter to the ZodType output.

  • Documentation:
    Added detailed JSDoc comments to clarify the usage, overloads, and behavior of the method.

Impact

These changes are entirely TypeScript enhancements and do not affect runtime functionality. They improve type safety and developer experience when using Zod's API.

Please review the changes and let me know if any adjustments are needed.

Summary by CodeRabbit

  • Documentation

    • Refined documentation for array validations, distinguishing exact length enforcement from minimum/maximum constraints and adding clear TypeScript usage guidelines with illustrative examples.
  • New Features

    • Introduced support for enforcing an exact number of elements in arrays and enhanced string validation behaviors for improved type safety and clearer error feedback.
  • Tests

    • Expanded test coverage to verify accurate enforcement of length constraints and proper handling of string case validations.

…e `_addCheck` for `trim`/`toLowerCase`/`toUpperCase`

- Updated ZodString to be generic over its output type, enabling propagation of case transformations.
- Introduced `inferCase` helper type so that `includes`, `startsWith`, and `endsWith` methods accept values matching the current output case.
- Updated `toLowerCase` and `toUpperCase` to return `Lowercase<Output>` and `Uppercase<Output>`, respectively, using `_addCheck`.
- Made the `length` method generic, returning `Output & { length: StringLenght }`.
- Updated tests to validate the correct behavior.
…`.length` method

- Implement tuple-based type inference for fixed-length arrays
- Enforce TypeScript limitations with a maximum inferred tuple length of 100
- Preserve simple array types for generic number inputs
- Updated Arrays documentation
Copy link

coderabbitai bot commented Mar 1, 2025

Walkthrough

The changes enhance the documentation, tests, and type safety for array length validation and string manipulation in the Zod library. The README sections have been split and clarified, detailing how .length() enforces an exact array length and how .min()/.max() work without altering types. New test cases validate string lengths and case transformations, while additional utility types and a protected type-assertion method enhance type checking in both helpers and core Zod types.

Changes

File(s) Change Summary
README.md, deno/lib/README.md Documentation restructured: split the original .min/.max/.length section into distinct .length and .min/.max sections; added a "TypeScript Limitations" subsection with examples and behavioral details for the .length() method.
deno/lib/tests/string.test.ts, src/tests/string.test.ts Enhanced string tests by adding a "length checks" test to verify minimum, maximum, and exact length validations; refactored case conversion tests with variable assignments and improved error handling for method chaining.
deno/lib/helpers/util.ts, src/helpers/util.ts Introduced new utility types: NonNegative<T>, digit, ExactArray<T, N, R>, and inferCase<T> to provide stricter type-checking and enforce precise array length definitions.
deno/lib/types.ts, src/types.ts Added a protected _assertParsedDataType method (with overloads) in the ZodType class to assert and narrow parsed data types; updated method signatures in ZodString and ZodArray (notably for .length()) and expanded ArrayCardinality to include an "exact" option for improved type safety.

Sequence Diagram(s)

sequenceDiagram
    participant C as Client
    participant Z as ZodType::_parse
    participant A as _assertParsedDataType
    C->>Z: Provide input data for parsing
    Z->>A: Invoke _assertParsedDataType(input)
    A-->>Z: Confirm input type and return
    Z->>C: Return validated, parsed output
Loading

Poem

Hop, hop, I’m a rabbit on the run,
In docs and tests, precision’s begun.
Arrays now match their exact design,
Types and strings align just fine.
With every change, I leap through code—
A bunny’s delight on this new road! 🐰

✨ Finishing Touches
  • 📝 Generate Docstrings

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ 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.

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

Copy link

netlify bot commented Mar 1, 2025

Deploy Preview for guileless-rolypoly-866f8a ready!

Built without sensitive environment variables

Name Link
🔨 Latest commit 4890cf9
🔍 Latest deploy log https://app.netlify.com/sites/guileless-rolypoly-866f8a/deploys/67c367c1b542bc0008ff0ae0
😎 Deploy Preview https://deploy-preview-4013--guileless-rolypoly-866f8a.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (7)
deno/lib/README.md (1)

1638-1644: Grammar and Clarity Improvement in the .length() Section

Please update the phrase "a exact‑length array" to "an exact‑length array" to ensure correct English usage. The code example and description otherwise clearly demonstrate the intended tuple transformation behavior.

-To enforce a exact-length array, use `.length()`.
+To enforce an exact-length array, use `.length()`.
🧰 Tools
🪛 LanguageTool

[misspelling] ~1640-~1640: Use “an” instead of ‘a’ if the following word starts with a vowel sound, e.g. ‘an article’, ‘an hour’.
Context: ...y!", }); ``` ### .length To enforce a exact-length array, use `.length()`. Th...

(EN_A_VS_AN)

README.md (3)

1638-1646: Grammar Enhancement in the .length Section

The documentation currently reads “To enforce a exact-length array, use .length().” For proper grammar, “exact” should be preceded by an rather than a.

-To enforce a exact-length array, use `.length()`.
+To enforce an exact-length array, use `.length()`.
🧰 Tools
🪛 LanguageTool

[misspelling] ~1640-~1640: Use “an” instead of ‘a’ if the following word starts with a vowel sound, e.g. ‘an article’, ‘an hour’.
Context: ...y!", }); ``` ### .length To enforce a exact-length array, use `.length()`. Th...

(EN_A_VS_AN)


1667-1684: Typo Correction in the TypeScript Limitations Section

There appears to be a typo in the sentence: “If a generic number is passed to .lenght(), Zod will validate the length but lose the tuple inference…”
It should be spelled as .length().

-If a generic `number` is passed to `.lenght()`, Zod will validate the length but lose the tuple inference, resulting in a simple array:
+If a generic `number` is passed to `.length()`, Zod will validate the length but lose the tuple inference, resulting in a simple array:
🧰 Tools
🪛 LanguageTool

[typographical] ~1684-~1684: It is considered good style to insert a comma after introductory phrases with dates or proper nouns.
Context: ...-Infinitycannot be checked because in TypeScript they are of typenumber`.
## T...

(IN_NNP_COMMA)


1680-1685: Punctuation Improvement in Listing Special Numbers

For clarity and consistency, consider adding a comma in the list of special numeric values. For example, change “NaN, Infinity and -Infinity” to “NaN, Infinity, and -Infinity”.

- > ⚠️ `NaN`, `Infinity` and `-Infinity` cannot be checked because in TypeScript they are of type `number`.
+ > ⚠️ `NaN`, `Infinity`, and `-Infinity` cannot be checked because in TypeScript they are of type `number`.
🧰 Tools
🪛 LanguageTool

[typographical] ~1684-~1684: It is considered good style to insert a comma after introductory phrases with dates or proper nouns.
Context: ...-Infinitycannot be checked because in TypeScript they are of typenumber`.
## T...

(IN_NNP_COMMA)

src/types.ts (1)

1112-1112: Returning value as any
Casting to any can reduce type safety. Consider maintaining stronger typing to avoid losing type information.

deno/lib/types.ts (2)

1112-1112: Remove the as any to strengthen type safety.
While this works, it's safer to avoid any if possible.

Apply this diff to remove the unnecessary cast:

-    return { status: status.value, value: input.data } as any;
+    return { status: status.value, value: input.data };

2354-2354: Consider adjusting the naming of “atleastone”.
Renaming to “atLeastOne” may improve clarity and consistency within codebases.

Suggested update:

-export type ArrayCardinality = "many" | "atleastone" | "exact";
+export type ArrayCardinality = "many" | "atLeastOne" | "exact";
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between e2b9a5f and 07e34bf.

📒 Files selected for processing (8)
  • README.md (1 hunks)
  • deno/lib/README.md (1 hunks)
  • deno/lib/__tests__/string.test.ts (2 hunks)
  • deno/lib/helpers/util.ts (2 hunks)
  • deno/lib/types.ts (51 hunks)
  • src/__tests__/string.test.ts (2 hunks)
  • src/helpers/util.ts (2 hunks)
  • src/types.ts (51 hunks)
🧰 Additional context used
🪛 LanguageTool
deno/lib/README.md

[misspelling] ~1640-~1640: Use “an” instead of ‘a’ if the following word starts with a vowel sound, e.g. ‘an article’, ‘an hour’.
Context: ...y!", }); ``` ### .length To enforce a exact-length array, use `.length()`. Th...

(EN_A_VS_AN)


[typographical] ~1684-~1684: It is considered good style to insert a comma after introductory phrases with dates or proper nouns.
Context: ...-Infinitycannot be checked because in TypeScript they are of typenumber`.
## T...

(IN_NNP_COMMA)

README.md

[misspelling] ~1640-~1640: Use “an” instead of ‘a’ if the following word starts with a vowel sound, e.g. ‘an article’, ‘an hour’.
Context: ...y!", }); ``` ### .length To enforce a exact-length array, use `.length()`. Th...

(EN_A_VS_AN)


[typographical] ~1684-~1684: It is considered good style to insert a comma after introductory phrases with dates or proper nouns.
Context: ...-Infinitycannot be checked because in TypeScript they are of typenumber`.
## T...

(IN_NNP_COMMA)

🔇 Additional comments (74)
deno/lib/README.md (1)

1647-1657: Documentation Clarity for .min()/.max()

The updated section clearly explains that the .min() and .max() methods enforce length constraints without altering the inferred type. The provided examples are concise and helpful. No further modifications are needed here.

src/__tests__/string.test.ts (3)

41-47: Well-structured tests for string length validation.

The new test case thoroughly validates the behavior of the string length validators (min, max, and length), ensuring that they enforce the correct constraints. This corresponds well to the TypeScript enhancements mentioned in the PR objectives.


576-595: Excellent test coverage for case transformation type safety.

The updated tests for toLowerCase() properly validate the TypeScript enhancements for case-sensitive methods. The tests check both the transformation behavior and the type-safety improvements:

  1. Basic case transformation is verified
  2. Method ordering is tested (e.g., toLowerCase().startsWith("a") vs startsWith("A").toLowerCase())
  3. Type errors are correctly expected when case doesn't match the output type

This implementation directly supports the PR objective of improving generic case inference and ensuring that methods like startsWith, endsWith, and includes accept values that match the output case.


597-617: Comprehensive test pattern for uppercase transformation.

The toUpperCase() tests mirror the structure of the lowercase tests, creating a thorough and consistent test suite. These tests properly validate that:

  1. Uppercase transformations work correctly
  2. Method chaining order is respected
  3. Type errors occur when case-sensitive methods are called with the wrong case

The symmetrical implementation between lowercase and uppercase tests is a good practice for ensuring consistent behavior across similar functionality.

deno/lib/__tests__/string.test.ts (3)

42-48: Good addition of comprehensive length validation tests!

These tests properly validate that string length validators behave as expected:

  • minFive correctly accepts strings of exactly 5 characters or longer
  • maxFive correctly accepts strings of exactly 5 characters or shorter
  • justFive correctly enforces strings to be exactly 5 characters

577-595: Enhanced case transformation tests with proper type-safety checks.

The test has been improved to:

  1. Make the code more maintainable by using a variable for the transformed string
  2. Verify correct string conversion with toLowerCase()
  3. Test method chaining behavior where transformations are applied in the correct order
  4. Validate type-safety with appropriate error cases

598-617: Good symmetrical testing for uppercase transformations.

This mirrors the lowercase tests with the same quality improvements:

  1. Variable assignment for better readability
  2. Clear test cases for the toUpperCase() transformation
  3. Proper validation of method ordering in chains
  4. Type-safety checks that verify incompatible case operations throw errors
src/helpers/util.ts (4)

20-22: Well-designed type constraint for non-negative numbers.

The NonNegative<T> type elegantly uses template literal types to check for negative numbers, resolving to never when negative or T when not, providing strong guarantees for numeric parameters.


24-24: Clean digit union type definition.

The digit type creates a reusable union of all single-digit numbers, which improves readability when used in other type expressions.


26-38: Sophisticated recursive type for exact-length arrays.

The ExactArray<T, N, R> type is well-structured with:

  1. Special handling for non-literal numbers (number extends N)
  2. Rejection of negative lengths
  3. Optimization for small arrays (up to 100) with recursive building
  4. Fallback to a simpler constraint for larger arrays

This type powers the .length() array validation method to produce exact tuple types.


95-99: Useful string case inference type.

The inferCase<T> type elegantly detects whether a string type is lowercase, uppercase, or mixed case, enabling type-safe operations with case-sensitive string methods like .startsWith(), .endsWith(), and .includes().

deno/lib/helpers/util.ts (4)

20-22: Well-designed type constraint for non-negative numbers.

The NonNegative<T> type elegantly uses template literal types to check for negative numbers, providing type-level guarantees for numeric parameters.


24-24: Clean digit union type definition.

Simple but effective definition of digits as a union of literal numbers from 0 to 9.


26-38: Sophisticated recursive type for exact-length arrays.

The ExactArray<T, N, R> type is well-designed with appropriate special cases:

  1. Fallback to T[] for non-literal numbers
  2. Rejection of negative numbers
  3. Recursive construction for reasonable lengths (single/double digit and 100)
  4. Simple constraint for larger numbers

This type supports strong typing for the array length validation feature.


95-99: Useful string case inference type.

The inferCase<T> type intelligently determines the case characteristic of a string type, enabling type-safe operations with case-sensitive string methods, improving the developer experience when using string validators.

src/types.ts (17)

191-217: Nice introduction of typed assertion method
This newly introduced _assertParsedDataType method is well-documented and provides a clear way to narrow input data types in TypeScript. It's a no-op at runtime, so there's minimal overhead.


800-804: Additional generic type parameter for ZodString
Allowing Output extends string enables more advanced transformations while preserving type safety.


820-820: Runtime no-op for type assertion
Calling _assertParsedDataType<string>(input) here ensures correct type-narrowing, consistent with the new generic approach.


978-978: Includes check
Utilizing input.data.includes(check.value, check.position) is correct; no issues found.


992-992: startsWith check
Straightforward usage of startsWith; no immediate concerns.


1002-1002: endsWith check
Straightforward usage of endsWith; no immediate concerns.


1127-1128: Generic check addition
The <CheckOutput extends string = Output> approach for _addCheck helps preserve type information for custom checks.


1254-1257: Improved includes signature
Accepting value: util.inferCase<Output> enforces consistent case inference for string includes.


1266-1266: startsWith with typed value
Using util.inferCase<Output> for the value parameter supports advanced type safety for string transformations.


1274-1274: endsWith with typed value
Retaining the consistent approach from startsWith and includes; looks good.


1298-1302: Typed string length
Returning Output & { length: StringLength } is an elegant way to carry length-specific type info through the chain.


1317-1317: Trim check
Defining a check with kind: "trim" is a clean extension and aligns with the new pattern.


1321-1321: toLowerCase
Inferring Lowercase<Output> for advanced transformations; no issues identified.


1325-1325: toUpperCase
Similarly inferring Uppercase<Output> provides symmetrical handling for uppercase transformations.


1460-1460: Type assertion in ZodNumber
Consistently ensures correct type after parsing a number.


1744-1744: Type assertion in ZodBigInt
Aligns with the existing pattern of calling _assertParsedDataType after validating the parsed type.


3894-3900: Enhanced parse path for Map
Separating key and value paths (e.g., [index, "key"], [index, "value"]) improves error messaging for ZodMap.

deno/lib/types.ts (42)

191-217: Introduce _assertParsedDataType: Great addition for type narrowing.
This utility method clarifies the intended type post-checks and improves readability. Ensure it's always invoked after confirming the data's type to avoid incorrect narrowing.


800-804: Refining ZodString with a generic output type.
Leveraging generics for the string output type more accurately handles transformations and advanced checks.


820-820: Confirming _assertParsedDataType<string> after string check.
You validate parsedType before asserting, which is consistent and safe for narrowing.


978-978: includes check introduced.
Using String.prototype.includes is clear and aligns with ES2015+ features.


992-992: startsWith check introduced.
The addition is straightforward and follows the same pattern as includes.


1002-1002: endsWith check introduced.
Aligns with the existing pattern for string validation methods.


1127-1128: Generic _addCheck for string output improvements.
Cleverly allows the output type to adjust based on transformations or checks.


1254-1257: includes now inferring case.
This is a neat way to ensure consistency with transformed string types.


1266-1266: startsWith inferring output case.
This extension elegantly supports generic string transformations.


1274-1274: endsWith inferring output case.
Continues the consistent approach of tailoring checks to the string’s transformation.


1298-1302: Generic length method for string schemas.
Tying the length requirement into the output type is an excellent way to reflect exact lengths in the type system.


1317-1317: trim check for string schemas.
Straightforward approach to strip whitespace in place. Ensure users are aware of the in-place data modification.


1321-1321: toLowerCase returning Lowercase<Output>.
Accurately reflects the lowered case in the output type.


1325-1325: toUpperCase returning Uppercase<Output>.
Parallel approach to toLowerCase, preserving type correctness.


2466-2469: Generic min method for arrays.
Strongly typed approach to constraining minimum length is well-implemented.


2476-2479: Generic max method for arrays.
Matches the min pattern, maintaining consistent constraints.


2486-2489: Exact length for arrays.
Helps define strictly sized arrays, a nice feature for robust schemas.


2496-2496: nonempty for arrays.
Convenient helper for quickly ensuring at least one element is present.


2515-2519: New array type aliases: ZodNonEmptyArray and ZodExactArray.
These aliases neatly capture specific cardinalities for array validations.


3160-3160: Ensuring union type correctness via _assertParsedDataType.
Called after validating each subtype. This is consistent with the pattern used.


3362-3362: Ensuring discriminated union type correctness.
This explicit assertion aligns with the prior type checks on the discriminator field.


3535-3535: Intersection type assertion.
Methodically placed after verifying left and right schemas in _parse logic.


3663-3663: Tuple type assertion.
Similarly consistent with the pattern of asserting post-parse.


3788-3788: Object schema assertion.
Maintaining uniform usage of _assertParsedDataType across Zod data structures.


3888-3888: Map type assertion.
Checks the parse context thoroughly before finalizing.


3893-3899: Parsing key-value pairs for ZodMap.
Nicely splits out key and value parsing with distinct lazy paths for clarity.


3985-3985: Set type assertion.
Consistent with the rest of the library’s design, ensuring type correctness.


4029-4031: Mapping set elements with spread and _parse.
Effective approach for iterating sets while preserving the parse context for each element.


4124-4124: Function type assertion.
Permits refined type inference after verifying the input is a function.


4303-4303: Lazy type assertion.
Ensures the lazily-evaluated schema is properly validated at runtime.


4343-4343: Literal type assertion.
Straightforward check, verifying exact literal match and refining the type.


4434-4434: Enum type assertion.
Helpful for guaranteeing the input matches one of the enumerated string options.


4621-4621: Promise type assertion.
Checks that data is a genuine Promise, then awaits it to validate the resolved value.


4629-4629: Parsing a promise via .then chain.
Implementation is clean, returning a validated async result.


4705-4705: Effects type assertion.
Ensures that the data meets the underlying schema’s checks before applying transformations or refinements.


4883-4883: Optional type assertion.
Appropriately short-circuits when data is undefined before continuing validation.


4928-4928: Nullable type assertion.
Likewise short-circuits for null, closely paralleling the optional logic.


5033-5033: Catch type assertion.
Separates the context to avoid polluting the main parse flow with errors, then reasserts type correctness.


5118-5118: NaN type assertion.
A specialized check distinguishing NaN from typical numeric values.


5155-5155: Branded type assertion.
Useful for combining a standard schema with brand metadata at the type level.


5190-5190: Pipeline type assertion.
Ensures the data from the “in” side is valid before passing to the “out” schema, with consistent usage of _assertParsedDataType.


5286-5286: Readonly type assertion.
Completes the final step by freezing the data after successful validation.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (5)
deno/lib/README.md (1)

1638-1645: Documentation Clarity & Grammar for .length() Method

The new section for the .length() method is very clear: it explains that using .length() converts an array schema into a tuple type of exactly the specified length. The provided code snippet neatly demonstrates how a 5‑item array becomes a tuple with an enforced length of 5.

Suggestion: Change “a exact‑length array” to “an exact‑length array” to improve grammatical correctness (since “exact” starts with a vowel sound).

-To enforce a exact-length array, use `.length()`.
+To enforce an exact-length array, use `.length()`.
🧰 Tools
🪛 LanguageTool

[misspelling] ~1640-~1640: Use “an” instead of ‘a’ if the following word starts with a vowel sound, e.g. ‘an article’, ‘an hour’.
Context: ...y!", }); ``` ### .length To enforce a exact-length array, use `.length()`. Th...

(EN_A_VS_AN)

README.md (1)

1683-1684: Minor Stylistic Suggestion for Special Numeric Values

The warning that NaN, Infinity, and -Infinity cannot be checked is valuable. For clarity and style, consider adding a comma after “Infinity” to improve readability:

-> ⚠️ `NaN`, `Infinity` and `-Infinity` cannot be checked because in TypeScript they are of type `number`.
+> ⚠️ `NaN`, `Infinity`, and `-Infinity` cannot be checked because in TypeScript they are of type `number`.
🧰 Tools
🪛 LanguageTool

[typographical] ~1684-~1684: It is considered good style to insert a comma after introductory phrases with dates or proper nouns.
Context: ...-Infinitycannot be checked because in TypeScript they are of typenumber`.
## T...

(IN_NNP_COMMA)

deno/lib/types.ts (3)

4550-4551: Double _assertParsedDataType call is redundant

There are two consecutive calls to _assertParsedDataType with different parameters (input and ctx).

Since you're already calling the method with input on line 4550, the second call with ctx on line 4551 is redundant and could be removed:

-    this._assertParsedDataType(input);
-    this._assertParsedDataType(ctx);
+    this._assertParsedDataType(input);

4567-4567: Consider consistent return style

The ZodNativeEnum._parse method returns OK(input.data) directly while most other implementations define the value in a variable first.

For consistency with other parsers, consider using the same style as most other implementations:

-    return OK(input.data);
+    return { status: "valid", value: input.data };

4629-4629: Arrow function unnecessary wrapping parameter

The arrow function in ZodPromise._parse unnecessarily wraps the promisified.then parameter.

The arrow function syntax could be simplified:

-      promisified.then((data) => {
+      promisified.then(data => {
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 07e34bf and 4ddf845.

📒 Files selected for processing (4)
  • README.md (1 hunks)
  • deno/lib/README.md (1 hunks)
  • deno/lib/types.ts (51 hunks)
  • src/types.ts (51 hunks)
🧰 Additional context used
🪛 LanguageTool
deno/lib/README.md

[misspelling] ~1640-~1640: Use “an” instead of ‘a’ if the following word starts with a vowel sound, e.g. ‘an article’, ‘an hour’.
Context: ...y!", }); ``` ### .length To enforce a exact-length array, use `.length()`. Th...

(EN_A_VS_AN)


[typographical] ~1684-~1684: It is considered good style to insert a comma after introductory phrases with dates or proper nouns.
Context: ...-Infinitycannot be checked because in TypeScript they are of typenumber`.
## T...

(IN_NNP_COMMA)

README.md

[misspelling] ~1640-~1640: Use “an” instead of ‘a’ if the following word starts with a vowel sound, e.g. ‘an article’, ‘an hour’.
Context: ...y!", }); ``` ### .length To enforce a exact-length array, use `.length()`. Th...

(EN_A_VS_AN)


[typographical] ~1684-~1684: It is considered good style to insert a comma after introductory phrases with dates or proper nouns.
Context: ...-Infinitycannot be checked because in TypeScript they are of typenumber`.
## T...

(IN_NNP_COMMA)

🔇 Additional comments (42)
deno/lib/README.md (2)

1647-1654: .min() and .max() Documentation Review

The documentation for the .min() and .max() methods is concise and clear. It shows how to constrain arrays to have at least or at most a given number of items without changing the inferred type, which contrasts nicely with the tuple effect of .length().


1658-1685: TypeScript Limitations Section – Excellent Detail with Minor Observations

This new “TypeScript Limitations” section is very useful. It clearly explains:

  • How passing a literal (e.g. 101) results in a tuple type, while a generic number argument loses tuple inference.
  • That negative numbers are disallowed for .length(), .min(), and .max() to avoid runtime errors.
  • The note on NaN, Infinity, and -Infinity is also valuable for setting the developer’s expectations.

The examples are well chosen and readable. One minor suggestion: if desired, you might consider a very brief summary statement at the end of this section to reiterate the safe usage practices—though it is optional given the thoroughness already present.

🧰 Tools
🪛 LanguageTool

[typographical] ~1684-~1684: It is considered good style to insert a comma after introductory phrases with dates or proper nouns.
Context: ...-Infinitycannot be checked because in TypeScript they are of typenumber`.
## T...

(IN_NNP_COMMA)

README.md (5)

1638-1645: Improve Article Usage & Clarity in .length() Section

The documentation for the .length() method is very clear and the code snippet nicely illustrates that using .length(5) enforces an exact tuple type. However, note that the phrase “a exact‐length array” should use “an” rather than “a” since “exact” begins with a vowel sound.

-To enforce a exact-length array, use `.length()`.
+To enforce an exact-length array, use `.length()`.

[refactor_suggestion_nitpick]

🧰 Tools
🪛 LanguageTool

[misspelling] ~1640-~1640: Use “an” instead of ‘a’ if the following word starts with a vowel sound, e.g. ‘an article’, ‘an hour’.
Context: ...y!", }); ``` ### .length To enforce a exact-length array, use `.length()`. Th...

(EN_A_VS_AN)


1647-1651: Clear Explanation for .min/.max Methods

The new “.min/.max” section clearly distinguishes these methods from .length() by explicitly noting that they do not alter the inferred type. The provided code snippets are concise and easy to follow.


1658-1665: Detailed Explanation of Tuple Inference Limits

The TypeScript Limitations section effectively explains that for .length(), the maximum inferred tuple length is capped at 100 and demonstrates what happens when a larger number is passed. This is very useful for users familiar with TypeScript’s limitations.


1668-1673: Document Behavior When Using Generic Numbers for .length()

The explanation making users aware that passing a generic number to .length() results in a simple array (losing tuple inference) is clear and helpful. Consider emphasizing this edge case so developers know to use explicit literals when tuple types are desired.


1675-1680: Clarify Disallowing Negative Values

The note and code example demonstrating that negative numbers are disallowed (with an appropriate TypeScript error) provide useful safety information. This addition will help prevent potential runtime errors.

deno/lib/types.ts (14)

191-216: Excellent addition of type assertion helper!

This protected method provides a clean way to narrow input/context data types after validations. The no-op implementation properly preserves runtime performance while providing valuable TypeScript type narrowing.

This is a sophisticated TypeScript technique that leverages TypeScript's type assertion capabilities without adding runtime overhead. It will help prevent type errors in downstream code that uses the parsed values.


800-804: Great enhancement with generic ZodString class!

Making ZodString generic over its output type is a significant improvement that allows for better type inference with string transformations.

This change enables the type system to track string case transformations and length constraints, which will improve developer experience when chaining string operations.


805-821: Proper implementation of _assertParsedDataType in ZodString._parse

Good implementation of the new type assertion method to narrow the input data type after validation.


1254-1257: Improved type safety for string operations with inferCase

Using util.inferCase<Output> instead of just string ensures that case transformations are properly typed.

This will maintain case information when performing operations like .includes(), ensuring that the type system knows about any prior case transformations.


1266-1266: Type-safe string comparison with inferCase

The startsWith method now correctly uses the inferred case type.


1274-1274: Type-safe string comparison with inferCase

The endsWith method now correctly uses the inferred case type.


1298-1302: Enhanced length method with string length constraint

The length method now properly returns a type that includes the exact length constraint.

This will allow TypeScript to know the exact length of the string in the type system, improving type safety.


1317-1318: Maintain generics with trim operation

The trim operation properly maintains the output type.


1321-1322: Properly typed case transformation

The toLowerCase method now correctly returns Lowercase<Output> instead of just string.

This change enables the type system to track the lowercase transformation in the type.


1325-1326: Properly typed case transformation

The toUpperCase method now correctly returns Uppercase<Output> instead of just string.

This change enables the type system to track the uppercase transformation in the type.


2354-2358: Enhanced array cardinality with exact length support

Adding the "exact" cardinality type extends ZodArray's capabilities to handle fixed-length arrays with proper typing.

This change enables the creation of arrays with exact length constraints that will be reflected in the TypeScript types, similar to tuples.


2361-2366: Type-safe exact array length output

The arrayOutputType now correctly handles the "exact" cardinality with proper tuple typing.

This ensures that arrays with exact length constraints are properly typed as tuples of that specific length.


2486-2494: Enhanced array length method with tuple type

The length method now returns a ZodExactArray type with the exact length constraint.

This allows for creating exact-length arrays that are typed as tuples in TypeScript, providing better type safety.


2516-2519: New ZodExactArray type for fixed-length arrays

The new ZodExactArray type provides a clean way to represent arrays with exact length constraints.

This type specialization helps maintain the distinction between regular arrays and fixed-length arrays in the type system.

src/types.ts (21)

191-217: Improve clarity with usage examples.

This block of JSDoc and overload declarations for _assertParsedDataType is clear about its purpose of narrowing types at compile time. Consider including a short code snippet in the documentation to illustrate how developers can use it in _parse methods, which would make it even more intuitive.


800-804: Generic parameter for ZodString.

Introducing Output extends string = string to ZodString is a solid enhancement, helping produce refined string types for case transformations and other checks.


820-820: Asserting input data type in ZodString parse method.

Calling _assertParsedDataType<string>(input) consistently enforces compile-time type narrowing for string parsing without affecting runtime behavior.


978-978: Check validity of position when calling .includes.

includes(check.value, check.position) works as intended, but ensure check.position is an integer and inform users how negative or large positions behave.


992-992: startsWith usage looks correct.

No logical issues spotted. This change properly uses startsWith(check.value).


1002-1002: endsWith usage looks correct.

No logical issues spotted. This change properly uses endsWith(check.value).


1112-1112: Returning parse result in ZodString.

Returning { status: status.value, value: input.data } aligns with the parsing flow.


1127-1128: Introducing a generic parameter in _addCheck.

Allowing <CheckOutput extends string = Output> enables fine-grained transformations for future checks without breaking existing usage.


1254-1257: Refined signature for .includes.

Accepting util.inferCase<Output> for the search string and an optional position is consistent with the rest of the case-centric API.


1266-1266: Refined signature for .startsWith.

Using util.inferCase<Output> for the prefix is consistent with the library’s new case inference design.


1274-1274: Refined signature for .endsWith.

Similarly, adopting util.inferCase<Output> for the suffix is consistent and maintains type safety.


1298-1301: Generic .length method to encode string length.

This change helps encode a compile-time string length constraint (Output & { length: N }). It’s a neat approach for advanced type inference.


1317-1317: .trim check addition.

Simple addition that calls _addCheck({ kind: "trim" }). No functional issues identified.


1321-1321: .toLowerCase returning Lowercase.

This new approach ensures type-level knowledge that the resulting string is lowercase.


1325-1325: .toUpperCase returning Uppercase.

Likewise, returning Uppercase<Output> helps advanced type inference for uppercase transformations.


1460-1460: Type narrowing for ZodNumber.

Using _assertParsedDataType(input) here is consistent with the new pattern for numeric parsing.


1744-1744: Type narrowing for ZodBigInt.

Similar to ZodNumber, this ensures the parse path is typed as bigint.


1957-1957: Type narrowing for ZodBoolean.

No concerns; consistent usage in boolean parsing.


2191-2191: Type assertion within ZodNull parse.

Ensures the parse path is typed as null; consistent with the approach in other type classes.


2211-2211: Redundant but harmless parse assertion in ZodNull.

A second call to _assertParsedDataType(input) is benign. If not required, consider removing to avoid confusion, but it won’t harm runtime.


2262-2262: Type narrowing for ZodUnknown.

No issues spotted; consistent with the library’s new pattern.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (4)
deno/lib/README.md (1)

1656-1676: Detailed Explanation of TypeScript Limitations and Negative Value Handling
The "TypeScript Limitations" block is thorough and helpful. It clearly documents the inherent limitations with tuple inference (including the maximum tuple length of 100), the loss of tuple inference when a generic number is passed, and the decision to disallow negative values for .length(), .min(), and .max(). The code examples are well chosen to demonstrate these points.

Minor nitpick: While the technical content is excellent, consider reviewing the phrasing for any potential stylistic improvements (for example, ensuring that introductory phrases are punctuated consistently for optimal readability).

README.md (1)

1656-1675: TypeScript Limitations Section – Informative with Minor Punctuation Suggestion

The "TypeScript Limitations" section effectively documents critical constraints:

  • It clearly explains that .length() creates a tuple type (up to 100 elements) and that using a generic number parameter results in a simple array.
  • The examples handling negative values and the resulting TypeScript errors are very useful for users to anticipate potential issues.

Nitpick: In the warning message, consider adding an Oxford comma for clarity. For example:

- > ⚠️ `NaN`, `Infinity` and `-Infinity` cannot be checked because in TypeScript they are of type `number`.
+ > ⚠️ `NaN`, `Infinity`, and `-Infinity` cannot be checked because in TypeScript they are of type `number`.

This minor change improves readability and conforms to common style guidelines.

deno/lib/types.ts (2)

210-217: Introduce runtime clarifications for type-narrowing.
The _assertParsedDataType method serves as a no-op assertion for TypeScript but provides no runtime checks. If you require actual runtime validation, consider supplementing this with real checks or clarifying its TS-only usage.


1127-1128: Cautious generic override in _addCheck.
Inferring CheckOutput is flexible, but ensure it doesn't inadvertently widen or conflict with the pre-declared Output type.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 4ddf845 and 4890cf9.

📒 Files selected for processing (4)
  • README.md (1 hunks)
  • deno/lib/README.md (1 hunks)
  • deno/lib/types.ts (51 hunks)
  • src/types.ts (51 hunks)
🧰 Additional context used
🪛 LanguageTool
README.md

[typographical] ~1684-~1684: It is considered good style to insert a comma after introductory phrases with dates or proper nouns.
Context: ...-Infinitycannot be checked because in TypeScript they are of typenumber`.
## T...

(IN_NNP_COMMA)

deno/lib/README.md

[typographical] ~1684-~1684: It is considered good style to insert a comma after introductory phrases with dates or proper nouns.
Context: ...-Infinitycannot be checked because in TypeScript they are of typenumber`.
## T...

(IN_NNP_COMMA)

🔇 Additional comments (50)
deno/lib/README.md (2)

1638-1645: Clear and Concise Explanation of .length()
The new documentation for the .length() method is clear and informative. It succinctly explains that using .length() on an array schema forces the array to have an exact number of items and even shows the resulting inferred tuple type. This example effectively highlights the added type-safety benefit.


1647-1654: Well-Documented .min() and .max() Behavior
The updated section on .min() and .max() clearly distinguishes these methods from .length() and .nonempty() by noting that their application does not alter the inferred type. The provided code examples are straightforward and serve to illustrate the expected behavior.

README.md (2)

1638-1645: .length Section – Clear Explanation & Concise Code Example

The new section explaining the .length() method is clear and concise. The text explains that using .length() enforces an exact-length array and changes the inferred type into a tuple. The code example is well chosen to illustrate this behavior.


1646-1654: .min/.max Section – Effective and Straightforward

This section neatly explains how to use .min() and .max() for array length constraints without altering the inferred type. The code snippet is straightforward and demonstrates the intended usage. Overall, it meets the objective of clarifying the behavior of these methods.

deno/lib/types.ts (18)

191-191: Doc comment clarity.
This updated JSDoc header accurately captures the purpose of the new type-narrowing helper.


800-804: Generic output parameter in ZodString.
Adopting a generic <Output extends string = string> parameter broadens type inference without breaking existing usage.


820-821: Type assertion for string inputs.
This confirms that input.data is treated as a string type in TypeScript, though it remains a no-op at runtime.


978-978: Safe usage of includes.
No issues spotted. The precedential type assertion averts potential type errors in includes().


992-992: Safe usage of startsWith.
Calling startsWith is straightforward here; no further concerns.


1002-1002: Safe usage of endsWith.
This mirrors the logic of startsWith without evident concerns.


1112-1112: Return statement correctness.
Returning both parse status and the unmodified input.data looks consistent with the parsing flow.


1254-1257: Case inference in .includes().
Accepting util.inferCase<Output> for the value parameter enhances type safety and alignment with transformations.


1266-1266: Case inference in .startsWith().
Matches the .includes() approach for type-sound case handling.


1282-1282: Case inference in .endsWith().
Seamless continuation of the new pattern for advanced type inference.


1298-1302: Parametric .length().
This generic returns an intersection type (Output & { length: StringLength }) for more precise type inference, which is a neat approach.


1317-1317: Trim check.
Performing a .trim() with an added check is straightforward and consistent.


1321-1321: Type-narrowing for toLowerCase().
This properly narrows the string output to Lowercase<Output>.


1325-1325: Type-narrowing for toUpperCase().
Similarly extends type inference to Uppercase<Output>.


1744-1744: Repeated _assertParsedDataType usage.
No further concerns beyond earlier comments on this no-op TS assertion.


1957-1957: Repeated _assertParsedDataType usage.
No further concerns beyond earlier comments on this no-op TS assertion.


2211-2211: Repeated _assertParsedDataType usage.
No further concerns beyond earlier comments on this no-op TS assertion.


2262-2262: Repeated _assertParsedDataType usage.
No further concerns beyond earlier comments on this no-op TS assertion.

src/types.ts (28)

191-217: Well-documented type-narrowing utility
This new _assertParsedDataType method introduction and its accompanying doc comment thoroughly explain its purpose for narrowing input data types from any. The overloads for ParseInput and ParseContext look consistent.


800-805: Generic output type for ZodString
Allowing ZodString to be generic over Output extends string is a valuable enhancement for typed string transformations (e.g., case conversions). This approach cleanly aligns with the PR’s objectives.


820-820: Proper usage of _assertParsedDataType
Ensuring the call to _assertParsedDataType<string>(input) after verifying parsedType === ZodParsedType.string solidifies type correctness at the TS level.


978-978: Verify string containment
The new includes check logically uses input.data.includes(check.value, check.position). Since ZodParsedType.string is already enforced, this is safe.


992-992: Starts-with validation
The updated startsWith check properly uses input.data.startsWith(check.value). Looks good.


1002-1002: Ends-with validation
Similar to the startsWith approach, this is a straightforward endsWith validation.


1112-1112: Returning final string result
Returning { status: status.value, value: input.data } ensures the parse result is correctly propagated as valid output.


1127-1128: Chaining checks with type refinement
The _addCheck<CheckOutput extends string = Output> method returns a new ZodString<CheckOutput>, allowing the schema to refine the inferred output type.


1254-1257: Enhanced .includes() signature
Accepting value: util.inferCase<Output> helps maintain consistent casing in includes checks.


1266-1266: Enhanced .startsWith() signature
Expanding startsWith to accept util.inferCase<Output> further strengthens type correctness.


1274-1274: Enhanced .endsWith() signature
Similarly, endsWith now infers the correct case from the generic Output.


1298-1302: Typed .length() method
This implementation captures the exact length at the type level, returning Output & { length: StringLength }. It’s a powerful approach for structural string constraints.


1317-1317: Trim check
The simple trim check is logically placed and well-structured.


1321-1321: Lowercase transform
toLowerCase() returning Lowercase<Output> ensures strong TypeScript guarantees post-transformation.


1325-1325: Uppercase transform
toUpperCase() returning Uppercase<Output> consistently mirrors the toLowerCase() approach.


2354-2357: Introduction of "exact" array cardinality
The new "exact" option in ArrayCardinality and related ExactArrayLength type paves the way for perfectly sized arrays.


2360-2361: Additional parameters in arrayOutputType
Extending array output types with generics for cardinality and exact length matches the new design.


2370-2371: ZodArray extended generic signature
ZodArray<T, Cardinality, ArrayLength> elegantly encapsulates advanced array length constraints.


2395-2395: Ensuring correct array type
this._assertParsedDataType<T[]>(ctx); helps confirm type-narrowing after verifying ctx.parsedType === ZodParsedType.array.


2466-2469: Generic .min()
min<MinLength extends number> is consistent with the new typed approach for array lengths.


2476-2479: Generic .max()
Similarly, max<MaxLength extends number> aligns with typed array constraints.


2486-2489: Exact .length()
Exposing a specialized ZodExactArray for fixed-length arrays is a clean solution for precise cardinalities.


2496-2496: .nonempty() usage
Internally delegating to .min(1) is a neat, DRY approach.


2515-2519: Distinct array types
ZodNonEmptyArray and ZodExactArray provide a clear naming scheme for special cardinalities.


3894-3901: Key–value pairing with lazy path
Mapping [key, value] to ParseInputLazyPath for each entry ensures detailed context in ZodMap.


4629-4629: Promise resolution chain
Wrapping promisified.then with the subsequent parse logic is consistent with ZodPromise usage.


5118-5118: Confirming NaN type
Calling this._assertParsedDataType(input); for ZodNaN cements the type guarantee in the parse route.


5286-5286: Read-only parse
The _assertParsedDataType usage in ZodReadonly helps ensure the internal type matches expectations before freezing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant