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
@@ -1,7 +1,10 @@
1
1
  import { isObject } from "../core/guards.mjs";
2
+ import "../core/limits.mjs";
3
+ import { emitDiagnostic } from "../core/diagnostics.mjs";
2
4
  import { getHtmlRenderFn } from "../core/renderer.mjs";
3
5
  import { VOID_ELEMENTS, h, raw, serialize, serializeAttributes } from "./html.mjs";
4
- import { ariaLabelAttrs, buildHintElement, buildInputId, requiredIndicator } from "./a11y.mjs";
6
+ import { ariaLabelAttrs, buildHintElement, buildInputId, joinPath, requiredIndicator } from "./a11y.mjs";
7
+ import { recursionSentinelHtml } from "./renderToHtml.mjs";
5
8
  //#region src/html/streamRenderers.ts
6
9
  function yieldOpen(el) {
7
10
  const attrStr = serializeAttributes(el.attributes);
@@ -27,8 +30,8 @@ function renderLeaf(tree, value, mergedResolver, path) {
27
30
  if (value === void 0 || value === null) return serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
28
31
  return serialize(h("span", { class: "sc-value" }, typeof value === "string" ? value : JSON.stringify(value)));
29
32
  }
30
- function renderFieldSync(tree, value, mergedResolver, path, rawResolver) {
31
- return [...streamField(tree, value, mergedResolver, path, rawResolver)].join("");
33
+ function renderFieldSync(tree, value, mergedResolver, path, rawResolver, currentDepth, diagnostics) {
34
+ return [...streamField(tree, value, mergedResolver, path, rawResolver, currentDepth, diagnostics)].join("");
32
35
  }
33
36
  function matchUnionOption(options, value) {
34
37
  if (typeof value === "string") return options.find((o) => o.type === "string" || o.type === "enum");
@@ -37,7 +40,26 @@ function matchUnionOption(options, value) {
37
40
  if (Array.isArray(value)) return options.find((o) => o.type === "array");
38
41
  if (typeof value === "object" && value !== null) return options.find((o) => o.type === "object");
39
42
  }
40
- function* streamField(tree, value, mergedResolver, path, rawResolver) {
43
+ /**
44
+ * Build a visible placeholder element used when a value does not match
45
+ * the shape implied by its field type. The expected-shape label is
46
+ * passed verbatim into `h()` so the serialiser escapes it.
47
+ *
48
+ * Streaming must keep producing output, so we never throw here — the
49
+ * diagnostic surfaces the problem to the caller (when a sink is wired)
50
+ * while the rendered output remains structurally valid.
51
+ */
52
+ function typeMismatchPlaceholder(expectedShape) {
53
+ return serialize(h("span", {
54
+ class: "sc-value sc-value--invalid",
55
+ role: "alert"
56
+ }, `invalid value (expected ${expectedShape})`));
57
+ }
58
+ function* streamField(tree, value, mergedResolver, path, rawResolver, currentDepth = 0, diagnostics) {
59
+ if (currentDepth >= 10) {
60
+ yield recursionSentinelHtml(typeof tree.meta.description === "string" ? tree.meta.description : "schema");
61
+ return;
62
+ }
41
63
  const effectiveValue = value ?? tree.defaultValue;
42
64
  const type = tree.type;
43
65
  if (type === "string" || type === "number" || type === "boolean" || type === "enum" || type === "literal" || type === "file" || type === "unknown") {
@@ -45,30 +67,44 @@ function* streamField(tree, value, mergedResolver, path, rawResolver) {
45
67
  return;
46
68
  }
47
69
  if (type === "union") {
48
- yield* streamUnion(tree, effectiveValue, mergedResolver, path, rawResolver);
70
+ yield* streamUnion(tree, effectiveValue, mergedResolver, path, rawResolver, currentDepth, diagnostics);
49
71
  return;
50
72
  }
51
73
  if (type === "discriminatedUnion") {
52
- yield* streamDiscriminatedUnion(tree, value, mergedResolver, path, rawResolver);
74
+ yield* streamDiscriminatedUnion(tree, value, mergedResolver, path, rawResolver, currentDepth, diagnostics);
53
75
  return;
54
76
  }
55
77
  if (type === "object") {
56
- yield* streamObject(tree, value, mergedResolver, path, rawResolver);
78
+ yield* streamObject(tree, value, mergedResolver, path, rawResolver, currentDepth, diagnostics);
57
79
  return;
58
80
  }
59
81
  if (type === "array") {
60
- yield* streamArray(tree, value, mergedResolver, path, rawResolver);
82
+ yield* streamArray(tree, value, mergedResolver, path, rawResolver, currentDepth, diagnostics);
61
83
  return;
62
84
  }
63
85
  if (type === "record") {
64
- yield* streamRecord(tree, value, mergedResolver, path, rawResolver);
86
+ yield* streamRecord(tree, value, mergedResolver, path, rawResolver, currentDepth, diagnostics);
65
87
  return;
66
88
  }
67
89
  yield renderLeaf(tree, value, mergedResolver, path);
68
90
  }
69
- function* streamObject(tree, value, mergedResolver, path, rawResolver) {
91
+ function* streamObject(tree, value, mergedResolver, path, rawResolver, currentDepth, diagnostics) {
70
92
  if (tree.type !== "object") return;
71
93
  const fields = tree.fields;
94
+ if (value !== void 0 && value !== null && !isObject(value)) {
95
+ emitDiagnostic(diagnostics, {
96
+ code: "type-mismatch",
97
+ message: "Object schema received non-object value during streaming render",
98
+ pointer: path === "" ? "/" : `/${path}`,
99
+ detail: {
100
+ expected: "object",
101
+ actualType: typeof value,
102
+ path
103
+ }
104
+ });
105
+ yield typeMismatchPlaceholder("object");
106
+ return;
107
+ }
72
108
  const obj = isObject(value) ? value : {};
73
109
  const readOnly = tree.editability === "presentation";
74
110
  const descriptionText = typeof tree.meta.description === "string" ? tree.meta.description : void 0;
@@ -82,7 +118,7 @@ function* streamObject(tree, value, mergedResolver, path, rawResolver) {
82
118
  for (const [key, field] of Object.entries(fields)) {
83
119
  const label = typeof field.meta.description === "string" ? field.meta.description : key;
84
120
  const childValue = obj[key];
85
- const childHtml = renderFieldSync(field, childValue, mergedResolver, key, rawResolver);
121
+ const childHtml = renderFieldSync(field, childValue, mergedResolver, joinPath(path, key), rawResolver, currentDepth + 1, diagnostics);
86
122
  yield `${serialize(h("dt", { class: "sc-label" }, label))}${serialize(h("dd", { class: "sc-value" }, raw(childHtml)))}`;
87
123
  }
88
124
  yield yieldClose(dl);
@@ -96,7 +132,7 @@ function* streamObject(tree, value, mergedResolver, path, rawResolver) {
96
132
  const label = typeof field.meta.description === "string" ? field.meta.description : key;
97
133
  const fieldId = buildInputId(path, key);
98
134
  const childValue = obj[key];
99
- const childChunks = [...streamField(field, childValue, mergedResolver, key, rawResolver)].join("");
135
+ const childChunks = [...streamField(field, childValue, mergedResolver, joinPath(path, key), rawResolver, currentDepth + 1, diagnostics)].join("");
100
136
  const required = requiredIndicator(field);
101
137
  const labelContent = [label];
102
138
  if (required !== void 0) labelContent.push(required);
@@ -111,29 +147,55 @@ function* streamObject(tree, value, mergedResolver, path, rawResolver) {
111
147
  yield yieldClose(fieldset);
112
148
  }
113
149
  }
114
- function* streamArray(tree, value, mergedResolver, path, rawResolver) {
115
- const arr = Array.isArray(value) ? value : [];
150
+ function* streamArray(tree, value, mergedResolver, path, rawResolver, currentDepth, diagnostics) {
116
151
  if (tree.type !== "array") return;
117
152
  const element = tree.element;
118
153
  if (element === void 0) return;
119
- const readOnly = tree.editability === "presentation";
120
- const elementPath = typeof element.meta.description === "string" ? element.meta.description : "";
121
- if (readOnly) {
154
+ if (value !== void 0 && value !== null && !Array.isArray(value)) {
155
+ emitDiagnostic(diagnostics, {
156
+ code: "type-mismatch",
157
+ message: "Array schema received non-array value during streaming render",
158
+ pointer: path === "" ? "/" : `/${path}`,
159
+ detail: {
160
+ expected: "array",
161
+ actualType: typeof value,
162
+ path
163
+ }
164
+ });
165
+ yield typeMismatchPlaceholder("array");
166
+ return;
167
+ }
168
+ const arr = Array.isArray(value) ? value : [];
169
+ if (tree.editability === "presentation") {
122
170
  const ul = h("ul", { class: "sc-array" });
123
171
  yield yieldOpen(ul);
124
- for (const item of arr) yield serialize(h("li", { class: "sc-item" }, raw(renderFieldSync(element, item, mergedResolver, elementPath, rawResolver))));
172
+ for (const [i, item] of arr.entries()) yield serialize(h("li", { class: "sc-item" }, raw(renderFieldSync(element, item, mergedResolver, joinPath(path, `[${String(i)}]`), rawResolver, currentDepth + 1, diagnostics))));
125
173
  yield yieldClose(ul);
126
174
  } else {
127
175
  const div = h("div", { class: "sc-array" });
128
176
  yield yieldOpen(div);
129
- for (const item of arr) yield serialize(h("div", {}, raw(renderFieldSync(element, item, mergedResolver, elementPath, rawResolver))));
177
+ for (const [i, item] of arr.entries()) yield serialize(h("div", {}, raw(renderFieldSync(element, item, mergedResolver, joinPath(path, `[${String(i)}]`), rawResolver, currentDepth + 1, diagnostics))));
130
178
  yield yieldClose(div);
131
179
  }
132
180
  }
133
- function* streamRecord(tree, value, mergedResolver, path, rawResolver) {
134
- const obj = isObject(value) ? value : {};
181
+ function* streamRecord(tree, value, mergedResolver, path, rawResolver, currentDepth, diagnostics) {
135
182
  if (tree.type !== "record") return;
136
183
  const valueType = tree.valueType;
184
+ if (value !== void 0 && value !== null && !isObject(value)) {
185
+ emitDiagnostic(diagnostics, {
186
+ code: "type-mismatch",
187
+ message: "Record schema received non-object value during streaming render",
188
+ pointer: path === "" ? "/" : `/${path}`,
189
+ detail: {
190
+ expected: "object",
191
+ actualType: typeof value,
192
+ path
193
+ }
194
+ });
195
+ yield typeMismatchPlaceholder("object");
196
+ return;
197
+ }
198
+ const obj = isObject(value) ? value : {};
137
199
  const readOnly = tree.editability === "presentation";
138
200
  const attrs = {
139
201
  class: "sc-record",
@@ -143,7 +205,7 @@ function* streamRecord(tree, value, mergedResolver, path, rawResolver) {
143
205
  const dl = h("dl", attrs);
144
206
  yield yieldOpen(dl);
145
207
  for (const [key, val] of Object.entries(obj)) {
146
- const childHtml = renderFieldSync(valueType, val, mergedResolver, key, rawResolver);
208
+ const childHtml = renderFieldSync(valueType, val, mergedResolver, joinPath(path, key), rawResolver, currentDepth + 1, diagnostics);
147
209
  yield `${serialize(h("dt", { class: "sc-label" }, key))}${serialize(h("dd", { class: "sc-value" }, raw(childHtml)))}`;
148
210
  }
149
211
  yield yieldClose(dl);
@@ -151,13 +213,13 @@ function* streamRecord(tree, value, mergedResolver, path, rawResolver) {
151
213
  const container = h("div", attrs);
152
214
  yield yieldOpen(container);
153
215
  for (const [key, val] of Object.entries(obj)) {
154
- const childHtml = renderFieldSync(valueType, val, mergedResolver, key, rawResolver);
216
+ const childHtml = renderFieldSync(valueType, val, mergedResolver, joinPath(path, key), rawResolver, currentDepth + 1, diagnostics);
155
217
  yield serialize(h("div", { class: "sc-field" }, h("label", { class: "sc-label" }, key), raw(childHtml)));
156
218
  }
157
219
  yield yieldClose(container);
158
220
  }
159
221
  }
160
- function* streamUnion(tree, value, mergedResolver, path, rawResolver) {
222
+ function* streamUnion(tree, value, mergedResolver, path, rawResolver, currentDepth, diagnostics) {
161
223
  const options = tree.type === "union" ? tree.options : void 0;
162
224
  if (options === void 0 || options.length === 0) {
163
225
  if (value === void 0 || value === null) yield serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
@@ -165,10 +227,10 @@ function* streamUnion(tree, value, mergedResolver, path, rawResolver) {
165
227
  return;
166
228
  }
167
229
  const target = matchUnionOption(options, value) ?? options[0];
168
- if (target !== void 0) yield* streamField(target, value, mergedResolver, typeof target.meta.description === "string" ? target.meta.description : "", rawResolver);
230
+ if (target !== void 0) yield* streamField(target, value, mergedResolver, path, rawResolver, currentDepth + 1, diagnostics);
169
231
  else yield serialize(h("span", { class: "sc-value sc-value--empty" }, "—"));
170
232
  }
171
- function* streamDiscriminatedUnion(tree, value, mergedResolver, path, rawResolver) {
233
+ function* streamDiscriminatedUnion(tree, value, mergedResolver, path, rawResolver, currentDepth, diagnostics) {
172
234
  const options = tree.type === "discriminatedUnion" ? tree.options : void 0;
173
235
  const discriminator = tree.type === "discriminatedUnion" ? tree.discriminator : void 0;
174
236
  if (options === void 0 || options.length === 0) {
@@ -176,8 +238,7 @@ function* streamDiscriminatedUnion(tree, value, mergedResolver, path, rawResolve
176
238
  else yield serialize(h("span", { class: "sc-value" }, JSON.stringify(value)));
177
239
  return;
178
240
  }
179
- const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
180
- const obj = isRecord(value) ? value : {};
241
+ const obj = isObject(value) ? value : {};
181
242
  const discKey = discriminator ?? "";
182
243
  const currentDiscriminatorValue = typeof obj[discKey] === "string" ? obj[discKey] : void 0;
183
244
  const optionLabels = options.map((opt) => {
@@ -197,7 +258,7 @@ function* streamDiscriminatedUnion(tree, value, mergedResolver, path, rawResolve
197
258
  }
198
259
  const activeOption = options[activeIndex];
199
260
  if (tree.editability === "presentation") {
200
- if (activeOption !== void 0) yield* streamField(activeOption, value, mergedResolver, typeof activeOption.meta.description === "string" ? activeOption.meta.description : "", rawResolver);
261
+ if (activeOption !== void 0) yield* streamField(activeOption, value, mergedResolver, path, rawResolver, currentDepth + 1, diagnostics);
201
262
  return;
202
263
  }
203
264
  const panelId = `sc-${path}-panel`;
@@ -224,7 +285,7 @@ function* streamDiscriminatedUnion(tree, value, mergedResolver, path, rawResolve
224
285
  "aria-labelledby": `sc-${path}-tab-${String(activeIndex)}`
225
286
  });
226
287
  yield yieldOpen(panelOpen);
227
- if (activeOption !== void 0) yield* streamField(activeOption, value, mergedResolver, typeof activeOption.meta.description === "string" ? activeOption.meta.description : "", rawResolver);
288
+ if (activeOption !== void 0) yield* streamField(activeOption, value, mergedResolver, path, rawResolver, currentDepth + 1, diagnostics);
228
289
  yield yieldClose(panelOpen);
229
290
  yield yieldClose(wrapper);
230
291
  }
@@ -0,0 +1,29 @@
1
+ //#region src/core/limits.d.ts
2
+ /**
3
+ * Shared depth caps and hop counts used to bound recursion across
4
+ * schema-components. All numeric limits live here so the renderer, the
5
+ * ref resolver, the OpenAPI parser, and the type-level inference engine
6
+ * agree on the same constants.
7
+ */
8
+ /**
9
+ * Maximum recursion depth for the schema walker, the React renderers,
10
+ * the streaming HTML renderer, and the server-side renderer. Beyond
11
+ * this depth a recursion sentinel is emitted instead of further descent
12
+ * — the only safe response to a cyclic walked-field graph.
13
+ */
14
+ declare const MAX_RENDER_DEPTH = 10;
15
+ /**
16
+ * Maximum depth for `$ref` resolution and Zod-tree walks. Mirrors the
17
+ * type-level `DEFAULT_MAX_DEPTH` ({@link MaxRefDepth}) so the runtime
18
+ * and compile-time bounds agree.
19
+ */
20
+ type MaxRefDepth = 64;
21
+ declare const MAX_REF_DEPTH: MaxRefDepth;
22
+ /**
23
+ * Maximum number of `$ref` hops permitted when walking a chain of
24
+ * OpenAPI Path Item Object references. Beyond this a
25
+ * `path-item-ref-too-deep` diagnostic is emitted and resolution stops.
26
+ */
27
+ declare const MAX_PATH_ITEM_REF_HOPS = 8;
28
+ //#endregion
29
+ export { MaxRefDepth as i, MAX_REF_DEPTH as n, MAX_RENDER_DEPTH as r, MAX_PATH_ITEM_REF_HOPS as t };