Skip to content

feat(types): tighten Format.assets typing + emit named slot unions/Zod schemas#1654

Merged
bokelley merged 1 commit into
mainfrom
bokelley/issue-1652
May 10, 2026
Merged

feat(types): tighten Format.assets typing + emit named slot unions/Zod schemas#1654
bokelley merged 1 commit into
mainfrom
bokelley/issue-1652

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

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.ts redeclares the Individual*Asset slot types but doesn't import the per-asset-type *AssetRequirements interfaces from core.generated.ts, so the post-processor at scripts/generate-types.ts couldn't add requirements?: there. Format.assets[i] typed IndividualImageAsset without requirements, forcing consumers to cast to read it.
  • The 12 Group*Asset types nested in RepeatableGroupAsset.assets[] were bare BaseGroupAsset aliases — no asset_type, no requirements. The same codegen bug, never patched.
  • No named IndividualAssetSlot / GroupAssetSlot / FormatAssetSlot exports. The unions only existed inline in Format.assets[], so ts-to-zod produced no top-level union schemas. Consumers had to fork their own z.union([...]) or hand-roll a discriminated schema.

What

scripts/generate-types.ts::applyIndividualAssetDiscriminators now:

  • Injects import type { ImageAssetRequirements, … } from './core.generated'; at the top of tools.generated.ts when those types aren't locally declared, so requirements?: references resolve. core.generated.ts is unaffected (already declares the types).
  • Restores asset_type + requirements?: on the 12 Group*Asset shapes via a new GROUP_ASSET_DISCRIMINATORS table.
  • Emits named IndividualAssetSlot, GroupAssetSlot, and FormatAssetSlot unions in both generated files.
  • Replaces the inline anonymous unions in Format.assets[] and RepeatableGroupAsset.assets[] with the named types via regex. Both replacements count their hits and warn loudly on zero — silent no-op would otherwise leave Format.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 / FormatAssetSlotSchema are now generated alongside per-type IndividualImageAssetSchema { asset_type, requirements } and GroupImageAssetSchema { asset_type, requirements }.

The hand-authored shim at src/lib/types/format-asset-slots.ts is 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 in format-asset-slot-builders.ts import codegen types directly. Two builders (briefGroupAsset, catalogGroupAsset) are removed — the spec doesn't include brief or catalog in RepeatableGroupAsset.assets[].oneOf, so calling these always produced wire-invalid output.

Compat

  • Format.assets typing: strictly tightening. Adopters who relied on the loose shape (no requirements) get the field they were missing; the cast pattern in Codegen: Zod schemas for FormatAssetSlot, and tighten Format.assets typing #1652 goes away.
  • *Slot alias optionality: the prior hand-authored shim modeled requirements as required; it's now optional (?:) to match the spec. Adopters that destructured slot.requirements and treated the value as defined will need to assert or handle undefined. Called out in the changeset.
  • Removed builders: briefGroupAsset / catalogGroupAsset were always type-broken per spec. No FormatAsset.groupBrief / groupCatalog namespace 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:

  • Broadened the requirementsLocallyDeclared sentinel regex to match interface | type (cheap forward-compat insurance).
  • Added zero-replacement warnings on both assets[] rewrites so a future jsts layout change can't silently regress Format.assets[] to the anonymous union.
  • Beefed up the changeset with the requirements-optionality migration note and the new *SlotSchema runtime-validation entry points.

Protocol verification: the 14-individual / 12-group split matches schemas/cache/latest/bundled/creative/list-creative-formats-response.json exactly. requirements is correctly optional (appears in properties but never in any branch's required:).

Test plan

  • npm run typecheck — clean
  • npm run format:check — clean
  • npm run build:lib — clean
  • Focused tests — 96/96 pass
    • test/lib/zod-schemas.test.js
    • test/lib/format-assets.test.js
    • test/lib/format-asset-slot-builders.test.js
    • test/lib/validation.test.js
    • test/examples/hello-creative-adapter-template.test.js
    • test/examples/hello-creative-adapter-ad-server.test.js
  • Type-test (src/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

…+ 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 bokelley merged commit 29e76a9 into main May 10, 2026
10 checks passed
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>
@github-actions github-actions Bot mentioned this pull request May 11, 2026
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.

Codegen: Zod schemas for FormatAssetSlot, and tighten Format.assets typing

1 participant