Skip to content

Commit d059760

Browse files
bokelleyclaude
andauthored
feat(types): strict discriminator unions — AssetInstance, sync rows, vendor pricing (#961)
* feat(types): strict discriminator unions — AssetInstance, sync rows, vendor pricing The codegen emits strict per-variant interfaces (ImageAsset, CpmPricing, etc.) but not the discriminated unions over them. Handlers that returned Record<string, unknown> dodged the compile-time check and hit runtime schema validation instead. This adds three hand-authored unions on top of the generated bases so handler authors can opt into compile-time discriminator checking. Added: - src/lib/types/asset-instances.ts — AssetInstance / CommonAssetInstance / AssetInstanceType discriminated unions over the 14 generated asset-type interfaces, keyed on asset_type - src/lib/types/sync-rows.ts — SyncAccountsResponseRow + SyncGovernanceResponseRow named-type extractions of the inline accounts[] shapes, forcing the action / status literal-union discriminators at compile time - src/lib/types/asset-instances.test.ts — type-level tests using // @ts-expect-error to lock in the constraints; if a future codegen regression loosens a discriminator, the now-unexpected error fails tsc Re-export gaps closed in src/lib/index.ts: - vendor-pricing: PerUnitPricing, CustomPricing, VendorPricing, VendorPricingOption (previously only CpmPricing / PercentOfMediaPricing / FlatFeePricing were exported) - product-pricing: CPMPricingOption, VCPMPricingOption, CPCPricingOption, CPCVPricingOption, CPVPricingOption, CPPPricingOption, FlatRatePricingOption, TimeBasedPricingOption This is dx-expert priority #3 from the matrix-v18 review (CI defenses #1 and #2 shipped in #945 and #957). Catches the same drift class the matrix catches at runtime — discriminator omission, missing required fields, wrong-shaped factory objects — at compile time. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(types): code-review fixes — wire type-checks into typecheck Code reviewer found three blockers in the previous commit: 1. The type-test file was excluded from typecheck. tsconfig.json and tsconfig.lib.json both excluded **/*.test.ts; npm test runs node's test runner against .test.js only. The @ts-expect-error safety net was a no-op. Fixed: rename to *.type-checks.ts so it's part of the normal typecheck. Add explicit exclude in tsconfig.lib.json so it doesn't ship in dist. 2. asset.format on VideoAsset doesn't exist (only container_format). Once typechecked, this would have failed. Fixed both the test file and the JSDoc example in asset-instances.ts. 3. Three @ts-expect-error directives were misplaced. Bare-const-assignment placement is fragile because TS reports object-literal-required-property errors at varying line/col positions. Restructured to use the function- return pattern: the directive lands immediately above the `return` line that triggers the error, which is what TS expects. Smoke-tested: changing one of the negative-test field names to the correct field causes the @ts-expect-error to become unused and tsc fails with TS2578. The regression alarm is now real. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(types): expert-review fixes Three reviewers (dx-expert, ad-tech-protocol-expert, javascript-protocol- expert) green-lit shape but converged on six concrete improvements: - Drop CommonAssetInstance. Excludes VAST which dominates third-party- served video (CTV, Magnite/FreeWheel) — calling it "common" misleads. Removing is the cleanest path; can re-add as HostedAssetInstance later if there's demand. - Fix source-of-truth comment paths in asset-instances.ts and sync-rows.ts — header pointed to schemas/cache/{version}/bundled/... but the actual paths are schemas/cache/{version}/{creative,account}/... - Fix changeset filename reference asset-instances.test.ts → asset-instances.type-checks.ts (ships into CHANGELOG verbatim). - Add explicit exhaustiveness rail to describeAsset() switch via `const _exhaustive: never = asset` — current pattern only catches missing branches via noImplicitReturns, fragile to refactors that move returns out of switch arms. - Drop redundant `void X;` lines in favor of file-level eslint disable + a single `_references` export holding all symbols. - Surface SyncAccountsResponseRow in build-seller-agent SKILL.md alongside the existing pitfall about the action field. Without that, the type ships and nobody finds it. Smoke-tested: typecheck clean, 5854/5862 tests pass, prettier clean, typecheck-skill-examples baseline still 0 new errors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 4d91c11 commit d059760

8 files changed

Lines changed: 387 additions & 15 deletions

File tree

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
'@adcp/client': minor
3+
---
4+
5+
Strict discriminator types for creative assets, vendor pricing, and sync rows.
6+
7+
The codegen produces strict per-variant interfaces (`ImageAsset`, `CpmPricing`, etc.) but doesn't emit canonical discriminated unions over them. This release adds three hand-authored unions on top of the generated bases so handler authors can opt into compile-time discriminator checking instead of runtime schema validation:
8+
9+
- **`AssetInstance`** — discriminated union of every creative asset instance (`ImageAsset | VideoAsset | AudioAsset | TextAsset | HTMLAsset | URLAsset | CSSAsset | JavaScriptAsset | MarkdownAsset | VASTAsset | DAASTAsset | BriefAsset | CatalogAsset | WebhookAsset`), keyed on `asset_type`. Use as the value type for `creative_manifest.assets[<key>]`. Omitting `asset_type` or returning a plain `{ url, width, height }` against this type fails to compile.
10+
- **`AssetInstanceType`** — the `asset_type` discriminator value union (`'image' | 'video' | …`). Useful for exhaustive switch-case helpers.
11+
- **`SyncAccountsResponseRow`** — extracted named type for one row in `SyncAccountsSuccess.accounts[]`. Forces the `action` literal-union discriminator (`'created' | 'updated' | 'unchanged' | 'failed'`) and the `status` enum on every row at compile time.
12+
- **`SyncGovernanceResponseRow`** — same pattern for `SyncGovernanceSuccess.accounts[]`. Forces the `status: 'synced' | 'failed'` discriminator.
13+
- **Vendor-pricing exports completed**`PerUnitPricing`, `CustomPricing`, `VendorPricing`, `VendorPricingOption` are now re-exported from `@adcp/client` (previously only `CpmPricing`, `PercentOfMediaPricing`, `FlatFeePricing` were).
14+
- **Product-pricing exports completed**`CPMPricingOption`, `VCPMPricingOption`, `CPCPricingOption`, `CPCVPricingOption`, `CPVPricingOption`, `CPPPricingOption`, `FlatRatePricingOption`, `TimeBasedPricingOption` re-exported (the union type `PricingOption` and `CPAPricingOption` were already exported).
15+
16+
Type tests in `src/lib/types/asset-instances.type-checks.ts` use `// @ts-expect-error` to lock in the constraints — if a future codegen regression loosens any discriminator (e.g., makes `asset_type` optional), `tsc --noEmit` fails on a now-unexpected error. The file uses the `.type-checks.ts` suffix (not `.test.ts`) so it participates in the project's normal `npm run typecheck` pass; explicitly excluded from `tsconfig.lib.json` so it doesn't ship in `dist/`.
17+
18+
Drift class this catches at compile time:
19+
20+
```ts
21+
// Before: this slipped past TS, was caught only by runtime validator.
22+
const asset: Record<string, unknown> = { url: '...', width: 1920, height: 1080 };
23+
return { creative_manifest: { format_id, assets: { hero: asset } } };
24+
25+
// After: typed as AssetInstance, missing asset_type is a compile error.
26+
const asset: AssetInstance = { url: '...', width: 1920, height: 1080 };
27+
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
28+
// error TS2353: Object literal may only specify known properties, and
29+
// 'url' does not exist in type 'AssetInstance'. Property 'asset_type'
30+
// is missing.
31+
```
32+
33+
This is dx-expert priority #3 from the matrix-v18 review (CI defenses #1 and #2 shipped in #945 and #957).

skills/build-seller-agent/SKILL.md

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -411,7 +411,7 @@ Non-guaranteed buys are always instant confirmation.
411411
> - `get_media_buy_delivery /media_buy_deliveries[i]/by_package[j]` rows are strict: each requires `package_id`, `spend` (number), `pricing_model`, `rate` (number), and `currency`. A mock that returns `{package_id, impressions, clicks}` fails validation — include the billing quintet on every package row.
412412
> - `get_media_buy_delivery /reporting_period/start` and `/end` are ISO 8601 **date-time** strings (`YYYY-MM-DDTHH:MM:SS.sssZ` via `new Date().toISOString()`), not date-only. A mock that returns `'2026-04-21'` fails the format check in GA.
413413
> - `get_media_buys /media_buys[i]` rows require **`media_buy_id`, `status`, `currency`, `total_budget`, `packages`**. When you persist a buy in `create_media_buy`, save `currency` and `total_budget` so the `get_media_buys` response can echo them verbatim — reconstructing later drops one of the required fields in ~every Claude build we've tested.
414-
> - `sync_accounts` response: each row in `accounts[]` requires **`action: 'created' | 'updated' | 'unchanged' | 'failed'`** (not just `account_id`, `status`). Compare to sync_creatives — same pattern. Omitting `action` fails schema validation at `/accounts/0/action` and blocks every downstream stateful step in the storyboard.
414+
> - `sync_accounts` response: each row in `accounts[]` requires **`action: 'created' | 'updated' | 'unchanged' | 'failed'`** (not just `account_id`, `status`). Compare to sync_creatives — same pattern. Omitting `action` fails schema validation at `/accounts/0/action` and blocks every downstream stateful step in the storyboard. Type your row array as `SyncAccountsResponseRow[]` (exported from `@adcp/client`) to catch the missing-`action` drift at compile time instead of runtime.
415415
416416
**`get_adcp_capabilities`** — register first, empty `{}` schema
417417

@@ -544,7 +544,14 @@ Retail-media sponsored-products formats often reach for `asset_type: 'promoted_o
544544
Use the typed slot builders — they inject `item_type` and `asset_type`, and the `requirements` object is strictly typed per asset_type, so `file_types`, `min_duration_seconds`, and `min_count` on an individual asset all fail at compile time:
545545

546546
```typescript
547-
import { imageAssetSlot, videoAssetSlot, catalogAssetSlot, repeatableGroup, imageGroupAsset, textGroupAsset } from '@adcp/client';
547+
import {
548+
imageAssetSlot,
549+
videoAssetSlot,
550+
catalogAssetSlot,
551+
repeatableGroup,
552+
imageGroupAsset,
553+
textGroupAsset,
554+
} from '@adcp/client';
548555

549556
// Single image asset slot
550557
imageAssetSlot({
@@ -561,7 +568,11 @@ repeatableGroup({
561568
max_count: 5,
562569
selection_mode: 'sequential',
563570
assets: [
564-
imageGroupAsset({ asset_id: 'card_image', required: true, requirements: { aspect_ratio: '1:1', formats: ['jpg', 'png'] } }),
571+
imageGroupAsset({
572+
asset_id: 'card_image',
573+
required: true,
574+
requirements: { aspect_ratio: '1:1', formats: ['jpg', 'png'] },
575+
}),
565576
textGroupAsset({ asset_id: 'card_headline', required: true, requirements: { max_length: 40 } }),
566577
],
567578
});
@@ -1362,17 +1373,17 @@ When `adcp storyboard run <url> <storyboard> --json` reports a failure, the `det
13621373

13631374
Each specialism below has a companion file with its delta on top of the baseline. Fetch only the one you are building.
13641375

1365-
| Specialism | Status | Companion file |
1366-
|---|---|---|
1367-
| `sales-guaranteed` | stable | [`specialisms/sales-guaranteed.md`](./specialisms/sales-guaranteed.md) |
1368-
| `sales-non-guaranteed` | stable | [`specialisms/sales-non-guaranteed.md`](./specialisms/sales-non-guaranteed.md) |
1369-
| `sales-broadcast-tv` | stable | [`specialisms/sales-broadcast-tv.md`](./specialisms/sales-broadcast-tv.md) |
1370-
| `sales-streaming-tv` | preview | baseline only |
1371-
| `sales-social` | stable | [`specialisms/sales-social.md`](./specialisms/sales-social.md) |
1372-
| `sales-exchange` | preview | baseline only |
1373-
| `sales-proposal-mode` | stable | [`specialisms/sales-proposal-mode.md`](./specialisms/sales-proposal-mode.md) |
1374-
| `audience-sync` | stable | [`specialisms/audience-sync.md`](./specialisms/audience-sync.md) |
1375-
| `signed-requests` | preview | [`specialisms/signed-requests.md`](./specialisms/signed-requests.md) — cross-cutting; applies to every mutating agent |
1376+
| Specialism | Status | Companion file |
1377+
| ---------------------- | ------- | --------------------------------------------------------------------------------------------------------------------- |
1378+
| `sales-guaranteed` | stable | [`specialisms/sales-guaranteed.md`](./specialisms/sales-guaranteed.md) |
1379+
| `sales-non-guaranteed` | stable | [`specialisms/sales-non-guaranteed.md`](./specialisms/sales-non-guaranteed.md) |
1380+
| `sales-broadcast-tv` | stable | [`specialisms/sales-broadcast-tv.md`](./specialisms/sales-broadcast-tv.md) |
1381+
| `sales-streaming-tv` | preview | baseline only |
1382+
| `sales-social` | stable | [`specialisms/sales-social.md`](./specialisms/sales-social.md) |
1383+
| `sales-exchange` | preview | baseline only |
1384+
| `sales-proposal-mode` | stable | [`specialisms/sales-proposal-mode.md`](./specialisms/sales-proposal-mode.md) |
1385+
| `audience-sync` | stable | [`specialisms/audience-sync.md`](./specialisms/audience-sync.md) |
1386+
| `signed-requests` | preview | [`specialisms/signed-requests.md`](./specialisms/signed-requests.md) — cross-cutting; applies to every mutating agent |
13761387

13771388
Claim exactly the specialisms your agent actually implements in `capabilities.specialisms`. Don't claim a specialism you only partially support — the compliance storyboard for that specialism will fail hard.
13781389

src/lib/index.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,20 @@ export type {
320320
CpmPricing,
321321
PercentOfMediaPricing,
322322
FlatFeePricing,
323+
PerUnitPricing,
324+
CustomPricing,
325+
VendorPricing,
326+
VendorPricingOption,
327+
// Pricing variants for products (publisher rate cards in get_products etc.)
328+
// PricingOption + CPAPricingOption are exported below near the other tool types.
329+
CPMPricingOption,
330+
VCPMPricingOption,
331+
CPCPricingOption,
332+
CPCVPricingOption,
333+
CPVPricingOption,
334+
CPPPricingOption,
335+
FlatRatePricingOption,
336+
TimeBasedPricingOption,
323337
// Governance Domain - Property Lists
324338
CreatePropertyListRequest,
325339
CreatePropertyListResponse,

src/lib/types/asset-instances.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Discriminated union of creative asset instances — what a buyer DELIVERS
2+
// inside `creative_manifest.assets`. Distinct from the asset slots that a
3+
// publisher declares in `Format.assets[]` (see `format-asset-slots.ts`).
4+
//
5+
// The per-asset-type interfaces (`ImageAsset`, `VideoAsset`, …) already
6+
// exist in `tools.generated.ts` and each carries an `asset_type` literal
7+
// discriminator. What was missing was the union itself: a canonical
8+
// `AssetInstance` you can use as a parameter or return type so TypeScript
9+
// narrows correctly on the discriminator and won't accept an asset
10+
// without one.
11+
//
12+
// Why this matters: handlers that returned a plain
13+
// `{ url: '...', width: 1920, height: 1080 }` typed against
14+
// `Record<string, unknown>` slipped through without `asset_type`. The
15+
// schema validator caught it at runtime; with a strict union as the
16+
// declared parameter / return type, the same error surfaces at compile
17+
// time. PR #945's `videoAsset({...})` width/height GA tightening came
18+
// out of this same drift class.
19+
//
20+
// Source of truth: schemas/cache/{version}/creative/asset-types/index.json
21+
// (registry) and schemas/cache/{version}/core/assets/*-asset.json (per-type).
22+
23+
import type {
24+
ImageAsset,
25+
VideoAsset,
26+
AudioAsset,
27+
TextAsset,
28+
HTMLAsset,
29+
URLAsset,
30+
CSSAsset,
31+
JavaScriptAsset,
32+
MarkdownAsset,
33+
VASTAsset,
34+
DAASTAsset,
35+
BriefAsset,
36+
CatalogAsset,
37+
WebhookAsset,
38+
} from './tools.generated';
39+
40+
/**
41+
* Discriminated union of every creative asset instance recognised by the
42+
* AdCP creative protocol. Narrow by `asset_type` to access the per-type
43+
* fields:
44+
*
45+
* ```ts
46+
* function describe(asset: AssetInstance): string {
47+
* switch (asset.asset_type) {
48+
* case 'image':
49+
* return `${asset.width}x${asset.height} @ ${asset.url}`;
50+
* case 'video':
51+
* return `${asset.duration_ms ?? 0}ms ${asset.container_format ?? ''}`;
52+
* case 'html':
53+
* return `${asset.content.length}B inline HTML`;
54+
* // ...all branches required — exhaustiveness is enforced
55+
* }
56+
* }
57+
* ```
58+
*
59+
* This is the type to use for `creative_manifest.assets[<key>]` values.
60+
* The `assets` map itself is `Record<string, AssetInstance>` — keys come
61+
* from the format's declared asset slot ids; values are these instances.
62+
*/
63+
export type AssetInstance =
64+
| ImageAsset
65+
| VideoAsset
66+
| AudioAsset
67+
| TextAsset
68+
| HTMLAsset
69+
| URLAsset
70+
| CSSAsset
71+
| JavaScriptAsset
72+
| MarkdownAsset
73+
| VASTAsset
74+
| DAASTAsset
75+
| BriefAsset
76+
| CatalogAsset
77+
| WebhookAsset;
78+
79+
/**
80+
* The discriminator value (`asset_type`) of every variant in
81+
* {@link AssetInstance}. Useful for runtime branching and exhaustive
82+
* switch-case helpers.
83+
*/
84+
export type AssetInstanceType = AssetInstance['asset_type'];
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/* eslint-disable @typescript-eslint/no-unused-vars */
2+
// Type-only tests for the AssetInstance discriminated union and the
3+
// SyncAccountsResponseRow / SyncGovernanceResponseRow row types.
4+
//
5+
// The "test" is whether this file compiles. Each `// @ts-expect-error`
6+
// comment claims the next line WILL fail typechecking. If TypeScript ever
7+
// stops flagging that line — e.g., because the discriminator was loosened
8+
// or a required field became optional — the `@ts-expect-error` itself
9+
// becomes an error and the project's `tsc --noEmit` fails. That's the
10+
// regression alarm.
11+
//
12+
// Pattern: each negative test uses an arrow function returning the value.
13+
// TS reports the error on the `return` line, so the directive directly
14+
// above is what catches it. Bare-const-assignment placement is fragile —
15+
// TS reports object-literal-required-property errors at varying line/col
16+
// positions depending on the type structure.
17+
//
18+
// Run with `npm run typecheck`.
19+
20+
import type { AssetInstance, AssetInstanceType, SyncAccountsResponseRow, SyncGovernanceResponseRow } from './index';
21+
22+
// ── AssetInstance: discriminator narrowing + exhaustiveness ──────────────
23+
24+
function describeAsset(asset: AssetInstance): string {
25+
switch (asset.asset_type) {
26+
case 'image':
27+
return `${asset.width}x${asset.height} @ ${asset.url}`;
28+
case 'video':
29+
return `video ${asset.container_format ?? ''} ${asset.duration_ms ?? 0}ms`;
30+
case 'audio':
31+
return `audio ${asset.codec}`;
32+
case 'text':
33+
return asset.content;
34+
case 'html':
35+
return asset.content;
36+
case 'url':
37+
return asset.url;
38+
case 'css':
39+
case 'javascript':
40+
case 'markdown':
41+
case 'vast':
42+
case 'daast':
43+
case 'brief':
44+
case 'catalog':
45+
case 'webhook':
46+
return asset.asset_type;
47+
default: {
48+
// Exhaustiveness rail: if a new asset_type lands in AssetInstance
49+
// without a case above, `asset` here is no longer `never` and this
50+
// line fails compilation. Stronger than `noImplicitReturns` —
51+
// survives refactors that move returns out of switch arms.
52+
const _exhaustive: never = asset;
53+
return _exhaustive;
54+
}
55+
}
56+
}
57+
58+
// ── AssetInstance: omitting asset_type is rejected ───────────────────────
59+
60+
function _assetInstance_missingDiscriminator(): AssetInstance {
61+
// @ts-expect-error — `asset_type` is the discriminator and required.
62+
return { url: 'https://x.test/img.png', width: 300, height: 250 };
63+
}
64+
65+
// ── AssetInstance: image variant requires width and height ───────────────
66+
67+
function _imageAsset_missingWidth(): AssetInstance {
68+
// @ts-expect-error — ImageAsset requires `width` (per AdCP 3.0 GA).
69+
return { asset_type: 'image', url: 'https://x.test/img.png', height: 250 };
70+
}
71+
72+
// ── AssetInstance: html instance carries `content`, not `html` ───────────
73+
74+
function _htmlAsset_wrongFieldName(): AssetInstance {
75+
// @ts-expect-error — HTMLAsset has `content`, not `html`. Common drift.
76+
return { asset_type: 'html', html: '<div>...</div>' };
77+
}
78+
79+
// ── AssetInstanceType: enumerates every variant's discriminator ──────────
80+
81+
const _all_types: AssetInstanceType[] = [
82+
'image',
83+
'video',
84+
'audio',
85+
'text',
86+
'html',
87+
'url',
88+
'css',
89+
'javascript',
90+
'markdown',
91+
'vast',
92+
'daast',
93+
'brief',
94+
'catalog',
95+
'webhook',
96+
];
97+
98+
function _assetType_bogusValue(): AssetInstanceType {
99+
// @ts-expect-error — 'banner' is not a valid asset_type.
100+
return 'banner';
101+
}
102+
103+
// ── SyncAccountsResponseRow: action discriminator is required ────────────
104+
105+
const _row_ok: SyncAccountsResponseRow = {
106+
account_id: 'acct_1',
107+
brand: { domain: 'example.com' },
108+
operator: 'agency.example',
109+
action: 'created',
110+
status: 'active',
111+
};
112+
113+
function _row_missingAction(): SyncAccountsResponseRow {
114+
// @ts-expect-error — `action` is required on every row.
115+
return {
116+
account_id: 'acct_1',
117+
brand: { domain: 'example.com' },
118+
operator: 'agency.example',
119+
status: 'active',
120+
};
121+
}
122+
123+
function _row_badAction(): SyncAccountsResponseRow {
124+
return {
125+
account_id: 'acct_1',
126+
brand: { domain: 'example.com' },
127+
operator: 'agency.example',
128+
// @ts-expect-error — 'archived' is not a valid action enum value.
129+
action: 'archived',
130+
status: 'active',
131+
};
132+
}
133+
134+
// ── SyncGovernanceResponseRow: status discriminator is required ──────────
135+
136+
const _gov_row_ok: SyncGovernanceResponseRow = {
137+
account: { account_id: 'acct_1' },
138+
status: 'synced',
139+
};
140+
141+
function _gov_row_missingStatus(): SyncGovernanceResponseRow {
142+
// @ts-expect-error — `status` is required.
143+
return { account: { account_id: 'acct_1' } };
144+
}
145+
146+
// Reference all symbols once to keep the file's intent visible to readers
147+
// even though they're never executed. The file-level eslint-disable above
148+
// is what actually silences the unused-vars lint.
149+
export const _references = [
150+
describeAsset,
151+
_assetInstance_missingDiscriminator,
152+
_imageAsset_missingWidth,
153+
_htmlAsset_wrongFieldName,
154+
_all_types,
155+
_assetType_bogusValue,
156+
_row_ok,
157+
_row_missingAction,
158+
_row_badAction,
159+
_gov_row_ok,
160+
_gov_row_missingStatus,
161+
] as const;

src/lib/types/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,18 @@ export type { FormatID } from './core.generated';
2626
// discriminated per-asset-type branches of Format.assets[]).
2727
export * from './format-asset-slots';
2828

29+
// Discriminated union of creative asset INSTANCES (what a buyer delivers
30+
// inside `creative_manifest.assets`). Companion to format-asset-slots.ts
31+
// (which describes what a publisher SELLS in `Format.assets[]`). The
32+
// individual ImageAsset / VideoAsset / etc. interfaces are generated; this
33+
// file is the missing canonical union over them.
34+
export * from './asset-instances';
35+
36+
// Strict per-row types for sync_* response success arms. The codegen
37+
// leaves these row shapes inline; named types make the discriminators
38+
// (e.g., SyncAccountsResponseRow.action) reachable to handler authors.
39+
export * from './sync-rows';
40+
2941
// Re-export Zod schemas for runtime validation
3042
export * from './schemas.generated';
3143

0 commit comments

Comments
 (0)