schema-components 1.21.0 → 1.22.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.
Files changed (72) hide show
  1. package/README.md +1 -1
  2. package/dist/core/adapter.d.mts +20 -3
  3. package/dist/core/adapter.mjs +209 -28
  4. package/dist/core/constraints.d.mts +2 -2
  5. package/dist/core/diagnostics.d.mts +1 -1
  6. package/dist/core/errors.d.mts +1 -1
  7. package/dist/core/fieldOrder.d.mts +1 -1
  8. package/dist/core/formats.d.mts +22 -1
  9. package/dist/core/formats.mjs +21 -0
  10. package/dist/core/limits.d.mts +2 -0
  11. package/dist/core/limits.mjs +23 -0
  12. package/dist/core/merge.d.mts +1 -1
  13. package/dist/core/normalise.d.mts +29 -3
  14. package/dist/core/normalise.mjs +2 -2
  15. package/dist/core/openapi30.mjs +1 -1
  16. package/dist/core/ref.d.mts +1 -1
  17. package/dist/core/ref.mjs +1 -0
  18. package/dist/core/renderer.d.mts +1 -1
  19. package/dist/core/renderer.mjs +0 -2
  20. package/dist/core/swagger2.d.mts +1 -1
  21. package/dist/core/swagger2.mjs +1 -1
  22. package/dist/core/typeInference.d.mts +2 -2
  23. package/dist/core/types.d.mts +2 -2
  24. package/dist/core/types.mjs +1 -4
  25. package/dist/core/version.d.mts +1 -1
  26. package/dist/core/walkBuilders.d.mts +3 -3
  27. package/dist/core/walker.d.mts +1 -1
  28. package/dist/core/walker.mjs +79 -2
  29. package/dist/{diagnostics-CbBPsxSt.d.mts → diagnostics-D0QCYGv0.d.mts} +1 -1
  30. package/dist/{errors-QEwOtQAA.d.mts → errors-DpFwqs5C.d.mts} +1 -1
  31. package/dist/html/a11y.d.mts +2 -2
  32. package/dist/html/a11y.mjs +10 -3
  33. package/dist/html/renderToHtml.d.mts +10 -3
  34. package/dist/html/renderToHtml.mjs +13 -3
  35. package/dist/html/renderToHtmlStream.d.mts +2 -2
  36. package/dist/html/renderers.d.mts +2 -2
  37. package/dist/html/renderers.mjs +0 -5
  38. package/dist/html/streamRenderers.d.mts +5 -4
  39. package/dist/html/streamRenderers.mjs +91 -30
  40. package/dist/limits-Cw5QZND8.d.mts +29 -0
  41. package/dist/{normalise-DaSrnr8g.mjs → normalise-DVEJQmF7.mjs} +468 -115
  42. package/dist/openapi/ApiCallbacks.d.mts +1 -1
  43. package/dist/openapi/ApiLinks.d.mts +1 -1
  44. package/dist/openapi/ApiResponseHeaders.d.mts +1 -1
  45. package/dist/openapi/ApiSecurity.d.mts +1 -1
  46. package/dist/openapi/ApiSecurity.mjs +16 -2
  47. package/dist/openapi/components.d.mts +150 -18
  48. package/dist/openapi/components.mjs +129 -15
  49. package/dist/openapi/parser.d.mts +1 -1
  50. package/dist/openapi/parser.mjs +35 -3
  51. package/dist/openapi/resolve.d.mts +12 -5
  52. package/dist/openapi/resolve.mjs +183 -23
  53. package/dist/react/SchemaComponent.d.mts +100 -35
  54. package/dist/react/SchemaComponent.mjs +59 -45
  55. package/dist/react/SchemaView.d.mts +3 -3
  56. package/dist/react/SchemaView.mjs +2 -2
  57. package/dist/react/fieldPath.d.mts +1 -1
  58. package/dist/react/headless.d.mts +1 -1
  59. package/dist/react/headless.mjs +1 -2
  60. package/dist/react/headlessRenderers.d.mts +3 -4
  61. package/dist/react/headlessRenderers.mjs +10 -30
  62. package/dist/{ref-si8ViYun.d.mts → ref-D-_JBZkF.d.mts} +1 -1
  63. package/dist/{renderer-DI6ZYf7a.d.mts → renderer-BaRlQIuN.d.mts} +2 -2
  64. package/dist/themes/mantine.d.mts +1 -1
  65. package/dist/themes/mui.d.mts +1 -1
  66. package/dist/themes/radix.d.mts +1 -1
  67. package/dist/themes/shadcn.d.mts +1 -1
  68. package/dist/typeInference-DkcUHfaM.d.mts +982 -0
  69. package/dist/{types-BnxPEElk.d.mts → types-BrRMV0en.d.mts} +3 -10
  70. package/package.json +1 -3
  71. package/dist/typeInference-Bxw3NOG1.d.mts +0 -647
  72. /package/dist/{version-D-u7aMfy.d.mts → version-D2jfdX6E.d.mts} +0 -0
@@ -0,0 +1,982 @@
1
+ import { d as FieldOverrides, u as FieldOverride } from "./types-BrRMV0en.mjs";
2
+ import { i as MaxRefDepth } from "./limits-Cw5QZND8.mjs";
3
+ import { z } from "zod";
4
+
5
+ //#region src/core/typeInference.d.ts
6
+ /**
7
+ * Zod 4 types that have no useful JSON Schema representation and so
8
+ * cannot meaningfully be rendered by schema-components. The runtime
9
+ * adapter (`packages/core/src/core/adapter.ts`, see the
10
+ * `zod-type-unrepresentable` classifier rules) catches the thrown
11
+ * error and surfaces it as a `SchemaNormalisationError` — but for
12
+ * the runtime-throwing variants the failure only happens on first
13
+ * render. Statically rejecting these types at the props boundary
14
+ * gives the same diagnostic at compile time.
15
+ *
16
+ * Two categories are listed:
17
+ *
18
+ * 1. **Runtime-throwing.** `z.toJSONSchema()` itself throws when it
19
+ * encounters one of these — bigint, date, map, set, symbol,
20
+ * function, custom, undefined, void, nan, codec. Source-of-truth
21
+ * is the classifier in `adapter.ts` (search for
22
+ * `zod-type-unrepresentable`).
23
+ * 2. **Statically rejected by schema-components.** `z.toJSONSchema()`
24
+ * accepts these without throwing, but the resulting JSON Schema
25
+ * is either degenerate (`ZodNever` becomes `{ not: {} }`,
26
+ * contributing nothing renderable) or the async/Promise dimension
27
+ * is dropped silently (`ZodPromise` is unwrapped to its inner
28
+ * type with no signal to the consumer). Both surface here so the
29
+ * rejection is explicit at the type level even though the runtime
30
+ * is permissive.
31
+ *
32
+ * Names mirror the Zod 4 classic interface exports in
33
+ * `node_modules/zod/v4/classic/schemas.d.cts`.
34
+ */
35
+ type UnrepresentableZodType = z.ZodBigInt | z.ZodDate | z.ZodMap | z.ZodSet | z.ZodSymbol | z.ZodFunction | z.ZodUndefined | z.ZodVoid | z.ZodNaN | z.ZodCodec | z.ZodCustom | z.ZodNever | z.ZodPromise;
36
+ /**
37
+ * Brand returned in place of a rejected Zod input. The descriptive
38
+ * literal is what TypeScript displays when the rejection fires, so
39
+ * developers see why their schema is incompatible.
40
+ */
41
+ interface UnrepresentableZodSchemaError {
42
+ readonly __schemaComponentsError: "Zod 4 type has no JSON Schema representation. See SchemaNormalisationError code 'zod-type-unrepresentable'.";
43
+ }
44
+ /**
45
+ * Recursively unwrap Zod 4 wrappers that hold an inner schema —
46
+ * `ZodOptional`, `ZodNullable`, `ZodReadonly`, `ZodLazy`, and `ZodPipe`.
47
+ *
48
+ * The runtime conversion still throws for `z.optional(z.bigint())`,
49
+ * `z.lazy(() => z.bigint())`, `z.nullable(z.bigint())`,
50
+ * `z.bigint().readonly()`, and `z.bigint().pipe(z.bigint())` because
51
+ * `z.toJSONSchema()` walks into the wrapped schema before reporting the
52
+ * unrepresentable type. The compile-time rejection must therefore peel
53
+ * those wrappers off before checking against the rejection list, or the
54
+ * brand would never surface for wrapped inputs.
55
+ *
56
+ * `ZodCodec` is short-circuited at the top because it is itself listed
57
+ * in {@link UnrepresentableZodType}. It extends `ZodPipe` structurally,
58
+ * so without the early exit the `ZodPipe` branch would unwrap a bare
59
+ * codec into its two sides and the rejection would no longer fire for
60
+ * `z.codec(...)`.
61
+ *
62
+ * `ZodPipe` (non-codec) has two slots (`in` and `out`); both are
63
+ * unwrapped into a union so a single unrepresentable side surfaces via
64
+ * {@link AnyMemberIsUnrepresentable}.
65
+ */
66
+ type UnwrapZodWrapper<T> = T extends z.ZodCodec ? T : T extends z.ZodOptional<infer Inner> ? UnwrapZodWrapper<Inner> : T extends z.ZodNullable<infer Inner> ? UnwrapZodWrapper<Inner> : T extends z.ZodReadonly<infer Inner> ? UnwrapZodWrapper<Inner> : T extends z.ZodLazy<infer Inner> ? UnwrapZodWrapper<Inner> : T extends z.ZodPipe<infer InnerIn, infer InnerOut> ? UnwrapZodWrapper<InnerIn> | UnwrapZodWrapper<InnerOut> : T;
67
+ /**
68
+ * True when any member of the (possibly unioned) input extends one of
69
+ * the unrepresentable Zod types. Distribution over the union ensures a
70
+ * single unrepresentable member triggers true — matching runtime
71
+ * semantics for `ZodPipe`, whose two sides expand to a union via
72
+ * {@link UnwrapZodWrapper}.
73
+ */
74
+ type AnyMemberIsUnrepresentable<T> = (T extends UnrepresentableZodType ? true : false) extends false ? false : true;
75
+ /**
76
+ * Reject Zod 4 inputs whose runtime conversion is known to throw.
77
+ *
78
+ * - When `T` (or any inner schema wrapped by `ZodOptional`,
79
+ * `ZodNullable`, `ZodReadonly`, `ZodLazy`, or `ZodPipe`) is one of
80
+ * the {@link UnrepresentableZodType} variants, the resolved type is
81
+ * {@link UnrepresentableZodSchemaError}, which is not assignable from
82
+ * any legitimate Zod / JSON Schema / OpenAPI input — so the prop
83
+ * fails to typecheck.
84
+ * - Anything else (Zod 4 schemas that DO convert, JSON Schema literals,
85
+ * OpenAPI documents, `unknown` for runtime inputs) passes through
86
+ * unchanged.
87
+ */
88
+ type RejectUnrepresentableZod<T> = AnyMemberIsUnrepresentable<UnwrapZodWrapper<T>> extends true ? UnrepresentableZodSchemaError : T;
89
+ /**
90
+ * Convert a readonly tuple/array of values to a union type.
91
+ * Handles both `as const` readonly tuples and mutable arrays.
92
+ */
93
+ type ArrayToUnion<A> = A extends readonly unknown[] ? A[number] : never;
94
+ /**
95
+ * Direction of inference for `FromJSONSchema`.
96
+ *
97
+ * JSON Schema's `readOnly` / `writeOnly` keywords carry directional
98
+ * semantics: a `readOnly` property must not appear in client → server
99
+ * payloads, and a `writeOnly` property must not appear in server →
100
+ * client payloads. Mapping a schema to a TypeScript type therefore
101
+ * requires knowing which direction the value travels.
102
+ *
103
+ * - `"both"` — return every property regardless of `readOnly` /
104
+ * `writeOnly`. Default, preserves the prior behaviour for callers
105
+ * that do not care about the distinction.
106
+ * - `"input"` — omit properties marked `readOnly: true`. Use for the
107
+ * shape consumers may supply (e.g. `onChange` arguments, POST
108
+ * bodies).
109
+ * - `"output"` — omit properties marked `writeOnly: true`. Use for
110
+ * the shape the server returns (e.g. rendered `value` props, GET
111
+ * responses).
112
+ */
113
+ type FromJSONSchemaMode = "input" | "output" | "both";
114
+ /**
115
+ * Maps a JSON Schema structure to a TypeScript type.
116
+ * Works with `as const` literals -- provides full autocomplete for `fields`.
117
+ *
118
+ * Supports all JSON Schema draft versions (04-2020-12) and OpenAPI 3.x:
119
+ * - Primitive types: string, number, integer, boolean, null
120
+ * - type as array: `["string", "null"]` -> `string | null` (nullable)
121
+ * - enum -> union of literal types
122
+ * - const -> literal type
123
+ * - object with properties/required -> specific object type
124
+ * - object with properties + additionalProperties -> object & Record<string, V>
125
+ * - object with additionalProperties only -> Record<string, T>
126
+ * - array with items -> T[]
127
+ * - array with prefixItems -> tuple type
128
+ * - allOf -> intersection type
129
+ * - anyOf -> union type
130
+ * - oneOf -> union type (plain union, or tagged union when `discriminator` is set)
131
+ * - $ref -> resolved via $defs/definitions/$anchor context
132
+ * - $dynamicRef -> resolved via $dynamicAnchor in definitions
133
+ * - $recursiveRef -> unknown (recursive types not expressible in TS)
134
+ * - if/then/else -> base schema (conditionals not expressible in TS)
135
+ * - not -> unknown (negation not expressible in TS)
136
+ * - patternProperties -> merged into loose index signature
137
+ *
138
+ * The `Mode` parameter controls how `readOnly` / `writeOnly` keywords
139
+ * influence inferred object properties — see {@link FromJSONSchemaMode}.
140
+ */
141
+ type FromJSONSchema<S, Defs extends Record<string, unknown> = Record<string, never>, Depth extends readonly unknown[] = [], Mode extends FromJSONSchemaMode = "both"> = MergeRootDefs<S, Defs> extends infer MergedDefs extends Record<string, unknown> ? S extends {
142
+ nullable: true;
143
+ } ?
144
+ /**
145
+ * OpenAPI 3.0 `nullable: true` — surface the keyword wherever it
146
+ * appears (not just inside `ResolveMaybeRef`). The runtime path
147
+ * rewrites this to `anyOf: [T, { type: "null" }]` via
148
+ * `normaliseOpenApi30Node` (`openapi30.ts`). Mirroring at the
149
+ * `FromJSONSchema` level means nested fields inside refs preserve
150
+ * nullability when resolved.
151
+ */
152
+ FromJSONSchema<Omit<S, "nullable">, MergedDefs, Depth, Mode> | null : S extends {
153
+ $ref: infer R extends string;
154
+ } ? ResolveSchemaRef<R, MergedDefs, Depth, Mode> : S extends {
155
+ $recursiveRef: string;
156
+ } ? unknown : S extends {
157
+ $dynamicRef: infer R extends string;
158
+ } ? ResolveSchemaRef<R, MergedDefs, Depth, Mode> : S extends {
159
+ allOf: infer A;
160
+ } ? AllOfToType<A, MergedDefs, Depth, Mode> : S extends {
161
+ anyOf: infer A;
162
+ } ? UnionOfMembers<A, MergedDefs, Depth, Mode> : S extends {
163
+ oneOf: infer A;
164
+ discriminator: {
165
+ propertyName: infer DP extends string;
166
+ };
167
+ } ? DiscriminatedOneOfToUnion<A, DP, GetDiscriminatorMapping<S>, MergedDefs, Depth, Mode> : S extends {
168
+ oneOf: infer A;
169
+ } ? UnionOfMembers<A, MergedDefs, Depth, Mode> : S extends {
170
+ if: unknown;
171
+ } ? FromJSONSchema<Omit<S, "if" | "then" | "else">, MergedDefs, Depth, Mode> : S extends {
172
+ not: unknown;
173
+ } ? unknown : S extends {
174
+ const: infer V;
175
+ } ? V : S extends {
176
+ enum: infer E;
177
+ } ? ArrayToUnion<E> : S extends {
178
+ type: infer T;
179
+ } ? TypeToTs<T, S, MergedDefs, Depth, Mode> : S extends readonly (infer E)[] ? E : unknown : unknown;
180
+ /**
181
+ * Extract the `discriminator.mapping` map if present, otherwise the
182
+ * empty sentinel. Used by {@link DiscriminatedOneOfToUnion} to inject
183
+ * the discriminator literal into members referenced via $ref.
184
+ */
185
+ type GetDiscriminatorMapping<S> = S extends {
186
+ discriminator: {
187
+ mapping: infer M extends Record<string, string>;
188
+ };
189
+ } ? M : Record<string, never>;
190
+ /**
191
+ * Merge `$defs` / `definitions` declared at the current schema position with
192
+ * the caller-supplied `Defs` map BEFORE the ref/allOf/anyOf/oneOf dispatch.
193
+ *
194
+ * SOURCE-OF-TRUTH: parity with the runtime walker, which uses `rootDocument`
195
+ * (see `packages/core/src/core/ref.ts` line 91) to resolve any `$ref` against
196
+ * the full document — including sibling definitions colocated with the
197
+ * reference. Without this merge, a legal schema like
198
+ * `{ $ref: "#/definitions/Foo", definitions: { Foo: {...} } }` would lose
199
+ * its sibling defs because the ref branch fires before `ExtractDefs` runs
200
+ * inside `ObjectSchemaToTs`.
201
+ *
202
+ * Merge semantics (per-key resolution via {@link CollisionSafeMerge}):
203
+ * - Parent-only keys: parent value wins
204
+ * - Local-only keys: local value wins
205
+ * - Shared keys: parent value wins (caller / inherited context takes
206
+ * precedence over a deeper redeclaration)
207
+ *
208
+ * When the current schema declares no local defs (`HasLocalDefs<S>` is
209
+ * `false`), `ParentDefs` is returned unchanged so the inherited context
210
+ * is never poisoned by the empty index-signature sentinel.
211
+ */
212
+ type MergeRootDefs<S, ParentDefs extends Record<string, unknown>> = HasLocalDefs<S> extends true ? ExtractRawDefs<S> extends infer RawDefs extends Record<string, unknown> ? CollisionSafeMerge<CollisionSafeMerge<RawDefs, ExtractAnchors<RawDefs>>, ParentDefs> : ParentDefs : ParentDefs;
213
+ /**
214
+ * Merge two record-shaped types where keys present in `B` always take
215
+ * precedence over the same key in `A`.
216
+ *
217
+ * The empty default `Record<string, never>` is detected explicitly via
218
+ * {@link IsEmptyDefs}: when either side is the sentinel, the other side
219
+ * is returned unchanged. This avoids two pitfalls of the naive
220
+ * `Omit<A, keyof B> & B` approach:
221
+ *
222
+ * 1. `keyof Record<string, never>` is the entire `string` type, so
223
+ * `Omit<A, string>` would strip every key from `A`.
224
+ * 2. Iterating a mapped type over `keyof A | keyof B` where either side
225
+ * contributes `string` collapses every entry to the index-signature
226
+ * value (`never` in the sentinel), wiping the literal keys from the
227
+ * other side.
228
+ *
229
+ * Only when both sides hold concrete literal keys does the per-key
230
+ * mapped merge run.
231
+ */
232
+ type CollisionSafeMerge<A, B> = IsEmptyDefs<A> extends true ? B : IsEmptyDefs<B> extends true ? A : { [K in keyof A | keyof B]: K extends keyof B ? B[K] : K extends keyof A ? A[K] : never };
233
+ /**
234
+ * True for the empty-default `Record<string, never>` sentinel used as the
235
+ * initial `Defs` map — i.e. an open index signature `[string]: never`
236
+ * with no literal keys. Distinguished from a record with at least one
237
+ * literal key by checking that `string extends keyof T`.
238
+ */
239
+ type IsEmptyDefs<T> = [keyof T] extends [never] ? true : string extends keyof T ? true : false;
240
+ /**
241
+ * True when the schema declares `$defs`, `definitions`, or
242
+ * `components.schemas` as an object, false otherwise. Used by
243
+ * {@link MergeRootDefs} and {@link ExtractDefs} to avoid intersecting
244
+ * the parent context with an empty index-signature sentinel.
245
+ *
246
+ * `components.schemas` is recognised so OpenAPI documents whose root
247
+ * `oneOf`/`anyOf`/`$ref` schemas point into the components tree resolve
248
+ * via the same `Defs` map as `$defs`/`definitions` entries.
249
+ */
250
+ type HasLocalDefs<S> = S extends {
251
+ $defs: Record<string, unknown>;
252
+ } ? true : S extends {
253
+ definitions: Record<string, unknown>;
254
+ } ? true : S extends {
255
+ components: {
256
+ schemas: Record<string, unknown>;
257
+ };
258
+ } ? true : false;
259
+ /**
260
+ * Marker type emitted when OpenAPI $ref resolution hits the type-level
261
+ * recursion depth limit. Instead of silently falling back to
262
+ * `Record<string, FieldOverride>`, produces this branded type so
263
+ * consumers can detect it via conditional types.
264
+ *
265
+ * Usage:
266
+ * ```ts
267
+ * type Fields = InferRequestBodyFields<Doc, "/users", "post">;
268
+ * type IsFallback = Fields extends __SchemaInferenceFellBack ? true : false;
269
+ * ```
270
+ */
271
+ interface __SchemaInferenceFellBack {
272
+ readonly __schemaInferenceFallback: unique symbol;
273
+ }
274
+ /**
275
+ * Escape hatch for recursive schemas where type-level inference
276
+ * cannot proceed. Typed as a map from field name (any string except
277
+ * the brand key) to `FieldOverride`, branded with a **required**
278
+ * discriminator so the unsafe path is an explicit opt-in rather than
279
+ * a silent default.
280
+ *
281
+ * Earlier revisions made the brand optional (`__unsafe?: true`).
282
+ * That defeated the brand's purpose: any plain `Record<string,
283
+ * FieldOverride>` literal silently satisfied the type and the
284
+ * "unsafe" intent was invisible to readers and reviewers. Marking
285
+ * the brand required forces callers to write `{ __unsafe: true,
286
+ * ... }`, making the escape-hatch use visible at the call site.
287
+ *
288
+ * The brand key is carved out of the field-name index signature so
289
+ * `__unsafe: true` does not collide with the `FieldOverride` value
290
+ * constraint — an index signature `[string]: FieldOverride` would
291
+ * otherwise reject the boolean literal.
292
+ *
293
+ * JSDoc trade-off note: This bypasses field-level type safety.
294
+ * Prefer restructuring the schema to avoid deep $ref chains
295
+ * when possible.
296
+ */
297
+ interface UnsafeFields {
298
+ /**
299
+ * Required marker — set to `true` to acknowledge that callers
300
+ * are deliberately bypassing field-level inference. The literal
301
+ * value is not used at runtime.
302
+ */
303
+ readonly __unsafe: true;
304
+ /**
305
+ * Field overrides keyed by name. The recursive `Record` exclusion
306
+ * prevents the brand from being assigned through the index
307
+ * signature.
308
+ */
309
+ readonly [field: string]: FieldOverride | true;
310
+ }
311
+ /**
312
+ * Convert a `FromJSONSchema` result to `unknown` when recursion is detected.
313
+ * Returns the original type when the schema is non-recursive.
314
+ */
315
+ type DetectRecursiveFallback<T> = unknown extends T ? __SchemaInferenceFellBack : T;
316
+ /**
317
+ * Type-level recursion bound for $ref resolution.
318
+ *
319
+ * The TypeScript type system imposes its own recursion limit; without an
320
+ * explicit bound a cyclic schema graph would exhaust it and degrade to
321
+ * `any`/`unknown` silently. This number is the runtime walker's parallel
322
+ * — see `resolveRef` in `packages/core/src/core/ref.ts` (line 119), whose
323
+ * default `maxDepth` is `64`. Matching the runtime bound here means a
324
+ * schema that the runtime resolves successfully is never silently dropped
325
+ * to `__SchemaInferenceFellBack` at compile time purely because the
326
+ * type-level limit was lower.
327
+ *
328
+ * A fixed bound is used here rather than a derived one because the type
329
+ * system has no way to count distinct strings across a recursive `Defs`
330
+ * map without itself recursing — which is the problem the bound exists
331
+ * to solve. Real-world OpenAPI documents (Stripe, GitHub, AWS) routinely
332
+ * contain 30-100+ distinct `$ref` strings, so a low ceiling would mask
333
+ * legitimate references. Deeper graphs surface as
334
+ * `__SchemaInferenceFellBack` so consumers can detect the limit
335
+ * explicitly.
336
+ */
337
+ type DEFAULT_MAX_DEPTH = MaxRefDepth;
338
+ /**
339
+ * Resolve a $ref against the local definitions context.
340
+ *
341
+ * SOURCE-OF-TRUTH: mirrors runtime `resolveRef` in
342
+ * `packages/core/src/core/ref.ts` (line 90). Any change to the runtime
343
+ * ref-resolution rules (new ref forms, different cycle handling) must be
344
+ * reflected here and pinned in
345
+ * `packages/core/tests/typeInference-walker-parity.test.ts`.
346
+ *
347
+ * Supports:
348
+ * - `#` (root)
349
+ * - `#/$defs/Name` and `#/definitions/Name` (named definitions)
350
+ * - `#/components/schemas/Name` (OpenAPI 3.x component schemas)
351
+ * - `#SomeName` ($anchor, $dynamicAnchor resolved from definitions)
352
+ *
353
+ * `#/components/schemas/` is resolved here for parity with the runtime's
354
+ * `dereference` (`ref.ts` line 217), which walks any `#/...` JSON Pointer
355
+ * uniformly. When the runtime walker encounters an inline `$ref` inside
356
+ * a Zod-converted or hand-written JSON Schema that points into the
357
+ * OpenAPI component tree, this branch produces the corresponding type.
358
+ */
359
+ type ResolveSchemaRef<R extends string, Defs extends Record<string, unknown>, Depth extends readonly unknown[] = [], Mode extends FromJSONSchemaMode = "both"> = Depth["length"] extends DEFAULT_MAX_DEPTH ? __SchemaInferenceFellBack : R extends "#" ? unknown : R extends `#/$defs/${infer Name}` ? Name extends keyof Defs ? DetectRecursiveFallback<FromJSONSchema<Defs[Name], Defs, [unknown, ...Depth], Mode>> : unknown : R extends `#/definitions/${infer Name}` ? Name extends keyof Defs ? DetectRecursiveFallback<FromJSONSchema<Defs[Name], Defs, [unknown, ...Depth], Mode>> : unknown : R extends `#/components/schemas/${infer Name}` ? Name extends keyof Defs ? DetectRecursiveFallback<FromJSONSchema<Defs[Name], Defs, [unknown, ...Depth], Mode>> : unknown : R extends `#${infer AnchorName}` ? AnchorName extends keyof Defs ? DetectRecursiveFallback<FromJSONSchema<Defs[AnchorName], Defs, [unknown, ...Depth], Mode>> : unknown : unknown;
360
+ /**
361
+ * Merge an allOf array into an intersection type.
362
+ *
363
+ * KNOWN LIMITATION: when any member of the `allOf` array is itself a
364
+ * union (e.g. produced by an `anyOf` inside one member), distribution
365
+ * across the intersection does not always behave as a hand-written
366
+ * intersection would. `(A | B) & C` distributes to `(A & C) | (B & C)`,
367
+ * but TypeScript's mapped-type machinery — combined with the conditional
368
+ * dispatch above — does not always recover that distribution when the
369
+ * union arises from a `FromJSONSchema` expansion. The pinned regression
370
+ * test `allOf of unions is treated as the intersection of the union
371
+ * members` in `tests/type-inference-issue-fixes.test.ts` documents the
372
+ * current behaviour so future refactors do not silently make it worse.
373
+ * There is no known way to express "distribute every member-side union
374
+ * across the intersection" in TypeScript today without losing the
375
+ * non-union members' information.
376
+ */
377
+ type AllOfToType<A, Defs extends Record<string, unknown>, Depth extends readonly unknown[] = [], Mode extends FromJSONSchemaMode = "both"> = A extends readonly unknown[] ? UnionToIntersection<FromJSONSchema<A[number], Defs, Depth, Mode>> : unknown;
378
+ /**
379
+ * Convert an anyOf/oneOf array into a union type.
380
+ *
381
+ * SOURCE-OF-TRUTH: mirrors runtime `walkUnion` (and the
382
+ * `walkDiscriminatedUnion` fast path) in
383
+ * `packages/core/src/core/walker.ts` (lines 723-752), together with
384
+ * `detectDiscriminated` and `normaliseAnyOf` in
385
+ * `packages/core/src/core/merge.ts` (lines 190-260).
386
+ *
387
+ * Deliberate divergence: the walker collapses qualifying `oneOf` members
388
+ * into a `discriminatedUnion` field at runtime. The type-level helper
389
+ * produces a plain TypeScript union because a discriminated union and a
390
+ * plain union over the same members are structurally indistinguishable
391
+ * at the type level. Parity is pinned in
392
+ * `packages/core/tests/typeInference-walker-parity.test.ts`.
393
+ *
394
+ * Filters out `{ type: "null" }` members and instead makes the result
395
+ * nullable when at least one null member is present — mirrors the
396
+ * walker's `normaliseAnyOf`.
397
+ */
398
+ type UnionOfMembers<A, Defs extends Record<string, unknown>, Depth extends readonly unknown[] = [], Mode extends FromJSONSchemaMode = "both"> = A extends readonly unknown[] ? HasNullMember<A> extends true ? Exclude<FromJSONSchema<A[number], Defs, Depth, Mode>, null> | null : FromJSONSchema<A[number], Defs, Depth, Mode> : unknown;
399
+ /**
400
+ * Convert an OpenAPI 3.x `oneOf` + `discriminator` schema into a true
401
+ * tagged union by injecting the discriminator literal value into each
402
+ * member at the property named `PropertyName`.
403
+ *
404
+ * Resolution rules per OpenAPI 3.x §4.7.25 (Discriminator Object):
405
+ *
406
+ * - When the member is a `$ref` and `Mapping` contains an entry whose
407
+ * value equals the ref string, the entry key is used as the
408
+ * discriminator literal — this is the explicit mapping form. When
409
+ * no mapping entry matches, the ref's terminal name (the segment
410
+ * after the last `/`) is used per the implicit-mapping rule.
411
+ * - When the member is an inline schema and already declares a
412
+ * `const` value at `PropertyName`, that const is the discriminator
413
+ * value; the member's parsed type already carries the literal so
414
+ * no injection is required.
415
+ * - When the member is an inline schema without a `const` at
416
+ * `PropertyName`, the discriminator value cannot be inferred at
417
+ * the type level — fall through to the plain union.
418
+ *
419
+ * KNOWN LIMITATION: discriminator mappings whose values are external
420
+ * `$ref`s (e.g. `"#/components/schemas/SomeType"` defined in a
421
+ * different document) cannot be resolved at the type level because
422
+ * `FromJSONSchema` only sees the local `Defs` map. For external
423
+ * mappings the result falls back to the plain union of members.
424
+ */
425
+ type DiscriminatedOneOfToUnion<A, PropertyName extends string, Mapping extends Record<string, string>, Defs extends Record<string, unknown>, Depth extends readonly unknown[], Mode extends FromJSONSchemaMode> = A extends readonly unknown[] ? HasNullMember<A> extends true ? Exclude<DistributeDiscriminator<A[number], PropertyName, Mapping, Defs, Depth, Mode>, null> | null : DistributeDiscriminator<A[number], PropertyName, Mapping, Defs, Depth, Mode> : unknown;
426
+ /**
427
+ * For each member of the discriminated `oneOf`, parse the member via
428
+ * `FromJSONSchema` and intersect with the discriminator literal when
429
+ * one can be derived from the mapping table or the implicit ref name.
430
+ */
431
+ type DistributeDiscriminator<M, PropertyName extends string, Mapping extends Record<string, string>, Defs extends Record<string, unknown>, Depth extends readonly unknown[], Mode extends FromJSONSchemaMode> = M extends {
432
+ $ref: infer R extends string;
433
+ } ? FromJSONSchema<M, Defs, Depth, Mode> & Record<PropertyName, DiscriminatorLiteralFor<R, Mapping>> : FromJSONSchema<M, Defs, Depth, Mode>;
434
+ /**
435
+ * Resolve the literal discriminator value for a `$ref` string. First
436
+ * checks the explicit `Mapping` for any entry whose value equals the
437
+ * ref; falls back to the trailing path segment if no mapping match is
438
+ * found. `[K] extends [never]` short-circuits the empty-mapping case
439
+ * without colliding with the `K = never` distribution rules.
440
+ */
441
+ type DiscriminatorLiteralFor<R extends string, Mapping extends Record<string, string>> = [LookupMappingKey<R, Mapping>] extends [never] ? RefTerminalName<R> : LookupMappingKey<R, Mapping>;
442
+ /**
443
+ * Find the key in `Mapping` whose value equals `R`. Returns the union
444
+ * of matching keys, or `never` when no key matches.
445
+ */
446
+ type LookupMappingKey<R extends string, Mapping extends Record<string, string>> = { [K in keyof Mapping]: Mapping[K] extends R ? K : never }[keyof Mapping];
447
+ /** Last `/`-delimited segment of a ref string, or the ref itself. */
448
+ type RefTerminalName<R extends string> = R extends `${string}/${infer Rest}` ? RefTerminalName<Rest> : R;
449
+ /**
450
+ * Check whether an anyOf/oneOf array contains a `{ type: "null" }` member.
451
+ *
452
+ * SOURCE-OF-TRUTH: mirrors runtime `normaliseAnyOf` in
453
+ * `packages/core/src/core/merge.ts` (lines 190-209). Both implementations
454
+ * only recognise schema-shaped null members (`{ type: "null" }`); a bare
455
+ * `null` literal in the array is treated as non-nullable. Parity is
456
+ * pinned in `packages/core/tests/typeInference-walker-parity.test.ts`.
457
+ */
458
+ type HasNullMember<A> = A extends readonly unknown[] ? null extends A[number] ? false : {
459
+ type: "null";
460
+ } extends A[number] ? true : false : false;
461
+ /**
462
+ * Dispatch on a `type` value -- handles single types, type arrays,
463
+ * and delegates to the appropriate type-specific resolver.
464
+ */
465
+ type TypeToTs<T, S, Defs extends Record<string, unknown>, Depth extends readonly unknown[] = [], Mode extends FromJSONSchemaMode = "both"> = T extends "string" ? string : T extends "number" | "integer" ? number : T extends "boolean" ? boolean : T extends "null" ? null : T extends "array" ? ArraySchemaToTs<S, Defs, Depth, Mode> : T extends "object" ? ObjectSchemaToTs<S, Defs, Depth, Mode> : T extends readonly (infer E)[] ? TypeArrayToTs<E, S, Defs, Depth, Mode> : unknown;
466
+ /**
467
+ * Handle `type` as an array (Draft 04-07): `["string", "null"]`.
468
+ * Filters out "null" and makes the result nullable.
469
+ *
470
+ * Earlier revisions ran the array / object branches through
471
+ * `OmitArrayHelpers` to strip `prefixItems`, `items`, and
472
+ * `additionalProperties` before re-parsing. That was a bug: the strip
473
+ * stopped the type-array form from carrying any element / property
474
+ * information, so `{ type: ["array", "null"], items: { type: "string" } }`
475
+ * collapsed to `unknown[] | null` instead of `string[] | null`. The
476
+ * helper still parses the same schema, only without the unnecessary
477
+ * key removal. The regression is pinned in
478
+ * `tests/type-inference-issue-fixes.test.ts` via the
479
+ * `type: ["array", "null"] preserves items` case.
480
+ */
481
+ type TypeArrayToTs<E, S, Defs extends Record<string, unknown>, Depth extends readonly unknown[] = [], Mode extends FromJSONSchemaMode = "both"> = E extends "null" ? null : E extends "string" ? NullableResult<string, S> : E extends "number" | "integer" ? NullableResult<number, S> : E extends "boolean" ? NullableResult<boolean, S> : E extends "array" ? NullableResult<ArraySchemaToTs<S, Defs, Depth, Mode>, S> : E extends "object" ? NullableResult<ObjectSchemaToTs<S, Defs, Depth, Mode>, S> : unknown;
482
+ /**
483
+ * Make a type nullable if the original schema `type` array includes "null".
484
+ * Detects nullable from the type array directly.
485
+ */
486
+ type NullableResult<Base, S> = S extends {
487
+ type: readonly (infer T)[];
488
+ } ? "null" extends T ? Base | null : Base : Base;
489
+ /**
490
+ * Parse an array schema: prefixItems -> tuple, items -> T[], or unknown[].
491
+ *
492
+ * Draft 04 used tuple-form `items` (an array of schemas) for tuple typing;
493
+ * Draft 2020-12 renamed this to `prefixItems`. The runtime normaliser in
494
+ * `packages/core/src/core/normalise.ts` (lines 526-534) rewrites the legacy
495
+ * form to `prefixItems` before the walker sees it. We mirror that rewrite
496
+ * here so `as const` literals using the legacy form infer the same tuple
497
+ * type at compile time.
498
+ *
499
+ * `contains` / `minContains` / `maxContains` constrain elements at runtime
500
+ * but don't change the compile-time array element type.
501
+ */
502
+ type ArraySchemaToTs<S, Defs extends Record<string, unknown>, Depth extends readonly unknown[] = [], Mode extends FromJSONSchemaMode = "both"> = S extends {
503
+ prefixItems: infer P;
504
+ } ? PrefixItemsToTuple<P, Defs, Depth, Mode> : S extends {
505
+ items: infer I extends readonly unknown[];
506
+ } ? PrefixItemsToTuple<I, Defs, Depth, Mode> : S extends {
507
+ items: infer I;
508
+ } ? FromJSONSchema<I, Defs, Depth, Mode>[] : unknown[];
509
+ /**
510
+ * Convert a prefixItems array to a TypeScript tuple type.
511
+ */
512
+ type PrefixItemsToTuple<P, Defs extends Record<string, unknown>, Depth extends readonly unknown[] = [], Mode extends FromJSONSchemaMode = "both"> = P extends readonly [infer First, ...infer Rest] ? [FromJSONSchema<First, Defs, Depth, Mode>, ...PrefixItemsToTuple<Rest, Defs, Depth, Mode>] : [];
513
+ /**
514
+ * Parse an object schema: properties + required -> specific object,
515
+ * additionalProperties -> Record, or empty object.
516
+ *
517
+ * Handles:
518
+ * - `properties` + `required` -> specific object type with required/optional keys
519
+ * - `additionalProperties` as schema -> Record<string, T>
520
+ * - `properties` + `additionalProperties` -> base object intersected with
521
+ * `Record<string, V>`, preserving the typed value shape of the extra props
522
+ * - `patternProperties` -> merged into a loose index signature alongside specific props
523
+ * (TypeScript cannot express regex-keyed properties)
524
+ * - `propertyNames` -> ignored at type level (TS cannot validate key shapes)
525
+ * - `dependentSchemas` / `dependentRequired` -> ignored (runtime-only conditionals)
526
+ * - `unevaluatedProperties` -> ignored (runtime-only)
527
+ *
528
+ * Properties marked `readOnly: true` are omitted when `Mode` is
529
+ * `"input"`; properties marked `writeOnly: true` are omitted when
530
+ * `Mode` is `"output"`. `Mode = "both"` (the default) ignores both
531
+ * keywords and preserves prior behaviour.
532
+ */
533
+ type ObjectSchemaToTs<S, Defs extends Record<string, unknown>, Depth extends readonly unknown[] = [], Mode extends FromJSONSchemaMode = "both"> = S extends {
534
+ type: "object";
535
+ properties: infer P;
536
+ } ? ExtractDefs<S, Defs> extends infer D extends Record<string, unknown> ? MergePatternProps<MergeAdditionalProperties<{ [K in keyof P as K extends RequiredKeysOf<S> ? IsPropertyHidden<P[K], Mode> extends true ? never : K : never]: FromJSONSchema<P[K], D, Depth, Mode> } & { [K in keyof P as K extends RequiredKeysOf<S> ? never : IsPropertyHidden<P[K], Mode> extends true ? never : K]?: FromJSONSchema<P[K], D, Depth, Mode> }, S, D, Depth, Mode>, S, D, Depth, Mode> : never : S extends {
537
+ additionalProperties: infer V;
538
+ } ? Record<string, FromJSONSchema<V, Defs, Depth, Mode>> : Record<string, unknown>;
539
+ /**
540
+ * Decide whether a property should be omitted from the inferred type
541
+ * for the supplied `Mode`. `readOnly: true` properties are excluded
542
+ * from `"input"` mode (POST bodies, `onChange` arguments) and
543
+ * `writeOnly: true` properties are excluded from `"output"` mode
544
+ * (rendered values, GET responses). `"both"` is the permissive
545
+ * default and never hides anything.
546
+ */
547
+ type IsPropertyHidden<P, Mode extends FromJSONSchemaMode> = Mode extends "input" ? P extends {
548
+ readOnly: true;
549
+ } ? true : false : Mode extends "output" ? P extends {
550
+ writeOnly: true;
551
+ } ? true : false : false;
552
+ /**
553
+ * Intersect the base object type with `Record<string, V>` when the
554
+ * schema declares `additionalProperties` as a schema in addition to
555
+ * `properties`. Without this branch the inferred type silently dropped
556
+ * the value-type information for the extra props, leaving consumers
557
+ * with no way to type un-named keys that the schema explicitly permits.
558
+ *
559
+ * `additionalProperties: false` keeps the base unchanged (no extra
560
+ * keys are allowed); `additionalProperties: true` widens the value
561
+ * type to `unknown`; an inline schema produces a typed index
562
+ * signature.
563
+ */
564
+ type MergeAdditionalProperties<Base, S, Defs extends Record<string, unknown>, Depth extends readonly unknown[], Mode extends FromJSONSchemaMode> = S extends {
565
+ additionalProperties: false;
566
+ } ? Base : S extends {
567
+ additionalProperties: true;
568
+ } ? Base & Record<string, unknown> : S extends {
569
+ additionalProperties: infer V;
570
+ } ? Base & Record<string, FromJSONSchema<V, Defs, Depth, Mode>> : Base;
571
+ /**
572
+ * If the schema has `patternProperties`, intersect the base object type
573
+ * with a `Record<string, T>` index signature covering all pattern values.
574
+ * If no `patternProperties`, return the base type unchanged.
575
+ */
576
+ type MergePatternProps<Base, S, Defs extends Record<string, unknown>, Depth extends readonly unknown[] = [], Mode extends FromJSONSchemaMode = "both"> = S extends {
577
+ patternProperties: infer PP;
578
+ } ? PP extends Record<string, unknown> ? Base & Record<string, UnionOfPatternValues<PP, Defs, Depth, Mode>> : Base : Base;
579
+ /**
580
+ * Extract the union of all pattern property value types.
581
+ */
582
+ type UnionOfPatternValues<PP extends Record<string, unknown>, Defs extends Record<string, unknown>, Depth extends readonly unknown[] = [], Mode extends FromJSONSchemaMode = "both"> = { [K in keyof PP]: FromJSONSchema<PP[K], Defs, Depth, Mode> }[keyof PP];
583
+ /**
584
+ * Extract the `required` array from a schema as a union of string literals.
585
+ *
586
+ * Accepts both the `as const` readonly tuple form and a plain mutable
587
+ * `string[]` literal. The mutable form arises when a hand-written
588
+ * schema literal omits `as const` on the `required` field — without
589
+ * the second branch the conditional silently resolves to `never` and
590
+ * every property is marked optional, which is a silent footgun. See
591
+ * the regression test `RequiredKeysOf accepts widened string[] arrays`
592
+ * in `tests/type-inference-issue-fixes.test.ts`.
593
+ */
594
+ type RequiredKeysOf<S> = S extends {
595
+ required: infer R;
596
+ } ? R extends readonly string[] ? R[number] : R extends string[] ? R[number] : never : never;
597
+ /**
598
+ * Extract $defs / definitions from a schema for $ref resolution context.
599
+ * Also indexes schemas with `$anchor` or `$dynamicAnchor` by their anchor
600
+ * name, enabling `#SomeName` ref resolution.
601
+ *
602
+ * Shares merge semantics with {@link MergeRootDefs}: caller-supplied
603
+ * (`ParentDefs`) entries win on key collision, the empty-default
604
+ * sentinel is detected so it does not poison the parent context, and the
605
+ * `HasLocalDefs` guard short-circuits when the current node declares no
606
+ * defs of its own.
607
+ */
608
+ type ExtractDefs<S, ParentDefs extends Record<string, unknown>> = HasLocalDefs<S> extends true ? ExtractRawDefs<S> extends infer RawDefs extends Record<string, unknown> ? CollisionSafeMerge<CollisionSafeMerge<RawDefs, ExtractAnchors<RawDefs>>, ParentDefs> : ParentDefs : ParentDefs;
609
+ /**
610
+ * Extract raw `$defs` / `definitions` / `components.schemas` maps.
611
+ *
612
+ * Multiple keys may be present in a single schema (e.g. an OpenAPI
613
+ * document that also declares `$defs` for a Zod-converted subtree).
614
+ * `CollisionSafeMerge` resolves overlaps so the first non-empty source
615
+ * wins, mirroring the runtime's preference for `$defs` over the legacy
616
+ * `definitions` / `components.schemas` locations.
617
+ */
618
+ type ExtractRawDefs<S> = CollisionSafeMerge<CollisionSafeMerge<RawDefsOf<S>, RawDefinitionsOf<S>>, RawComponentSchemasOf<S>>;
619
+ type RawDefsOf<S> = S extends {
620
+ $defs: infer D;
621
+ } ? D extends Record<string, unknown> ? D : Record<string, never> : Record<string, never>;
622
+ type RawDefinitionsOf<S> = S extends {
623
+ definitions: infer D;
624
+ } ? D extends Record<string, unknown> ? D : Record<string, never> : Record<string, never>;
625
+ type RawComponentSchemasOf<S> = S extends {
626
+ components: {
627
+ schemas: infer D;
628
+ };
629
+ } ? D extends Record<string, unknown> ? D : Record<string, never> : Record<string, never>;
630
+ /**
631
+ * Build a map of `$anchor` name -> schema from a definitions block.
632
+ * Scans each definition value for `$anchor`, `$dynamicAnchor`, or the
633
+ * Draft 2019-09 `$recursiveAnchor` keyword and creates entries like
634
+ * `{ Tree: <schema-with-$anchor-Tree> }`.
635
+ *
636
+ * SOURCE-OF-TRUTH: mirrors `normaliseDraft201909NodeWithContext` in
637
+ * `packages/core/src/core/normalise.ts` (lines 638-650), which rewrites
638
+ * `$recursiveAnchor: true` to `$anchor: "__recursive__"` and a string
639
+ * `$recursiveAnchor: "name"` to `$anchor: "name"`. The corresponding
640
+ * `$recursiveRef: "#"` therefore resolves through the same `Defs` map
641
+ * as a modern `$ref: "#__recursive__"`.
642
+ */
643
+ type ExtractAnchors<D extends Record<string, unknown>> = { [K in keyof D as D[K] extends {
644
+ $anchor: infer A extends string;
645
+ } ? A : D[K] extends {
646
+ $dynamicAnchor: infer A extends string;
647
+ } ? A : D[K] extends {
648
+ $recursiveAnchor: infer A extends string;
649
+ } ? A : D[K] extends {
650
+ $recursiveAnchor: true;
651
+ } ? "__recursive__" : never]: D[K] };
652
+ /**
653
+ * Convert a union to an intersection.
654
+ * `A | B` -> `A & B`. Used for allOf merging.
655
+ */
656
+ type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
657
+ /**
658
+ * Resolves an OpenAPI `ref` string to its JSON Schema, then parses it.
659
+ *
660
+ * SOURCE-OF-TRUTH: mirrors runtime `resolveRef` in
661
+ * `packages/core/src/core/ref.ts` (line 90), which is invoked by the
662
+ * walker entry point in `packages/core/src/core/walker.ts` (lines
663
+ * 144-154) for OpenAPI documents. Any change to the runtime ref-resolution
664
+ * rules (new ref forms, different cycle handling, JSON Pointer decoding)
665
+ * must be reflected here and pinned in
666
+ * `packages/core/tests/typeInference-walker-parity.test.ts`.
667
+ *
668
+ * Handles:
669
+ * - `#/components/schemas/Name` (OpenAPI 3.x)
670
+ * - `#/definitions/Name` (Swagger 2.0)
671
+ * - `#/paths/...` (path-based refs, navigating the document tree)
672
+ *
673
+ * When the resolved schema itself contains nested `$ref`s, the helper
674
+ * seeds {@link FromJSONSchema}'s `Defs` parameter with the document's
675
+ * `components.schemas` (OAS 3.x) and `definitions` (Swagger 2.0) maps
676
+ * so the nested refs resolve correctly. `Depth` is threaded too so the
677
+ * recursion budget is shared with the calling context. Earlier
678
+ * revisions called `FromJSONSchema<...>` with the empty defaults and
679
+ * silently produced `unknown` for any nested ref.
680
+ */
681
+ type ResolveOpenAPIRef<Spec extends Record<string, unknown>, Ref extends string, Depth extends readonly unknown[] = [], Mode extends FromJSONSchemaMode = "both"> = SpecDefs<Spec> extends infer Defs extends Record<string, unknown> ? Ref extends `#/components/schemas/${infer Name}` ? Spec["components"] extends Record<string, unknown> ? Spec["components"]["schemas"] extends Record<string, unknown> ? Name extends keyof Spec["components"]["schemas"] ? FromJSONSchema<Spec["components"]["schemas"][Name], Defs, Depth, Mode> : unknown : unknown : unknown : Ref extends `#/definitions/${infer Name}` ? Spec["definitions"] extends Record<string, unknown> ? Name extends keyof Spec["definitions"] ? FromJSONSchema<Spec["definitions"][Name], Defs, Depth, Mode> : unknown : unknown : Ref extends `#/paths/${infer PathRest}` ? ResolvePathBasedRef<Spec, PathRest> : unknown : unknown;
682
+ /**
683
+ * Build the `Defs` map for an OpenAPI document by combining
684
+ * `components.schemas` (OAS 3.x) and `definitions` (Swagger 2.0).
685
+ *
686
+ * Both keys are accepted so a single helper handles both versions of
687
+ * the spec. {@link CollisionSafeMerge} ensures the OAS-3.x entries
688
+ * take precedence when both are present, matching the runtime
689
+ * preference for components/schemas over the legacy definitions field.
690
+ */
691
+ type SpecDefs<Spec> = ExtractComponentsSchemas<Spec> extends infer C extends Record<string, unknown> ? ExtractDefinitions<Spec> extends infer D extends Record<string, unknown> ? CollisionSafeMerge<D, C> : C : Record<string, never>;
692
+ /** Extract `components.schemas` from a spec, or the empty sentinel. */
693
+ type ExtractComponentsSchemas<Spec> = Spec extends {
694
+ components: {
695
+ schemas: infer S;
696
+ };
697
+ } ? S extends Record<string, unknown> ? S : Record<string, never> : Record<string, never>;
698
+ /** Extract `definitions` from a spec, or the empty sentinel. */
699
+ type ExtractDefinitions<Spec> = Spec extends {
700
+ definitions: infer D;
701
+ } ? D extends Record<string, unknown> ? D : Record<string, never> : Record<string, never>;
702
+ /**
703
+ * Resolve a path-based $ref after the `#/paths/` prefix.
704
+ * Splits on `/` and navigates the document tree, decoding JSON Pointer
705
+ * tilde escapes (`~1` -> `/`, `~0` -> `~`) on every segment.
706
+ *
707
+ * SOURCE-OF-TRUTH: mirrors runtime `dereference` in
708
+ * `packages/core/src/core/ref.ts` (line 226), which applies the same
709
+ * `~1` -> `/`, `~0` -> `~` substitutions per RFC 6901 §4. The runtime
710
+ * uses ordered string replacement; the type-level mirror does the same
711
+ * via {@link DecodeJsonPointerSegment}.
712
+ */
713
+ type ResolvePathBasedRef<Spec extends Record<string, unknown>, PathRest extends string> = Spec["paths"] extends Record<string, unknown> ? ResolvePathSegments<Spec["paths"], SplitPath<PathRest>> : unknown;
714
+ /**
715
+ * Replace every occurrence of `From` with `To` inside `S`.
716
+ *
717
+ * Pure type-level alternative to `String.prototype.replaceAll` used for
718
+ * JSON Pointer escape decoding. Terminates when no further match is
719
+ * found in the tail.
720
+ */
721
+ type ReplaceAll<S extends string, From extends string, To extends string> = S extends `${infer Head}${From}${infer Tail}` ? `${Head}${To}${ReplaceAll<Tail, From, To>}` : S;
722
+ /**
723
+ * Decode a single JSON Pointer reference token per RFC 6901 §4:
724
+ * apply `~1` -> `/` first, then `~0` -> `~`. The order matters — an
725
+ * encoded `~` containing a literal `1` (e.g. `~01`) must remain `~1`
726
+ * after decoding, which only works when `~1` is processed first.
727
+ */
728
+ type DecodeJsonPointerSegment<S extends string> = ReplaceAll<ReplaceAll<S, "~1", "/">, "~0", "~">;
729
+ /**
730
+ * Split a path string on `/` into a tuple of segments.
731
+ * The first segment is the path key (may be empty for `/pets` -> `""` / `"pets"`).
732
+ */
733
+ type SplitPath<S extends string> = S extends `${infer Head}/${infer Tail}` ? [Head, ...SplitPath<Tail>] : [S];
734
+ /**
735
+ * Recursively navigate into a document object by path segments. Each
736
+ * segment is JSON-Pointer-decoded before indexing so encoded forms such
737
+ * as `~1pets` correctly resolve to the `"/pets"` key.
738
+ */
739
+ type ResolvePathSegments<Doc, Segs extends string[]> = Segs extends [infer Head extends string, ...infer Rest extends string[]] ? Doc extends Record<string, unknown> ? DecodeJsonPointerSegment<Head> extends infer Decoded extends string ? Rest extends [] ? Doc[Decoded] : ResolvePathSegments<Doc[Decoded], Rest> : unknown : unknown : unknown;
740
+ /** Navigate to a path item in an OpenAPI document. */
741
+ type PathItemOf<Doc, Path extends string> = Doc extends {
742
+ paths: Record<string, unknown>;
743
+ } ? Path extends keyof Doc["paths"] ? Doc["paths"][Path] : unknown : unknown;
744
+ /** Navigate to an operation within a path item. */
745
+ type OperationOf<PathItem, Method extends string> = PathItem extends Record<string, unknown> ? Method extends keyof PathItem ? PathItem[Method] : unknown : unknown;
746
+ /**
747
+ * Default content type preferred when callers do not specify one.
748
+ *
749
+ * `"application/json"` matches the most common OpenAPI convention and
750
+ * keeps prior `FromJSONSchema` behaviour for the common case. When the
751
+ * default content type is absent from an operation, the helpers below
752
+ * fall back to the first declared media type — matching the runtime
753
+ * resolver's first-match semantics.
754
+ */
755
+ type DEFAULT_OPENAPI_CONTENT_TYPE = "application/json";
756
+ /**
757
+ * Pick the content type to use for a request/response when the caller
758
+ * supplies one explicitly: when `ContentType` is present in `Content`
759
+ * use it verbatim, otherwise fall through to the default content type
760
+ * if present, otherwise pick the first key.
761
+ */
762
+ type PickContentType<Content, ContentType extends string> = Content extends Record<string, unknown> ? ContentType extends keyof Content ? ContentType : DEFAULT_OPENAPI_CONTENT_TYPE extends keyof Content ? DEFAULT_OPENAPI_CONTENT_TYPE : FirstKey<Content> : never;
763
+ /** First string key of an object type, or `never` for an empty map. */
764
+ type FirstKey<O> = keyof O extends infer K ? K extends string ? K : never : never;
765
+ /**
766
+ * Extract the schema for a specific content type from a request body
767
+ * Content map.
768
+ *
769
+ * The caller's `ContentType` selects the media type: it falls back to
770
+ * `DEFAULT_OPENAPI_CONTENT_TYPE` and then to the first declared media
771
+ * type when the requested one is absent.
772
+ */
773
+ type RequestBodySchemaOf<Op, ContentType extends string = DEFAULT_OPENAPI_CONTENT_TYPE> = Op extends {
774
+ requestBody: {
775
+ content: infer C;
776
+ };
777
+ } ? PickContentType<C, ContentType> extends infer K extends string ? C extends Record<string, unknown> ? K extends keyof C ? C[K] extends {
778
+ schema: infer S;
779
+ } ? S : unknown : unknown : unknown : unknown : unknown;
780
+ /**
781
+ * Extract the schema for a specific status code and content type from
782
+ * a response map.
783
+ *
784
+ * Status-code resolution mirrors the OpenAPI 3.x §4.7.10 priority order:
785
+ *
786
+ * 1. The literal status (e.g. `"200"`) — exact match.
787
+ * 2. The class wildcard (e.g. `"2XX"`) derived from the leading digit.
788
+ * 3. The `"default"` key.
789
+ *
790
+ * Without this fallback, querying a concrete status against a document
791
+ * that declares only `"2XX"` or `"default"` would silently produce
792
+ * `unknown`. The runtime resolver applies the same fall-through
793
+ * behaviour in `resolveResponseFromParsed`.
794
+ */
795
+ type ResponseSchemaOf<Op, Status extends string, ContentType extends string = DEFAULT_OPENAPI_CONTENT_TYPE> = Op extends {
796
+ responses: infer Rs extends Record<string, unknown>;
797
+ } ? PickResponse<Rs, Status> extends infer Resp ? Resp extends {
798
+ content: infer C;
799
+ } ? PickContentType<C, ContentType> extends infer K extends string ? C extends Record<string, unknown> ? K extends keyof C ? C[K] extends {
800
+ schema: infer S;
801
+ } ? S : unknown : unknown : unknown : unknown : unknown : unknown : unknown;
802
+ /**
803
+ * Resolve a response entry from a status code following the OpenAPI
804
+ * priority order: concrete > class wildcard > `default`. When none of
805
+ * the three matches, the result is `never` and the caller's outer
806
+ * conditional falls through to `unknown`.
807
+ */
808
+ type PickResponse<Rs, Status extends string> = Status extends keyof Rs ? Rs[Status] : StatusClassWildcard<Status> extends infer Wildcard extends string ? Wildcard extends keyof Rs ? Rs[Wildcard] : "default" extends keyof Rs ? Rs["default"] : never : "default" extends keyof Rs ? Rs["default"] : never;
809
+ /**
810
+ * Derive the class wildcard (`"2XX"`, `"4XX"`, etc.) for a numeric
811
+ * status code, or `never` for non-numeric inputs. Only the first digit
812
+ * of the status code participates so `"200"`, `"201"`, `"204"` all map
813
+ * to `"2XX"`.
814
+ */
815
+ type StatusClassWildcard<Status extends string> = Status extends `${infer D extends "1" | "2" | "3" | "4" | "5"}${string}` ? `${D}XX` : never;
816
+ /**
817
+ * Resolve a schema that may be a `$ref` pointer.
818
+ *
819
+ * The `nullable: true` handling lives inside `FromJSONSchema` so it
820
+ * applies uniformly to direct schemas, refs, and nested fields. This
821
+ * helper only dispatches between ref-resolution and plain inference.
822
+ *
823
+ * Threads `Defs`/`Depth`/`Mode` into both `ResolveOpenAPIRef` and
824
+ * `FromJSONSchema` so a nested `$ref` inside an inline schema is
825
+ * resolved against the same component-schemas context the parent
826
+ * document supplies. Without the propagation, nested refs degraded
827
+ * silently to `unknown`.
828
+ */
829
+ type ResolveMaybeRef<Doc, S, Depth extends readonly unknown[] = [], Mode extends FromJSONSchemaMode = "both"> = S extends {
830
+ $ref: infer R extends string;
831
+ } ? ResolveOpenAPIRef<Doc & Record<string, unknown>, R, Depth, Mode> : S extends Record<string, unknown> ? FromJSONSchema<S, SpecDefs<Doc>, Depth, Mode> : unknown;
832
+ /** Extract parameter names from an operation. */
833
+ type ParameterNamesOf<Doc, Path extends string, Method extends string> = OperationOf<PathItemOf<Doc, Path>, Method> extends {
834
+ parameters: readonly unknown[];
835
+ } ? OperationOf<PathItemOf<Doc, Path>, Method>["parameters"][number] extends {
836
+ name: infer N;
837
+ } ? N extends string ? N : never : never : never;
838
+ /**
839
+ * Detect whether a document is Swagger 2.0 (OpenAPI 2.0).
840
+ *
841
+ * SOURCE-OF-TRUTH: mirrors runtime `isSwagger2` in
842
+ * `packages/core/src/core/version.ts` (line 305), which parses the
843
+ * `swagger` field via `detectOpenApiVersion` (line 264) and returns true
844
+ * for any document whose major version is `2`. Runtime therefore accepts
845
+ * `"2.0"`, `"2.0.0"`, `"2.1"`, any other `2.x` form — and the numeric
846
+ * literals `2` and `2.0`. The type-level detector must mirror every
847
+ * shape the runtime accepts, otherwise a numeric-versioned document
848
+ * silently bypasses the fallback and produces `unknown` instead of the
849
+ * `__SchemaInferenceFellBack` brand consumers expect.
850
+ *
851
+ * Type-level Swagger 2.0 documents cannot be fully normalised at compile
852
+ * time — the rewrite reorders the document tree (definitions →
853
+ * components/schemas, body parameters → requestBody, etc.) in ways
854
+ * TypeScript's mapped-type machinery cannot express. Detecting the
855
+ * version is tractable, so we surface `__SchemaInferenceFellBack`
856
+ * deliberately rather than silently producing `unknown`.
857
+ *
858
+ * Accepted shapes:
859
+ * - `{ swagger: "2.<anything>" }` — the on-the-wire string form
860
+ * - `{ swagger: 2 }` / `{ swagger: 2.0 }` — numeric on-the-wire form
861
+ * (some YAML serialisers emit a number rather than a string)
862
+ * - `{ swagger: { major: 2, ... } }` — the parsed `OpenApiVersionInfo`
863
+ * object form, mirroring the runtime's tolerance for pre-parsed
864
+ * version metadata
865
+ */
866
+ type IsSwagger2Doc<Doc> = Doc extends {
867
+ swagger: `2.${string}`;
868
+ } ? true : Doc extends {
869
+ swagger: 2;
870
+ } ? true : Doc extends {
871
+ swagger: 2.0;
872
+ } ? true : Doc extends {
873
+ swagger: {
874
+ major: 2;
875
+ };
876
+ } ? true : false;
877
+ /**
878
+ * Infer the TypeScript type of an OpenAPI operation's request body.
879
+ *
880
+ * `ContentType` selects which media type's schema to infer; defaults
881
+ * to {@link DEFAULT_OPENAPI_CONTENT_TYPE} and falls back to the first
882
+ * declared content type when the default is absent (see
883
+ * {@link PickContentType}).
884
+ *
885
+ * Swagger 2.0 documents are not normalised at the type level. When the
886
+ * input is Swagger 2.0, this returns `__SchemaInferenceFellBack` so
887
+ * callers can detect the fallback explicitly via a conditional type.
888
+ */
889
+ type OpenAPIRequestBodyType<Doc, Path extends string, Method extends string, ContentType extends string = DEFAULT_OPENAPI_CONTENT_TYPE> = IsSwagger2Doc<Doc> extends true ? __SchemaInferenceFellBack : ResolveMaybeRef<Doc, RequestBodySchemaOf<OperationOf<PathItemOf<Doc, Path>, Method>, ContentType>>;
890
+ /**
891
+ * Infer the TypeScript type of an OpenAPI operation's response.
892
+ *
893
+ * `ContentType` selects which media type's schema to infer; defaults
894
+ * to {@link DEFAULT_OPENAPI_CONTENT_TYPE} and falls back to the first
895
+ * declared content type when the default is absent.
896
+ *
897
+ * Status-code resolution follows the OpenAPI priority order: concrete
898
+ * code > class wildcard (e.g. `"2XX"`) > `"default"`. See
899
+ * {@link ResponseSchemaOf}.
900
+ *
901
+ * Swagger 2.0 documents are not normalised at the type level. When the
902
+ * input is Swagger 2.0, this returns `__SchemaInferenceFellBack` so
903
+ * callers can detect the fallback explicitly via a conditional type.
904
+ */
905
+ type OpenAPIResponseType<Doc, Path extends string, Method extends string, Status extends string, ContentType extends string = DEFAULT_OPENAPI_CONTENT_TYPE> = IsSwagger2Doc<Doc> extends true ? __SchemaInferenceFellBack : ResolveMaybeRef<Doc, ResponseSchemaOf<OperationOf<PathItemOf<Doc, Path>, Method>, Status, ContentType>>;
906
+ /**
907
+ * Convert a resolved request/response type into the corresponding
908
+ * `fields` prop type used by ApiRequestBody / ApiResponse:
909
+ *
910
+ * - `__SchemaInferenceFellBack` (Swagger 2.0, depth-exceeded refs) is
911
+ * preserved verbatim so callers can detect the brand.
912
+ * - `unknown` (no schema found at the supplied path/status, or the
913
+ * resolved operation itself widened to `unknown`) falls back to the
914
+ * loose `Record<string, FieldOverride>` shape so runtime documents
915
+ * still typecheck.
916
+ * - Any other concrete type is mapped through `FieldOverrides`.
917
+ *
918
+ * The brand check intentionally precedes the `unknown` check. The brand
919
+ * is a structural object type and is therefore NOT assignable to
920
+ * `unknown extends T` — checking that first would always short-circuit
921
+ * to the loose `Record` fallback and the brand would never surface.
922
+ *
923
+ * TRADE-OFF: when the operation resolves to `unknown` (e.g. the path or
924
+ * method does not exist on a typed `Doc`), `FieldsFromInferred` widens
925
+ * silently to `Record<string, FieldOverride>` so any key is accepted.
926
+ * The alternative — surfacing a distinct compile-time error — would
927
+ * trade autocomplete on typed paths for noisy diagnostics on runtime
928
+ * documents, and the existing `@ts-expect-error` regressions in
929
+ * `type-inference.test.ts` rely on the current widening behaviour.
930
+ * The trade-off is pinned by the
931
+ * `FieldsFromInferred widens to Record<string, FieldOverride> when the
932
+ * operation is unknown` regression test.
933
+ */
934
+ type FieldsFromInferred<T> = [T] extends [__SchemaInferenceFellBack] ? __SchemaInferenceFellBack : unknown extends T ? Record<string, FieldOverride> : FieldOverrides<T>;
935
+ /**
936
+ * Infer the fields prop type for ApiRequestBody.
937
+ *
938
+ * Surfaces `__SchemaInferenceFellBack` for Swagger 2.0 documents and
939
+ * for schemas whose $ref chains exceed type-level depth limits. Falls
940
+ * back to `Record<string, FieldOverride>` for runtime documents whose
941
+ * shape cannot be inferred at compile time.
942
+ *
943
+ * `ContentType` mirrors the parameter on
944
+ * {@link OpenAPIRequestBodyType}.
945
+ */
946
+ type InferRequestBodyFields<Doc, Path extends string, Method extends string, ContentType extends string = DEFAULT_OPENAPI_CONTENT_TYPE> = FieldsFromInferred<OpenAPIRequestBodyType<Doc, Path, Method, ContentType>>;
947
+ /**
948
+ * Infer the fields prop type for ApiResponse.
949
+ *
950
+ * Surfaces `__SchemaInferenceFellBack` for Swagger 2.0 documents and
951
+ * for schemas whose $ref chains exceed type-level depth limits. Falls
952
+ * back to `Record<string, FieldOverride>` for runtime documents whose
953
+ * shape cannot be inferred at compile time.
954
+ *
955
+ * `ContentType` mirrors the parameter on {@link OpenAPIResponseType}.
956
+ */
957
+ type InferResponseFields<Doc, Path extends string, Method extends string, Status extends string, ContentType extends string = DEFAULT_OPENAPI_CONTENT_TYPE> = FieldsFromInferred<OpenAPIResponseType<Doc, Path, Method, Status, ContentType>>;
958
+ /**
959
+ * Infer the overrides prop type for ApiParameters.
960
+ * Falls back to `Record<string, FieldOverride>` for runtime documents.
961
+ */
962
+ type InferParameterOverrides<Doc, Path extends string, Method extends string> = string extends ParameterNamesOf<Doc, Path, Method> ? Record<string, FieldOverride> : Partial<Record<ParameterNamesOf<Doc, Path, Method>, FieldOverride>>;
963
+ /**
964
+ * Check if T is a "narrow" type (not wide like object, Record, or unknown).
965
+ * Used to determine if we can enumerate keys for path inference.
966
+ */
967
+ type IsNarrowObject<T> = T extends string | number | boolean | null | undefined | unknown[] ? false : T extends object ? Record<string, never> extends T ? false : true : false;
968
+ /**
969
+ * Extract all valid dot-separated paths from an object type.
970
+ * Produces paths like "name" | "address.city" | "address.postcode".
971
+ * Stops at leaf types (string, number, boolean, null) and arrays.
972
+ * Returns `string` for wide types (object, Record, unknown).
973
+ * Handles optional/nullable fields by unwrapping T | undefined.
974
+ */
975
+ type PathOfType<T, Prefix extends string = ""> = IsNarrowObject<T> extends true ? { [K in keyof T & string]: T[K] extends string | number | boolean | null | undefined ? `${Prefix}${K}` : T[K] extends unknown[] ? `${Prefix}${K}` : T[K] extends object | undefined ? PathOfType<Exclude<T[K], undefined>, `${Prefix}${K}.`> | `${Prefix}${K}` : `${Prefix}${K}` }[keyof T & string] : string;
976
+ /**
977
+ * Extract the type at a given dot-separated path.
978
+ * PathOfType<T> produces valid paths; TypeAtPath resolves the leaf type.
979
+ */
980
+ type TypeAtPath<T, P extends string> = P extends `${infer Key}.${infer Rest}` ? Key extends keyof T ? TypeAtPath<T[Key], Rest> : unknown : P extends keyof T ? T[P] : unknown;
981
+ //#endregion
982
+ export { __SchemaInferenceFellBack as _, InferParameterOverrides as a, OpenAPIRequestBodyType as c, RejectUnrepresentableZod as d, ResolveOpenAPIRef as f, UnsafeFields as g, UnrepresentableZodType as h, FromJSONSchemaMode as i, OpenAPIResponseType as l, UnrepresentableZodSchemaError as m, DEFAULT_OPENAPI_CONTENT_TYPE as n, InferRequestBodyFields as o, TypeAtPath as p, FromJSONSchema as r, InferResponseFields as s, DEFAULT_MAX_DEPTH as t, PathOfType as u };