feat(types): tighten Format.assets typing + emit named slot unions/Zod schemas#1654
Merged
Conversation
…+ Zod schemas Closes #1652. Codegen post-processor now (a) injects an import for the *AssetRequirements types into tools.generated.ts so the per-slot requirements?: field survives, (b) restores asset_type + requirements on the 12 Group*Asset shapes, and (c) emits named IndividualAssetSlot / GroupAssetSlot / FormatAssetSlot unions. Format.assets[] and RepeatableGroupAsset.assets[] now reference those names. ts-to-zod produces matching IndividualAssetSlotSchema / GroupAssetSlotSchema / FormatAssetSlotSchema and per-type slot schemas carrying the requirements branch. The hand-authored format-asset-slots.ts shim is reduced to thin *Slot aliases over the codegen names; downstream imports keep working. Two no-op group builders (briefGroupAsset, catalogGroupAsset) are removed — the spec excludes brief/catalog from RepeatableGroupAsset.assets[]. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bokelley
added a commit
that referenced
this pull request
May 11, 2026
…1660) * fix(types): restore typed Zod for per-asset-type AssetRequirements (#1659) Strip the cross-file `import type { ... } from './core.generated';` block from tools.generated.ts before merging into the combined source passed to ts-to-zod. With that import in place, ts-to-zod treated the 12 *AssetRequirements names as external and emitted z.any() stubs — even though the actual interfaces were inlined in the merged source. The import was added in #1654 so tools.generated.ts typechecks standalone; it's redundant inside the merged Zod codegen source, where core.generated is already concatenated. Restores typed Zod for ImageAssetRequirementsSchema, TextAssetRequirements- Schema, and the 10 siblings, plus the AssetRequirementsSchema union and the `requirements` field on every Individual*AssetSchema / Group*AssetSchema slot. Adds a regression test that probes wrong-typed fields — a z.any() regression would silently accept them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(codegen): fail-fast guards on cross-file import and z.any() stubs Two cheap build-time defenses, both class-level not instance-level: 1. After stripping `import type { ... } from './core.generated'`, assert the strip actually removed it. If the injector in generate-types.ts ever changes shape (different specifier, single-line form), the silent no-op would regress to z.any() stubs. 2. After ts-to-zod runs, scan for `const FooSchema = z.any();` lines and throw. This catches any future codegen path that degrades a named schema to z.any(), not just the import-shadow class. Both surface re-regressions at the codegen step rather than in a downstream consumer's test suite. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Merged
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.
Closes #1652.
Why
Issue #1652 flagged two related codegen gaps. After #1514 (the asset_type-discriminator fix) shipped, both gaps reduced to one root cause:
tools.generated.tsredeclares theIndividual*Assetslot types but doesn't import the per-asset-type*AssetRequirementsinterfaces fromcore.generated.ts, so the post-processor atscripts/generate-types.tscouldn't addrequirements?:there.Format.assets[i]typedIndividualImageAssetwithoutrequirements, forcing consumers to cast to read it.Group*Assettypes nested inRepeatableGroupAsset.assets[]were bareBaseGroupAssetaliases — noasset_type, norequirements. The same codegen bug, never patched.IndividualAssetSlot/GroupAssetSlot/FormatAssetSlotexports. The unions only existed inline inFormat.assets[], so ts-to-zod produced no top-level union schemas. Consumers had to fork their ownz.union([...])or hand-roll a discriminated schema.What
scripts/generate-types.ts::applyIndividualAssetDiscriminatorsnow:import type { ImageAssetRequirements, … } from './core.generated';at the top oftools.generated.tswhen those types aren't locally declared, sorequirements?:references resolve.core.generated.tsis unaffected (already declares the types).asset_type+requirements?:on the 12Group*Assetshapes via a newGROUP_ASSET_DISCRIMINATORStable.IndividualAssetSlot,GroupAssetSlot, andFormatAssetSlotunions in both generated files.Format.assets[]andRepeatableGroupAsset.assets[]with the named types via regex. Both replacements count their hits and warn loudly on zero — silent no-op would otherwise leaveFormat.assets[]fallen back to the loose anonymous form without TS surfacing it.ts-to-zod automatically picks up the new union exports, so
IndividualAssetSlotSchema/GroupAssetSlotSchema/FormatAssetSlotSchemaare now generated alongside per-typeIndividualImageAssetSchema { asset_type, requirements }andGroupImageAssetSchema { asset_type, requirements }.The hand-authored shim at
src/lib/types/format-asset-slots.tsis reduced from ~200 lines of duplicated type system to a thin re-export that aliases the prior*Slot-suffixed names (e.g. `IndividualImageAssetSlot = IndividualImageAsset`) to the codegen names. Builders informat-asset-slot-builders.tsimport codegen types directly. Two builders (briefGroupAsset,catalogGroupAsset) are removed — the spec doesn't includebrieforcataloginRepeatableGroupAsset.assets[].oneOf, so calling these always produced wire-invalid output.Compat
requirements) get the field they were missing; the cast pattern in Codegen: Zod schemas for FormatAssetSlot, and tighten Format.assets typing #1652 goes away.*Slotalias optionality: the prior hand-authored shim modeledrequirementsas required; it's now optional (?:) to match the spec. Adopters that destructuredslot.requirementsand treated the value as defined will need to assert or handleundefined. Called out in the changeset.briefGroupAsset/catalogGroupAssetwere always type-broken per spec. NoFormatAsset.groupBrief/groupCatalognamespace entries existed (only the 12 valid ones are in the namespace). Internal grep finds zero callers across examples, skills, tests. Treating as a bug-fix removal under the minor bump.Expert review
Three parallel reviewers (code-reviewer, ad-tech-protocol-expert, dx-expert) converged to ship-it with three minor follow-ups, all addressed in this PR:
requirementsLocallyDeclaredsentinel regex to matchinterface | type(cheap forward-compat insurance).assets[]rewrites so a future jsts layout change can't silently regressFormat.assets[]to the anonymous union.*SlotSchemaruntime-validation entry points.Protocol verification: the 14-individual / 12-group split matches
schemas/cache/latest/bundled/creative/list-creative-formats-response.jsonexactly.requirementsis correctly optional (appears inpropertiesbut never in any branch'srequired:).Test plan
npm run typecheck— cleannpm run format:check— cleannpm run build:lib— cleantest/lib/zod-schemas.test.jstest/lib/format-assets.test.jstest/lib/format-asset-slot-builders.test.jstest/lib/validation.test.jstest/examples/hello-creative-adapter-template.test.jstest/examples/hello-creative-adapter-ad-server.test.jssrc/type-tests/format-asset-slots.type-test.ts) covered by root tsconfig — preserves the four PR chore: release package #118 invariants on the codegen types🤖 Generated with Claude Code