fix: make generated types agree with their valibot schemas - #28
Merged
Merged
Conversation
JSON object keys are always strings, so Record<string | number, T> made Object.entries-style helpers type keys as string | number. Co-Authored-By: LLM <noreply@block65.dev>
valibot wraps every nullable schema in v.nullable, but the TS type for an object, an array, or a non-temporal string returned before the null union was applied. A validated response could then hold null where its type said a value was always there, as docker's IPAMConfig does. Co-Authored-By: LLM <noreply@block65.dev>
A property was optional only when its parent declared `type: "object"`, so a schema with properties and no type made every property required, while valibot made the unlisted ones optional. The parent is now judged by isObjectSchema, as the type dispatch already is. Co-Authored-By: LLM <noreply@block65.dev>
getDependents followed a $ref only at a few top-level positions, so a ref under an items object, an additionalProperties value, or combinators beside properties was left out of the sort. Its schema could register before its target, and the TS side then emitted `never` while valibot emitted v.unknown(), which dropped out of unions (openai's RunObject.tools). It now walks the whole schema, and an unresolved ref throws on both sides rather than emitting a placeholder. Co-Authored-By: LLM <noreply@block65.dev>
An empty schema, an untyped record value, or an array without items was Jsonifiable in TS, which admits toJSON objects that JSON.parse never yields. valibot typed the same values as unknown, and an empty schema there required a record, rejecting other JSON values. Both sides now use type-fest's JsonValue: valibot checks the value recursively with a lazy union typed v.GenericSchema<JsonValue>, emitted only in a module that uses it. Co-Authored-By: LLM <noreply@block65.dev>
This was referenced Sep 27, 2026
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.
Each commit fixes one way the generated TS type and its valibot schema disagreed. Each comes with whole-file snapshot specs, and the fixtures are regenerated. It works on the published rest-client 15.
additionalPropertiesrecords are keyed bystring, notstring | number.null. A combinator folds in its members'nullable: a union is nullable if any member is, an intersection only if every member is.required: an object schema that omitstypestill honoursrequired. openai'sOpenAIFile.status_detailsis now optional.$ref(items objects, additionalProperties, combinators beside properties). Before, a schema could register ahead of its target, which gaveneverin TS andv.unknown()in valibot, and silently dropped union members (openaiRunObject.tools). An unresolved ref now throws.{}, an untyped record value, an array withoutitems) isJsonValuein TS. In valibot it's a recursivejsonValueSchema, emitted only in a module that uses it. Before, TS saidJsonifiable, valibot saidunknown, and an empty schema only accepted objects.Stacked on #27.