Feature/zod v4#288
Conversation
Updates the zod peer dependency from v3 to v4 and adapts all form validation utilities to the revised API. Key changes: `ZodTypeAny` replaced by `ZodType`, internal `._def` sentinel replaced by `._zod`, parse result errors accessed via `.issues` instead of `.errors`, and `ZodEffects` / `ZodNativeEnum` removed from public type unions which no longer exist in v4's type system. BREAKING CHANGE: peerDependency `zod` bumped from `3.*` to `4.*`; consumers must upgrade to Zod v4
…rove validation error logging Zod v4 deprecated ZodRawShape in favour of the internal $ZodShape type from zod/v4/core. Updates all type casts and interface definitions to use the new type. Also improves the logSchemaErrors output to use z.prettifyError for human-readable error formatting instead of dumping raw validation internals.
There was a problem hiding this comment.
Pull request overview
This PR upgrades the form validation layer to be compatible with Zod v4, updating dependency versions and adjusting schema/error handling where Zod’s APIs and type surface have changed.
Changes:
- Bumps Zod from v3 to v4 in the package manifest and lockfile.
- Updates schema typing/helpers to use Zod v4 types and internal shape/issue structures.
- Updates
useFormvalidation flow to readerror.issuesand improves schema error logging viaz.prettifyError.
Reviewed changes
Copilot reviewed 4 out of 5 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| module/src/form/utils/validation.ts | Updates schema-to-zod conversion typing and switches error mapping to Zod v4 issues. |
| module/src/form/types.ts | Updates exported form/zod helper types for Zod v4 compatibility. |
| module/src/form/hooks/useForm.ts | Switches to error.issues and improves optional validation error logging output. |
| module/pnpm-lock.yaml | Locks updated Zod version and adds @types/node. |
| module/package.json | Updates Zod version ranges and adds @types/node dev dependency. |
Files not reviewed (1)
- module/pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (typeof incomingToZod === 'object') { | ||
| return z.object(unpackObject(incomingToZod as z.ZodRawShape)); | ||
| return z.object(unpackObject(incomingToZod as z.core.$ZodShape)); | ||
| } | ||
| return unpackValueToZod(incomingToZod); |
There was a problem hiding this comment.
unpackValueToZod can hit an infinite recursion for non-object, non-zod inputs (the final return unpackValueToZod(incomingToZod); never changes the value). Also, typeof incomingToZod === 'object' includes null, which would throw when unpackObject calls Object.keys. Consider handling null/primitives explicitly (e.g. throw a clear error for invalid schema values) instead of recursing.
| export const getMyZodErrors = (errors: z.core.$ZodIssue[], keyChainString?: string) => { | ||
| return errors | ||
| .filter(e => (keyChainString ? isMyKeyChainItem(keyStringFromKeyChain(e.path, 'dots'), keyChainString) : true)) | ||
| .filter(e => | ||
| keyChainString ? isMyKeyChainItem(keyStringFromKeyChain(e.path as KeyChain, 'dots'), keyChainString) : true | ||
| ) | ||
| .map(e => ({ | ||
| key: keyStringFromKeyChain(e.path, 'dots'), | ||
| key: keyStringFromKeyChain(e.path as KeyChain, 'dots'), | ||
| message: e.message, | ||
| })); |
There was a problem hiding this comment.
getMyZodErrors was updated for Zod v4 issues, but there are no unit tests covering its filtering/mapping behavior (especially important after switching from error.errors to error.issues and adjusting path handling). Add targeted tests that build a failing schema, call safeParse, and assert the returned {key,message} list for both root and nested keyChain filters.
| | ZodOptional<ZodNullable<ZodTypeAny>> | ||
| | ZodEffects<ZodTypeAny>; | ||
| ob: ZodObject<TProp & $ZodShape> | ||
| ) => ZodObject<$ZodShape> | ZodNullable<ZodType> | ZodOptional<ZodType> | ZodOptional<ZodNullable<ZodType>>; |
There was a problem hiding this comment.
IObjectOfZod.opts returns ZodObject<$ZodShape>, which drops the original object shape type (TProp) and can degrade type inference for callers chaining object validators. Consider preserving the generic (e.g. returning ZodObject<TProp & $ZodShape> or a generic ZodObject<...> tied to the input) so opts doesn't erase the object's typed shape.
| ) => ZodObject<$ZodShape> | ZodNullable<ZodType> | ZodOptional<ZodType> | ZodOptional<ZodNullable<ZodType>>; | |
| ) => ZodObject<TProp & $ZodShape> | ZodNullable<ZodType> | ZodOptional<ZodType> | ZodOptional<ZodNullable<ZodType>>; |
| "react": "19.x", | ||
| "react-dom": "19.x", | ||
| "zod": "3.*" | ||
| "zod": "4.*" |
There was a problem hiding this comment.
zod version ranges differ between peerDependencies (4.*) and devDependencies (^4.3.6). This can allow consumers to resolve older 4.x minors than the version you develop/test against, increasing the risk of runtime/type mismatches. Consider aligning the ranges (e.g. set the peer range to the same minimum you require) and keeping a single source of truth for the supported Zod v4 baseline.
| "zod": "4.*" | |
| "zod": "^4.3.6" |
| "peerDependencies": { | ||
| "date-fns": "2.x", | ||
| "react": "19.x", | ||
| "react-dom": "19.x", | ||
| "zod": "3.*" | ||
| "zod": "4.*" | ||
| }, |
There was a problem hiding this comment.
PR description says Zod was upgraded in "dependencies" and "devDependencies", but in package.json it's in peerDependencies (and devDependencies). If this is a published library change, consider updating the PR description (or the dependency section, if intended) to match how Zod is actually declared.
62cddbf to
3c3c424
Compare
# Conflicts: # module/src/form/hooks/useForm.ts # module/src/form/utils/validation.ts
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 5 changed files in this pull request and generated 3 comments.
Files not reviewed (1)
- module/pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (1)
module/src/form/types.ts:26
- There’s a stray
ßcharacter in this doc comment (type.ß), which looks like an accidental typo and will leak into generated docs / IDE tooltips. Please remove it.
/**
* Works out whether some data is an object, and array, or another type.ß
* Used by `formProp` to type the next argument in the array.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if (typeof incomingToZod === 'object') { | ||
| return z.object(unpackObject(incomingToZod as z.ZodRawShape)); | ||
| return z.object(unpackObject(incomingToZod as z.core.$ZodShape)); | ||
| } | ||
| return unpackValueToZod(incomingToZod); | ||
| }; |
There was a problem hiding this comment.
unpackValueToZod ends with return unpackValueToZod(incomingToZod);, which will recurse forever for any non-object value that reaches this point (e.g. undefined) and eventually stack overflow. Consider making the fallback a hard error (throw) or returning incomingToZod (or z.any()) so the function has a terminating base case.
| // eslint-disable-next-line no-underscore-dangle -- these are zod values | ||
| if ((incomingToZod as z.ZodAny)._zod) { |
There was a problem hiding this comment.
The check if ((incomingToZod as z.ZodAny)._zod) relies on a private/underscored internal property to detect Zod schemas. This is brittle across Zod releases and can break validation schema conversion if the internal marker changes. Prefer a public/stable detection approach (e.g. incomingToZod instanceof z.ZodType or another officially supported predicate) to avoid coupling to Zod internals.
| // eslint-disable-next-line no-underscore-dangle -- these are zod values | |
| if ((incomingToZod as z.ZodAny)._zod) { | |
| if (incomingToZod instanceof z.ZodType) { |
| if (isObjectOfZod(incomingToZod)) { | ||
| const obInner = unpackValueToZod(incomingToZod.schema) as z.ZodObject<z.ZodRawShape>; | ||
| const obInner = unpackValueToZod(incomingToZod.schema) as z.ZodObject<z.core.$ZodShape>; | ||
| return incomingToZod.opts ? incomingToZod.opts(obInner) : obInner; |
There was a problem hiding this comment.
This file now uses internal Zod v4 core types via z.core.$ZodShape / z.core.$ZodIssue. These $Zod* types are part of Zod’s internal core surface and increase the chance of breakage on patch upgrades. If possible, prefer public types exported from zod (or consistently import types from zod/v4/core in one place) rather than mixing z.core.* references in type assertions/signatures.
# Conflicts: # module/src/form/hooks/useForm.ts
Tightens the zod peer dependency from the broad `4.*` wildcard to `^4.3.6` to ensure a minimum compatible version is enforced while still allowing compatible minor/patch updates.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 4 out of 5 changed files in this pull request and generated 1 comment.
Files not reviewed (1)
- module/pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| import type { $ZodShape } from 'zod/v4/core'; | ||
|
|
There was a problem hiding this comment.
Importing $ZodShape from the deep path zod/v4/core couples this library's public type surface to a non-public Zod module path. Because types.ts is part of the emitted .d.ts, this can break consumers if the subpath isn't exported (or changes) in their installed Zod v4. Prefer using only public types exported from zod (or define your own shape type like Record<string, ZodType>), so consumers don't need zod/v4/* to exist.
| import type { $ZodShape } from 'zod/v4/core'; | |
| type ZodShape = Record<string, ZodType>; |
…add getMyZodErrors tests The $ZodShape type was previously imported directly from 'zod/v4/core', but should be accessed via z.core.$ZodShape to align with Zod v4's public API surface and avoid relying on an internal subpath export. Also adds comprehensive test coverage for the getMyZodErrors utility, covering root-level key filtering, nested key chain filtering, and empty input handling.
What's new?
This pull request upgrades the
zodvalidation library from version 3 to version 4 and updates the codebase to ensure compatibility with the new version. The changes include dependency updates, type adjustments, and modifications to how validation errors are handled and logged.Dependency upgrade:
zodfrom version 3.x to 4.x in bothdependenciesanddevDependenciesinpackage.json, and updated the lockfile accordingly. [1] [2] [3] [4] [5]Code compatibility updates:
zodto use the new type names and structures (e.g., replacedZodTypeAnywithZodType, adjusted object/array schema typing, and removed deprecated types). [1] [2] [3] [4] [5]getMyZodErrorsfunction and related validation logic to use the new error structure (issuesinstead oferrors) and updated type signatures. [1] [2]Validation and logging improvements:
useFormto usez.prettifyErrorfor clearer error output, and updated the logic to match the new structure of validation results in Zod v4.General code cleanup:
These changes ensure the codebase is compatible with Zod v4 and take advantage of improved type safety and error reporting.
Ticket number(s) in JIRA (if internal)
ARM-??
board
Checklist