schema-components 1.17.0 → 1.18.1
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 +1 -1
- package/dist/core/constraints.d.mts +1 -1
- package/dist/core/diagnostics.d.mts +1 -1
- package/dist/core/fieldOrder.d.mts +10 -0
- package/dist/core/fieldOrder.mjs +12 -0
- package/dist/core/merge.d.mts +14 -8
- package/dist/core/merge.mjs +109 -12
- package/dist/core/normalise.d.mts +1 -1
- package/dist/core/ref.d.mts +1 -1
- package/dist/core/renderer.d.mts +2 -2
- package/dist/core/renderer.mjs +31 -1
- package/dist/core/swagger2.d.mts +1 -1
- package/dist/core/typeInference.d.mts +2 -2
- package/dist/core/walkBuilders.d.mts +2 -2
- package/dist/core/walker.mjs +2 -2
- package/dist/{diagnostics-DzbZmcLI.d.mts → diagnostics-BYk63jsC.d.mts} +1 -1
- package/dist/html/a11y.d.mts +13 -2
- package/dist/html/a11y.mjs +26 -2
- package/dist/html/renderToHtml.d.mts +1 -1
- package/dist/html/renderToHtml.mjs +18 -33
- package/dist/html/renderToHtmlStream.d.mts +1 -1
- package/dist/html/renderers.d.mts +4 -3
- package/dist/html/renderers.mjs +37 -36
- package/dist/html/streamRenderers.d.mts +1 -1
- package/dist/html/streamRenderers.mjs +10 -21
- package/dist/openapi/ApiSecurity.mjs +1 -1
- package/dist/openapi/bundle.d.mts +9 -4
- package/dist/openapi/bundle.mjs +74 -15
- package/dist/openapi/components.d.mts +1 -1
- package/dist/openapi/components.mjs +20 -15
- package/dist/openapi/parser.d.mts +2 -2
- package/dist/openapi/parser.mjs +12 -12
- package/dist/openapi/resolve.d.mts +13 -2
- package/dist/openapi/resolve.mjs +19 -3
- package/dist/react/SchemaComponent.d.mts +3 -3
- package/dist/react/SchemaComponent.mjs +1 -30
- package/dist/react/SchemaView.d.mts +4 -3
- package/dist/react/SchemaView.mjs +12 -43
- package/dist/react/headless.d.mts +1 -1
- package/dist/react/headlessRenderers.d.mts +1 -1
- package/dist/react/headlessRenderers.mjs +37 -32
- package/dist/{ref-DvWoULcy.d.mts → ref-Ckt5liZs.d.mts} +1 -1
- package/dist/{renderer-B3s8o2B8.d.mts → renderer-BAGoX4AK.d.mts} +20 -26
- package/dist/themes/mantine.d.mts +1 -1
- package/dist/themes/mantine.mjs +6 -4
- package/dist/themes/mui.d.mts +1 -1
- package/dist/themes/mui.mjs +7 -5
- package/dist/themes/radix.d.mts +1 -1
- package/dist/themes/radix.mjs +5 -4
- package/dist/themes/shadcn.d.mts +1 -1
- package/dist/themes/shadcn.mjs +8 -6
- package/dist/{typeInference-k7FXfTVO.d.mts → typeInference-5JiqIZ8t.d.mts} +57 -4
- package/package.json +1 -1
package/dist/html/renderers.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sortFieldsByOrder } from "../core/fieldOrder.mjs";
|
|
1
2
|
import { h, raw, serialize } from "./html.mjs";
|
|
2
3
|
import { ariaDescribedByAttrs, ariaLabelAttrs, ariaReadonlyAttrs, ariaRequiredAttrs, buildHintElement, buildInputId, requiredIndicator } from "./a11y.mjs";
|
|
3
4
|
//#region src/html/renderers.ts
|
|
@@ -7,11 +8,12 @@ function dateInputType(format) {
|
|
|
7
8
|
if (format === "date-time" || format === "datetime") return "datetime-local";
|
|
8
9
|
}
|
|
9
10
|
/**
|
|
10
|
-
* Normalise a
|
|
11
|
-
* Dots
|
|
11
|
+
* Normalise a structural path into a valid, `sc-` prefixed HTML ID.
|
|
12
|
+
* Dots (object nesting) and brackets (array indices) become hyphens so
|
|
13
|
+
* the id remains a valid CSS selector and predictable in test queries.
|
|
12
14
|
*/
|
|
13
15
|
function fieldId(path) {
|
|
14
|
-
return `sc-${path.replace(
|
|
16
|
+
return `sc-${path.replace(/[.[\]]+/g, "-").replace(/-+$/g, "")}`;
|
|
15
17
|
}
|
|
16
18
|
function renderStringHtml(props) {
|
|
17
19
|
if (props.readOnly) return serialize(renderStringReadOnly(props));
|
|
@@ -134,7 +136,8 @@ function renderEnumEditable(props) {
|
|
|
134
136
|
const enumValue = typeof props.value === "string" ? props.value : "";
|
|
135
137
|
const id = fieldId(props.path);
|
|
136
138
|
const selectedValue = props.writeOnly ? "" : enumValue;
|
|
137
|
-
const
|
|
139
|
+
const enumValues = props.tree.type === "enum" ? props.tree.enumValues : [];
|
|
140
|
+
const optionNodes = [h("option", { value: "" }, "Select…"), ...enumValues.map((v) => {
|
|
138
141
|
const display = v === null ? "null" : typeof v === "string" ? v : String(v);
|
|
139
142
|
const attrs = { value: display };
|
|
140
143
|
if (display === selectedValue) attrs.selected = true;
|
|
@@ -152,22 +155,20 @@ function renderObjectHtml(props) {
|
|
|
152
155
|
return serialize(renderObjectNode(props));
|
|
153
156
|
}
|
|
154
157
|
function renderObjectNode(props) {
|
|
155
|
-
|
|
156
|
-
|
|
158
|
+
if (props.tree.type !== "object") return "";
|
|
159
|
+
const fields = props.tree.fields;
|
|
157
160
|
const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
158
161
|
const obj = isRecord(props.value) ? props.value : {};
|
|
159
162
|
const descriptionText = typeof props.meta.description === "string" ? props.meta.description : void 0;
|
|
160
163
|
const legend = descriptionText !== void 0 ? h("legend", {}, descriptionText) : void 0;
|
|
161
|
-
const sortedEntries =
|
|
162
|
-
return (typeof a[1].meta.order === "number" ? a[1].meta.order : Infinity) - (typeof b[1].meta.order === "number" ? b[1].meta.order : Infinity);
|
|
163
|
-
}).filter(([, field]) => field.meta.visible !== false);
|
|
164
|
+
const sortedEntries = sortFieldsByOrder(fields).filter(([, field]) => field.meta.visible !== false);
|
|
164
165
|
if (props.readOnly) {
|
|
165
166
|
const children = [];
|
|
166
167
|
if (legend !== void 0) children.push(legend);
|
|
167
168
|
for (const [key, field] of sortedEntries) {
|
|
168
169
|
const label = typeof field.meta.description === "string" ? field.meta.description : key;
|
|
169
170
|
const childValue = obj[key];
|
|
170
|
-
const childHtml = props.renderChild(field, childValue,
|
|
171
|
+
const childHtml = props.renderChild(field, childValue, key);
|
|
171
172
|
children.push(h("dt", { class: "sc-label" }, label));
|
|
172
173
|
children.push(h("dd", { class: "sc-value" }, raw(childHtml)));
|
|
173
174
|
}
|
|
@@ -180,9 +181,8 @@ function renderObjectNode(props) {
|
|
|
180
181
|
for (const [key, field] of sortedEntries) {
|
|
181
182
|
const label = typeof field.meta.description === "string" ? field.meta.description : key;
|
|
182
183
|
const fieldId = buildInputId(props.path, key);
|
|
183
|
-
const childPath = props.path ? `${props.path}.${key}` : key;
|
|
184
184
|
const childValue = obj[key];
|
|
185
|
-
const childHtml = props.renderChild(field, childValue,
|
|
185
|
+
const childHtml = props.renderChild(field, childValue, key);
|
|
186
186
|
const required = requiredIndicator(field);
|
|
187
187
|
const labelContent = [label];
|
|
188
188
|
if (required !== void 0) labelContent.push(required);
|
|
@@ -203,24 +203,20 @@ function renderArrayHtml(props) {
|
|
|
203
203
|
}
|
|
204
204
|
function renderArrayNode(props) {
|
|
205
205
|
const arr = Array.isArray(props.value) ? props.value : [];
|
|
206
|
-
const element = props.element;
|
|
206
|
+
const element = props.tree.type === "array" ? props.tree.element : void 0;
|
|
207
207
|
if (element === void 0) return "";
|
|
208
|
-
const
|
|
209
|
-
|
|
210
|
-
});
|
|
211
|
-
if (props.readOnly) return h("ul", { class: "sc-array" }, ...items);
|
|
212
|
-
return h("div", { class: "sc-array" }, ...arr.map((item) => {
|
|
213
|
-
return h("div", {}, raw(props.renderChild(element, item)));
|
|
214
|
-
}));
|
|
208
|
+
const childHtmls = arr.map((item, i) => props.renderChild(element, item, `[${String(i)}]`));
|
|
209
|
+
if (props.readOnly) return h("ul", { class: "sc-array" }, ...childHtmls.map((childHtml) => h("li", { class: "sc-item" }, raw(childHtml))));
|
|
210
|
+
return h("div", { class: "sc-array" }, ...childHtmls.map((childHtml) => h("div", {}, raw(childHtml))));
|
|
215
211
|
}
|
|
216
212
|
function renderRecordHtml(props) {
|
|
217
213
|
return serialize(renderRecordNode(props));
|
|
218
214
|
}
|
|
219
215
|
function renderRecordNode(props) {
|
|
216
|
+
if (props.tree.type !== "record") return "";
|
|
220
217
|
const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
221
218
|
const obj = isRecord(props.value) ? props.value : {};
|
|
222
|
-
const valueType = props.valueType;
|
|
223
|
-
if (valueType === void 0) return "";
|
|
219
|
+
const valueType = props.tree.valueType;
|
|
224
220
|
const attrs = {
|
|
225
221
|
class: "sc-record",
|
|
226
222
|
role: "group"
|
|
@@ -242,12 +238,13 @@ function renderRecordNode(props) {
|
|
|
242
238
|
return h("div", attrs, ...children);
|
|
243
239
|
}
|
|
244
240
|
function renderLiteralHtml(props) {
|
|
245
|
-
|
|
246
|
-
|
|
241
|
+
if (props.tree.type !== "literal") return serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
|
|
242
|
+
const values = props.tree.literalValues;
|
|
243
|
+
if (values.length === 0) return serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
|
|
247
244
|
return serialize(h("span", { class: "sc-value" }, values.map((v) => v === null ? "null" : String(v)).join(", ")));
|
|
248
245
|
}
|
|
249
246
|
function renderUnionHtml(props) {
|
|
250
|
-
const options = props.options;
|
|
247
|
+
const options = props.tree.type === "union" || props.tree.type === "discriminatedUnion" ? props.tree.options : void 0;
|
|
251
248
|
if (options === void 0 || options.length === 0) {
|
|
252
249
|
if (props.value === void 0 || props.value === null) return serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
|
|
253
250
|
return serialize(h("span", { class: "sc-value" }, JSON.stringify(props.value)));
|
|
@@ -259,8 +256,8 @@ function renderUnionHtml(props) {
|
|
|
259
256
|
return serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
|
|
260
257
|
}
|
|
261
258
|
function renderDiscriminatedUnionHtml(props) {
|
|
262
|
-
const options = props.options;
|
|
263
|
-
const discriminator = props.discriminator;
|
|
259
|
+
const options = props.tree.type === "discriminatedUnion" ? props.tree.options : void 0;
|
|
260
|
+
const discriminator = props.tree.type === "discriminatedUnion" ? props.tree.discriminator : void 0;
|
|
264
261
|
if (options === void 0 || options.length === 0) {
|
|
265
262
|
if (props.value === void 0 || props.value === null) return serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
|
|
266
263
|
return serialize(h("span", { class: "sc-value" }, JSON.stringify(props.value)));
|
|
@@ -289,7 +286,9 @@ function renderDiscriminatedUnionHtml(props) {
|
|
|
289
286
|
if (activeOption !== void 0) return props.renderChild(activeOption, props.value);
|
|
290
287
|
return serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
|
|
291
288
|
}
|
|
292
|
-
const
|
|
289
|
+
const baseId = fieldId(props.path);
|
|
290
|
+
const panelId = `${baseId}-panel`;
|
|
291
|
+
const tabId = (i) => `${baseId}-tab-${String(i)}`;
|
|
293
292
|
const children = [h("div", {
|
|
294
293
|
role: "tablist",
|
|
295
294
|
class: "sc-tabs",
|
|
@@ -299,7 +298,7 @@ function renderDiscriminatedUnionHtml(props) {
|
|
|
299
298
|
type: "button",
|
|
300
299
|
role: "tab",
|
|
301
300
|
class: i === activeIndex ? "sc-tab sc-tab--active" : "sc-tab",
|
|
302
|
-
id:
|
|
301
|
+
id: tabId(i),
|
|
303
302
|
"aria-selected": i === activeIndex ? "true" : void 0,
|
|
304
303
|
"aria-controls": panelId,
|
|
305
304
|
tabindex: i === activeIndex ? "0" : "-1"
|
|
@@ -310,7 +309,7 @@ function renderDiscriminatedUnionHtml(props) {
|
|
|
310
309
|
children.push(h("div", {
|
|
311
310
|
role: "tabpanel",
|
|
312
311
|
id: panelId,
|
|
313
|
-
"aria-labelledby":
|
|
312
|
+
"aria-labelledby": tabId(activeIndex)
|
|
314
313
|
}, raw(childHtml)));
|
|
315
314
|
}
|
|
316
315
|
return serialize(h("div", { class: "sc-discriminated-union" }, ...children));
|
|
@@ -335,7 +334,7 @@ function renderFileHtml(props) {
|
|
|
335
334
|
return serialize(h("input", attrs));
|
|
336
335
|
}
|
|
337
336
|
function renderRecursiveHtml(props) {
|
|
338
|
-
const refTarget = props.refTarget
|
|
337
|
+
const refTarget = props.tree.type === "recursive" ? props.tree.refTarget : "";
|
|
339
338
|
return serialize(h("fieldset", { class: "sc-recursive" }, `↻ ${typeof props.meta.description === "string" ? props.meta.description : refTarget} (recursive)`));
|
|
340
339
|
}
|
|
341
340
|
function renderUnknownHtml(props) {
|
|
@@ -354,9 +353,9 @@ function renderUnknownHtml(props) {
|
|
|
354
353
|
return serialize(h("input", attrs));
|
|
355
354
|
}
|
|
356
355
|
function renderTupleHtml(props) {
|
|
356
|
+
if (props.tree.type !== "tuple") return renderUnknownHtml(props);
|
|
357
357
|
const arr = Array.isArray(props.value) ? props.value : [];
|
|
358
|
-
const prefixItems = props.prefixItems;
|
|
359
|
-
if (prefixItems === void 0) return renderUnknownHtml(props);
|
|
358
|
+
const prefixItems = props.tree.prefixItems;
|
|
360
359
|
const children = [];
|
|
361
360
|
for (let i = 0; i < prefixItems.length; i++) {
|
|
362
361
|
const itemValue = arr[i];
|
|
@@ -369,9 +368,11 @@ function renderTupleHtml(props) {
|
|
|
369
368
|
}
|
|
370
369
|
function renderConditionalHtml(props) {
|
|
371
370
|
const children = [];
|
|
372
|
-
if (props.
|
|
373
|
-
|
|
374
|
-
|
|
371
|
+
if (props.tree.type === "conditional") {
|
|
372
|
+
children.push(h("div", { class: "sc-conditional-if" }, raw("if: ...")));
|
|
373
|
+
if (props.tree.thenClause !== void 0) children.push(h("div", { class: "sc-conditional-then" }, raw("then: ...")));
|
|
374
|
+
if (props.tree.elseClause !== void 0) children.push(h("div", { class: "sc-conditional-else" }, raw("else: ...")));
|
|
375
|
+
}
|
|
375
376
|
return serialize(h("div", { class: "sc-conditional" }, ...children));
|
|
376
377
|
}
|
|
377
378
|
function renderNegationHtml(props) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { M as WalkedField } from "../types-D_5ST7SS.mjs";
|
|
2
|
-
import { o as HtmlResolver } from "../renderer-
|
|
2
|
+
import { o as HtmlResolver } from "../renderer-BAGoX4AK.mjs";
|
|
3
3
|
import { HtmlElement } from "./html.mjs";
|
|
4
4
|
|
|
5
5
|
//#region src/html/streamRenderers.d.ts
|
|
@@ -14,27 +14,16 @@ function yieldClose(el) {
|
|
|
14
14
|
}
|
|
15
15
|
function renderLeaf(tree, value, mergedResolver, path) {
|
|
16
16
|
const renderFn = getHtmlRenderFn(tree.type, mergedResolver);
|
|
17
|
-
if (renderFn !== void 0) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
};
|
|
28
|
-
if (tree.type === "enum") props.enumValues = tree.enumValues;
|
|
29
|
-
if (tree.type === "literal") props.literalValues = tree.literalValues;
|
|
30
|
-
if (tree.type === "array" && tree.element !== void 0) props.element = tree.element;
|
|
31
|
-
if (tree.type === "object") props.fields = tree.fields;
|
|
32
|
-
if (tree.type === "union") props.options = tree.options;
|
|
33
|
-
if (tree.type === "discriminatedUnion") props.discriminator = tree.discriminator;
|
|
34
|
-
if (tree.type === "record") props.keyType = tree.keyType;
|
|
35
|
-
if (tree.type === "record") props.valueType = tree.valueType;
|
|
36
|
-
return renderFn(props);
|
|
37
|
-
}
|
|
17
|
+
if (renderFn !== void 0) return renderFn({
|
|
18
|
+
value,
|
|
19
|
+
readOnly: tree.editability === "presentation",
|
|
20
|
+
writeOnly: tree.editability === "input",
|
|
21
|
+
meta: tree.meta,
|
|
22
|
+
constraints: tree.constraints,
|
|
23
|
+
path,
|
|
24
|
+
tree,
|
|
25
|
+
renderChild: () => ""
|
|
26
|
+
});
|
|
38
27
|
if (value === void 0 || value === null) return serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
|
|
39
28
|
return serialize(h("span", { class: "sc-value" }, typeof value === "string" ? value : JSON.stringify(value)));
|
|
40
29
|
}
|
|
@@ -13,7 +13,7 @@ function ApiSecurity({ requirements, schemes }) {
|
|
|
13
13
|
"data-security-name": true,
|
|
14
14
|
children: req.name
|
|
15
15
|
}),
|
|
16
|
-
scheme !== void 0 && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
16
|
+
scheme !== void 0 && /* @__PURE__ */ jsxs(Fragment, { children: [scheme.type !== void 0 && /* @__PURE__ */ jsx("span", {
|
|
17
17
|
"data-security-type": true,
|
|
18
18
|
children: scheme.type
|
|
19
19
|
}), scheme.description && /* @__PURE__ */ jsx("span", {
|
|
@@ -34,13 +34,18 @@ type BundleResolver = (uri: string) => unknown;
|
|
|
34
34
|
*
|
|
35
35
|
* Walks every $ref in the document. For external refs (not starting with `#`),
|
|
36
36
|
* calls the resolver to fetch the external document, extracts the referenced
|
|
37
|
-
* schema, inlines it into `components.schemas`
|
|
38
|
-
* rewrites the $ref to point
|
|
37
|
+
* schema, inlines it into `components.schemas` under a synthesised name, and
|
|
38
|
+
* rewrites the original $ref to point at the new internal location
|
|
39
|
+
* (`#/components/schemas/<name>`).
|
|
40
|
+
*
|
|
41
|
+
* Identical external refs share a single entry — the second occurrence of
|
|
42
|
+
* the same `(uri, fragment)` pair reuses the name produced for the first.
|
|
43
|
+
* Name collisions between different refs are resolved by suffixing a counter.
|
|
39
44
|
*
|
|
40
45
|
* The resolver is called once per unique URI and the result is cached.
|
|
41
46
|
*
|
|
42
|
-
* Returns a deep-cloned document with all external refs
|
|
43
|
-
* The original document is never mutated.
|
|
47
|
+
* Returns a deep-cloned document with all external refs replaced by internal
|
|
48
|
+
* refs. The original document is never mutated.
|
|
44
49
|
*/
|
|
45
50
|
declare function bundleOpenApiDoc(doc: Record<string, unknown>, resolver: BundleResolver): Promise<Record<string, unknown>>;
|
|
46
51
|
//#endregion
|
package/dist/openapi/bundle.mjs
CHANGED
|
@@ -29,28 +29,38 @@ import { isObject } from "../core/guards.mjs";
|
|
|
29
29
|
*
|
|
30
30
|
* Walks every $ref in the document. For external refs (not starting with `#`),
|
|
31
31
|
* calls the resolver to fetch the external document, extracts the referenced
|
|
32
|
-
* schema, inlines it into `components.schemas`
|
|
33
|
-
* rewrites the $ref to point
|
|
32
|
+
* schema, inlines it into `components.schemas` under a synthesised name, and
|
|
33
|
+
* rewrites the original $ref to point at the new internal location
|
|
34
|
+
* (`#/components/schemas/<name>`).
|
|
35
|
+
*
|
|
36
|
+
* Identical external refs share a single entry — the second occurrence of
|
|
37
|
+
* the same `(uri, fragment)` pair reuses the name produced for the first.
|
|
38
|
+
* Name collisions between different refs are resolved by suffixing a counter.
|
|
34
39
|
*
|
|
35
40
|
* The resolver is called once per unique URI and the result is cached.
|
|
36
41
|
*
|
|
37
|
-
* Returns a deep-cloned document with all external refs
|
|
38
|
-
* The original document is never mutated.
|
|
42
|
+
* Returns a deep-cloned document with all external refs replaced by internal
|
|
43
|
+
* refs. The original document is never mutated.
|
|
39
44
|
*/
|
|
40
45
|
async function bundleOpenApiDoc(doc, resolver) {
|
|
41
46
|
const result = structuredClone(doc);
|
|
42
47
|
const uriCache = /* @__PURE__ */ new Map();
|
|
48
|
+
const inlineCache = /* @__PURE__ */ new Map();
|
|
43
49
|
if (!isObject(result.components)) result.components = {};
|
|
44
|
-
|
|
45
|
-
if (isObject(
|
|
46
|
-
|
|
50
|
+
const components = result.components;
|
|
51
|
+
if (!isObject(components)) throw new Error("bundleOpenApiDoc: components is not an object");
|
|
52
|
+
if (!isObject(components.schemas)) components.schemas = {};
|
|
53
|
+
const schemasNode = components.schemas;
|
|
54
|
+
if (!isObject(schemasNode)) throw new Error("bundleOpenApiDoc: components.schemas is not an object");
|
|
55
|
+
await walkAndInline(result, schemasNode, uriCache, inlineCache, resolver);
|
|
47
56
|
return result;
|
|
48
57
|
}
|
|
49
58
|
/**
|
|
50
59
|
* Walk a document tree, find external $ref strings, resolve them,
|
|
51
|
-
* inline the targets
|
|
60
|
+
* inline the targets into `components.schemas`, and rewrite each $ref
|
|
61
|
+
* to point at the new internal location.
|
|
52
62
|
*/
|
|
53
|
-
async function walkAndInline(node, uriCache, resolver) {
|
|
63
|
+
async function walkAndInline(node, schemasNode, uriCache, inlineCache, resolver) {
|
|
54
64
|
if (!isObject(node)) return;
|
|
55
65
|
if (typeof node.$ref === "string" && !node.$ref.startsWith("#")) {
|
|
56
66
|
const ref = node.$ref;
|
|
@@ -66,15 +76,22 @@ async function walkAndInline(node, uriCache, resolver) {
|
|
|
66
76
|
}
|
|
67
77
|
}
|
|
68
78
|
if (externalDoc !== void 0) {
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
79
|
+
const cacheKey = `${uri}${fragment}`;
|
|
80
|
+
let inlinedName = inlineCache.get(cacheKey);
|
|
81
|
+
if (inlinedName === void 0) {
|
|
82
|
+
const target = resolveFragment(externalDoc, fragment);
|
|
83
|
+
if (isObject(target)) {
|
|
84
|
+
inlinedName = registerInline(schemasNode, uri, fragment, target);
|
|
85
|
+
inlineCache.set(cacheKey, inlinedName);
|
|
86
|
+
await walkAndInline(schemasNode[inlinedName], schemasNode, uriCache, inlineCache, resolver);
|
|
87
|
+
}
|
|
73
88
|
}
|
|
89
|
+
if (inlinedName !== void 0) node.$ref = `#/components/schemas/${inlinedName}`;
|
|
74
90
|
}
|
|
91
|
+
return;
|
|
75
92
|
}
|
|
76
|
-
for (const value of Object.values(node)) if (isObject(value)) await walkAndInline(value, uriCache, resolver);
|
|
77
|
-
else if (Array.isArray(value)) for (const item of value) await walkAndInline(item, uriCache, resolver);
|
|
93
|
+
for (const value of Object.values(node)) if (isObject(value)) await walkAndInline(value, schemasNode, uriCache, inlineCache, resolver);
|
|
94
|
+
else if (Array.isArray(value)) for (const item of value) await walkAndInline(item, schemasNode, uriCache, inlineCache, resolver);
|
|
78
95
|
}
|
|
79
96
|
/**
|
|
80
97
|
* Resolve a JSON Pointer fragment within a document.
|
|
@@ -91,5 +108,47 @@ function resolveFragment(doc, fragment) {
|
|
|
91
108
|
}
|
|
92
109
|
return isObject(current) ? current : void 0;
|
|
93
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Derive a candidate identifier for an inlined external schema. Prefers
|
|
113
|
+
* the last meaningful segment of the JSON Pointer fragment; falls back
|
|
114
|
+
* to the URI's filename (sans extension), then to a generic prefix.
|
|
115
|
+
*/
|
|
116
|
+
function deriveCandidateName(uri, fragment) {
|
|
117
|
+
if (fragment.startsWith("#/")) {
|
|
118
|
+
const last = fragment.slice(2).split("/").at(-1);
|
|
119
|
+
if (last !== void 0 && last.length > 0) return sanitiseName(last);
|
|
120
|
+
}
|
|
121
|
+
const pathOnly = uri.split(/[?#]/)[0] ?? uri;
|
|
122
|
+
const lastSlash = pathOnly.lastIndexOf("/");
|
|
123
|
+
const filename = lastSlash >= 0 ? pathOnly.slice(lastSlash + 1) : pathOnly;
|
|
124
|
+
const dot = filename.lastIndexOf(".");
|
|
125
|
+
const stem = dot > 0 ? filename.slice(0, dot) : filename;
|
|
126
|
+
if (stem.length > 0) return sanitiseName(stem);
|
|
127
|
+
return "ExternalSchema";
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Sanitise a string into a JSON Pointer-safe identifier: alphanumerics
|
|
131
|
+
* and underscores only. An empty result falls back to "Schema".
|
|
132
|
+
*/
|
|
133
|
+
function sanitiseName(raw) {
|
|
134
|
+
const cleaned = raw.replace(/[^A-Za-z0-9_]/g, "_");
|
|
135
|
+
return cleaned.length > 0 ? cleaned : "Schema";
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Place the resolved target into `components.schemas` under a unique
|
|
139
|
+
* name derived from the ref, and return the chosen name. Collisions
|
|
140
|
+
* with existing entries are resolved by suffixing a counter.
|
|
141
|
+
*/
|
|
142
|
+
function registerInline(schemasNode, uri, fragment, target) {
|
|
143
|
+
const base = deriveCandidateName(uri, fragment);
|
|
144
|
+
let name = base;
|
|
145
|
+
let counter = 2;
|
|
146
|
+
while (name in schemasNode) {
|
|
147
|
+
name = `${base}_${String(counter)}`;
|
|
148
|
+
counter++;
|
|
149
|
+
}
|
|
150
|
+
schemasNode[name] = structuredClone(target);
|
|
151
|
+
return name;
|
|
152
|
+
}
|
|
94
153
|
//#endregion
|
|
95
154
|
export { bundleOpenApiDoc };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { T as SchemaMeta, u as FieldOverride } from "../types-D_5ST7SS.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { a as InferResponseFields, d as UnsafeFields, i as InferRequestBodyFields, r as InferParameterOverrides } from "../typeInference-5JiqIZ8t.mjs";
|
|
3
3
|
import { WidgetMap } from "../react/SchemaComponent.mjs";
|
|
4
4
|
import { ReactNode } from "react";
|
|
5
5
|
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { toRecordOrUndefined } from "../core/guards.mjs";
|
|
2
|
-
import { SchemaNormalisationError } from "../core/errors.mjs";
|
|
3
|
-
import { normaliseSchema } from "../core/adapter.mjs";
|
|
1
|
+
import { isObject, toRecordOrUndefined } from "../core/guards.mjs";
|
|
4
2
|
import { walk } from "../core/walker.mjs";
|
|
5
3
|
import { ApiCallbacks } from "./ApiCallbacks.mjs";
|
|
6
4
|
import { ApiLinks } from "./ApiLinks.mjs";
|
|
@@ -27,25 +25,17 @@ import { useId } from "react";
|
|
|
27
25
|
*/
|
|
28
26
|
function noop() {}
|
|
29
27
|
function renderSchema(schema, rootDocument, options) {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
try {
|
|
33
|
-
const normalised = normaliseSchema(schema);
|
|
34
|
-
jsonSchema = normalised.jsonSchema;
|
|
35
|
-
rootMeta = normalised.rootMeta;
|
|
36
|
-
} catch (err) {
|
|
37
|
-
throw new SchemaNormalisationError(err instanceof Error ? err.message : "Failed to normalise schema", schema, "unknown");
|
|
38
|
-
}
|
|
28
|
+
if (!isObject(schema)) throw new Error("renderSchema received a non-object schema from the resolver.");
|
|
29
|
+
const rootMeta = extractRootMetaFromSchema(schema);
|
|
39
30
|
const componentMeta = {};
|
|
40
31
|
if (options.readOnly === true) componentMeta.readOnly = true;
|
|
41
32
|
if (options.meta !== void 0) for (const [k, v] of Object.entries(options.meta)) componentMeta[k] = v;
|
|
42
|
-
const
|
|
33
|
+
const tree = walk(schema, {
|
|
43
34
|
componentMeta,
|
|
44
35
|
rootMeta,
|
|
45
36
|
fieldOverrides: toRecordOrUndefined(options.fields),
|
|
46
37
|
rootDocument
|
|
47
|
-
};
|
|
48
|
-
const tree = walk(jsonSchema, walkOpts);
|
|
38
|
+
});
|
|
49
39
|
const makeRenderChild = (parentPath) => (childTree, childValue, childOnChange, pathSuffix) => {
|
|
50
40
|
const childPath = joinPath(parentPath, pathSuffix);
|
|
51
41
|
return renderField(childTree, childValue, childOnChange, void 0, makeRenderChild(childPath), childPath, options.widgets);
|
|
@@ -262,5 +252,20 @@ function buildParamMeta(param, overrides, meta) {
|
|
|
262
252
|
if (meta !== void 0) for (const [k, v] of Object.entries(meta)) result[k] = v;
|
|
263
253
|
return Object.keys(result).length > 0 ? result : void 0;
|
|
264
254
|
}
|
|
255
|
+
/**
|
|
256
|
+
* Extract root-level meta (title, description, readOnly, etc.) from a
|
|
257
|
+
* JSON Schema node. Mirrors `extractRootMetaFromJson` in the adapter so
|
|
258
|
+
* pre-normalised schemas (extracted from `getParsed`) still surface root
|
|
259
|
+
* meta to the walker without an extra adapter round-trip.
|
|
260
|
+
*/
|
|
261
|
+
function extractRootMetaFromSchema(jsonSchema) {
|
|
262
|
+
const meta = {};
|
|
263
|
+
if (jsonSchema.readOnly === true) meta.readOnly = true;
|
|
264
|
+
if (jsonSchema.writeOnly === true) meta.writeOnly = true;
|
|
265
|
+
if (typeof jsonSchema.description === "string") meta.description = jsonSchema.description;
|
|
266
|
+
if (typeof jsonSchema.title === "string") meta.title = jsonSchema.title;
|
|
267
|
+
if (typeof jsonSchema.deprecated === "boolean") meta.deprecated = jsonSchema.deprecated;
|
|
268
|
+
return Object.keys(meta).length > 0 ? meta : void 0;
|
|
269
|
+
}
|
|
265
270
|
//#endregion
|
|
266
271
|
export { ApiOperation, ApiParameters, ApiRequestBody, ApiResponse };
|
|
@@ -41,7 +41,7 @@ interface SecurityRequirement {
|
|
|
41
41
|
scopes: string[];
|
|
42
42
|
}
|
|
43
43
|
interface SecurityScheme {
|
|
44
|
-
type: string;
|
|
44
|
+
type: string | undefined;
|
|
45
45
|
description: string | undefined;
|
|
46
46
|
name: string | undefined;
|
|
47
47
|
location: string | undefined;
|
|
@@ -92,7 +92,7 @@ declare function getRequestBody(parsed: OpenApiDocument, path: string, method: s
|
|
|
92
92
|
declare function getResponses(parsed: OpenApiDocument, path: string, method: string): ResponseInfo[];
|
|
93
93
|
declare function getSecurityRequirements(parsed: OpenApiDocument, path: string, method: string): SecurityRequirement[];
|
|
94
94
|
declare function getSecuritySchemes(parsed: OpenApiDocument): Map<string, SecurityScheme>;
|
|
95
|
-
declare function getResponseHeaders(response: JsonObject): Map<string, HeaderInfo>;
|
|
95
|
+
declare function getResponseHeaders(response: JsonObject, doc?: JsonObject): Map<string, HeaderInfo>;
|
|
96
96
|
declare function listWebhooks(parsed: OpenApiDocument): WebhookInfo[];
|
|
97
97
|
declare function getExternalDocs(obj: JsonObject): ExternalDocs | undefined;
|
|
98
98
|
declare function getXmlInfo(schema: JsonObject): XmlInfo | undefined;
|
package/dist/openapi/parser.mjs
CHANGED
|
@@ -79,22 +79,22 @@ function getParameters(parsed, path, method) {
|
|
|
79
79
|
if (pathItem === void 0) return [];
|
|
80
80
|
const operation = getProperty(pathItem, method);
|
|
81
81
|
if (!isObject(operation)) return [];
|
|
82
|
-
const pathParams = extractParameterList(getProperty(pathItem, "parameters"));
|
|
83
|
-
const opParams = extractParameterList(getProperty(operation, "parameters"));
|
|
82
|
+
const pathParams = extractParameterList(parsed.doc, getProperty(pathItem, "parameters"));
|
|
83
|
+
const opParams = extractParameterList(parsed.doc, getProperty(operation, "parameters"));
|
|
84
84
|
const map = /* @__PURE__ */ new Map();
|
|
85
85
|
for (const param of pathParams) map.set(`${param.name}:${param.location}`, param);
|
|
86
86
|
for (const param of opParams) map.set(`${param.name}:${param.location}`, param);
|
|
87
87
|
return [...map.values()];
|
|
88
88
|
}
|
|
89
|
-
function extractParameterList(parameters) {
|
|
89
|
+
function extractParameterList(doc, parameters) {
|
|
90
90
|
if (!Array.isArray(parameters)) return [];
|
|
91
91
|
const result = [];
|
|
92
92
|
for (const param of parameters) {
|
|
93
93
|
if (!isObject(param)) continue;
|
|
94
|
-
const
|
|
95
|
-
const
|
|
94
|
+
const resolved = resolveParam(doc, param);
|
|
95
|
+
const name = getProperty(resolved, "name");
|
|
96
|
+
const location = getProperty(resolved, "in");
|
|
96
97
|
if (typeof name !== "string" || typeof location !== "string") continue;
|
|
97
|
-
const resolved = resolveParam(param);
|
|
98
98
|
const schema = getProperty(resolved, "schema");
|
|
99
99
|
result.push({
|
|
100
100
|
name,
|
|
@@ -107,10 +107,10 @@ function extractParameterList(parameters) {
|
|
|
107
107
|
}
|
|
108
108
|
return result;
|
|
109
109
|
}
|
|
110
|
-
function resolveParam(param) {
|
|
110
|
+
function resolveParam(doc, param) {
|
|
111
111
|
const ref = getProperty(param, "$ref");
|
|
112
112
|
if (typeof ref === "string" && ref.startsWith("#/")) {
|
|
113
|
-
const resolved = resolveRefInDoc(
|
|
113
|
+
const resolved = resolveRefInDoc(doc, ref);
|
|
114
114
|
if (resolved !== void 0) return resolved;
|
|
115
115
|
}
|
|
116
116
|
return param;
|
|
@@ -143,7 +143,7 @@ function getResponses(parsed, path, method) {
|
|
|
143
143
|
const content = getProperty(response, "content");
|
|
144
144
|
const contentTypes = isObject(content) ? Object.keys(content) : [];
|
|
145
145
|
const schema = isObject(content) ? extractSchemaFromContent(content) : void 0;
|
|
146
|
-
const headers = getResponseHeaders(response);
|
|
146
|
+
const headers = getResponseHeaders(response, parsed.doc);
|
|
147
147
|
result.push({
|
|
148
148
|
statusCode,
|
|
149
149
|
description: getString(response, "description"),
|
|
@@ -196,7 +196,7 @@ function getSecuritySchemes(parsed) {
|
|
|
196
196
|
if (!isObject(scheme)) continue;
|
|
197
197
|
const flowsProp = getProperty(scheme, "flows");
|
|
198
198
|
result.set(name, {
|
|
199
|
-
type: getString(scheme, "type")
|
|
199
|
+
type: getString(scheme, "type"),
|
|
200
200
|
description: getString(scheme, "description"),
|
|
201
201
|
name: getString(scheme, "name"),
|
|
202
202
|
location: getString(scheme, "in"),
|
|
@@ -208,14 +208,14 @@ function getSecuritySchemes(parsed) {
|
|
|
208
208
|
}
|
|
209
209
|
return result;
|
|
210
210
|
}
|
|
211
|
-
function getResponseHeaders(response) {
|
|
211
|
+
function getResponseHeaders(response, doc) {
|
|
212
212
|
const result = /* @__PURE__ */ new Map();
|
|
213
213
|
const headers = getProperty(response, "headers");
|
|
214
214
|
if (!isObject(headers)) return result;
|
|
215
215
|
for (const [name, headerObj] of Object.entries(headers)) {
|
|
216
216
|
if (!isObject(headerObj)) continue;
|
|
217
217
|
const ref = getString(headerObj, "$ref");
|
|
218
|
-
const header = (ref !== void 0 ? resolveRefInDoc(
|
|
218
|
+
const header = (ref !== void 0 && doc !== void 0 ? resolveRefInDoc(doc, ref) : void 0) ?? headerObj;
|
|
219
219
|
const schemaProp = getProperty(header, "schema");
|
|
220
220
|
result.set(name, {
|
|
221
221
|
name,
|
|
@@ -2,8 +2,19 @@ import { OpenApiDocument, OperationInfo, ParameterInfo, ResponseInfo, getRequest
|
|
|
2
2
|
|
|
3
3
|
//#region src/openapi/resolve.d.ts
|
|
4
4
|
/**
|
|
5
|
-
* Parse and cache an OpenAPI document. Returns cached
|
|
6
|
-
*
|
|
5
|
+
* Parse and cache an OpenAPI document. Returns the cached parse for the
|
|
6
|
+
* same object identity.
|
|
7
|
+
*
|
|
8
|
+
* Before parsing, the document is run through the version-aware
|
|
9
|
+
* normalisation pipeline (`normaliseOpenApiSchemas`) so OpenAPI 3.0.x
|
|
10
|
+
* keywords (`nullable`, `discriminator`, `example`) and Swagger 2.0
|
|
11
|
+
* documents are converted to canonical Draft 2020-12 form. The parser
|
|
12
|
+
* and downstream extractors (`getRequestBody`, `getResponses`, etc.) then
|
|
13
|
+
* observe schemas in the same form `<SchemaComponent>` does, keeping the
|
|
14
|
+
* OpenAPI components on the same pipeline as the top-level adapter.
|
|
15
|
+
*
|
|
16
|
+
* The cache is keyed by the caller-supplied document so subsequent calls
|
|
17
|
+
* with the same input bypass both normalisation and parsing.
|
|
7
18
|
*/
|
|
8
19
|
declare function getParsed(doc: Record<string, unknown>): OpenApiDocument;
|
|
9
20
|
/**
|
package/dist/openapi/resolve.mjs
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { isObject } from "../core/guards.mjs";
|
|
2
|
+
import { detectOpenApiVersion } from "../core/version.mjs";
|
|
3
|
+
import { i as normaliseOpenApiSchemas } from "../normalise-tL9FckAk.mjs";
|
|
2
4
|
import { getParameters, getRequestBody, getResponses, listOperations, parseOpenApiDocument } from "./parser.mjs";
|
|
3
5
|
//#region src/openapi/resolve.ts
|
|
4
6
|
/**
|
|
@@ -10,14 +12,28 @@ import { getParameters, getRequestBody, getResponses, listOperations, parseOpenA
|
|
|
10
12
|
*/
|
|
11
13
|
const docCache = /* @__PURE__ */ new WeakMap();
|
|
12
14
|
/**
|
|
13
|
-
* Parse and cache an OpenAPI document. Returns cached
|
|
14
|
-
*
|
|
15
|
+
* Parse and cache an OpenAPI document. Returns the cached parse for the
|
|
16
|
+
* same object identity.
|
|
17
|
+
*
|
|
18
|
+
* Before parsing, the document is run through the version-aware
|
|
19
|
+
* normalisation pipeline (`normaliseOpenApiSchemas`) so OpenAPI 3.0.x
|
|
20
|
+
* keywords (`nullable`, `discriminator`, `example`) and Swagger 2.0
|
|
21
|
+
* documents are converted to canonical Draft 2020-12 form. The parser
|
|
22
|
+
* and downstream extractors (`getRequestBody`, `getResponses`, etc.) then
|
|
23
|
+
* observe schemas in the same form `<SchemaComponent>` does, keeping the
|
|
24
|
+
* OpenAPI components on the same pipeline as the top-level adapter.
|
|
25
|
+
*
|
|
26
|
+
* The cache is keyed by the caller-supplied document so subsequent calls
|
|
27
|
+
* with the same input bypass both normalisation and parsing.
|
|
15
28
|
*/
|
|
16
29
|
function getParsed(doc) {
|
|
17
30
|
const cached = docCache.get(doc);
|
|
18
31
|
if (cached !== void 0) return cached;
|
|
19
|
-
const
|
|
32
|
+
const version = detectOpenApiVersion(doc);
|
|
33
|
+
const normalisedDoc = version !== void 0 ? normaliseOpenApiSchemas(doc, version) : doc;
|
|
34
|
+
const parsed = parseOpenApiDocument(normalisedDoc);
|
|
20
35
|
docCache.set(doc, parsed);
|
|
36
|
+
if (normalisedDoc !== doc) docCache.set(normalisedDoc, parsed);
|
|
21
37
|
return parsed;
|
|
22
38
|
}
|
|
23
39
|
/**
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { M as WalkedField, T as SchemaMeta, d as FieldOverrides, u as FieldOverride } from "../types-D_5ST7SS.mjs";
|
|
2
|
-
import { t as Diagnostic } from "../diagnostics-
|
|
2
|
+
import { t as Diagnostic } from "../diagnostics-BYk63jsC.mjs";
|
|
3
3
|
import { t as SchemaError } from "../errors-C5zRC2PU.mjs";
|
|
4
|
-
import { l as RenderProps, r as ComponentResolver } from "../renderer-
|
|
5
|
-
import { c as
|
|
4
|
+
import { l as RenderProps, r as ComponentResolver } from "../renderer-BAGoX4AK.mjs";
|
|
5
|
+
import { c as PathOfType, l as ResolveOpenAPIRef, n as FromJSONSchema } from "../typeInference-5JiqIZ8t.mjs";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import * as _$react_jsx_runtime0 from "react/jsx-runtime";
|
|
8
8
|
import { ReactNode } from "react";
|