schema-components 1.20.0 → 1.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +1 -1
  2. package/dist/core/adapter.d.mts +28 -4
  3. package/dist/core/adapter.mjs +408 -71
  4. package/dist/core/constraints.d.mts +2 -2
  5. package/dist/core/constraints.mjs +0 -2
  6. package/dist/core/diagnostics.d.mts +1 -1
  7. package/dist/core/errors.d.mts +1 -1
  8. package/dist/core/errors.mjs +9 -15
  9. package/dist/core/fieldOrder.d.mts +1 -1
  10. package/dist/core/formats.d.mts +22 -1
  11. package/dist/core/formats.mjs +21 -0
  12. package/dist/core/limits.d.mts +2 -0
  13. package/dist/core/limits.mjs +23 -0
  14. package/dist/core/merge.d.mts +11 -2
  15. package/dist/core/merge.mjs +11 -0
  16. package/dist/core/normalise.d.mts +36 -4
  17. package/dist/core/normalise.mjs +2 -2
  18. package/dist/core/openapi30.d.mts +24 -1
  19. package/dist/core/openapi30.mjs +2 -2
  20. package/dist/core/ref.d.mts +1 -1
  21. package/dist/core/ref.mjs +35 -9
  22. package/dist/core/renderer.d.mts +1 -1
  23. package/dist/core/renderer.mjs +0 -2
  24. package/dist/core/swagger2.d.mts +1 -1
  25. package/dist/core/swagger2.mjs +1 -1
  26. package/dist/core/typeInference.d.mts +2 -2
  27. package/dist/core/types.d.mts +2 -2
  28. package/dist/core/types.mjs +1 -4
  29. package/dist/core/version.d.mts +1 -1
  30. package/dist/core/walkBuilders.d.mts +13 -5
  31. package/dist/core/walkBuilders.mjs +11 -3
  32. package/dist/core/walker.d.mts +1 -1
  33. package/dist/core/walker.mjs +110 -26
  34. package/dist/{diagnostics-CbBPsxSt.d.mts → diagnostics-D0QCYGv0.d.mts} +1 -1
  35. package/dist/{errors-C2iABcn9.d.mts → errors-DpFwqs5C.d.mts} +7 -11
  36. package/dist/html/a11y.d.mts +2 -2
  37. package/dist/html/a11y.mjs +10 -3
  38. package/dist/html/renderToHtml.d.mts +10 -3
  39. package/dist/html/renderToHtml.mjs +13 -3
  40. package/dist/html/renderToHtmlStream.d.mts +2 -2
  41. package/dist/html/renderers.d.mts +2 -2
  42. package/dist/html/renderers.mjs +1 -6
  43. package/dist/html/streamRenderers.d.mts +5 -4
  44. package/dist/html/streamRenderers.mjs +91 -30
  45. package/dist/limits-Cw5QZND8.d.mts +29 -0
  46. package/dist/{normalise-CMMEl4cd.mjs → normalise-DVEJQmF7.mjs} +791 -141
  47. package/dist/openapi/ApiCallbacks.d.mts +1 -1
  48. package/dist/openapi/ApiLinks.d.mts +1 -1
  49. package/dist/openapi/ApiResponseHeaders.d.mts +1 -1
  50. package/dist/openapi/ApiSecurity.d.mts +1 -1
  51. package/dist/openapi/ApiSecurity.mjs +127 -7
  52. package/dist/openapi/components.d.mts +175 -21
  53. package/dist/openapi/components.mjs +145 -21
  54. package/dist/openapi/parser.d.mts +1 -1
  55. package/dist/openapi/parser.mjs +74 -7
  56. package/dist/openapi/resolve.d.mts +70 -12
  57. package/dist/openapi/resolve.mjs +265 -42
  58. package/dist/react/SchemaComponent.d.mts +100 -35
  59. package/dist/react/SchemaComponent.mjs +66 -24
  60. package/dist/react/SchemaView.d.mts +3 -3
  61. package/dist/react/SchemaView.mjs +2 -2
  62. package/dist/react/fieldPath.d.mts +1 -1
  63. package/dist/react/headless.d.mts +1 -1
  64. package/dist/react/headless.mjs +1 -2
  65. package/dist/react/headlessRenderers.d.mts +3 -4
  66. package/dist/react/headlessRenderers.mjs +11 -31
  67. package/dist/{ref-C8JbwfiS.d.mts → ref-D-_JBZkF.d.mts} +7 -2
  68. package/dist/{renderer-SOIbJBtk.d.mts → renderer-BaRlQIuN.d.mts} +2 -2
  69. package/dist/themes/mantine.d.mts +1 -1
  70. package/dist/themes/mui.d.mts +1 -1
  71. package/dist/themes/radix.d.mts +1 -1
  72. package/dist/themes/shadcn.d.mts +1 -1
  73. package/dist/typeInference-DkcUHfaM.d.mts +982 -0
  74. package/dist/{types-C9zw9wbX.d.mts → types-BrRMV0en.d.mts} +15 -12
  75. package/package.json +1 -3
  76. package/dist/typeInference-CDoD_LZ_.d.mts +0 -533
  77. /package/dist/{version-D-u7aMfy.d.mts → version-D2jfdX6E.d.mts} +0 -0
package/README.md CHANGED
@@ -16,7 +16,7 @@ Peer dependencies: `zod@^4.0.0`, `react@^18.0.0 || ^19.0.0`.
16
16
 
17
17
  ### Zod version requirement
18
18
 
19
- schema-components requires **Zod 4**. If you are on Zod 3, see the [Zod 4 migration guide](https://zod.dev/v4/migration). The library detects Zod 3 inputs and throws a descriptive error rather than silently misbehaving.
19
+ schema-components requires **Zod 4**. If you are on Zod 3, see the [Zod 4 migration guide](https://zod.dev/v4/migration). If a Zod 3 schema is passed (detected via `_def.typeName`), a descriptive `SchemaNormalisationError` is raised pointing at the Zod 4 migration guide. Schemas from other Standard Schema libraries are not currently supported.
20
20
 
21
21
  ## `SchemaComponent`
22
22
 
@@ -1,10 +1,34 @@
1
- import { T as SchemaMeta, m as JsonObject } from "../types-C9zw9wbX.mjs";
2
- import { i as DiagnosticsOptions } from "../diagnostics-CbBPsxSt.mjs";
1
+ import { m as JsonObject, w as SchemaMeta } from "../types-BrRMV0en.mjs";
2
+ import { i as DiagnosticsOptions } from "../diagnostics-D0QCYGv0.mjs";
3
3
 
4
4
  //#region src/core/adapter.d.ts
5
5
  type SchemaInput = Record<string, unknown>;
6
- type SchemaKind = "zod4" | "zod3" | "jsonSchema" | "openapi";
6
+ type SchemaKind = "zod4" | "zod3" | "jsonSchema" | "openapi" | "unsupported-schema-lib";
7
+ /**
8
+ * Classify the input schema by its structural markers.
9
+ *
10
+ * - `zod4` — has a `_zod` marker (further validation that `_zod.def` is a
11
+ * non-null object happens inside `normaliseZod4`).
12
+ * - `zod3` — has `_def` and no `_zod`. The `typeName` field is no longer
13
+ * required: any `_def` without `_zod` is treated as a probable Zod 3
14
+ * schema. Third-party libraries that expose `_def` without `_zod` are
15
+ * nearly always Zod 3 forks; surfacing the migration message is the
16
+ * correct response.
17
+ * - `openapi` — has `openapi` or `swagger` at the root.
18
+ * - `unsupported-schema-lib` — has `parse` and `safeParse` callables but
19
+ * no `_zod` and no `_def` marker. This catches Standard Schema
20
+ * implementations (valibot, arktype, etc.) that would otherwise flow
21
+ * through as "malformed JSON Schema".
22
+ * - `jsonSchema` — fallback for anything that does not match the above.
23
+ */
7
24
  declare function detectSchemaKind(input: unknown): SchemaKind;
25
+ /**
26
+ * Exposed for unit testing — lets the contract test enumerate every rule's
27
+ * `prefix` value and assert mutual non-prefixing.
28
+ */
29
+ declare const __CLASSIFIER_RULES_FOR_TEST: readonly {
30
+ readonly prefix: string;
31
+ }[];
8
32
  interface NormalisedSchema {
9
33
  /** JSON Schema object — the authoritative schema for rendering. */
10
34
  jsonSchema: JsonObject;
@@ -21,4 +45,4 @@ interface NormaliseOptions {
21
45
  }
22
46
  declare function normaliseSchema(input: unknown, ref?: string, options?: NormaliseOptions): NormalisedSchema;
23
47
  //#endregion
24
- export { type JsonObject, NormaliseOptions, NormalisedSchema, SchemaInput, SchemaKind, type SchemaMeta, detectSchemaKind, normaliseSchema };
48
+ export { type JsonObject, NormaliseOptions, NormalisedSchema, SchemaInput, SchemaKind, type SchemaMeta, __CLASSIFIER_RULES_FOR_TEST, detectSchemaKind, normaliseSchema };
@@ -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
4
  import { 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-CMMEl4cd.mjs";
7
+ import { a as normaliseOpenApiSchemas, i as normaliseJsonSchema$1 } from "../normalise-DVEJQmF7.mjs";
7
8
  import { z } from "zod";
8
9
  //#region src/core/adapter.ts
9
10
  /**
@@ -18,13 +19,46 @@ 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
  /**
47
+ * Heuristic: a non-Zod object exposing both `.parse` and `.safeParse` as
48
+ * callables is almost certainly an instance of a competing schema library
49
+ * (Standard Schema, valibot, arktype, etc.). schema-components requires
50
+ * Zod 4 throughout — surfacing the unsupported library by name beats
51
+ * letting the input drop through to the JSON Schema branch where it
52
+ * would fail as "malformed JSON Schema" without explanation.
53
+ */
54
+ function isLikelyOtherSchemaLib(input) {
55
+ if (!isObject(input)) return false;
56
+ if (hasProperty(input, "_zod") || hasProperty(input, "_def")) return false;
57
+ const parse = input.parse;
58
+ const safeParse = input.safeParse;
59
+ return typeof parse === "function" && typeof safeParse === "function";
60
+ }
61
+ /**
28
62
  * Wraps z.toJSONSchema() for a runtime-validated Zod schema.
29
63
  *
30
64
  * The _zod guard in normaliseZod4 has confirmed this is a valid Zod schema,
@@ -32,107 +66,388 @@ function detectSchemaKind(input) {
32
66
  * that z.toJSONSchema expects. This is the library boundary equivalent of
33
67
  * object → Record<string, unknown> — the type mismatch is genuinely unavoidable.
34
68
  *
69
+ * # Options
70
+ *
71
+ * `z.toJSONSchema` is invoked with an explicit options object rather than
72
+ * Zod's defaults so the conversion contract is pinned and stable:
73
+ *
74
+ * - `target: "draft-2020-12"` — matches the walker's draft target.
75
+ * - `unrepresentable: "throw"` — keeps the unrepresentable-type rules in
76
+ * the classifier table firing instead of silently emitting `{}`.
77
+ * - `cycles: "ref"` — converts cyclic graphs into $ref pairs rather than
78
+ * throwing. Cycles in user schemas surface through the walker's $ref
79
+ * resolution rather than the adapter.
80
+ * - `io: "output"` — convert the OUTPUT side of every transform / pipe /
81
+ * codec. The input side is invisible to the converted schema, even
82
+ * though `safeParse` on the same Zod schema consumes the input shape.
83
+ * For transforms this divergence is fatal and the call throws via
84
+ * `Transforms cannot be represented`; for `z.codec(...)` the call
85
+ * succeeds but only the output side is rendered. Consumers receive a
86
+ * `zod-codec-output-only` diagnostic in the codec case so the
87
+ * asymmetry is visible — see `screenPreConversion`.
88
+ *
89
+ * # Error classification
90
+ *
35
91
  * Any exception thrown by z.toJSONSchema is classified into a
36
92
  * SchemaNormalisationError so the caller does not have to re-parse error
37
93
  * message strings. The classification covers:
38
94
  *
39
- * - Nested Zod 3 schemas inside a Zod 4 tree (which surface as
40
- * "Cannot read properties of undefined (reading 'def')") → zod3-unsupported
41
- * - Transforms ("Transforms cannot be represented") zod-transform-unsupported
42
- * - Dynamic catch values whose handler throws ("Dynamic catch values are not
43
- * supported") zod-type-unrepresentable with zodType "dynamic-catch"
95
+ * - Nested Zod 3 schemas inside a Zod 4 tree zod3-unsupported.
96
+ * Detected structurally (presence of `_def.typeName` markers anywhere
97
+ * in the schema tree) so the check works across V8, JavaScriptCore,
98
+ * and SpiderMonkey, none of which agree on the wording of
99
+ * "Cannot read properties of undefined".
100
+ * - Transforms → zod-transform-unsupported. This also catches `z.codec(…)`
101
+ * because Zod implements codecs as a pipe + transform internally, so
102
+ * they trip the same processor when round-tripping is forced. (Plain
103
+ * `z.toJSONSchema(codec)` itself does NOT throw because Zod picks one
104
+ * side of the codec; the static rejection in `typeInference.ts` is the
105
+ * compile-time guard.)
106
+ * - Dynamic catch values whose handler throws → zod-type-unrepresentable
107
+ * with zodType "dynamic-catch".
44
108
  * - Unrepresentable types — bigint, date, map, set, symbol, function, custom,
45
109
  * undefined, void, NaN, and the literal-only forms `z.literal(undefined)`
46
110
  * ("undefined-literal") and `z.literal(<bigint>)` ("bigint-literal") →
47
- * zod-type-unrepresentable
111
+ * zod-type-unrepresentable.
48
112
  * - The catch-all "Non-representable type encountered: <type>" fallback Zod
49
113
  * emits for any new schema kind without a registered processor →
50
- * zod-type-unrepresentable with zodType set to the offending def.type
51
- * - Anything else → zod-conversion-failed
114
+ * zod-type-unrepresentable with zodType set to the offending def.type.
115
+ * - Cycle detected (`cycles: "throw"`) → zod-cycle-detected.
116
+ * - Duplicate schema id → zod-duplicate-id.
117
+ * - "Unprocessed schema. This is a bug in Zod." → zod-conversion-bug.
118
+ * - "Error converting schema to JSON." → zod-conversion-failed (explicit
119
+ * classification rather than the generic fallback so the contract test
120
+ * protects the prefix from drift).
121
+ * - Anything else → zod-conversion-failed.
52
122
  *
53
123
  * The original error is preserved on each classified error via the `cause`
54
124
  * field so consumers can still inspect the Zod stack trace.
55
125
  */
56
126
  function callToJsonSchema(schema) {
57
127
  try {
58
- return z.toJSONSchema(schema);
128
+ return z.toJSONSchema(schema, {
129
+ target: "draft-2020-12",
130
+ unrepresentable: "throw",
131
+ cycles: "ref",
132
+ io: "output"
133
+ });
59
134
  } catch (err) {
60
135
  throw classifyZodConversionError(err, schema);
61
136
  }
62
137
  }
63
138
  /**
64
- * Error messages emitted by Zod 4's z.toJSONSchema for unrepresentable types.
65
- * Mapping is exact-prefix on the message and the corresponding Zod type name
66
- * surfaced to the consumer via SchemaNormalisationError.zodType.
67
- *
68
- * Sources (verbatim message prefixes):
69
- * - zod/src/v4/core/json-schema-processors.ts L104 (bigint), L110 (symbol),
70
- * L126 (undefined), L132 (void), L150 (date), L169 (literal-undefined),
71
- * L175 (literal-bigint), L204 (NaN), L246 (custom), L252 (function),
72
- * L264 (map), L270 (set), L521 (dynamic catch).
73
- * - zod/src/v4/core/to-json-schema.ts L182 (non-representable type fallback).
139
+ * Zod `def.type` tags that have no useful JSON Schema representation but
140
+ * do NOT throw when passed through `z.toJSONSchema`. Each tag is handled
141
+ * by Zod with a processor that silently rewrites the output:
74
142
  *
75
- * The kept message prefix is the shortest substring that uniquely identifies
76
- * the source the test in tests/zod-error-wording-contract.unit.test.ts
77
- * asserts each prefix is still present in the live Zod output so a Zod
78
- * patch upgrade that changes wording fails the build.
143
+ * - `promise` `promiseProcessor` unwraps the inner type, dropping the
144
+ * `Promise<...>` wrapper without any error. (`json-schema-processors.ts`,
145
+ * the body of `promiseProcessor` calls `process(def.innerType, ...)`.)
146
+ * schema-components considers this a silent shape mismatch the input
147
+ * tree advertised a `Promise<T>` and the consumer would render `T`
148
+ * without ever being told the wrapping was lost.
79
149
  *
80
- * Note: the more specific literal-* prefixes precede the generic "BigInt"
81
- * prefix so the literal classifications win. JavaScript object iteration
82
- * order preserves insertion order, and the loop short-circuits on first
83
- * match, so ordering here is load-bearing.
150
+ * Detection happens BEFORE the call to `z.toJSONSchema` so the response is
151
+ * an immediate `SchemaNormalisationError` with `kind:
152
+ * "zod-type-unrepresentable"`, matching the philosophy of
153
+ * `UnrepresentableZodType` in `typeInference.ts` these types are
154
+ * rejected, not coerced.
84
155
  */
85
- const UNREPRESENTABLE_ZOD_TYPES = [
86
- ["Literal `undefined` cannot be represented", "undefined-literal"],
87
- ["BigInt literals cannot be represented", "bigint-literal"],
88
- ["BigInt cannot be represented", "bigint"],
89
- ["Date cannot be represented", "date"],
90
- ["Map cannot be represented", "map"],
91
- ["Set cannot be represented", "set"],
92
- ["Symbols cannot be represented", "symbol"],
93
- ["Function types cannot be represented", "function"],
94
- ["Custom types cannot be represented", "custom"],
95
- ["Undefined cannot be represented", "undefined"],
96
- ["Void cannot be represented", "void"],
97
- ["NaN cannot be represented", "nan"]
98
- ];
156
+ 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."]]);
99
157
  /**
100
- * Marker for Zod's catch-all message when a brand-new schema type has no
101
- * registered processor (e.g. ahead of a Zod patch adding a new schema kind).
158
+ * Pre-conversion screening. Inspects the root `_zod.def.type` tag for
159
+ * known-problematic types that either silently misrender (handled via
160
+ * {@link PRECONVERSION_UNREPRESENTABLE_TAGS}, raising a
161
+ * `SchemaNormalisationError`) or render correctly but with consumer-visible
162
+ * caveats (codecs, raising a `zod-codec-output-only` diagnostic).
102
163
  *
103
- * Source: zod/src/v4/core/to-json-schema.ts L182
104
- * `[toJSONSchema]: Non-representable type encountered: ${def.type}`
164
+ * Design choice: `z.never()` is NOT classified here. The Zod processor for
165
+ * `never` already produces `{ not: {} }`, which the walker understands via
166
+ * its `walkBooleanSchema(false)` branch (`walker.ts` boolean-schema
167
+ * handling). Throwing a `zod-type-unrepresentable` for `never` would break
168
+ * the legitimate "this field cannot hold any value" use case that the
169
+ * walker already supports. Documented for posterity so future passes do
170
+ * not "fix" it.
105
171
  */
106
- const NON_REPRESENTABLE_TYPE_MARKER = "[toJSONSchema]: Non-representable type encountered:";
172
+ function screenPreConversion(input, def, diagnostics) {
173
+ const tag = def.type;
174
+ if (typeof tag !== "string") return;
175
+ const unrepresentableMessage = PRECONVERSION_UNREPRESENTABLE_TAGS.get(tag);
176
+ if (unrepresentableMessage !== void 0) throw new SchemaNormalisationError(unrepresentableMessage, input, "zod-type-unrepresentable", tag);
177
+ if (tag === "pipe" && isCodecSchema(input)) emitDiagnostic(diagnostics, {
178
+ code: "zod-codec-output-only",
179
+ 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.",
180
+ pointer: "",
181
+ detail: { zodType: "codec" }
182
+ });
183
+ }
184
+ /**
185
+ * True when `input` is a `z.codec(...)` instance. Detection looks for the
186
+ * `$ZodCodec` entry in `_zod.traits` — the same marker `z.toJSONSchema`'s
187
+ * own `isTransforming` helper uses to distinguish codecs from generic
188
+ * pipes.
189
+ */
190
+ function isCodecSchema(input) {
191
+ const zod = getProperty(input, "_zod");
192
+ if (!isObject(zod)) return false;
193
+ const traits = zod.traits;
194
+ if (traits instanceof Set) return traits.has("$ZodCodec");
195
+ return false;
196
+ }
197
+ /**
198
+ * Escape a string for inclusion in a `RegExp`. Required because Zod
199
+ * messages contain `[`, `]`, `.`, `(`, and `)` characters which have regex
200
+ * meaning. The set covers every character with special meaning in a
201
+ * JavaScript regular-expression source — RegExp.escape is not yet widely
202
+ * available so we escape manually.
203
+ */
204
+ function escapeRegExp(literal) {
205
+ return literal.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
206
+ }
207
+ /**
208
+ * Compile a prefix into an anchored regex that captures any trailing text
209
+ * (used by rules that need to extract dynamic data such as the duplicate id
210
+ * or the def.type that tripped the non-representable fallback).
211
+ */
212
+ function anchored(prefix) {
213
+ return new RegExp(`^${escapeRegExp(prefix)}(.*)$`, "s");
214
+ }
107
215
  /**
108
- * Marker for dynamic catch failures Zod throws when `def.catchValue(...)`
109
- * itself throws while building the JSON Schema default.
216
+ * Build the message body shared by every unrepresentable-type rule.
217
+ */
218
+ function unrepresentableMessage(typeName, fullMessage) {
219
+ return `Zod type ${typeName} cannot be represented in JSON Schema and is not supported by schema-components. Original message: ${fullMessage}`;
220
+ }
221
+ /**
222
+ * Classifier rules ordered most-specific first. Order is load-bearing:
223
+ * `Literal \`undefined\` cannot be represented` must precede the broader
224
+ * `Undefined cannot be represented` so the literal classification wins
225
+ * even when both share a leading word. A consistency check in the unit
226
+ * test suite asserts no two `prefix` values are prefixes of each other —
227
+ * any future rule that breaks the invariant fails the build.
228
+ *
229
+ * Verbatim sources (kept aligned with `tests/zod-error-wording-contract.unit.test.ts`).
230
+ * Source files are referenced by message-content anchors rather than line
231
+ * numbers — line numbers drift across Zod patch releases but the message
232
+ * strings themselves are stable and protected by the contract test suite:
110
233
  *
111
- * Source: zod/src/v4/core/json-schema-processors.ts L521
234
+ * - `zod/src/v4/core/json-schema-processors.ts` — emits `BigInt cannot be
235
+ * represented`, `Symbols cannot be represented`, `Undefined cannot be
236
+ * represented`, `Void cannot be represented`, `Date cannot be
237
+ * represented`, `Literal \`undefined\` cannot be represented`,
238
+ * `BigInt literals cannot be represented`, `NaN cannot be represented`,
239
+ * `Custom types cannot be represented`, `Function types cannot be
240
+ * represented`, `Transforms cannot be represented`, `Map cannot be
241
+ * represented`, `Set cannot be represented`, `Dynamic catch values are
242
+ * not supported`.
243
+ * - `zod/src/v4/core/to-json-schema.ts` — emits `[toJSONSchema]:
244
+ * Non-representable type encountered: ${def.type}` (the catch-all
245
+ * fallback), `Unprocessed schema. This is a bug in Zod.` (the
246
+ * internal-bug branch), `Duplicate schema id "${id}" detected during
247
+ * JSON Schema conversion.` (the duplicate-id branch), `Cycle detected:
248
+ * ` (the cycle-throw branch), and `Error converting schema to JSON.`
249
+ * (the Standard Schema boundary wrapper).
250
+ */
251
+ const CLASSIFIER_RULES = [
252
+ {
253
+ prefix: "Literal `undefined` cannot be represented",
254
+ kind: "zod-type-unrepresentable",
255
+ zodType: "undefined-literal",
256
+ build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("undefined-literal", full), schema, "zod-type-unrepresentable", "undefined-literal", cause)
257
+ },
258
+ {
259
+ prefix: "BigInt literals cannot be represented",
260
+ kind: "zod-type-unrepresentable",
261
+ zodType: "bigint-literal",
262
+ build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("bigint-literal", full), schema, "zod-type-unrepresentable", "bigint-literal", cause)
263
+ },
264
+ {
265
+ prefix: "BigInt cannot be represented",
266
+ kind: "zod-type-unrepresentable",
267
+ zodType: "bigint",
268
+ build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("bigint", full), schema, "zod-type-unrepresentable", "bigint", cause)
269
+ },
270
+ {
271
+ prefix: "Date cannot be represented",
272
+ kind: "zod-type-unrepresentable",
273
+ zodType: "date",
274
+ build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("date", full), schema, "zod-type-unrepresentable", "date", cause)
275
+ },
276
+ {
277
+ prefix: "Map cannot be represented",
278
+ kind: "zod-type-unrepresentable",
279
+ zodType: "map",
280
+ build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("map", full), schema, "zod-type-unrepresentable", "map", cause)
281
+ },
282
+ {
283
+ prefix: "Set cannot be represented",
284
+ kind: "zod-type-unrepresentable",
285
+ zodType: "set",
286
+ build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("set", full), schema, "zod-type-unrepresentable", "set", cause)
287
+ },
288
+ {
289
+ prefix: "Symbols cannot be represented",
290
+ kind: "zod-type-unrepresentable",
291
+ zodType: "symbol",
292
+ build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("symbol", full), schema, "zod-type-unrepresentable", "symbol", cause)
293
+ },
294
+ {
295
+ prefix: "Function types cannot be represented",
296
+ kind: "zod-type-unrepresentable",
297
+ zodType: "function",
298
+ build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("function", full), schema, "zod-type-unrepresentable", "function", cause)
299
+ },
300
+ {
301
+ prefix: "Custom types cannot be represented",
302
+ kind: "zod-type-unrepresentable",
303
+ zodType: "custom",
304
+ build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("custom", full), schema, "zod-type-unrepresentable", "custom", cause)
305
+ },
306
+ {
307
+ prefix: "Undefined cannot be represented",
308
+ kind: "zod-type-unrepresentable",
309
+ zodType: "undefined",
310
+ build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("undefined", full), schema, "zod-type-unrepresentable", "undefined", cause)
311
+ },
312
+ {
313
+ prefix: "Void cannot be represented",
314
+ kind: "zod-type-unrepresentable",
315
+ zodType: "void",
316
+ build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("void", full), schema, "zod-type-unrepresentable", "void", cause)
317
+ },
318
+ {
319
+ prefix: "NaN cannot be represented",
320
+ kind: "zod-type-unrepresentable",
321
+ zodType: "nan",
322
+ build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("nan", full), schema, "zod-type-unrepresentable", "nan", cause)
323
+ },
324
+ {
325
+ prefix: "Transforms cannot be represented",
326
+ kind: "zod-transform-unsupported",
327
+ build: (_m, cause, schema) => new SchemaNormalisationError("Zod transforms cannot be represented in JSON Schema. Remove the .transform() call, or pre-transform the input before passing it to the component. (Note: z.codec(...) is implemented as a transform internally — codecs that force round-tripping trip this same rule.)", schema, "zod-transform-unsupported", void 0, cause)
328
+ },
329
+ {
330
+ prefix: "Dynamic catch values are not supported",
331
+ kind: "zod-type-unrepresentable",
332
+ zodType: "dynamic-catch",
333
+ build: (_m, cause, schema) => new SchemaNormalisationError("Zod catch values that depend on runtime computation cannot be represented in JSON Schema. Provide a static catch value or remove the .catch() call.", schema, "zod-type-unrepresentable", "dynamic-catch", cause)
334
+ },
335
+ {
336
+ prefix: "[toJSONSchema]: Non-representable type encountered:",
337
+ kind: "zod-type-unrepresentable",
338
+ build: (match, cause, schema, full) => {
339
+ const trailing = match[1]?.trim() ?? "";
340
+ const typeName = trailing.length > 0 ? trailing.split(/\s+/)[0] : void 0;
341
+ 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);
342
+ }
343
+ },
344
+ {
345
+ prefix: "Cycle detected: ",
346
+ kind: "zod-cycle-detected",
347
+ build: (match, cause, schema, full) => {
348
+ return new SchemaNormalisationError(`Zod detected a cycle in the schema graph at ${(match[1] ?? "").split(/\s+/)[0] ?? ""}. 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);
349
+ }
350
+ },
351
+ {
352
+ prefix: "Duplicate schema id \"",
353
+ kind: "zod-duplicate-id",
354
+ build: (match, cause, schema, full) => {
355
+ const trailing = match[1] ?? "";
356
+ const closing = trailing.indexOf("\"");
357
+ 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);
358
+ }
359
+ },
360
+ {
361
+ prefix: "Unprocessed schema. This is a bug in Zod.",
362
+ kind: "zod-conversion-bug",
363
+ build: (_m, cause, schema, full) => new SchemaNormalisationError(`Zod failed to process this schema during JSON Schema conversion and reports it as an internal bug. File an issue on the Zod tracker with a reproduction. Original message: ${full}`, schema, "zod-conversion-bug", void 0, cause)
364
+ },
365
+ {
366
+ prefix: "Error converting schema to JSON.",
367
+ kind: "zod-conversion-failed",
368
+ build: (_m, cause, schema, full) => new SchemaNormalisationError(`z.toJSONSchema() failed to produce a Standard Schema payload. Inspect the underlying cause for the original error. Original message: ${full}`, schema, "zod-conversion-failed", void 0, cause)
369
+ }
370
+ ];
371
+ /**
372
+ * Compiled regex form of {@link CLASSIFIER_RULES} — built once at module
373
+ * load. Avoids per-error compilation.
374
+ */
375
+ const COMPILED_CLASSIFIER_RULES = CLASSIFIER_RULES.map((rule) => ({
376
+ rule,
377
+ pattern: anchored(rule.prefix)
378
+ }));
379
+ /**
380
+ * Maximum recursion depth for {@link containsNestedZod3}. Reuses the
381
+ * shared {@link MAX_REF_DEPTH} so the runtime walk and the compile-time
382
+ * `DEFAULT_MAX_DEPTH` (type-aliased to the same value) stay in lockstep.
112
383
  */
113
- const DYNAMIC_CATCH_MARKER = "Dynamic catch values are not supported";
114
384
  /**
115
- * The cryptic error produced when z.toJSONSchema encounters a nested Zod 3
116
- * schema (one without `_zod.def`). Reproduced verbatim from Node's TypeError
117
- * for property access on undefined.
385
+ * Walk an arbitrary value looking for Zod 3 markers (`_def` without
386
+ * `_zod`). Zod 4 schemas always carry `_zod.def`; Zod 3 schemas carry
387
+ * `_def` (with or without a `typeName` field — third-party Zod-3-style
388
+ * libraries occasionally omit `typeName`). Presence of `_def` without
389
+ * `_zod` anywhere in the tree means a Zod 3 (or Zod-3-like) schema was
390
+ * nested inside a Zod 4 input, which is what trips the V8
391
+ * `"Cannot read properties of undefined"` failure.
392
+ *
393
+ * Engine-agnostic by construction — the detector inspects schema shape
394
+ * instead of pattern-matching against the runtime's TypeError message,
395
+ * so it works equivalently under V8, JavaScriptCore (Bun/Safari), and
396
+ * SpiderMonkey (Firefox) — none of which agree on the wording.
397
+ *
398
+ * Performance shortcuts:
399
+ *
400
+ * - **Targeted descent into Zod 4 nodes.** Once a node is identified as a
401
+ * Zod 4 schema (`_zod.def` is an object), the only branch that can
402
+ * carry user-supplied sub-schemas is `_zod.def` itself. Zod's other
403
+ * internal members (`_zod.traits`, `_zod.parse`, `_zod.bag`, etc.) are
404
+ * implementation surface and never contain user schemas, so walking
405
+ * them on every conversion failure is wasted work. Switching to a
406
+ * targeted descent (only `_zod.def` plus the schema root's `_def`
407
+ * field) trims the walk dramatically.
408
+ * - **Depth cap.** Recursion is bounded by {@link MAX_REF_DEPTH}
409
+ * so a pathological schema graph cannot cause stack overflow. The
410
+ * `visited` set still defends against cyclic references; the depth
411
+ * cap defends against deep-but-acyclic trees.
118
412
  */
119
- const NESTED_ZOD3_MARKER = "Cannot read properties of undefined";
413
+ function containsNestedZod3(value, visited) {
414
+ return containsNestedZod3Inner(value, visited, 0);
415
+ }
416
+ function containsNestedZod3Inner(value, visited, depth) {
417
+ if (depth >= 64) return false;
418
+ if (value === null || typeof value !== "object") return false;
419
+ if (visited.has(value)) return false;
420
+ visited.add(value);
421
+ if (Array.isArray(value)) {
422
+ for (const item of value) if (containsNestedZod3Inner(item, visited, depth + 1)) return true;
423
+ return false;
424
+ }
425
+ if (!isObject(value)) return false;
426
+ const def = value._def;
427
+ const zod = value._zod;
428
+ if (zod === void 0 && isObject(def)) return true;
429
+ if (isObject(zod) && isObject(zod.def)) return containsNestedZod3Inner(zod.def, visited, depth + 1);
430
+ for (const key of Object.keys(value)) if (containsNestedZod3Inner(value[key], visited, depth + 1)) return true;
431
+ return false;
432
+ }
120
433
  function classifyZodConversionError(err, schema) {
121
434
  const message = err instanceof Error ? err.message : String(err);
122
- if (message.includes(NESTED_ZOD3_MARKER)) return new SchemaNormalisationError("A nested Zod 3 schema was found inside a Zod 4 schema. schema-components requires Zod 4 throughout the schema tree. See the Zod 4 migration guide at https://zod.dev/v4/migration or run: pnpm add zod@^4", schema, "zod3-unsupported", void 0, err);
123
- if (message.includes("Transforms cannot be represented")) return new SchemaNormalisationError("Zod transforms cannot be represented in JSON Schema. Remove the .transform() call, or pre-transform the input before passing it to the component.", schema, "zod-transform-unsupported", void 0, err);
124
- if (message.includes(DYNAMIC_CATCH_MARKER)) return new SchemaNormalisationError("Zod catch values that depend on runtime computation cannot be represented in JSON Schema. Provide a static catch value or remove the .catch() call.", schema, "zod-type-unrepresentable", "dynamic-catch", err);
125
- for (const [prefix, typeName] of UNREPRESENTABLE_ZOD_TYPES) if (message.includes(prefix)) return new SchemaNormalisationError(`Zod type ${typeName} cannot be represented in JSON Schema and is not supported by schema-components. Original message: ${message}`, schema, "zod-type-unrepresentable", typeName, err);
126
- const nonReprIndex = message.indexOf(NON_REPRESENTABLE_TYPE_MARKER);
127
- if (nonReprIndex !== -1) {
128
- const trailing = message.slice(nonReprIndex + 51).trim();
129
- const typeName = trailing.length > 0 ? trailing.split(/\s+/)[0] : void 0;
130
- 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: ${message}`, schema, "zod-type-unrepresentable", typeName, err);
435
+ if (containsNestedZod3(schema, /* @__PURE__ */ new Set())) return new SchemaNormalisationError("A nested Zod 3 schema was found inside a Zod 4 schema. schema-components requires Zod 4 throughout the schema tree. See the Zod 4 migration guide at https://zod.dev/v4/migration or run: pnpm add zod@^4", schema, "zod3-unsupported", void 0, err);
436
+ for (const { rule, pattern } of COMPILED_CLASSIFIER_RULES) {
437
+ const match = pattern.exec(message);
438
+ if (match !== null) return rule.build(match, err, schema, message);
131
439
  }
132
440
  return new SchemaNormalisationError(`z.toJSONSchema() failed: ${message}`, schema, "zod-conversion-failed", void 0, err);
133
441
  }
442
+ /**
443
+ * Exposed for unit testing — lets the contract test enumerate every rule's
444
+ * `prefix` value and assert mutual non-prefixing.
445
+ */
446
+ const __CLASSIFIER_RULES_FOR_TEST = CLASSIFIER_RULES;
134
447
  function normaliseSchema(input, ref, options) {
135
- if (ref === void 0 && isObject(input)) {
448
+ const usesDiagnostics = options?.diagnostics !== void 0;
449
+ const cacheEligible = ref === void 0 && isObject(input) && !usesDiagnostics;
450
+ if (cacheEligible) {
136
451
  const cached = schemaCache.get(input);
137
452
  if (cached !== void 0) return cached;
138
453
  }
@@ -140,11 +455,12 @@ function normaliseSchema(input, ref, options) {
140
455
  let result;
141
456
  switch (kind) {
142
457
  case "zod4":
143
- result = normaliseZod4(input);
458
+ result = normaliseZod4(input, options?.diagnostics);
144
459
  break;
145
460
  case "zod3":
146
461
  result = normaliseZod3(input);
147
462
  break;
463
+ case "unsupported-schema-lib": throw new SchemaNormalisationError("Input looks like a schema from a non-Zod library — 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");
148
464
  case "openapi":
149
465
  if (!isObject(input)) throw new SchemaNormalisationError("Invalid OpenAPI document", input, "openapi-invalid");
150
466
  result = normaliseOpenApi(input, ref, options);
@@ -154,13 +470,15 @@ function normaliseSchema(input, ref, options) {
154
470
  result = normaliseJsonSchema(input, options?.diagnostics);
155
471
  break;
156
472
  }
157
- if (ref === void 0 && isObject(input)) schemaCache.set(input, result);
473
+ if (cacheEligible) schemaCache.set(input, result);
158
474
  return result;
159
475
  }
160
- function normaliseZod4(input) {
476
+ function normaliseZod4(input, diagnostics) {
161
477
  const zod = getProperty(input, "_zod");
162
- if (!isObject(zod)) throw new SchemaNormalisationError("Invalid Zod 4 schema: missing _zod property", input, "invalid-zod");
163
- if (!("def" in zod)) throw new SchemaNormalisationError("Invalid Zod 4 schema: missing _zod.def", input, "invalid-zod");
478
+ 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");
479
+ const def = getProperty(zod, "def");
480
+ if (!isObject(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");
481
+ screenPreConversion(input, def, diagnostics);
164
482
  const jsonSchema = callToJsonSchema(input);
165
483
  if (!isObject(jsonSchema)) throw new SchemaNormalisationError("z.toJSONSchema() did not produce an object", input, "invalid-zod");
166
484
  return {
@@ -284,6 +602,23 @@ function resolveOpenApiRef(doc, ref) {
284
602
  }
285
603
  throw new Error(`Unsupported OpenAPI ref format: ${ref}`);
286
604
  }
605
+ /**
606
+ * Surface root-level metadata from the JSON Schema into the `rootMeta`
607
+ * shape consumed by the walker. Pulls `readOnly`, `writeOnly`,
608
+ * `description`, `title`, `deprecated`, `examples`, and `default`
609
+ * directly from the schema root.
610
+ *
611
+ * `examples` is forwarded only when present as an array (per JSON Schema
612
+ * Draft 2020-12 — Draft 04's `example` singular is normalised upstream).
613
+ * `default` is forwarded for any value the schema declares (any JSON
614
+ * value, including `null` and `false`); the presence check uses `in`
615
+ * so a literal `false` or `null` default is preserved.
616
+ *
617
+ * `examples` and `default` ride on the `[key: string]: unknown` index
618
+ * signature of {@link SchemaMeta}. They are not declared as named fields
619
+ * on `SchemaMeta` because that type lives in `types.ts` and is shared
620
+ * with the walker; the index signature is the agreed extension point.
621
+ */
287
622
  function extractRootMetaFromJson(jsonSchema) {
288
623
  const meta = {};
289
624
  if (jsonSchema.readOnly === true) meta.readOnly = true;
@@ -291,7 +626,9 @@ function extractRootMetaFromJson(jsonSchema) {
291
626
  if (typeof jsonSchema.description === "string") meta.description = jsonSchema.description;
292
627
  if (typeof jsonSchema.title === "string") meta.title = jsonSchema.title;
293
628
  if (typeof jsonSchema.deprecated === "boolean") meta.deprecated = jsonSchema.deprecated;
629
+ if (Array.isArray(jsonSchema.examples)) meta.examples = jsonSchema.examples;
630
+ if ("default" in jsonSchema) meta.default = jsonSchema.default;
294
631
  return Object.keys(meta).length > 0 ? meta : void 0;
295
632
  }
296
633
  //#endregion
297
- export { detectSchemaKind, normaliseSchema };
634
+ export { __CLASSIFIER_RULES_FOR_TEST, detectSchemaKind, normaliseSchema };
@@ -1,5 +1,5 @@
1
- import { D as StringConstraints, f as FileConstraints, t as ArrayConstraints, x as ObjectConstraints, y as NumberConstraints } from "../types-C9zw9wbX.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-BrRMV0en.mjs";
2
+ import { i as DiagnosticsOptions } from "../diagnostics-D0QCYGv0.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;