From 4881a686f8b66dcb0f9aae63156d60351808e5ab Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sat, 23 May 2026 21:47:03 -0400 Subject: [PATCH 1/9] fix: preserve ProductSchema ZodObject ergonomics --- scripts/generate-zod-from-ts.ts | 167 +++++++++++++++++- src/lib/types/schemas.generated.ts | 112 ++++++------ .../zod-schema-ergonomics.type-test.ts | 23 +++ test/lib/zod-schemas.test.js | 19 ++ 4 files changed, 262 insertions(+), 59 deletions(-) create mode 100644 src/type-tests/zod-schema-ergonomics.type-test.ts diff --git a/scripts/generate-zod-from-ts.ts b/scripts/generate-zod-from-ts.ts index 6c39a2ab3..4e0629c3c 100644 --- a/scripts/generate-zod-from-ts.ts +++ b/scripts/generate-zod-from-ts.ts @@ -274,7 +274,8 @@ function postProcessUndefinedUnions(content: string): string { /** * Post-process generated Zod schemas to strip .and(z.record(...)) intersections - * from object schemas that already have .passthrough(). + * and equivalent record-only union intersections from object schemas that + * already have .passthrough(). * * ts-to-zod generates these for TypeScript types with index signatures like * `{ field: string } & { [k: string]: unknown }`. Since .passthrough() already @@ -282,7 +283,9 @@ function postProcessUndefinedUnions(content: string): string { * ZodIntersection types that lose .shape access (needed by MCP SDK for tool registration). * * Also handles z.record(...).and(z.object({...})) patterns (record-first intersections) - * by extracting just the z.object() portion. + * by extracting just the z.object() portion, plus + * z.union([RecordUnknownA, RecordUnknownB]).and(z.object({...})) where every + * union member is only a Record container. */ function postProcessRecordIntersections(content: string): string { let result = content; @@ -293,7 +296,10 @@ function postProcessRecordIntersections(content: string): string { // Pass 2: Replace `z.record(...).and(CONTENT)` with CONTENT (only for redundant records) result = unwrapRecordIntersections(result); - // Pass 3: Strip `.and(z.union([...]))` where content contains z.never() + // Pass 3: Replace `z.union([RecordUnknown...]).and(CONTENT)` with CONTENT. + result = unwrapRecordUnionIntersections(result); + + // Pass 4: Strip `.and(z.union([...]))` where content contains z.never() result = stripNeverUnionIntersections(result); return result; @@ -452,6 +458,161 @@ function unwrapRecordIntersections(content: string): string { return result; } +function readBalancedBody( + content: string, + start: number, + openChar: '[' | '(', + closeChar: ']' | ')' +): { body: string; end: number } | undefined { + let i = start; + let depth = 1; + let body = ''; + + while (i < content.length && depth > 0) { + if (content[i] === '"' || content[i] === "'") { + const quote = content[i]; + body += content[i]; + i++; + while (i < content.length && content[i] !== quote) { + if (content[i] === '\\') { + body += content[i]; + i++; + } + body += content[i]; + i++; + } + if (i < content.length) { + body += content[i]; + i++; + } + continue; + } + + if (content[i] === openChar) depth++; + else if (content[i] === closeChar) { + depth--; + if (depth === 0) { + return { body, end: i + 1 }; + } + } + + body += content[i]; + i++; + } + + return undefined; +} + +function splitTopLevelCommaList(content: string): string[] { + const parts: string[] = []; + let current = ''; + let depth = 0; + let i = 0; + + while (i < content.length) { + if (content[i] === '"' || content[i] === "'") { + const quote = content[i]; + current += content[i]; + i++; + while (i < content.length && content[i] !== quote) { + if (content[i] === '\\') { + current += content[i]; + i++; + } + current += content[i]; + i++; + } + if (i < content.length) { + current += content[i]; + i++; + } + continue; + } + + if (content[i] === '(' || content[i] === '[' || content[i] === '{') depth++; + else if (content[i] === ')' || content[i] === ']' || content[i] === '}') depth--; + else if (content[i] === ',' && depth === 0) { + parts.push(current.trim()); + current = ''; + i++; + continue; + } + + current += content[i]; + i++; + } + + const finalPart = current.trim(); + if (finalPart) parts.push(finalPart); + return parts; +} + +function collectRedundantRecordSchemaNames(content: string): Set { + const names = new Set(); + const recordSchemaPattern = /(?:export\s+)?const\s+(\w+)\s*=\s*z\.record\(z\.string\(\), z\.unknown\(\)\);/g; + for (const match of content.matchAll(recordSchemaPattern)) { + names.add(match[1]!); + } + return names; +} + +function isRedundantRecordMember(member: string, recordSchemaNames: Set): boolean { + const trimmed = member.trim(); + return trimmed === 'z.record(z.string(), z.unknown())' || recordSchemaNames.has(trimmed); +} + +/** + * Replace `z.union([RecordUnknownA, RecordUnknownB]).and(CONTENT)` with CONTENT. + * + * Some schema variants are intentionally opaque because TypeScript represents + * sibling-field constraints as `Record` marker arms. Intersecting + * a union of those arms with the real object shape only removes ZodObject methods; + * the later `.passthrough()` object already accepts the same unknown keys. + */ +function unwrapRecordUnionIntersections(content: string): string { + const MARKER = 'z.union(['; + const recordSchemaNames = collectRedundantRecordSchemaNames(content); + let result = ''; + let i = 0; + + while (i < content.length) { + if (content.startsWith(MARKER, i)) { + const unionStart = i; + const unionBodyStart = i + MARKER.length; + const unionBody = readBalancedBody(content, unionBodyStart, '[', ']'); + + if (!unionBody || content[unionBody.end] !== ')') { + result += content[i]; + i++; + continue; + } + + const unionEnd = unionBody.end + 1; + const members = splitTopLevelCommaList(unionBody.body); + const isRedundantUnion = + members.length > 0 && members.every(member => isRedundantRecordMember(member, recordSchemaNames)); + + if (isRedundantUnion && content.startsWith('.and(', unionEnd)) { + const andBodyStart = unionEnd + '.and('.length; + const andBody = readBalancedBody(content, andBodyStart, '(', ')'); + if (andBody && andBody.body.trimStart().startsWith('z.object(')) { + result += andBody.body; + i = andBody.end; + continue; + } + } + + result += content.substring(unionStart, unionEnd); + i = unionEnd; + } else { + result += content[i]; + i++; + } + } + + return result; +} + /** * Strip `.and(z.union([...]))` where the union body contains z.never(). * diff --git a/src/lib/types/schemas.generated.ts b/src/lib/types/schemas.generated.ts index b1057b545..c07284477 100644 --- a/src/lib/types/schemas.generated.ts +++ b/src/lib/types/schemas.generated.ts @@ -1,5 +1,5 @@ // Generated Zod v4 schemas from TypeScript types -// Generated at: 2026-05-24T13:59:59.954Z +// Generated at: 2026-05-24T01:31:36.131Z // Sources: // - core.generated.ts (core types) // - tools.generated.ts (tool types) @@ -631,7 +631,7 @@ export const NoneSchema = z.record(z.string(), z.unknown()); export const SizeModeMutexSchema = z.union([FixedSchema, MultiSizeSchema, ResponsiveSchema, NoneSchema]); -export const CanonicalFormatDisplayTagSchema = z.object({ +export const CanonicalFormatDisplayTagSchema = SizeModeMutexSchema.and(z.object({ experimental: z.boolean().optional(), deprecated: z.boolean().optional(), v1_translatable: z.boolean().optional(), @@ -660,7 +660,7 @@ export const CanonicalFormatDisplayTagSchema = z.object({ backup_image_required: z.boolean().optional(), backup_image_max_size_kb: z.number().min(1).optional(), om_sdk_required: z.boolean().optional() -}).passthrough(); +}).passthrough()); export const PriceAdjustmentKindSchema = z.union([z.literal("fee"), z.literal("discount"), z.literal("commission"), z.literal("settlement")]); @@ -698,7 +698,7 @@ export const TalentRoleSchema = z.union([z.literal("host"), z.literal("guest"), export const DerivativeTypeSchema = z.union([z.literal("clip"), z.literal("highlight"), z.literal("recap"), z.literal("trailer"), z.literal("bonus")]); -export const CanonicalFormatImageSchema = z.object({ +export const CanonicalFormatImageSchema = SizeModeMutexSchema.and(z.object({ experimental: z.boolean().optional(), deprecated: z.boolean().optional(), v1_translatable: z.boolean().optional(), @@ -729,9 +729,9 @@ export const CanonicalFormatImageSchema = z.object({ cta_values: z.array(z.string()).optional(), asset_source: z.union([z.literal("buyer_uploaded"), z.literal("publisher_host_recorded"), z.literal("seller_pre_rendered_from_brief"), z.literal("seller_human_designed"), z.literal("agent_synthesized")]).optional(), buyer_asset_acceptance: z.union([z.literal("accepted"), z.literal("rejected")]).optional() -}).passthrough(); +}).passthrough()); -export const CanonicalFormatHTML5BannerSchema = z.object({ +export const CanonicalFormatHTML5BannerSchema = SizeModeMutexSchema.and(z.object({ experimental: z.boolean().optional(), deprecated: z.boolean().optional(), v1_translatable: z.boolean().optional(), @@ -765,7 +765,7 @@ export const CanonicalFormatHTML5BannerSchema = z.object({ backup_image_required: z.boolean().optional(), backup_image_max_size_kb: z.number().min(1).optional(), ssl_required: z.boolean().optional() -}).passthrough(); +}).passthrough()); export const DisplayTagFormatDeclarationSchema = z.object({ format_kind: z.literal("display_tag"), @@ -2684,7 +2684,7 @@ export const ValidateContentDeliveryResponseSchema = z.object({ ext: ExtensionObjectSchema.optional() }).passthrough()])); -export const TasksGetRequestSchema = AdCPVersionEnvelopeSchema.merge(z.object({ +export const TasksGetRequestSchema = AdCPVersionEnvelopeSchema.and(z.object({ task_id: z.string(), include_history: z.boolean().optional(), include_result: z.boolean().optional(), @@ -2948,7 +2948,7 @@ export const SyncCatalogsInputRequiredSchema = z.object({ export const SortDirectionSchema = z.union([z.literal("asc"), z.literal("desc")]); -export const TasksListResponseSchema = AdCPVersionEnvelopeSchema.merge(ProtocolEnvelopeSchema).merge(z.object({ +export const TasksListResponseSchema = AdCPVersionEnvelopeSchema.and(ProtocolEnvelopeSchema).and(z.object({ query_summary: z.object({ total_matching: z.number().min(0).optional(), returned: z.number().min(0).optional(), @@ -5170,62 +5170,62 @@ export const V1V2CanonicalFormatMappingRegistrySchema = z.object({ }).passthrough()) }).passthrough(); -export const GroupImageAssetSchema = BaseGroupAssetSchema.merge(z.object({ +export const GroupImageAssetSchema = BaseGroupAssetSchema.and(z.object({ asset_type: z.literal("image"), requirements: ImageAssetRequirementsSchema.optional() }).passthrough()); -export const GroupVideoAssetSchema = BaseGroupAssetSchema.merge(z.object({ +export const GroupVideoAssetSchema = BaseGroupAssetSchema.and(z.object({ asset_type: z.literal("video"), requirements: VideoAssetRequirementsSchema.optional() }).passthrough()); -export const GroupAudioAssetSchema = BaseGroupAssetSchema.merge(z.object({ +export const GroupAudioAssetSchema = BaseGroupAssetSchema.and(z.object({ asset_type: z.literal("audio"), requirements: AudioAssetRequirementsSchema.optional() }).passthrough()); -export const GroupTextAssetSchema = BaseGroupAssetSchema.merge(z.object({ +export const GroupTextAssetSchema = BaseGroupAssetSchema.and(z.object({ asset_type: z.literal("text"), requirements: TextAssetRequirementsSchema.optional() }).passthrough()); -export const GroupMarkdownAssetSchema = BaseGroupAssetSchema.merge(z.object({ +export const GroupMarkdownAssetSchema = BaseGroupAssetSchema.and(z.object({ asset_type: z.literal("markdown"), requirements: MarkdownAssetRequirementsSchema.optional() }).passthrough()); -export const GroupHtmlAssetSchema = BaseGroupAssetSchema.merge(z.object({ +export const GroupHtmlAssetSchema = BaseGroupAssetSchema.and(z.object({ asset_type: z.literal("html"), requirements: HTMLAssetRequirementsSchema.optional() }).passthrough()); -export const GroupCssAssetSchema = BaseGroupAssetSchema.merge(z.object({ +export const GroupCssAssetSchema = BaseGroupAssetSchema.and(z.object({ asset_type: z.literal("css"), requirements: CSSAssetRequirementsSchema.optional() }).passthrough()); -export const GroupJavaScriptAssetSchema = BaseGroupAssetSchema.merge(z.object({ +export const GroupJavaScriptAssetSchema = BaseGroupAssetSchema.and(z.object({ asset_type: z.literal("javascript"), requirements: JavaScriptAssetRequirementsSchema.optional() }).passthrough()); -export const GroupVastAssetSchema = BaseGroupAssetSchema.merge(z.object({ +export const GroupVastAssetSchema = BaseGroupAssetSchema.and(z.object({ asset_type: z.literal("vast"), requirements: VASTAssetRequirementsSchema.optional() }).passthrough()); -export const GroupDaastAssetSchema = BaseGroupAssetSchema.merge(z.object({ +export const GroupDaastAssetSchema = BaseGroupAssetSchema.and(z.object({ asset_type: z.literal("daast"), requirements: DAASTAssetRequirementsSchema.optional() }).passthrough()); -export const GroupUrlAssetSchema = BaseGroupAssetSchema.merge(z.object({ +export const GroupUrlAssetSchema = BaseGroupAssetSchema.and(z.object({ asset_type: z.literal("url"), requirements: URLAssetRequirementsSchema.optional() }).passthrough()); -export const GroupWebhookAssetSchema = BaseGroupAssetSchema.merge(z.object({ +export const GroupWebhookAssetSchema = BaseGroupAssetSchema.and(z.object({ asset_type: z.literal("webhook"), requirements: WebhookAssetRequirementsSchema.optional() }).passthrough()); @@ -5401,68 +5401,68 @@ export const BaseIndividualAssetSchema = z.object({ asset_group_id: z.string().optional() }).passthrough(); -export const IndividualVideoAssetSchema = BaseIndividualAssetSchema.merge(z.object({ +export const IndividualVideoAssetSchema = BaseIndividualAssetSchema.and(z.object({ asset_type: z.literal("video"), requirements: VideoAssetRequirementsSchema.optional() }).passthrough()); -export const IndividualAudioAssetSchema = BaseIndividualAssetSchema.merge(z.object({ +export const IndividualAudioAssetSchema = BaseIndividualAssetSchema.and(z.object({ asset_type: z.literal("audio"), requirements: AudioAssetRequirementsSchema.optional() }).passthrough()); -export const IndividualTextAssetSchema = BaseIndividualAssetSchema.merge(z.object({ +export const IndividualTextAssetSchema = BaseIndividualAssetSchema.and(z.object({ asset_type: z.literal("text"), requirements: TextAssetRequirementsSchema.optional() }).passthrough()); -export const IndividualMarkdownAssetSchema = BaseIndividualAssetSchema.merge(z.object({ +export const IndividualMarkdownAssetSchema = BaseIndividualAssetSchema.and(z.object({ asset_type: z.literal("markdown"), requirements: MarkdownAssetRequirementsSchema.optional() }).passthrough()); -export const IndividualHtmlAssetSchema = BaseIndividualAssetSchema.merge(z.object({ +export const IndividualHtmlAssetSchema = BaseIndividualAssetSchema.and(z.object({ asset_type: z.literal("html"), requirements: HTMLAssetRequirementsSchema.optional() }).passthrough()); -export const IndividualCssAssetSchema = BaseIndividualAssetSchema.merge(z.object({ +export const IndividualCssAssetSchema = BaseIndividualAssetSchema.and(z.object({ asset_type: z.literal("css"), requirements: CSSAssetRequirementsSchema.optional() }).passthrough()); -export const IndividualJavaScriptAssetSchema = BaseIndividualAssetSchema.merge(z.object({ +export const IndividualJavaScriptAssetSchema = BaseIndividualAssetSchema.and(z.object({ asset_type: z.literal("javascript"), requirements: JavaScriptAssetRequirementsSchema.optional() }).passthrough()); export const IndividualZipAssetSchema = BaseIndividualAssetSchema; -export const IndividualVastAssetSchema = BaseIndividualAssetSchema.merge(z.object({ +export const IndividualVastAssetSchema = BaseIndividualAssetSchema.and(z.object({ asset_type: z.literal("vast"), requirements: VASTAssetRequirementsSchema.optional() }).passthrough()); -export const IndividualDaastAssetSchema = BaseIndividualAssetSchema.merge(z.object({ +export const IndividualDaastAssetSchema = BaseIndividualAssetSchema.and(z.object({ asset_type: z.literal("daast"), requirements: DAASTAssetRequirementsSchema.optional() }).passthrough()); -export const IndividualUrlAssetSchema = BaseIndividualAssetSchema.merge(z.object({ +export const IndividualUrlAssetSchema = BaseIndividualAssetSchema.and(z.object({ asset_type: z.literal("url"), requirements: URLAssetRequirementsSchema.optional() }).passthrough()); -export const IndividualWebhookAssetSchema = BaseIndividualAssetSchema.merge(z.object({ +export const IndividualWebhookAssetSchema = BaseIndividualAssetSchema.and(z.object({ asset_type: z.literal("webhook"), requirements: WebhookAssetRequirementsSchema.optional() }).passthrough()); -export const IndividualBriefAssetSchema = BaseIndividualAssetSchema.merge(z.object({ +export const IndividualBriefAssetSchema = BaseIndividualAssetSchema.and(z.object({ asset_type: z.literal("brief") }).passthrough()); -export const IndividualCatalogAssetSchema = BaseIndividualAssetSchema.merge(z.object({ +export const IndividualCatalogAssetSchema = BaseIndividualAssetSchema.and(z.object({ asset_type: z.literal("catalog") }).passthrough()); @@ -5746,10 +5746,10 @@ export const GetMediaBuyDeliveryResponseSchema = z.object({ is_final: z.boolean().optional(), finalized_at: z.iso.datetime().optional(), pricing_model: PricingModelSchema.optional(), - totals: DeliveryMetricsSchema.merge(z.object({ + totals: DeliveryMetricsSchema.and(z.object({ effective_rate: z.number().min(0).optional() }).passthrough()), - by_package: z.array(DeliveryMetricsSchema.merge(z.object({ + by_package: z.array(DeliveryMetricsSchema.and(z.object({ package_id: z.string(), pacing_index: z.number().min(0).optional(), pricing_model: PricingModelSchema.optional(), @@ -5776,40 +5776,40 @@ export const GetMediaBuyDeliveryResponseSchema = z.object({ vendor: BrandReferenceSchema, metric_id: VendorMetricIDSchema }).passthrough()])).optional(), - by_catalog_item: z.array(DeliveryMetricsSchema.merge(z.object({ + by_catalog_item: z.array(DeliveryMetricsSchema.and(z.object({ content_id: z.string().optional(), content_id_type: ContentIDTypeSchema.optional() }).passthrough())).optional(), - by_creative: z.array(DeliveryMetricsSchema.merge(z.object({ + by_creative: z.array(DeliveryMetricsSchema.and(z.object({ creative_id: z.string(), weight: z.number().min(0).max(100).optional() }).passthrough())).optional(), - by_keyword: z.array(DeliveryMetricsSchema.merge(z.object({ + by_keyword: z.array(DeliveryMetricsSchema.and(z.object({ keyword: z.string().optional(), match_type: MatchTypeSchema.optional() }).passthrough())).optional(), - by_geo: z.array(DeliveryMetricsSchema.merge(z.object({ + by_geo: z.array(DeliveryMetricsSchema.and(z.object({ geo_level: GeographicTargetingLevelSchema.optional(), system: z.string().optional(), geo_code: z.string().optional(), geo_name: z.string().optional() }).passthrough())).optional(), by_geo_truncated: z.boolean().optional(), - by_device_type: z.array(DeliveryMetricsSchema.merge(z.object({ + by_device_type: z.array(DeliveryMetricsSchema.and(z.object({ device_type: DeviceTypeSchema.optional() }).passthrough())).optional(), by_device_type_truncated: z.boolean().optional(), - by_device_platform: z.array(DeliveryMetricsSchema.merge(z.object({ + by_device_platform: z.array(DeliveryMetricsSchema.and(z.object({ device_platform: DevicePlatformSchema.optional() }).passthrough())).optional(), by_device_platform_truncated: z.boolean().optional(), - by_audience: z.array(DeliveryMetricsSchema.merge(z.object({ + by_audience: z.array(DeliveryMetricsSchema.and(z.object({ audience_id: z.string().optional(), audience_source: AudienceSourceSchema.optional(), audience_name: z.string().optional() }).passthrough())).optional(), by_audience_truncated: z.boolean().optional(), - by_placement: z.array(DeliveryMetricsSchema.merge(z.object({ + by_placement: z.array(DeliveryMetricsSchema.and(z.object({ placement_id: z.string().optional(), placement_name: z.string().optional() }).passthrough())).optional(), @@ -5828,7 +5828,7 @@ export const GetMediaBuyDeliveryResponseSchema = z.object({ window_start: z.iso.datetime(), window_end: z.iso.datetime(), totals: DeliveryMetricsSchema, - by_package: z.array(DeliveryMetricsSchema.merge(z.object({ + by_package: z.array(DeliveryMetricsSchema.and(z.object({ package_id: z.string() }).passthrough())).optional(), is_final: z.boolean().optional(), @@ -6070,7 +6070,7 @@ export const BuildCreativeMultiSuccessSchema = z.object({ ext: ExtensionObjectSchema.optional() }).passthrough(); -export const PreviewCreativeRequestSchema: z.ZodObject<{ [K in keyof PreviewCreativeRequest]-?: z.ZodType }, any> & z.ZodType, PreviewCreativeRequest & Record> = z.object({ +export const PreviewCreativeRequestSchema: z.ZodType, PreviewCreativeRequest & Record> = z.object({ adcp_version: z.string().optional(), adcp_major_version: z.number().optional(), request_type: z.union([z.literal("single"), z.literal("batch"), z.literal("variant")]), @@ -6125,7 +6125,7 @@ export const PreviewCreativeVariantResponseSchema = z.object({ ext: ExtensionObjectSchema.optional() }).passthrough(); -export const CreativeVariantSchema = DeliveryMetricsSchema.merge(z.object({ +export const CreativeVariantSchema = DeliveryMetricsSchema.and(z.object({ variant_id: z.string(), manifest: CreativeManifestSchema.optional(), generation_context: z.object({ @@ -7501,7 +7501,7 @@ export const ListAccountsResponseSchema = z.object({ payload: z.object({}).passthrough().optional(), adcp_version: z.string().optional(), adcp_major_version: z.number().optional(), - accounts: z.array(AccountSchema.merge(z.object({ + accounts: z.array(AccountSchema.and(z.object({ authorization: AccountAuthorizationSchema.optional() }).passthrough())), errors: z.array(ErrorSchema).optional(), @@ -7793,7 +7793,7 @@ export const DigestAttestationSchema = z.object({ attestation_mode: z.literal("digest") }).passthrough(); -export const IndividualImageAssetSchema = BaseIndividualAssetSchema.merge(z.object({ +export const IndividualImageAssetSchema = BaseIndividualAssetSchema.and(z.object({ asset_type: z.literal("image"), requirements: ImageAssetRequirementsSchema.optional() }).passthrough()); @@ -8234,7 +8234,7 @@ export const V2ManifestCanonicalFormatKind1Schema = z.object({ ext: ExtensionObjectSchema.optional() }).passthrough(); -export const TasksListRequestSchema = AdCPVersionEnvelopeSchema.merge(z.object({ +export const TasksListRequestSchema = AdCPVersionEnvelopeSchema.and(z.object({ filters: z.object({ protocol: AdCPProtocolSchema.optional(), protocols: z.array(AdCPProtocolSchema).optional(), @@ -8260,7 +8260,7 @@ export const TasksListRequestSchema = AdCPVersionEnvelopeSchema.merge(z.object({ ext: ExtensionObjectSchema.optional() }).passthrough()); -export const ListCreativeFormatsRequestCreativeAgentSchema = AdCPVersionEnvelopeSchema.merge(z.object({ +export const ListCreativeFormatsRequestCreativeAgentSchema = AdCPVersionEnvelopeSchema.and(z.object({ format_ids: z.array(FormatReferenceStructuredObjectSchema).optional(), type: z.union([z.literal("audio"), z.literal("video"), z.literal("display"), z.literal("dooh")]).optional(), asset_types: z.array(z.union([z.literal("image"), z.literal("video"), z.literal("audio"), z.literal("text"), z.literal("html"), z.literal("javascript"), z.literal("url")])).optional(), @@ -8563,7 +8563,7 @@ export const CreatePropertyListResponseSchema = z.object({ ext: ExtensionObjectSchema.optional() }).passthrough(); -export const ValidatePropertyDeliveryRequestSchema = AdCPVersionEnvelopeSchema.merge(z.object({ +export const ValidatePropertyDeliveryRequestSchema = AdCPVersionEnvelopeSchema.and(z.object({ list_id: z.string(), account: AccountReferenceSchema.optional(), records: z.array(DeliveryRecordSchema), @@ -9000,7 +9000,7 @@ export const CreateMediaBuyResponseSchema = z.object({ adcp_major_version: z.number().optional() }).passthrough().and(z.union([CreateMediaBuySuccessSchema, CreateMediaBuyErrorSchema, CreateMediaBuySubmittedSchema])); -export const UpdateMediaBuyRequestSchema: z.ZodObject<{ [K in keyof UpdateMediaBuyRequest]-?: z.ZodType }, any> & z.ZodType, UpdateMediaBuyRequest & Record> = z.object({ +export const UpdateMediaBuyRequestSchema: z.ZodType, UpdateMediaBuyRequest & Record> = z.object({ adcp_version: z.string().optional(), adcp_major_version: z.number().optional(), account: AccountReferenceSchema, @@ -9217,7 +9217,7 @@ export const GetRightsResponseSchema = z.object({ adcp_major_version: z.number().optional() }).passthrough().and(z.union([GetRightsSuccessSchema, GetRightsErrorSchema])); -export const SearchBrandsResponseSchema = AdCPVersionEnvelopeSchema.merge(ProtocolEnvelopeSchema).and(z.union([SearchBrandsSuccessSchema, SearchBrandsErrorSchema])); +export const SearchBrandsResponseSchema = AdCPVersionEnvelopeSchema.and(ProtocolEnvelopeSchema).and(z.union([SearchBrandsSuccessSchema, SearchBrandsErrorSchema])); export const VerifyBrandClaimsResponseBulkSchema = z.object({ context_id: z.string().optional(), @@ -9235,7 +9235,7 @@ export const VerifyBrandClaimsResponseBulkSchema = z.object({ adcp_major_version: z.number().optional() }).passthrough().and(z.union([VerifyBrandClaimsSuccessSchema, VerifyBrandClaimsErrorSchema])); -export const TasksGetResponseSchema = AdCPVersionEnvelopeSchema.merge(ProtocolEnvelopeSchema).merge(z.object({ +export const TasksGetResponseSchema = AdCPVersionEnvelopeSchema.and(ProtocolEnvelopeSchema).and(z.object({ task_id: z.string(), task_type: TaskTypeSchema, protocol: AdCPProtocolSchema, @@ -9269,7 +9269,7 @@ export const TasksGetResponseSchema = AdCPVersionEnvelopeSchema.merge(ProtocolEn ext: ExtensionObjectSchema.optional() }).passthrough()); -export const ValidatePropertyDeliveryResponseSchema = AdCPVersionEnvelopeSchema.merge(ProtocolEnvelopeSchema).merge(z.object({ +export const ValidatePropertyDeliveryResponseSchema = AdCPVersionEnvelopeSchema.and(ProtocolEnvelopeSchema).and(z.object({ compliant: z.boolean().optional(), list_id: z.string(), summary: z.object({ diff --git a/src/type-tests/zod-schema-ergonomics.type-test.ts b/src/type-tests/zod-schema-ergonomics.type-test.ts new file mode 100644 index 000000000..c35bde4a0 --- /dev/null +++ b/src/type-tests/zod-schema-ergonomics.type-test.ts @@ -0,0 +1,23 @@ +// Type-level gate for generated Zod object ergonomics. +// +// The public schemas are commonly composed by adopters with ZodObject helpers. +// A redundant `Record` union intersection must not erase those +// methods from schemas whose effective runtime surface is the object shape. + +import { z } from 'zod'; +import { ProductSchema } from '../lib/types/schemas.generated'; + +const ProductWithCacheSchema = ProductSchema.extend({ + _cached_at: z.string().datetime(), +}); +void ProductWithCacheSchema; + +const ProductWithoutDescriptionSchema = ProductSchema.omit({ + description: true, +}); +void ProductWithoutDescriptionSchema; + +const ProductIdentifierSchema = ProductSchema.pick({ + product_id: true, +}); +void ProductIdentifierSchema; diff --git a/test/lib/zod-schemas.test.js b/test/lib/zod-schemas.test.js index 2c4eb513d..5544f141b 100644 --- a/test/lib/zod-schemas.test.js +++ b/test/lib/zod-schemas.test.js @@ -1,5 +1,6 @@ const { test, describe } = require('node:test'); const assert = require('node:assert'); +const { z } = require('zod'); describe('Zod Schema Validation', () => { let schemas; @@ -23,6 +24,24 @@ describe('Zod Schema Validation', () => { assert.ok(schemas.CanonicalFormatImageSchema.shape.image_formats, 'canonical formats should expose object shape'); }); + test('ProductSchema exposes ZodObject composition helpers', async () => { + if (!schemas) { + schemas = await import('../../dist/lib/types/schemas.generated.js'); + } + + assert.strictEqual(typeof schemas.ProductSchema.extend, 'function', 'ProductSchema should expose .extend()'); + assert.strictEqual(typeof schemas.ProductSchema.omit, 'function', 'ProductSchema should expose .omit()'); + assert.strictEqual(typeof schemas.ProductSchema.pick, 'function', 'ProductSchema should expose .pick()'); + + const extended = schemas.ProductSchema.extend({ _cached_at: z.string().datetime() }); + const picked = schemas.ProductSchema.pick({ product_id: true }); + assert.ok(extended.shape._cached_at, 'extended ProductSchema should include the extension field'); + assert.ok( + picked.safeParse({ product_id: 'prod_123' }).success, + 'picked ProductSchema should validate picked shape' + ); + }); + test('MediaBuySchema validates valid media buy', async () => { if (!schemas) { schemas = await import('../../dist/lib/types/schemas.generated.js'); From 8697f36438130ba3521dfae2eeb815e6d0680aa8 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 24 May 2026 06:47:34 -0400 Subject: [PATCH 2/9] chore: sync package lock versions --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index dd2f5fff2..74883e0f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@adcp/sdk", - "version": "8.1.0-beta.8", + "version": "8.1.0-beta.7", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@adcp/sdk", - "version": "8.1.0-beta.8", + "version": "8.1.0-beta.7", "license": "Apache-2.0", "workspaces": [ ".", @@ -7348,7 +7348,7 @@ "version": "6.0.0", "license": "Apache-2.0", "dependencies": { - "@adcp/sdk": "^8.1.0-beta.8" + "@adcp/sdk": "^8.1.0-beta.7" }, "bin": { "adcp": "bin/adcp.js" From d12594481af9bb7614367b4305fd1fca1c6ea1cd Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 24 May 2026 06:56:18 -0400 Subject: [PATCH 3/9] fix: unwrap named record union schema markers --- scripts/generate-zod-from-ts.ts | 81 ++++++++++++++++++++++++++++-- src/lib/types/schemas.generated.ts | 14 +++--- test/lib/zod-schemas.test.js | 13 +++++ 3 files changed, 97 insertions(+), 11 deletions(-) diff --git a/scripts/generate-zod-from-ts.ts b/scripts/generate-zod-from-ts.ts index 4e0629c3c..d53076db0 100644 --- a/scripts/generate-zod-from-ts.ts +++ b/scripts/generate-zod-from-ts.ts @@ -283,9 +283,9 @@ function postProcessUndefinedUnions(content: string): string { * ZodIntersection types that lose .shape access (needed by MCP SDK for tool registration). * * Also handles z.record(...).and(z.object({...})) patterns (record-first intersections) - * by extracting just the z.object() portion, plus - * z.union([RecordUnknownA, RecordUnknownB]).and(z.object({...})) where every - * union member is only a Record container. + * by extracting just the z.object() portion, plus inline or named + * z.union([RecordUnknownA, RecordUnknownB]).and(z.object({...})) patterns where + * every union member is only a Record container. */ function postProcessRecordIntersections(content: string): string { let result = content; @@ -299,7 +299,10 @@ function postProcessRecordIntersections(content: string): string { // Pass 3: Replace `z.union([RecordUnknown...]).and(CONTENT)` with CONTENT. result = unwrapRecordUnionIntersections(result); - // Pass 4: Strip `.and(z.union([...]))` where content contains z.never() + // Pass 4: Replace `NamedRecordUnion.and(CONTENT)` with CONTENT. + result = unwrapNamedRecordUnionIntersections(result); + + // Pass 5: Strip `.and(z.union([...]))` where content contains z.never() result = stripNeverUnionIntersections(result); return result; @@ -561,6 +564,29 @@ function isRedundantRecordMember(member: string, recordSchemaNames: Set) return trimmed === 'z.record(z.string(), z.unknown())' || recordSchemaNames.has(trimmed); } +function collectRedundantRecordUnionSchemaNames(content: string, recordSchemaNames: Set): Set { + const names = new Set(); + const unionDeclPattern = /(?:export\s+)?const\s+(\w+)\s*=\s*z\.union\(\[/g; + let match: RegExpExecArray | null; + + while ((match = unionDeclPattern.exec(content)) !== null) { + const name = match[1]!; + const unionBodyStart = unionDeclPattern.lastIndex; + const unionBody = readBalancedBody(content, unionBodyStart, '[', ']'); + if (!unionBody || content[unionBody.end] !== ')') continue; + + const afterUnion = content.slice(unionBody.end + 1).trimStart(); + if (!afterUnion.startsWith(';')) continue; + + const members = splitTopLevelCommaList(unionBody.body); + if (members.length > 0 && members.every(member => isRedundantRecordMember(member, recordSchemaNames))) { + names.add(name); + } + } + + return names; +} + /** * Replace `z.union([RecordUnknownA, RecordUnknownB]).and(CONTENT)` with CONTENT. * @@ -613,6 +639,53 @@ function unwrapRecordUnionIntersections(content: string): string { return result; } +/** + * Replace `NamedRecordUnion.and(z.object({...}))` with just the object side. + * + * This is the named-schema counterpart to `unwrapRecordUnionIntersections`. + * It intentionally applies only during the marker-only era, where the named + * union is composed exclusively of `Record` marker arms. If a + * future spec version adds real fields to those variants, they will no longer + * be collected here and the generated schema will correctly remain a + * ZodIntersection until the richer constraints are modeled another way. + */ +function unwrapNamedRecordUnionIntersections(content: string): string { + const recordSchemaNames = collectRedundantRecordSchemaNames(content); + const unionSchemaNames = collectRedundantRecordUnionSchemaNames(content, recordSchemaNames); + let result = ''; + let i = 0; + + while (i < content.length) { + let matchedName: string | undefined; + for (const name of unionSchemaNames) { + if (content.startsWith(`${name}.and(`, i)) { + matchedName = name; + break; + } + } + + if (!matchedName) { + result += content[i]; + i++; + continue; + } + + const intersectionStart = i; + const andBodyStart = i + `${matchedName}.and(`.length; + const andBody = readBalancedBody(content, andBodyStart, '(', ')'); + if (andBody && andBody.body.trimStart().startsWith('z.object(')) { + result += andBody.body; + i = andBody.end; + continue; + } + + result += content.substring(intersectionStart, andBody?.end ?? i + matchedName.length); + i = andBody?.end ?? i + matchedName.length; + } + + return result; +} + /** * Strip `.and(z.union([...]))` where the union body contains z.never(). * diff --git a/src/lib/types/schemas.generated.ts b/src/lib/types/schemas.generated.ts index c07284477..17e7672d9 100644 --- a/src/lib/types/schemas.generated.ts +++ b/src/lib/types/schemas.generated.ts @@ -1,5 +1,5 @@ // Generated Zod v4 schemas from TypeScript types -// Generated at: 2026-05-24T01:31:36.131Z +// Generated at: 2026-05-24T10:55:14.077Z // Sources: // - core.generated.ts (core types) // - tools.generated.ts (tool types) @@ -631,7 +631,7 @@ export const NoneSchema = z.record(z.string(), z.unknown()); export const SizeModeMutexSchema = z.union([FixedSchema, MultiSizeSchema, ResponsiveSchema, NoneSchema]); -export const CanonicalFormatDisplayTagSchema = SizeModeMutexSchema.and(z.object({ +export const CanonicalFormatDisplayTagSchema = z.object({ experimental: z.boolean().optional(), deprecated: z.boolean().optional(), v1_translatable: z.boolean().optional(), @@ -660,7 +660,7 @@ export const CanonicalFormatDisplayTagSchema = SizeModeMutexSchema.and(z.object( backup_image_required: z.boolean().optional(), backup_image_max_size_kb: z.number().min(1).optional(), om_sdk_required: z.boolean().optional() -}).passthrough()); +}).passthrough(); export const PriceAdjustmentKindSchema = z.union([z.literal("fee"), z.literal("discount"), z.literal("commission"), z.literal("settlement")]); @@ -698,7 +698,7 @@ export const TalentRoleSchema = z.union([z.literal("host"), z.literal("guest"), export const DerivativeTypeSchema = z.union([z.literal("clip"), z.literal("highlight"), z.literal("recap"), z.literal("trailer"), z.literal("bonus")]); -export const CanonicalFormatImageSchema = SizeModeMutexSchema.and(z.object({ +export const CanonicalFormatImageSchema = z.object({ experimental: z.boolean().optional(), deprecated: z.boolean().optional(), v1_translatable: z.boolean().optional(), @@ -729,9 +729,9 @@ export const CanonicalFormatImageSchema = SizeModeMutexSchema.and(z.object({ cta_values: z.array(z.string()).optional(), asset_source: z.union([z.literal("buyer_uploaded"), z.literal("publisher_host_recorded"), z.literal("seller_pre_rendered_from_brief"), z.literal("seller_human_designed"), z.literal("agent_synthesized")]).optional(), buyer_asset_acceptance: z.union([z.literal("accepted"), z.literal("rejected")]).optional() -}).passthrough()); +}).passthrough(); -export const CanonicalFormatHTML5BannerSchema = SizeModeMutexSchema.and(z.object({ +export const CanonicalFormatHTML5BannerSchema = z.object({ experimental: z.boolean().optional(), deprecated: z.boolean().optional(), v1_translatable: z.boolean().optional(), @@ -765,7 +765,7 @@ export const CanonicalFormatHTML5BannerSchema = SizeModeMutexSchema.and(z.object backup_image_required: z.boolean().optional(), backup_image_max_size_kb: z.number().min(1).optional(), ssl_required: z.boolean().optional() -}).passthrough()); +}).passthrough(); export const DisplayTagFormatDeclarationSchema = z.object({ format_kind: z.literal("display_tag"), diff --git a/test/lib/zod-schemas.test.js b/test/lib/zod-schemas.test.js index 5544f141b..1441b2ec4 100644 --- a/test/lib/zod-schemas.test.js +++ b/test/lib/zod-schemas.test.js @@ -1,5 +1,7 @@ const { test, describe } = require('node:test'); const assert = require('node:assert'); +const { readFileSync } = require('node:fs'); +const path = require('node:path'); const { z } = require('zod'); describe('Zod Schema Validation', () => { @@ -42,6 +44,17 @@ describe('Zod Schema Validation', () => { ); }); + test('generated declarations do not expose record-union object intersections', () => { + const declarations = readFileSync(path.join(__dirname, '../../dist/lib/types/schemas.generated.d.ts'), 'utf8'); + const matches = declarations.match(/z\.ZodIntersection { if (!schemas) { schemas = await import('../../dist/lib/types/schemas.generated.js'); From c402a4392bced4077be940a04f222d7751240230 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 24 May 2026 07:01:20 -0400 Subject: [PATCH 4/9] chore: add ProductSchema DX changeset --- .changeset/product-schema-zodobject-ergonomics.md | 7 +++++++ scripts/generate-zod-from-ts.ts | 3 ++- test/lib/zod-schemas.test.js | 2 ++ 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 .changeset/product-schema-zodobject-ergonomics.md diff --git a/.changeset/product-schema-zodobject-ergonomics.md b/.changeset/product-schema-zodobject-ergonomics.md new file mode 100644 index 000000000..dfc739d81 --- /dev/null +++ b/.changeset/product-schema-zodobject-ergonomics.md @@ -0,0 +1,7 @@ +--- +'@adcp/sdk': patch +--- + +Restore `ZodObject` ergonomics for generated schemas whose only intersection arms are opaque `Record` markers. + +`ProductSchema` and related marker-only format schemas now expose object helpers like `.extend()`, `.omit()`, `.pick()`, and `.shape` again without changing runtime validation behavior. diff --git a/scripts/generate-zod-from-ts.ts b/scripts/generate-zod-from-ts.ts index d53076db0..bc38de2e1 100644 --- a/scripts/generate-zod-from-ts.ts +++ b/scripts/generate-zod-from-ts.ts @@ -552,7 +552,8 @@ function splitTopLevelCommaList(content: string): string[] { function collectRedundantRecordSchemaNames(content: string): Set { const names = new Set(); - const recordSchemaPattern = /(?:export\s+)?const\s+(\w+)\s*=\s*z\.record\(z\.string\(\), z\.unknown\(\)\);/g; + const recordSchemaPattern = + /(?:export\s+)?const\s+(\w+)\s*=\s*z\.record\(\s*z\.string\(\),\s*z\.unknown\(\)\s*\)\s*;?/g; for (const match of content.matchAll(recordSchemaPattern)) { names.add(match[1]!); } diff --git a/test/lib/zod-schemas.test.js b/test/lib/zod-schemas.test.js index 1441b2ec4..731ff506c 100644 --- a/test/lib/zod-schemas.test.js +++ b/test/lib/zod-schemas.test.js @@ -36,8 +36,10 @@ describe('Zod Schema Validation', () => { assert.strictEqual(typeof schemas.ProductSchema.pick, 'function', 'ProductSchema should expose .pick()'); const extended = schemas.ProductSchema.extend({ _cached_at: z.string().datetime() }); + const omitted = schemas.ProductSchema.omit({ description: true }); const picked = schemas.ProductSchema.pick({ product_id: true }); assert.ok(extended.shape._cached_at, 'extended ProductSchema should include the extension field'); + assert.ok(!('description' in omitted.shape), 'omitted ProductSchema should remove the omitted field'); assert.ok( picked.safeParse({ product_id: 'prod_123' }).success, 'picked ProductSchema should validate picked shape' From d2a6f54e259852dcb3865653a086e0e40d1e7392 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 24 May 2026 08:41:50 -0400 Subject: [PATCH 5/9] fix: identifier-boundary guard + loud-fail in named-union unwrap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address two reviewer follow-ups on #1972 (`code-reviewer` and `javascript-protocol-expert` converged on the same nit independently): 1. Left identifier-boundary check in `unwrapNamedRecordUnionIntersections`. `content.startsWith(${name}.and(, i)` previously matched suffixes — `SizeModeMutexSchema` collected today, but a future `FooSizeModeMutexSchema` would silently corrupt output by emitting the prefix character and then the inner schema body. Gate on `i === 0 || !/[A-Za-z0-9_$]/.test(content[i-1])`. 2. Loud-fail when `readBalancedBody` returns undefined. Previously swallowed silently (advanced by name length, dropping content); now throws so a malformed ts-to-zod payload crashes codegen. Also extends `zod-schema-ergonomics.type-test.ts` with `.extend()` / `.omit()` / `.pick()` assertions against `CanonicalFormatDisplayTagSchema`, `CanonicalFormatImageSchema`, and `CanonicalFormatHTML5BannerSchema` — the three Pass 4 targets the original PR only covered via runtime `.d.ts` grep. Regen brings inline-enums and schemas.generated forward. Co-Authored-By: Claude Opus 4.7 (1M context) --- package-lock.json | 6 ++--- scripts/generate-zod-from-ts.ts | 25 +++++++++++++------ src/lib/types/inline-enums.generated.ts | 7 ------ src/lib/types/schemas.generated.ts | 2 +- src/lib/version.ts | 2 +- .../zod-schema-ergonomics.type-test.ts | 25 ++++++++++++++++++- 6 files changed, 46 insertions(+), 21 deletions(-) diff --git a/package-lock.json b/package-lock.json index 74883e0f4..dd2f5fff2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@adcp/sdk", - "version": "8.1.0-beta.7", + "version": "8.1.0-beta.8", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@adcp/sdk", - "version": "8.1.0-beta.7", + "version": "8.1.0-beta.8", "license": "Apache-2.0", "workspaces": [ ".", @@ -7348,7 +7348,7 @@ "version": "6.0.0", "license": "Apache-2.0", "dependencies": { - "@adcp/sdk": "^8.1.0-beta.7" + "@adcp/sdk": "^8.1.0-beta.8" }, "bin": { "adcp": "bin/adcp.js" diff --git a/scripts/generate-zod-from-ts.ts b/scripts/generate-zod-from-ts.ts index bc38de2e1..93118b563 100644 --- a/scripts/generate-zod-from-ts.ts +++ b/scripts/generate-zod-from-ts.ts @@ -659,10 +659,11 @@ function unwrapNamedRecordUnionIntersections(content: string): string { while (i < content.length) { let matchedName: string | undefined; for (const name of unionSchemaNames) { - if (content.startsWith(`${name}.and(`, i)) { - matchedName = name; - break; - } + if (!content.startsWith(`${name}.and(`, i)) continue; + // Left identifier boundary: don't match `FooSizeModeMutexSchema` as `SizeModeMutexSchema`. + if (i > 0 && /[A-Za-z0-9_$]/.test(content[i - 1])) continue; + matchedName = name; + break; } if (!matchedName) { @@ -671,17 +672,25 @@ function unwrapNamedRecordUnionIntersections(content: string): string { continue; } - const intersectionStart = i; const andBodyStart = i + `${matchedName}.and(`.length; const andBody = readBalancedBody(content, andBodyStart, '(', ')'); - if (andBody && andBody.body.trimStart().startsWith('z.object(')) { + if (!andBody) { + // A collected union name followed by `.and(` with no balanced body means + // the ts-to-zod output is malformed. Crash rather than silently corrupt. + throw new Error( + `unwrapNamedRecordUnionIntersections: unbalanced \`.and(\` at offset ${andBodyStart} for ${matchedName}` + ); + } + if (andBody.body.trimStart().startsWith('z.object(')) { result += andBody.body; i = andBody.end; continue; } - result += content.substring(intersectionStart, andBody?.end ?? i + matchedName.length); - i = andBody?.end ?? i + matchedName.length; + // Right-hand side isn't a plain `z.object(...)` — preserve the original + // intersection byte-for-byte so any non-marker constraints survive. + result += content.substring(i, andBody.end); + i = andBody.end; } return result; diff --git a/src/lib/types/inline-enums.generated.ts b/src/lib/types/inline-enums.generated.ts index b27aa96e3..53c0b0648 100644 --- a/src/lib/types/inline-enums.generated.ts +++ b/src/lib/types/inline-enums.generated.ts @@ -285,13 +285,6 @@ export const JobItem_EmploymentTypeValues = ["full_time", "part_time", "contract /** single | JobItem.experience_level */ export const JobItem_ExperienceLevelValues = ["entry_level", "mid_level", "senior", "director", "executive"] as const; -// ====== ListCreativeFormatsRequestCreativeAgent ====== - -/** array of | ListCreativeFormatsRequestCreativeAgent.asset_types */ -export const ListCreativeFormatsRequestCreativeAgent_AssetTypesValues = ["image", "video", "audio", "text", "html", "javascript", "url"] as const; -/** single | ListCreativeFormatsRequestCreativeAgent.type */ -export const ListCreativeFormatsRequestCreativeAgent_TypeValues = ["audio", "video", "display", "dooh"] as const; - // ====== ListCreativeFormatsResponse ====== /** single | ListCreativeFormatsResponse.source */ diff --git a/src/lib/types/schemas.generated.ts b/src/lib/types/schemas.generated.ts index 17e7672d9..702ad504c 100644 --- a/src/lib/types/schemas.generated.ts +++ b/src/lib/types/schemas.generated.ts @@ -1,5 +1,5 @@ // Generated Zod v4 schemas from TypeScript types -// Generated at: 2026-05-24T10:55:14.077Z +// Generated at: 2026-05-24T12:50:07.865Z // Sources: // - core.generated.ts (core types) // - tools.generated.ts (tool types) diff --git a/src/lib/version.ts b/src/lib/version.ts index e546b2fd8..39b7cbf60 100644 --- a/src/lib/version.ts +++ b/src/lib/version.ts @@ -66,7 +66,7 @@ export const VERSION_INFO = { library: '8.1.0-beta.8', adcp: '3.1.0-beta.3', compatibleVersions: COMPATIBLE_ADCP_VERSIONS, - generatedAt: '2026-05-24T12:44:56.798Z', + generatedAt: '2026-05-24T12:39:59.880Z', } as const; /** diff --git a/src/type-tests/zod-schema-ergonomics.type-test.ts b/src/type-tests/zod-schema-ergonomics.type-test.ts index c35bde4a0..311284983 100644 --- a/src/type-tests/zod-schema-ergonomics.type-test.ts +++ b/src/type-tests/zod-schema-ergonomics.type-test.ts @@ -5,7 +5,12 @@ // methods from schemas whose effective runtime surface is the object shape. import { z } from 'zod'; -import { ProductSchema } from '../lib/types/schemas.generated'; +import { + CanonicalFormatDisplayTagSchema, + CanonicalFormatHTML5BannerSchema, + CanonicalFormatImageSchema, + ProductSchema, +} from '../lib/types/schemas.generated'; const ProductWithCacheSchema = ProductSchema.extend({ _cached_at: z.string().datetime(), @@ -21,3 +26,21 @@ const ProductIdentifierSchema = ProductSchema.pick({ product_id: true, }); void ProductIdentifierSchema; + +// Pass 4 (`unwrapNamedRecordUnionIntersections`) target schemas: the +// `SizeModeMutexSchema.and(z.object(...))` form previously left these as +// `ZodIntersection`. Type-level assertion that helpers come back — a +// future codegen regression here surfaces at compile time instead of +// only via the `.d.ts` regression grep. +const DisplayTagExtended = CanonicalFormatDisplayTagSchema.extend({ + _adopter_marker: z.string(), +}); +void DisplayTagExtended; + +const ImagePicked = CanonicalFormatImageSchema.pick({ experimental: true }); +void ImagePicked; + +const HTML5BannerOmitted = CanonicalFormatHTML5BannerSchema.omit({ + deprecated: true, +}); +void HTML5BannerOmitted; From e1c13608ccd731e3410be4f815d2c3a90cf07ab4 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 24 May 2026 12:54:59 +0000 Subject: [PATCH 6/9] test(codegen): fold #1979 hardening items before merge - Pair negative .d.ts intersection grep with runtime _def.typeName===ZodObject assertion in zod-schemas.test.js so the test fails closed if Zod adjusts its tuple stringification. - Note the z.object( body-prefix constraint in both unwrap docstrings so a future editor doesn't accidentally broaden the right-hand side. - Add TODO comment on the one-level limit in collectRedundantRecordUnionSchemaNames. Refs #1979 https://claude.ai/code/session_01768uE6DmJzUwwq4tpb4hqZ --- scripts/generate-zod-from-ts.ts | 9 +++++++-- test/lib/zod-schemas.test.js | 13 ++++++++++++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/scripts/generate-zod-from-ts.ts b/scripts/generate-zod-from-ts.ts index 93118b563..3cfdfb61d 100644 --- a/scripts/generate-zod-from-ts.ts +++ b/scripts/generate-zod-from-ts.ts @@ -580,6 +580,7 @@ function collectRedundantRecordUnionSchemaNames(content: string, recordSchemaNam if (!afterUnion.startsWith(';')) continue; const members = splitTopLevelCommaList(unionBody.body); + // TODO: one-level only — arms that are themselves z.union(...) are not collected if (members.length > 0 && members.every(member => isRedundantRecordMember(member, recordSchemaNames))) { names.add(name); } @@ -589,7 +590,9 @@ function collectRedundantRecordUnionSchemaNames(content: string, recordSchemaNam } /** - * Replace `z.union([RecordUnknownA, RecordUnknownB]).and(CONTENT)` with CONTENT. + * Replace `z.union([RecordUnknownA, RecordUnknownB]).and(CONTENT)` with CONTENT, + * but only when CONTENT begins with `z.object(`. Non-object right-hand sides are + * preserved byte-for-byte so richer constraints survive. * * Some schema variants are intentionally opaque because TypeScript represents * sibling-field constraints as `Record` marker arms. Intersecting @@ -641,7 +644,9 @@ function unwrapRecordUnionIntersections(content: string): string { } /** - * Replace `NamedRecordUnion.and(z.object({...}))` with just the object side. + * Replace `NamedRecordUnion.and(z.object({...}))` with just the object side, + * but only when the right-hand side begins with `z.object(`. Non-object right-hand + * sides are preserved byte-for-byte so richer constraints survive. * * This is the named-schema counterpart to `unwrapRecordUnionIntersections`. * It intentionally applies only during the marker-only era, where the named diff --git a/test/lib/zod-schemas.test.js b/test/lib/zod-schemas.test.js index 731ff506c..711df4564 100644 --- a/test/lib/zod-schemas.test.js +++ b/test/lib/zod-schemas.test.js @@ -46,7 +46,18 @@ describe('Zod Schema Validation', () => { ); }); - test('generated declarations do not expose record-union object intersections', () => { + test('generated declarations do not expose record-union object intersections', async () => { + if (!schemas) { + schemas = await import('../../dist/lib/types/schemas.generated.js'); + } + + // Runtime check so the test fails closed even if Zod adjusts its tuple stringification. + assert.strictEqual( + schemas.ProductSchema._def.typeName, + 'ZodObject', + 'ProductSchema._def.typeName should be ZodObject, not ZodIntersection' + ); + const declarations = readFileSync(path.join(__dirname, '../../dist/lib/types/schemas.generated.d.ts'), 'utf8'); const matches = declarations.match(/z\.ZodIntersection Date: Sun, 24 May 2026 09:19:23 -0400 Subject: [PATCH 7/9] fix(test): use ZodObject constructor name, not Zod 3 _def.typeName MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Triage commit fd68ac693 added a `ProductSchema._def.typeName === 'ZodObject'` assertion based on the reviewer's follow-up suggestion. The reviewer's suggested probe was written against Zod 3 — Zod 4 dropped `_def.typeName` in favor of `_def.type` ('object' lowercase) and the codebase imports Zod 4. The assertion was failing because `_def.typeName` is `undefined`. Use `constructor.name === 'ZodObject'` instead — stable across both Zod major versions and the same closed-fail behavior the reviewer asked for. Co-Authored-By: Claude Opus 4.7 (1M context) --- test/lib/zod-schemas.test.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/lib/zod-schemas.test.js b/test/lib/zod-schemas.test.js index 711df4564..5ce778751 100644 --- a/test/lib/zod-schemas.test.js +++ b/test/lib/zod-schemas.test.js @@ -52,10 +52,11 @@ describe('Zod Schema Validation', () => { } // Runtime check so the test fails closed even if Zod adjusts its tuple stringification. + // Zod 4 dropped `_def.typeName`; constructor name is the stable cross-version probe. assert.strictEqual( - schemas.ProductSchema._def.typeName, + schemas.ProductSchema.constructor.name, 'ZodObject', - 'ProductSchema._def.typeName should be ZodObject, not ZodIntersection' + 'ProductSchema should be a ZodObject, not a ZodIntersection' ); const declarations = readFileSync(path.join(__dirname, '../../dist/lib/types/schemas.generated.d.ts'), 'utf8'); From 76914de4a31af26836c92b59259c20fbb201b9de Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 24 May 2026 09:39:35 -0400 Subject: [PATCH 8/9] chore: regen schemas after rebase onto main with #1975 --- src/lib/types/inline-enums.generated.ts | 7 ++ src/lib/types/schemas.generated.ts | 96 ++++++++++++------------- 2 files changed, 55 insertions(+), 48 deletions(-) diff --git a/src/lib/types/inline-enums.generated.ts b/src/lib/types/inline-enums.generated.ts index 53c0b0648..b27aa96e3 100644 --- a/src/lib/types/inline-enums.generated.ts +++ b/src/lib/types/inline-enums.generated.ts @@ -285,6 +285,13 @@ export const JobItem_EmploymentTypeValues = ["full_time", "part_time", "contract /** single | JobItem.experience_level */ export const JobItem_ExperienceLevelValues = ["entry_level", "mid_level", "senior", "director", "executive"] as const; +// ====== ListCreativeFormatsRequestCreativeAgent ====== + +/** array of | ListCreativeFormatsRequestCreativeAgent.asset_types */ +export const ListCreativeFormatsRequestCreativeAgent_AssetTypesValues = ["image", "video", "audio", "text", "html", "javascript", "url"] as const; +/** single | ListCreativeFormatsRequestCreativeAgent.type */ +export const ListCreativeFormatsRequestCreativeAgent_TypeValues = ["audio", "video", "display", "dooh"] as const; + // ====== ListCreativeFormatsResponse ====== /** single | ListCreativeFormatsResponse.source */ diff --git a/src/lib/types/schemas.generated.ts b/src/lib/types/schemas.generated.ts index 702ad504c..7f0430194 100644 --- a/src/lib/types/schemas.generated.ts +++ b/src/lib/types/schemas.generated.ts @@ -1,5 +1,5 @@ // Generated Zod v4 schemas from TypeScript types -// Generated at: 2026-05-24T12:50:07.865Z +// Generated at: 2026-05-24T13:38:18.504Z // Sources: // - core.generated.ts (core types) // - tools.generated.ts (tool types) @@ -2684,7 +2684,7 @@ export const ValidateContentDeliveryResponseSchema = z.object({ ext: ExtensionObjectSchema.optional() }).passthrough()])); -export const TasksGetRequestSchema = AdCPVersionEnvelopeSchema.and(z.object({ +export const TasksGetRequestSchema = AdCPVersionEnvelopeSchema.merge(z.object({ task_id: z.string(), include_history: z.boolean().optional(), include_result: z.boolean().optional(), @@ -2948,7 +2948,7 @@ export const SyncCatalogsInputRequiredSchema = z.object({ export const SortDirectionSchema = z.union([z.literal("asc"), z.literal("desc")]); -export const TasksListResponseSchema = AdCPVersionEnvelopeSchema.and(ProtocolEnvelopeSchema).and(z.object({ +export const TasksListResponseSchema = AdCPVersionEnvelopeSchema.merge(ProtocolEnvelopeSchema).merge(z.object({ query_summary: z.object({ total_matching: z.number().min(0).optional(), returned: z.number().min(0).optional(), @@ -5170,62 +5170,62 @@ export const V1V2CanonicalFormatMappingRegistrySchema = z.object({ }).passthrough()) }).passthrough(); -export const GroupImageAssetSchema = BaseGroupAssetSchema.and(z.object({ +export const GroupImageAssetSchema = BaseGroupAssetSchema.merge(z.object({ asset_type: z.literal("image"), requirements: ImageAssetRequirementsSchema.optional() }).passthrough()); -export const GroupVideoAssetSchema = BaseGroupAssetSchema.and(z.object({ +export const GroupVideoAssetSchema = BaseGroupAssetSchema.merge(z.object({ asset_type: z.literal("video"), requirements: VideoAssetRequirementsSchema.optional() }).passthrough()); -export const GroupAudioAssetSchema = BaseGroupAssetSchema.and(z.object({ +export const GroupAudioAssetSchema = BaseGroupAssetSchema.merge(z.object({ asset_type: z.literal("audio"), requirements: AudioAssetRequirementsSchema.optional() }).passthrough()); -export const GroupTextAssetSchema = BaseGroupAssetSchema.and(z.object({ +export const GroupTextAssetSchema = BaseGroupAssetSchema.merge(z.object({ asset_type: z.literal("text"), requirements: TextAssetRequirementsSchema.optional() }).passthrough()); -export const GroupMarkdownAssetSchema = BaseGroupAssetSchema.and(z.object({ +export const GroupMarkdownAssetSchema = BaseGroupAssetSchema.merge(z.object({ asset_type: z.literal("markdown"), requirements: MarkdownAssetRequirementsSchema.optional() }).passthrough()); -export const GroupHtmlAssetSchema = BaseGroupAssetSchema.and(z.object({ +export const GroupHtmlAssetSchema = BaseGroupAssetSchema.merge(z.object({ asset_type: z.literal("html"), requirements: HTMLAssetRequirementsSchema.optional() }).passthrough()); -export const GroupCssAssetSchema = BaseGroupAssetSchema.and(z.object({ +export const GroupCssAssetSchema = BaseGroupAssetSchema.merge(z.object({ asset_type: z.literal("css"), requirements: CSSAssetRequirementsSchema.optional() }).passthrough()); -export const GroupJavaScriptAssetSchema = BaseGroupAssetSchema.and(z.object({ +export const GroupJavaScriptAssetSchema = BaseGroupAssetSchema.merge(z.object({ asset_type: z.literal("javascript"), requirements: JavaScriptAssetRequirementsSchema.optional() }).passthrough()); -export const GroupVastAssetSchema = BaseGroupAssetSchema.and(z.object({ +export const GroupVastAssetSchema = BaseGroupAssetSchema.merge(z.object({ asset_type: z.literal("vast"), requirements: VASTAssetRequirementsSchema.optional() }).passthrough()); -export const GroupDaastAssetSchema = BaseGroupAssetSchema.and(z.object({ +export const GroupDaastAssetSchema = BaseGroupAssetSchema.merge(z.object({ asset_type: z.literal("daast"), requirements: DAASTAssetRequirementsSchema.optional() }).passthrough()); -export const GroupUrlAssetSchema = BaseGroupAssetSchema.and(z.object({ +export const GroupUrlAssetSchema = BaseGroupAssetSchema.merge(z.object({ asset_type: z.literal("url"), requirements: URLAssetRequirementsSchema.optional() }).passthrough()); -export const GroupWebhookAssetSchema = BaseGroupAssetSchema.and(z.object({ +export const GroupWebhookAssetSchema = BaseGroupAssetSchema.merge(z.object({ asset_type: z.literal("webhook"), requirements: WebhookAssetRequirementsSchema.optional() }).passthrough()); @@ -5401,68 +5401,68 @@ export const BaseIndividualAssetSchema = z.object({ asset_group_id: z.string().optional() }).passthrough(); -export const IndividualVideoAssetSchema = BaseIndividualAssetSchema.and(z.object({ +export const IndividualVideoAssetSchema = BaseIndividualAssetSchema.merge(z.object({ asset_type: z.literal("video"), requirements: VideoAssetRequirementsSchema.optional() }).passthrough()); -export const IndividualAudioAssetSchema = BaseIndividualAssetSchema.and(z.object({ +export const IndividualAudioAssetSchema = BaseIndividualAssetSchema.merge(z.object({ asset_type: z.literal("audio"), requirements: AudioAssetRequirementsSchema.optional() }).passthrough()); -export const IndividualTextAssetSchema = BaseIndividualAssetSchema.and(z.object({ +export const IndividualTextAssetSchema = BaseIndividualAssetSchema.merge(z.object({ asset_type: z.literal("text"), requirements: TextAssetRequirementsSchema.optional() }).passthrough()); -export const IndividualMarkdownAssetSchema = BaseIndividualAssetSchema.and(z.object({ +export const IndividualMarkdownAssetSchema = BaseIndividualAssetSchema.merge(z.object({ asset_type: z.literal("markdown"), requirements: MarkdownAssetRequirementsSchema.optional() }).passthrough()); -export const IndividualHtmlAssetSchema = BaseIndividualAssetSchema.and(z.object({ +export const IndividualHtmlAssetSchema = BaseIndividualAssetSchema.merge(z.object({ asset_type: z.literal("html"), requirements: HTMLAssetRequirementsSchema.optional() }).passthrough()); -export const IndividualCssAssetSchema = BaseIndividualAssetSchema.and(z.object({ +export const IndividualCssAssetSchema = BaseIndividualAssetSchema.merge(z.object({ asset_type: z.literal("css"), requirements: CSSAssetRequirementsSchema.optional() }).passthrough()); -export const IndividualJavaScriptAssetSchema = BaseIndividualAssetSchema.and(z.object({ +export const IndividualJavaScriptAssetSchema = BaseIndividualAssetSchema.merge(z.object({ asset_type: z.literal("javascript"), requirements: JavaScriptAssetRequirementsSchema.optional() }).passthrough()); export const IndividualZipAssetSchema = BaseIndividualAssetSchema; -export const IndividualVastAssetSchema = BaseIndividualAssetSchema.and(z.object({ +export const IndividualVastAssetSchema = BaseIndividualAssetSchema.merge(z.object({ asset_type: z.literal("vast"), requirements: VASTAssetRequirementsSchema.optional() }).passthrough()); -export const IndividualDaastAssetSchema = BaseIndividualAssetSchema.and(z.object({ +export const IndividualDaastAssetSchema = BaseIndividualAssetSchema.merge(z.object({ asset_type: z.literal("daast"), requirements: DAASTAssetRequirementsSchema.optional() }).passthrough()); -export const IndividualUrlAssetSchema = BaseIndividualAssetSchema.and(z.object({ +export const IndividualUrlAssetSchema = BaseIndividualAssetSchema.merge(z.object({ asset_type: z.literal("url"), requirements: URLAssetRequirementsSchema.optional() }).passthrough()); -export const IndividualWebhookAssetSchema = BaseIndividualAssetSchema.and(z.object({ +export const IndividualWebhookAssetSchema = BaseIndividualAssetSchema.merge(z.object({ asset_type: z.literal("webhook"), requirements: WebhookAssetRequirementsSchema.optional() }).passthrough()); -export const IndividualBriefAssetSchema = BaseIndividualAssetSchema.and(z.object({ +export const IndividualBriefAssetSchema = BaseIndividualAssetSchema.merge(z.object({ asset_type: z.literal("brief") }).passthrough()); -export const IndividualCatalogAssetSchema = BaseIndividualAssetSchema.and(z.object({ +export const IndividualCatalogAssetSchema = BaseIndividualAssetSchema.merge(z.object({ asset_type: z.literal("catalog") }).passthrough()); @@ -5746,10 +5746,10 @@ export const GetMediaBuyDeliveryResponseSchema = z.object({ is_final: z.boolean().optional(), finalized_at: z.iso.datetime().optional(), pricing_model: PricingModelSchema.optional(), - totals: DeliveryMetricsSchema.and(z.object({ + totals: DeliveryMetricsSchema.merge(z.object({ effective_rate: z.number().min(0).optional() }).passthrough()), - by_package: z.array(DeliveryMetricsSchema.and(z.object({ + by_package: z.array(DeliveryMetricsSchema.merge(z.object({ package_id: z.string(), pacing_index: z.number().min(0).optional(), pricing_model: PricingModelSchema.optional(), @@ -5776,40 +5776,40 @@ export const GetMediaBuyDeliveryResponseSchema = z.object({ vendor: BrandReferenceSchema, metric_id: VendorMetricIDSchema }).passthrough()])).optional(), - by_catalog_item: z.array(DeliveryMetricsSchema.and(z.object({ + by_catalog_item: z.array(DeliveryMetricsSchema.merge(z.object({ content_id: z.string().optional(), content_id_type: ContentIDTypeSchema.optional() }).passthrough())).optional(), - by_creative: z.array(DeliveryMetricsSchema.and(z.object({ + by_creative: z.array(DeliveryMetricsSchema.merge(z.object({ creative_id: z.string(), weight: z.number().min(0).max(100).optional() }).passthrough())).optional(), - by_keyword: z.array(DeliveryMetricsSchema.and(z.object({ + by_keyword: z.array(DeliveryMetricsSchema.merge(z.object({ keyword: z.string().optional(), match_type: MatchTypeSchema.optional() }).passthrough())).optional(), - by_geo: z.array(DeliveryMetricsSchema.and(z.object({ + by_geo: z.array(DeliveryMetricsSchema.merge(z.object({ geo_level: GeographicTargetingLevelSchema.optional(), system: z.string().optional(), geo_code: z.string().optional(), geo_name: z.string().optional() }).passthrough())).optional(), by_geo_truncated: z.boolean().optional(), - by_device_type: z.array(DeliveryMetricsSchema.and(z.object({ + by_device_type: z.array(DeliveryMetricsSchema.merge(z.object({ device_type: DeviceTypeSchema.optional() }).passthrough())).optional(), by_device_type_truncated: z.boolean().optional(), - by_device_platform: z.array(DeliveryMetricsSchema.and(z.object({ + by_device_platform: z.array(DeliveryMetricsSchema.merge(z.object({ device_platform: DevicePlatformSchema.optional() }).passthrough())).optional(), by_device_platform_truncated: z.boolean().optional(), - by_audience: z.array(DeliveryMetricsSchema.and(z.object({ + by_audience: z.array(DeliveryMetricsSchema.merge(z.object({ audience_id: z.string().optional(), audience_source: AudienceSourceSchema.optional(), audience_name: z.string().optional() }).passthrough())).optional(), by_audience_truncated: z.boolean().optional(), - by_placement: z.array(DeliveryMetricsSchema.and(z.object({ + by_placement: z.array(DeliveryMetricsSchema.merge(z.object({ placement_id: z.string().optional(), placement_name: z.string().optional() }).passthrough())).optional(), @@ -5828,7 +5828,7 @@ export const GetMediaBuyDeliveryResponseSchema = z.object({ window_start: z.iso.datetime(), window_end: z.iso.datetime(), totals: DeliveryMetricsSchema, - by_package: z.array(DeliveryMetricsSchema.and(z.object({ + by_package: z.array(DeliveryMetricsSchema.merge(z.object({ package_id: z.string() }).passthrough())).optional(), is_final: z.boolean().optional(), @@ -6125,7 +6125,7 @@ export const PreviewCreativeVariantResponseSchema = z.object({ ext: ExtensionObjectSchema.optional() }).passthrough(); -export const CreativeVariantSchema = DeliveryMetricsSchema.and(z.object({ +export const CreativeVariantSchema = DeliveryMetricsSchema.merge(z.object({ variant_id: z.string(), manifest: CreativeManifestSchema.optional(), generation_context: z.object({ @@ -7501,7 +7501,7 @@ export const ListAccountsResponseSchema = z.object({ payload: z.object({}).passthrough().optional(), adcp_version: z.string().optional(), adcp_major_version: z.number().optional(), - accounts: z.array(AccountSchema.and(z.object({ + accounts: z.array(AccountSchema.merge(z.object({ authorization: AccountAuthorizationSchema.optional() }).passthrough())), errors: z.array(ErrorSchema).optional(), @@ -7793,7 +7793,7 @@ export const DigestAttestationSchema = z.object({ attestation_mode: z.literal("digest") }).passthrough(); -export const IndividualImageAssetSchema = BaseIndividualAssetSchema.and(z.object({ +export const IndividualImageAssetSchema = BaseIndividualAssetSchema.merge(z.object({ asset_type: z.literal("image"), requirements: ImageAssetRequirementsSchema.optional() }).passthrough()); @@ -8234,7 +8234,7 @@ export const V2ManifestCanonicalFormatKind1Schema = z.object({ ext: ExtensionObjectSchema.optional() }).passthrough(); -export const TasksListRequestSchema = AdCPVersionEnvelopeSchema.and(z.object({ +export const TasksListRequestSchema = AdCPVersionEnvelopeSchema.merge(z.object({ filters: z.object({ protocol: AdCPProtocolSchema.optional(), protocols: z.array(AdCPProtocolSchema).optional(), @@ -8260,7 +8260,7 @@ export const TasksListRequestSchema = AdCPVersionEnvelopeSchema.and(z.object({ ext: ExtensionObjectSchema.optional() }).passthrough()); -export const ListCreativeFormatsRequestCreativeAgentSchema = AdCPVersionEnvelopeSchema.and(z.object({ +export const ListCreativeFormatsRequestCreativeAgentSchema = AdCPVersionEnvelopeSchema.merge(z.object({ format_ids: z.array(FormatReferenceStructuredObjectSchema).optional(), type: z.union([z.literal("audio"), z.literal("video"), z.literal("display"), z.literal("dooh")]).optional(), asset_types: z.array(z.union([z.literal("image"), z.literal("video"), z.literal("audio"), z.literal("text"), z.literal("html"), z.literal("javascript"), z.literal("url")])).optional(), @@ -8563,7 +8563,7 @@ export const CreatePropertyListResponseSchema = z.object({ ext: ExtensionObjectSchema.optional() }).passthrough(); -export const ValidatePropertyDeliveryRequestSchema = AdCPVersionEnvelopeSchema.and(z.object({ +export const ValidatePropertyDeliveryRequestSchema = AdCPVersionEnvelopeSchema.merge(z.object({ list_id: z.string(), account: AccountReferenceSchema.optional(), records: z.array(DeliveryRecordSchema), @@ -9217,7 +9217,7 @@ export const GetRightsResponseSchema = z.object({ adcp_major_version: z.number().optional() }).passthrough().and(z.union([GetRightsSuccessSchema, GetRightsErrorSchema])); -export const SearchBrandsResponseSchema = AdCPVersionEnvelopeSchema.and(ProtocolEnvelopeSchema).and(z.union([SearchBrandsSuccessSchema, SearchBrandsErrorSchema])); +export const SearchBrandsResponseSchema = AdCPVersionEnvelopeSchema.merge(ProtocolEnvelopeSchema).and(z.union([SearchBrandsSuccessSchema, SearchBrandsErrorSchema])); export const VerifyBrandClaimsResponseBulkSchema = z.object({ context_id: z.string().optional(), @@ -9235,7 +9235,7 @@ export const VerifyBrandClaimsResponseBulkSchema = z.object({ adcp_major_version: z.number().optional() }).passthrough().and(z.union([VerifyBrandClaimsSuccessSchema, VerifyBrandClaimsErrorSchema])); -export const TasksGetResponseSchema = AdCPVersionEnvelopeSchema.and(ProtocolEnvelopeSchema).and(z.object({ +export const TasksGetResponseSchema = AdCPVersionEnvelopeSchema.merge(ProtocolEnvelopeSchema).merge(z.object({ task_id: z.string(), task_type: TaskTypeSchema, protocol: AdCPProtocolSchema, @@ -9269,7 +9269,7 @@ export const TasksGetResponseSchema = AdCPVersionEnvelopeSchema.and(ProtocolEnve ext: ExtensionObjectSchema.optional() }).passthrough()); -export const ValidatePropertyDeliveryResponseSchema = AdCPVersionEnvelopeSchema.and(ProtocolEnvelopeSchema).and(z.object({ +export const ValidatePropertyDeliveryResponseSchema = AdCPVersionEnvelopeSchema.merge(ProtocolEnvelopeSchema).merge(z.object({ compliant: z.boolean().optional(), list_id: z.string(), summary: z.object({ From be3f7f680df3dfecc696846edc68c7f416cd84b8 Mon Sep 17 00:00:00 2001 From: Brian O'Kelley Date: Sun, 24 May 2026 10:50:31 -0400 Subject: [PATCH 9/9] chore: regen schemas after rebase onto main with #1981 --- src/lib/types/schemas.generated.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lib/types/schemas.generated.ts b/src/lib/types/schemas.generated.ts index 7f0430194..5a16bb544 100644 --- a/src/lib/types/schemas.generated.ts +++ b/src/lib/types/schemas.generated.ts @@ -1,5 +1,5 @@ // Generated Zod v4 schemas from TypeScript types -// Generated at: 2026-05-24T13:38:18.504Z +// Generated at: 2026-05-24T14:49:43.372Z // Sources: // - core.generated.ts (core types) // - tools.generated.ts (tool types) @@ -6070,7 +6070,7 @@ export const BuildCreativeMultiSuccessSchema = z.object({ ext: ExtensionObjectSchema.optional() }).passthrough(); -export const PreviewCreativeRequestSchema: z.ZodType, PreviewCreativeRequest & Record> = z.object({ +export const PreviewCreativeRequestSchema: z.ZodObject<{ [K in keyof PreviewCreativeRequest]-?: z.ZodType }, any> & z.ZodType, PreviewCreativeRequest & Record> = z.object({ adcp_version: z.string().optional(), adcp_major_version: z.number().optional(), request_type: z.union([z.literal("single"), z.literal("batch"), z.literal("variant")]), @@ -9000,7 +9000,7 @@ export const CreateMediaBuyResponseSchema = z.object({ adcp_major_version: z.number().optional() }).passthrough().and(z.union([CreateMediaBuySuccessSchema, CreateMediaBuyErrorSchema, CreateMediaBuySubmittedSchema])); -export const UpdateMediaBuyRequestSchema: z.ZodType, UpdateMediaBuyRequest & Record> = z.object({ +export const UpdateMediaBuyRequestSchema: z.ZodObject<{ [K in keyof UpdateMediaBuyRequest]-?: z.ZodType }, any> & z.ZodType, UpdateMediaBuyRequest & Record> = z.object({ adcp_version: z.string().optional(), adcp_major_version: z.number().optional(), account: AccountReferenceSchema,