schema-components 1.28.2 → 2.0.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 (105) hide show
  1. package/README.md +38 -16
  2. package/dist/core/adapter.d.mts +213 -3
  3. package/dist/core/adapter.mjs +21 -2
  4. package/dist/core/constraintHint.d.mts +15 -0
  5. package/dist/core/constraintHint.mjs +24 -0
  6. package/dist/core/constraints.d.mts +34 -2
  7. package/dist/core/constraints.mjs +33 -1
  8. package/dist/core/cssClasses.d.mts +1 -0
  9. package/dist/core/diagnostics.d.mts +1 -1
  10. package/dist/core/errors.d.mts +1 -1
  11. package/dist/core/errors.mjs +22 -12
  12. package/dist/core/fieldOrder.d.mts +1 -1
  13. package/dist/core/formats.d.mts +7 -1
  14. package/dist/core/formats.mjs +6 -0
  15. package/dist/core/idPath.d.mts +35 -5
  16. package/dist/core/idPath.mjs +79 -7
  17. package/dist/core/inferValue.d.mts +2 -0
  18. package/dist/core/inferValue.mjs +1 -0
  19. package/dist/core/limits.d.mts +1 -1
  20. package/dist/core/limits.mjs +6 -0
  21. package/dist/core/merge.d.mts +22 -1
  22. package/dist/core/merge.mjs +66 -3
  23. package/dist/core/normalise.d.mts +17 -2
  24. package/dist/core/normalise.mjs +1 -1
  25. package/dist/core/openapi30.mjs +1 -1
  26. package/dist/core/openapiConstants.d.mts +1 -0
  27. package/dist/core/ref.d.mts +1 -1
  28. package/dist/core/refChain.d.mts +3 -4
  29. package/dist/core/refChain.mjs +2 -3
  30. package/dist/core/renderer.d.mts +199 -2
  31. package/dist/core/renderer.mjs +5 -0
  32. package/dist/core/swagger2.d.mts +1 -1
  33. package/dist/core/swagger2.mjs +1 -1
  34. package/dist/core/typeInference.d.mts +3 -3
  35. package/dist/core/types.d.mts +1 -1
  36. package/dist/core/types.mjs +17 -0
  37. package/dist/core/unionMatch.d.mts +1 -1
  38. package/dist/core/uri.d.mts +12 -4
  39. package/dist/core/uri.mjs +30 -4
  40. package/dist/core/version.d.mts +1 -1
  41. package/dist/core/walkBuilders.d.mts +63 -6
  42. package/dist/core/walkBuilders.mjs +33 -1
  43. package/dist/core/walker.d.mts +14 -1
  44. package/dist/core/walker.mjs +18 -0
  45. package/dist/{diagnostics-Cbwak-ZX.d.mts → diagnostics-BTrm3O6J.d.mts} +9 -1
  46. package/dist/{errors-DQSIK4n1.d.mts → errors-Dki7tji4.d.mts} +23 -13
  47. package/dist/html/a11y.d.mts +3 -7
  48. package/dist/html/a11y.mjs +1 -16
  49. package/dist/html/html.d.mts +11 -0
  50. package/dist/html/html.mjs +11 -0
  51. package/dist/html/renderToHtml.d.mts +45 -12
  52. package/dist/html/renderToHtml.mjs +20 -4
  53. package/dist/html/renderToHtmlStream.d.mts +63 -18
  54. package/dist/html/renderToHtmlStream.mjs +34 -8
  55. package/dist/html/renderers.d.mts +6 -31
  56. package/dist/html/renderers.mjs +45 -91
  57. package/dist/html/streamRenderers.d.mts +31 -3
  58. package/dist/html/streamRenderers.mjs +41 -8
  59. package/dist/inferValue-PPXWJpbN.d.mts +77 -0
  60. package/dist/{limits-DJhgx5Ay.d.mts → limits-x4OiyJxh.d.mts} +6 -0
  61. package/dist/{normalise-Db1xaxgx.mjs → normalise-DB-Xtjmn.mjs} +43 -2
  62. package/dist/openapi/ApiCallbacks.d.mts +13 -1
  63. package/dist/openapi/ApiCallbacks.mjs +7 -0
  64. package/dist/openapi/ApiLinks.d.mts +13 -1
  65. package/dist/openapi/ApiLinks.mjs +7 -0
  66. package/dist/openapi/ApiResponseHeaders.d.mts +13 -1
  67. package/dist/openapi/ApiResponseHeaders.mjs +7 -0
  68. package/dist/openapi/ApiSecurity.d.mts +14 -1
  69. package/dist/openapi/ApiSecurity.mjs +29 -8
  70. package/dist/openapi/bundle.d.mts +31 -0
  71. package/dist/openapi/components.d.mts +135 -20
  72. package/dist/openapi/components.mjs +90 -15
  73. package/dist/openapi/parser.d.mts +140 -13
  74. package/dist/openapi/parser.mjs +84 -12
  75. package/dist/openapi/resolve.d.mts +42 -47
  76. package/dist/openapi/resolve.mjs +62 -56
  77. package/dist/react/SchemaComponent.d.mts +90 -88
  78. package/dist/react/SchemaComponent.mjs +74 -2
  79. package/dist/react/SchemaErrorBoundary.d.mts +18 -1
  80. package/dist/react/SchemaErrorBoundary.mjs +13 -1
  81. package/dist/react/SchemaView.d.mts +39 -11
  82. package/dist/react/SchemaView.mjs +23 -6
  83. package/dist/react/a11y.d.mts +74 -7
  84. package/dist/react/a11y.mjs +67 -6
  85. package/dist/react/fieldPath.d.mts +16 -1
  86. package/dist/react/fieldPath.mjs +25 -1
  87. package/dist/react/fieldShell.d.mts +49 -0
  88. package/dist/react/fieldShell.mjs +37 -0
  89. package/dist/react/headless.d.mts +1 -1
  90. package/dist/react/headlessRenderers.d.mts +13 -2
  91. package/dist/react/headlessRenderers.mjs +134 -54
  92. package/dist/{ref-TdeMfaV_.d.mts → ref-DdsbekXX.d.mts} +33 -1
  93. package/dist/themes/mantine.d.mts +54 -12
  94. package/dist/themes/mantine.mjs +195 -140
  95. package/dist/themes/mui.d.mts +64 -11
  96. package/dist/themes/mui.mjs +277 -213
  97. package/dist/themes/radix.d.mts +67 -15
  98. package/dist/themes/radix.mjs +235 -170
  99. package/dist/themes/shadcn.d.mts +25 -1
  100. package/dist/themes/shadcn.mjs +112 -91
  101. package/dist/{types-BTB73MB8.d.mts → types-BrYbjC7_.d.mts} +30 -0
  102. package/dist/{version-ZzL5R6cS.d.mts → version-DL8U5RuA.d.mts} +6 -0
  103. package/package.json +8 -1
  104. package/dist/adapter-DqlAnZ_w.d.mts +0 -172
  105. package/dist/renderer-Ul9taFYp.d.mts +0 -169
@@ -21,16 +21,46 @@
21
21
  * Whitelist (not blacklist) so unexpected characters from free-text sources
22
22
  * — `meta.description`, label-derived suffixes, encoded JSON Pointers —
23
23
  * cannot leak into ids and break CSS selectors or aria associations.
24
+ *
25
+ * Non-ASCII inputs (e.g. CJK property names like `名前`, accented Latin
26
+ * like `café`, emoji like `🦄`) collapse under the whitelist to a short
27
+ * or empty string and would silently collide on `sc-`. To keep ids
28
+ * deterministic AND unique per input, the normaliser appends a short
29
+ * hash suffix derived from the original string whenever the whitelisted
30
+ * collapse:
31
+ *
32
+ * - produces an empty string, OR
33
+ * - dropped non-structural characters from the input (i.e. anything
34
+ * besides the path joiners `.`, `[`, `]` and ASCII whitespace).
35
+ *
36
+ * Structural separator runs do NOT trigger the disambiguator so
37
+ * canonical paths like `user.preferences` and `tags[0]` keep their
38
+ * historic readable form (`user-preferences`, `tags-0`).
39
+ *
40
+ * The hash is a 32-bit FNV-1a variant rendered in base-36. It is
41
+ * deterministic (same input → same output), short (≤ 7 characters), and
42
+ * non-cryptographic — collision resistance is good enough for DOM ids,
43
+ * and a cryptographic primitive is unnecessary and not universally
44
+ * available (no `crypto` global in every JS runtime that consumes the
45
+ * library).
46
+ *
47
+ * The leading character is guaranteed to be an ASCII letter so the full
48
+ * `sc-<segment>` id is always a valid CSS identifier and `querySelector`
49
+ * target. Empty-collapse inputs receive a synthetic `u` (for "unicode")
50
+ * prefix on the hash so the id never starts with a digit.
24
51
  */
25
52
  declare function normaliseIdSegment(value: string): string;
26
53
  /**
27
- * Build the canonical `sc-`-prefixed DOM id for a structural path.
28
- * Use this as the base id for an input element; derived ids (panel, tab,
54
+ * Build the canonical `sc-`-prefixed DOM id for a structural path. Use
55
+ * this as the base id for an input element; derived ids (panel, tab,
29
56
  * hint) compose suffixes onto the returned string.
30
57
  *
31
- * Throws on an empty path: a previous "sc-field" fallback caused every
32
- * input across a form to share the same id, breaking label-input pairing
33
- * and screen reader navigation.
58
+ * An empty `path` is permitted it surfaces as the bare prefix `sc-`
59
+ * so a leaf renderer at the schema root (e.g.
60
+ * `renderToHtml(z.string())`) still emits a usable id without throwing.
61
+ * Container renderers always thread a non-empty path through
62
+ * `renderChild`, so the empty-id case can never produce sibling
63
+ * collisions inside a structured form.
34
64
  */
35
65
  declare function fieldDomId(path: string): string;
36
66
  /**
@@ -15,6 +15,17 @@ import "./cssClasses.mjs";
15
15
  * Pipelines should import the helpers below rather than re-deriving them.
16
16
  */
17
17
  /**
18
+ * Characters the path joiners (`react/SchemaComponent.joinPath`,
19
+ * `html/a11y.joinPath`) emit between segments — `.` between object
20
+ * keys, `[` / `]` around array indices. The disambiguator below treats
21
+ * these as benign structural separators: collapsing them into a hyphen
22
+ * is part of the canonical id form and does NOT signal a collision
23
+ * risk, so no hash suffix is appended for paths like `user.preferences`
24
+ * or `tags[0]`. ASCII whitespace is included for the same reason —
25
+ * label-derived suffixes may carry incidental whitespace.
26
+ */
27
+ const STRUCTURAL_SEPARATOR_PATTERN = /^[.[\]\s]+$/;
28
+ /**
18
29
  * Normalise a structural path into the id segment used after the `sc-`
19
30
  * prefix. Whitelist-based: any run of characters outside `[A-Za-z0-9_-]`
20
31
  * collapses to a single hyphen, with trailing hyphens stripped.
@@ -22,21 +33,82 @@ import "./cssClasses.mjs";
22
33
  * Whitelist (not blacklist) so unexpected characters from free-text sources
23
34
  * — `meta.description`, label-derived suffixes, encoded JSON Pointers —
24
35
  * cannot leak into ids and break CSS selectors or aria associations.
36
+ *
37
+ * Non-ASCII inputs (e.g. CJK property names like `名前`, accented Latin
38
+ * like `café`, emoji like `🦄`) collapse under the whitelist to a short
39
+ * or empty string and would silently collide on `sc-`. To keep ids
40
+ * deterministic AND unique per input, the normaliser appends a short
41
+ * hash suffix derived from the original string whenever the whitelisted
42
+ * collapse:
43
+ *
44
+ * - produces an empty string, OR
45
+ * - dropped non-structural characters from the input (i.e. anything
46
+ * besides the path joiners `.`, `[`, `]` and ASCII whitespace).
47
+ *
48
+ * Structural separator runs do NOT trigger the disambiguator so
49
+ * canonical paths like `user.preferences` and `tags[0]` keep their
50
+ * historic readable form (`user-preferences`, `tags-0`).
51
+ *
52
+ * The hash is a 32-bit FNV-1a variant rendered in base-36. It is
53
+ * deterministic (same input → same output), short (≤ 7 characters), and
54
+ * non-cryptographic — collision resistance is good enough for DOM ids,
55
+ * and a cryptographic primitive is unnecessary and not universally
56
+ * available (no `crypto` global in every JS runtime that consumes the
57
+ * library).
58
+ *
59
+ * The leading character is guaranteed to be an ASCII letter so the full
60
+ * `sc-<segment>` id is always a valid CSS identifier and `querySelector`
61
+ * target. Empty-collapse inputs receive a synthetic `u` (for "unicode")
62
+ * prefix on the hash so the id never starts with a digit.
25
63
  */
26
64
  function normaliseIdSegment(value) {
27
- return value.replace(/[^A-Za-z0-9_-]+/g, "-").replace(/-+$/g, "");
65
+ const collapsed = value.replace(/[^A-Za-z0-9_-]+/g, "-").replace(/-+$/g, "");
66
+ if (collapsed.length === 0) return `u${hashSuffix(value)}`;
67
+ if (collapsed !== value && droppedNonStructural(value)) return `${collapsed}-${hashSuffix(value)}`;
68
+ return collapsed;
69
+ }
70
+ /**
71
+ * True when `value` contains at least one character outside both the
72
+ * id whitelist `[A-Za-z0-9_-]` and the structural separator set
73
+ * (`.`, `[`, `]`, ASCII whitespace). Used by `normaliseIdSegment` to
74
+ * decide whether a non-canonical dropped character (a real Unicode
75
+ * character, a label-derived punctuation, etc.) means the collapse is
76
+ * lossy and disambiguation is required.
77
+ */
78
+ function droppedNonStructural(value) {
79
+ const nonWhitelisted = value.replace(/[A-Za-z0-9_-]+/g, "");
80
+ if (nonWhitelisted.length === 0) return false;
81
+ return !STRUCTURAL_SEPARATOR_PATTERN.test(nonWhitelisted);
82
+ }
83
+ /**
84
+ * Deterministic 32-bit FNV-1a hash of `value` rendered in base-36. Used
85
+ * solely to disambiguate id segments that would otherwise collide after
86
+ * the whitelisted character collapse; not security-sensitive, so a
87
+ * non-cryptographic hash is sufficient and avoids depending on platform
88
+ * `crypto` availability.
89
+ */
90
+ function hashSuffix(value) {
91
+ let hash = 2166136261;
92
+ for (let i = 0; i < value.length; i++) {
93
+ hash ^= value.charCodeAt(i);
94
+ hash = Math.imul(hash, 16777619);
95
+ }
96
+ return (hash >>> 0).toString(36);
28
97
  }
29
98
  /**
30
- * Build the canonical `sc-`-prefixed DOM id for a structural path.
31
- * Use this as the base id for an input element; derived ids (panel, tab,
99
+ * Build the canonical `sc-`-prefixed DOM id for a structural path. Use
100
+ * this as the base id for an input element; derived ids (panel, tab,
32
101
  * hint) compose suffixes onto the returned string.
33
102
  *
34
- * Throws on an empty path: a previous "sc-field" fallback caused every
35
- * input across a form to share the same id, breaking label-input pairing
36
- * and screen reader navigation.
103
+ * An empty `path` is permitted it surfaces as the bare prefix `sc-`
104
+ * so a leaf renderer at the schema root (e.g.
105
+ * `renderToHtml(z.string())`) still emits a usable id without throwing.
106
+ * Container renderers always thread a non-empty path through
107
+ * `renderChild`, so the empty-id case can never produce sibling
108
+ * collisions inside a structured form.
37
109
  */
38
110
  function fieldDomId(path) {
39
- if (path.length === 0) throw new Error("fieldDomId requires a non-empty path. Thread a root path from the renderer entry point and derive children via joinPath().");
111
+ if (path.length === 0) return "sc-";
40
112
  return `sc-${normaliseIdSegment(path)}`;
41
113
  }
42
114
  /**
@@ -0,0 +1,2 @@
1
+ import { a as InferredValue, i as InferredOutputValue, n as InferSchemaValue, r as InferredInputValue, t as InferFields } from "../inferValue-PPXWJpbN.mjs";
2
+ export { InferFields, InferSchemaValue, InferredInputValue, InferredOutputValue, InferredValue };
@@ -0,0 +1 @@
1
+ export {};
@@ -1,2 +1,2 @@
1
- import { i as MaxRefDepth, n as MAX_REF_DEPTH, r as MAX_RENDER_DEPTH, t as MAX_PATH_ITEM_REF_HOPS } from "../limits-DJhgx5Ay.mjs";
1
+ import { i as MaxRefDepth, n as MAX_REF_DEPTH, r as MAX_RENDER_DEPTH, t as MAX_PATH_ITEM_REF_HOPS } from "../limits-x4OiyJxh.mjs";
2
2
  export { MAX_PATH_ITEM_REF_HOPS, MAX_REF_DEPTH, MAX_RENDER_DEPTH, MaxRefDepth };
@@ -12,11 +12,17 @@
12
12
  * — the only safe response to a cyclic walked-field graph.
13
13
  */
14
14
  const MAX_RENDER_DEPTH = 10;
15
+ /** Runtime constant matching the type-level {@link MaxRefDepth} bound. */
15
16
  const MAX_REF_DEPTH = 64;
16
17
  /**
17
18
  * Maximum number of `$ref` hops permitted when walking a chain of
18
19
  * OpenAPI Path Item Object references. Beyond this a
19
20
  * `path-item-ref-too-deep` diagnostic is emitted and resolution stops.
21
+ *
22
+ * Also the default `maxHops` for the generic `resolveRefChain` helper
23
+ * in `core/refChain.ts`, so every ref-chain walker — Path Item refs,
24
+ * Parameter / Response refs, Reference Object chains, etc. — shares
25
+ * the same hop cap.
20
26
  */
21
27
  const MAX_PATH_ITEM_REF_HOPS = 8;
22
28
  //#endregion
@@ -1,4 +1,4 @@
1
- import { i as DiagnosticsOptions } from "../diagnostics-Cbwak-ZX.mjs";
1
+ import { i as DiagnosticsOptions } from "../diagnostics-BTrm3O6J.mjs";
2
2
 
3
3
  //#region src/core/merge.d.ts
4
4
  /**
@@ -45,6 +45,11 @@ declare function mergeRefSiblings(referencer: Record<string, unknown>, resolvedM
45
45
  * inputs that cannot represent a schema; skip them as before.
46
46
  */
47
47
  declare function mergeAllOf(schemas: unknown[], diagnostics?: DiagnosticsOptions, pointer?: string): Record<string, unknown> | false;
48
+ /**
49
+ * Result returned by {@link normaliseAnyOf} when an `anyOf` schema
50
+ * collapses to a nullable shape: the non-null branch and the nullable
51
+ * flag.
52
+ */
48
53
  interface NormalisedAnyOf {
49
54
  inner: Record<string, unknown>;
50
55
  isNullable: boolean;
@@ -54,6 +59,11 @@ interface NormalisedAnyOf {
54
59
  * Returns the non-null schema and a nullable flag.
55
60
  */
56
61
  declare function normaliseAnyOf(options: unknown[]): NormalisedAnyOf | undefined;
62
+ /**
63
+ * Result returned by {@link detectDiscriminated} when a `oneOf` schema
64
+ * is structurally a discriminated union: the option schemas plus the
65
+ * shared property name carrying the const discriminator.
66
+ */
57
67
  interface Discriminated {
58
68
  options: Record<string, unknown>[];
59
69
  discriminator: string;
@@ -66,6 +76,17 @@ interface Discriminated {
66
76
  * uses `kind` while another uses `type`) detection fails and a
67
77
  * `discriminator-inconsistent` diagnostic is emitted so callers can
68
78
  * see why the union falls back to a generic oneOf.
79
+ *
80
+ * When two or more options share the same discriminator `const` value,
81
+ * the union is still treated as discriminated — the first-match
82
+ * behaviour in `resolveDiscriminatedActive` (in `core/unionMatch.ts`)
83
+ * resolves the active option — but a `discriminator-duplicate`
84
+ * diagnostic is emitted so the unreachable branch is visible to the
85
+ * consumer. Changing the behaviour to fall back to a generic union
86
+ * would be a silent regression for the much commoner case of two
87
+ * intentionally-identical discriminator values appearing in distinct
88
+ * sub-schemas (e.g. an `allOf`-driven hierarchy where the base option
89
+ * duplicates the leaf).
69
90
  */
70
91
  declare function detectDiscriminated(options: unknown[], diagnostics?: DiagnosticsOptions, pointer?: string): Discriminated | undefined;
71
92
  //#endregion
@@ -1,5 +1,6 @@
1
1
  import { isObject } from "./guards.mjs";
2
- import { emitDiagnostic } from "./diagnostics.mjs";
2
+ import { appendPointer, emitDiagnostic } from "./diagnostics.mjs";
3
+ import { isPrototypePollutingKey } from "./uri.mjs";
3
4
  //#region src/core/merge.ts
4
5
  /**
5
6
  * Schema merging, nullable detection, and discriminated union detection.
@@ -137,7 +138,18 @@ function mergeAllOf(schemas, diagnostics, pointer = "") {
137
138
  if (entry === true) continue;
138
139
  if (!isObject(entry)) continue;
139
140
  const props = getObject(entry, "properties");
140
- if (props !== void 0) for (const [key, value] of Object.entries(props)) properties[key] = value;
141
+ if (props !== void 0) for (const [key, value] of Object.entries(props)) {
142
+ if (isPrototypePollutingKey(key)) {
143
+ emitDiagnostic(diagnostics, {
144
+ code: "prototype-polluting-property",
145
+ message: `Refusing to merge prototype-polluting property name from allOf branch: ${key}`,
146
+ pointer: appendPointer(pointer, `properties/${key}`),
147
+ detail: { propertyName: key }
148
+ });
149
+ continue;
150
+ }
151
+ properties[key] = value;
152
+ }
141
153
  const req = getArray(entry, "required");
142
154
  if (req !== void 0) {
143
155
  for (const r of req) if (typeof r === "string" && !required.includes(r)) required.push(r);
@@ -232,6 +244,17 @@ function normaliseAnyOf(options) {
232
244
  * uses `kind` while another uses `type`) detection fails and a
233
245
  * `discriminator-inconsistent` diagnostic is emitted so callers can
234
246
  * see why the union falls back to a generic oneOf.
247
+ *
248
+ * When two or more options share the same discriminator `const` value,
249
+ * the union is still treated as discriminated — the first-match
250
+ * behaviour in `resolveDiscriminatedActive` (in `core/unionMatch.ts`)
251
+ * resolves the active option — but a `discriminator-duplicate`
252
+ * diagnostic is emitted so the unreachable branch is visible to the
253
+ * consumer. Changing the behaviour to fall back to a generic union
254
+ * would be a silent regression for the much commoner case of two
255
+ * intentionally-identical discriminator values appearing in distinct
256
+ * sub-schemas (e.g. an `allOf`-driven hierarchy where the base option
257
+ * duplicates the leaf).
235
258
  */
236
259
  function detectDiscriminated(options, diagnostics, pointer = "") {
237
260
  if (options.length === 0) return void 0;
@@ -263,10 +286,50 @@ function detectDiscriminated(options, diagnostics, pointer = "") {
263
286
  return;
264
287
  }
265
288
  if (discriminator === void 0) return void 0;
289
+ const objectOptions = options.filter(isObject);
290
+ emitDuplicateDiscriminatorDiagnostic(objectOptions, discriminator, diagnostics, pointer);
266
291
  return {
267
- options: options.filter(isObject),
292
+ options: objectOptions,
268
293
  discriminator
269
294
  };
270
295
  }
296
+ /**
297
+ * Inspect the discriminator `const` value declared on each option and
298
+ * emit a `discriminator-duplicate` diagnostic when two or more options
299
+ * share the same value. The diagnostic detail carries the offending
300
+ * value plus the indices of the colliding options so consumers can
301
+ * point at them directly.
302
+ */
303
+ function emitDuplicateDiscriminatorDiagnostic(options, discriminator, diagnostics, pointer) {
304
+ const groups = /* @__PURE__ */ new Map();
305
+ for (const [index, option] of options.entries()) {
306
+ const props = getObject(option, "properties");
307
+ if (props === void 0) continue;
308
+ const discriminatorSchema = getObject(props, discriminator);
309
+ if (discriminatorSchema === void 0) continue;
310
+ if (!("const" in discriminatorSchema)) continue;
311
+ const constValue = discriminatorSchema.const;
312
+ const key = JSON.stringify(constValue);
313
+ const existing = groups.get(key);
314
+ if (existing === void 0) groups.set(key, {
315
+ value: constValue,
316
+ indices: [index]
317
+ });
318
+ else existing.indices.push(index);
319
+ }
320
+ for (const { value, indices } of groups.values()) {
321
+ if (indices.length < 2) continue;
322
+ emitDiagnostic(diagnostics, {
323
+ code: "discriminator-duplicate",
324
+ message: `oneOf options ${indices.join(", ")} share the same discriminator value for "${discriminator}" (${JSON.stringify(value)}); only the first option is reachable`,
325
+ pointer,
326
+ detail: {
327
+ discriminator,
328
+ value,
329
+ indices
330
+ }
331
+ });
332
+ }
333
+ }
271
334
  //#endregion
272
335
  export { ANNOTATION_SIBLINGS, detectDiscriminated, mergeAllOf, mergeRefSiblings, normaliseAnyOf };
@@ -1,7 +1,15 @@
1
- import { i as DiagnosticsOptions } from "../diagnostics-Cbwak-ZX.mjs";
2
- import { i as OpenApiVersionInfo, r as JsonSchemaDraft } from "../version-ZzL5R6cS.mjs";
1
+ import { i as DiagnosticsOptions } from "../diagnostics-BTrm3O6J.mjs";
2
+ import { i as OpenApiVersionInfo, r as JsonSchemaDraft } from "../version-DL8U5RuA.mjs";
3
3
 
4
4
  //#region src/core/normalise.d.ts
5
+ /**
6
+ * Per-node transform applied by {@link deepNormalise} when no
7
+ * diagnostics context needs to be threaded — see
8
+ * {@link NodeTransformWithContext} for the variant used by the JSON
9
+ * Schema normalisation path.
10
+ *
11
+ * @group Adapter
12
+ */
5
13
  type NodeTransform = (node: Record<string, unknown>) => Record<string, unknown>;
6
14
  /**
7
15
  * Deep-normalise a JSON Schema object by applying a per-node transform
@@ -43,6 +51,13 @@ interface NodeContext {
43
51
  documentHasRecursiveAnchor: boolean;
44
52
  declaredDraft: JsonSchemaDraft | undefined;
45
53
  }
54
+ /**
55
+ * Per-node transform applied by {@link deepNormaliseWithContext}.
56
+ * Receives the supplied {@link NodeContext} alongside the node so
57
+ * draft-specific rewrites can emit diagnostics with accurate pointers.
58
+ *
59
+ * @group Adapter
60
+ */
46
61
  type NodeTransformWithContext = (node: Record<string, unknown>, ctx: NodeContext) => Record<string, unknown>;
47
62
  /**
48
63
  * Deep-normalise a JSON Schema object, threading a context (diagnostics
@@ -1,2 +1,2 @@
1
- import { a as normaliseJsonSchema, i as normaliseDraft04Node, n as deepNormaliseWithContext, o as normaliseOpenApiSchemas, r as documentContainsKeyword, s as selectDraftTransform, t as deepNormalise } from "../normalise-Db1xaxgx.mjs";
1
+ import { a as normaliseJsonSchema, i as normaliseDraft04Node, n as deepNormaliseWithContext, o as normaliseOpenApiSchemas, r as documentContainsKeyword, s as selectDraftTransform, t as deepNormalise } from "../normalise-DB-Xtjmn.mjs";
2
2
  export { deepNormalise, deepNormaliseWithContext, documentContainsKeyword, normaliseDraft04Node, normaliseJsonSchema, normaliseOpenApiSchemas, selectDraftTransform };
@@ -1,2 +1,2 @@
1
- import { d as deepNormaliseOpenApiDoc, f as liftExampleToExamples, h as normaliseOpenApi30Node, l as applyDiscriminatorAllOfPrepass, m as normaliseOpenApi30Discriminator, p as normaliseOpenApi30Combined, u as deepNormaliseOpenApi30Doc } from "../normalise-Db1xaxgx.mjs";
1
+ import { d as deepNormaliseOpenApiDoc, f as liftExampleToExamples, h as normaliseOpenApi30Node, l as applyDiscriminatorAllOfPrepass, m as normaliseOpenApi30Discriminator, p as normaliseOpenApi30Combined, u as deepNormaliseOpenApi30Doc } from "../normalise-DB-Xtjmn.mjs";
2
2
  export { applyDiscriminatorAllOfPrepass, deepNormaliseOpenApi30Doc, deepNormaliseOpenApiDoc, liftExampleToExamples, normaliseOpenApi30Combined, normaliseOpenApi30Discriminator, normaliseOpenApi30Node };
@@ -12,6 +12,7 @@
12
12
  * Spec: https://spec.openapis.org/oas/v3.1.1#path-item-object
13
13
  */
14
14
  declare const HTTP_METHODS: readonly ["get", "put", "post", "delete", "options", "head", "patch", "trace"];
15
+ /** Canonical OpenAPI 3.x HTTP method literal, derived from {@link HTTP_METHODS}. */
15
16
  type HttpMethod = (typeof HTTP_METHODS)[number];
16
17
  /**
17
18
  * Swagger 2.0 omits `trace` — the keyword was introduced in OpenAPI 3.0.
@@ -1,2 +1,2 @@
1
- import { a as dereference, i as countDistinctRefs, n as RECURSIVE_ANCHOR_SENTINEL, o as findAnchor, r as RefOptions, s as resolveRef, t as ExternalResolver } from "../ref-TdeMfaV_.mjs";
1
+ import { a as dereference, i as countDistinctRefs, n as RECURSIVE_ANCHOR_SENTINEL, o as findAnchor, r as RefOptions, s as resolveRef, t as ExternalResolver } from "../ref-DdsbekXX.mjs";
2
2
  export { ExternalResolver, RECURSIVE_ANCHOR_SENTINEL, RefOptions, countDistinctRefs, dereference, findAnchor, resolveRef };
@@ -13,8 +13,6 @@
13
13
  * detect cycles, and tracking hop count against `maxHops`. The caller chooses
14
14
  * what to do on cycle or depth-cap via `onCycle` / `onDepthExceeded`.
15
15
  */
16
- /** Maximum number of `$ref` hops permitted by default. */
17
- declare const DEFAULT_REF_CHAIN_MAX_HOPS = 8;
18
16
  /**
19
17
  * Configuration for a single chain resolution.
20
18
  *
@@ -51,7 +49,8 @@ interface ResolveRefChainOptions<T> {
51
49
  readonly onDepthExceeded?: (ref: string) => T | undefined;
52
50
  /**
53
51
  * Maximum number of `$ref` hops permitted before `onDepthExceeded` fires.
54
- * Defaults to `DEFAULT_REF_CHAIN_MAX_HOPS`.
52
+ * Defaults to `MAX_PATH_ITEM_REF_HOPS` from `core/limits.ts`, the
53
+ * canonical cap shared with the OpenAPI Path Item ref walker.
55
54
  */
56
55
  readonly maxHops?: number;
57
56
  /**
@@ -67,4 +66,4 @@ interface ResolveRefChainOptions<T> {
67
66
  */
68
67
  declare function resolveRefChain<T>(initial: T, options: ResolveRefChainOptions<T>): T | undefined;
69
68
  //#endregion
70
- export { DEFAULT_REF_CHAIN_MAX_HOPS, ResolveRefChainOptions, resolveRefChain };
69
+ export { ResolveRefChainOptions, resolveRefChain };
@@ -1,3 +1,4 @@
1
+ import "./limits.mjs";
1
2
  //#region src/core/refChain.ts
2
3
  /**
3
4
  * Generic single-pass `$ref` chain resolver.
@@ -13,8 +14,6 @@
13
14
  * detect cycles, and tracking hop count against `maxHops`. The caller chooses
14
15
  * what to do on cycle or depth-cap via `onCycle` / `onDepthExceeded`.
15
16
  */
16
- /** Maximum number of `$ref` hops permitted by default. */
17
- const DEFAULT_REF_CHAIN_MAX_HOPS = 8;
18
17
  function defaultExtractRef(node) {
19
18
  if (typeof node !== "object" || node === null) return void 0;
20
19
  if (!("$ref" in node)) return void 0;
@@ -41,4 +40,4 @@ function resolveRefChain(initial, options) {
41
40
  }
42
41
  }
43
42
  //#endregion
44
- export { DEFAULT_REF_CHAIN_MAX_HOPS, resolveRefChain };
43
+ export { resolveRefChain };
@@ -1,2 +1,199 @@
1
- import { a as HtmlRenderProps, c as RenderFunction, d as getHtmlRenderFn, f as getRenderFunction, h as typeToKey, i as HtmlRenderFunction, l as RenderProps, m as mergeResolvers, n as BaseFieldProps, o as HtmlResolver, p as mergeHtmlResolvers, r as ComponentResolver, s as RESOLVER_KEYS, t as AllConstraints, u as buildRenderProps } from "../renderer-Ul9taFYp.mjs";
2
- export { AllConstraints, BaseFieldProps, ComponentResolver, HtmlRenderFunction, HtmlRenderProps, HtmlResolver, RESOLVER_KEYS, RenderFunction, RenderProps, buildRenderProps, getHtmlRenderFn, getRenderFunction, mergeHtmlResolvers, mergeResolvers, typeToKey };
1
+ import { E as StringConstraints, f as FileConstraints, j as WalkedField, t as ArrayConstraints, w as SchemaMeta, x as ObjectConstraints, y as NumberConstraints } from "../types-BrYbjC7_.mjs";
2
+
3
+ //#region src/core/renderer.d.ts
4
+ /**
5
+ * Flat intersection of all constraint types.
6
+ * Used in renderer props where the render function receives the union
7
+ * but knows (by resolver key) which subset applies.
8
+ *
9
+ * The walker's discriminated WalkedField enforces type-correct constraints
10
+ * at construction time; the renderer consumes them as this flat type.
11
+ */
12
+ type AllConstraints = StringConstraints & NumberConstraints & ArrayConstraints & ObjectConstraints & FileConstraints;
13
+ /**
14
+ * Properties available on every schema field, regardless of rendering target.
15
+ * Both React and HTML renderers receive these.
16
+ *
17
+ * Per-type schema data — enum values, object fields, array element schema,
18
+ * union options, record key/value types, tuple `prefixItems`, conditional
19
+ * if/then/else clauses, negation `negated`, recursive `refTarget`, literal
20
+ * values — lives on the discriminated `tree`. Renderers narrow on
21
+ * `tree.type` and read from the matching variant; there are no duplicate
22
+ * sibling fields on these props.
23
+ */
24
+ interface BaseFieldProps {
25
+ /** Current field value. */
26
+ value: unknown;
27
+ /** Whether to render as read-only display. */
28
+ readOnly: boolean;
29
+ /** Whether to render as an empty input. */
30
+ writeOnly: boolean;
31
+ /** Schema metadata for this field. */
32
+ meta: SchemaMeta;
33
+ /** Constraints from schema checks. */
34
+ constraints: AllConstraints;
35
+ /** Dot-separated path from root (e.g. "address.city"). */
36
+ path: string;
37
+ /** Example values from the schema's `examples` keyword. */
38
+ examples?: unknown[];
39
+ /** Walked field tree for recursive rendering. */
40
+ tree: WalkedField;
41
+ }
42
+ /**
43
+ * Props for React render functions. Extends BaseFieldProps with:
44
+ * - `onChange` — callback to propagate value changes back to state
45
+ * - `renderChild` — recursively renders a child field, threading onChange
46
+ */
47
+ interface RenderProps extends BaseFieldProps {
48
+ /** Callback to update the field value. */
49
+ onChange: (value: unknown) => void;
50
+ /**
51
+ * Render a child field. Theme adapters call this to recursively render
52
+ * nested structures (object fields, array elements, union options).
53
+ * The resolver and rendering context are already wired in.
54
+ *
55
+ * @param tree - The walked field tree for the child
56
+ * @param value - The child's current value
57
+ * @param onChange - Callback receiving the child's next value
58
+ * @param pathSuffix - Path segment from the parent (e.g. "city",
59
+ * "[0]"). Joined to the parent's path with a dot, or substituted
60
+ * when the parent acts as a transparent wrapper (union options).
61
+ * Required for every container — without it children inherit no
62
+ * path and `inputId()` will throw.
63
+ */
64
+ renderChild: (tree: WalkedField, value: unknown, onChange: (v: unknown) => void, pathSuffix?: string) => unknown;
65
+ }
66
+ /**
67
+ * Props for HTML render functions. Extends BaseFieldProps with:
68
+ * - `renderChild` — recursively renders a child field to HTML string
69
+ *
70
+ * No `onChange` — HTML rendering is pure output with no event handling.
71
+ */
72
+ interface HtmlRenderProps extends BaseFieldProps {
73
+ /**
74
+ * Render a child field to an HTML string. Theme adapters call this
75
+ * to recursively render nested structures.
76
+ *
77
+ * @param tree - The walked field tree for the child
78
+ * @param value - The child's current value
79
+ * @param pathSuffix - Path segment from the parent (e.g. "city",
80
+ * "[0]"). When omitted, the child's description is used as fallback.
81
+ */
82
+ renderChild: (tree: WalkedField, value: unknown, pathSuffix?: string) => string;
83
+ }
84
+ /**
85
+ * Build the `RenderProps` object handed to a resolver render function or a
86
+ * widget. Used by both the server-side `<SchemaView>` (which has no
87
+ * `onChange`) and the client-side `<SchemaComponent>` (which threads an
88
+ * `onChange` callback).
89
+ *
90
+ * When `onChange` is `undefined` the caller is rendering in read-only mode:
91
+ * a noop `onChange` is wired up, `readOnly` is forced to `true`, and
92
+ * `writeOnly` is forced to `false`. Otherwise the editability is taken
93
+ * from `tree.editability`.
94
+ */
95
+ declare function buildRenderProps(tree: WalkedField, value: unknown, onChange: ((next: unknown) => void) | undefined, renderChild: RenderProps["renderChild"], path: string): RenderProps;
96
+ /**
97
+ * Signature for a React render function attached to a
98
+ * {@link ComponentResolver}. Receives the per-field {@link RenderProps}
99
+ * built by the walker and returns any ReactNode-compatible value.
100
+ */
101
+ type RenderFunction = (props: RenderProps) => unknown;
102
+ /**
103
+ * Widget map — maps component hints (from `.meta({ component })`) to render
104
+ * functions. A per-render bag consumed by every renderer surface that
105
+ * dispatches widget overrides; conceptually parallel to
106
+ * {@link ComponentResolver} but keyed by user-supplied hint names rather
107
+ * than schema types.
108
+ *
109
+ * Scoped at three levels in the React renderer:
110
+ *
111
+ * 1. **Per-instance** — `widgets` prop on `<SchemaComponent>`
112
+ * 2. **Context-scoped** — `widgets` prop on `<SchemaProvider>`
113
+ * 3. **Global** — `registerWidget()` (app-wide defaults)
114
+ *
115
+ * Resolution order: instance → context → global → resolver → headless.
116
+ */
117
+ type WidgetMap = ReadonlyMap<string, RenderFunction>;
118
+ /**
119
+ * Theme adapter — maps every schema field type to its React renderer.
120
+ * Unset keys fall back to the headless resolver. Pass to
121
+ * `SchemaProvider` (or `SchemaView.resolver`) to drive every
122
+ * schema-driven render with a specific theme.
123
+ */
124
+ interface ComponentResolver {
125
+ string?: RenderFunction;
126
+ number?: RenderFunction;
127
+ boolean?: RenderFunction;
128
+ null?: RenderFunction;
129
+ enum?: RenderFunction;
130
+ object?: RenderFunction;
131
+ array?: RenderFunction;
132
+ tuple?: RenderFunction;
133
+ record?: RenderFunction;
134
+ union?: RenderFunction;
135
+ discriminatedUnion?: RenderFunction;
136
+ conditional?: RenderFunction;
137
+ negation?: RenderFunction;
138
+ literal?: RenderFunction;
139
+ file?: RenderFunction;
140
+ never?: RenderFunction;
141
+ unknown?: RenderFunction;
142
+ }
143
+ /** An HTML render function returns a string. */
144
+ type HtmlRenderFunction = (props: HtmlRenderProps) => string;
145
+ /**
146
+ * HTML resolver — maps schema types to HTML string renderers.
147
+ * Structurally mirrors ComponentResolver but produces strings.
148
+ */
149
+ interface HtmlResolver {
150
+ string?: HtmlRenderFunction;
151
+ number?: HtmlRenderFunction;
152
+ boolean?: HtmlRenderFunction;
153
+ null?: HtmlRenderFunction;
154
+ enum?: HtmlRenderFunction;
155
+ object?: HtmlRenderFunction;
156
+ array?: HtmlRenderFunction;
157
+ tuple?: HtmlRenderFunction;
158
+ record?: HtmlRenderFunction;
159
+ union?: HtmlRenderFunction;
160
+ discriminatedUnion?: HtmlRenderFunction;
161
+ conditional?: HtmlRenderFunction;
162
+ negation?: HtmlRenderFunction;
163
+ literal?: HtmlRenderFunction;
164
+ file?: HtmlRenderFunction;
165
+ never?: HtmlRenderFunction;
166
+ unknown?: HtmlRenderFunction;
167
+ }
168
+ /**
169
+ * Canonical list of resolver keys, one per {@link WalkedField} variant.
170
+ * Iterated by the resolver merge helpers so adding a new key here is the
171
+ * single point of change when a new field variant is introduced.
172
+ */
173
+ declare const RESOLVER_KEYS: readonly ["string", "number", "boolean", "null", "enum", "object", "array", "tuple", "record", "union", "discriminatedUnion", "conditional", "negation", "literal", "file", "never", "unknown"];
174
+ type ResolverKey = (typeof RESOLVER_KEYS)[number];
175
+ /**
176
+ * Map a schema type to the resolver key that handles it.
177
+ * Every WalkedField variant has a direct resolver key — exhaustive switch
178
+ * ensures new variants surface as a type error rather than silently
179
+ * falling through to "unknown".
180
+ */
181
+ declare function typeToKey(type: WalkedField["type"]): ResolverKey;
182
+ /**
183
+ * Look up the render function for a schema type in a ComponentResolver.
184
+ */
185
+ declare function getRenderFunction(type: WalkedField["type"], resolver: ComponentResolver): RenderFunction | undefined;
186
+ /**
187
+ * Look up the render function for a schema type in an HtmlResolver.
188
+ */
189
+ declare function getHtmlRenderFn(type: WalkedField["type"], resolver: HtmlResolver): HtmlRenderFunction | undefined;
190
+ /**
191
+ * Merge two ComponentResolvers — user values take priority, fallback fills gaps.
192
+ */
193
+ declare function mergeResolvers(user: ComponentResolver, fallback: ComponentResolver): ComponentResolver;
194
+ /**
195
+ * Merge two HtmlResolvers — user values take priority, fallback fills gaps.
196
+ */
197
+ declare function mergeHtmlResolvers(user: HtmlResolver, fallback: HtmlResolver): HtmlResolver;
198
+ //#endregion
199
+ export { AllConstraints, BaseFieldProps, ComponentResolver, HtmlRenderFunction, HtmlRenderProps, HtmlResolver, RESOLVER_KEYS, RenderFunction, RenderProps, WidgetMap, buildRenderProps, getHtmlRenderFn, getRenderFunction, mergeHtmlResolvers, mergeResolvers, typeToKey };
@@ -29,6 +29,11 @@ function buildRenderProps(tree, value, onChange, renderChild, path) {
29
29
  if (tree.examples !== void 0) props.examples = tree.examples;
30
30
  return props;
31
31
  }
32
+ /**
33
+ * Canonical list of resolver keys, one per {@link WalkedField} variant.
34
+ * Iterated by the resolver merge helpers so adding a new key here is the
35
+ * single point of change when a new field variant is introduced.
36
+ */
32
37
  const RESOLVER_KEYS = [
33
38
  "string",
34
39
  "number",
@@ -1,4 +1,4 @@
1
- import { i as DiagnosticsOptions } from "../diagnostics-Cbwak-ZX.mjs";
1
+ import { i as DiagnosticsOptions } from "../diagnostics-BTrm3O6J.mjs";
2
2
  import { NodeTransform } from "./normalise.mjs";
3
3
 
4
4
  //#region src/core/swagger2.d.ts