Skip to content

chore: move the contracts to zod 4 - #283

Merged
AKogut merged 1 commit into
mainfrom
chore/zod-4
Aug 18, 2026
Merged

chore: move the contracts to zod 4#283
AKogut merged 1 commit into
mainfrom
chore/zod-4

Conversation

@AKogut

@AKogut AKogut commented Aug 18, 2026

Copy link
Copy Markdown
Owner

Closes #269.

Smaller than the issue feared

Two z.record(schema) calls needing an explicit key schema, and five .default({}) on objects whose fields all carry defaults — v4 types .default against the output, and .prefault is the replacement for the input-side behaviour. That was the whole breaking surface.

The wire format was the thing to watch

apps/api returns zod's own message text in its 400 bodies, so a zod major moves what API clients see. Verified against a live instance — the shape is unchanged and the content is better:

before  {"path":"resource.commitSha","message":"Invalid"}
after   {"path":"resource.commitSha",
         "message":"Invalid string: must match pattern /^[0-9a-f]{7,40}$/i"}

A valid batch still returns 202.

zod-to-json-schema is gone

v4 generates JSON Schema itself, so the OpenAPI document and the API reference now come from the same library that validates the requests — one fewer place for the description of a request to disagree with its enforcement.

The swap brought a defect, and then a second one

zod emits $defs beside a recursive schema while anchoring the $ref at the document root, where nothing lives. Every reference from the recursive attributes type dangled, and a generated client stops there.

Hoisting the definitions to the root exposed a second: a recursive definition refers to itself, so copying it verbatim left that reference pointing at the pre-hoist name. The fix rewrites references inside the definitions as well as around them.

The new guard walks every $ref in the document and resolves it against the document. It is what caught the second bug — after I had already written what I thought was the fix for the first. It also asserts the document still contains references, so it cannot pass by having nothing to check.

Verified

18 operations · root $defs: ["IngestRunBatch___schema0"] · dangling refs: 0

62/62 turbo tasks under --force, which runs the packages concurrently rather than from cache — the same check that caught the Prisma 7 regression in #268.

The surface was smaller than the issue feared: two `z.record` calls needing an
explicit key schema, and five `.default({})` on objects whose fields all have
defaults, which v4 types against the output rather than the input — `.prefault`
is the replacement.

The wire format is what mattered, since apps/api returns zod's own message text
in its 400 bodies. It is unchanged in shape and better in content:

  before  {"path":"resource.commitSha","message":"Invalid"}
  after   {"path":"resource.commitSha",
           "message":"Invalid string: must match pattern /^[0-9a-f]{7,40}$/i"}

zod-to-json-schema is dropped entirely — v4 generates JSON Schema itself, so
the OpenAPI document and the API reference now come from the same library that
validates the requests.

That swap brought a defect worth naming. zod emits `$defs` beside a recursive
schema while anchoring the `$ref` at the document root, so every reference from
the attributes type dangled. Hoisting them exposed a second one: a recursive
definition refers to itself, and copying it verbatim left that reference
pointing at the old name. The new guard walks every `$ref` in the document and
resolves it, which is what caught the second bug after I had written the fix
for the first.

Verified against a live API: 18 operations, no dangling reference, a valid
batch still accepted with 202. Full suite under `turbo run test --force`, which
runs the packages concurrently.
@AKogut
AKogut merged commit eae093b into main Aug 18, 2026
9 checks passed
@AKogut
AKogut deleted the chore/zod-4 branch August 18, 2026 12:43
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.

zod 4: the contracts package is the single source of truth, so the major lands everywhere at once

1 participant