typespec-http-zod 0.30.0 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/src/zod.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { type Enum, type Model, type ModelProperty, type Program, type Scalar, type Type } from "@typespec/compiler";
2
- import { Visibility } from "@typespec/http";
1
+ import { type Enum, type Model, type ModelProperty, type Program, type Scalar, type Type, type Union } from "@typespec/compiler";
2
+ import { type MetadataInfo, Visibility } from "@typespec/http";
3
3
  /**
4
4
  * What a walk returns after reporting a refusal.
5
5
  *
@@ -10,17 +10,101 @@ import { Visibility } from "@typespec/http";
10
10
  * failed the compile that would have written it.
11
11
  */
12
12
  export declare const UNREPRESENTABLE = "z.never()";
13
- export declare function withDeferredAnnotator<T>(annotator: (property: ModelProperty) => string | undefined, run: () => T): T;
13
+ export declare function withDeferredAnnotator<T>(annotator: (property: ModelProperty, position: PropertyPosition) => string | undefined, typeAnnotator: (type: Type) => string | undefined, run: () => T): T;
14
14
  export declare function withSealedObjects<T>(sealed: boolean, run: () => T): T;
15
- export declare function withVisibility<T>(program: Program, next: Visibility, run: () => T): T;
15
+ /**
16
+ * **openapi3's own metadata view: `Read` is canonical, and a read-only property is shareable.**
17
+ *
18
+ * Built with `@typespec/http`'s defaults, the canonical visibility was `All`, so a model whose
19
+ * create-only property is visible at `All` was not transformed at `Create`: its create body
20
+ * collapsed onto the bare declaration, and every response was then validated as the CREATE view -
21
+ * `{name}` refused for want of the `secret` no response carries. And a read-only `id` made a model
22
+ * transformed at `Create`, where openapi3 shares the canonical schema, so the create body was
23
+ * validated without the `id` the document requires. Both are openapi3's arguments in
24
+ * `openapi.js`, passed as it passes them.
25
+ */
26
+ export declare function metadataInfoOf(program: Program): MetadataInfo;
27
+ export declare function withVisibility<T>(next: Visibility, run: () => T): T;
16
28
  /** Whether a property is optional AT THE CURRENT POSITION - PATCH makes update fields optional. */
17
29
  export declare function isOptionalAt(program: Program, property: ModelProperty): boolean;
18
30
  /** Whether a property belongs in the BODY at the current position, rather than being metadata. */
19
31
  export declare function isPayloadProperty(program: Program, property: ModelProperty): boolean;
20
- /** Whether this model is shaped differently at `visibility` than at the canonical `Read`. */
21
- export declare function isTransformedBy(program: Program, model: Model, at: Visibility): boolean;
32
+ export declare function withMetadataAnnotationsIgnored<T>(ignored: boolean, run: () => T): T;
33
+ export declare function withMultipartBinary<T>(binary: boolean, run: () => T): T;
34
+ export declare function multipartBinaryContext(): boolean;
35
+ /** `bytes` kept raw, as openapi3's `isBytesKeptRaw` reads it: the standard scalar, not encoded. */
36
+ export declare function isRawBytes(program: Program, type: Type): boolean;
37
+ /** A union one of whose own members is raw `bytes` - see `withMultipartBinary`. */
38
+ export declare function holdsRawBytes(program: Program, type: Type): boolean;
39
+ /** Whether metadata annotations are ignored where the walk is - the registry keys declarations on it. */
40
+ export declare function metadataAnnotationsIgnored(): boolean;
41
+ /** Whether this model or union is shaped differently at `visibility` than at the canonical `Read`. */
42
+ export declare function isTransformedBy(program: Program, type: Model | Union, at: Visibility): boolean;
43
+ /**
44
+ * **The visibility a type is rendered at where another type refers to it: openapi3's
45
+ * `reduceContext`.** A type that is not transformed at the requested visibility renders at `Read`, so
46
+ * a model with nothing create-specific inside a create body IS the canonical schema - a read-only
47
+ * property shared into it, as `canShareProperty` says. Rendered at the requested visibility under the
48
+ * canonical name, the body dropped that property while the document requires it.
49
+ *
50
+ * **Only where openapi3 reduces.** Its asset emitter asks for a reference context by what it emits:
51
+ * a named model, a model expression, a template instance, a named union, a scalar or an enum. A list -
52
+ * `T[]` or `model Tags is Tag[]` - and a union expression or instance have none, so they keep the
53
+ * visibility they were reached at, and so does everything inside them. Reduced here as well, a
54
+ * `model Tags is Tag[]` sent in a create body and returned was one declaration where the document
55
+ * publishes `TagsCreate` and `Tags`, and a model expression inside a union expression in a create
56
+ * body was judged at `Read`, where openapi3 reads its properties at `Create`.
57
+ */
58
+ export declare function renderedVisibilityOf(program: Program, type: Type, requested: Visibility): Visibility;
59
+ /**
60
+ * **The visibility an operation's body, parameter or response is rendered at: openapi3's
61
+ * `emitTypeWithSchemaEmitter`**, which renders a type at `Read` unless it is transformed at the
62
+ * requested visibility - whatever kind of type it is, a list included.
63
+ */
64
+ export declare function rootVisibilityOf(program: Program, type: Type, requested: Visibility): Visibility;
65
+ /**
66
+ * **Whether openapi3 can name a template instance**: its asset emitter's `declarationName`, which
67
+ * spells each argument's own name after the template's and gives up where one has none - a model
68
+ * expression, a literal, an instance of no nameable arguments. Such an instance is written out where
69
+ * it is used: `Maybe<{ x: string }>` is no component, and declared as `Maybe` it was one name for two
70
+ * schemas wherever a second argument appeared.
71
+ */
72
+ export declare function isSpeakableInstance(type: Type): boolean;
73
+ /** A named list model, `model Tags is Tag[]`, which openapi3 emits as an `arrayDeclaration`. */
74
+ export declare function isNamedList(type: Model): boolean;
75
+ /**
76
+ * **An array literal's elements are rendered at `Item` visibility**, as openapi3's
77
+ * `arrayLiteralReferenceContext` renders them: PATCH leaves a list's items required, and metadata on
78
+ * an element is data, since no element carries a header. Read at the list's own visibility, a PATCH
79
+ * made every item's properties optional, a `@header` on an element dropped out of the list the
80
+ * document requires it in, and one such model in a request list and a response list was two shapes
81
+ * under one name - `TaggedCreateItem` and `TaggedItem` in the document. A named list model is a
82
+ * declaration, rendered at its own visibility.
83
+ */
84
+ export declare function atItem<T>(program: Program, list: Model, run: () => T): T;
85
+ /**
86
+ * **An array literal, `T[]`, rather than a named list model** - as openapi3's asset emitter tells
87
+ * them apart, by name: a list called `Array` with no properties of its own. A visibility template's
88
+ * copy of `Pet[]` is no standard type, and read as a named list it was declared as `Array`, one
89
+ * name for every such copy.
90
+ */
91
+ export declare function isArrayLiteral(program: Program, list: Model): boolean;
92
+ export declare function withParameterPosition<T>(inside: boolean, run: () => T): T;
93
+ export declare function inParameterPosition(): boolean;
94
+ /** Why a tuple parameter is refused, and what to write instead - both walks say the same. */
95
+ export declare const TUPLE_PARAMETER: {
96
+ readonly why: "a parameter typed as a tuple, which openapi3 publishes as `prefixItems` over the values it carries and this emitter does not read from the wire";
97
+ readonly remedy: "Declare the parameter as a list of one element type, or carry the value in the body.";
98
+ };
22
99
  /** The visibility the current walk is emitting for - the registry keys declarations on it. */
23
100
  export declare function currentVisibility(): Visibility;
101
+ /**
102
+ * openapi3's own rule for which derived models count - `@typespec/openapi3`'s `util.ts`.
103
+ *
104
+ * A template declaration is not a real subtype, and neither is an uninstantiated template; both
105
+ * would otherwise stop a base from being sealed for no reason a reader could see.
106
+ */
107
+ export declare function includeDerivedModel(model: Model): boolean;
24
108
  /**
25
109
  * Record every enum a service DECLARES, not merely every one its success bodies happen to reach.
26
110
  *
@@ -75,6 +159,21 @@ export declare function withDeclarationSite<T>(site: Type | undefined, run: () =
75
159
  * walked, which does.
76
160
  */
77
161
  export declare function refusalTarget(type: Type): Type;
162
+ /**
163
+ * **The external document a type is published as a reference to**, by `@useRef`: openapi3 writes
164
+ * `$ref: <url>` wherever it is used and no component, so the document states no schema this emitter
165
+ * can read. Read off the decorator applied, as `@typespec/openapi3`, which declares it, is no
166
+ * dependency of this package.
167
+ */
168
+ export declare function externalReferenceOf(type: Type): string | undefined;
169
+ /** Refuse a type the document publishes only as a reference to another document. */
170
+ export declare function refuseExternalReference(program: Program, type: Type, url: string): string;
171
+ /**
172
+ * **The Zod a standard scalar becomes, or `undefined` for any other scalar** - looked up only for
173
+ * the standard library's own: a spec's `scalar uint8 extends string` is a string, and found in this
174
+ * table by its name it was validated as a byte, refusing every string the document accepts.
175
+ */
176
+ export declare function standardScalarSchemaOf(program: Program, scalar: Scalar): string | undefined;
78
177
  /**
79
178
  * **A doc becomes `.describe()`, which is the one place Zod keeps a description.**
80
179
  *
@@ -109,17 +208,83 @@ export declare function described(expression: string, doc: string | undefined):
109
208
  * Without that, `string`'s own "A sequence of textual characters." would sit on every `z.string()`.
110
209
  */
111
210
  export declare function scalarDocOf(program: Program, scalar: Scalar): string | undefined;
112
- /** A scalar's schema without its description, for a position that publishes its own. */
113
211
  /**
114
- * The scalars an `@encode` chain passes through, when it returns to one it already passed - and
115
- * `undefined` when it ends at a type that crosses the wire.
212
+ * **The `@encode` a scalar is published under: its own, or the nearest one up its `extends` chain.**
213
+ *
214
+ * openapi3 builds a scalar that is not standard from its base's schema - encoding included - and
215
+ * then applies its own decorators over it, so `scalar TsChild extends Ts` with
216
+ * `@encode(unixTimestamp, int32)` on `Ts` is published as an integer, exactly as `Ts` is. This read
217
+ * the scalar's own `@encode` alone, so `TsChild` was validated as an ISO date-time string, refusing
218
+ * every value the document accepts and accepting every value it refuses. `link` is the scalar that
219
+ * declares the encoding.
220
+ */
221
+ export declare function scalarEncodingOf(program: Program, scalar: Scalar): {
222
+ readonly link: Scalar;
223
+ readonly encoded: Scalar;
224
+ } | undefined;
225
+ export declare function withSyntheticDeclarations<T>(hook: (name: string, build: () => string, site: Type) => string, run: () => T): T;
226
+ /**
227
+ * **Why a variant of an envelope-less `@discriminated` union cannot be dispatched to by its wire
228
+ * discriminator, or `undefined` where it can.** The variant must carry the discriminator under its
229
+ * WIRE name, as one fixed value. Matched on the TypeSpec name as well, a variant whose `kind` was
230
+ * renamed `t` passed, and the module threw "Invalid discriminated union option" on import; one
231
+ * inheriting an open `type: string` threw on every parse.
232
+ */
233
+ export declare function undeclaredVariantOf(program: Program, variant: Type, property: string): "missing" | "open" | "refused" | undefined;
234
+ /**
235
+ * **The variants a `@discriminated` union dispatches to: each but one no value can be.**
116
236
  *
117
- * **The one guard for every walk that follows `@encode`.** The schema, the wire kind and the contract
118
- * type each follow the chain by calling themselves on the encoded type, and each one overflowed the
119
- * stack on `Ping -> Pong -> Ping`. A scalar encoded as itself is no cycle: the chain ends there.
237
+ * A variant without an envelope that accepts nothing - its fixed discriminator refused by a
238
+ * declaration above it, `kind: "cobra"` under `@maxLength(3) kind: string`, or a key it requires
239
+ * refused by a base openapi3 seals - is published by the document as a member no value satisfies, and
240
+ * a `oneOf` with such a member accepts what the others accept. Refusing the union for it refused every
241
+ * `asp` the document accepts, and dispatching to it put a `z.never()` where Zod reads a literal: the
242
+ * first parse threw "Invalid discriminated union option".
120
243
  */
121
- export declare function encodeCycleOf(program: Program, scalar: Scalar): readonly Scalar[] | undefined;
122
- export declare function withSyntheticDeclarations<T>(hook: (name: string, build: () => string) => string, run: () => T): T;
244
+ export declare function dispatchedVariantsOf(program: Program, union: Union): readonly [string, Type][];
245
+ /**
246
+ * One variant of a `@discriminated` union, as it appears ON THE WIRE.
247
+ *
248
+ * **The union is not a choice between its variant types - it is a choice between wrappings of
249
+ * them**, and this emitter used to ignore the wrapping entirely. `z.discriminatedUnion("kind",
250
+ * [catSchema, dogSchema])` over models that carry no `kind` at all **matches nothing**: every
251
+ * request to a polymorphic endpoint failed, and the differential could not see it because it read a
252
+ * discriminated component as an object.
253
+ *
254
+ * `envelope: "object"` (the default) wraps the variant - `{kind: "cat", value: {...Cat}}` - and
255
+ * openapi3 publishes each wrapper as a component named `union.type.name + capitalize(variantName)`.
256
+ * Mirrored exactly, so the identifiers line up with the component names.
257
+ *
258
+ * `envelope: "none"` injects the discriminator into the variant - `{kind: "cat", ...Cat}` - and is
259
+ * emitted **only when the variant declares that property itself**. Otherwise the document does not
260
+ * publish it and the union is refused; see `undeclared-discriminator`.
261
+ */
262
+ /**
263
+ * **Whether a required property is optional at `at`**: a PATCH with implicit optionality, outside a
264
+ * list's elements - `@typespec/http`'s `isOptional`, for a property with no declaration of its own
265
+ * to ask, as an envelope's are.
266
+ */
267
+ export declare function implicitlyOptional(at: Visibility): boolean;
268
+ /**
269
+ * **The visibility an envelope is published at, within a union rendered at the current one.**
270
+ * openapi3 synthesises the envelope as a model of two properties - the discriminator and the variant
271
+ * - and emits it from the union's context, which reduces it to `Read` unless it is transformed
272
+ * there: where the variant is, or where a PATCH makes both its properties optional. Read as one
273
+ * envelope for every visibility, a PATCH body was held to the read wrapper's required `kind` and
274
+ * `value`, and a response to the create wrapper's variant.
275
+ */
276
+ export declare function envelopeVisibilityOf(program: Program, union: Union, variant: Type): Visibility;
277
+ /** Whether a variant's discriminator may be absent where the union is rendered - see `unionToZod`. */
278
+ export declare function optionalDiscriminator(program: Program, union: Union, variant: Type, options: {
279
+ envelope: string;
280
+ discriminatorPropertyName: string;
281
+ }): boolean;
282
+ /**
283
+ * **The component openapi3 publishes an envelope as**: the union's name and the variant's, suffixed
284
+ * with the envelope's visibility where the union - whose usage openapi3 gives the envelope - is used
285
+ * at more than one. None for a union with no name, whose envelopes are written out where it is.
286
+ */
287
+ export declare function envelopeNameOf(program: Program, union: Union, variantName: string, variant: Type): string | undefined;
123
288
  /**
124
289
  * Every property the model declares, **including the ones it inherits**.
125
290
  *
@@ -142,32 +307,355 @@ export declare function withSyntheticDeclarations<T>(hook: (name: string, build:
142
307
  * refers back to the derived model - `type/model/inheritance/recursive`, the exact case - would throw
143
308
  * during module initialisation. Flattening asks nothing of the base but its properties.
144
309
  */
145
- export declare function inheritedAndOwnProperties(model: Model): ModelProperty[];
310
+ export declare function inheritedAndOwnProperties(program: Program, model: Model): ModelProperty[];
311
+ /**
312
+ * A property of a model as the document publishes the model: the most derived declaration of a wire
313
+ * key, and the declarations of the same key further up the chain, nearest first.
314
+ *
315
+ * **Keyed on the WIRE name, because that is what the document keys a schema's properties on.** A
316
+ * subtype is published as `allOf` its base, so every declaration of a key along the chain still
317
+ * applies to the value. Keyed on the TypeSpec name, `@encodedName("application/json", "k") kind` on a
318
+ * base was REPLACED by a subtype's `kind`, so the subtype lost the `k` its base requires; and a base's
319
+ * `kind` renamed to `type` beside a subtype's own `type` was written as two `type` keys, which does not
320
+ * compile.
321
+ */
322
+ export interface FlattenedProperty {
323
+ readonly property: ModelProperty;
324
+ readonly overridden: readonly ModelProperty[];
325
+ }
326
+ export declare function flattenedPropertiesOf(program: Program, model: Model): readonly FlattenedProperty[];
327
+ /**
328
+ * The keys a model publishes at the current position, each with every declaration of it the
329
+ * document holds the value to - its most derived published declaration first.
330
+ *
331
+ * **A declaration counts where openapi3 publishes it, and only there.** Each model in the chain is
332
+ * published as `allOf` the one above, and each judges its own properties at the position: a
333
+ * `@header` or a create-only property above a response is in no schema the response is held to, so
334
+ * its `@maxLength` bounds nothing there; a `never` or an invisible redeclaration drops out of its own
335
+ * model's schema and leaves the key to the ones above it. Judged on the most derived declaration
336
+ * alone, the first was enforced where the document does not, and the second dropped a key the base
337
+ * still requires.
338
+ *
339
+ * **HTTP metadata is not payload.** A `@header` or `@statusCode` property describes the envelope, so
340
+ * it never appears in the body: RFC 9457 puts `@header("content-type") contentType:
341
+ * "application/problem+json"` on the problem model, the document described a body without it, and
342
+ * this emitter required `contentType` as a member. **A `never` property is dropped, as the document
343
+ * drops it**: `model N { value: never; other: string }` is published as `{other}`, and a validator
344
+ * keeping `value: z.never()` refused `{"other": "x"}`, the exact body the document describes.
345
+ */
346
+ export declare function publishedPropertiesOf(program: Program, model: Model): readonly FlattenedProperty[];
347
+ /**
348
+ * The discriminators a model is published under: its own and every one up its chain, by wire name.
349
+ * openapi3 lists each in its base's `required`, and the base applies through `allOf`.
350
+ */
351
+ export declare function discriminatorsOf(program: Program, model: Model): readonly string[];
352
+ /**
353
+ * **What the document asserts of a property at its position in a model, beyond its own declaration.**
354
+ *
355
+ * openapi3 publishes a subtype as `allOf` its base, so every declaration of a key along the chain
356
+ * still applies. Flattened to the subtype's declaration alone, `Pet { @maxLength(3) name: string }`
357
+ * let `Dog`'s own `name: string` be `"rexie"`, which the document refuses - and, under a discriminated
358
+ * base, so did the base, whose dispatch accepted it.
359
+ *
360
+ * **Where the overriding value narrows each declaration above it, the rest is their keywords**:
361
+ *
362
+ * - `constrainedBy`: the ancestors' own constraints, and their declared scalars', applied to the
363
+ * value for its own JSON type; a literal is checked against them statically.
364
+ * - `never`: no value is both - another JSON type, or a literal the ancestor refuses.
365
+ * - `nullOnly`: the override's literals are all refused, and both sides admit `null`.
366
+ * - `nonNull`: the override admits `null` and a declaration above it does not.
367
+ * - `stringOnly`: openapi3 adds the discriminator as a `string` to the model declaring
368
+ * `@discriminator` where that model's OWN properties lack it, whatever a model above it declares.
369
+ *
370
+ * **Where it does not, the declarations are merged into one schema** - `value`, see
371
+ * {@link mergeParts}: two unrelated models key by key, two lists by element, a union member by
372
+ * member. Zod's `.and()` is never put between two objects: it pools the keys one sealed side refuses,
373
+ * throws on two defaults, and is no option a dispatch can read.
374
+ *
375
+ * `required` holds where the property is a discriminator, which openapi3 adds to the base's
376
+ * `required` however it is declared, or where any declaration above requires it.
377
+ */
378
+ export interface PropertyPosition {
379
+ readonly constrainedBy: readonly Keyworded[];
380
+ readonly required: boolean;
381
+ readonly stringOnly: boolean;
382
+ readonly never: boolean;
383
+ readonly nullOnly: boolean;
384
+ readonly nonNull: boolean;
385
+ /** The value as one schema, where the declarations above are merged with it rather than implied. */
386
+ readonly value: Merged | undefined;
387
+ /** The declarations above, each published at this position. */
388
+ readonly ancestors: readonly ModelProperty[];
389
+ /**
390
+ * **The visibility the most derived declaration's model is published at**, where a merge holds
391
+ * declarations published apart - see `mergeShapes`. Its optionality is read there: `M6 { b }`
392
+ * merged at a PATCH beside an `S8` published at `Read` was `b` required, where the document's
393
+ * `M6` leaves it optional.
394
+ */
395
+ readonly at?: Visibility;
396
+ /** A merge no one schema states - see `unrepresentable-override`. */
397
+ readonly refused: {
398
+ readonly ancestor: ModelProperty;
399
+ readonly why: string;
400
+ readonly recursive: boolean;
401
+ } | undefined;
402
+ }
403
+ export declare const OWN_POSITION: PropertyPosition;
404
+ export declare function positionOf(program: Program, model: Model, entry: FlattenedProperty): PropertyPosition;
405
+ /**
406
+ * **A declaration is judged at the visibility its own model is rendered at**, as openapi3 renders
407
+ * each model of a chain: a subtype's create body is `allOf` its base, and a base with nothing
408
+ * create-specific is the canonical schema - its read-only `id` shared in, `readOnly` and required.
409
+ * Judged at the subtype's visibility, the base's `id` dropped out of the create body the document
410
+ * requires it in. See `renderedVisibilityOf`.
411
+ */
412
+ /** At the visibility a merged member's declaration is published at, where it is not the merge's. */
413
+ export declare function atPosition<T>(position: PropertyPosition, run: () => T): T;
414
+ export declare function atDeclaration<T>(program: Program, property: ModelProperty, run: () => T): T;
415
+ /**
416
+ * **A property's type is referred to at the visibility openapi3 reads its declaration's properties
417
+ * at** - see `readVisibilityOf` - where the declaration is rendered at the current one. Its
418
+ * optionality and its position stay where the model is published; only what the type is named, a
419
+ * list or a union instance openapi3 does not reduce, can differ.
420
+ */
421
+ export declare function atReading<T>(program: Program, property: ModelProperty, run: () => T): T;
422
+ /**
423
+ * **Whether a union reaches itself through union members alone**: `union string { int32, string }`
424
+ * in a namespace, where the member `string` names the union and not the scalar, or `A { int32, B }`
425
+ * beside `B { string, A }`. No property or list element stands between the union and its own
426
+ * reference, so the schema openapi3 publishes, `anyOf: [int32, { $ref: string }]`, refers to itself
427
+ * with no value passed through on the way, and JSON Schema leaves that undefined - measured, Ajv
428
+ * overflows its stack on `5`, `"abc"` and `null` alike. TypeScript refuses the alias it would be,
429
+ * and the walks here that flatten a union recursed until the compiler reported "Emitter crashed!
430
+ * This is a bug." A union with an envelope wraps each member in an object, which a value does pass
431
+ * through, so the walk stops there.
432
+ */
433
+ export declare function reachesItselfThroughUnions(program: Program, union: Union): boolean;
434
+ /** Refuse a union that reaches itself through union members alone - see `reachesItselfThroughUnions`. */
435
+ export declare function refuseSelfReachingUnion(program: Program, union: Union): void;
436
+ /** TypeSpec's own assignability, which an override is checked against and a rename is not. */
437
+ export declare function isAssignable(program: Program, source: Type, target: Type): boolean;
438
+ /**
439
+ * A declaration whose keywords - `@minLength`, `@minItems` and the like - apply to a value beside its
440
+ * type: a property, a declared scalar above it, a model.
441
+ */
442
+ export type Keyworded = ModelProperty | Scalar | Model;
443
+ /**
444
+ * The wrapper openapi3 synthesises for one variant of a `@discriminated` union with an envelope -
445
+ * `{kind: "cat", value: Cat}` - which no TypeSpec type stands for. A member of a merge like any other.
446
+ */
447
+ interface Envelope {
448
+ readonly kind: "Envelope";
449
+ readonly union: Union;
450
+ readonly name: string;
451
+ readonly variant: Type;
452
+ readonly discriminator: string;
453
+ readonly property: string;
454
+ }
455
+ type Member = Type | Envelope;
456
+ /** One schema of the `allOf` a value is held to - see {@link mergeParts}. */
457
+ interface Part {
458
+ /** What the value may be, `null` aside: one member, or each member of a union. */
459
+ readonly members: readonly Member[];
460
+ readonly nullable: boolean;
461
+ readonly keywords: readonly Keyworded[];
462
+ /** The declared type, where the part is one - rendered by reference where it narrows the rest. */
463
+ readonly type?: Type;
464
+ /** A declaration only valid whole: an encoding that keeps its declared type beside another. */
465
+ readonly whole?: ModelProperty;
466
+ /** Why no one schema states the part - a `@discriminated` union with a default variant. */
467
+ readonly refused?: string;
468
+ /** Its members are a `@oneOf` union's: exactly one may accept a value. */
469
+ readonly exclusive?: boolean;
470
+ /**
471
+ * **The visibility its value is published at, where that is not the merge's own** - each
472
+ * declaration's, as openapi3 publishes each: a list literal's element at `Item`, a named list's
473
+ * at the list's own visibility, a record's value at the record's. Merged at one visibility, a
474
+ * PATCH made every element of a list literal optional beside a named list, and a record's values
475
+ * optional where the document keeps them as declared.
476
+ */
477
+ readonly at?: Visibility;
478
+ }
479
+ /**
480
+ * **The declarations of one key, merged into the one schema the document holds its value to.**
481
+ *
482
+ * The document publishes each declaration in its own model and joins the models with `allOf`, so a
483
+ * value at the key is valid where every declaration accepts it. This is that conjunction, worked out
484
+ * as the document's own validator works it out, keyword family by keyword family:
485
+ *
486
+ * - a declaration whose type every other declaration's type admits is the value, with every
487
+ * declaration's keywords on it - a subtype, a derived scalar, a literal the others allow;
488
+ * - a union is merged member by member, and a member no other declaration admits is dropped;
489
+ * - two objects are merged key by key: a key one sealed side does not declare is refused, a key
490
+ * either requires is required, and a typed indexer holds every key it does not name;
491
+ * - two lists are merged element by element, with every declaration's `@minItems` and `@maxItems`;
492
+ * - two scalars of one JSON type that neither narrows are both checked, `.and()` between leaves.
493
+ *
494
+ * `null` is valid only where every declaration admits it, and a JSON type no declaration shares is no
495
+ * value at all. Refused only where no one schema can say it: a merge that refers back to itself, and a
496
+ * `@discriminated` union with a default variant, which this emitter refuses on its own.
497
+ */
498
+ export type Merged =
499
+ /** No value - `withheld` where a refused declaration leaves it so, not the document. */
500
+ {
501
+ readonly kind: "never";
502
+ readonly withheld?: true;
503
+ } | {
504
+ readonly kind: "refused";
505
+ readonly why: string;
506
+ readonly recursive?: true;
507
+ } | {
508
+ readonly kind: "type";
509
+ readonly type: Type;
510
+ readonly keywords: readonly Keyworded[];
511
+ /** The visibility the type is published at, where it is not the merge's own - see `Part`. */
512
+ readonly at?: Visibility;
513
+ } | {
514
+ readonly kind: "whole";
515
+ readonly property: ModelProperty;
516
+ } | {
517
+ readonly kind: "nullable";
518
+ readonly inner: Merged;
519
+ } | {
520
+ readonly kind: "union";
521
+ readonly members: readonly Merged[];
522
+ readonly exclusive?: boolean;
523
+ } | {
524
+ readonly kind: "all";
525
+ readonly parts: readonly Merged[];
526
+ } | {
527
+ readonly kind: "array";
528
+ readonly element: Merged;
529
+ readonly keywords: readonly Keyworded[];
530
+ /** An array literal, whose element is computed and rendered at `Item` visibility. */
531
+ readonly list?: Model;
532
+ } | MergedObject;
533
+ export interface MergedObject {
534
+ readonly kind: "object";
535
+ readonly members: readonly MergedMember[];
536
+ /**
537
+ * What a key no member names may hold: nothing (`sealed`), anything, kept (`loose`) or stripped
538
+ * (`open`), or a value of a schema - a typed indexer.
539
+ */
540
+ readonly rest: "sealed" | "open" | "loose" | Merged;
541
+ /** Every key held to `rest`, and none declared: a dictionary. */
542
+ readonly dictionary: boolean;
543
+ readonly keywords: readonly Keyworded[];
544
+ /** Where a refusal inside the indexer is reported. */
545
+ readonly site: Type | undefined;
546
+ }
547
+ export interface MergedMember {
548
+ readonly key: string;
549
+ /** The declaration whose optionality, default and doc the member takes, or none where none states it. */
550
+ readonly property: ModelProperty | undefined;
551
+ readonly position: PropertyPosition;
552
+ }
553
+ export declare function mergeParts(program: Program, given: readonly (Part | undefined)[]): Merged;
554
+ /**
555
+ * **The indexer that holds a model's keys: the TOPMOST string indexer in its chain.**
556
+ *
557
+ * openapi3 publishes a root model's indexer as `additionalProperties`, which holds every key the
558
+ * model does not declare itself - a subtype's own keys included, since the subtype is `allOf` the
559
+ * root - and a lower model's as `unevaluatedProperties`, which the root's has already evaluated. So
560
+ * the topmost indexer holds every key its own model and those above it do not declare, and any below
561
+ * it never applies. Read as the nearest, a subtype's `m: Inner2` escaped the base's
562
+ * `...Record<Inner>` and a subtype's own indexer refused keys the document accepts.
563
+ */
564
+ export declare function bindingIndexerOf(model: Model): {
565
+ readonly link: Model;
566
+ readonly value: Type;
567
+ } | undefined;
146
568
  /** The indexer in force, which a model can inherit - `model X extends Record<unknown>`. */
147
569
  export declare function effectiveIndexer(model: Model): Model["indexer"];
148
570
  /**
149
- * The subtypes a `@discriminator` base stands for, in declaration order.
571
+ * The subtypes a `@discriminator` base dispatches to: the models its document's `mapping` names.
572
+ *
573
+ * **The compiler's own reading, which is what openapi3 writes the mapping from.** A derived model
574
+ * that declares no value for the discriminator is looked through to the models derived from it
575
+ * that do. This used to be the base's DIRECT subtypes, so `Animal` -> `Mammal` (no `kind`) -> `Cat`
576
+ * dispatched to `Mammal`, which carries no literal `kind`, and Zod threw "Invalid discriminated union
577
+ * option" on every parse - a known kind, an unknown one, anything - while `Cat`, the model the
578
+ * mapping names for `"cat"`, was not in the dispatch at all.
150
579
  *
151
580
  * Filtered by openapi3's own {@link includeDerivedModel} so a template declaration is not mistaken
152
581
  * for a real subtype - the same rule that decides whether a base may be sealed.
153
582
  */
154
583
  export declare function discriminatedSubtypes(program: Program, model: Model): readonly Model[];
155
584
  /**
156
- * **How a `@discriminator` base is validated: a value whose discriminator names a subtype as that
157
- * subtype, and any other value as the base model itself.**
585
+ * The subtypes a discriminated base dispatches to, each with the discriminator value its mapping
586
+ * names it for - see {@link discriminatedSubtypes}.
587
+ */
588
+ export declare function dispatchedSubtypesOf(program: Program, model: Model): readonly (readonly [string, Model])[];
589
+ /**
590
+ * **Why a model the mapping names cannot be dispatched to, or `undefined` where it can.**
158
591
  *
159
- * The document publishes the base as an object of its own properties carrying `discriminator:
160
- * {propertyName, mapping}`, and every subtype as `allOf` that base. The mapping says which subtype a
161
- * value naming it is; nothing in the document refuses a value naming none. This emitter dispatched
162
- * on the mapping and stopped there, so `{ "kind": "wrongKind", "weight": 8 }` was refused for a
163
- * `Dog` whose `kind` is `string | "golden"`, though `Dog` requires only `kind` and `weight`. That is
164
- * the response `@typespec/http-specs` declares for `type/model/inheritance/enum-discriminator`,
165
- * `single-discriminator` and `nested-discriminator`, and a handler returning it failed its own
166
- * response contract (typespec-http-graphql, 2026-10-16).
592
+ * A dispatch reads one fixed value per subtype - `z.discriminatedUnion` has no other way to choose -
593
+ * and the document's `mapping` names a subtype for exactly the values its discriminator fixes. Two
594
+ * declarations break that, and each used to break the emitted module:
167
595
  *
168
- * **A closed discriminator every value of which a subtype claims has no base arm**: a value outside
169
- * it is refused by the base's own discriminator, and a value inside it always names a subtype. So a
170
- * fixed enum fully covered by its subtypes is validated exactly as before.
596
+ * - **a discriminator that is not one fixed value** - `kind: "lab" | string`, or a subtype that
597
+ * renames `kind` with `@encodedName` so the `kind` it publishes is its base's open `string`. Zod
598
+ * threw "Invalid discriminated union option" on the first parse, or on import.
599
+ * - **a value a declaration above it refuses** - `kind: "cobra"` under `@maxLength(3) kind: string`.
600
+ * The document publishes the subtype as `allOf` that base, so no value is one; validated as its own
601
+ * declaration alone, every `"cobra"` was accepted.
602
+ *
603
+ * Each is refused as `invalid-subtype-discriminator`, as `undeclared-discriminator` refuses a
604
+ * discriminator the document cannot state, and the base dispatches to its other subtypes.
605
+ */
606
+ export declare function invalidSubtypeOf(program: Program, subtype: Model, propertyName: string): {
607
+ readonly why: string;
608
+ readonly satisfiable: boolean;
609
+ readonly invisibleAt?: Visibility;
610
+ } | undefined;
611
+ /** A visibility as its flags read: `Update | Patch`, `Create | Item`. */
612
+ export declare function visibilityName(at: Visibility): string;
613
+ /**
614
+ * **Whether a model is a subtype its base's mapping names that no value can be** - its fixed
615
+ * discriminator refused by a declaration above it. Refused as `invalid-subtype-discriminator` and
616
+ * emitted as nothing, so used directly as a body it accepts nothing, as the document does, where it
617
+ * used to accept its own declaration.
618
+ */
619
+ export declare function unsatisfiableSubtype(program: Program, model: Model): boolean;
620
+ /**
621
+ * **A model extending a base that openapi3 seals because it does not count the model as derived.**
622
+ *
623
+ * openapi3 seals a model with no derived models it publishes, and a template instance is not one
624
+ * (`includeDerivedModel`). So with `seal-object-schemas` on, `Pet<string> extends Animal` leaves
625
+ * `Animal` sealed, and `Pet<string>` - published as `allOf` that sealed `Animal` - refuses every
626
+ * value carrying a property `Pet<string>` adds, since `Animal`'s own `unevaluatedProperties` cannot
627
+ * see it. Validated as its own flattened properties, such a value was accepted. Returns the sealed
628
+ * base and the keys it refuses, or `undefined` where nothing is refused.
629
+ */
630
+ export declare function sealedBaseExtensionOf(program: Program, model: Model): {
631
+ readonly base: Model;
632
+ readonly keys: readonly string[];
633
+ readonly required: boolean;
634
+ } | undefined;
635
+ /**
636
+ * **How a `@discriminator` base is validated: as the document's base, deserialized as the subtype
637
+ * its discriminator names where the value is one.**
638
+ *
639
+ * The document publishes the base as an object of its own properties beside `discriminator:
640
+ * {propertyName, mapping}`, and every subtype as `allOf` that base. OAS 3.1.1 section 4.8.25 is
641
+ * normative that the discriminator "MUST NOT change the validation outcome of the schema", and
642
+ * section 4.8.25.2 that in this `allOf` form validating the parent "does not perform a search for
643
+ * child schemas or use them in validation in any way" - restated in 3.2.0 and 3.2.1, and by the
644
+ * spec's editors (OAI/OpenAPI-Specification#3901: "`discriminator` only really helps with things
645
+ * like code generation and data de-serialization. It doesn't help with validation"). Ajv defines it
646
+ * as a no-op for validation.
647
+ *
648
+ * **So the verdict is the base's, and the subtype is a deserialization.** A union of the dispatch
649
+ * and the base accepts exactly what the base accepts - every subtype is `allOf` the base, so a valid
650
+ * subtype is a valid base - and hands back, for a value that is a valid instance of the subtype its
651
+ * discriminator names, that subtype with its own properties, as an SDK deserializing the same body
652
+ * would. A value naming no subtype, or one that is not a valid instance of the subtype it names, is
653
+ * the base.
654
+ *
655
+ * This emitter refused a value naming no subtype until typespec-http-graphql's report of
656
+ * 2026-10-16, and 0.30.0 then guarded the base against a value naming a subtype, so a `"golden"`
657
+ * value missing `Golden`'s own fields was refused though the document accepts it as a `Dog`. Both
658
+ * made the validator stricter than the document it exists to agree with.
171
659
  */
172
660
  export interface DiscriminatedBase {
173
661
  /** The discriminator's wire name. */
@@ -175,18 +663,48 @@ export interface DiscriminatedBase {
175
663
  /** The subtypes a `z.discriminatedUnion` dispatches to, each carrying literal discriminator values. */
176
664
  readonly dispatched: readonly Model[];
177
665
  /**
178
- * Subtypes that are themselves bases validated with a base arm. A `z.union` has no literal values
179
- * to dispatch on, so no `z.discriminatedUnion` can hold one; it sits beside the dispatch, where its
180
- * own literal discriminator keeps it exclusive.
666
+ * Subtypes that are themselves discriminated bases, and so `z.union`s with no literal values to
667
+ * dispatch on; no `z.discriminatedUnion` can hold one, so each sits beside the dispatch.
181
668
  */
182
669
  readonly beside: readonly Model[];
183
- /** Every value a subtype claims, which are the keys of the document's `mapping`. */
184
- readonly claimed: readonly string[];
185
- /** Whether a value no subtype claims is validated as the base model. */
186
- readonly open: boolean;
187
670
  }
188
671
  export declare function discriminatedBaseOf(program: Program, model: Model): DiscriminatedBase | undefined;
189
- export declare function wireKindOf(program: Program, type: Type): "number" | "boolean" | undefined;
672
+ /** What the registry knows of the declarations a value refers to, which the value is built around. */
673
+ export interface HeldDeclarations {
674
+ /**
675
+ * **A declaration refused as validating nothing - a collision - which no dispatch can hold**: its
676
+ * validator is `z.never()`, and `z.discriminatedUnion` throws on every parse given one. The
677
+ * registry re-renders a dispatch that held it before the refusal.
678
+ */
679
+ readonly refused: (expression: string) => boolean;
680
+ /**
681
+ * **A declaration annotated `z.ZodType<T>`, which no dispatch can hold**: the annotation
682
+ * `z.lazy()` requires for a recursive declaration erases the literal values
683
+ * `z.discriminatedUnion` reads, and the module failed to typecheck with `TS2322` - "not
684
+ * assignable to `$ZodTypeDiscriminable`".
685
+ */
686
+ readonly annotated: (expression: string) => boolean;
687
+ /**
688
+ * **A type whose declaration at a visibility is refused** - as a collision, or as
689
+ * `split-visibility` - which a merge or a `@oneOf` holding it holds to nothing. The registry
690
+ * re-renders a value that asked before the refusal.
691
+ */
692
+ readonly refusedAt: (type: Type, at: Visibility) => boolean;
693
+ /**
694
+ * **A type whose declaration at a visibility holds a refused one anywhere it refers to** - see
695
+ * `holdsRefusedMember`. The registry re-renders a value that asked before the refusal.
696
+ */
697
+ readonly holdsRefusalAt: (type: Type, at: Visibility) => boolean;
698
+ }
699
+ export declare function withHeldDeclarations<T>(given: HeldDeclarations, run: () => T): T;
700
+ /**
701
+ * A model as the object it publishes at the current position: the merge of one - see
702
+ * {@link mergeShapes}. **HTTP metadata is not payload**, and neither is a property invisible here or
703
+ * a `never` one, which the document drops; each is judged declaration by declaration - see
704
+ * {@link publishedPropertiesOf}.
705
+ */
706
+ export declare function objectOfModel(program: Program, model: Model): Merged;
707
+ export declare function readsFromText(program: Program, type: Type): boolean;
190
708
  /**
191
709
  * A name as an object key - quoted when it is not a legal JavaScript identifier.
192
710
  *
@@ -221,8 +739,154 @@ export declare function objectKey(name: string): string;
221
739
  * comment indented differently from the member it documents is the tell that a generator wrote it.
222
740
  */
223
741
  export declare function jsDocComment(text: string | undefined, indent: string): string;
742
+ /**
743
+ * The name a property carries **on the wire**, which is not always the name the spec gave it.
744
+ *
745
+ * **`@encodedName` is a first-party decorator, and a decorator is a declaration rather than a
746
+ * behaviour.** It attaches metadata to the type graph and does nothing on its own; every emitter has
747
+ * to go and ask. `@typespec/openapi3` asks - `resolveEncodedName` at `schema-emitter.js:214` - so the
748
+ * document publishes the wire name. This emitter did not, so the validator was keyed on the TypeSpec
749
+ * name and a request carrying exactly what the contract required failed twice over: the declared
750
+ * property was missing, and the one actually sent was an unrecognised key a sealed model rejects.
751
+ *
752
+ * The corpus scenario for this is called "Testing that you send the right JSON name on the wire",
753
+ * and it sat in the green column throughout - compiling cleanly while emitting the wrong key.
754
+ *
755
+ * **The domain therefore sees the wire name too**, because `types.ts` resolves it identically. A
756
+ * validator that renamed on the way through would be a transform, which JSON Schema cannot express
757
+ * and no document could state - the same reason `@trimmed` was removed.
758
+ */
759
+ export declare function rawPropertyKey(program: Program, property: ModelProperty): string;
224
760
  export declare function encodedTypeOf(program: Program, target: ModelProperty | Scalar): Scalar | undefined;
225
- export declare function propertyToZod(program: Program, property: ModelProperty): string;
761
+ /**
762
+ * **What a property under `@encode` must be: what `@typespec/openapi3` publishes for it.**
763
+ *
764
+ * Its `applyEncoding` takes the schema the property would publish - a `$ref` to a declared type, or
765
+ * the type written out inline - and replaces only its `type` with the encoded scalar's, dropping
766
+ * `items` unless that is an array. Everything else stays. So `@encode("x", string) color: Color` is
767
+ * `{ $ref: Color, type: "string" }`: a string, and one of `Color`'s values. This emitter replaced the
768
+ * type outright, so it validated `z.string()` beside that and accepted `"green"`; it dropped a named
769
+ * scalar's `@pattern`, a literal union's values, and the `null` of a nullable property, the last
770
+ * contradicting the comment that said it was kept.
771
+ *
772
+ * - `declared`: no type change - no `@encode`, a query parameter's array style, `rfc3339` on a
773
+ * date-time.
774
+ * - `replaced`: the declared type is written out inline, and nothing it kept constrains a value of
775
+ * the new type - a standard scalar, an inline list, record or object.
776
+ * - `both`: the declared type is kept beside the new one and still constrains it. `kept` is what is
777
+ * left of it once the new `type` has ruled out every member of another JSON type - the whole
778
+ * declared type where nothing was ruled out - and `within` where that alone says both.
779
+ * - `unsatisfiable`: no value is both - `{ $ref: Filter, type: "string" }` - or only `null` is, or
780
+ * the encoded scalar is not standard, so the document states no type at all. Refused, so no
781
+ * validator contradicts it.
782
+ *
783
+ * `nullable` is the document's: openapi3 3.1 applies the encoding to the one member beside `null`
784
+ * when the property publishes as `anyOf: [<member>, { type: "null" }]`, and to the whole `anyOf`
785
+ * otherwise, where the new `type` rules `null` out with everything else of another type.
786
+ */
787
+ export type EncodedValue = {
788
+ readonly kind: "declared";
789
+ } | {
790
+ readonly kind: "replaced";
791
+ readonly scalar: Scalar;
792
+ readonly nullable: boolean;
793
+ } | {
794
+ readonly kind: "both";
795
+ readonly scalar: Scalar;
796
+ /** The encoding's name - `rfc7231`, `ISO8601` - which decides what a member's check is. */
797
+ readonly form: string | undefined;
798
+ readonly nullable: boolean;
799
+ readonly kept: Type | readonly Type[];
800
+ readonly within: boolean;
801
+ } | {
802
+ readonly kind: "unsatisfiable";
803
+ readonly scalar: Scalar;
804
+ readonly declared: Type;
805
+ readonly why: "typeless" | "nothing" | "null";
806
+ };
807
+ export declare function encodedValueOf(program: Program, property: ModelProperty): EncodedValue;
808
+ /**
809
+ * The JSON type a value is published as. `fraction` is a number literal that is not an integer: a
810
+ * `number` scalar may hold an integer, so an `integer` encoding admits it, but `1.5` never can.
811
+ */
812
+ type JsonType = "string" | "integer" | "number" | "fraction" | "boolean" | "object" | "array" | "null";
813
+ /**
814
+ * The JSON type a scalar is published as, its own scalar-level `@encode` included - or `undefined`
815
+ * for a scalar with no standard base, which openapi3 publishes as `{}`, any value. Read as a string,
816
+ * `@encode("x", int32)` on one was refused as unsatisfiable while the document accepts `5`.
817
+ */
818
+ export declare function jsonTypeOfScalar(program: Program, scalar: Scalar): JsonType | undefined;
819
+ /**
820
+ * **Whether an encoded value is written as the encoded scalar intersected with what it keeps.**
821
+ *
822
+ * Where the kept type alone says both it is written alone - but a constraint on the property cannot
823
+ * go on a kept union or literal, which carries no `.min` or `.regex`, so then the constraints go on
824
+ * the encoded scalar and the kept type is intersected beside it. Applied to the union,
825
+ * `@encode("x", string) @minLength(2) v: string | "a"` emitted `z.union(...).min(2)`, and the module
826
+ * threw on import.
827
+ */
828
+ export declare function encodedIntersects(program: Program, property: ModelProperty, encoding: Extract<EncodedValue, {
829
+ kind: "both";
830
+ }>): boolean;
831
+ /**
832
+ * **A union property's constraints, applied to each member as JSON Schema applies them.**
833
+ *
834
+ * `@minLength(2) v: string | "a" | int32` is published `{ anyOf: [...], minLength: 2 }`, and
835
+ * `minLength` constrains a value only where it is a string: `"ab"` and `5` are valid, `"a"` is not.
836
+ * Each member takes the keywords of its own JSON type, and a literal member breaking one is no value
837
+ * at all. Applied to the union, this emitted `z.union(...).min(2)`, and the module threw on import.
838
+ * The constraints are the property's own and those of every declaration above it the position
839
+ * leaves - see `PropertyPosition` - and, where openapi3 adds the discriminator as a `string`, only
840
+ * the members that can be a string are left. Returns the members left, or `undefined` where the
841
+ * property is not a union read member by member.
842
+ */
843
+ export declare function constrainedMembersOf(program: Program, property: ModelProperty, position?: PropertyPosition): readonly Type[] | undefined;
844
+ /** A member with no JSON type of its own: `unknown`, or a scalar with no standard base. */
845
+ export declare function isTypelessMember(program: Program, member: Type): boolean;
846
+ export declare const ANY_JSON_TS: string;
847
+ /** Whether a kept member is a scalar whose own check the encoding's wire form replaces. */
848
+ export declare function replacesFormOf(program: Program, encoding: Extract<EncodedValue, {
849
+ kind: "both";
850
+ }>, member: Type): member is Scalar;
851
+ export declare function isExclusive(program: Program, target: Type): boolean;
852
+ /** Whether a union is exclusive where it is rendered: its own `@oneOf`, or its property's. */
853
+ export declare function isExclusiveHere(program: Program, union: Union): boolean;
854
+ /** The inline union a property's `@oneOf` makes exclusive; a named one is a `$ref` it leaves alone. */
855
+ export declare function exclusiveSiteOf(program: Program, property: ModelProperty, declared: Type): Type | undefined;
856
+ /**
857
+ * **Whether a `@oneOf` holds a member whose declaration is refused, or refers to one that is**, so
858
+ * that it validates nothing. openapi3 still publishes the member, and counts it: `@oneOf union O {
859
+ * a: S, b: N }` with `S` refused as `split-visibility` accepted `{"id": "i"}`, which matches both
860
+ * members in the document and so is refused there - `z.xor` counted the refused `S` as matching
861
+ * nothing. **A member holding a refused declaration further down is held stricter in the same way**:
862
+ * `@oneOf { v0: M16, v1: M13 }`, with `M13`'s `c?: M14` refused, accepted `{ c: {...}, e: "red" }`,
863
+ * which matches both in the document, as `M16` alone. Both walks ask it, so the validator and the
864
+ * contract type are `never` together.
865
+ */
866
+ export declare function holdsRefusedMember(program: Program, union: Union, exclusive: boolean): boolean;
867
+ export declare function withExclusiveSite<T>(site: Type | undefined, run: () => T): T;
868
+ /**
869
+ * The members of a union a value's keywords leave, or `undefined` where the type is no union: a
870
+ * literal member breaking one is no value at all. Shared by both walks, so they list the same members.
871
+ */
872
+ export declare function keptMembersOf(program: Program, type: Type, keywords: readonly Keyworded[]): readonly Type[] | undefined;
873
+ /** The keywords that constrain anything, of the declarations a merged value carries. */
874
+ export declare function constrainingKeywords(program: Program, keywords: readonly Keyworded[]): readonly Keyworded[];
875
+ /**
876
+ * **The members a merged union is written with, by the validator and the contract type alike: one
877
+ * of each text the validator writes, in an `anyOf`.** `M19 | M19` merged with `Record<M19>` was two
878
+ * identical objects, whose two `z.input` types the wire assertion told apart from the one `Record` the
879
+ * contract type is (`TS2344`). Each walk deduplicating by its own text disagreed the other way: `S3`
880
+ * merged under `M1 | M2` is two objects the validator writes apart - `b` held to one `@minLength` and
881
+ * to two - and the contract type wrote as one, and TypeScript counts a union of two such objects as
882
+ * no object. So the validator decides, as it writes them, and the contract type writes the members
883
+ * it kept - see {@link noteWrittenMembers}. Repeated in a `@oneOf`, a value matching both is refused,
884
+ * so there each stays.
885
+ */
886
+ export declare function membersToWrite(program: Program, value: Merged & {
887
+ readonly kind: "union";
888
+ }): readonly Merged[];
889
+ export declare function propertyToZod(program: Program, property: ModelProperty, position?: PropertyPosition): string;
226
890
  /**
227
891
  * **Whether `propertyToZod` emits `.default(...)` for this property, which is exactly whether its
228
892
  * INPUT admits an explicit `undefined`.**
@@ -235,7 +899,9 @@ export declare function propertyToZod(program: Program, property: ModelProperty)
235
899
  *
236
900
  * Pure: it reports nothing, so the type walk can ask it without duplicating a diagnostic.
237
901
  */
238
- export declare function emitsDefault(program: Program, property: ModelProperty): boolean;
902
+ export declare function emitsDefault(program: Program, property: ModelProperty, position?: PropertyPosition): boolean;
903
+ /** The non-null half of a two-variant `T | null` union, or `undefined` if it is not one. */
904
+ export declare function nullableInnerOf(type: Type): Type | undefined;
239
905
  export declare function withRefResolver<T>(resolver: (type: Type) => string | undefined, run: () => T): T;
240
906
  /** Called by the registry when it hands out the identifier of a declaration that is still open. */
241
907
  export declare function noteBackEdge(): void;
@@ -260,4 +926,14 @@ export declare function typeToZod(program: Program, type: Type): string;
260
926
  * occurrences still go through {@link typeToZod} and do resolve - which is what makes a recursive
261
927
  * model reference itself instead of inlining itself forever.
262
928
  */
929
+ /**
930
+ * **An instance of a list template is refused**: `model Page<T> is T[]` made `Page<Item>`, which
931
+ * openapi3 emits as a template instance - an object - and publishes as `type: object` with the
932
+ * element as `additionalProperties`, a dictionary where the spec declares a list. No validator agrees
933
+ * with both. A named list per element type, `model Items is Item[]`, or `Item[]` itself, is a list in
934
+ * the document too.
935
+ */
936
+ export declare function listTemplateInstanceWhy(program: Program, type: Model): string | undefined;
937
+ export declare const LIST_TEMPLATE_REMEDY = "Declare a named list model per element type, `model Items is Item[]`, or use `Item[]` itself.";
263
938
  export declare function typeToZodBody(program: Program, type: Type): string;
939
+ export {};