Skip to content

Codegen: hardening follow-ups for Zod ergonomics passes (post #1972/#1975) #1979

@bokelley

Description

@bokelley

Consolidated non-blocking follow-ups from review of #1972 (unwrapNamedRecordUnionIntersections) and #1975 (canSafelyMerge object intersections). Identifier-boundary guard and readBalancedBody loud-fail were already addressed in-line on #1972. Remaining items below are explicitly flagged "non-blocking — file as issues" by reviewers.

From #1972 follow-up review

.d.ts regression grep is shape-fragile

test/lib/zod-schemas.test.js:46 — pattern z\.ZodIntersection<z\.ZodUnion<readonly \[z\.ZodRecord depends on Zod v4's exact emitter format (the readonly tuple prefix in particular). If Zod adjusts tuple stringification the test goes vacuously green.

Fix shape: pair with a positive assertion — confirm ProductSchema._def.typeName === 'ZodObject' so the test fails closed if regression returns under a different format.

Pass 3/4 docstrings should mention z.object( shape gate

scripts/generate-zod-from-ts.ts:585-592 (Pass 3) and :636-645 (Pass 4) describe the inline pattern but the body-prefix check is what makes the unwrap safe. Call that constraint out so the next editor doesn't broaden the right-hand side.

unionSchemaNames collection by recursive union-of-unions

Today collectRedundantRecordUnionSchemaNames only sees one-level unions. If ts-to-zod ever emits z.union([z.union([RecordA, RecordB]), z.union([RecordC, RecordD])]), the inner unions won't be detected as redundant. Pre-existing gap, not a regression.

From #1975 follow-up review

Silent skip in TOOL_INPUT_SHAPES construction

src/lib/schemas/index.ts:72-75 does flatMap → [] when shapeOf returns undefined. The added test catches the TOOL_REQUEST_SCHEMAS codepath, but any other consumer of .shape on a non-mergeable intersection still silently degrades. Replace the silent drop with an explicit throw at TOOL_INPUT_SHAPES construction so future regressions surface loudly.

Subsume earlier extractObjectSchema helper?

The extractObjectSchema / ProductObjectSchema workaround in the recent ProductSchema DX fix (#1971 draft) solved the same problem class one layer up. Worth checking whether this codegen-level fix lets us deprecate it.

isPlainZodObjectTail keep-in-sync

scripts/generate-zod-from-ts.ts:622 currently allows only .passthrough() / .strict() / .strip(). If ts-to-zod starts emitting .catchall(z.unknown()) for an index signature, safe merges silently stop happening. One-line comment near the tail-list pointing at ts-to-zod's emission would buy a heads-up later.

Typed-export regex

scripts/generate-zod-from-ts.ts:738 uses (?::[^=]+)? for optional type annotations. Today every typed export is plain; a future schema picking up a type annotation with = inside (default-typed generic) would silently fall out of the rewriter's view. Latent papercut.

Test harness spawns npx tsx per test

test/generate-zod-object-intersections.test.js:11 uses mkdtempSync inside the repo root. Cold tsx startup × 3 noticeably extends npm test, and a SIGKILL mid-run leaves orphan .zod-object-intersections-* dirs in the working tree. os.tmpdir() would be safer, or import the rewriter as a real export and skip the spawn.


None of these block #1972 or #1975. Filing as a single issue rather than per-item because they share an owner (the codegen post-processor) and would naturally cluster.

cc: surfaced by code-reviewer + javascript-protocol-expert parallel review.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions