Summary
deepPartial from @traversable/zod throws on any schema whose tree contains a refined object (.refine() / .superRefine()), because it internally calls zod's .partial() — which zod has rejected on object schemas containing refinements since v4.3.0.
Reproduction
import { z } from 'zod'
import { deepPartial } from '@traversable/zod/deep-partial'
// ❌ Error: .partial() cannot be used on object schemas containing refinements
deepPartial(z.object({ a: z.string() }).refine(() => true))
// ❌ Same error when the refinement is nested anywhere in the tree:
deepPartial(z.object({ inner: z.object({ a: z.string() }).refine(() => true) }))
// ✅ Works without a refinement:
deepPartial(z.object({ a: z.string() }))
Standalone:
mkdir repro && cd repro && npm init -y && npm pkg set type=module
npm install zod@4.4.3 @traversable/zod@0.0.57
# put the snippet above in repro.mjs
node repro.mjs
Actual
Error: .partial() cannot be used on object schemas containing refinements
at Module.partial (zod/v4/core/util.js)
at @traversable/zod/src/deep-partial.ts:91
Expected
deepPartial should produce a deep-partial schema for trees that contain refined objects — e.g. by partialing the underlying object shape and dropping the refinement (a .refine() predicate can't be preserved through a structural partial anyway), instead of calling .partial() directly on the refined wrapper.
This shows up for any nested refined field — in our case a shared LocalizedString = z.object({...}).refine(...) used across many models, so deepPartial(SomeModel) fails at module-load for every model that embeds it.
Root cause / zod version boundary
zod introduced the .partial()-on-refinements restriction in 4.3.0:
| zod |
z.object({...}).refine(...).partial() |
| ≤ 4.2.1 |
✅ allowed |
| 4.3.0 → 4.4.3 (latest) |
❌ throws |
@traversable/zod's peer is zod: 4, so any consumer on current zod (4.3+/4.4) hits this.
Environment
@traversable/zod 0.0.57 (latest)
zod 4.4.3 (also reproduces on 4.3.0)
- node v24.12.0
Possible fix
In deep-partial.ts, when a node is an object schema carrying refinements, partial the inner shape / unwrap to the underlying ZodObject def and rebuild, rather than calling .partial() on the refined wrapper.
Summary
deepPartialfrom@traversable/zodthrows on any schema whose tree contains a refined object (.refine()/.superRefine()), because it internally calls zod's.partial()— which zod has rejected on object schemas containing refinements since v4.3.0.Reproduction
Standalone:
Actual
Expected
deepPartialshould produce a deep-partial schema for trees that contain refined objects — e.g. by partialing the underlying object shape and dropping the refinement (a.refine()predicate can't be preserved through a structural partial anyway), instead of calling.partial()directly on the refined wrapper.This shows up for any nested refined field — in our case a shared
LocalizedString = z.object({...}).refine(...)used across many models, sodeepPartial(SomeModel)fails at module-load for every model that embeds it.Root cause / zod version boundary
zod introduced the
.partial()-on-refinements restriction in 4.3.0:z.object({...}).refine(...).partial()@traversable/zod's peer iszod: 4, so any consumer on current zod (4.3+/4.4) hits this.Environment
@traversable/zod0.0.57 (latest)zod4.4.3 (also reproduces on 4.3.0)Possible fix
In
deep-partial.ts, when a node is an object schema carrying refinements, partial the innershape/ unwrap to the underlyingZodObjectdef and rebuild, rather than calling.partial()on the refined wrapper.