schema-components 1.12.11 → 1.14.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 (83) hide show
  1. package/README.md +35 -0
  2. package/dist/core/adapter.d.mts +8 -3
  3. package/dist/core/adapter.mjs +58 -14
  4. package/dist/core/constraints.d.mts +17 -0
  5. package/dist/core/constraints.mjs +150 -0
  6. package/dist/core/diagnostics.d.mts +2 -0
  7. package/dist/core/diagnostics.mjs +33 -0
  8. package/dist/core/errors.d.mts +1 -1
  9. package/dist/core/formats.d.mts +33 -0
  10. package/dist/core/formats.mjs +67 -0
  11. package/dist/core/merge.d.mts +48 -0
  12. package/dist/core/merge.mjs +125 -0
  13. package/dist/core/normalise.d.mts +41 -0
  14. package/dist/core/normalise.mjs +2 -0
  15. package/dist/core/openapi30.d.mts +41 -0
  16. package/dist/core/openapi30.mjs +2 -0
  17. package/dist/core/ref.d.mts +2 -0
  18. package/dist/core/ref.mjs +165 -0
  19. package/dist/core/renderer.d.mts +2 -2
  20. package/dist/core/renderer.mjs +8 -0
  21. package/dist/core/swagger2.d.mts +11 -0
  22. package/dist/core/swagger2.mjs +2 -0
  23. package/dist/core/typeInference.d.mts +2 -0
  24. package/dist/core/typeInference.mjs +1 -0
  25. package/dist/core/types.d.mts +2 -3
  26. package/dist/core/types.mjs +58 -2
  27. package/dist/core/version.d.mts +2 -0
  28. package/dist/core/version.mjs +151 -0
  29. package/dist/core/walkBuilders.d.mts +66 -0
  30. package/dist/core/walkBuilders.mjs +152 -0
  31. package/dist/core/walker.d.mts +3 -10
  32. package/dist/core/walker.mjs +245 -233
  33. package/dist/diagnostics-DzbZmcLI.d.mts +64 -0
  34. package/dist/html/a11y.d.mts +5 -4
  35. package/dist/html/renderToHtml.d.mts +3 -3
  36. package/dist/html/renderToHtml.mjs +23 -379
  37. package/dist/html/renderToHtmlStream.d.mts +29 -47
  38. package/dist/html/renderToHtmlStream.mjs +33 -305
  39. package/dist/html/renderers.d.mts +14 -0
  40. package/dist/html/renderers.mjs +406 -0
  41. package/dist/html/streamRenderers.d.mts +13 -0
  42. package/dist/html/streamRenderers.mjs +243 -0
  43. package/dist/normalise-tL9FckAk.mjs +748 -0
  44. package/dist/openapi/ApiCallbacks.d.mts +16 -0
  45. package/dist/openapi/ApiCallbacks.mjs +34 -0
  46. package/dist/openapi/ApiLinks.d.mts +16 -0
  47. package/dist/openapi/ApiLinks.mjs +42 -0
  48. package/dist/openapi/ApiResponseHeaders.d.mts +16 -0
  49. package/dist/openapi/ApiResponseHeaders.mjs +35 -0
  50. package/dist/openapi/ApiSecurity.d.mts +19 -0
  51. package/dist/openapi/ApiSecurity.mjs +33 -0
  52. package/dist/openapi/bundle.d.mts +47 -0
  53. package/dist/openapi/bundle.mjs +95 -0
  54. package/dist/openapi/components.d.mts +7 -1
  55. package/dist/openapi/components.mjs +30 -6
  56. package/dist/openapi/parser.d.mts +59 -2
  57. package/dist/openapi/parser.mjs +189 -8
  58. package/dist/react/SchemaComponent.d.mts +13 -4
  59. package/dist/react/SchemaComponent.mjs +51 -91
  60. package/dist/react/SchemaView.d.mts +10 -2
  61. package/dist/react/SchemaView.mjs +33 -15
  62. package/dist/react/fieldPath.d.mts +20 -0
  63. package/dist/react/fieldPath.mjs +81 -0
  64. package/dist/react/headless.d.mts +2 -4
  65. package/dist/react/headless.mjs +3 -492
  66. package/dist/react/headlessRenderers.d.mts +23 -0
  67. package/dist/react/headlessRenderers.mjs +507 -0
  68. package/dist/ref-DvWoULcy.d.mts +44 -0
  69. package/dist/renderer-BdSqllx5.d.mts +160 -0
  70. package/dist/themes/mantine.d.mts +1 -1
  71. package/dist/themes/mantine.mjs +2 -1
  72. package/dist/themes/mui.d.mts +1 -1
  73. package/dist/themes/mui.mjs +3 -2
  74. package/dist/themes/radix.d.mts +1 -1
  75. package/dist/themes/radix.mjs +2 -1
  76. package/dist/themes/shadcn.d.mts +1 -1
  77. package/dist/themes/shadcn.mjs +10 -6
  78. package/dist/typeInference-k7FXfTVO.d.mts +335 -0
  79. package/dist/types-D_5ST7SS.d.mts +269 -0
  80. package/dist/version-B5NV-35j.d.mts +69 -0
  81. package/package.json +1 -1
  82. package/dist/types-BJzEgJdX.d.mts +0 -335
  83. /package/dist/{errors-DIKI2C78.d.mts → errors-C5zRC2PU.d.mts} +0 -0
@@ -35,12 +35,29 @@ const METHODS = [
35
35
  "patch",
36
36
  "delete"
37
37
  ];
38
+ /**
39
+ * Resolve a path item, following a `$ref` to `components/pathItems/<Name>`
40
+ * (OpenAPI 3.1) if present. Returns `undefined` when the value is not a
41
+ * path item, the ref is malformed, or the target does not resolve.
42
+ */
43
+ function resolvePathItem(parsed, pathItem) {
44
+ if (!isObject(pathItem)) return void 0;
45
+ const ref = getString(pathItem, "$ref");
46
+ if (ref === void 0) return pathItem;
47
+ return resolveRefInDoc(parsed.doc, ref) ?? void 0;
48
+ }
49
+ function lookupPathItem(parsed, path) {
50
+ const resolved = resolvePathItem(parsed, getProperty(getProperty(parsed.doc, "paths"), path));
51
+ if (resolved !== void 0) return resolved;
52
+ return resolvePathItem(parsed, getProperty(getProperty(parsed.doc, "webhooks"), path));
53
+ }
38
54
  function listOperations(parsed) {
39
55
  const operations = [];
40
56
  const paths = getProperty(parsed.doc, "paths");
41
57
  if (!isObject(paths)) return operations;
42
- for (const [path, pathItem] of Object.entries(paths)) {
43
- if (!isObject(pathItem)) continue;
58
+ for (const [path, rawPathItem] of Object.entries(paths)) {
59
+ const pathItem = resolvePathItem(parsed, rawPathItem);
60
+ if (pathItem === void 0) continue;
44
61
  for (const method of METHODS) {
45
62
  const operation = getProperty(pathItem, method);
46
63
  if (!isObject(operation)) continue;
@@ -58,8 +75,8 @@ function listOperations(parsed) {
58
75
  return operations;
59
76
  }
60
77
  function getParameters(parsed, path, method) {
61
- const pathItem = getProperty(getProperty(parsed.doc, "paths"), path);
62
- if (!isObject(pathItem)) return [];
78
+ const pathItem = lookupPathItem(parsed, path);
79
+ if (pathItem === void 0) return [];
63
80
  const operation = getProperty(pathItem, method);
64
81
  if (!isObject(operation)) return [];
65
82
  const pathParams = extractParameterList(getProperty(pathItem, "parameters"));
@@ -99,7 +116,7 @@ function resolveParam(param) {
99
116
  return param;
100
117
  }
101
118
  function getRequestBody(parsed, path, method) {
102
- const requestBody = getProperty(getProperty(getProperty(getProperty(parsed.doc, "paths"), path), method), "requestBody");
119
+ const requestBody = getProperty(getProperty(lookupPathItem(parsed, path), method), "requestBody");
103
120
  if (!isObject(requestBody)) return void 0;
104
121
  const content = getProperty(requestBody, "content");
105
122
  if (!isObject(content)) return {
@@ -118,7 +135,7 @@ function getRequestBody(parsed, path, method) {
118
135
  };
119
136
  }
120
137
  function getResponses(parsed, path, method) {
121
- const responses = getProperty(getProperty(getProperty(getProperty(parsed.doc, "paths"), path), method), "responses");
138
+ const responses = getProperty(getProperty(lookupPathItem(parsed, path), method), "responses");
122
139
  if (!isObject(responses)) return [];
123
140
  const result = [];
124
141
  for (const [statusCode, response] of Object.entries(responses)) {
@@ -126,11 +143,13 @@ function getResponses(parsed, path, method) {
126
143
  const content = getProperty(response, "content");
127
144
  const contentTypes = isObject(content) ? Object.keys(content) : [];
128
145
  const schema = isObject(content) ? extractSchemaFromContent(content) : void 0;
146
+ const headers = getResponseHeaders(response);
129
147
  result.push({
130
148
  statusCode,
131
149
  description: getString(response, "description"),
132
150
  contentTypes,
133
- schema
151
+ schema,
152
+ headers
134
153
  });
135
154
  }
136
155
  return result;
@@ -155,5 +174,167 @@ function resolveRefInDoc(doc, ref) {
155
174
  }
156
175
  return isObject(current) ? current : void 0;
157
176
  }
177
+ function getSecurityRequirements(parsed, path, method) {
178
+ const opSecurity = getProperty(getProperty(lookupPathItem(parsed, path), method), "security");
179
+ const globalSecurity = getProperty(parsed.doc, "security");
180
+ const securityArray = Array.isArray(opSecurity) ? opSecurity : Array.isArray(globalSecurity) ? globalSecurity : [];
181
+ const result = [];
182
+ for (const entry of securityArray) {
183
+ if (!isObject(entry)) continue;
184
+ for (const [name, scopes] of Object.entries(entry)) result.push({
185
+ name,
186
+ scopes: Array.isArray(scopes) ? scopes.filter((s) => typeof s === "string") : []
187
+ });
188
+ }
189
+ return result;
190
+ }
191
+ function getSecuritySchemes(parsed) {
192
+ const result = /* @__PURE__ */ new Map();
193
+ const securitySchemes = getProperty(getProperty(parsed.doc, "components"), "securitySchemes");
194
+ if (!isObject(securitySchemes)) return result;
195
+ for (const [name, scheme] of Object.entries(securitySchemes)) {
196
+ if (!isObject(scheme)) continue;
197
+ const flowsProp = getProperty(scheme, "flows");
198
+ result.set(name, {
199
+ type: getString(scheme, "type") ?? "",
200
+ description: getString(scheme, "description"),
201
+ name: getString(scheme, "name"),
202
+ location: getString(scheme, "in"),
203
+ scheme: getString(scheme, "scheme"),
204
+ bearerFormat: getString(scheme, "bearerFormat"),
205
+ flows: isObject(flowsProp) ? flowsProp : void 0,
206
+ openIdConnectUrl: getString(scheme, "openIdConnectUrl")
207
+ });
208
+ }
209
+ return result;
210
+ }
211
+ function getResponseHeaders(response) {
212
+ const result = /* @__PURE__ */ new Map();
213
+ const headers = getProperty(response, "headers");
214
+ if (!isObject(headers)) return result;
215
+ for (const [name, headerObj] of Object.entries(headers)) {
216
+ if (!isObject(headerObj)) continue;
217
+ const ref = getString(headerObj, "$ref");
218
+ const header = (ref !== void 0 ? resolveRefInDoc(headerObj, ref) : void 0) ?? headerObj;
219
+ const schemaProp = getProperty(header, "schema");
220
+ result.set(name, {
221
+ name,
222
+ description: getString(header, "description"),
223
+ required: getProperty(header, "required") === true,
224
+ deprecated: getProperty(header, "deprecated") === true,
225
+ schema: isObject(schemaProp) ? schemaProp : void 0
226
+ });
227
+ }
228
+ return result;
229
+ }
230
+ function listWebhooks(parsed) {
231
+ const result = [];
232
+ const webhooks = getProperty(parsed.doc, "webhooks");
233
+ if (!isObject(webhooks)) return result;
234
+ for (const [name, hookItem] of Object.entries(webhooks)) {
235
+ if (!isObject(hookItem)) continue;
236
+ const operations = [];
237
+ for (const method of METHODS) {
238
+ const operation = getProperty(hookItem, method);
239
+ if (!isObject(operation)) continue;
240
+ operations.push({
241
+ path: name,
242
+ method,
243
+ operationId: getString(operation, "operationId"),
244
+ summary: getString(operation, "summary"),
245
+ description: getString(operation, "description"),
246
+ deprecated: getProperty(operation, "deprecated") === true,
247
+ operation
248
+ });
249
+ }
250
+ result.push({
251
+ name,
252
+ operations
253
+ });
254
+ }
255
+ return result;
256
+ }
257
+ function getExternalDocs(obj) {
258
+ const docs = getProperty(obj, "externalDocs");
259
+ if (!isObject(docs)) return void 0;
260
+ const url = getString(docs, "url");
261
+ if (typeof url !== "string") return void 0;
262
+ return {
263
+ url,
264
+ description: getString(docs, "description")
265
+ };
266
+ }
267
+ function getXmlInfo(schema) {
268
+ const xml = getProperty(schema, "xml");
269
+ if (!isObject(xml)) return void 0;
270
+ return {
271
+ name: getString(xml, "name"),
272
+ namespace: getString(xml, "namespace"),
273
+ prefix: getString(xml, "prefix"),
274
+ attribute: getProperty(xml, "attribute") === true,
275
+ wrapped: getProperty(xml, "wrapped") === true
276
+ };
277
+ }
278
+ function listCallbacks(parsed, path, method) {
279
+ const operation = getProperty(lookupPathItem(parsed, path), method);
280
+ if (!isObject(operation)) return [];
281
+ const callbacks = getProperty(operation, "callbacks");
282
+ if (!isObject(callbacks)) return [];
283
+ const result = [];
284
+ for (const [name, callbackItem] of Object.entries(callbacks)) {
285
+ if (!isObject(callbackItem)) continue;
286
+ const operations = [];
287
+ for (const [cbPath, cbPathItem] of Object.entries(callbackItem)) {
288
+ if (!isObject(cbPathItem)) continue;
289
+ for (const cbMethod of METHODS) {
290
+ const cbOp = getProperty(cbPathItem, cbMethod);
291
+ if (!isObject(cbOp)) continue;
292
+ const ref = getString(cbOp, "$ref");
293
+ const resolved = ref !== void 0 ? resolveRefInDoc(parsed.doc, ref) ?? cbOp : cbOp;
294
+ operations.push({
295
+ path: `${name}/${cbPath}`,
296
+ method: cbMethod,
297
+ operationId: getString(resolved, "operationId"),
298
+ summary: getString(resolved, "summary"),
299
+ description: getString(resolved, "description"),
300
+ deprecated: getProperty(resolved, "deprecated") === true,
301
+ operation: isObject(resolved) ? resolved : cbOp
302
+ });
303
+ }
304
+ }
305
+ result.push({
306
+ name,
307
+ operations
308
+ });
309
+ }
310
+ return result;
311
+ }
312
+ function getLinks(parsed, path, method, statusCode) {
313
+ const response = getProperty(getProperty(getProperty(lookupPathItem(parsed, path), method), "responses"), statusCode);
314
+ if (!isObject(response)) return [];
315
+ const links = getProperty(response, "links");
316
+ if (!isObject(links)) return [];
317
+ const result = [];
318
+ for (const [name, linkObj] of Object.entries(links)) {
319
+ if (!isObject(linkObj)) continue;
320
+ const ref = getString(linkObj, "$ref");
321
+ const resolved = ref !== void 0 ? resolveRefInDoc(parsed.doc, ref) ?? linkObj : linkObj;
322
+ const link = isObject(resolved) ? resolved : linkObj;
323
+ const params = getProperty(link, "parameters");
324
+ const paramMap = /* @__PURE__ */ new Map();
325
+ if (isObject(params)) {
326
+ for (const [paramName, paramValue] of Object.entries(params)) if (typeof paramValue === "string") paramMap.set(paramName, paramValue);
327
+ }
328
+ result.push({
329
+ name,
330
+ operationId: getString(link, "operationId"),
331
+ operationRef: getString(link, "operationRef"),
332
+ description: getString(link, "description"),
333
+ parameters: paramMap,
334
+ requestBody: getString(link, "requestBody")
335
+ });
336
+ }
337
+ return result;
338
+ }
158
339
  //#endregion
159
- export { getParameters, getRequestBody, getResponses, getSchema, listOperations, parseOpenApiDocument };
340
+ export { getExternalDocs, getLinks, getParameters, getRequestBody, getResponseHeaders, getResponses, getSchema, getSecurityRequirements, getSecuritySchemes, getXmlInfo, listCallbacks, listOperations, listWebhooks, parseOpenApiDocument };
@@ -1,8 +1,11 @@
1
- import { E as RenderProps, _ as WalkedField, a as FromJSONSchema, b as ComponentResolver, f as PathOfType, i as FieldOverrides, m as SchemaMeta, p as ResolveOpenAPIRef, r as FieldOverride } from "../types-BJzEgJdX.mjs";
2
- import { t as SchemaError } from "../errors-DIKI2C78.mjs";
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-DzbZmcLI.mjs";
3
+ import { t as SchemaError } from "../errors-C5zRC2PU.mjs";
4
+ import { l as RenderProps, r as ComponentResolver } from "../renderer-BdSqllx5.mjs";
5
+ import { c as ResolveOpenAPIRef, s as PathOfType, t as FromJSONSchema } from "../typeInference-k7FXfTVO.mjs";
3
6
  import { z } from "zod";
4
- import { ReactNode } from "react";
5
7
  import * as _$react_jsx_runtime0 from "react/jsx-runtime";
8
+ import { ReactNode } from "react";
6
9
 
7
10
  //#region src/react/SchemaComponent.d.ts
8
11
  /**
@@ -51,6 +54,10 @@ interface SchemaComponentProps<T = unknown, Ref extends string | undefined = und
51
54
  onValidationError?: (error: unknown) => void;
52
55
  /** Called when schema normalisation or rendering fails. */
53
56
  onError?: (error: SchemaError) => void;
57
+ /** Called with each diagnostic emitted during schema processing. */
58
+ onDiagnostic?: (diagnostic: Diagnostic) => void;
59
+ /** When true, any diagnostic becomes a thrown error. */
60
+ strict?: boolean;
54
61
  /** Per-field meta overrides — nested object mirroring schema shape. */
55
62
  fields?: InferFields<T, Ref>;
56
63
  /** Meta overrides applied to the root schema. */
@@ -72,6 +79,8 @@ declare function SchemaComponent<T = unknown, Ref extends string | undefined = u
72
79
  validate,
73
80
  onValidationError,
74
81
  onError,
82
+ onDiagnostic,
83
+ strict,
75
84
  fields,
76
85
  meta: componentMeta,
77
86
  readOnly,
@@ -79,7 +88,7 @@ declare function SchemaComponent<T = unknown, Ref extends string | undefined = u
79
88
  description,
80
89
  widgets: instanceWidgets
81
90
  }: SchemaComponentProps<T, Ref>): ReactNode;
82
- declare function renderField(tree: WalkedField, value: unknown, onChange: (v: unknown) => void, userResolver: ComponentResolver | undefined, renderChild: (tree: WalkedField, value: unknown, onChange: (v: unknown) => void) => ReactNode, instanceWidgets?: WidgetMap, contextWidgets?: WidgetMap): ReactNode;
91
+ declare function renderField(tree: WalkedField, value: unknown, onChange: (v: unknown) => void, userResolver: ComponentResolver | undefined, renderChild: (tree: WalkedField, value: unknown, onChange: (v: unknown) => void) => ReactNode, instanceWidgets?: WidgetMap, contextWidgets?: WidgetMap, depth?: number): ReactNode;
83
92
  /**
84
93
  * Infer the schema's output type for SchemaField path inference.
85
94
  */
@@ -1,13 +1,14 @@
1
1
  "use client";
2
- import { isObject, toRecord, toRecordOrUndefined } from "../core/guards.mjs";
3
- import { normaliseSchema } from "../core/adapter.mjs";
2
+ import { isObject, toRecordOrUndefined } from "../core/guards.mjs";
4
3
  import { SchemaFieldError, SchemaNormalisationError, SchemaRenderError } from "../core/errors.mjs";
4
+ import { normaliseSchema } from "../core/adapter.mjs";
5
5
  import { getRenderFunction, mergeResolvers } from "../core/renderer.mjs";
6
6
  import { walk } from "../core/walker.mjs";
7
7
  import { headlessResolver } from "./headless.mjs";
8
+ import { resolvePath, resolveValue, setNestedValue } from "./fieldPath.mjs";
8
9
  import { z } from "zod";
10
+ import { jsx, jsxs } from "react/jsx-runtime";
9
11
  import { createContext, isValidElement, useCallback, useContext, useMemo } from "react";
10
- import { jsx } from "react/jsx-runtime";
11
12
  //#region src/react/SchemaComponent.tsx
12
13
  /**
13
14
  * <SchemaComponent> — renders UI from Zod, JSON Schema, or OpenAPI schemas.
@@ -45,7 +46,7 @@ const globalWidgets = /* @__PURE__ */ new Map();
45
46
  function registerWidget(name, render) {
46
47
  globalWidgets.set(name, render);
47
48
  }
48
- function SchemaComponent({ schema: schemaInput, ref: refInput, value, onChange, validate, onValidationError, onError, fields, meta: componentMeta, readOnly, writeOnly, description, widgets: instanceWidgets }) {
49
+ function SchemaComponent({ schema: schemaInput, ref: refInput, value, onChange, validate, onValidationError, onError, onDiagnostic, strict, fields, meta: componentMeta, readOnly, writeOnly, description, widgets: instanceWidgets }) {
49
50
  const userResolver = useContext(UserResolverContext);
50
51
  const contextWidgets = useContext(WidgetsContext);
51
52
  const mergedMeta = useMemo(() => {
@@ -60,12 +61,16 @@ function SchemaComponent({ schema: schemaInput, ref: refInput, value, onChange,
60
61
  writeOnly,
61
62
  description
62
63
  ]);
64
+ const diagnostics = onDiagnostic !== void 0 || strict === true ? {
65
+ ...onDiagnostic !== void 0 ? { diagnostics: onDiagnostic } : {},
66
+ ...strict !== void 0 ? { strict } : {}
67
+ } : void 0;
63
68
  let jsonSchema;
64
69
  let zodSchema;
65
70
  let rootMeta;
66
71
  let rootDocument;
67
72
  try {
68
- const normalised = normaliseSchema(schemaInput, refInput);
73
+ const normalised = normaliseSchema(schemaInput, refInput, diagnostics !== void 0 ? { diagnostics } : void 0);
69
74
  jsonSchema = normalised.jsonSchema;
70
75
  zodSchema = normalised.zodSchema;
71
76
  rootMeta = normalised.rootMeta;
@@ -95,16 +100,19 @@ function SchemaComponent({ schema: schemaInput, ref: refInput, value, onChange,
95
100
  onValidationError,
96
101
  fields
97
102
  ]);
98
- const tree = walk(jsonSchema, {
103
+ const walkOptions = {
99
104
  componentMeta: mergedMeta,
100
105
  rootMeta,
101
106
  fieldOverrides: fields,
102
- rootDocument
103
- });
104
- const renderChild = (childTree, childValue, childOnChange) => {
105
- return renderField(childTree, childValue, childOnChange, userResolver, renderChild, instanceWidgets, contextWidgets);
107
+ rootDocument,
108
+ ...diagnostics !== void 0 ? { diagnostics } : {}
109
+ };
110
+ const tree = walk(jsonSchema, walkOptions);
111
+ const makeRenderChild = (currentDepth) => (childTree, childValue, childOnChange) => {
112
+ return renderField(childTree, childValue, childOnChange, userResolver, makeRenderChild(currentDepth + 1), instanceWidgets, contextWidgets, currentDepth + 1);
106
113
  };
107
- return renderField(tree, value ?? tree.defaultValue, handleChange, userResolver, renderChild, instanceWidgets, contextWidgets);
114
+ const renderChild = makeRenderChild(0);
115
+ return renderField(tree, value ?? tree.defaultValue, handleChange, userResolver, renderChild, instanceWidgets, contextWidgets, 0);
108
116
  }
109
117
  function runValidation(zodSchema, jsonSchema, value) {
110
118
  if (zodSchema !== void 0 && isObject(zodSchema)) {
@@ -124,8 +132,17 @@ function runValidation(zodSchema, jsonSchema, value) {
124
132
  }
125
133
  }
126
134
  }
127
- function renderField(tree, value, onChange, userResolver, renderChild, instanceWidgets, contextWidgets) {
128
- if (tree.meta.visible === false) return null;
135
+ /** Maximum rendering depth before treating a field as recursive. */
136
+ const MAX_RENDER_DEPTH = 10;
137
+ function renderField(tree, value, onChange, userResolver, renderChild, instanceWidgets, contextWidgets, depth = 0) {
138
+ if (depth >= MAX_RENDER_DEPTH) {
139
+ const refTarget = tree.type === "recursive" ? tree.refTarget : "";
140
+ return /* @__PURE__ */ jsx("fieldset", { children: /* @__PURE__ */ jsxs("em", { children: [
141
+ "↻ ",
142
+ (typeof tree.meta.description === "string" ? tree.meta.description : refTarget) || "schema",
143
+ " (recursive)"
144
+ ] }) });
145
+ }
129
146
  const componentHint = tree.meta.component;
130
147
  if (typeof componentHint === "string") {
131
148
  const widget = instanceWidgets?.get(componentHint) ?? contextWidgets?.get(componentHint) ?? globalWidgets.get(componentHint);
@@ -166,13 +183,21 @@ function buildRenderProps(tree, value, onChange, renderChild) {
166
183
  tree,
167
184
  renderChild
168
185
  };
169
- if (tree.enumValues !== void 0) props.enumValues = tree.enumValues;
170
- if (tree.element !== void 0) props.element = tree.element;
171
- if (tree.fields !== void 0) props.fields = tree.fields;
172
- if (tree.options !== void 0) props.options = tree.options;
173
- if (tree.discriminator !== void 0) props.discriminator = tree.discriminator;
174
- if (tree.keyType !== void 0) props.keyType = tree.keyType;
175
- if (tree.valueType !== void 0) props.valueType = tree.valueType;
186
+ if (tree.type === "enum") props.enumValues = tree.enumValues;
187
+ if (tree.type === "array" && tree.element !== void 0) props.element = tree.element;
188
+ if (tree.type === "object") props.fields = tree.fields;
189
+ if (tree.type === "union" || tree.type === "discriminatedUnion") props.options = tree.options;
190
+ if (tree.type === "discriminatedUnion") props.discriminator = tree.discriminator;
191
+ if (tree.type === "record") props.keyType = tree.keyType;
192
+ if (tree.type === "record") props.valueType = tree.valueType;
193
+ if (tree.type === "tuple") props.prefixItems = tree.prefixItems;
194
+ if (tree.type === "conditional") props.ifClause = tree.ifClause;
195
+ if (tree.type === "conditional" && tree.thenClause !== void 0) props.thenClause = tree.thenClause;
196
+ if (tree.type === "conditional" && tree.elseClause !== void 0) props.elseClause = tree.elseClause;
197
+ if (tree.type === "negation") props.negated = tree.negated;
198
+ if (tree.type === "recursive") props.refTarget = tree.refTarget;
199
+ if (tree.type === "literal") props.literalValues = tree.literalValues;
200
+ if (tree.examples !== void 0) props.examples = tree.examples;
176
201
  return props;
177
202
  }
178
203
  function SchemaField({ path, schema: schemaInput, ref: refInput, value, onChange, meta: fieldMeta, validate, onValidationError }) {
@@ -215,78 +240,10 @@ function SchemaField({ path, schema: schemaInput, ref: refInput, value, onChange
215
240
  onChange,
216
241
  onValidationError
217
242
  ]);
218
- const renderChild = (childTree, childValue, childOnChange) => {
219
- return renderField(childTree, childValue, childOnChange, userResolver, renderChild, void 0, contextWidgets);
243
+ const makeRenderChild = (currentDepth) => (childTree, childValue, childOnChange) => {
244
+ return renderField(childTree, childValue, childOnChange, userResolver, makeRenderChild(currentDepth + 1), void 0, contextWidgets, currentDepth + 1);
220
245
  };
221
- return renderField(fieldTree, fieldValue, handleChange, userResolver, renderChild, void 0, contextWidgets);
222
- }
223
- function resolvePath(tree, path) {
224
- if (path.length === 0) return tree;
225
- const parts = path.split(".");
226
- let current = tree;
227
- for (const part of parts) {
228
- if (current === void 0) return void 0;
229
- const bracketMatch = /^(.+)\[(\d+)\]$/.exec(part);
230
- if (bracketMatch?.[1] !== void 0 && bracketMatch[2] !== void 0) {
231
- const arrayField = bracketMatch[1];
232
- if (current.fields !== void 0) current = current.fields[arrayField];
233
- if (current?.element !== void 0) current = current.element;
234
- continue;
235
- }
236
- if (current.fields !== void 0) current = current.fields[part];
237
- else if (current.element !== void 0) current = current.element;
238
- else return;
239
- }
240
- return current;
241
- }
242
- function resolveValue(root, path) {
243
- if (path.length === 0) return root;
244
- const parts = path.split(".");
245
- let current = root;
246
- for (const part of parts) {
247
- if (typeof current !== "object" || current === null) return void 0;
248
- const bracketMatch = /^(.+)\[(\d+)\]$/.exec(part);
249
- if (bracketMatch?.[1] !== void 0 && bracketMatch[2] !== void 0) {
250
- const key = bracketMatch[1];
251
- const index = Number(bracketMatch[2]);
252
- const arr = toRecord(current)[key];
253
- if (Array.isArray(arr)) current = arr[index];
254
- else return;
255
- } else current = toRecord(current)[part];
256
- }
257
- return current;
258
- }
259
- function setNestedValue(root, path, leafValue) {
260
- if (path.length === 0) return leafValue;
261
- const parts = path.split(".");
262
- const result = isObject(root) ? { ...toRecord(root) } : {};
263
- let current = result;
264
- for (let i = 0; i < parts.length; i++) {
265
- const part = parts[i];
266
- if (part === void 0) break;
267
- const isLast = i === parts.length - 1;
268
- const bracketMatch = /^(.+)\[(\d+)\]$/.exec(part);
269
- if (bracketMatch?.[1] !== void 0 && bracketMatch[2] !== void 0) {
270
- const key = bracketMatch[1];
271
- const index = Number(bracketMatch[2]);
272
- const existing = current[key];
273
- const arr = Array.isArray(existing) ? existing.slice() : [];
274
- if (isLast) arr[index] = leafValue;
275
- current[key] = arr;
276
- const nextCurrent = arr[index];
277
- if (nextCurrent !== void 0 && isObject(nextCurrent)) current = toRecord(nextCurrent);
278
- } else if (isLast) current[part] = leafValue;
279
- else {
280
- const existing = current[part];
281
- const next = isObject(existing) ? { ...toRecord(existing) } : {};
282
- current[part] = next;
283
- current = next;
284
- }
285
- }
286
- return result;
287
- }
288
- function isFieldErrorCallback(value) {
289
- return typeof value === "function";
246
+ return renderField(fieldTree, fieldValue, handleChange, userResolver, makeRenderChild(0), void 0, contextWidgets, 0);
290
247
  }
291
248
  /**
292
249
  * Dispatch Zod errors to per-field onValidationError callbacks.
@@ -315,6 +272,9 @@ function dispatchFieldErrors(fields, error) {
315
272
  if (fieldErrors.length > 0 && isFieldErrorCallback(fieldCallback)) fieldCallback({ issues: fieldErrors });
316
273
  }
317
274
  }
275
+ function isFieldErrorCallback(value) {
276
+ return typeof value === "function";
277
+ }
318
278
  function isCallable(value) {
319
279
  return typeof value === "function";
320
280
  }
@@ -1,4 +1,6 @@
1
- import { b as ComponentResolver, m as SchemaMeta } from "../types-BJzEgJdX.mjs";
1
+ import { T as SchemaMeta } from "../types-D_5ST7SS.mjs";
2
+ import { t as Diagnostic } from "../diagnostics-DzbZmcLI.mjs";
3
+ import { r as ComponentResolver } from "../renderer-BdSqllx5.mjs";
2
4
  import { WidgetMap } from "./SchemaComponent.mjs";
3
5
  import { ReactNode } from "react";
4
6
 
@@ -24,6 +26,10 @@ interface SchemaViewProps {
24
26
  resolver?: ComponentResolver;
25
27
  /** Instance-scoped widgets. */
26
28
  widgets?: WidgetMap;
29
+ /** Called with each diagnostic emitted during schema processing. */
30
+ onDiagnostic?: (diagnostic: Diagnostic) => void;
31
+ /** When true, any diagnostic becomes a thrown error. */
32
+ strict?: boolean;
27
33
  }
28
34
  /**
29
35
  * Server-safe schema renderer — no hooks, no context, no state.
@@ -39,7 +45,9 @@ declare function SchemaView({
39
45
  meta: componentMeta,
40
46
  description,
41
47
  resolver,
42
- widgets
48
+ widgets,
49
+ onDiagnostic,
50
+ strict
43
51
  }: SchemaViewProps): ReactNode;
44
52
  //#endregion
45
53
  export { SchemaView, SchemaViewProps };
@@ -1,10 +1,10 @@
1
- import { normaliseSchema } from "../core/adapter.mjs";
2
1
  import { SchemaNormalisationError, SchemaRenderError } from "../core/errors.mjs";
2
+ import { normaliseSchema } from "../core/adapter.mjs";
3
3
  import { getRenderFunction, mergeResolvers } from "../core/renderer.mjs";
4
4
  import { walk } from "../core/walker.mjs";
5
5
  import { headlessResolver } from "./headless.mjs";
6
- import { isValidElement } from "react";
7
6
  import { jsx } from "react/jsx-runtime";
7
+ import { createElement, isValidElement } from "react";
8
8
  //#region src/react/SchemaView.tsx
9
9
  /**
10
10
  * React Server Component for read-only schema rendering.
@@ -37,31 +37,42 @@ function noop() {}
37
37
  * Always renders in read-only mode. For editable forms, use
38
38
  * `<SchemaComponent>` with `"use client"`.
39
39
  */
40
- function SchemaView({ schema: schemaInput, ref: refInput, value, fields, meta: componentMeta, description, resolver, widgets }) {
40
+ function SchemaView({ schema: schemaInput, ref: refInput, value, fields, meta: componentMeta, description, resolver, widgets, onDiagnostic, strict }) {
41
41
  const mergedMeta = {
42
42
  ...componentMeta,
43
43
  readOnly: true
44
44
  };
45
45
  if (description !== void 0) mergedMeta.description = description;
46
+ const diagnostics = onDiagnostic !== void 0 || strict === true ? {
47
+ ...onDiagnostic !== void 0 ? { diagnostics: onDiagnostic } : {},
48
+ ...strict !== void 0 ? { strict } : {}
49
+ } : void 0;
46
50
  let jsonSchema;
47
51
  let rootMeta;
48
52
  let rootDocument;
49
53
  try {
50
- const normalised = normaliseSchema(schemaInput, refInput);
54
+ const normalised = normaliseSchema(schemaInput, refInput, diagnostics !== void 0 ? { diagnostics } : void 0);
51
55
  jsonSchema = normalised.jsonSchema;
52
56
  rootMeta = normalised.rootMeta;
53
57
  rootDocument = normalised.rootDocument;
54
58
  } catch (err) {
55
59
  throw new SchemaNormalisationError(err instanceof Error ? err.message : "Failed to normalise schema", schemaInput, "unknown");
56
60
  }
57
- const tree = walk(jsonSchema, {
61
+ const walkOptions = {
58
62
  componentMeta: mergedMeta,
59
63
  rootMeta,
60
64
  fieldOverrides: fields,
61
- rootDocument
62
- });
65
+ rootDocument,
66
+ ...diagnostics !== void 0 ? { diagnostics } : {}
67
+ };
68
+ const tree = walk(jsonSchema, walkOptions);
63
69
  const userResolver = resolver !== void 0 ? mergeResolvers(resolver, headlessResolver) : headlessResolver;
64
- const renderChild = (childTree, childValue) => renderFieldServer(childTree, childValue, userResolver, renderChild, widgets);
70
+ const MAX_SERVER_DEPTH = 10;
71
+ const makeRenderChild = (currentDepth) => (childTree, childValue) => {
72
+ if (currentDepth >= MAX_SERVER_DEPTH) return createElement("fieldset", null, createElement("em", null, `\u21bb ${typeof childTree.meta.description === "string" ? childTree.meta.description : childTree.type === "recursive" ? childTree.refTarget : "schema"} (recursive)`));
73
+ return renderFieldServer(childTree, childValue, userResolver, makeRenderChild(currentDepth + 1), widgets);
74
+ };
75
+ const renderChild = makeRenderChild(0);
65
76
  return renderFieldServer(tree, value ?? tree.defaultValue, userResolver, renderChild, widgets);
66
77
  }
67
78
  function renderFieldServer(tree, value, resolver, renderChild, widgets) {
@@ -99,13 +110,20 @@ function renderFieldServer(tree, value, resolver, renderChild, widgets) {
99
110
  tree,
100
111
  renderChild: (childTree, childValue) => renderChild(childTree, childValue)
101
112
  };
102
- if (tree.enumValues !== void 0) props.enumValues = tree.enumValues;
103
- if (tree.element !== void 0) props.element = tree.element;
104
- if (tree.fields !== void 0) props.fields = tree.fields;
105
- if (tree.options !== void 0) props.options = tree.options;
106
- if (tree.discriminator !== void 0) props.discriminator = tree.discriminator;
107
- if (tree.keyType !== void 0) props.keyType = tree.keyType;
108
- if (tree.valueType !== void 0) props.valueType = tree.valueType;
113
+ if (tree.type === "enum") props.enumValues = tree.enumValues;
114
+ if (tree.type === "array" && tree.element !== void 0) props.element = tree.element;
115
+ if (tree.type === "object") props.fields = tree.fields;
116
+ if (tree.type === "union" || tree.type === "discriminatedUnion") props.options = tree.options;
117
+ if (tree.type === "discriminatedUnion") props.discriminator = tree.discriminator;
118
+ if (tree.type === "record") props.keyType = tree.keyType;
119
+ if (tree.type === "record") props.valueType = tree.valueType;
120
+ if (tree.type === "tuple") props.prefixItems = tree.prefixItems;
121
+ if (tree.type === "conditional") props.ifClause = tree.ifClause;
122
+ if (tree.type === "conditional" && tree.thenClause !== void 0) props.thenClause = tree.thenClause;
123
+ if (tree.type === "conditional" && tree.elseClause !== void 0) props.elseClause = tree.elseClause;
124
+ if (tree.type === "negation") props.negated = tree.negated;
125
+ if (tree.type === "recursive") props.refTarget = tree.refTarget;
126
+ if (tree.type === "literal") props.literalValues = tree.literalValues;
109
127
  try {
110
128
  const result = renderFn(props);
111
129
  if (result !== void 0 && result !== null) {
@@ -0,0 +1,20 @@
1
+ import { M as WalkedField } from "../types-D_5ST7SS.mjs";
2
+
3
+ //#region src/react/fieldPath.d.ts
4
+ /**
5
+ * Resolve a dot-separated path through a WalkedField tree.
6
+ * Supports array index notation: `field[0]`.
7
+ */
8
+ declare function resolvePath(tree: WalkedField, path: string): WalkedField | undefined;
9
+ /**
10
+ * Resolve a dot-separated path through a data value.
11
+ * Supports array index notation: `field[0]`.
12
+ */
13
+ declare function resolveValue(root: unknown, path: string): unknown;
14
+ /**
15
+ * Set a value at a dot-separated path, producing a new root object.
16
+ * Does not mutate the input — returns a shallow-updated copy at each level.
17
+ */
18
+ declare function setNestedValue(root: unknown, path: string, leafValue: unknown): unknown;
19
+ //#endregion
20
+ export { resolvePath, resolveValue, setNestedValue };