schema-components 1.21.0 → 1.23.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 (91) hide show
  1. package/README.md +3 -1
  2. package/dist/core/adapter.d.mts +115 -4
  3. package/dist/core/adapter.mjs +405 -75
  4. package/dist/core/constraints.d.mts +2 -2
  5. package/dist/core/constraints.mjs +0 -7
  6. package/dist/core/cssClasses.d.mts +52 -0
  7. package/dist/core/cssClasses.mjs +51 -0
  8. package/dist/core/diagnostics.d.mts +1 -1
  9. package/dist/core/errors.d.mts +1 -1
  10. package/dist/core/errors.mjs +5 -13
  11. package/dist/core/fieldOrder.d.mts +1 -1
  12. package/dist/core/formats.d.mts +30 -2
  13. package/dist/core/formats.mjs +33 -1
  14. package/dist/core/idPath.d.mts +54 -0
  15. package/dist/core/idPath.mjs +66 -0
  16. package/dist/core/limits.d.mts +2 -0
  17. package/dist/core/limits.mjs +23 -0
  18. package/dist/core/merge.d.mts +10 -1
  19. package/dist/core/merge.mjs +49 -10
  20. package/dist/core/normalise.d.mts +40 -3
  21. package/dist/core/normalise.mjs +2 -2
  22. package/dist/core/openapi30.d.mts +15 -1
  23. package/dist/core/openapi30.mjs +2 -2
  24. package/dist/core/openapiConstants.d.mts +67 -0
  25. package/dist/core/openapiConstants.mjs +90 -0
  26. package/dist/core/ref.d.mts +2 -2
  27. package/dist/core/ref.mjs +85 -6
  28. package/dist/core/refChain.d.mts +70 -0
  29. package/dist/core/refChain.mjs +44 -0
  30. package/dist/core/renderer.d.mts +1 -1
  31. package/dist/core/renderer.mjs +0 -2
  32. package/dist/core/swagger2.d.mts +1 -1
  33. package/dist/core/swagger2.mjs +1 -1
  34. package/dist/core/typeInference.d.mts +982 -2
  35. package/dist/core/types.d.mts +2 -2
  36. package/dist/core/types.mjs +1 -4
  37. package/dist/core/unionMatch.d.mts +36 -0
  38. package/dist/core/unionMatch.mjs +53 -0
  39. package/dist/core/version.d.mts +1 -1
  40. package/dist/core/version.mjs +29 -17
  41. package/dist/core/walkBuilders.d.mts +23 -4
  42. package/dist/core/walkBuilders.mjs +27 -7
  43. package/dist/core/walker.d.mts +1 -1
  44. package/dist/core/walker.mjs +123 -47
  45. package/dist/{diagnostics-CbBPsxSt.d.mts → diagnostics-BS2kaUyE.d.mts} +1 -1
  46. package/dist/{errors-QEwOtQAA.d.mts → errors-g_MCTQel.d.mts} +10 -16
  47. package/dist/html/a11y.d.mts +9 -4
  48. package/dist/html/a11y.mjs +10 -12
  49. package/dist/html/renderToHtml.d.mts +10 -3
  50. package/dist/html/renderToHtml.mjs +13 -3
  51. package/dist/html/renderToHtmlStream.d.mts +2 -2
  52. package/dist/html/renderToHtmlStream.mjs +12 -1
  53. package/dist/html/renderers.d.mts +43 -8
  54. package/dist/html/renderers.mjs +136 -116
  55. package/dist/html/streamRenderers.d.mts +6 -6
  56. package/dist/html/streamRenderers.mjs +129 -89
  57. package/dist/limits-Cw5QZND8.d.mts +29 -0
  58. package/dist/{normalise-DaSrnr8g.mjs → normalise-DCYp06Sr.mjs} +770 -227
  59. package/dist/openapi/ApiCallbacks.d.mts +1 -1
  60. package/dist/openapi/ApiLinks.d.mts +1 -1
  61. package/dist/openapi/ApiResponseHeaders.d.mts +1 -1
  62. package/dist/openapi/ApiSecurity.d.mts +1 -1
  63. package/dist/openapi/ApiSecurity.mjs +16 -2
  64. package/dist/openapi/components.d.mts +234 -23
  65. package/dist/openapi/components.mjs +183 -52
  66. package/dist/openapi/parser.d.mts +9 -8
  67. package/dist/openapi/parser.mjs +252 -70
  68. package/dist/openapi/resolve.d.mts +31 -15
  69. package/dist/openapi/resolve.mjs +260 -40
  70. package/dist/react/SchemaComponent.d.mts +126 -36
  71. package/dist/react/SchemaComponent.mjs +95 -57
  72. package/dist/react/SchemaView.d.mts +30 -10
  73. package/dist/react/SchemaView.mjs +2 -2
  74. package/dist/react/a11y.d.mts +21 -0
  75. package/dist/react/a11y.mjs +24 -0
  76. package/dist/react/fieldPath.d.mts +1 -1
  77. package/dist/react/headless.d.mts +1 -1
  78. package/dist/react/headless.mjs +1 -2
  79. package/dist/react/headlessRenderers.d.mts +9 -11
  80. package/dist/react/headlessRenderers.mjs +51 -102
  81. package/dist/{ref-si8ViYun.d.mts → ref-DjLEKa_E.d.mts} +38 -3
  82. package/dist/{renderer-DI6ZYf7a.d.mts → renderer-CXJ8y0qw.d.mts} +2 -2
  83. package/dist/themes/mantine.d.mts +1 -1
  84. package/dist/themes/mui.d.mts +1 -1
  85. package/dist/themes/radix.d.mts +1 -1
  86. package/dist/themes/shadcn.d.mts +1 -1
  87. package/dist/themes/shadcn.mjs +2 -1
  88. package/dist/{types-BnxPEElk.d.mts → types-BTB73MB8.d.mts} +35 -14
  89. package/dist/{version-D-u7aMfy.d.mts → version-BFTVLsdb.d.mts} +7 -1
  90. package/package.json +1 -3
  91. package/dist/typeInference-Bxw3NOG1.d.mts +0 -647
@@ -1,9 +1,10 @@
1
1
  import { getProperty, hasProperty, isObject } from "./guards.mjs";
2
+ import "./limits.mjs";
2
3
  import { SchemaNormalisationError } from "./errors.mjs";
3
- import { emitDiagnostic } from "./diagnostics.mjs";
4
+ import { appendPointer, emitDiagnostic } from "./diagnostics.mjs";
4
5
  import { dereference } from "./ref.mjs";
5
6
  import { detectOpenApiVersion, inferJsonSchemaDraftWithReason, isSwagger2, matchJsonSchemaDraftUri } from "./version.mjs";
6
- import { a as normaliseOpenApiSchemas, i as normaliseJsonSchema$1 } from "../normalise-DaSrnr8g.mjs";
7
+ import { a as normaliseJsonSchema$1, o as normaliseOpenApiSchemas } from "../normalise-DCYp06Sr.mjs";
7
8
  import { z } from "zod";
8
9
  //#region src/core/adapter.ts
9
10
  /**
@@ -18,60 +19,295 @@ import { z } from "zod";
18
19
  * All narrowing uses type guards — no type assertions.
19
20
  */
20
21
  const schemaCache = /* @__PURE__ */ new WeakMap();
22
+ /**
23
+ * Classify the input schema by its structural markers.
24
+ *
25
+ * - `zod4` — has a `_zod` marker (further validation that `_zod.def` is a
26
+ * non-null object happens inside `normaliseZod4`).
27
+ * - `zod3` — has `_def` and no `_zod`. The `typeName` field is no longer
28
+ * required: any `_def` without `_zod` is treated as a probable Zod 3
29
+ * schema. Third-party libraries that expose `_def` without `_zod` are
30
+ * nearly always Zod 3 forks; surfacing the migration message is the
31
+ * correct response.
32
+ * - `openapi` — has `openapi` or `swagger` at the root.
33
+ * - `unsupported-schema-lib` — has `parse` and `safeParse` callables but
34
+ * no `_zod` and no `_def` marker. This catches Standard Schema
35
+ * implementations (valibot, arktype, etc.) that would otherwise flow
36
+ * through as "malformed JSON Schema".
37
+ * - `jsonSchema` — fallback for anything that does not match the above.
38
+ */
21
39
  function detectSchemaKind(input) {
22
40
  if (hasProperty(input, "_zod")) return "zod4";
23
41
  if (hasProperty(input, "_def") && !hasProperty(input, "_zod")) return "zod3";
24
42
  if (hasProperty(input, "openapi") || hasProperty(input, "swagger")) return "openapi";
43
+ if (isLikelyOtherSchemaLib(input)) return "unsupported-schema-lib";
25
44
  return "jsonSchema";
26
45
  }
27
46
  /**
28
- * Wraps z.toJSONSchema() for a runtime-validated Zod schema.
47
+ * Heuristic: a non-Zod object that exposes either a Standard Schema
48
+ * `~standard.validate` entry point (valibot, arktype, and any pure
49
+ * Standard-Schema-conformant library) or both legacy `.parse`/`.safeParse`
50
+ * callables is almost certainly an instance of a competing schema
51
+ * library. schema-components requires Zod 4 throughout — surfacing the
52
+ * unsupported library by name beats letting the input drop through to
53
+ * the JSON Schema branch where it would fail as "malformed JSON Schema"
54
+ * without explanation.
55
+ *
56
+ * Standard Schema detection takes priority: the spec mandates a
57
+ * `~standard` property carrying `{ validate, vendor, version }`. Pure
58
+ * Standard Schema implementations may not expose any `.parse`/`.safeParse`
59
+ * surface (those are a Zod / convenience API, not part of the spec), so
60
+ * the legacy heuristic alone would miss them. See
61
+ * https://standardschema.dev/ for the contract.
62
+ */
63
+ function isLikelyOtherSchemaLib(input) {
64
+ if (!isObject(input)) return false;
65
+ if (hasProperty(input, "_zod") || hasProperty(input, "_def")) return false;
66
+ if (isObject(input["~standard"])) return true;
67
+ const parse = input.parse;
68
+ const safeParse = input.safeParse;
69
+ return typeof parse === "function" && typeof safeParse === "function";
70
+ }
71
+ /**
72
+ * Extract the Standard Schema vendor string from a non-Zod input, when
73
+ * present. Returns `undefined` if the input does not advertise itself
74
+ * via the `~standard.vendor` field. Used to enrich the
75
+ * `unsupported-schema` error message with the library name so the
76
+ * consumer knows whether they have valibot, arktype, or another
77
+ * implementation in front of them.
78
+ */
79
+ function extractStandardSchemaVendor(input) {
80
+ const vendor = getProperty(getProperty(input, "~standard"), "vendor");
81
+ return typeof vendor === "string" && vendor.length > 0 ? vendor : void 0;
82
+ }
83
+ function callToJsonSchema(schema, io = "output") {
84
+ try {
85
+ return z.toJSONSchema(schema, {
86
+ target: "draft-2020-12",
87
+ unrepresentable: "throw",
88
+ cycles: "ref",
89
+ io
90
+ });
91
+ } catch (err) {
92
+ throw classifyZodConversionError(err, schema);
93
+ }
94
+ }
95
+ /**
96
+ * Zod `def.type` tags that have no useful JSON Schema representation but
97
+ * do NOT throw when passed through `z.toJSONSchema`. Each tag is handled
98
+ * by Zod with a processor that silently rewrites the output:
99
+ *
100
+ * - `promise` — `promiseProcessor` unwraps the inner type, dropping the
101
+ * `Promise<...>` wrapper without any error. (`json-schema-processors.ts`,
102
+ * the body of `promiseProcessor` calls `process(def.innerType, ...)`.)
103
+ * schema-components considers this a silent shape mismatch — the input
104
+ * tree advertised a `Promise<T>` and the consumer would render `T`
105
+ * without ever being told the wrapping was lost.
106
+ *
107
+ * Detection happens BEFORE the call to `z.toJSONSchema` so the response is
108
+ * an immediate `SchemaNormalisationError` with `kind:
109
+ * "zod-type-unrepresentable"`, matching the philosophy of
110
+ * `UnrepresentableZodType` in `typeInference.ts` — these types are
111
+ * rejected, not coerced.
112
+ */
113
+ const PRECONVERSION_UNREPRESENTABLE_TAGS = new Map([["promise", "z.promise(T) cannot be represented in JSON Schema. Zod silently unwraps it to the inner type, which would leave the rendered schema out of sync with the source. Resolve the promise at the data boundary before passing the value to the component."]]);
114
+ /**
115
+ * Pre-conversion screening. Walks the entire Zod schema tree looking for
116
+ * silently-misrendered or caveat-bearing constructs and surfaces each as
117
+ * either a hard rejection (raised as a `SchemaNormalisationError`) or a
118
+ * diagnostic on the configured sink:
119
+ *
120
+ * - `z.promise(T)` at any depth → rejection (see
121
+ * {@link PRECONVERSION_UNREPRESENTABLE_TAGS}). Each nested occurrence
122
+ * first emits a `zod-promise-nested-unwrap` diagnostic so consumers
123
+ * with a sink see every offending location before the throw fires.
124
+ * The root occurrence still throws via the same path so behaviour is
125
+ * uniform regardless of position in the tree.
126
+ * - `z.codec(...)` at the root → `zod-codec-output-only` diagnostic.
127
+ * - `z.codec(...)` nested below the root →
128
+ * `zod-codec-nested-output-only` diagnostic per occurrence.
129
+ * - `z.preprocess(...)` at any depth → `zod-preprocess-output-only`
130
+ * diagnostic per occurrence. Preprocess never throws inside Zod (it
131
+ * silently rewrites to the output side), so the diagnostic is the
132
+ * only consumer-visible signal.
133
+ *
134
+ * Detection is structural — `_zod.def.type` plus `_zod.traits` (where
135
+ * present) — and is depth-capped via {@link MAX_REF_DEPTH} with a
136
+ * `visited` set to defend against cyclic graphs. JSON-pointer fragments
137
+ * are accumulated as the walk descends so diagnostics report the exact
138
+ * subschema location rather than `""`.
139
+ *
140
+ * Design choice: `z.never()` is NOT classified here. The Zod processor
141
+ * for `never` already produces `{ not: {} }`, which the walker
142
+ * understands via its `walkBooleanSchema(false)` branch (`walker.ts`
143
+ * boolean-schema handling). Throwing a `zod-type-unrepresentable` for
144
+ * `never` would break the legitimate "this field cannot hold any value"
145
+ * use case that the walker already supports. Documented for posterity
146
+ * so future passes do not "fix" it.
147
+ */
148
+ function screenPreConversion(input, diagnostics) {
149
+ let rejection;
150
+ screenPreConversionWalk(input, "", 0, true, /* @__PURE__ */ new Set(), diagnostics, (err) => {
151
+ rejection ??= err;
152
+ });
153
+ if (rejection !== void 0) throw rejection;
154
+ }
155
+ /**
156
+ * Inner recursion for {@link screenPreConversion}. Visits every Zod
157
+ * node reachable from `node`, emitting diagnostics and capturing
158
+ * rejections through `recordRejection`. The walk is targeted: only
159
+ * `_zod.def` is descended into (sibling `_zod.*` members are
160
+ * implementation surface and never carry user schemas — same rule as
161
+ * {@link containsNestedZod3Inner}).
29
162
  *
30
- * The _zod guard in normaliseZod4 has confirmed this is a valid Zod schema,
31
- * but TypeScript cannot represent "has _zod.def" as the $ZodType parameter
32
- * that z.toJSONSchema expects. This is the library boundary equivalent of
33
- * object Record<string, unknown> — the type mismatch is genuinely unavoidable.
163
+ * The `pointer` parameter tracks the JSON Pointer to the current
164
+ * subschema so diagnostics carry an accurate location. The `isRoot`
165
+ * flag distinguishes the entry call from recursive descents so
166
+ * `zod-codec-output-only` (root) and `zod-codec-nested-output-only`
167
+ * (nested) fire from the same code path.
168
+ */
169
+ function screenPreConversionWalk(node, pointer, depth, isRoot, visited, diagnostics, recordRejection) {
170
+ if (depth >= 64) return;
171
+ if (!isObject(node)) return;
172
+ if (visited.has(node)) return;
173
+ visited.add(node);
174
+ const zod = getProperty(node, "_zod");
175
+ if (!isObject(zod)) return;
176
+ const def = getProperty(zod, "def");
177
+ if (!isObject(def)) return;
178
+ const tag = def.type;
179
+ if (typeof tag === "string") {
180
+ const unrepresentableMessage = PRECONVERSION_UNREPRESENTABLE_TAGS.get(tag);
181
+ if (unrepresentableMessage !== void 0) {
182
+ if (tag === "promise") emitDiagnostic(diagnostics, {
183
+ code: "zod-promise-nested-unwrap",
184
+ message: `z.promise(...) detected at ${formatPointer(pointer)}. Zod silently unwraps it to the inner type, which would leave the rendered schema out of sync with the source. Resolve the promise at the data boundary before passing the value to the component.`,
185
+ pointer,
186
+ detail: { zodType: "promise" }
187
+ });
188
+ recordRejection(new SchemaNormalisationError(unrepresentableMessage, node, "zod-type-unrepresentable", tag));
189
+ }
190
+ }
191
+ if (tag === "pipe" && hasTrait(zod, "$ZodCodec")) if (isRoot) emitDiagnostic(diagnostics, {
192
+ code: "zod-codec-output-only",
193
+ message: "z.codec(...) was passed at the schema root. Only the OUTPUT side is rendered by schema-components; the input side may differ. If you intend to render the input side instead, restructure the codec so the input type is the rendered shape.",
194
+ pointer,
195
+ detail: { zodType: "codec" }
196
+ });
197
+ else emitDiagnostic(diagnostics, {
198
+ code: "zod-codec-nested-output-only",
199
+ message: `z.codec(...) detected at ${formatPointer(pointer)}. Only the OUTPUT side is rendered by schema-components; the input side is invisible to the converted schema even though safeParse still consumes the input shape.`,
200
+ pointer,
201
+ detail: { zodType: "codec" }
202
+ });
203
+ if (tag === "pipe" && hasTrait(zod, "$ZodPreprocess")) emitDiagnostic(diagnostics, {
204
+ code: "zod-preprocess-output-only",
205
+ message: `z.preprocess(...) detected at ${formatPointer(pointer)}. Zod silently renders the OUTPUT-side schema; the preprocess function and its input shape are invisible to the rendered schema. If you need the input shape, restructure the schema to declare it directly.`,
206
+ pointer,
207
+ detail: { zodType: "preprocess" }
208
+ });
209
+ screenPreConversionDescend(def, pointer, depth + 1, visited, diagnostics, recordRejection);
210
+ }
211
+ /**
212
+ * Descend into the values of a Zod `def` object, visiting every nested
213
+ * Zod schema. `def` shapes are heterogeneous, so we walk recursively
214
+ * through plain objects and arrays until we find a value with a
215
+ * `_zod.def` marker — those nodes are the user-supplied sub-schemas.
34
216
  *
35
- * Any exception thrown by z.toJSONSchema is classified into a
36
- * SchemaNormalisationError so the caller does not have to re-parse error
37
- * message strings. The classification covers:
217
+ * Pointer accumulation:
38
218
  *
39
- * - Nested Zod 3 schemas inside a Zod 4 tree → zod3-unsupported.
40
- * Detected structurally (presence of `_def.typeName` markers anywhere
41
- * in the schema tree) so the check works across V8, JavaScriptCore,
42
- * and SpiderMonkey, none of which agree on the wording of
43
- * "Cannot read properties of undefined".
44
- * - Transforms → zod-transform-unsupported. This also catches `z.codec(…)`
45
- * because Zod implements codecs as a pipe + transform internally, so
46
- * they trip the same processor when round-tripping is forced. (Plain
47
- * `z.toJSONSchema(codec)` itself does NOT throw because Zod picks one
48
- * side of the codec; the static rejection in `typeInference.ts` is the
49
- * compile-time guard.)
50
- * - Dynamic catch values whose handler throws → zod-type-unrepresentable
51
- * with zodType "dynamic-catch".
52
- * - Unrepresentable types — bigint, date, map, set, symbol, function, custom,
53
- * undefined, void, NaN, and the literal-only forms `z.literal(undefined)`
54
- * ("undefined-literal") and `z.literal(<bigint>)` ("bigint-literal") →
55
- * zod-type-unrepresentable.
56
- * - The catch-all "Non-representable type encountered: <type>" fallback Zod
57
- * emits for any new schema kind without a registered processor →
58
- * zod-type-unrepresentable with zodType set to the offending def.type.
59
- * - Cycle detected (`cycles: "throw"`) → zod-cycle-detected.
60
- * - Duplicate schema id → zod-duplicate-id.
61
- * - "Unprocessed schema. This is a bug in Zod." → zod-conversion-bug.
62
- * - "Error converting schema to JSON." → zod-conversion-failed (explicit
63
- * classification rather than the generic fallback so the contract test
64
- * protects the prefix from drift).
65
- * - Anything else → zod-conversion-failed.
219
+ * - For `def.shape.<key>` we emit pointers of the form
220
+ * `/properties/<key>`, matching the JSON Schema rendering of an
221
+ * object's properties so diagnostics line up with what consumers see
222
+ * in the rendered output.
223
+ * - For `def.items[<i>]` we emit `/items/<i>`.
224
+ * - For `def.options[<i>]` we emit `/anyOf/<i>` so union members line
225
+ * up with their JSON Schema position.
226
+ * - For pipe `def.in` / `def.out` we emit `/in` / `/out`.
227
+ * - Everything else descends without extending the pointer (the
228
+ * diagnostic stays anchored at the parent location).
66
229
  *
67
- * The original error is preserved on each classified error via the `cause`
68
- * field so consumers can still inspect the Zod stack trace.
230
+ * The pointer scheme is deliberately conservative it errs on the
231
+ * side of "parent-anchored" when the JSON Schema name for a Zod field
232
+ * is ambiguous, rather than fabricating a synthetic location.
69
233
  */
70
- function callToJsonSchema(schema) {
234
+ function screenPreConversionDescend(def, parentPointer, depth, visited, diagnostics, recordRejection) {
235
+ if (depth >= 64) return;
236
+ const shape = getProperty(def, "shape");
237
+ if (isObject(shape)) {
238
+ const shapeBase = appendPointer(parentPointer, "properties");
239
+ for (const [key, value] of Object.entries(shape)) screenPreConversionWalk(value, appendPointer(shapeBase, key), depth + 1, false, visited, diagnostics, recordRejection);
240
+ }
241
+ const items = getProperty(def, "items");
242
+ if (Array.isArray(items)) {
243
+ const itemsBase = appendPointer(parentPointer, "items");
244
+ items.forEach((item, index) => {
245
+ screenPreConversionWalk(item, appendPointer(itemsBase, String(index)), depth + 1, false, visited, diagnostics, recordRejection);
246
+ });
247
+ } else if (isObject(items)) screenPreConversionWalk(items, appendPointer(parentPointer, "items"), depth + 1, false, visited, diagnostics, recordRejection);
248
+ const options = getProperty(def, "options");
249
+ if (Array.isArray(options)) {
250
+ const optionsBase = appendPointer(parentPointer, "anyOf");
251
+ options.forEach((option, index) => {
252
+ screenPreConversionWalk(option, appendPointer(optionsBase, String(index)), depth + 1, false, visited, diagnostics, recordRejection);
253
+ });
254
+ }
255
+ const inSide = getProperty(def, "in");
256
+ if (isObject(inSide)) screenPreConversionWalk(inSide, appendPointer(parentPointer, "in"), depth + 1, false, visited, diagnostics, recordRejection);
257
+ const outSide = getProperty(def, "out");
258
+ if (isObject(outSide)) screenPreConversionWalk(outSide, appendPointer(parentPointer, "out"), depth + 1, false, visited, diagnostics, recordRejection);
259
+ const innerType = getProperty(def, "innerType");
260
+ if (isObject(innerType)) screenPreConversionWalk(innerType, parentPointer, depth + 1, false, visited, diagnostics, recordRejection);
261
+ const valueType = getProperty(def, "valueType");
262
+ if (isObject(valueType)) screenPreConversionWalk(valueType, appendPointer(parentPointer, "additionalProperties"), depth + 1, false, visited, diagnostics, recordRejection);
263
+ const inner = safeCallNoArgs(getProperty(def, "getter"));
264
+ if (isObject(inner)) screenPreConversionWalk(inner, parentPointer, depth + 1, false, visited, diagnostics, recordRejection);
265
+ }
266
+ /**
267
+ * Format an empty pointer as `<root>` so error messages do not contain
268
+ * a stray bare `""`. Non-empty pointers are returned verbatim.
269
+ */
270
+ function formatPointer(pointer) {
271
+ return pointer === "" ? "<root>" : pointer;
272
+ }
273
+ /**
274
+ * True when a Zod node's `_zod.traits` set contains the named marker.
275
+ * Returns false when traits is absent or not a Set — Zod always
276
+ * populates it on real schemas, so the missing-Set case is treated as
277
+ * "marker not present".
278
+ */
279
+ function hasTrait(zod, traitName) {
280
+ const traits = zod.traits;
281
+ if (traits instanceof Set) return traits.has(traitName);
282
+ return false;
283
+ }
284
+ /**
285
+ * Type guard narrowing `unknown` to a zero-argument function returning
286
+ * `unknown`. The narrowing is genuinely structural: `typeof === "function"`
287
+ * at runtime is exactly the membership test we want, and Zod has no
288
+ * way to make a getter "have the wrong arity" without breaking its own
289
+ * lazy implementation. Surfacing the narrowing through a guard means
290
+ * the call site can invoke `fn()` without an `as` assertion and the
291
+ * boundary lives in one named, documented location.
292
+ */
293
+ function isNoArgFunction(value) {
294
+ return typeof value === "function";
295
+ }
296
+ /**
297
+ * Invoke a value as a zero-argument function safely, returning whatever
298
+ * the function returns or `undefined` if it throws or is not callable.
299
+ * Centralises the lazy-schema getter invocation that both
300
+ * {@link containsNestedZod3Inner} and {@link screenPreConversionDescend}
301
+ * need; the throw is swallowed because the absence of a materialisable
302
+ * inner is not a screening concern — downstream `z.toJSONSchema` will
303
+ * surface any genuine construction failure with its own message.
304
+ */
305
+ function safeCallNoArgs(candidate) {
306
+ if (!isNoArgFunction(candidate)) return void 0;
71
307
  try {
72
- return z.toJSONSchema(schema);
73
- } catch (err) {
74
- throw classifyZodConversionError(err, schema);
308
+ return candidate();
309
+ } catch {
310
+ return;
75
311
  }
76
312
  }
77
313
  /**
@@ -106,14 +342,27 @@ function unrepresentableMessage(typeName, fullMessage) {
106
342
  * test suite asserts no two `prefix` values are prefixes of each other —
107
343
  * any future rule that breaks the invariant fails the build.
108
344
  *
109
- * Verbatim sources (kept aligned with `tests/zod-error-wording-contract.unit.test.ts`):
110
- * - zod/src/v4/core/json-schema-processors.ts L104 (bigint), L110 (symbol),
111
- * L126 (undefined), L132 (void), L150 (date), L169 (literal-undefined),
112
- * L175 (literal-bigint), L204 (NaN), L246 (custom), L252 (function),
113
- * L258 (transforms), L264 (map), L270 (set), L521 (dynamic catch).
114
- * - zod/src/v4/core/to-json-schema.ts L182 (non-representable type fallback),
115
- * L225 + L364 (unprocessed schema), L235 (duplicate id), L307 (cycle),
116
- * L522 (error converting).
345
+ * Verbatim sources (kept aligned with `tests/zod-error-wording-contract.unit.test.ts`).
346
+ * Source files are referenced by message-content anchors rather than line
347
+ * numbers line numbers drift across Zod patch releases but the message
348
+ * strings themselves are stable and protected by the contract test suite:
349
+ *
350
+ * - `zod/src/v4/core/json-schema-processors.ts` emits `BigInt cannot be
351
+ * represented`, `Symbols cannot be represented`, `Undefined cannot be
352
+ * represented`, `Void cannot be represented`, `Date cannot be
353
+ * represented`, `Literal \`undefined\` cannot be represented`,
354
+ * `BigInt literals cannot be represented`, `NaN cannot be represented`,
355
+ * `Custom types cannot be represented`, `Function types cannot be
356
+ * represented`, `Transforms cannot be represented`, `Map cannot be
357
+ * represented`, `Set cannot be represented`, `Dynamic catch values are
358
+ * not supported`.
359
+ * - `zod/src/v4/core/to-json-schema.ts` — emits `[toJSONSchema]:
360
+ * Non-representable type encountered: ${def.type}` (the catch-all
361
+ * fallback), `Unprocessed schema. This is a bug in Zod.` (the
362
+ * internal-bug branch), `Duplicate schema id "${id}" detected during
363
+ * JSON Schema conversion.` (the duplicate-id branch), `Cycle detected:
364
+ * ` (the cycle-throw branch), and `Error converting schema to JSON.`
365
+ * (the Standard Schema boundary wrapper).
117
366
  */
118
367
  const CLASSIFIER_RULES = [
119
368
  {
@@ -203,8 +452,11 @@ const CLASSIFIER_RULES = [
203
452
  prefix: "[toJSONSchema]: Non-representable type encountered:",
204
453
  kind: "zod-type-unrepresentable",
205
454
  build: (match, cause, schema, full) => {
206
- const trailing = match[1]?.trim() ?? "";
207
- const typeName = trailing.length > 0 ? trailing.split(/\s+/)[0] : void 0;
455
+ const trailing = match[1];
456
+ if (trailing === void 0) return describeUnparsableZodWording("Non-representable type prefix matched but no trailing capture", full, schema, cause);
457
+ const trimmed = trailing.trim();
458
+ const firstToken = trimmed.length > 0 ? trimmed.split(/\s+/)[0] : void 0;
459
+ const typeName = firstToken !== void 0 && firstToken.length > 0 ? firstToken : void 0;
208
460
  return new SchemaNormalisationError(`Zod encountered a schema kind${typeName !== void 0 ? ` "${typeName}"` : ""} with no JSON Schema processor registered. This usually means Zod added a new schema type that schema-components does not yet support. Original message: ${full}`, schema, "zod-type-unrepresentable", typeName, cause);
209
461
  }
210
462
  },
@@ -212,16 +464,22 @@ const CLASSIFIER_RULES = [
212
464
  prefix: "Cycle detected: ",
213
465
  kind: "zod-cycle-detected",
214
466
  build: (match, cause, schema, full) => {
215
- return new SchemaNormalisationError(`Zod detected a cycle in the schema graph at ${(match[1] ?? "").split(/\s+/)[0] ?? ""}. Cycles can only be converted when z.toJSONSchema is called with { cycles: "ref" } — schema-components calls it without options for cache safety, so the cycle surfaces as an error. Restructure the schema to break the cycle, or use a $ref-based definition. Original message: ${full}`, schema, "zod-cycle-detected", void 0, cause);
467
+ const trailing = match[1];
468
+ if (trailing === void 0) return describeUnparsableZodWording("Cycle detected prefix matched but no trailing capture", full, schema, cause);
469
+ const path = trailing.split(/\s+/)[0];
470
+ if (path === void 0 || path.length === 0) return describeUnparsableZodWording("Cycle detected message contained no pointer token", full, schema, cause);
471
+ return new SchemaNormalisationError(`Zod detected a cycle in the schema graph at ${path}. schema-components calls z.toJSONSchema with { cycles: "ref" } so legitimate cyclic graphs convert to $ref pairs; this error surfaces only when Zod is unable to break the cycle even under the "ref" policy. Restructure the schema to break the cycle, or use an explicit $ref-based definition. Original message: ${full}`, schema, "zod-cycle-detected", void 0, cause);
216
472
  }
217
473
  },
218
474
  {
219
475
  prefix: "Duplicate schema id \"",
220
476
  kind: "zod-duplicate-id",
221
477
  build: (match, cause, schema, full) => {
222
- const trailing = match[1] ?? "";
478
+ const trailing = match[1];
479
+ if (trailing === void 0) return describeUnparsableZodWording("Duplicate schema id prefix matched but no trailing capture", full, schema, cause);
223
480
  const closing = trailing.indexOf("\"");
224
- return new SchemaNormalisationError(`Two different Zod schemas share the same id "${closing === -1 ? trailing : trailing.slice(0, closing)}". JSON Schema requires distinct ids when multiple schemas are bundled together. Give each schema its own .meta({ id: ... }) or remove the duplicate. Original message: ${full}`, schema, "zod-duplicate-id", void 0, cause);
481
+ if (closing === -1) return describeUnparsableZodWording("Duplicate schema id message had no closing quote", full, schema, cause);
482
+ return new SchemaNormalisationError(`Two different Zod schemas share the same id "${trailing.slice(0, closing)}". JSON Schema requires distinct ids when multiple schemas are bundled together. Give each schema its own .meta({ id: ... }) or remove the duplicate. Original message: ${full}`, schema, "zod-duplicate-id", void 0, cause);
225
483
  }
226
484
  },
227
485
  {
@@ -244,33 +502,79 @@ const COMPILED_CLASSIFIER_RULES = CLASSIFIER_RULES.map((rule) => ({
244
502
  pattern: anchored(rule.prefix)
245
503
  }));
246
504
  /**
247
- * Walk an arbitrary value looking for Zod 3 markers (`_def.typeName`).
248
- * Zod 4 schemas always carry a `_zod.def`; Zod 3 schemas carry `_def`
249
- * with a `typeName` field. Presence of the latter anywhere in the tree
250
- * means a Zod 3 schema was nested inside a Zod 4 input, which is what
251
- * trips the V8 `"Cannot read properties of undefined"` failure.
505
+ * Build a structured `zod-conversion-failed` error for the case where a
506
+ * classifier rule's prefix matched but the trailing capture or follow-on
507
+ * parsing could not extract the expected payload (cycle pointer,
508
+ * duplicate id, non-representable type name, ...).
509
+ *
510
+ * This replaces the previous pattern of substituting an empty string
511
+ * fallback — `match[1] ?? ""` would silently produce error messages like
512
+ * `"Zod detected a cycle in the schema graph at ."` whenever Zod's
513
+ * wording drifted, hiding the regression behind a misleading message.
514
+ * Raising a wording-regression error instead surfaces the drift loudly
515
+ * so the classifier rule (and its contract test) can be repaired.
516
+ */
517
+ function describeUnparsableZodWording(reason, fullMessage, schema, cause) {
518
+ return new SchemaNormalisationError(`Zod error matched a classifier prefix but the trailing message could not be parsed (${reason}). This usually means Zod has reworded the error since the classifier was last updated — the matching rule in adapter.ts CLASSIFIER_RULES needs to be revised to track the new wording. Original message: ${fullMessage}`, schema, "zod-conversion-failed", void 0, cause);
519
+ }
520
+ /**
521
+ * Maximum recursion depth for {@link containsNestedZod3}. Reuses the
522
+ * shared {@link MAX_REF_DEPTH} so the runtime walk and the compile-time
523
+ * `DEFAULT_MAX_DEPTH` (type-aliased to the same value) stay in lockstep.
524
+ */
525
+ /**
526
+ * Walk an arbitrary value looking for Zod 3 markers (`_def` without
527
+ * `_zod`). Zod 4 schemas always carry `_zod.def`; Zod 3 schemas carry
528
+ * `_def` (with or without a `typeName` field — third-party Zod-3-style
529
+ * libraries occasionally omit `typeName`). Presence of `_def` without
530
+ * `_zod` anywhere in the tree means a Zod 3 (or Zod-3-like) schema was
531
+ * nested inside a Zod 4 input, which is what trips the V8
532
+ * `"Cannot read properties of undefined"` failure.
252
533
  *
253
534
  * Engine-agnostic by construction — the detector inspects schema shape
254
535
  * instead of pattern-matching against the runtime's TypeError message,
255
536
  * so it works equivalently under V8, JavaScriptCore (Bun/Safari), and
256
537
  * SpiderMonkey (Firefox) — none of which agree on the wording.
257
538
  *
258
- * The walk is bounded by an explicit `visited` set so cyclical references
259
- * cannot cause stack overflow. The recursion follows both array elements
260
- * and own enumerable properties of every object encountered.
539
+ * Performance shortcuts:
540
+ *
541
+ * - **Targeted descent into Zod 4 nodes.** Once a node is identified as a
542
+ * Zod 4 schema (`_zod.def` is an object), the only branch that can
543
+ * carry user-supplied sub-schemas is `_zod.def` itself. Zod's other
544
+ * internal members (`_zod.traits`, `_zod.parse`, `_zod.bag`, etc.) are
545
+ * implementation surface and never contain user schemas, so walking
546
+ * them on every conversion failure is wasted work. Switching to a
547
+ * targeted descent (only `_zod.def` plus the schema root's `_def`
548
+ * field) trims the walk dramatically.
549
+ * - **Depth cap.** Recursion is bounded by {@link MAX_REF_DEPTH}
550
+ * so a pathological schema graph cannot cause stack overflow. The
551
+ * `visited` set still defends against cyclic references; the depth
552
+ * cap defends against deep-but-acyclic trees.
261
553
  */
262
554
  function containsNestedZod3(value, visited) {
555
+ return containsNestedZod3Inner(value, visited, 0);
556
+ }
557
+ function containsNestedZod3Inner(value, visited, depth) {
558
+ if (depth >= 64) return false;
263
559
  if (value === null || typeof value !== "object") return false;
264
560
  if (visited.has(value)) return false;
265
561
  visited.add(value);
266
562
  if (Array.isArray(value)) {
267
- for (const item of value) if (containsNestedZod3(item, visited)) return true;
563
+ for (const item of value) if (containsNestedZod3Inner(item, visited, depth + 1)) return true;
268
564
  return false;
269
565
  }
270
566
  if (!isObject(value)) return false;
271
567
  const def = value._def;
272
- if (value._zod === void 0 && isObject(def) && typeof def.typeName === "string") return true;
273
- for (const key of Object.keys(value)) if (containsNestedZod3(value[key], visited)) return true;
568
+ const zod = value._zod;
569
+ if (zod === void 0 && isObject(def)) return true;
570
+ if (isObject(zod) && isObject(zod.def)) {
571
+ const def4 = zod.def;
572
+ if (def4.type === "lazy") {
573
+ if (containsNestedZod3Inner(safeCallNoArgs(def4.getter), visited, depth + 1)) return true;
574
+ }
575
+ return containsNestedZod3Inner(def4, visited, depth + 1);
576
+ }
577
+ for (const key of Object.keys(value)) if (containsNestedZod3Inner(value[key], visited, depth + 1)) return true;
274
578
  return false;
275
579
  }
276
580
  function classifyZodConversionError(err, schema) {
@@ -288,7 +592,9 @@ function classifyZodConversionError(err, schema) {
288
592
  */
289
593
  const __CLASSIFIER_RULES_FOR_TEST = CLASSIFIER_RULES;
290
594
  function normaliseSchema(input, ref, options) {
291
- if (ref === void 0 && isObject(input)) {
595
+ const usesDiagnostics = options?.diagnostics !== void 0;
596
+ const cacheEligible = ref === void 0 && isObject(input) && !usesDiagnostics;
597
+ if (cacheEligible) {
292
598
  const cached = schemaCache.get(input);
293
599
  if (cached !== void 0) return cached;
294
600
  }
@@ -296,11 +602,15 @@ function normaliseSchema(input, ref, options) {
296
602
  let result;
297
603
  switch (kind) {
298
604
  case "zod4":
299
- result = normaliseZod4(input);
605
+ result = normaliseZod4(input, options?.diagnostics);
300
606
  break;
301
607
  case "zod3":
302
608
  result = normaliseZod3(input);
303
609
  break;
610
+ case "unsupported-schema-lib": {
611
+ const vendor = extractStandardSchemaVendor(input);
612
+ throw new SchemaNormalisationError(`Input looks like a schema from a non-Zod library — ${vendor !== void 0 ? `it self-identifies as the Standard Schema implementation "${vendor}"` : "it exposes `parse` and `safeParse` but carries no Zod 4 (`_zod`) or Zod 3 (`_def`) marker"}. schema-components requires a Zod 4 schema. Convert the schema with the equivalent Zod 4 builder, or feed schema-components a JSON Schema / OpenAPI document instead. See the Zod 4 contract at https://zod.dev/v4 or run: pnpm add zod@^4`, input, "unsupported-schema");
613
+ }
304
614
  case "openapi":
305
615
  if (!isObject(input)) throw new SchemaNormalisationError("Invalid OpenAPI document", input, "openapi-invalid");
306
616
  result = normaliseOpenApi(input, ref, options);
@@ -310,13 +620,14 @@ function normaliseSchema(input, ref, options) {
310
620
  result = normaliseJsonSchema(input, options?.diagnostics);
311
621
  break;
312
622
  }
313
- if (ref === void 0 && isObject(input)) schemaCache.set(input, result);
623
+ if (cacheEligible) schemaCache.set(input, result);
314
624
  return result;
315
625
  }
316
- function normaliseZod4(input) {
626
+ function normaliseZod4(input, diagnostics) {
317
627
  const zod = getProperty(input, "_zod");
318
- if (!isObject(zod)) throw new SchemaNormalisationError("Invalid Zod 4 schema: missing _zod property", input, "invalid-zod");
319
- if (!("def" in zod)) throw new SchemaNormalisationError("Invalid Zod 4 schema: missing _zod.def", input, "invalid-zod");
628
+ if (!isObject(zod)) throw new SchemaNormalisationError("Input is not a valid Zod 4 schema: `_zod` is present but is not an object. schema-components expected a Zod 4 schema produced by the `zod` package version 4 or later. See the Zod 4 migration guide at https://zod.dev/v4/migration or run: pnpm add zod@^4", input, "unsupported-schema");
629
+ if (!isObject(getProperty(zod, "def"))) throw new SchemaNormalisationError("Input is not a valid Zod 4 schema: `_zod.def` is missing or not an object. schema-components expected a Zod 4 schema produced by the `zod` package version 4 or later. See the Zod 4 migration guide at https://zod.dev/v4/migration or run: pnpm add zod@^4", input, "unsupported-schema");
630
+ screenPreConversion(input, diagnostics);
320
631
  const jsonSchema = callToJsonSchema(input);
321
632
  if (!isObject(jsonSchema)) throw new SchemaNormalisationError("z.toJSONSchema() did not produce an object", input, "invalid-zod");
322
633
  return {
@@ -436,10 +747,27 @@ function resolveOpenApiRef(doc, ref) {
436
747
  }
437
748
  if (ref.startsWith("#/")) {
438
749
  const resolved = dereference(ref, doc);
439
- if (resolved !== void 0) return resolved;
750
+ if (resolved !== void 0 && typeof resolved !== "boolean") return resolved;
440
751
  }
441
752
  throw new Error(`Unsupported OpenAPI ref format: ${ref}`);
442
753
  }
754
+ /**
755
+ * Surface root-level metadata from the JSON Schema into the `rootMeta`
756
+ * shape consumed by the walker. Pulls `readOnly`, `writeOnly`,
757
+ * `description`, `title`, `deprecated`, `examples`, and `default`
758
+ * directly from the schema root.
759
+ *
760
+ * `examples` is forwarded only when present as an array (per JSON Schema
761
+ * Draft 2020-12 — Draft 04's `example` singular is normalised upstream).
762
+ * `default` is forwarded for any value the schema declares (any JSON
763
+ * value, including `null` and `false`); the presence check uses `in`
764
+ * so a literal `false` or `null` default is preserved.
765
+ *
766
+ * `examples` and `default` ride on the `[key: string]: unknown` index
767
+ * signature of {@link SchemaMeta}. They are not declared as named fields
768
+ * on `SchemaMeta` because that type lives in `types.ts` and is shared
769
+ * with the walker; the index signature is the agreed extension point.
770
+ */
443
771
  function extractRootMetaFromJson(jsonSchema) {
444
772
  const meta = {};
445
773
  if (jsonSchema.readOnly === true) meta.readOnly = true;
@@ -447,7 +775,9 @@ function extractRootMetaFromJson(jsonSchema) {
447
775
  if (typeof jsonSchema.description === "string") meta.description = jsonSchema.description;
448
776
  if (typeof jsonSchema.title === "string") meta.title = jsonSchema.title;
449
777
  if (typeof jsonSchema.deprecated === "boolean") meta.deprecated = jsonSchema.deprecated;
778
+ if (Array.isArray(jsonSchema.examples)) meta.examples = jsonSchema.examples;
779
+ if ("default" in jsonSchema) meta.default = jsonSchema.default;
450
780
  return Object.keys(meta).length > 0 ? meta : void 0;
451
781
  }
452
782
  //#endregion
453
- export { __CLASSIFIER_RULES_FOR_TEST, detectSchemaKind, normaliseSchema };
783
+ export { __CLASSIFIER_RULES_FOR_TEST, detectSchemaKind, extractRootMetaFromJson, normaliseSchema };
@@ -1,5 +1,5 @@
1
- import { D as StringConstraints, f as FileConstraints, t as ArrayConstraints, x as ObjectConstraints, y as NumberConstraints } from "../types-BnxPEElk.mjs";
2
- import { i as DiagnosticsOptions } from "../diagnostics-CbBPsxSt.mjs";
1
+ import { E as StringConstraints, f as FileConstraints, t as ArrayConstraints, x as ObjectConstraints, y as NumberConstraints } from "../types-BTB73MB8.mjs";
2
+ import { i as DiagnosticsOptions } from "../diagnostics-BS2kaUyE.mjs";
3
3
 
4
4
  //#region src/core/constraints.d.ts
5
5
  declare function extractStringConstraints(schema: Record<string, unknown>, diagnostics?: DiagnosticsOptions, pointer?: string): StringConstraints;
@@ -1,4 +1,3 @@
1
- import { isObject } from "./guards.mjs";
2
1
  import { emitDiagnostic } from "./diagnostics.mjs";
3
2
  import { FORMAT_PATTERNS } from "./formats.mjs";
4
3
  //#region src/core/constraints.ts
@@ -10,10 +9,6 @@ function getNumber(obj, key) {
10
9
  const value = obj[key];
11
10
  return typeof value === "number" ? value : void 0;
12
11
  }
13
- function getObject(obj, key) {
14
- const value = obj[key];
15
- return isObject(value) ? value : void 0;
16
- }
17
12
  function extractStringConstraints(schema, diagnostics, pointer = "") {
18
13
  const c = {};
19
14
  const minLength = getNumber(schema, "minLength");
@@ -65,8 +60,6 @@ function extractArrayConstraints(schema) {
65
60
  if (minContains !== void 0) c.minContains = minContains;
66
61
  const maxContains = getNumber(schema, "maxContains");
67
62
  if (maxContains !== void 0) c.maxContains = maxContains;
68
- const unevaluatedItems = getObject(schema, "unevaluatedItems");
69
- if (unevaluatedItems !== void 0) c.unevaluatedItems = unevaluatedItems;
70
63
  return c;
71
64
  }
72
65
  function extractObjectConstraints(schema) {