feat: Map ZodTuple to a fixed-length array schema - #180
Merged
Conversation
Tuples fell through to catchAllParser, so a `z.tuple()` field emitted an empty schema with no `type`. Downstream tooling then has nothing to work with: the Seam blueprint generator skips any property whose schema lacks a type, so tuple fields silently dropped out of the API documentation. OpenAPI 3.0 has no positional item schemas, so a tuple becomes a fixed-length array whose `items` accept every position's schema — a single schema when the positions agree, otherwise a `oneOf`. A `.rest()` type contributes its schema and drops `maxItems`, since a variadic tuple has no upper bound. `prefixItems` would describe the positions exactly, but it is OpenAPI 3.1 only and would produce specs that 3.0 tooling rejects. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013zayu5zwnZjsQXjNhDmsLF
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Tuples fell through to
catchAllParser, so az.tuple()field emitted an empty schema with notype. Downstream tooling then has nothing to work with — the Seam blueprint generator skips any property whose schema lacks a type, so tuple fields silently dropped out of the API documentation:OpenAPI 3.0 has no positional item schemas, so a tuple becomes a fixed-length array whose
itemsaccept every position's schema — a single schema when the positions agree, otherwise aoneOf. A.rest()type contributes its schema and dropsmaxItems, since a variadic tuple has no upper bound.prefixItemswould describe the positions exactly, but it is OpenAPI 3.1 only, and consumers such asseam-connectemitopenapi: "3.0.0"and lint the result, so emitting it would produce specs their tooling rejects.Generated schemas, from the three fields added to the example app's
/api/todo/addbody:z.tuple([z.string().datetime(), z.string().datetime()]){type: "array", items: {type: "string", format: "date-time"}, minItems: 2, maxItems: 2}z.tuple([z.number(), z.string()]){type: "array", items: {oneOf: [{type: "number", format: "float"}, {type: "string"}]}, minItems: 2, maxItems: 2}z.tuple([z.string()]).rest(z.string()){type: "array", items: {type: "string"}, minItems: 1}getTupleRestis added to the zod compat layer alongside the existinggetTupleItems; both Zod 3 and Zod 4 keep the rest type atdef.rest, so the existinggetDefsplit covers it.zod-to-tsalready handledZodTuple, so the generated TypeScript types are unchanged — this only affects the OpenAPI output.Testing
yarn workspace nextlove build,typecheck, andtest(8 tests) pass.Generated by Claude Code