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
@@ -1,647 +0,0 @@
1
- import { d as FieldOverrides, u as FieldOverride } from "./types-BnxPEElk.mjs";
2
- import { z } from "zod";
3
-
4
- //#region src/core/typeInference.d.ts
5
- /**
6
- * Zod 4 types that `z.toJSONSchema()` rejects at runtime because they
7
- * have no JSON Schema representation. The runtime adapter
8
- * (`packages/core/src/core/adapter.ts` lines 106-116) catches the
9
- * thrown error and surfaces it as a `SchemaNormalisationError` with
10
- * kind `zod-type-unrepresentable` — but the failure only happens on
11
- * first render. Statically rejecting these types at the props boundary
12
- * gives the same diagnostic at compile time.
13
- *
14
- * SOURCE-OF-TRUTH: list mirrors `UNREPRESENTABLE_ZOD_TYPES` in
15
- * `adapter.ts`. Add or remove entries here whenever the runtime list
16
- * changes.
17
- */
18
- type UnrepresentableZodType = z.ZodBigInt | z.ZodDate | z.ZodMap | z.ZodSet | z.ZodSymbol | z.ZodFunction | z.ZodUndefined | z.ZodVoid | z.ZodNaN | z.ZodCodec;
19
- /**
20
- * Brand returned in place of a rejected Zod input. The descriptive
21
- * literal is what TypeScript displays when the rejection fires, so
22
- * developers see why their schema is incompatible.
23
- */
24
- interface UnrepresentableZodSchemaError {
25
- readonly __schemaComponentsError: "Zod 4 type has no JSON Schema representation. See SchemaNormalisationError code 'zod-type-unrepresentable'.";
26
- }
27
- /**
28
- * Recursively unwrap Zod 4 wrappers that hold an inner schema —
29
- * `ZodOptional`, `ZodNullable`, `ZodReadonly`, `ZodLazy`, and `ZodPipe`.
30
- *
31
- * The runtime conversion still throws for `z.optional(z.bigint())`,
32
- * `z.lazy(() => z.bigint())`, `z.nullable(z.bigint())`,
33
- * `z.bigint().readonly()`, and `z.bigint().pipe(z.bigint())` because
34
- * `z.toJSONSchema()` walks into the wrapped schema before reporting the
35
- * unrepresentable type. The compile-time rejection must therefore peel
36
- * those wrappers off before checking against the rejection list, or the
37
- * brand would never surface for wrapped inputs.
38
- *
39
- * `ZodCodec` is short-circuited at the top because it is itself listed
40
- * in {@link UnrepresentableZodType}. It extends `ZodPipe` structurally,
41
- * so without the early exit the `ZodPipe` branch would unwrap a bare
42
- * codec into its two sides and the rejection would no longer fire for
43
- * `z.codec(...)`.
44
- *
45
- * `ZodPipe` (non-codec) has two slots (`in` and `out`); both are
46
- * unwrapped into a union so a single unrepresentable side surfaces via
47
- * {@link AnyMemberIsUnrepresentable}.
48
- */
49
- 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;
50
- /**
51
- * True when any member of the (possibly unioned) input extends one of
52
- * the unrepresentable Zod types. Distribution over the union ensures a
53
- * single unrepresentable member triggers true — matching runtime
54
- * semantics for `ZodPipe`, whose two sides expand to a union via
55
- * {@link UnwrapZodWrapper}.
56
- */
57
- type AnyMemberIsUnrepresentable<T> = (T extends UnrepresentableZodType ? true : false) extends false ? false : true;
58
- /**
59
- * Reject Zod 4 inputs whose runtime conversion is known to throw.
60
- *
61
- * - When `T` (or any inner schema wrapped by `ZodOptional`,
62
- * `ZodNullable`, `ZodReadonly`, `ZodLazy`, or `ZodPipe`) is one of
63
- * the {@link UnrepresentableZodType} variants, the resolved type is
64
- * {@link UnrepresentableZodSchemaError}, which is not assignable from
65
- * any legitimate Zod / JSON Schema / OpenAPI input — so the prop
66
- * fails to typecheck.
67
- * - Anything else (Zod 4 schemas that DO convert, JSON Schema literals,
68
- * OpenAPI documents, `unknown` for runtime inputs) passes through
69
- * unchanged.
70
- */
71
- type RejectUnrepresentableZod<T> = AnyMemberIsUnrepresentable<UnwrapZodWrapper<T>> extends true ? UnrepresentableZodSchemaError : T;
72
- /**
73
- * Convert a readonly tuple/array of values to a union type.
74
- * Handles both `as const` readonly tuples and mutable arrays.
75
- */
76
- type ArrayToUnion<A> = A extends readonly unknown[] ? A[number] : never;
77
- /**
78
- * Maps a JSON Schema structure to a TypeScript type.
79
- * Works with `as const` literals -- provides full autocomplete for `fields`.
80
- *
81
- * Supports all JSON Schema draft versions (04-2020-12) and OpenAPI 3.x:
82
- * - Primitive types: string, number, integer, boolean, null
83
- * - type as array: `["string", "null"]` -> `string | null` (nullable)
84
- * - enum -> union of literal types
85
- * - const -> literal type
86
- * - object with properties/required -> specific object type
87
- * - object with additionalProperties -> Record<string, T>
88
- * - array with items -> T[]
89
- * - array with prefixItems -> tuple type
90
- * - allOf -> intersection type
91
- * - anyOf -> union type
92
- * - oneOf -> union type
93
- * - $ref -> resolved via $defs/definitions/$anchor context
94
- * - $dynamicRef -> resolved via $dynamicAnchor in definitions
95
- * - $recursiveRef -> unknown (recursive types not expressible in TS)
96
- * - if/then/else -> base schema (conditionals not expressible in TS)
97
- * - not -> unknown (negation not expressible in TS)
98
- * - patternProperties -> merged into loose index signature
99
- */
100
- type FromJSONSchema<S, Defs extends Record<string, unknown> = Record<string, never>, Depth extends readonly unknown[] = []> = MergeRootDefs<S, Defs> extends infer MergedDefs extends Record<string, unknown> ? S extends {
101
- nullable: true;
102
- } ?
103
- /**
104
- * OpenAPI 3.0 `nullable: true` — surface the keyword wherever it
105
- * appears (not just inside `ResolveMaybeRef`). The runtime path
106
- * rewrites this to `anyOf: [T, { type: "null" }]` via
107
- * `normaliseOpenApi30Node` (`openapi30.ts`). Mirroring at the
108
- * `FromJSONSchema` level means nested fields inside refs preserve
109
- * nullability when resolved.
110
- */
111
- FromJSONSchema<Omit<S, "nullable">, MergedDefs, Depth> | null : S extends {
112
- $ref: infer R extends string;
113
- } ? ResolveSchemaRef<R, MergedDefs, Depth> : S extends {
114
- $recursiveRef: string;
115
- } ? unknown : S extends {
116
- $dynamicRef: infer R extends string;
117
- } ? ResolveSchemaRef<R, MergedDefs, Depth> : S extends {
118
- allOf: infer A;
119
- } ? AllOfToType<A, MergedDefs, Depth> : S extends {
120
- anyOf: infer A;
121
- } ? UnionOfMembers<A, MergedDefs, Depth> : S extends {
122
- oneOf: infer A;
123
- } ? UnionOfMembers<A, MergedDefs, Depth> : S extends {
124
- if: unknown;
125
- } ? FromJSONSchema<Omit<S, "if" | "then" | "else">, MergedDefs, Depth> : S extends {
126
- not: unknown;
127
- } ? unknown : S extends {
128
- const: infer V;
129
- } ? V : S extends {
130
- enum: infer E;
131
- } ? ArrayToUnion<E> : S extends {
132
- type: infer T;
133
- } ? TypeToTs<T, S, MergedDefs, Depth> : S extends readonly (infer E)[] ? E : unknown : unknown;
134
- /**
135
- * Merge `$defs` / `definitions` declared at the current schema position with
136
- * the caller-supplied `Defs` map BEFORE the ref/allOf/anyOf/oneOf dispatch.
137
- *
138
- * SOURCE-OF-TRUTH: parity with the runtime walker, which uses `rootDocument`
139
- * (see `packages/core/src/core/ref.ts` line 91) to resolve any `$ref` against
140
- * the full document — including sibling definitions colocated with the
141
- * reference. Without this merge, a legal schema like
142
- * `{ $ref: "#/definitions/Foo", definitions: { Foo: {...} } }` would lose
143
- * its sibling defs because the ref branch fires before `ExtractDefs` runs
144
- * inside `ObjectSchemaToTs`.
145
- *
146
- * Merge semantics (per-key resolution via {@link CollisionSafeMerge}):
147
- * - Parent-only keys: parent value wins
148
- * - Local-only keys: local value wins
149
- * - Shared keys: parent value wins (caller / inherited context takes
150
- * precedence over a deeper redeclaration)
151
- *
152
- * When the current schema declares no local defs (`HasLocalDefs<S>` is
153
- * `false`), `ParentDefs` is returned unchanged so the inherited context
154
- * is never poisoned by the empty index-signature sentinel.
155
- */
156
- 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;
157
- /**
158
- * Merge two record-shaped types where keys present in `B` always take
159
- * precedence over the same key in `A`.
160
- *
161
- * The empty default `Record<string, never>` is detected explicitly via
162
- * {@link IsEmptyDefs}: when either side is the sentinel, the other side
163
- * is returned unchanged. This avoids two pitfalls of the naive
164
- * `Omit<A, keyof B> & B` approach:
165
- *
166
- * 1. `keyof Record<string, never>` is the entire `string` type, so
167
- * `Omit<A, string>` would strip every key from `A`.
168
- * 2. Iterating a mapped type over `keyof A | keyof B` where either side
169
- * contributes `string` collapses every entry to the index-signature
170
- * value (`never` in the sentinel), wiping the literal keys from the
171
- * other side.
172
- *
173
- * Only when both sides hold concrete literal keys does the per-key
174
- * mapped merge run.
175
- */
176
- 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 };
177
- /**
178
- * True for the empty-default `Record<string, never>` sentinel used as the
179
- * initial `Defs` map — i.e. an open index signature `[string]: never`
180
- * with no literal keys. Distinguished from a record with at least one
181
- * literal key by checking that `string extends keyof T`.
182
- */
183
- type IsEmptyDefs<T> = [keyof T] extends [never] ? true : string extends keyof T ? true : false;
184
- /**
185
- * True when the schema declares `$defs` or `definitions` as an object,
186
- * false otherwise. Used by {@link MergeRootDefs} and {@link ExtractDefs}
187
- * to avoid intersecting the parent context with an empty
188
- * index-signature sentinel.
189
- */
190
- type HasLocalDefs<S> = S extends {
191
- $defs: Record<string, unknown>;
192
- } ? true : S extends {
193
- definitions: Record<string, unknown>;
194
- } ? true : false;
195
- /**
196
- * Marker type emitted when OpenAPI $ref resolution hits the type-level
197
- * recursion depth limit. Instead of silently falling back to
198
- * `Record<string, FieldOverride>`, produces this branded type so
199
- * consumers can detect it via conditional types.
200
- *
201
- * Usage:
202
- * ```ts
203
- * type Fields = InferRequestBodyFields<Doc, "/users", "post">;
204
- * type IsFallback = Fields extends __SchemaInferenceFellBack ? true : false;
205
- * ```
206
- */
207
- interface __SchemaInferenceFellBack {
208
- readonly __schemaInferenceFallback: unique symbol;
209
- }
210
- /**
211
- * Escape hatch for recursive schemas where type-level inference
212
- * cannot proceed. Typed as `Record<string, FieldOverride>` but
213
- * explicitly branded so callers know they are using the unsafe path.
214
- *
215
- * JSDoc trade-off note: This bypasses field-level type safety.
216
- * Prefer restructuring the schema to avoid deep $ref chains
217
- * when possible.
218
- */
219
- type UnsafeFields = Record<string, FieldOverride> & {
220
- /** Marks this as the unsafe fallback for recursive schemas. */readonly __unsafe?: true;
221
- };
222
- /**
223
- * Convert a `FromJSONSchema` result to `unknown` when recursion is detected.
224
- * Returns the original type when the schema is non-recursive.
225
- */
226
- type DetectRecursiveFallback<T> = unknown extends T ? __SchemaInferenceFellBack : T;
227
- /**
228
- * Type-level recursion bound for $ref resolution.
229
- *
230
- * The TypeScript type system imposes its own recursion limit; without an
231
- * explicit bound a cyclic schema graph would exhaust it and degrade to
232
- * `any`/`unknown` silently. This number is the runtime walker's parallel
233
- * — see `resolveRef` in `packages/core/src/core/ref.ts` (line 119), whose
234
- * default `maxDepth` is `64`. Matching the runtime bound here means a
235
- * schema that the runtime resolves successfully is never silently dropped
236
- * to `__SchemaInferenceFellBack` at compile time purely because the
237
- * type-level limit was lower.
238
- *
239
- * A fixed bound is used here rather than a derived one because the type
240
- * system has no way to count distinct strings across a recursive `Defs`
241
- * map without itself recursing — which is the problem the bound exists
242
- * to solve. Real-world OpenAPI documents (Stripe, GitHub, AWS) routinely
243
- * contain 30-100+ distinct `$ref` strings, so a low ceiling would mask
244
- * legitimate references. Deeper graphs surface as
245
- * `__SchemaInferenceFellBack` so consumers can detect the limit
246
- * explicitly.
247
- */
248
- type DEFAULT_MAX_DEPTH = 64;
249
- /**
250
- * Resolve a $ref against the local definitions context.
251
- *
252
- * SOURCE-OF-TRUTH: mirrors runtime `resolveRef` in
253
- * `packages/core/src/core/ref.ts` (line 90). Any change to the runtime
254
- * ref-resolution rules (new ref forms, different cycle handling) must be
255
- * reflected here and pinned in
256
- * `packages/core/tests/typeInference-walker-parity.test.ts`.
257
- *
258
- * Supports:
259
- * - `#` (root)
260
- * - `#/$defs/Name` and `#/definitions/Name` (named definitions)
261
- * - `#/components/schemas/Name` (OpenAPI 3.x component schemas)
262
- * - `#SomeName` ($anchor, $dynamicAnchor resolved from definitions)
263
- *
264
- * `#/components/schemas/` is resolved here for parity with the runtime's
265
- * `dereference` (`ref.ts` line 217), which walks any `#/...` JSON Pointer
266
- * uniformly. When the runtime walker encounters an inline `$ref` inside
267
- * a Zod-converted or hand-written JSON Schema that points into the
268
- * OpenAPI component tree, this branch produces the corresponding type.
269
- */
270
- type ResolveSchemaRef<R extends string, Defs extends Record<string, unknown>, Depth extends readonly unknown[] = []> = 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]>> : unknown : R extends `#/definitions/${infer Name}` ? Name extends keyof Defs ? DetectRecursiveFallback<FromJSONSchema<Defs[Name], Defs, [unknown, ...Depth]>> : unknown : R extends `#/components/schemas/${infer Name}` ? Name extends keyof Defs ? DetectRecursiveFallback<FromJSONSchema<Defs[Name], Defs, [unknown, ...Depth]>> : unknown : R extends `#${infer AnchorName}` ? AnchorName extends keyof Defs ? DetectRecursiveFallback<FromJSONSchema<Defs[AnchorName], Defs, [unknown, ...Depth]>> : unknown : unknown;
271
- /**
272
- * Merge an allOf array into an intersection type.
273
- */
274
- type AllOfToType<A, Defs extends Record<string, unknown>, Depth extends readonly unknown[] = []> = A extends readonly unknown[] ? UnionToIntersection<FromJSONSchema<A[number], Defs, Depth>> : unknown;
275
- /**
276
- * Convert an anyOf/oneOf array into a union type.
277
- *
278
- * SOURCE-OF-TRUTH: mirrors runtime `walkUnion` (and the
279
- * `walkDiscriminatedUnion` fast path) in
280
- * `packages/core/src/core/walker.ts` (lines 723-752), together with
281
- * `detectDiscriminated` and `normaliseAnyOf` in
282
- * `packages/core/src/core/merge.ts` (lines 190-260).
283
- *
284
- * Deliberate divergence: the walker collapses qualifying `oneOf` members
285
- * into a `discriminatedUnion` field at runtime. The type-level helper
286
- * produces a plain TypeScript union because a discriminated union and a
287
- * plain union over the same members are structurally indistinguishable
288
- * at the type level. Parity is pinned in
289
- * `packages/core/tests/typeInference-walker-parity.test.ts`.
290
- *
291
- * Filters out `{ type: "null" }` members and instead makes the result
292
- * nullable when at least one null member is present — mirrors the
293
- * walker's `normaliseAnyOf`.
294
- */
295
- type UnionOfMembers<A, Defs extends Record<string, unknown>, Depth extends readonly unknown[] = []> = A extends readonly unknown[] ? HasNullMember<A> extends true ? Exclude<FromJSONSchema<A[number], Defs, Depth>, null> | null : FromJSONSchema<A[number], Defs, Depth> : unknown;
296
- /**
297
- * Check whether an anyOf/oneOf array contains a `{ type: "null" }` member.
298
- *
299
- * SOURCE-OF-TRUTH: mirrors runtime `normaliseAnyOf` in
300
- * `packages/core/src/core/merge.ts` (lines 190-209). Both implementations
301
- * only recognise schema-shaped null members (`{ type: "null" }`); a bare
302
- * `null` literal in the array is treated as non-nullable. Parity is
303
- * pinned in `packages/core/tests/typeInference-walker-parity.test.ts`.
304
- */
305
- type HasNullMember<A> = A extends readonly unknown[] ? null extends A[number] ? false : {
306
- type: "null";
307
- } extends A[number] ? true : false : false;
308
- /**
309
- * Dispatch on a `type` value -- handles single types, type arrays,
310
- * and delegates to the appropriate type-specific resolver.
311
- */
312
- type TypeToTs<T, S, Defs extends Record<string, unknown>, Depth extends readonly unknown[] = []> = T extends "string" ? string : T extends "number" | "integer" ? number : T extends "boolean" ? boolean : T extends "null" ? null : T extends "array" ? ArraySchemaToTs<S, Defs, Depth> : T extends "object" ? ObjectSchemaToTs<S, Defs, Depth> : T extends readonly (infer E)[] ? TypeArrayToTs<E, S, Defs, Depth> : unknown;
313
- /**
314
- * Handle `type` as an array (Draft 04-07): `["string", "null"]`.
315
- * Filters out "null" and makes the result nullable.
316
- */
317
- type TypeArrayToTs<E, S, Defs extends Record<string, unknown>, Depth extends readonly unknown[] = []> = 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<OmitArrayHelpers<S>, Defs, Depth>, S> : E extends "object" ? NullableResult<ObjectSchemaToTs<OmitArrayHelpers<S>, Defs, Depth>, S> : unknown;
318
- /**
319
- * Make a type nullable if the original schema `type` array includes "null".
320
- * Detects nullable from the type array directly.
321
- */
322
- type NullableResult<Base, S> = S extends {
323
- type: readonly (infer T)[];
324
- } ? "null" extends T ? Base | null : Base : Base;
325
- /**
326
- * Omit array-utility keys that interfere with object/array matching
327
- * when re-parsing a schema for a single type from a type array.
328
- */
329
- type OmitArrayHelpers<S> = Omit<S, "prefixItems" | "items" | "additionalProperties">;
330
- /**
331
- * Parse an array schema: prefixItems -> tuple, items -> T[], or unknown[].
332
- *
333
- * Draft 04 used tuple-form `items` (an array of schemas) for tuple typing;
334
- * Draft 2020-12 renamed this to `prefixItems`. The runtime normaliser in
335
- * `packages/core/src/core/normalise.ts` (lines 526-534) rewrites the legacy
336
- * form to `prefixItems` before the walker sees it. We mirror that rewrite
337
- * here so `as const` literals using the legacy form infer the same tuple
338
- * type at compile time.
339
- *
340
- * `contains` / `minContains` / `maxContains` constrain elements at runtime
341
- * but don't change the compile-time array element type.
342
- */
343
- type ArraySchemaToTs<S, Defs extends Record<string, unknown>, Depth extends readonly unknown[] = []> = S extends {
344
- prefixItems: infer P;
345
- } ? PrefixItemsToTuple<P, Defs, Depth> : S extends {
346
- items: infer I extends readonly unknown[];
347
- } ? PrefixItemsToTuple<I, Defs, Depth> : S extends {
348
- items: infer I;
349
- } ? FromJSONSchema<I, Defs, Depth>[] : unknown[];
350
- /**
351
- * Convert a prefixItems array to a TypeScript tuple type.
352
- */
353
- type PrefixItemsToTuple<P, Defs extends Record<string, unknown>, Depth extends readonly unknown[] = []> = P extends readonly [infer First, ...infer Rest] ? [FromJSONSchema<First, Defs, Depth>, ...PrefixItemsToTuple<Rest, Defs, Depth>] : [];
354
- /**
355
- * Parse an object schema: properties + required -> specific object,
356
- * additionalProperties -> Record, or empty object.
357
- *
358
- * Handles:
359
- * - `properties` + `required` -> specific object type with required/optional keys
360
- * - `additionalProperties` as schema -> Record<string, T>
361
- * - `patternProperties` -> merged into a loose index signature alongside specific props
362
- * (TypeScript cannot express regex-keyed properties)
363
- * - `propertyNames` -> ignored at type level (TS cannot validate key shapes)
364
- * - `dependentSchemas` / `dependentRequired` -> ignored (runtime-only conditionals)
365
- * - `unevaluatedProperties` -> ignored (runtime-only)
366
- */
367
- type ObjectSchemaToTs<S, Defs extends Record<string, unknown>, Depth extends readonly unknown[] = []> = S extends {
368
- type: "object";
369
- properties: infer P;
370
- } ? ExtractDefs<S, Defs> extends infer D extends Record<string, unknown> ? MergePatternProps<{ [K in keyof P as K extends RequiredKeysOf<S> ? K : never]: FromJSONSchema<P[K], D, Depth> } & { [K in keyof P as K extends RequiredKeysOf<S> ? never : K]?: FromJSONSchema<P[K], D, Depth> }, S, D, Depth> : never : S extends {
371
- additionalProperties: infer V;
372
- } ? Record<string, FromJSONSchema<V, Defs, Depth>> : Record<string, unknown>;
373
- /**
374
- * If the schema has `patternProperties`, intersect the base object type
375
- * with a `Record<string, T>` index signature covering all pattern values.
376
- * If no `patternProperties`, return the base type unchanged.
377
- */
378
- type MergePatternProps<Base, S, Defs extends Record<string, unknown>, Depth extends readonly unknown[] = []> = S extends {
379
- patternProperties: infer PP;
380
- } ? PP extends Record<string, unknown> ? Base & Record<string, UnionOfPatternValues<PP, Defs, Depth>> : Base : Base;
381
- /**
382
- * Extract the union of all pattern property value types.
383
- */
384
- type UnionOfPatternValues<PP extends Record<string, unknown>, Defs extends Record<string, unknown>, Depth extends readonly unknown[] = []> = { [K in keyof PP]: FromJSONSchema<PP[K], Defs, Depth> }[keyof PP];
385
- /**
386
- * Extract the `required` array from a schema as a union of string literals.
387
- * Handles both readonly `as const` arrays and mutable arrays.
388
- */
389
- type RequiredKeysOf<S> = S extends {
390
- required: infer R;
391
- } ? R extends readonly string[] ? R[number] : never : never;
392
- /**
393
- * Extract $defs / definitions from a schema for $ref resolution context.
394
- * Also indexes schemas with `$anchor` or `$dynamicAnchor` by their anchor
395
- * name, enabling `#SomeName` ref resolution.
396
- *
397
- * Shares merge semantics with {@link MergeRootDefs}: caller-supplied
398
- * (`ParentDefs`) entries win on key collision, the empty-default
399
- * sentinel is detected so it does not poison the parent context, and the
400
- * `HasLocalDefs` guard short-circuits when the current node declares no
401
- * defs of its own.
402
- */
403
- 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;
404
- /** Extract raw $defs / definitions maps. */
405
- type ExtractRawDefs<S> = S extends {
406
- $defs: infer D;
407
- } ? D extends Record<string, unknown> ? D : Record<string, never> : S extends {
408
- definitions: infer D;
409
- } ? D extends Record<string, unknown> ? D : Record<string, never> : Record<string, never>;
410
- /**
411
- * Build a map of `$anchor` name -> schema from a definitions block.
412
- * Scans each definition value for `$anchor`, `$dynamicAnchor`, or the
413
- * Draft 2019-09 `$recursiveAnchor` keyword and creates entries like
414
- * `{ Tree: <schema-with-$anchor-Tree> }`.
415
- *
416
- * SOURCE-OF-TRUTH: mirrors `normaliseDraft201909NodeWithContext` in
417
- * `packages/core/src/core/normalise.ts` (lines 638-650), which rewrites
418
- * `$recursiveAnchor: true` to `$anchor: "__recursive__"` and a string
419
- * `$recursiveAnchor: "name"` to `$anchor: "name"`. The corresponding
420
- * `$recursiveRef: "#"` therefore resolves through the same `Defs` map
421
- * as a modern `$ref: "#__recursive__"`.
422
- */
423
- type ExtractAnchors<D extends Record<string, unknown>> = { [K in keyof D as D[K] extends {
424
- $anchor: infer A extends string;
425
- } ? A : D[K] extends {
426
- $dynamicAnchor: infer A extends string;
427
- } ? A : D[K] extends {
428
- $recursiveAnchor: infer A extends string;
429
- } ? A : D[K] extends {
430
- $recursiveAnchor: true;
431
- } ? "__recursive__" : never]: D[K] };
432
- /**
433
- * Convert a union to an intersection.
434
- * `A | B` -> `A & B`. Used for allOf merging.
435
- */
436
- type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
437
- /**
438
- * Resolves an OpenAPI `ref` string to its JSON Schema, then parses it.
439
- *
440
- * SOURCE-OF-TRUTH: mirrors runtime `resolveRef` in
441
- * `packages/core/src/core/ref.ts` (line 90), which is invoked by the
442
- * walker entry point in `packages/core/src/core/walker.ts` (lines
443
- * 144-154) for OpenAPI documents. Any change to the runtime ref-resolution
444
- * rules (new ref forms, different cycle handling, JSON Pointer decoding)
445
- * must be reflected here and pinned in
446
- * `packages/core/tests/typeInference-walker-parity.test.ts`.
447
- *
448
- * Handles:
449
- * - `#/components/schemas/Name` (OpenAPI 3.x)
450
- * - `#/definitions/Name` (Swagger 2.0)
451
- * - `#/paths/...` (path-based refs, navigating the document tree)
452
- */
453
- type ResolveOpenAPIRef<Spec extends Record<string, unknown>, Ref extends string> = 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]> : unknown : unknown : unknown : Ref extends `#/definitions/${infer Name}` ? Spec["definitions"] extends Record<string, unknown> ? Name extends keyof Spec["definitions"] ? FromJSONSchema<Spec["definitions"][Name]> : unknown : unknown : Ref extends `#/paths/${infer PathRest}` ? ResolvePathBasedRef<Spec, PathRest> : unknown;
454
- /**
455
- * Resolve a path-based $ref after the `#/paths/` prefix.
456
- * Splits on `/` and navigates the document tree, decoding JSON Pointer
457
- * tilde escapes (`~1` -> `/`, `~0` -> `~`) on every segment.
458
- *
459
- * SOURCE-OF-TRUTH: mirrors runtime `dereference` in
460
- * `packages/core/src/core/ref.ts` (line 226), which applies the same
461
- * `~1` -> `/`, `~0` -> `~` substitutions per RFC 6901 §4. The runtime
462
- * uses ordered string replacement; the type-level mirror does the same
463
- * via {@link DecodeJsonPointerSegment}.
464
- */
465
- type ResolvePathBasedRef<Spec extends Record<string, unknown>, PathRest extends string> = Spec["paths"] extends Record<string, unknown> ? ResolvePathSegments<Spec["paths"], SplitPath<PathRest>> : unknown;
466
- /**
467
- * Replace every occurrence of `From` with `To` inside `S`.
468
- *
469
- * Pure type-level alternative to `String.prototype.replaceAll` used for
470
- * JSON Pointer escape decoding. Terminates when no further match is
471
- * found in the tail.
472
- */
473
- 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;
474
- /**
475
- * Decode a single JSON Pointer reference token per RFC 6901 §4:
476
- * apply `~1` -> `/` first, then `~0` -> `~`. The order matters — an
477
- * encoded `~` containing a literal `1` (e.g. `~01`) must remain `~1`
478
- * after decoding, which only works when `~1` is processed first.
479
- */
480
- type DecodeJsonPointerSegment<S extends string> = ReplaceAll<ReplaceAll<S, "~1", "/">, "~0", "~">;
481
- /**
482
- * Split a path string on `/` into a tuple of segments.
483
- * The first segment is the path key (may be empty for `/pets` -> `""` / `"pets"`).
484
- */
485
- type SplitPath<S extends string> = S extends `${infer Head}/${infer Tail}` ? [Head, ...SplitPath<Tail>] : [S];
486
- /**
487
- * Recursively navigate into a document object by path segments. Each
488
- * segment is JSON-Pointer-decoded before indexing so encoded forms such
489
- * as `~1pets` correctly resolve to the `"/pets"` key.
490
- */
491
- 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;
492
- /** Navigate to a path item in an OpenAPI document. */
493
- type PathItemOf<Doc, Path extends string> = Doc extends {
494
- paths: Record<string, unknown>;
495
- } ? Path extends keyof Doc["paths"] ? Doc["paths"][Path] : unknown : unknown;
496
- /** Navigate to an operation within a path item. */
497
- type OperationOf<PathItem, Method extends string> = PathItem extends Record<string, unknown> ? Method extends keyof PathItem ? PathItem[Method] : unknown : unknown;
498
- /**
499
- * Extract the schema from request body content (any media type).
500
- *
501
- * `Record<string, { schema: infer S }>` already subsumes the previous
502
- * `application/json`-specific branch — if the JSON content matches the
503
- * specific shape it also matches the general index-signature pattern.
504
- * The narrower branch was therefore unreachable and has been removed.
505
- */
506
- type RequestBodySchemaOf<Op> = Op extends {
507
- requestBody: {
508
- content: Record<string, {
509
- schema: infer S;
510
- }>;
511
- };
512
- } ? S : unknown;
513
- /**
514
- * Extract the schema from response content (any media type).
515
- *
516
- * Same rationale as `RequestBodySchemaOf`: the index-signature branch
517
- * subsumes the `application/json` branch, which was unreachable.
518
- */
519
- type ResponseSchemaOf<Op, Status extends string> = Op extends {
520
- responses: Record<string, unknown>;
521
- } ? Status extends keyof Op["responses"] ? Op["responses"][Status] extends {
522
- content: Record<string, {
523
- schema: infer S;
524
- }>;
525
- } ? S : unknown : unknown : unknown;
526
- /**
527
- * Resolve a schema that may be a `$ref` pointer.
528
- *
529
- * The `nullable: true` handling lives inside `FromJSONSchema` so it
530
- * applies uniformly to direct schemas, refs, and nested fields. This
531
- * helper only dispatches between ref-resolution and plain inference.
532
- */
533
- type ResolveMaybeRef<Doc, S> = S extends {
534
- $ref: infer R extends string;
535
- } ? ResolveOpenAPIRef<Doc & Record<string, unknown>, R> : S extends Record<string, unknown> ? FromJSONSchema<S> : unknown;
536
- /** Extract parameter names from an operation. */
537
- type ParameterNamesOf<Doc, Path extends string, Method extends string> = OperationOf<PathItemOf<Doc, Path>, Method> extends {
538
- parameters: readonly unknown[];
539
- } ? OperationOf<PathItemOf<Doc, Path>, Method>["parameters"][number] extends {
540
- name: infer N;
541
- } ? N extends string ? N : never : never : never;
542
- /**
543
- * Detect whether a document is Swagger 2.0 (OpenAPI 2.0).
544
- *
545
- * SOURCE-OF-TRUTH: mirrors runtime `isSwagger2` in
546
- * `packages/core/src/core/version.ts` (line 305), which parses the
547
- * `swagger` field via `detectOpenApiVersion` (line 264) and returns true
548
- * for any document whose major version is `2`. Runtime therefore accepts
549
- * `"2.0"`, `"2.0.0"`, `"2.1"`, and any other `2.x` form — so the
550
- * type-level detector must too.
551
- *
552
- * Type-level Swagger 2.0 documents cannot be fully normalised at compile
553
- * time — the rewrite reorders the document tree (definitions →
554
- * components/schemas, body parameters → requestBody, etc.) in ways
555
- * TypeScript's mapped-type machinery cannot express. Detecting the
556
- * version is tractable, so we surface `__SchemaInferenceFellBack`
557
- * deliberately rather than silently producing `unknown`.
558
- *
559
- * Two shapes are accepted:
560
- * - `{ swagger: "2.<anything>" }` — the on-the-wire string form
561
- * - `{ swagger: { major: 2, ... } }` — the parsed `OpenApiVersionInfo`
562
- * object form, mirroring the runtime's tolerance for pre-parsed
563
- * version metadata
564
- */
565
- type IsSwagger2Doc<Doc> = Doc extends {
566
- swagger: `2.${string}`;
567
- } ? true : Doc extends {
568
- swagger: {
569
- major: 2;
570
- };
571
- } ? true : false;
572
- /**
573
- * Infer the TypeScript type of an OpenAPI operation's request body.
574
- *
575
- * Swagger 2.0 documents are not normalised at the type level. When the
576
- * input is Swagger 2.0, this returns `__SchemaInferenceFellBack` so
577
- * callers can detect the fallback explicitly via a conditional type.
578
- */
579
- type OpenAPIRequestBodyType<Doc, Path extends string, Method extends string> = IsSwagger2Doc<Doc> extends true ? __SchemaInferenceFellBack : ResolveMaybeRef<Doc, RequestBodySchemaOf<OperationOf<PathItemOf<Doc, Path>, Method>>>;
580
- /**
581
- * Infer the TypeScript type of an OpenAPI operation's response.
582
- *
583
- * Swagger 2.0 documents are not normalised at the type level. When the
584
- * input is Swagger 2.0, this returns `__SchemaInferenceFellBack` so
585
- * callers can detect the fallback explicitly via a conditional type.
586
- */
587
- type OpenAPIResponseType<Doc, Path extends string, Method extends string, Status extends string> = IsSwagger2Doc<Doc> extends true ? __SchemaInferenceFellBack : ResolveMaybeRef<Doc, ResponseSchemaOf<OperationOf<PathItemOf<Doc, Path>, Method>, Status>>;
588
- /**
589
- * Convert a resolved request/response type into the corresponding
590
- * `fields` prop type used by ApiRequestBody / ApiResponse:
591
- *
592
- * - `__SchemaInferenceFellBack` (Swagger 2.0, depth-exceeded refs) is
593
- * preserved verbatim so callers can detect the brand.
594
- * - `unknown` (no schema found at the supplied path/status) falls back
595
- * to the loose `Record<string, FieldOverride>` shape so runtime
596
- * documents still typecheck.
597
- * - Any other concrete type is mapped through `FieldOverrides`.
598
- *
599
- * The brand check intentionally precedes the `unknown` check. The brand
600
- * is a structural object type and is therefore NOT assignable to
601
- * `unknown extends T` — checking that first would always short-circuit
602
- * to the loose `Record` fallback and the brand would never surface.
603
- */
604
- type FieldsFromInferred<T> = [T] extends [__SchemaInferenceFellBack] ? __SchemaInferenceFellBack : unknown extends T ? Record<string, FieldOverride> : FieldOverrides<T>;
605
- /**
606
- * Infer the fields prop type for ApiRequestBody.
607
- *
608
- * Surfaces `__SchemaInferenceFellBack` for Swagger 2.0 documents and
609
- * for schemas whose $ref chains exceed type-level depth limits. Falls
610
- * back to `Record<string, FieldOverride>` for runtime documents whose
611
- * shape cannot be inferred at compile time.
612
- */
613
- type InferRequestBodyFields<Doc, Path extends string, Method extends string> = FieldsFromInferred<OpenAPIRequestBodyType<Doc, Path, Method>>;
614
- /**
615
- * Infer the fields prop type for ApiResponse.
616
- *
617
- * Surfaces `__SchemaInferenceFellBack` for Swagger 2.0 documents and
618
- * for schemas whose $ref chains exceed type-level depth limits. Falls
619
- * back to `Record<string, FieldOverride>` for runtime documents whose
620
- * shape cannot be inferred at compile time.
621
- */
622
- type InferResponseFields<Doc, Path extends string, Method extends string, Status extends string> = FieldsFromInferred<OpenAPIResponseType<Doc, Path, Method, Status>>;
623
- /**
624
- * Infer the overrides prop type for ApiParameters.
625
- * Falls back to `Record<string, FieldOverride>` for runtime documents.
626
- */
627
- 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>>;
628
- /**
629
- * Check if T is a "narrow" type (not wide like object, Record, or unknown).
630
- * Used to determine if we can enumerate keys for path inference.
631
- */
632
- type IsNarrowObject<T> = T extends string | number | boolean | null | undefined | unknown[] ? false : T extends object ? Record<string, never> extends T ? false : true : false;
633
- /**
634
- * Extract all valid dot-separated paths from an object type.
635
- * Produces paths like "name" | "address.city" | "address.postcode".
636
- * Stops at leaf types (string, number, boolean, null) and arrays.
637
- * Returns `string` for wide types (object, Record, unknown).
638
- * Handles optional/nullable fields by unwrapping T | undefined.
639
- */
640
- 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;
641
- /**
642
- * Extract the type at a given dot-separated path.
643
- * PathOfType<T> produces valid paths; TypeAtPath resolves the leaf type.
644
- */
645
- 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;
646
- //#endregion
647
- export { InferResponseFields as a, PathOfType as c, TypeAtPath as d, UnrepresentableZodSchemaError as f, __SchemaInferenceFellBack as h, InferRequestBodyFields as i, RejectUnrepresentableZod as l, UnsafeFields as m, FromJSONSchema as n, OpenAPIRequestBodyType as o, UnrepresentableZodType as p, InferParameterOverrides as r, OpenAPIResponseType as s, DEFAULT_MAX_DEPTH as t, ResolveOpenAPIRef as u };