Conversation
The typescript-zod target of quicktype drops `dependentRequired`, so the generated TimeInterval accepted `opens` without `closes` (and the reverse), and the objects composing it through allOf (DailyHour, ExceptionHour) inherited the gap. Record the keyword in the constraint injector, keyed by the resolved property set like the other object-level rules and following $ref/allOf the way properties are resolved, and render it as an object-level superRefine. Every occurrence of a property set must agree on the rule list, so a coincidental shape clash injects nothing; a rule naming a field the property set does not carry (a request projection omitted it) is skipped rather than approximated. Idempotent on re-run. A rule whose dependents the generated object already requires can never change a verdict, because z.object rejects their absence first. Such a rule is not rendered: the superRefine wrapper turns an exported ZodObject into a ZodEffects and drops .shape, .extend, .pick, .omit and .merge at the type level for no behavioural gain. Vacuity is judged on the generated property chain, not on the schema `required` list. An update and a response variant of one type share a property set and differ only in optionality, so the property-set index cannot tell them apart; and quicktype sometimes emits a required property as optional (DailyHour: all three fields are optional in the generated object, a separate pre-existing loss), in which case the rule is live and kept. Effect on the generated file: TimeIntervalSchema, ExceptionHourElementSchema and DailyHourElementSchema (with the aliases ExceptionHourSchema and DailyHourSchema) gain the rule and become ZodEffects; each now rejects a payload main accepted. The destinations needs type rule of fulfillment_method.json is vacuous on every fulfillment object main generates, since `type` is required on all of them, so no fulfillment export changes class. Once the fulfillment family is projected per request variant (js-sdk#77), the update variant carries `type` optional and is the one object that gains the rule. Tests: main has 147, this change adds 11 (158 total).
damaz91
force-pushed
the
fix/dependent-required-injection
branch
from
September 23, 2026 16:24
795de24 to
c572b0c
Compare
damaz91
approved these changes
Sep 23, 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.
Description
dependentRequired(JSON Schema 2020-12) is dropped by the typescript-zodtarget of quicktype.
common/types/time_interval.jsonhas norequiredlistand allows an empty fragment, but declares
so
opensandclosesmust appear together. The generatedTimeIntervalSchemaaccepted{ opens: "09:00" }, and the objects composingit through
allOf(ExceptionHourElementSchema,DailyHourElementSchema)inherited the gap:
{ valid_from, valid_through, opens }and{ day, opens }both passed on main.
What changes
scripts/inject-schema-constraints.mjsrecords the keyword, keyed by theresolved property set like the other object-level rules and following
$refand
allOfthe way properties are resolved, and renders it as anobject-level
superRefine. Every occurrence of a property set must agree onthe rule list, so a coincidental shape clash injects nothing; a rule naming a
field the property set does not carry (a request projection omitted it) is
skipped rather than approximated. Idempotent on re-run.
Vacuous rules are not rendered
A rule whose dependents the generated object already requires can never
change a verdict:
z.objectrejects their absence first. Rendering it anywaywould wrap the exported
ZodObjectin aZodEffects, which loses.shape,.extend,.pick,.omitand.mergeat the type level for no behaviouralgain. Such rules are dropped per subject before rendering, and the injector
reports them (
N vacuous dependentRequired rule(s) skipped).Vacuity is judged on the generated property chain (
.optional()and itsrelatives, undefined-accepting bases, unions with such a member, references
followed to their top-level declaration), not on the schema
requiredlist,for two reasons found while measuring:
differ only in optionality, and the property-set index cannot tell them
apart. A schema-side check would record the rule for both, or through the
ambiguity guard for neither.
daily_hour.jsonlistsday,opensandclosesas required, yet onmain
DailyHourElementSchemaaccepts{}. On that object the pair rule islive, and a schema-side check would have dropped it and left
{ day, opens }passing.
Evidence, measured at runtime on the compiled zod schemas by comparing the
constructor of every
*Schemaexport on main (99572ca) with this branch:ZodObjecttoZodEffects, and eachis a live rule that now rejects a payload main accepted:
TimeIntervalSchema,ExceptionHourElementSchema,DailyHourElementSchema, and the aliasesExceptionHourSchemaandDailyHourSchema.FulfillmentMethodCreateRequestSchemaand its aliasesFulfillmentMethodSchemaandMethodElementSchema, wrapped for thedestinationsneedstyperule offulfillment_method.json, which isvacuous there because
typeis required on every fulfillment object maingenerates. They keep
.shapenow.Relation to #77 and the fulfillment request projection PR
fulfillment_method.jsondeclaresdependentRequired: { destinations: ["type"] }. On main every fulfillmentobject requires
type, so the rule is vacuous and nothing in that familychanges here. The companion PR that projects the fulfillment family per
request variant leaves one gap open on purpose: its update variant carries
typeoptional, so{ line_item_ids, destinations }withouttypeisaccepted there. With both applied (verified by stacking this commit on that
branch and regenerating),
FulfillmentMethodUpdateRequestSchemais the onefulfillment object that gains the rule, the create and response variants stay
plain
ZodObject(reported as 2 vacuous skips), and the test that PR dropped(
update request rejects destinations without type) passes: 233 tests, 0failing.
Verification
./generate_models.shagainst ucp d3ccb55c, twice: byte-identical bothtimes.
npm test: 158 passing, 0 failing (main: 147; this PR adds 11). Every addedtest was seen failing first: the injector tests against the unchanged
script, the generated-schema tests against the schemas compiled from main.
npm run buildandnpm run build:noEmit: clean. Prettier clean on everyfile the pre-commit hook covers.
allOfwalk from the dependentRequired collectorregenerates with only
TimeIntervalSchemaconstrained and turns 3 testsred (the allOf injector test,
ExceptionHourElementSchema,DailyHourElementSchema).Before/after:
Category (Required)
ucp-schematool (resolver, linter, validator). (Requires Maintainer approval)Related Issues
Related to #77: together with the fulfillment request projection PR this
closes its update-side gap. No issue is filed for the time_interval gap
itself.
Checklist
!for breaking changes).Screenshots / Logs (if applicable)
N/A. The before/after table and the runtime class comparison above are the
repro.