Repository navigation
fix(codegen): keep every union intersection compact under Zod 4.6 - #3408
Conversation
There was a problem hiding this comment.
Ladon verdict: Approve
Approve.
Clean, well-scoped patch. The single load-bearing change — postProcessUnionIntersectionOperands in scripts/generate-zod-from-ts.ts — annotates inline z.union([...]) operands of .and() intersections so Zod 4.6 stops distributing A & (X|Y) into (A&X)|(A&Y). Operand detection (both left- and right-operand cases) is correctly scoped; string literals are skipped via skipQuotedOrRegexLiteral, and already-annotated/chained unions are deliberately excluded (0 double-.describe() matches in generated output). Validation behavior is metadata-only and unchanged. Test coverage is thorough (all six branches plus exact-count and round-trip-through-z.toJSONSchema assertions). Changeset is present and correctly scoped (dev-only Zod 4.6.7 bump for GHSA-mrhx-c7xc-m3w5, peer range unchanged); bundle-size budgets and docs updated consistently.
No blocking or medium findings. high_risk is false, gated_paths is false, and no author team gate applies. Falls through to row 9.
Zod 4.6's toJSONSchema distributes an intersection over a union operand, copying the other operand into every branch. #3406 annotated the Product format declaration; this annotates every inline union operand of a generated intersection, so the rendered JSON Schema across all exports returns to the Zod 4.4.3 size (70.5 MB -> 61.2 MB in total). Validation is unchanged. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
5d1d1bd to
a085cfb
Compare
There was a problem hiding this comment.
Ladon verdict: Approve
Approve — clean pass, metadata-only codegen fix.
This PR adds a codegen post-processing step (postProcessUnionIntersectionOperands at scripts/generate-zod-from-ts.ts:35) that appends a .describe(...) note to inline z.union([...]) operands of .and(...), preventing Zod 4.6 from distributing intersections over unions (PlacementSchema 339 KB → 167 KB). Left/right-operand detection and the chained-case / string-literal guards are correct; all six fixtures and the Zod round-trip test match.
Change is metadata-only: parsing, wire shape, enums, tasks, and semantics are unchanged, so pathway-back (DR-0033) is N/A and witness-not-translator is unaffected. Changeset is present and the patch type fits a render-size regression fix.
No critical/high/medium findings. Consistent with the prior approve. No team gate, gated_paths: false, high_risk: false. Falls through to row 9.
Follow-up to #3406. That PR already bumped
zodto 4.6.7, sonpm audit --audit-level=highis clean on main. It also annotated the Product format declaration soProductSchemastays under the 1 MiB OpenAPI bound. This PR applies the same fix to every other generated intersection with a union.Problem
Zod 4.6's
toJSONSchemadistributes an intersection over a union operand:A & (X | Y)is emitted as(A & X) | (A & Y), which copiesAinto every branch. Validation is the same, but the rendered schema grows. On main today, comparing every exported schema between Zod 4.4.3 and 4.6.7 (z.toJSONSchema,io: 'input'):ProductSchemaPlacementSchemaInlineDeclarationSchemaMCPWebhookPayloadSchemaGeoPostalAreasSchemaAdopters bring their own Zod (peer
^4.1.5), so anyone on 4.6 rendering these schemas for OpenAPI, MCPtools/listor form tooling sees the larger output.Change
A new generator pass,
postProcessUnionIntersectionOperandsinscripts/generate-zod-from-ts.ts, adds.describe("Must also satisfy at least one of these alternatives.")to every inlinez.union([...])that is a left or right operand of.and(...). That's 129 sites. Zod declines the distribution when the union carries an annotation, so the rendering stays a singleallOf.src/lib/types/schemas.generated.tsdiffers from main only by those inserted.describe(...)calls. I checked this by stripping them and diffing against main: the files are identical. Regeneration is idempotent..describe()only adds metadata. Zod < 4.6 renders the sameallOfas before, plus the description..and(VendorPricingSchema). These are the 3 remaining > 20% cases, all under 6 KB. I left them alone because describing a referenced schema clones it and would break adopter$refregistration.Tests
allOfwith the shared properties emitted once.build:lib,typecheck,ci:codegen-strict, prettier, and thezod-openapi-portability,schemas-browser,zod-schemasand generator intersection suites pass.🤖 Generated with Claude Code