schema-components 1.18.1 → 1.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/adapter.d.mts +2 -2
- package/dist/core/adapter.mjs +128 -15
- package/dist/core/constraints.d.mts +2 -2
- package/dist/core/diagnostics.d.mts +1 -1
- package/dist/core/errors.d.mts +1 -1
- package/dist/core/errors.mjs +15 -1
- package/dist/core/fieldOrder.d.mts +1 -1
- package/dist/core/formats.d.mts +21 -14
- package/dist/core/formats.mjs +96 -4
- package/dist/core/merge.d.mts +1 -1
- package/dist/core/normalise.d.mts +38 -5
- package/dist/core/normalise.mjs +2 -2
- package/dist/core/openapi30.d.mts +33 -4
- package/dist/core/openapi30.mjs +2 -2
- package/dist/core/ref.d.mts +1 -1
- package/dist/core/renderer.d.mts +1 -1
- package/dist/core/renderer.mjs +7 -2
- package/dist/core/swagger2.d.mts +1 -1
- package/dist/core/swagger2.mjs +1 -1
- package/dist/core/typeInference.d.mts +2 -2
- package/dist/core/types.d.mts +1 -1
- package/dist/core/uri.d.mts +41 -0
- package/dist/core/uri.mjs +76 -0
- package/dist/core/version.d.mts +2 -2
- package/dist/core/version.mjs +43 -9
- package/dist/core/walkBuilders.d.mts +3 -3
- package/dist/core/walker.d.mts +1 -1
- package/dist/core/walker.mjs +50 -3
- package/dist/{diagnostics-BYk63jsC.d.mts → diagnostics-CbBPsxSt.d.mts} +1 -1
- package/dist/{errors-C5zRC2PU.d.mts → errors-C2iABcn9.d.mts} +14 -2
- package/dist/html/a11y.d.mts +2 -2
- package/dist/html/renderToHtml.d.mts +2 -2
- package/dist/html/renderToHtmlStream.d.mts +2 -2
- package/dist/html/renderers.d.mts +2 -2
- package/dist/html/renderers.mjs +37 -2
- package/dist/html/streamRenderers.d.mts +2 -2
- package/dist/normalise-CMMEl4cd.mjs +1306 -0
- package/dist/openapi/ApiCallbacks.d.mts +1 -1
- package/dist/openapi/ApiLinks.d.mts +1 -1
- package/dist/openapi/ApiResponseHeaders.d.mts +1 -1
- package/dist/openapi/ApiSecurity.d.mts +1 -1
- package/dist/openapi/bundle.mjs +2 -0
- package/dist/openapi/components.d.mts +2 -2
- package/dist/openapi/components.mjs +20 -5
- package/dist/openapi/parser.d.mts +1 -1
- package/dist/openapi/parser.mjs +6 -1
- package/dist/openapi/resolve.d.mts +17 -6
- package/dist/openapi/resolve.mjs +45 -7
- package/dist/react/SchemaComponent.d.mts +21 -9
- package/dist/react/SchemaComponent.mjs +3 -13
- package/dist/react/SchemaView.d.mts +3 -3
- package/dist/react/SchemaView.mjs +1 -0
- package/dist/react/fieldPath.d.mts +1 -1
- package/dist/react/headless.d.mts +7 -1
- package/dist/react/headless.mjs +13 -1
- package/dist/react/headlessRenderers.d.mts +54 -3
- package/dist/react/headlessRenderers.mjs +153 -3
- package/dist/{ref-Ckt5liZs.d.mts → ref-C8JbwfiS.d.mts} +1 -1
- package/dist/{renderer-BAGoX4AK.d.mts → renderer-SOIbJBtk.d.mts} +9 -3
- package/dist/themes/mantine.d.mts +1 -1
- package/dist/themes/mui.d.mts +1 -1
- package/dist/themes/radix.d.mts +1 -1
- package/dist/themes/shadcn.d.mts +1 -1
- package/dist/{typeInference-5JiqIZ8t.d.mts → typeInference-CDoD_LZ_.d.mts} +187 -42
- package/dist/{types-D_5ST7SS.d.mts → types-C9zw9wbX.d.mts} +6 -0
- package/dist/{version-B5NV-35j.d.mts → version-D-u7aMfy.d.mts} +43 -1
- package/package.json +1 -1
- package/dist/normalise-tL9FckAk.mjs +0 -748
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { isObject } from "../core/guards.mjs";
|
|
2
2
|
import { sortFieldsByOrder } from "../core/fieldOrder.mjs";
|
|
3
|
+
import { isSafeHyperlink, isSafeMailtoAddress } from "../core/uri.mjs";
|
|
3
4
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
5
|
import { isValidElement, useCallback, useEffect, useRef } from "react";
|
|
5
6
|
//#region src/react/headlessRenderers.tsx
|
|
@@ -83,13 +84,13 @@ function renderString(props) {
|
|
|
83
84
|
children: "—"
|
|
84
85
|
});
|
|
85
86
|
const format = props.constraints.format;
|
|
86
|
-
if (format === "email") return /* @__PURE__ */ jsx("a", {
|
|
87
|
+
if (format === "email" && isSafeMailtoAddress(strValue)) return /* @__PURE__ */ jsx("a", {
|
|
87
88
|
href: `mailto:${strValue}`,
|
|
88
89
|
id,
|
|
89
90
|
"aria-readonly": "true",
|
|
90
91
|
children: strValue
|
|
91
92
|
});
|
|
92
|
-
if (format === "uri" || format === "url") return /* @__PURE__ */ jsx("a", {
|
|
93
|
+
if ((format === "uri" || format === "url") && isSafeHyperlink(strValue)) return /* @__PURE__ */ jsx("a", {
|
|
93
94
|
href: strValue,
|
|
94
95
|
id,
|
|
95
96
|
"aria-readonly": "true",
|
|
@@ -595,6 +596,155 @@ function renderRecursive(props) {
|
|
|
595
596
|
" (recursive)"
|
|
596
597
|
] }) });
|
|
597
598
|
}
|
|
599
|
+
/**
|
|
600
|
+
* Render a literal field — `z.literal("a")` or `{ const: 5 }`.
|
|
601
|
+
*
|
|
602
|
+
* Literals are non-editable by nature (the value is fixed at the schema
|
|
603
|
+
* level), so both read-only and editable modes display the literal value(s).
|
|
604
|
+
* Multiple literals (`z.literal(["a", "b"])`) render comma-separated.
|
|
605
|
+
*/
|
|
606
|
+
function renderLiteral(props) {
|
|
607
|
+
const id = inputId(props.path);
|
|
608
|
+
if (props.tree.type !== "literal") return null;
|
|
609
|
+
const values = props.tree.literalValues;
|
|
610
|
+
if (values.length === 0) return /* @__PURE__ */ jsx("span", {
|
|
611
|
+
id,
|
|
612
|
+
"aria-readonly": "true",
|
|
613
|
+
children: "—"
|
|
614
|
+
});
|
|
615
|
+
return /* @__PURE__ */ jsx("span", {
|
|
616
|
+
id,
|
|
617
|
+
"aria-readonly": "true",
|
|
618
|
+
children: values.map((v) => v === null ? "null" : String(v)).join(", ")
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Render a null field — `z.null()` or `{ type: "null" }`.
|
|
623
|
+
*
|
|
624
|
+
* The only valid value is `null`, so render an em-dash placeholder
|
|
625
|
+
* regardless of mode. There is nothing the user can usefully change.
|
|
626
|
+
*/
|
|
627
|
+
function renderNull(props) {
|
|
628
|
+
return /* @__PURE__ */ jsx("span", {
|
|
629
|
+
id: inputId(props.path),
|
|
630
|
+
"aria-readonly": "true",
|
|
631
|
+
children: "—"
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Render a never field — `z.never()` or `{ not: {} }` / `false` schema.
|
|
636
|
+
*
|
|
637
|
+
* `never` indicates a position that cannot hold any value. We render a
|
|
638
|
+
* visible placeholder rather than throwing because some valid schemas
|
|
639
|
+
* intentionally contain `never` branches (e.g. exhaustive discriminated
|
|
640
|
+
* unions), and a runtime crash on render would be worse than a visible
|
|
641
|
+
* indicator.
|
|
642
|
+
*/
|
|
643
|
+
function renderNever(props) {
|
|
644
|
+
return /* @__PURE__ */ jsx("span", {
|
|
645
|
+
id: inputId(props.path),
|
|
646
|
+
"aria-readonly": "true",
|
|
647
|
+
className: "sc-never",
|
|
648
|
+
children: /* @__PURE__ */ jsx("em", { children: "never matches" })
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Render a tuple field — `z.tuple([z.string(), z.number()])` or
|
|
653
|
+
* `{ prefixItems: [...] }`.
|
|
654
|
+
*
|
|
655
|
+
* Positional rendering: each `prefixItems` entry is rendered at its index.
|
|
656
|
+
* The structural index (e.g. `[0]`) is passed as the path suffix so
|
|
657
|
+
* children get unique ids and labels.
|
|
658
|
+
*/
|
|
659
|
+
function renderTuple(props) {
|
|
660
|
+
if (props.tree.type !== "tuple") return null;
|
|
661
|
+
const prefixItems = props.tree.prefixItems;
|
|
662
|
+
const restItems = props.tree.restItems;
|
|
663
|
+
const arr = Array.isArray(props.value) ? props.value : [];
|
|
664
|
+
if (prefixItems.length === 0 && restItems === void 0 && arr.length === 0) return null;
|
|
665
|
+
const restCount = restItems !== void 0 ? Math.max(arr.length - prefixItems.length, 0) : 0;
|
|
666
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
667
|
+
role: "group",
|
|
668
|
+
"aria-label": props.meta.description ?? void 0,
|
|
669
|
+
children: [prefixItems.map((element, i) => {
|
|
670
|
+
const itemValue = arr[i];
|
|
671
|
+
const childOnChange = (v) => {
|
|
672
|
+
const next = arr.slice();
|
|
673
|
+
next[i] = v;
|
|
674
|
+
props.onChange(next);
|
|
675
|
+
};
|
|
676
|
+
return /* @__PURE__ */ jsx("div", { children: toReactNode(props.renderChild(element, itemValue, childOnChange, `[${String(i)}]`)) }, String(i));
|
|
677
|
+
}), restItems !== void 0 && Array.from({ length: restCount }, (_, j) => {
|
|
678
|
+
const i = prefixItems.length + j;
|
|
679
|
+
const itemValue = arr[i];
|
|
680
|
+
const childOnChange = (v) => {
|
|
681
|
+
const next = arr.slice();
|
|
682
|
+
next[i] = v;
|
|
683
|
+
props.onChange(next);
|
|
684
|
+
};
|
|
685
|
+
return /* @__PURE__ */ jsx("div", { children: toReactNode(props.renderChild(restItems, itemValue, childOnChange, `[${String(i)}]`)) }, `rest-${String(i)}`);
|
|
686
|
+
})]
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
/**
|
|
690
|
+
* Render a conditional field — JSON Schema `if`/`then`/`else`.
|
|
691
|
+
*
|
|
692
|
+
* Conditional schemas describe constraints rather than a single value
|
|
693
|
+
* shape, so the renderer surfaces each clause as a labelled fieldset.
|
|
694
|
+
* This mirrors the HTML renderer's annotation approach and gives a
|
|
695
|
+
* predictable structure for theme adapters that want to override it.
|
|
696
|
+
*/
|
|
697
|
+
function renderConditional(props) {
|
|
698
|
+
if (props.tree.type !== "conditional") return null;
|
|
699
|
+
const { ifClause, thenClause, elseClause } = props.tree;
|
|
700
|
+
return /* @__PURE__ */ jsxs("fieldset", {
|
|
701
|
+
className: "sc-conditional",
|
|
702
|
+
children: [
|
|
703
|
+
/* @__PURE__ */ jsxs("div", {
|
|
704
|
+
className: "sc-conditional-if",
|
|
705
|
+
children: [
|
|
706
|
+
/* @__PURE__ */ jsx("strong", { children: "if:" }),
|
|
707
|
+
" ",
|
|
708
|
+
toReactNode(props.renderChild(ifClause, props.value, props.onChange))
|
|
709
|
+
]
|
|
710
|
+
}),
|
|
711
|
+
thenClause !== void 0 && /* @__PURE__ */ jsxs("div", {
|
|
712
|
+
className: "sc-conditional-then",
|
|
713
|
+
children: [
|
|
714
|
+
/* @__PURE__ */ jsx("strong", { children: "then:" }),
|
|
715
|
+
" ",
|
|
716
|
+
toReactNode(props.renderChild(thenClause, props.value, props.onChange))
|
|
717
|
+
]
|
|
718
|
+
}),
|
|
719
|
+
elseClause !== void 0 && /* @__PURE__ */ jsxs("div", {
|
|
720
|
+
className: "sc-conditional-else",
|
|
721
|
+
children: [
|
|
722
|
+
/* @__PURE__ */ jsx("strong", { children: "else:" }),
|
|
723
|
+
" ",
|
|
724
|
+
toReactNode(props.renderChild(elseClause, props.value, props.onChange))
|
|
725
|
+
]
|
|
726
|
+
})
|
|
727
|
+
]
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
/**
|
|
731
|
+
* Render a negation field — JSON Schema `{ not: { ... } }`.
|
|
732
|
+
*
|
|
733
|
+
* Negation describes a constraint ("value must NOT match this schema")
|
|
734
|
+
* rather than a value shape. The renderer surfaces the negated schema
|
|
735
|
+
* beneath an explanatory preamble.
|
|
736
|
+
*/
|
|
737
|
+
function renderNegation(props) {
|
|
738
|
+
if (props.tree.type !== "negation") return null;
|
|
739
|
+
return /* @__PURE__ */ jsxs("fieldset", {
|
|
740
|
+
className: "sc-negation",
|
|
741
|
+
children: [
|
|
742
|
+
/* @__PURE__ */ jsx("strong", { children: "Must NOT match:" }),
|
|
743
|
+
" ",
|
|
744
|
+
toReactNode(props.renderChild(props.tree.negated, props.value, props.onChange))
|
|
745
|
+
]
|
|
746
|
+
});
|
|
747
|
+
}
|
|
598
748
|
function renderUnknown(props) {
|
|
599
749
|
const id = inputId(props.path);
|
|
600
750
|
if (props.readOnly) {
|
|
@@ -627,4 +777,4 @@ function matchUnionOption(options, value) {
|
|
|
627
777
|
if (typeof value === "object" && value !== null) return options.find((o) => o.type === "object");
|
|
628
778
|
}
|
|
629
779
|
//#endregion
|
|
630
|
-
export { defaultRecordValue, discriminatedUnionValueForTab, inputId, nextRecordKey, renameRecordKey, renderArray, renderBoolean, renderDiscriminatedUnion, renderEnum, renderFile, renderNumber, renderObject, renderRecord, renderRecursive, renderString, renderUnion, renderUnknown, toReactNode };
|
|
780
|
+
export { defaultRecordValue, discriminatedUnionValueForTab, inputId, nextRecordKey, renameRecordKey, renderArray, renderBoolean, renderConditional, renderDiscriminatedUnion, renderEnum, renderFile, renderLiteral, renderNegation, renderNever, renderNull, renderNumber, renderObject, renderRecord, renderRecursive, renderString, renderTuple, renderUnion, renderUnknown, toReactNode };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { D as StringConstraints, M as WalkedField, T as SchemaMeta, f as FileConstraints, t as ArrayConstraints, x as ObjectConstraints, y as NumberConstraints } from "./types-
|
|
1
|
+
import { D as StringConstraints, M as WalkedField, T as SchemaMeta, f as FileConstraints, t as ArrayConstraints, x as ObjectConstraints, y as NumberConstraints } from "./types-C9zw9wbX.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/core/renderer.d.ts
|
|
4
4
|
/**
|
|
@@ -98,6 +98,7 @@ interface ComponentResolver {
|
|
|
98
98
|
string?: RenderFunction;
|
|
99
99
|
number?: RenderFunction;
|
|
100
100
|
boolean?: RenderFunction;
|
|
101
|
+
null?: RenderFunction;
|
|
101
102
|
enum?: RenderFunction;
|
|
102
103
|
object?: RenderFunction;
|
|
103
104
|
array?: RenderFunction;
|
|
@@ -110,6 +111,7 @@ interface ComponentResolver {
|
|
|
110
111
|
recursive?: RenderFunction;
|
|
111
112
|
literal?: RenderFunction;
|
|
112
113
|
file?: RenderFunction;
|
|
114
|
+
never?: RenderFunction;
|
|
113
115
|
unknown?: RenderFunction;
|
|
114
116
|
}
|
|
115
117
|
/** An HTML render function returns a string. */
|
|
@@ -122,6 +124,7 @@ interface HtmlResolver {
|
|
|
122
124
|
string?: HtmlRenderFunction;
|
|
123
125
|
number?: HtmlRenderFunction;
|
|
124
126
|
boolean?: HtmlRenderFunction;
|
|
127
|
+
null?: HtmlRenderFunction;
|
|
125
128
|
enum?: HtmlRenderFunction;
|
|
126
129
|
object?: HtmlRenderFunction;
|
|
127
130
|
array?: HtmlRenderFunction;
|
|
@@ -134,13 +137,16 @@ interface HtmlResolver {
|
|
|
134
137
|
recursive?: HtmlRenderFunction;
|
|
135
138
|
literal?: HtmlRenderFunction;
|
|
136
139
|
file?: HtmlRenderFunction;
|
|
140
|
+
never?: HtmlRenderFunction;
|
|
137
141
|
unknown?: HtmlRenderFunction;
|
|
138
142
|
}
|
|
139
|
-
declare const RESOLVER_KEYS: readonly ["string", "number", "boolean", "enum", "object", "array", "tuple", "record", "union", "discriminatedUnion", "conditional", "negation", "recursive", "literal", "file", "unknown"];
|
|
143
|
+
declare const RESOLVER_KEYS: readonly ["string", "number", "boolean", "null", "enum", "object", "array", "tuple", "record", "union", "discriminatedUnion", "conditional", "negation", "recursive", "literal", "file", "never", "unknown"];
|
|
140
144
|
type ResolverKey = (typeof RESOLVER_KEYS)[number];
|
|
141
145
|
/**
|
|
142
146
|
* Map a schema type to the resolver key that handles it.
|
|
143
|
-
*
|
|
147
|
+
* Every WalkedField variant has a direct resolver key — exhaustive switch
|
|
148
|
+
* ensures new variants surface as a type error rather than silently
|
|
149
|
+
* falling through to "unknown".
|
|
144
150
|
*/
|
|
145
151
|
declare function typeToKey(type: WalkedField["type"]): ResolverKey;
|
|
146
152
|
/**
|
package/dist/themes/mui.d.mts
CHANGED
package/dist/themes/radix.d.mts
CHANGED
package/dist/themes/shadcn.d.mts
CHANGED
|
@@ -1,6 +1,43 @@
|
|
|
1
|
-
import { d as FieldOverrides, u as FieldOverride } from "./types-
|
|
1
|
+
import { d as FieldOverrides, u as FieldOverride } from "./types-C9zw9wbX.mjs";
|
|
2
|
+
import { z } from "zod";
|
|
2
3
|
|
|
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;
|
|
4
41
|
/**
|
|
5
42
|
* Convert a readonly tuple/array of values to a union type.
|
|
6
43
|
* Handles both `as const` readonly tuples and mutable arrays.
|
|
@@ -30,6 +67,17 @@ type ArrayToUnion<A> = A extends readonly unknown[] ? A[number] : never;
|
|
|
30
67
|
* - patternProperties -> merged into loose index signature
|
|
31
68
|
*/
|
|
32
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 {
|
|
33
81
|
$ref: infer R extends string;
|
|
34
82
|
} ? ResolveSchemaRef<R, Defs> : S extends {
|
|
35
83
|
$recursiveRef: string;
|
|
@@ -113,9 +161,16 @@ type DEFAULT_MAX_DEPTH = 10;
|
|
|
113
161
|
* Supports:
|
|
114
162
|
* - `#` (root)
|
|
115
163
|
* - `#/$defs/Name` and `#/definitions/Name` (named definitions)
|
|
164
|
+
* - `#/components/schemas/Name` (OpenAPI 3.x component schemas)
|
|
116
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.
|
|
117
172
|
*/
|
|
118
|
-
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 `#${infer AnchorName}` ? AnchorName extends keyof Defs ? DetectRecursiveFallback<FromJSONSchema<Defs[AnchorName], Defs>> : unknown : unknown;
|
|
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;
|
|
119
174
|
/**
|
|
120
175
|
* Merge an allOf array into an intersection type.
|
|
121
176
|
*/
|
|
@@ -178,12 +233,21 @@ type OmitArrayHelpers<S> = Omit<S, "prefixItems" | "items" | "additionalProperti
|
|
|
178
233
|
/**
|
|
179
234
|
* Parse an array schema: prefixItems -> tuple, items -> T[], or unknown[].
|
|
180
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
|
+
*
|
|
181
243
|
* `contains` / `minContains` / `maxContains` constrain elements at runtime
|
|
182
244
|
* but don't change the compile-time array element type.
|
|
183
245
|
*/
|
|
184
246
|
type ArraySchemaToTs<S, Defs extends Record<string, unknown>> = S extends {
|
|
185
247
|
prefixItems: infer P;
|
|
186
248
|
} ? PrefixItemsToTuple<P, Defs> : S extends {
|
|
249
|
+
items: infer I extends readonly unknown[];
|
|
250
|
+
} ? PrefixItemsToTuple<I, Defs> : S extends {
|
|
187
251
|
items: infer I;
|
|
188
252
|
} ? FromJSONSchema<I, Defs>[] : unknown[];
|
|
189
253
|
/**
|
|
@@ -243,14 +307,26 @@ type ExtractRawDefs<S> = S extends {
|
|
|
243
307
|
} ? D extends Record<string, unknown> ? D : Record<string, never> : Record<string, never>;
|
|
244
308
|
/**
|
|
245
309
|
* Build a map of `$anchor` name -> schema from a definitions block.
|
|
246
|
-
* Scans each definition value for `$anchor
|
|
247
|
-
*
|
|
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__"`.
|
|
248
320
|
*/
|
|
249
321
|
type ExtractAnchors<D extends Record<string, unknown>> = { [K in keyof D as D[K] extends {
|
|
250
322
|
$anchor: infer A extends string;
|
|
251
323
|
} ? A : D[K] extends {
|
|
252
324
|
$dynamicAnchor: infer A extends string;
|
|
253
|
-
} ? A :
|
|
325
|
+
} ? A : D[K] extends {
|
|
326
|
+
$recursiveAnchor: infer A extends string;
|
|
327
|
+
} ? A : D[K] extends {
|
|
328
|
+
$recursiveAnchor: true;
|
|
329
|
+
} ? "__recursive__" : never]: D[K] };
|
|
254
330
|
/**
|
|
255
331
|
* Convert a union to an intersection.
|
|
256
332
|
* `A | B` -> `A & B`. Used for allOf merging.
|
|
@@ -275,92 +351,161 @@ type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) exten
|
|
|
275
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;
|
|
276
352
|
/**
|
|
277
353
|
* Resolve a path-based $ref after the `#/paths/` prefix.
|
|
278
|
-
* Splits on `/` and navigates the document tree
|
|
354
|
+
* Splits on `/` and navigates the document tree, decoding JSON Pointer
|
|
355
|
+
* tilde escapes (`~1` -> `/`, `~0` -> `~`) on every segment.
|
|
279
356
|
*
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
*
|
|
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}.
|
|
283
362
|
*/
|
|
284
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", "~">;
|
|
285
379
|
/**
|
|
286
380
|
* Split a path string on `/` into a tuple of segments.
|
|
287
381
|
* The first segment is the path key (may be empty for `/pets` -> `""` / `"pets"`).
|
|
288
382
|
*/
|
|
289
383
|
type SplitPath<S extends string> = S extends `${infer Head}/${infer Tail}` ? [Head, ...SplitPath<Tail>] : [S];
|
|
290
384
|
/**
|
|
291
|
-
* Recursively navigate into a document object by path segments.
|
|
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.
|
|
292
388
|
*/
|
|
293
|
-
type ResolvePathSegments<Doc, Segs extends string[]> = Segs extends [infer Head extends string, ...infer Rest extends string[]] ? Doc extends Record<string, unknown> ? Rest extends [] ? Doc[
|
|
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;
|
|
294
390
|
/** Navigate to a path item in an OpenAPI document. */
|
|
295
391
|
type PathItemOf<Doc, Path extends string> = Doc extends {
|
|
296
392
|
paths: Record<string, unknown>;
|
|
297
393
|
} ? Path extends keyof Doc["paths"] ? Doc["paths"][Path] : unknown : unknown;
|
|
298
394
|
/** Navigate to an operation within a path item. */
|
|
299
395
|
type OperationOf<PathItem, Method extends string> = PathItem extends Record<string, unknown> ? Method extends keyof PathItem ? PathItem[Method] : unknown : unknown;
|
|
300
|
-
/**
|
|
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
|
+
*/
|
|
301
404
|
type RequestBodySchemaOf<Op> = Op extends {
|
|
302
405
|
requestBody: {
|
|
303
406
|
content: Record<string, {
|
|
304
407
|
schema: infer S;
|
|
305
408
|
}>;
|
|
306
409
|
};
|
|
307
|
-
} ? S : Op extends {
|
|
308
|
-
requestBody: {
|
|
309
|
-
content: {
|
|
310
|
-
"application/json": {
|
|
311
|
-
schema: infer S;
|
|
312
|
-
};
|
|
313
|
-
};
|
|
314
|
-
};
|
|
315
410
|
} ? S : unknown;
|
|
316
|
-
/**
|
|
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
|
+
*/
|
|
317
417
|
type ResponseSchemaOf<Op, Status extends string> = Op extends {
|
|
318
418
|
responses: Record<string, unknown>;
|
|
319
419
|
} ? Status extends keyof Op["responses"] ? Op["responses"][Status] extends {
|
|
320
420
|
content: Record<string, {
|
|
321
421
|
schema: infer S;
|
|
322
422
|
}>;
|
|
323
|
-
} ? S : Op["responses"][Status] extends {
|
|
324
|
-
content: {
|
|
325
|
-
"application/json": {
|
|
326
|
-
schema: infer S;
|
|
327
|
-
};
|
|
328
|
-
};
|
|
329
423
|
} ? S : unknown : unknown : unknown;
|
|
330
|
-
/**
|
|
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
|
+
*/
|
|
331
431
|
type ResolveMaybeRef<Doc, S> = S extends {
|
|
332
432
|
$ref: infer R extends string;
|
|
333
|
-
} ? ResolveOpenAPIRef<Doc & Record<string, unknown>, R> : S extends
|
|
334
|
-
nullable: true;
|
|
335
|
-
} & Record<string, unknown> ? FromJSONSchema<Omit<S, "nullable">> | null : S extends Record<string, unknown> ? FromJSONSchema<S> : unknown;
|
|
433
|
+
} ? ResolveOpenAPIRef<Doc & Record<string, unknown>, R> : S extends Record<string, unknown> ? FromJSONSchema<S> : unknown;
|
|
336
434
|
/** Extract parameter names from an operation. */
|
|
337
435
|
type ParameterNamesOf<Doc, Path extends string, Method extends string> = OperationOf<PathItemOf<Doc, Path>, Method> extends {
|
|
338
436
|
parameters: readonly unknown[];
|
|
339
437
|
} ? OperationOf<PathItemOf<Doc, Path>, Method>["parameters"][number] extends {
|
|
340
438
|
name: infer N;
|
|
341
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;
|
|
342
458
|
/**
|
|
343
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.
|
|
344
464
|
*/
|
|
345
|
-
type OpenAPIRequestBodyType<Doc, Path extends string, Method extends string> = ResolveMaybeRef<Doc, RequestBodySchemaOf<OperationOf<PathItemOf<Doc, Path>, Method>>>;
|
|
465
|
+
type OpenAPIRequestBodyType<Doc, Path extends string, Method extends string> = IsSwagger2Doc<Doc> extends true ? __SchemaInferenceFellBack : ResolveMaybeRef<Doc, RequestBodySchemaOf<OperationOf<PathItemOf<Doc, Path>, Method>>>;
|
|
346
466
|
/**
|
|
347
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.
|
|
348
489
|
*/
|
|
349
|
-
type
|
|
490
|
+
type FieldsFromInferred<T> = [T] extends [__SchemaInferenceFellBack] ? __SchemaInferenceFellBack : unknown extends T ? Record<string, FieldOverride> : FieldOverrides<T>;
|
|
350
491
|
/**
|
|
351
492
|
* Infer the fields prop type for ApiRequestBody.
|
|
352
|
-
*
|
|
353
|
-
*
|
|
354
|
-
*
|
|
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.
|
|
355
498
|
*/
|
|
356
|
-
type InferRequestBodyFields<Doc, Path extends string, Method extends string> =
|
|
499
|
+
type InferRequestBodyFields<Doc, Path extends string, Method extends string> = FieldsFromInferred<OpenAPIRequestBodyType<Doc, Path, Method>>;
|
|
357
500
|
/**
|
|
358
501
|
* Infer the fields prop type for ApiResponse.
|
|
359
|
-
*
|
|
360
|
-
*
|
|
361
|
-
*
|
|
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.
|
|
362
507
|
*/
|
|
363
|
-
type InferResponseFields<Doc, Path extends string, Method extends string, Status extends string> =
|
|
508
|
+
type InferResponseFields<Doc, Path extends string, Method extends string, Status extends string> = FieldsFromInferred<OpenAPIResponseType<Doc, Path, Method, Status>>;
|
|
364
509
|
/**
|
|
365
510
|
* Infer the overrides prop type for ApiParameters.
|
|
366
511
|
* Falls back to `Record<string, FieldOverride>` for runtime documents.
|
|
@@ -385,4 +530,4 @@ type PathOfType<T, Prefix extends string = ""> = IsNarrowObject<T> extends true
|
|
|
385
530
|
*/
|
|
386
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;
|
|
387
532
|
//#endregion
|
|
388
|
-
export { InferResponseFields as a, PathOfType as c,
|
|
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 };
|
|
@@ -184,6 +184,12 @@ interface TupleField extends FieldBase {
|
|
|
184
184
|
constraints: ArrayConstraints;
|
|
185
185
|
/** Positional element schemas from `prefixItems`. */
|
|
186
186
|
prefixItems: WalkedField[];
|
|
187
|
+
/**
|
|
188
|
+
* Schema for items beyond the `prefixItems` length. In Draft 2020-12,
|
|
189
|
+
* `items` adjacent to `prefixItems` describes the rest element. When
|
|
190
|
+
* absent, additional items are permitted but unconstrained.
|
|
191
|
+
*/
|
|
192
|
+
restItems?: WalkedField;
|
|
187
193
|
}
|
|
188
194
|
interface RecordField extends FieldBase {
|
|
189
195
|
type: "record";
|