Skip to content

chore: upgrade zod to 4.5.4 and drop the manual root $ref hoisting - #1969

Merged
dinwwwh merged 1 commit into
middleapi:mainfrom
dinwwwh:claude/zod-upgrade-ref-hoisting-41f12f
Aug 31, 2026
Merged

chore: upgrade zod to 4.5.4 and drop the manual root $ref hoisting#1969
dinwwwh merged 1 commit into
middleapi:mainfrom
dinwwwh:claude/zod-upgrade-ref-hoisting-41f12f

Conversation

@dinwwwh

@dinwwwh dinwwwh commented Aug 31, 2026

Copy link
Copy Markdown
Member

Upgrades zod from 4.4.3 to 4.5.4 across the monorepo and removes the manual root $ref hoisting from ZodToJsonSchemaConverter, which zod now does natively. @orpc/zod requires zod >=4.5.0 as a result.

Fixes

colinhacks/zod#6029, the PR the workaround comment pointed at, shipped in zod 4.5.0. A root schema carrying a registry id is now hoisted into $defs with a $ref left at the root, RFC 6901 pointer encoding included, so the hand-rolled version in the converter is redundant. The version floor was confirmed by converting a schema with .meta({ id: "User" }) under each release: 4.4.3 inlines it, 4.5.0 emits { $ref: "#/$defs/User", $defs: { User: {...} } }.

Behavior changes from zod 4.5

Two OpenAPI generator e2e expectations changed. Both come from zod itself, not from removing the workaround — they reproduce with the old converter still in place.

  • Object intersections are merged into a single object schema instead of emitting allOf. Non-object intersections (two constrained strings, say) still emit allOf. This is visible in generated OpenAPI documents for anyone using intersection schemas and may be worth a release note.
  • A root schema with an id is no longer inlined, so the find response in the crud test is now a $ref to the shared Planet component instead of an inlined copy. That brings the assertion in line with the test's own name.

The generator's own allOf path, which comes from combining multiple .input()/.output() calls rather than from zod, stays covered by openapi-generator.test.ts and a sibling test in the same file.

Testing

pnpm test passes (3310 tests, plus the bun, cloudflare, and nest suites), as do pnpm type:check and pnpm lint. packages/zod/src/converter.ts remains at 100% statement, branch, function, and line coverage, so the removal left nothing unreachable behind.

The dropped converter test fabricated a colliding $defs entry through a mocked toJSONSchema to exercise the workaround's root__0 rename. That collision cannot occur now that zod writes the root def into the same map it builds. The two remaining root-$ref tests still cover escaped JSON pointers and a custom metadata registry.

The lockfile diff is zod-only aside from pnpm peer-key normalization, which changed no package versions.

Zod 4.5.0 shipped colinhacks/zod#6029, which hoists a root schema carrying
a registry id into `$defs` and leaves a `$ref` at the root, including RFC
6901 pointer encoding. That is exactly what ZodToJsonSchemaConverter was
doing by hand, so the workaround is removed and `@orpc/zod` now requires
zod >= 4.5.0.

Zod 4.5 also merges object intersections into a single object schema
instead of emitting `allOf`, and a root schema with an id is no longer
inlined, so two OpenAPI generator e2e expectations are updated to match.
@pkg-pr-new

pkg-pr-new Bot commented Aug 31, 2026

Copy link
Copy Markdown
More templates

@orpc/ai-sdk

npm i https://pkg.pr.new/@orpc/ai-sdk@1969

@orpc/arktype

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

@orpc/bun

npm i https://pkg.pr.new/@orpc/bun@1969

@orpc/client

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

@orpc/cloudflare

npm i https://pkg.pr.new/@orpc/cloudflare@1969

@orpc/contract

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

@orpc/experimental-effect

npm i https://pkg.pr.new/@orpc/experimental-effect@1969

@orpc/evlog

npm i https://pkg.pr.new/@orpc/evlog@1969

@orpc/hibernation

npm i https://pkg.pr.new/@orpc/hibernation@1969

@orpc/json-schema

npm i https://pkg.pr.new/@orpc/json-schema@1969

@orpc/experimental-msw

npm i https://pkg.pr.new/@orpc/experimental-msw@1969

@orpc/nest

npm i https://pkg.pr.new/@orpc/nest@1969

@orpc/next

npm i https://pkg.pr.new/@orpc/next@1969

@orpc/node

npm i https://pkg.pr.new/@orpc/node@1969

@orpc/openapi

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

@orpc/opentelemetry

npm i https://pkg.pr.new/@orpc/opentelemetry@1969

@orpc/pinia-colada

npm i https://pkg.pr.new/@orpc/pinia-colada@1969

@orpc/pino

npm i https://pkg.pr.new/@orpc/pino@1969

@orpc/publisher

npm i https://pkg.pr.new/@orpc/publisher@1969

@orpc/ratelimit

npm i https://pkg.pr.new/@orpc/ratelimit@1969

@orpc/server

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

@orpc/shared

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

@orpc/swr

npm i https://pkg.pr.new/@orpc/swr@1969

@orpc/tanstack-query

npm i https://pkg.pr.new/@orpc/tanstack-query@1969

@orpc/trpc

npm i https://pkg.pr.new/@orpc/trpc@1969

@orpc/valibot

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

@orpc/zod

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

commit: e161799

@codecov

codecov Bot commented Aug 31, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@codspeed-hq

codspeed-hq Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 30 untouched benchmarks


Comparing dinwwwh:claude/zod-upgrade-ref-hoisting-41f12f (e161799) with main (870096d)

Open in CodSpeed

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes

  • zod 4.4.3 → 4.5.4 across the monorepo — every package.json (root, packages, playgrounds, apps/content) bumps zod to ^4.5.4, and @orpc/zod's peerDependencies floor moves from >=4.3.5 to >=4.5.0. The lockfile keeps the diff zod-only (plus one express-rate-limit peer-key normalization), and no pnpm-workspace.yaml age-exclude entry is involved.
  • Native root $ref hoisting replaces the workaroundZodToJsonSchemaConverter no longer hand-rolls the root-id → { $ref, $defs } hoist (RFC 6901 pointer encoding included); zod ≥ 4.5.0 does it natively. The fabricated collision test is removed; the two realistic root-$ref tests (escaped pointers, custom registry) remain and pass, with converter.ts still at 100% coverage.
  • OpenAPI generator e2e expectations updated — object intersections now emit a single merged object schema instead of allOf (non-object intersections keep allOf), and an id-carrying root output schema now references #/components/schemas/Planet rather than inlining.

I verified the zod claims empirically: under 4.5.4 a root meta({ id }) schema converts to { $ref: '#/$defs/…', $defs: { … } } with pointer escapes (root~/~0~1), object intersections merge into one object while non-object intersections stay allOf (both checked directly against toJSONSchema), and the generator's param/body extraction already resolves root local refs. The error the removed test fabricated a collision to hide — two schemas sharing an id in one tree — is thrown by zod itself ("Duplicate schema id…") in both 4.4.3 and 4.5.4, so the workaround's name__0 rename branch was only reachable through the mock. pnpm vitest run over the zod, json-schema, openapi, and ai-sdk packages (660 tests) plus the two touched e2e files all pass, as does type:check on the affected packages.

ℹ️ Behavior changes ship under a chore: title

This PR changes generated-OpenAPI output for real users: object intersections no longer emit allOf, and any root schema carrying meta({ id }) now surfaces as a shared component $ref where it previously inlined. Combined with the @orpc/zod peer floor rising to >=4.5.0 — a semver-breaking floor for consumers still pinned to zod 4.4.x — this is more than a mechanical dependency bump. The changelog is drafted from PR titles and the repo doesn't use changesets, so it's worth confirming the next release notes call these out explicitly; chore: may not surface them.

Technical details
# Release-note visibility

## Affected sites
- `packages/openapi/tests/openapi-generator/composed-schemas.test.ts:90` — object-intersection output moved from `allOf` to a merged object; any user generating docs from intersection schemas sees a different document shape.
- `packages/openapi/tests/openapi-generator/crud.test.ts:167` — id-carrying root output now renders as `{ $ref: '#/components/schemas/Planet' }` instead of an inline object.
- `packages/zod/package.json:49``peerDependencies.zod` floor raised `>=4.3.5``>=4.5.0`.

## Required outcome
- Confirm the next release notes mention (a) the `allOf` → merged-object change for object intersections and (b) the raised zod peer floor.

## Open questions for the human
- Is the `chore:` classification intended given the user-visible OpenAPI changes and the peer-floor bump?
- Should the zod/openapi docs pages note the intersection-merging behavior? (Nothing in `apps/content/docs` currently references `allOf`, so no content is stale.)

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@dinwwwh
dinwwwh merged commit b8d527a into middleapi:main Aug 31, 2026
11 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.

1 participant