schema-components 1.20.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 (77) hide show
  1. package/README.md +1 -1
  2. package/dist/core/adapter.d.mts +28 -4
  3. package/dist/core/adapter.mjs +408 -71
  4. package/dist/core/constraints.d.mts +2 -2
  5. package/dist/core/constraints.mjs +0 -2
  6. package/dist/core/diagnostics.d.mts +1 -1
  7. package/dist/core/errors.d.mts +1 -1
  8. package/dist/core/errors.mjs +9 -15
  9. package/dist/core/fieldOrder.d.mts +1 -1
  10. package/dist/core/formats.d.mts +22 -1
  11. package/dist/core/formats.mjs +21 -0
  12. package/dist/core/limits.d.mts +2 -0
  13. package/dist/core/limits.mjs +23 -0
  14. package/dist/core/merge.d.mts +11 -2
  15. package/dist/core/merge.mjs +11 -0
  16. package/dist/core/normalise.d.mts +36 -4
  17. package/dist/core/normalise.mjs +2 -2
  18. package/dist/core/openapi30.d.mts +24 -1
  19. package/dist/core/openapi30.mjs +2 -2
  20. package/dist/core/ref.d.mts +1 -1
  21. package/dist/core/ref.mjs +35 -9
  22. package/dist/core/renderer.d.mts +1 -1
  23. package/dist/core/renderer.mjs +0 -2
  24. package/dist/core/swagger2.d.mts +1 -1
  25. package/dist/core/swagger2.mjs +1 -1
  26. package/dist/core/typeInference.d.mts +2 -2
  27. package/dist/core/types.d.mts +2 -2
  28. package/dist/core/types.mjs +1 -4
  29. package/dist/core/version.d.mts +1 -1
  30. package/dist/core/walkBuilders.d.mts +13 -5
  31. package/dist/core/walkBuilders.mjs +11 -3
  32. package/dist/core/walker.d.mts +1 -1
  33. package/dist/core/walker.mjs +110 -26
  34. package/dist/{diagnostics-CbBPsxSt.d.mts → diagnostics-D0QCYGv0.d.mts} +1 -1
  35. package/dist/{errors-C2iABcn9.d.mts → errors-DpFwqs5C.d.mts} +7 -11
  36. package/dist/html/a11y.d.mts +2 -2
  37. package/dist/html/a11y.mjs +10 -3
  38. package/dist/html/renderToHtml.d.mts +10 -3
  39. package/dist/html/renderToHtml.mjs +13 -3
  40. package/dist/html/renderToHtmlStream.d.mts +2 -2
  41. package/dist/html/renderers.d.mts +2 -2
  42. package/dist/html/renderers.mjs +1 -6
  43. package/dist/html/streamRenderers.d.mts +5 -4
  44. package/dist/html/streamRenderers.mjs +91 -30
  45. package/dist/limits-Cw5QZND8.d.mts +29 -0
  46. package/dist/{normalise-CMMEl4cd.mjs → normalise-DVEJQmF7.mjs} +791 -141
  47. package/dist/openapi/ApiCallbacks.d.mts +1 -1
  48. package/dist/openapi/ApiLinks.d.mts +1 -1
  49. package/dist/openapi/ApiResponseHeaders.d.mts +1 -1
  50. package/dist/openapi/ApiSecurity.d.mts +1 -1
  51. package/dist/openapi/ApiSecurity.mjs +127 -7
  52. package/dist/openapi/components.d.mts +175 -21
  53. package/dist/openapi/components.mjs +145 -21
  54. package/dist/openapi/parser.d.mts +1 -1
  55. package/dist/openapi/parser.mjs +74 -7
  56. package/dist/openapi/resolve.d.mts +70 -12
  57. package/dist/openapi/resolve.mjs +265 -42
  58. package/dist/react/SchemaComponent.d.mts +100 -35
  59. package/dist/react/SchemaComponent.mjs +66 -24
  60. package/dist/react/SchemaView.d.mts +3 -3
  61. package/dist/react/SchemaView.mjs +2 -2
  62. package/dist/react/fieldPath.d.mts +1 -1
  63. package/dist/react/headless.d.mts +1 -1
  64. package/dist/react/headless.mjs +1 -2
  65. package/dist/react/headlessRenderers.d.mts +3 -4
  66. package/dist/react/headlessRenderers.mjs +11 -31
  67. package/dist/{ref-C8JbwfiS.d.mts → ref-D-_JBZkF.d.mts} +7 -2
  68. package/dist/{renderer-SOIbJBtk.d.mts → renderer-BaRlQIuN.d.mts} +2 -2
  69. package/dist/themes/mantine.d.mts +1 -1
  70. package/dist/themes/mui.d.mts +1 -1
  71. package/dist/themes/radix.d.mts +1 -1
  72. package/dist/themes/shadcn.d.mts +1 -1
  73. package/dist/typeInference-DkcUHfaM.d.mts +982 -0
  74. package/dist/{types-C9zw9wbX.d.mts → types-BrRMV0en.d.mts} +15 -12
  75. package/package.json +1 -3
  76. package/dist/typeInference-CDoD_LZ_.d.mts +0 -533
  77. /package/dist/{version-D-u7aMfy.d.mts → version-D2jfdX6E.d.mts} +0 -0
@@ -59,7 +59,7 @@ type FieldOverride = Partial<SchemaMeta> & {
59
59
  * All schema types the walker can produce.
60
60
  * Used as the discriminant in the WalkedField tagged union.
61
61
  */
62
- type SchemaType = "string" | "number" | "boolean" | "null" | "enum" | "literal" | "object" | "array" | "tuple" | "record" | "union" | "discriminatedUnion" | "conditional" | "negation" | "recursive" | "file" | "never" | "unknown";
62
+ type SchemaType = "string" | "number" | "boolean" | "null" | "enum" | "literal" | "object" | "array" | "tuple" | "record" | "union" | "discriminatedUnion" | "conditional" | "negation" | "file" | "never" | "unknown";
63
63
  /** Constraints that apply to string schemas. */
64
64
  interface StringConstraints {
65
65
  minLength?: number;
@@ -84,8 +84,6 @@ interface ArrayConstraints {
84
84
  minItems?: number;
85
85
  maxItems?: number;
86
86
  uniqueItems?: boolean;
87
- /** Schema that at least one array item must match. */
88
- contains?: Record<string, unknown>;
89
87
  minContains?: number;
90
88
  maxContains?: number;
91
89
  /** Constraint schema for unevaluated items. */
@@ -176,6 +174,12 @@ interface ArrayField extends FieldBase {
176
174
  constraints: ArrayConstraints;
177
175
  /** The element sub-schema. */
178
176
  element?: WalkedField;
177
+ /**
178
+ * Walked schema that at least one array item must match
179
+ * (`contains` keyword). Constrains element membership at runtime;
180
+ * paired with `minContains`/`maxContains` for cardinality.
181
+ */
182
+ contains?: WalkedField;
179
183
  /** Walked schema for unevaluated items. */
180
184
  unevaluatedItems?: WalkedField;
181
185
  }
@@ -190,6 +194,12 @@ interface TupleField extends FieldBase {
190
194
  * absent, additional items are permitted but unconstrained.
191
195
  */
192
196
  restItems?: WalkedField;
197
+ /**
198
+ * Walked schema that at least one array item must match
199
+ * (`contains` keyword). Tuples may declare it alongside positional
200
+ * element schemas to require the presence of a specific element.
201
+ */
202
+ contains?: WalkedField;
193
203
  }
194
204
  interface RecordField extends FieldBase {
195
205
  type: "record";
@@ -233,12 +243,6 @@ interface FileField extends FieldBase {
233
243
  type: "file";
234
244
  constraints: FileConstraints;
235
245
  }
236
- interface RecursiveField extends FieldBase {
237
- type: "recursive";
238
- constraints: Record<string, never>;
239
- /** The $ref string that would create the cycle (e.g. "#" or "#Node"). */
240
- refTarget: string;
241
- }
242
246
  /** Schema position where `false` appears — the field cannot have any value. */
243
247
  interface NeverField extends FieldBase {
244
248
  type: "never";
@@ -252,7 +256,7 @@ interface UnknownField extends FieldBase {
252
256
  * Tagged union of all schema field types produced by the walker.
253
257
  * Use `field.type` to narrow to a specific variant.
254
258
  */
255
- type WalkedField = StringField | NumberField | BooleanField | NullField | EnumField | LiteralField | ObjectField | ArrayField | TupleField | RecordField | UnionField | DiscriminatedUnionField | ConditionalField | NegationField | RecursiveField | NeverField | FileField | UnknownField;
259
+ type WalkedField = StringField | NumberField | BooleanField | NullField | EnumField | LiteralField | ObjectField | ArrayField | TupleField | RecordField | UnionField | DiscriminatedUnionField | ConditionalField | NegationField | NeverField | FileField | UnknownField;
256
260
  declare function isStringField(field: WalkedField): field is StringField;
257
261
  declare function isNumberField(field: WalkedField): field is NumberField;
258
262
  declare function isBooleanField(field: WalkedField): field is BooleanField;
@@ -268,8 +272,7 @@ declare function isDiscriminatedUnionField(field: WalkedField): field is Discrim
268
272
  declare function isConditionalField(field: WalkedField): field is ConditionalField;
269
273
  declare function isNegationField(field: WalkedField): field is NegationField;
270
274
  declare function isFileField(field: WalkedField): field is FileField;
271
- declare function isRecursiveField(field: WalkedField): field is RecursiveField;
272
275
  declare function isNeverField(field: WalkedField): field is NeverField;
273
276
  declare function isUnknownField(field: WalkedField): field is UnknownField;
274
277
  //#endregion
275
- export { UnionField as A, isNegationField as B, RecordField as C, StringConstraints as D, SchemaType as E, isConditionalField as F, isRecordField as G, isNullField as H, isDiscriminatedUnionField as I, isTupleField as J, isRecursiveField as K, isEnumField as L, WalkedField as M, isArrayField as N, StringField as O, isBooleanField as P, isFileField as R, ObjectField as S, SchemaMeta as T, isNumberField as U, isNeverField as V, isObjectField as W, isUnknownField as X, isUnionField as Y, resolveEditability as Z, NeverField as _, DiscriminatedUnionField as a, NumberField as b, FieldBase as c, FieldOverrides as d, FileConstraints as f, NegationField as g, LiteralField as h, ConditionalField as i, UnknownField as j, TupleField as k, FieldConstraints as l, JsonObject as m, ArrayField as n, Editability as o, FileField as p, isStringField as q, BooleanField as r, EnumField as s, ArrayConstraints as t, FieldOverride as u, NullField as v, RecursiveField as w, ObjectConstraints as x, NumberConstraints as y, isLiteralField as z };
278
+ export { UnknownField as A, isNeverField as B, RecordField as C, StringField as D, StringConstraints as E, isDiscriminatedUnionField as F, isStringField as G, isNumberField as H, isEnumField as I, isUnknownField as J, isTupleField as K, isFileField as L, isArrayField as M, isBooleanField as N, TupleField as O, isConditionalField as P, isLiteralField as R, ObjectField as S, SchemaType as T, isObjectField as U, isNullField as V, isRecordField as W, resolveEditability as Y, NeverField as _, DiscriminatedUnionField as a, NumberField as b, FieldBase as c, FieldOverrides as d, FileConstraints as f, NegationField as g, LiteralField as h, ConditionalField as i, WalkedField as j, UnionField as k, FieldConstraints as l, JsonObject as m, ArrayField as n, Editability as o, FileField as p, isUnionField as q, BooleanField as r, EnumField as s, ArrayConstraints as t, FieldOverride as u, NullField as v, SchemaMeta as w, ObjectConstraints as x, NumberConstraints as y, isNegationField as z };
package/package.json CHANGED
@@ -1,10 +1,8 @@
1
1
  {
2
2
  "name": "schema-components",
3
- "version": "1.20.0",
3
+ "version": "1.22.0",
4
4
  "description": "React components that render UI from Zod schemas, JSON Schema, and OpenAPI documents",
5
5
  "type": "module",
6
- "main": "./dist/index.mjs",
7
- "types": "./dist/index.d.mts",
8
6
  "exports": {
9
7
  "./styles.css": "./dist/html/styles.css",
10
8
  "./core/*": {
@@ -1,533 +0,0 @@
1
- import { d as FieldOverrides, u as FieldOverride } from "./types-C9zw9wbX.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
- * Reject Zod 4 inputs whose runtime conversion is known to throw.
29
- *
30
- * - When `T` is one of the {@link UnrepresentableZodType} variants, the
31
- * resolved type is {@link UnrepresentableZodSchemaError}, which is
32
- * not assignable from any legitimate Zod / JSON Schema / OpenAPI
33
- * input — so the prop fails to typecheck.
34
- * - Anything else (Zod 4 schemas that DO convert, JSON Schema literals,
35
- * OpenAPI documents, `unknown` for runtime inputs) passes through
36
- * unchanged.
37
- *
38
- * Wrapped in a tuple to suppress distribution over union types.
39
- */
40
- type RejectUnrepresentableZod<T> = [T] extends [UnrepresentableZodType] ? UnrepresentableZodSchemaError : T;
41
- /**
42
- * Convert a readonly tuple/array of values to a union type.
43
- * Handles both `as const` readonly tuples and mutable arrays.
44
- */
45
- type ArrayToUnion<A> = A extends readonly unknown[] ? A[number] : never;
46
- /**
47
- * Maps a JSON Schema structure to a TypeScript type.
48
- * Works with `as const` literals -- provides full autocomplete for `fields`.
49
- *
50
- * Supports all JSON Schema draft versions (04-2020-12) and OpenAPI 3.x:
51
- * - Primitive types: string, number, integer, boolean, null
52
- * - type as array: `["string", "null"]` -> `string | null` (nullable)
53
- * - enum -> union of literal types
54
- * - const -> literal type
55
- * - object with properties/required -> specific object type
56
- * - object with additionalProperties -> Record<string, T>
57
- * - array with items -> T[]
58
- * - array with prefixItems -> tuple type
59
- * - allOf -> intersection type
60
- * - anyOf -> union type
61
- * - oneOf -> union type
62
- * - $ref -> resolved via $defs/definitions/$anchor context
63
- * - $dynamicRef -> resolved via $dynamicAnchor in definitions
64
- * - $recursiveRef -> unknown (recursive types not expressible in TS)
65
- * - if/then/else -> base schema (conditionals not expressible in TS)
66
- * - not -> unknown (negation not expressible in TS)
67
- * - patternProperties -> merged into loose index signature
68
- */
69
- type FromJSONSchema<S, Defs extends Record<string, unknown> = Record<string, never>> = S extends {
70
- nullable: true;
71
- } ?
72
- /**
73
- * OpenAPI 3.0 `nullable: true` — surface the keyword wherever it
74
- * appears (not just inside `ResolveMaybeRef`). The runtime path
75
- * rewrites this to `anyOf: [T, { type: "null" }]` via
76
- * `normaliseOpenApi30Node` (`openapi30.ts`). Mirroring at the
77
- * `FromJSONSchema` level means nested fields inside refs preserve
78
- * nullability when resolved.
79
- */
80
- FromJSONSchema<Omit<S, "nullable">, Defs> | null : S extends {
81
- $ref: infer R extends string;
82
- } ? ResolveSchemaRef<R, Defs> : S extends {
83
- $recursiveRef: string;
84
- } ? unknown : S extends {
85
- $dynamicRef: infer R extends string;
86
- } ? ResolveSchemaRef<R, Defs> : S extends {
87
- allOf: infer A;
88
- } ? AllOfToType<A, Defs> : S extends {
89
- anyOf: infer A;
90
- } ? UnionOfMembers<A, Defs> : S extends {
91
- oneOf: infer A;
92
- } ? UnionOfMembers<A, Defs> : S extends {
93
- if: unknown;
94
- } ? FromJSONSchema<Omit<S, "if" | "then" | "else">, Defs> : S extends {
95
- not: unknown;
96
- } ? unknown : S extends {
97
- const: infer V;
98
- } ? V : S extends {
99
- enum: infer E;
100
- } ? ArrayToUnion<E> : S extends {
101
- type: infer T;
102
- } ? TypeToTs<T, S, Defs> : S extends readonly (infer E)[] ? E : unknown;
103
- /**
104
- * Marker type emitted when OpenAPI $ref resolution hits the type-level
105
- * recursion depth limit. Instead of silently falling back to
106
- * `Record<string, FieldOverride>`, produces this branded type so
107
- * consumers can detect it via conditional types.
108
- *
109
- * Usage:
110
- * ```ts
111
- * type Fields = InferRequestBodyFields<Doc, "/users", "post">;
112
- * type IsFallback = Fields extends __SchemaInferenceFellBack ? true : false;
113
- * ```
114
- */
115
- interface __SchemaInferenceFellBack {
116
- readonly __schemaInferenceFallback: unique symbol;
117
- }
118
- /**
119
- * Escape hatch for recursive schemas where type-level inference
120
- * cannot proceed. Typed as `Record<string, FieldOverride>` but
121
- * explicitly branded so callers know they are using the unsafe path.
122
- *
123
- * JSDoc trade-off note: This bypasses field-level type safety.
124
- * Prefer restructuring the schema to avoid deep $ref chains
125
- * when possible.
126
- */
127
- type UnsafeFields = Record<string, FieldOverride> & {
128
- /** Marks this as the unsafe fallback for recursive schemas. */readonly __unsafe?: true;
129
- };
130
- /**
131
- * Convert a `FromJSONSchema` result to `unknown` when recursion is detected.
132
- * Returns the original type when the schema is non-recursive.
133
- */
134
- type DetectRecursiveFallback<T> = unknown extends T ? __SchemaInferenceFellBack : T;
135
- /**
136
- * Type-level recursion bound for $ref resolution.
137
- *
138
- * The TypeScript type system imposes its own recursion limit; without an
139
- * explicit bound a cyclic schema graph would exhaust it and degrade to
140
- * `any`/`unknown` silently. Ten levels is the runtime walker's parallel
141
- * — see `countDistinctRefs` in `ref.ts` (lines 52-55), which derives its
142
- * bound from the number of distinct `$ref` strings in the document.
143
- *
144
- * A fixed bound is used here rather than a derived one because the type
145
- * system has no way to count distinct strings across a recursive `Defs`
146
- * map without itself recursing — which is the problem the bound exists
147
- * to solve. Ten covers every realistic schema graph encountered in
148
- * practice; deeper graphs surface as `__SchemaInferenceFellBack` so
149
- * consumers can detect the limit explicitly.
150
- */
151
- type DEFAULT_MAX_DEPTH = 10;
152
- /**
153
- * Resolve a $ref against the local definitions context.
154
- *
155
- * SOURCE-OF-TRUTH: mirrors runtime `resolveRef` in
156
- * `packages/core/src/core/ref.ts` (line 90). Any change to the runtime
157
- * ref-resolution rules (new ref forms, different cycle handling) must be
158
- * reflected here and pinned in
159
- * `packages/core/tests/typeInference-walker-parity.test.ts`.
160
- *
161
- * Supports:
162
- * - `#` (root)
163
- * - `#/$defs/Name` and `#/definitions/Name` (named definitions)
164
- * - `#/components/schemas/Name` (OpenAPI 3.x component schemas)
165
- * - `#SomeName` ($anchor, $dynamicAnchor resolved from definitions)
166
- *
167
- * `#/components/schemas/` is resolved here for parity with the runtime's
168
- * `dereference` (`ref.ts` line 217), which walks any `#/...` JSON Pointer
169
- * uniformly. When the runtime walker encounters an inline `$ref` inside
170
- * a Zod-converted or hand-written JSON Schema that points into the
171
- * OpenAPI component tree, this branch produces the corresponding type.
172
- */
173
- type ResolveSchemaRef<R extends string, Defs extends Record<string, unknown>, Depth extends number = 0> = Depth extends DEFAULT_MAX_DEPTH ? __SchemaInferenceFellBack : R extends "#" ? unknown : R extends `#/$defs/${infer Name}` ? Name extends keyof Defs ? DetectRecursiveFallback<FromJSONSchema<Defs[Name], Defs>> : unknown : R extends `#/definitions/${infer Name}` ? Name extends keyof Defs ? DetectRecursiveFallback<FromJSONSchema<Defs[Name], Defs>> : unknown : R extends `#/components/schemas/${infer Name}` ? Name extends keyof Defs ? DetectRecursiveFallback<FromJSONSchema<Defs[Name], Defs>> : unknown : R extends `#${infer AnchorName}` ? AnchorName extends keyof Defs ? DetectRecursiveFallback<FromJSONSchema<Defs[AnchorName], Defs>> : unknown : unknown;
174
- /**
175
- * Merge an allOf array into an intersection type.
176
- */
177
- type AllOfToType<A, Defs extends Record<string, unknown>> = A extends readonly unknown[] ? UnionToIntersection<FromJSONSchema<A[number], Defs>> : unknown;
178
- /**
179
- * Convert an anyOf/oneOf array into a union type.
180
- *
181
- * SOURCE-OF-TRUTH: mirrors runtime `walkUnion` (and the
182
- * `walkDiscriminatedUnion` fast path) in
183
- * `packages/core/src/core/walker.ts` (lines 723-752), together with
184
- * `detectDiscriminated` and `normaliseAnyOf` in
185
- * `packages/core/src/core/merge.ts` (lines 190-260).
186
- *
187
- * Deliberate divergence: the walker collapses qualifying `oneOf` members
188
- * into a `discriminatedUnion` field at runtime. The type-level helper
189
- * produces a plain TypeScript union because a discriminated union and a
190
- * plain union over the same members are structurally indistinguishable
191
- * at the type level. Parity is pinned in
192
- * `packages/core/tests/typeInference-walker-parity.test.ts`.
193
- *
194
- * Filters out `{ type: "null" }` members and instead makes the result
195
- * nullable when at least one null member is present — mirrors the
196
- * walker's `normaliseAnyOf`.
197
- */
198
- type UnionOfMembers<A, Defs extends Record<string, unknown>> = A extends readonly unknown[] ? HasNullMember<A> extends true ? Exclude<FromJSONSchema<A[number], Defs>, null> | null : FromJSONSchema<A[number], Defs> : unknown;
199
- /**
200
- * Check whether an anyOf/oneOf array contains a `{ type: "null" }` member.
201
- *
202
- * SOURCE-OF-TRUTH: mirrors runtime `normaliseAnyOf` in
203
- * `packages/core/src/core/merge.ts` (lines 190-209). Both implementations
204
- * only recognise schema-shaped null members (`{ type: "null" }`); a bare
205
- * `null` literal in the array is treated as non-nullable. Parity is
206
- * pinned in `packages/core/tests/typeInference-walker-parity.test.ts`.
207
- */
208
- type HasNullMember<A> = A extends readonly unknown[] ? null extends A[number] ? false : {
209
- type: "null";
210
- } extends A[number] ? true : false : false;
211
- /**
212
- * Dispatch on a `type` value -- handles single types, type arrays,
213
- * and delegates to the appropriate type-specific resolver.
214
- */
215
- type TypeToTs<T, S, Defs extends Record<string, unknown>> = T extends "string" ? string : T extends "number" | "integer" ? number : T extends "boolean" ? boolean : T extends "null" ? null : T extends "array" ? ArraySchemaToTs<S, Defs> : T extends "object" ? ObjectSchemaToTs<S, Defs> : T extends readonly (infer E)[] ? TypeArrayToTs<E, S, Defs> : unknown;
216
- /**
217
- * Handle `type` as an array (Draft 04-07): `["string", "null"]`.
218
- * Filters out "null" and makes the result nullable.
219
- */
220
- type TypeArrayToTs<E, S, Defs extends Record<string, 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>, S> : E extends "object" ? NullableResult<ObjectSchemaToTs<OmitArrayHelpers<S>, Defs>, S> : unknown;
221
- /**
222
- * Make a type nullable if the original schema `type` array includes "null".
223
- * Detects nullable from the type array directly.
224
- */
225
- type NullableResult<Base, S> = S extends {
226
- type: readonly (infer T)[];
227
- } ? "null" extends T ? Base | null : Base : Base;
228
- /**
229
- * Omit array-utility keys that interfere with object/array matching
230
- * when re-parsing a schema for a single type from a type array.
231
- */
232
- type OmitArrayHelpers<S> = Omit<S, "prefixItems" | "items" | "additionalProperties">;
233
- /**
234
- * Parse an array schema: prefixItems -> tuple, items -> T[], or unknown[].
235
- *
236
- * Draft 04 used tuple-form `items` (an array of schemas) for tuple typing;
237
- * Draft 2020-12 renamed this to `prefixItems`. The runtime normaliser in
238
- * `packages/core/src/core/normalise.ts` (lines 526-534) rewrites the legacy
239
- * form to `prefixItems` before the walker sees it. We mirror that rewrite
240
- * here so `as const` literals using the legacy form infer the same tuple
241
- * type at compile time.
242
- *
243
- * `contains` / `minContains` / `maxContains` constrain elements at runtime
244
- * but don't change the compile-time array element type.
245
- */
246
- type ArraySchemaToTs<S, Defs extends Record<string, unknown>> = S extends {
247
- prefixItems: infer P;
248
- } ? PrefixItemsToTuple<P, Defs> : S extends {
249
- items: infer I extends readonly unknown[];
250
- } ? PrefixItemsToTuple<I, Defs> : S extends {
251
- items: infer I;
252
- } ? FromJSONSchema<I, Defs>[] : unknown[];
253
- /**
254
- * Convert a prefixItems array to a TypeScript tuple type.
255
- */
256
- type PrefixItemsToTuple<P, Defs extends Record<string, unknown>> = P extends readonly [infer First, ...infer Rest] ? [FromJSONSchema<First, Defs>, ...PrefixItemsToTuple<Rest, Defs>] : [];
257
- /**
258
- * Parse an object schema: properties + required -> specific object,
259
- * additionalProperties -> Record, or empty object.
260
- *
261
- * Handles:
262
- * - `properties` + `required` -> specific object type with required/optional keys
263
- * - `additionalProperties` as schema -> Record<string, T>
264
- * - `patternProperties` -> merged into a loose index signature alongside specific props
265
- * (TypeScript cannot express regex-keyed properties)
266
- * - `propertyNames` -> ignored at type level (TS cannot validate key shapes)
267
- * - `dependentSchemas` / `dependentRequired` -> ignored (runtime-only conditionals)
268
- * - `unevaluatedProperties` -> ignored (runtime-only)
269
- */
270
- type ObjectSchemaToTs<S, Defs extends Record<string, unknown>> = S extends {
271
- type: "object";
272
- properties: infer P;
273
- } ? 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> } & { [K in keyof P as K extends RequiredKeysOf<S> ? never : K]?: FromJSONSchema<P[K], D> }, S, D> : never : S extends {
274
- additionalProperties: infer V;
275
- } ? Record<string, FromJSONSchema<V, Defs>> : Record<string, unknown>;
276
- /**
277
- * If the schema has `patternProperties`, intersect the base object type
278
- * with a `Record<string, T>` index signature covering all pattern values.
279
- * If no `patternProperties`, return the base type unchanged.
280
- */
281
- type MergePatternProps<Base, S, Defs extends Record<string, unknown>> = S extends {
282
- patternProperties: infer PP;
283
- } ? PP extends Record<string, unknown> ? Base & Record<string, UnionOfPatternValues<PP, Defs>> : Base : Base;
284
- /**
285
- * Extract the union of all pattern property value types.
286
- */
287
- type UnionOfPatternValues<PP extends Record<string, unknown>, Defs extends Record<string, unknown>> = { [K in keyof PP]: FromJSONSchema<PP[K], Defs> }[keyof PP];
288
- /**
289
- * Extract the `required` array from a schema as a union of string literals.
290
- * Handles both readonly `as const` arrays and mutable arrays.
291
- */
292
- type RequiredKeysOf<S> = S extends {
293
- required: infer R;
294
- } ? R extends readonly string[] ? R[number] : never : never;
295
- /**
296
- * Extract $defs / definitions from a schema for $ref resolution context.
297
- * Also indexes schemas with `$anchor` or `$dynamicAnchor` by their anchor name,
298
- * enabling `#SomeName` ref resolution.
299
- * Merges with the existing Defs context from parent schemas.
300
- */
301
- type ExtractDefs<S, ParentDefs extends Record<string, unknown>> = ExtractRawDefs<S> extends infer RawDefs extends Record<string, unknown> ? RawDefs & ParentDefs & ExtractAnchors<RawDefs> : ParentDefs;
302
- /** Extract raw $defs / definitions maps. */
303
- type ExtractRawDefs<S> = S extends {
304
- $defs: infer D;
305
- } ? D extends Record<string, unknown> ? D : Record<string, never> : S extends {
306
- definitions: infer D;
307
- } ? D extends Record<string, unknown> ? D : Record<string, never> : Record<string, never>;
308
- /**
309
- * Build a map of `$anchor` name -> schema from a definitions block.
310
- * Scans each definition value for `$anchor`, `$dynamicAnchor`, or the
311
- * Draft 2019-09 `$recursiveAnchor` keyword and creates entries like
312
- * `{ Tree: <schema-with-$anchor-Tree> }`.
313
- *
314
- * SOURCE-OF-TRUTH: mirrors `normaliseDraft201909NodeWithContext` in
315
- * `packages/core/src/core/normalise.ts` (lines 638-650), which rewrites
316
- * `$recursiveAnchor: true` to `$anchor: "__recursive__"` and a string
317
- * `$recursiveAnchor: "name"` to `$anchor: "name"`. The corresponding
318
- * `$recursiveRef: "#"` therefore resolves through the same `Defs` map
319
- * as a modern `$ref: "#__recursive__"`.
320
- */
321
- type ExtractAnchors<D extends Record<string, unknown>> = { [K in keyof D as D[K] extends {
322
- $anchor: infer A extends string;
323
- } ? A : D[K] extends {
324
- $dynamicAnchor: infer A extends string;
325
- } ? A : D[K] extends {
326
- $recursiveAnchor: infer A extends string;
327
- } ? A : D[K] extends {
328
- $recursiveAnchor: true;
329
- } ? "__recursive__" : never]: D[K] };
330
- /**
331
- * Convert a union to an intersection.
332
- * `A | B` -> `A & B`. Used for allOf merging.
333
- */
334
- type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
335
- /**
336
- * Resolves an OpenAPI `ref` string to its JSON Schema, then parses it.
337
- *
338
- * SOURCE-OF-TRUTH: mirrors runtime `resolveRef` in
339
- * `packages/core/src/core/ref.ts` (line 90), which is invoked by the
340
- * walker entry point in `packages/core/src/core/walker.ts` (lines
341
- * 144-154) for OpenAPI documents. Any change to the runtime ref-resolution
342
- * rules (new ref forms, different cycle handling, JSON Pointer decoding)
343
- * must be reflected here and pinned in
344
- * `packages/core/tests/typeInference-walker-parity.test.ts`.
345
- *
346
- * Handles:
347
- * - `#/components/schemas/Name` (OpenAPI 3.x)
348
- * - `#/definitions/Name` (Swagger 2.0)
349
- * - `#/paths/...` (path-based refs, navigating the document tree)
350
- */
351
- 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;
352
- /**
353
- * Resolve a path-based $ref after the `#/paths/` prefix.
354
- * Splits on `/` and navigates the document tree, decoding JSON Pointer
355
- * tilde escapes (`~1` -> `/`, `~0` -> `~`) on every segment.
356
- *
357
- * SOURCE-OF-TRUTH: mirrors runtime `dereference` in
358
- * `packages/core/src/core/ref.ts` (line 226), which applies the same
359
- * `~1` -> `/`, `~0` -> `~` substitutions per RFC 6901 §4. The runtime
360
- * uses ordered string replacement; the type-level mirror does the same
361
- * via {@link DecodeJsonPointerSegment}.
362
- */
363
- type ResolvePathBasedRef<Spec extends Record<string, unknown>, PathRest extends string> = Spec["paths"] extends Record<string, unknown> ? ResolvePathSegments<Spec["paths"], SplitPath<PathRest>> : unknown;
364
- /**
365
- * Replace every occurrence of `From` with `To` inside `S`.
366
- *
367
- * Pure type-level alternative to `String.prototype.replaceAll` used for
368
- * JSON Pointer escape decoding. Terminates when no further match is
369
- * found in the tail.
370
- */
371
- 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;
372
- /**
373
- * Decode a single JSON Pointer reference token per RFC 6901 §4:
374
- * apply `~1` -> `/` first, then `~0` -> `~`. The order matters — an
375
- * encoded `~` containing a literal `1` (e.g. `~01`) must remain `~1`
376
- * after decoding, which only works when `~1` is processed first.
377
- */
378
- type DecodeJsonPointerSegment<S extends string> = ReplaceAll<ReplaceAll<S, "~1", "/">, "~0", "~">;
379
- /**
380
- * Split a path string on `/` into a tuple of segments.
381
- * The first segment is the path key (may be empty for `/pets` -> `""` / `"pets"`).
382
- */
383
- type SplitPath<S extends string> = S extends `${infer Head}/${infer Tail}` ? [Head, ...SplitPath<Tail>] : [S];
384
- /**
385
- * Recursively navigate into a document object by path segments. Each
386
- * segment is JSON-Pointer-decoded before indexing so encoded forms such
387
- * as `~1pets` correctly resolve to the `"/pets"` key.
388
- */
389
- 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;
390
- /** Navigate to a path item in an OpenAPI document. */
391
- type PathItemOf<Doc, Path extends string> = Doc extends {
392
- paths: Record<string, unknown>;
393
- } ? Path extends keyof Doc["paths"] ? Doc["paths"][Path] : unknown : unknown;
394
- /** Navigate to an operation within a path item. */
395
- type OperationOf<PathItem, Method extends string> = PathItem extends Record<string, unknown> ? Method extends keyof PathItem ? PathItem[Method] : unknown : unknown;
396
- /**
397
- * Extract the schema from request body content (any media type).
398
- *
399
- * `Record<string, { schema: infer S }>` already subsumes the previous
400
- * `application/json`-specific branch — if the JSON content matches the
401
- * specific shape it also matches the general index-signature pattern.
402
- * The narrower branch was therefore unreachable and has been removed.
403
- */
404
- type RequestBodySchemaOf<Op> = Op extends {
405
- requestBody: {
406
- content: Record<string, {
407
- schema: infer S;
408
- }>;
409
- };
410
- } ? S : unknown;
411
- /**
412
- * Extract the schema from response content (any media type).
413
- *
414
- * Same rationale as `RequestBodySchemaOf`: the index-signature branch
415
- * subsumes the `application/json` branch, which was unreachable.
416
- */
417
- type ResponseSchemaOf<Op, Status extends string> = Op extends {
418
- responses: Record<string, unknown>;
419
- } ? Status extends keyof Op["responses"] ? Op["responses"][Status] extends {
420
- content: Record<string, {
421
- schema: infer S;
422
- }>;
423
- } ? S : unknown : unknown : unknown;
424
- /**
425
- * Resolve a schema that may be a `$ref` pointer.
426
- *
427
- * The `nullable: true` handling lives inside `FromJSONSchema` so it
428
- * applies uniformly to direct schemas, refs, and nested fields. This
429
- * helper only dispatches between ref-resolution and plain inference.
430
- */
431
- type ResolveMaybeRef<Doc, S> = S extends {
432
- $ref: infer R extends string;
433
- } ? ResolveOpenAPIRef<Doc & Record<string, unknown>, R> : S extends Record<string, unknown> ? FromJSONSchema<S> : unknown;
434
- /** Extract parameter names from an operation. */
435
- type ParameterNamesOf<Doc, Path extends string, Method extends string> = OperationOf<PathItemOf<Doc, Path>, Method> extends {
436
- parameters: readonly unknown[];
437
- } ? OperationOf<PathItemOf<Doc, Path>, Method>["parameters"][number] extends {
438
- name: infer N;
439
- } ? N extends string ? N : never : never : never;
440
- /**
441
- * Detect whether a document is Swagger 2.0 (OpenAPI 2.0).
442
- *
443
- * SOURCE-OF-TRUTH: mirrors runtime `isSwagger2` in
444
- * `packages/core/src/core/version.ts`, which checks for `swagger: "2.0"`.
445
- * The runtime path also recognises top-level `definitions` / parameters in
446
- * the body location, but `swagger: "2.0"` is the canonical marker.
447
- *
448
- * Type-level Swagger 2.0 documents cannot be fully normalised at compile
449
- * time — the rewrite reorders the document tree (definitions →
450
- * components/schemas, body parameters → requestBody, etc.) in ways
451
- * TypeScript's mapped-type machinery cannot express. Detecting the
452
- * version is tractable, so we surface `__SchemaInferenceFellBack`
453
- * deliberately rather than silently producing `unknown`.
454
- */
455
- type IsSwagger2Doc<Doc> = Doc extends {
456
- swagger: "2.0";
457
- } ? true : false;
458
- /**
459
- * Infer the TypeScript type of an OpenAPI operation's request body.
460
- *
461
- * Swagger 2.0 documents are not normalised at the type level. When the
462
- * input is Swagger 2.0, this returns `__SchemaInferenceFellBack` so
463
- * callers can detect the fallback explicitly via a conditional type.
464
- */
465
- type OpenAPIRequestBodyType<Doc, Path extends string, Method extends string> = IsSwagger2Doc<Doc> extends true ? __SchemaInferenceFellBack : ResolveMaybeRef<Doc, RequestBodySchemaOf<OperationOf<PathItemOf<Doc, Path>, Method>>>;
466
- /**
467
- * Infer the TypeScript type of an OpenAPI operation's response.
468
- *
469
- * Swagger 2.0 documents are not normalised at the type level. When the
470
- * input is Swagger 2.0, this returns `__SchemaInferenceFellBack` so
471
- * callers can detect the fallback explicitly via a conditional type.
472
- */
473
- 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>>;
474
- /**
475
- * Convert a resolved request/response type into the corresponding
476
- * `fields` prop type used by ApiRequestBody / ApiResponse:
477
- *
478
- * - `__SchemaInferenceFellBack` (Swagger 2.0, depth-exceeded refs) is
479
- * preserved verbatim so callers can detect the brand.
480
- * - `unknown` (no schema found at the supplied path/status) falls back
481
- * to the loose `Record<string, FieldOverride>` shape so runtime
482
- * documents still typecheck.
483
- * - Any other concrete type is mapped through `FieldOverrides`.
484
- *
485
- * The brand check intentionally precedes the `unknown` check. The brand
486
- * is a structural object type and is therefore NOT assignable to
487
- * `unknown extends T` — checking that first would always short-circuit
488
- * to the loose `Record` fallback and the brand would never surface.
489
- */
490
- type FieldsFromInferred<T> = [T] extends [__SchemaInferenceFellBack] ? __SchemaInferenceFellBack : unknown extends T ? Record<string, FieldOverride> : FieldOverrides<T>;
491
- /**
492
- * Infer the fields prop type for ApiRequestBody.
493
- *
494
- * Surfaces `__SchemaInferenceFellBack` for Swagger 2.0 documents and
495
- * for schemas whose $ref chains exceed type-level depth limits. Falls
496
- * back to `Record<string, FieldOverride>` for runtime documents whose
497
- * shape cannot be inferred at compile time.
498
- */
499
- type InferRequestBodyFields<Doc, Path extends string, Method extends string> = FieldsFromInferred<OpenAPIRequestBodyType<Doc, Path, Method>>;
500
- /**
501
- * Infer the fields prop type for ApiResponse.
502
- *
503
- * Surfaces `__SchemaInferenceFellBack` for Swagger 2.0 documents and
504
- * for schemas whose $ref chains exceed type-level depth limits. Falls
505
- * back to `Record<string, FieldOverride>` for runtime documents whose
506
- * shape cannot be inferred at compile time.
507
- */
508
- type InferResponseFields<Doc, Path extends string, Method extends string, Status extends string> = FieldsFromInferred<OpenAPIResponseType<Doc, Path, Method, Status>>;
509
- /**
510
- * Infer the overrides prop type for ApiParameters.
511
- * Falls back to `Record<string, FieldOverride>` for runtime documents.
512
- */
513
- 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>>;
514
- /**
515
- * Check if T is a "narrow" type (not wide like object, Record, or unknown).
516
- * Used to determine if we can enumerate keys for path inference.
517
- */
518
- type IsNarrowObject<T> = T extends string | number | boolean | null | undefined | unknown[] ? false : T extends object ? Record<string, never> extends T ? false : true : false;
519
- /**
520
- * Extract all valid dot-separated paths from an object type.
521
- * Produces paths like "name" | "address.city" | "address.postcode".
522
- * Stops at leaf types (string, number, boolean, null) and arrays.
523
- * Returns `string` for wide types (object, Record, unknown).
524
- * Handles optional/nullable fields by unwrapping T | undefined.
525
- */
526
- 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;
527
- /**
528
- * Extract the type at a given dot-separated path.
529
- * PathOfType<T> produces valid paths; TypeAtPath resolves the leaf type.
530
- */
531
- 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;
532
- //#endregion
533
- 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 };