Skip to content

Update to Zod 4.5 - #19

Merged
gajus merged 23 commits into
gajus:mainfrom
colinhacks:zod-4.5
Aug 29, 2026
Merged

gajus merged 23 commits into
gajus:mainfrom
colinhacks:zod-4.5

Conversation

@colinhacks

@colinhacks colinhacks commented Aug 29, 2026 •

Copy link
Copy Markdown
Contributor

Zod 4.5 is out now. Spun up a PR to update zod-compiler with some of the changes. Feel free to use it as a starting point (or close and do your own thing 👍).

Colin McDonnell and others added 14 commits August 29, 2026 09:02
zod 4.5 installs the English locale on the first schema construction, so a
module-scope z.config().localeError is undefined and every harness that
passed it as __zcMsg lost its messages (or, for hosted recursive validators,
formatted them after the inner finalizer had already stripped input).
Export the production __zcMsg from parity-harness and use it everywhere.
Also repair tests/compat.mjs, whose Function params never matched the
finalizer's bindings.
… stranded

zod 4.4 moved parse/safeParse/parseAsync/safeParseAsync/~standard to the
prototype: each is a getter that binds the method on first read and installs
the bound copy as an own property, so a fresh schema has no own descriptor to
snapshot. The accessor's fallback answered that empty snapshot with undefined,
which left a frozen schema, a target that refused every rollback, and two
copies of jit() on one schema with `safeParse is not a function`.

The fallback now reads the prototype getter with the schema as receiver, which
also hands the stranded slot over to zod's own copy where the target allows
it, and binds through a stand-in that inherits from the schema where it does
not (frozen, or a wrapper that vetoes defineProperty). A write to a pending
slot runs zod's own setter once the accessor is gone, instead of defining a
copy by hand.

Tests now describe both shapes a schema arrives in — untouched, or already
carrying zod's own copies — and the register fixtures expect the `bound
safeParse` an uninstrumented schema shows after one read.
…nd non-finite received

String length checks now measure Unicode code points the way zod 4.5 does,
paying for the count only inside the band of UTF-16 lengths where the verdict
is in doubt, so an ordinary ASCII string never reaches the new `__zcCpl`
helper and `min(1)` still compiles to `length>=1`.

`z.url()` reproduces `parseURLObject`: an http(s)-protocol check without
`normalize` rejects input lacking `://` with `note: "Invalid URL format"`
before the parser runs, and the returned value has tabs and newlines deleted
(`stripTabAndNewline`).

`z.partialRecord()` is extracted from `def.partial` — the key schema keeps its
values in 4.5 — and a partial record whose key schema declares a finite set
reports a stray key as one `unrecognized_keys` after the walk, as `$ZodRecord`
does, rather than `invalid_key`.

A non-finite number reports `received: String(input)`, so `-Infinity` reads as
such. Test pins updated for the 4.5 `httpUrl` issue shapes, the root `$ref`
`z.toJSONSchema()` emits for a schema with an id, and the locale that zod now
installs on first schema construction (read per call, as production does).
zod 4.5 skips a declared `__proto__` shape key outright, so the extractor
stores a placeholder for it and every property loop (eager walk, fast check,
build pass) skips it; the catchall passes skip an undeclared `__proto__` the
same way, while the strict pass still reports it.

`unrecognized_keys` now carries `continue: true` upstream, so it leaves the
aborting-code list: an object's own refine runs after it and a union no longer
counts a strict option as aborted.

`.catch()` hands its callback the raw input it was given rather than the
inner's partially built value, so the compiled catch snapshots its input
before the inner runs.

A check stamps its owning schema onto every issue it raises, so a schema-level
`error` now covers that schema's own checks after the check's own message:
every emitter resolves `check.message ?? typeMsg`, and the superRefine helper
takes the schema message as an extra argument.

Records: zod now filters non-enumerable keys, so that known divergence becomes
a parity pin and the README row names the one that remains (symbol keys). The
raw `ctx.issues` pin gains the `schema` key zod adds.
# Conflicts:
#	tests/zod-parity.test.ts
# Conflicts:
#	src/core/codegen/emit-issue.ts
#	src/core/codegen/schemas/tuple.ts
#	tests/zod-parity.test.ts
Copilot AI lite review requested due to automatic review settings August 29, 2026 16:56

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@colinhacks colinhacks closed this Aug 29, 2026
@gajus

gajus commented Aug 29, 2026

Copy link
Copy Markdown
Owner

@colinhacks only started reviewing this a second ago. Any reason for closing it?

@colinhacks colinhacks reopened this Aug 29, 2026
@colinhacks

colinhacks commented Aug 29, 2026 •

Copy link
Copy Markdown
Contributor Author

No, sorry! But if you want to close and handle all this yourself go ahead! I asked an agent to update your benchmarks against Zod 4.5 but it did the whole migration...😅

@gajus
gajus merged commit c7ff42a into gajus:main Aug 29, 2026
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 2.0.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants