schema-components 1.19.0 → 1.21.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 (70) hide show
  1. package/dist/core/adapter.d.mts +10 -3
  2. package/dist/core/adapter.mjs +237 -31
  3. package/dist/core/constraints.d.mts +2 -2
  4. package/dist/core/constraints.mjs +0 -2
  5. package/dist/core/diagnostics.d.mts +1 -1
  6. package/dist/core/errors.d.mts +1 -1
  7. package/dist/core/errors.mjs +10 -8
  8. package/dist/core/fieldOrder.d.mts +1 -1
  9. package/dist/core/formats.d.mts +21 -14
  10. package/dist/core/formats.mjs +88 -4
  11. package/dist/core/merge.d.mts +11 -2
  12. package/dist/core/merge.mjs +11 -0
  13. package/dist/core/normalise.d.mts +9 -3
  14. package/dist/core/normalise.mjs +1 -1
  15. package/dist/core/openapi30.d.mts +24 -1
  16. package/dist/core/openapi30.mjs +2 -2
  17. package/dist/core/ref.d.mts +1 -1
  18. package/dist/core/ref.mjs +34 -9
  19. package/dist/core/renderer.d.mts +1 -1
  20. package/dist/core/swagger2.d.mts +1 -1
  21. package/dist/core/swagger2.mjs +1 -1
  22. package/dist/core/typeInference.d.mts +2 -2
  23. package/dist/core/types.d.mts +1 -1
  24. package/dist/core/uri.d.mts +41 -0
  25. package/dist/core/uri.mjs +76 -0
  26. package/dist/core/version.d.mts +2 -2
  27. package/dist/core/version.mjs +25 -1
  28. package/dist/core/walkBuilders.d.mts +13 -5
  29. package/dist/core/walkBuilders.mjs +11 -3
  30. package/dist/core/walker.d.mts +1 -1
  31. package/dist/core/walker.mjs +80 -26
  32. package/dist/{diagnostics-VgEKI_Ct.d.mts → diagnostics-CbBPsxSt.d.mts} +1 -1
  33. package/dist/{errors-CnGjT1cg.d.mts → errors-QEwOtQAA.d.mts} +8 -5
  34. package/dist/html/a11y.d.mts +2 -2
  35. package/dist/html/renderToHtml.d.mts +2 -2
  36. package/dist/html/renderToHtmlStream.d.mts +2 -2
  37. package/dist/html/renderers.d.mts +2 -2
  38. package/dist/html/renderers.mjs +9 -2
  39. package/dist/html/streamRenderers.d.mts +2 -2
  40. package/dist/{normalise-C0ofw3W6.mjs → normalise-DaSrnr8g.mjs} +574 -40
  41. package/dist/openapi/ApiCallbacks.d.mts +1 -1
  42. package/dist/openapi/ApiLinks.d.mts +1 -1
  43. package/dist/openapi/ApiResponseHeaders.d.mts +1 -1
  44. package/dist/openapi/ApiSecurity.d.mts +1 -1
  45. package/dist/openapi/ApiSecurity.mjs +113 -7
  46. package/dist/openapi/bundle.mjs +2 -0
  47. package/dist/openapi/components.d.mts +32 -10
  48. package/dist/openapi/components.mjs +37 -16
  49. package/dist/openapi/parser.d.mts +1 -1
  50. package/dist/openapi/parser.mjs +41 -4
  51. package/dist/openapi/resolve.d.mts +70 -9
  52. package/dist/openapi/resolve.mjs +124 -24
  53. package/dist/react/SchemaComponent.d.mts +21 -9
  54. package/dist/react/SchemaComponent.mjs +32 -4
  55. package/dist/react/SchemaView.d.mts +3 -3
  56. package/dist/react/fieldPath.d.mts +1 -1
  57. package/dist/react/headless.d.mts +1 -1
  58. package/dist/react/headlessRenderers.d.mts +2 -2
  59. package/dist/react/headlessRenderers.mjs +18 -6
  60. package/dist/{ref-Bb43ZURY.d.mts → ref-si8ViYun.d.mts} +7 -2
  61. package/dist/{renderer-BQqiXUYP.d.mts → renderer-DI6ZYf7a.d.mts} +1 -1
  62. package/dist/themes/mantine.d.mts +1 -1
  63. package/dist/themes/mui.d.mts +1 -1
  64. package/dist/themes/radix.d.mts +1 -1
  65. package/dist/themes/shadcn.d.mts +1 -1
  66. package/dist/typeInference-Bxw3NOG1.d.mts +647 -0
  67. package/dist/{types-D_5ST7SS.d.mts → types-BnxPEElk.d.mts} +18 -2
  68. package/dist/{version-XNH7PRGP.d.mts → version-D-u7aMfy.d.mts} +36 -1
  69. package/package.json +1 -1
  70. package/dist/typeInference-5JiqIZ8t.d.mts +0 -388
@@ -1,10 +1,17 @@
1
- import { T as SchemaMeta, m as JsonObject } from "../types-D_5ST7SS.mjs";
2
- import { i as DiagnosticsOptions } from "../diagnostics-VgEKI_Ct.mjs";
1
+ import { T as SchemaMeta, m as JsonObject } from "../types-BnxPEElk.mjs";
2
+ import { i as DiagnosticsOptions } from "../diagnostics-CbBPsxSt.mjs";
3
3
 
4
4
  //#region src/core/adapter.d.ts
5
5
  type SchemaInput = Record<string, unknown>;
6
6
  type SchemaKind = "zod4" | "zod3" | "jsonSchema" | "openapi";
7
7
  declare function detectSchemaKind(input: unknown): SchemaKind;
8
+ /**
9
+ * Exposed for unit testing — lets the contract test enumerate every rule's
10
+ * `prefix` value and assert mutual non-prefixing.
11
+ */
12
+ declare const __CLASSIFIER_RULES_FOR_TEST: readonly {
13
+ readonly prefix: string;
14
+ }[];
8
15
  interface NormalisedSchema {
9
16
  /** JSON Schema object — the authoritative schema for rendering. */
10
17
  jsonSchema: JsonObject;
@@ -21,4 +28,4 @@ interface NormaliseOptions {
21
28
  }
22
29
  declare function normaliseSchema(input: unknown, ref?: string, options?: NormaliseOptions): NormalisedSchema;
23
30
  //#endregion
24
- export { type JsonObject, NormaliseOptions, NormalisedSchema, SchemaInput, SchemaKind, type SchemaMeta, detectSchemaKind, normaliseSchema };
31
+ export { type JsonObject, NormaliseOptions, NormalisedSchema, SchemaInput, SchemaKind, type SchemaMeta, __CLASSIFIER_RULES_FOR_TEST, detectSchemaKind, normaliseSchema };
@@ -3,7 +3,7 @@ import { SchemaNormalisationError } from "./errors.mjs";
3
3
  import { emitDiagnostic } from "./diagnostics.mjs";
4
4
  import { dereference } from "./ref.mjs";
5
5
  import { detectOpenApiVersion, inferJsonSchemaDraftWithReason, isSwagger2, matchJsonSchemaDraftUri } from "./version.mjs";
6
- import { a as normaliseOpenApiSchemas, i as normaliseJsonSchema$1 } from "../normalise-C0ofw3W6.mjs";
6
+ import { a as normaliseOpenApiSchemas, i as normaliseJsonSchema$1 } from "../normalise-DaSrnr8g.mjs";
7
7
  import { z } from "zod";
8
8
  //#region src/core/adapter.ts
9
9
  /**
@@ -36,12 +36,36 @@ function detectSchemaKind(input) {
36
36
  * SchemaNormalisationError so the caller does not have to re-parse error
37
37
  * message strings. The classification covers:
38
38
  *
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
- * - Unrepresentable types ("BigInt cannot be represented", "Date cannot be
43
- * represented", etc.) zod-type-unrepresentable
44
- * - Anything else → zod-conversion-failed
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.
66
+ *
67
+ * The original error is preserved on each classified error via the `cause`
68
+ * field so consumers can still inspect the Zod stack trace.
45
69
  */
46
70
  function callToJsonSchema(schema) {
47
71
  try {
@@ -51,36 +75,218 @@ function callToJsonSchema(schema) {
51
75
  }
52
76
  }
53
77
  /**
54
- * Error messages emitted by Zod 4's z.toJSONSchema for unrepresentable types.
55
- * Mapping is exact-prefix on the message and the corresponding Zod type name
56
- * surfaced to the consumer via SchemaNormalisationError.zodType.
78
+ * Escape a string for inclusion in a `RegExp`. Required because Zod
79
+ * messages contain `[`, `]`, `.`, `(`, and `)` characters which have regex
80
+ * meaning. The set covers every character with special meaning in a
81
+ * JavaScript regular-expression source — RegExp.escape is not yet widely
82
+ * available so we escape manually.
83
+ */
84
+ function escapeRegExp(literal) {
85
+ return literal.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
86
+ }
87
+ /**
88
+ * Compile a prefix into an anchored regex that captures any trailing text
89
+ * (used by rules that need to extract dynamic data such as the duplicate id
90
+ * or the def.type that tripped the non-representable fallback).
91
+ */
92
+ function anchored(prefix) {
93
+ return new RegExp(`^${escapeRegExp(prefix)}(.*)$`, "s");
94
+ }
95
+ /**
96
+ * Build the message body shared by every unrepresentable-type rule.
97
+ */
98
+ function unrepresentableMessage(typeName, fullMessage) {
99
+ return `Zod type ${typeName} cannot be represented in JSON Schema and is not supported by schema-components. Original message: ${fullMessage}`;
100
+ }
101
+ /**
102
+ * Classifier rules ordered most-specific first. Order is load-bearing:
103
+ * `Literal \`undefined\` cannot be represented` must precede the broader
104
+ * `Undefined cannot be represented` so the literal classification wins
105
+ * even when both share a leading word. A consistency check in the unit
106
+ * test suite asserts no two `prefix` values are prefixes of each other —
107
+ * any future rule that breaks the invariant fails the build.
57
108
  *
58
- * Source: zod/src/v4/core/to-json-schema.ts ("cannot be represented" messages).
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).
59
117
  */
60
- const UNREPRESENTABLE_ZOD_TYPES = [
61
- ["BigInt cannot be represented", "bigint"],
62
- ["Date cannot be represented", "date"],
63
- ["Map cannot be represented", "map"],
64
- ["Set cannot be represented", "set"],
65
- ["Symbols cannot be represented", "symbol"],
66
- ["Function types cannot be represented", "function"],
67
- ["Undefined cannot be represented", "undefined"],
68
- ["Void cannot be represented", "void"],
69
- ["NaN cannot be represented", "nan"]
118
+ const CLASSIFIER_RULES = [
119
+ {
120
+ prefix: "Literal `undefined` cannot be represented",
121
+ kind: "zod-type-unrepresentable",
122
+ zodType: "undefined-literal",
123
+ build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("undefined-literal", full), schema, "zod-type-unrepresentable", "undefined-literal", cause)
124
+ },
125
+ {
126
+ prefix: "BigInt literals cannot be represented",
127
+ kind: "zod-type-unrepresentable",
128
+ zodType: "bigint-literal",
129
+ build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("bigint-literal", full), schema, "zod-type-unrepresentable", "bigint-literal", cause)
130
+ },
131
+ {
132
+ prefix: "BigInt cannot be represented",
133
+ kind: "zod-type-unrepresentable",
134
+ zodType: "bigint",
135
+ build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("bigint", full), schema, "zod-type-unrepresentable", "bigint", cause)
136
+ },
137
+ {
138
+ prefix: "Date cannot be represented",
139
+ kind: "zod-type-unrepresentable",
140
+ zodType: "date",
141
+ build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("date", full), schema, "zod-type-unrepresentable", "date", cause)
142
+ },
143
+ {
144
+ prefix: "Map cannot be represented",
145
+ kind: "zod-type-unrepresentable",
146
+ zodType: "map",
147
+ build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("map", full), schema, "zod-type-unrepresentable", "map", cause)
148
+ },
149
+ {
150
+ prefix: "Set cannot be represented",
151
+ kind: "zod-type-unrepresentable",
152
+ zodType: "set",
153
+ build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("set", full), schema, "zod-type-unrepresentable", "set", cause)
154
+ },
155
+ {
156
+ prefix: "Symbols cannot be represented",
157
+ kind: "zod-type-unrepresentable",
158
+ zodType: "symbol",
159
+ build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("symbol", full), schema, "zod-type-unrepresentable", "symbol", cause)
160
+ },
161
+ {
162
+ prefix: "Function types cannot be represented",
163
+ kind: "zod-type-unrepresentable",
164
+ zodType: "function",
165
+ build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("function", full), schema, "zod-type-unrepresentable", "function", cause)
166
+ },
167
+ {
168
+ prefix: "Custom types cannot be represented",
169
+ kind: "zod-type-unrepresentable",
170
+ zodType: "custom",
171
+ build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("custom", full), schema, "zod-type-unrepresentable", "custom", cause)
172
+ },
173
+ {
174
+ prefix: "Undefined cannot be represented",
175
+ kind: "zod-type-unrepresentable",
176
+ zodType: "undefined",
177
+ build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("undefined", full), schema, "zod-type-unrepresentable", "undefined", cause)
178
+ },
179
+ {
180
+ prefix: "Void cannot be represented",
181
+ kind: "zod-type-unrepresentable",
182
+ zodType: "void",
183
+ build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("void", full), schema, "zod-type-unrepresentable", "void", cause)
184
+ },
185
+ {
186
+ prefix: "NaN cannot be represented",
187
+ kind: "zod-type-unrepresentable",
188
+ zodType: "nan",
189
+ build: (_m, cause, schema, full) => new SchemaNormalisationError(unrepresentableMessage("nan", full), schema, "zod-type-unrepresentable", "nan", cause)
190
+ },
191
+ {
192
+ prefix: "Transforms cannot be represented",
193
+ kind: "zod-transform-unsupported",
194
+ 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)
195
+ },
196
+ {
197
+ prefix: "Dynamic catch values are not supported",
198
+ kind: "zod-type-unrepresentable",
199
+ zodType: "dynamic-catch",
200
+ 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)
201
+ },
202
+ {
203
+ prefix: "[toJSONSchema]: Non-representable type encountered:",
204
+ kind: "zod-type-unrepresentable",
205
+ build: (match, cause, schema, full) => {
206
+ const trailing = match[1]?.trim() ?? "";
207
+ const typeName = trailing.length > 0 ? trailing.split(/\s+/)[0] : void 0;
208
+ 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
+ }
210
+ },
211
+ {
212
+ prefix: "Cycle detected: ",
213
+ kind: "zod-cycle-detected",
214
+ 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);
216
+ }
217
+ },
218
+ {
219
+ prefix: "Duplicate schema id \"",
220
+ kind: "zod-duplicate-id",
221
+ build: (match, cause, schema, full) => {
222
+ const trailing = match[1] ?? "";
223
+ 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);
225
+ }
226
+ },
227
+ {
228
+ prefix: "Unprocessed schema. This is a bug in Zod.",
229
+ kind: "zod-conversion-bug",
230
+ 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)
231
+ },
232
+ {
233
+ prefix: "Error converting schema to JSON.",
234
+ kind: "zod-conversion-failed",
235
+ 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)
236
+ }
70
237
  ];
71
238
  /**
72
- * The cryptic error produced when z.toJSONSchema encounters a nested Zod 3
73
- * schema (one without `_zod.def`). Reproduced verbatim from Node's TypeError
74
- * for property access on undefined.
239
+ * Compiled regex form of {@link CLASSIFIER_RULES} built once at module
240
+ * load. Avoids per-error compilation.
241
+ */
242
+ const COMPILED_CLASSIFIER_RULES = CLASSIFIER_RULES.map((rule) => ({
243
+ rule,
244
+ pattern: anchored(rule.prefix)
245
+ }));
246
+ /**
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.
252
+ *
253
+ * Engine-agnostic by construction — the detector inspects schema shape
254
+ * instead of pattern-matching against the runtime's TypeError message,
255
+ * so it works equivalently under V8, JavaScriptCore (Bun/Safari), and
256
+ * SpiderMonkey (Firefox) — none of which agree on the wording.
257
+ *
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.
75
261
  */
76
- const NESTED_ZOD3_MARKER = "Cannot read properties of undefined";
262
+ function containsNestedZod3(value, visited) {
263
+ if (value === null || typeof value !== "object") return false;
264
+ if (visited.has(value)) return false;
265
+ visited.add(value);
266
+ if (Array.isArray(value)) {
267
+ for (const item of value) if (containsNestedZod3(item, visited)) return true;
268
+ return false;
269
+ }
270
+ if (!isObject(value)) return false;
271
+ 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;
274
+ return false;
275
+ }
77
276
  function classifyZodConversionError(err, schema) {
78
277
  const message = err instanceof Error ? err.message : String(err);
79
- 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");
80
- 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");
81
- 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);
82
- return new SchemaNormalisationError(`z.toJSONSchema() failed: ${message}`, schema, "zod-conversion-failed");
278
+ 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);
279
+ for (const { rule, pattern } of COMPILED_CLASSIFIER_RULES) {
280
+ const match = pattern.exec(message);
281
+ if (match !== null) return rule.build(match, err, schema, message);
282
+ }
283
+ return new SchemaNormalisationError(`z.toJSONSchema() failed: ${message}`, schema, "zod-conversion-failed", void 0, err);
83
284
  }
285
+ /**
286
+ * Exposed for unit testing — lets the contract test enumerate every rule's
287
+ * `prefix` value and assert mutual non-prefixing.
288
+ */
289
+ const __CLASSIFIER_RULES_FOR_TEST = CLASSIFIER_RULES;
84
290
  function normaliseSchema(input, ref, options) {
85
291
  if (ref === void 0 && isObject(input)) {
86
292
  const cached = schemaCache.get(input);
@@ -206,7 +412,7 @@ function resolveOpenApiRef(doc, ref) {
206
412
  if (!isObject(resolved)) throw new Error(`OpenAPI ref not found: ${ref}`);
207
413
  return resolved;
208
414
  }
209
- const pathMatch = /^\/(.+)\/(get|post|put|patch|delete)$/.exec(ref);
415
+ const pathMatch = /^\/(.+)\/(get|post|put|patch|delete|head|options|trace)$/.exec(ref);
210
416
  if (pathMatch?.[1] !== void 0 && pathMatch[2] !== void 0) {
211
417
  const pathStr = pathMatch[1];
212
418
  const method = pathMatch[2];
@@ -244,4 +450,4 @@ function extractRootMetaFromJson(jsonSchema) {
244
450
  return Object.keys(meta).length > 0 ? meta : void 0;
245
451
  }
246
452
  //#endregion
247
- export { detectSchemaKind, normaliseSchema };
453
+ 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-D_5ST7SS.mjs";
2
- import { i as DiagnosticsOptions } from "../diagnostics-VgEKI_Ct.mjs";
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";
3
3
 
4
4
  //#region src/core/constraints.d.ts
5
5
  declare function extractStringConstraints(schema: Record<string, unknown>, diagnostics?: DiagnosticsOptions, pointer?: string): StringConstraints;
@@ -61,8 +61,6 @@ function extractArrayConstraints(schema) {
61
61
  const maxItems = getNumber(schema, "maxItems");
62
62
  if (maxItems !== void 0) c.maxItems = maxItems;
63
63
  if (schema.uniqueItems === true) c.uniqueItems = true;
64
- const contains = getObject(schema, "contains");
65
- if (contains !== void 0) c.contains = contains;
66
64
  const minContains = getNumber(schema, "minContains");
67
65
  if (minContains !== void 0) c.minContains = minContains;
68
66
  const maxContains = getNumber(schema, "maxContains");
@@ -1,2 +1,2 @@
1
- import { a as appendPointer, i as DiagnosticsOptions, n as DiagnosticCode, o as emitDiagnostic, r as DiagnosticSink, t as Diagnostic } from "../diagnostics-VgEKI_Ct.mjs";
1
+ import { a as appendPointer, i as DiagnosticsOptions, n as DiagnosticCode, o as emitDiagnostic, r as DiagnosticSink, t as Diagnostic } from "../diagnostics-CbBPsxSt.mjs";
2
2
  export { Diagnostic, DiagnosticCode, DiagnosticSink, DiagnosticsOptions, appendPointer, emitDiagnostic };
@@ -1,2 +1,2 @@
1
- import { i as SchemaRenderError, n as SchemaFieldError, r as SchemaNormalisationError, t as SchemaError } from "../errors-CnGjT1cg.mjs";
1
+ import { i as SchemaRenderError, n as SchemaFieldError, r as SchemaNormalisationError, t as SchemaError } from "../errors-QEwOtQAA.mjs";
2
2
  export { SchemaError, SchemaFieldError, SchemaNormalisationError, SchemaRenderError };
@@ -14,12 +14,17 @@
14
14
  /**
15
15
  * Base class for all schema-components errors.
16
16
  * Catch this to handle any library error uniformly.
17
+ *
18
+ * Forwards the optional `cause` to the native ES2022 `Error` constructor so
19
+ * `error.cause` is wired up by the runtime and rendered correctly by
20
+ * `util.inspect` ("Caused by: ..."). Subclasses that need a typed `cause`
21
+ * field still get it via the platform's own `Error.cause` getter.
17
22
  */
18
23
  var SchemaError = class extends Error {
19
24
  /** The schema input that caused the error. */
20
25
  schema;
21
- constructor(message, schema) {
22
- super(message);
26
+ constructor(message, schema, cause) {
27
+ super(message, cause !== void 0 ? { cause } : void 0);
23
28
  this.name = "SchemaError";
24
29
  this.schema = schema;
25
30
  }
@@ -37,8 +42,8 @@ var SchemaNormalisationError = class extends SchemaError {
37
42
  * (e.g. "bigint", "date", "map", "set"). `undefined` for other kinds.
38
43
  */
39
44
  zodType;
40
- constructor(message, schema, kind, zodType) {
41
- super(message, schema);
45
+ constructor(message, schema, kind, zodType, cause) {
46
+ super(message, schema, cause);
42
47
  this.name = "SchemaNormalisationError";
43
48
  this.kind = kind;
44
49
  this.zodType = zodType;
@@ -52,13 +57,10 @@ var SchemaNormalisationError = class extends SchemaError {
52
57
  var SchemaRenderError = class extends SchemaError {
53
58
  /** The schema type being rendered when the error occurred. */
54
59
  schemaType;
55
- /** The original error from the render function. */
56
- cause;
57
60
  constructor(message, schema, schemaType, cause) {
58
- super(message, schema);
61
+ super(message, schema, cause);
59
62
  this.name = "SchemaRenderError";
60
63
  this.schemaType = schemaType;
61
- this.cause = cause;
62
64
  }
63
65
  };
64
66
  /**
@@ -1,4 +1,4 @@
1
- import { M as WalkedField } from "../types-D_5ST7SS.mjs";
1
+ import { M as WalkedField } from "../types-BnxPEElk.mjs";
2
2
 
3
3
  //#region src/core/fieldOrder.d.ts
4
4
  /**
@@ -1,18 +1,14 @@
1
+ import { i as DiagnosticsOptions } from "../diagnostics-CbBPsxSt.mjs";
2
+
1
3
  //#region src/core/formats.d.ts
2
4
  /**
3
- * Built-in format patterns for JSON Schema string validation.
4
- *
5
- * Maps standard JSON Schema/OpenAPI `format` values to RegExp patterns
6
- * or predicate validators. Used by the constraint extractor to derive
7
- * `formatPattern` alongside the existing `format` string, and by the
8
- * `validate` helper for runtime format checking.
9
- *
10
- * Formats that cannot be validated by regex (iri, regex) use predicate
11
- * functions instead. `validateFormat` dispatches to whichever is available.
12
- *
13
- * The user's explicit `pattern` constraint always takes precedence —
14
- * `formatPattern` is exposed as a separate field for renderers.
5
+ * Maximum length of a string that will be compiled by `new RegExp(...)`.
6
+ * Patterns that exceed this cap are rejected without compilation. A
7
+ * pathological pattern (e.g. `(a+)+`) doesn't need to be long to hang,
8
+ * but the cap is cheap defence and stops obviously hostile input before
9
+ * it reaches the regex engine.
15
10
  */
11
+ declare const MAX_REGEX_PATTERN_LENGTH = 500;
16
12
  /**
17
13
  * A format validator: either a RegExp pattern or a predicate function.
18
14
  */
@@ -21,13 +17,24 @@ type FormatValidator = RegExp | ((value: string) => boolean);
21
17
  * Recognised JSON Schema formats with their validation patterns.
22
18
  * Unknown formats emit an `unknown-format` diagnostic and skip derivation.
23
19
  */
20
+ /**
21
+ * Email format pattern, exported as a named const so callers that need a
22
+ * guaranteed `RegExp` (rather than the `RegExp | undefined` shape of
23
+ * `FORMAT_PATTERNS[name]` under `noUncheckedIndexedAccess`) can import it
24
+ * directly. Used by the URI safety helpers for mailto address checks.
25
+ */
26
+ declare const EMAIL_FORMAT_PATTERN: RegExp;
24
27
  declare const FORMAT_PATTERNS: Readonly<Record<string, RegExp>>;
25
28
  /**
26
29
  * Validate a string value against format constraints.
27
30
  * Returns `true` when the value matches the format,
28
31
  * `false` when it does not, and `undefined` when the format
29
32
  * is not recognised (no validator available).
33
+ *
34
+ * `diagnostics` and `pointer` are forwarded to validators that can
35
+ * surface structured diagnostics — currently the `regex` format, which
36
+ * emits `pattern-invalid` for over-length or malformed input.
30
37
  */
31
- declare function validateFormat(value: string, format: string): boolean | undefined;
38
+ declare function validateFormat(value: string, format: string, diagnostics?: DiagnosticsOptions, pointer?: string): boolean | undefined;
32
39
  //#endregion
33
- export { FORMAT_PATTERNS, FormatValidator, validateFormat };
40
+ export { EMAIL_FORMAT_PATTERN, FORMAT_PATTERNS, FormatValidator, MAX_REGEX_PATTERN_LENGTH, validateFormat };
@@ -1,11 +1,27 @@
1
+ import { emitDiagnostic } from "./diagnostics.mjs";
1
2
  //#region src/core/formats.ts
2
3
  /**
4
+ * Maximum length of a string that will be compiled by `new RegExp(...)`.
5
+ * Patterns that exceed this cap are rejected without compilation. A
6
+ * pathological pattern (e.g. `(a+)+`) doesn't need to be long to hang,
7
+ * but the cap is cheap defence and stops obviously hostile input before
8
+ * it reaches the regex engine.
9
+ */
10
+ const MAX_REGEX_PATTERN_LENGTH = 500;
11
+ /**
3
12
  * Recognised JSON Schema formats with their validation patterns.
4
13
  * Unknown formats emit an `unknown-format` diagnostic and skip derivation.
5
14
  */
15
+ /**
16
+ * Email format pattern, exported as a named const so callers that need a
17
+ * guaranteed `RegExp` (rather than the `RegExp | undefined` shape of
18
+ * `FORMAT_PATTERNS[name]` under `noUncheckedIndexedAccess`) can import it
19
+ * directly. Used by the URI safety helpers for mailto address checks.
20
+ */
21
+ const EMAIL_FORMAT_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
6
22
  const FORMAT_PATTERNS = {
7
23
  uuid: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i,
8
- email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
24
+ email: EMAIL_FORMAT_PATTERN,
9
25
  "date-time": /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/,
10
26
  date: /^\d{4}-\d{2}-\d{2}$/,
11
27
  time: /^\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?$/,
@@ -27,11 +43,22 @@ const FORMAT_PATTERNS = {
27
43
  cidrv6: /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::|([0-9a-fA-F]{1,4})?::([0-9a-fA-F]{1,4}:?){0,6})\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/,
28
44
  base64: /^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/,
29
45
  base64url: /^[A-Za-z0-9_-]*$/,
30
- e164: /^\+[1-9]\d{6,14}$/
46
+ e164: /^\+[1-9]\d{6,14}$/,
47
+ emoji: /^(\p{Extended_Pictographic}|\p{Emoji_Component})+$/u,
48
+ ulid: /^[0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{26}$/,
49
+ xid: /^[0-9a-vA-V]{20}$/,
50
+ ksuid: /^[A-Za-z0-9]{27}$/,
51
+ lowercase: /^[^A-Z]*$/,
52
+ uppercase: /^[^a-z]*$/,
53
+ jwt: /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]*$/
31
54
  };
32
55
  /**
33
56
  * Format validators that use predicate functions instead of regex.
34
57
  * These are checked in `validateFormat` when no regex pattern exists.
58
+ *
59
+ * The `regex` format is intentionally absent here — it requires a
60
+ * length cap and structured diagnostic emission and is handled via
61
+ * {@link validateRegexFormat} directly from {@link validateFormat}.
35
62
  */
36
63
  const PREDICATE_VALIDATORS = {
37
64
  iri: (value) => {
@@ -57,19 +84,76 @@ const PREDICATE_VALIDATORS = {
57
84
  } catch {
58
85
  return false;
59
86
  }
87
+ },
88
+ "json-string": (value) => {
89
+ try {
90
+ JSON.parse(value);
91
+ return true;
92
+ } catch {
93
+ return false;
94
+ }
60
95
  }
61
96
  };
62
97
  /**
98
+ * Validate that a string is a syntactically valid regular expression.
99
+ *
100
+ * Hardened against ReDoS-style abuse:
101
+ * - Values longer than {@link MAX_REGEX_PATTERN_LENGTH} are treated as
102
+ * "format unmatched" without ever being compiled.
103
+ * - Any `SyntaxError` thrown by `new RegExp(...)` (malformed pattern,
104
+ * invalid flags, unbalanced groups) is caught and treated as "format
105
+ * unmatched".
106
+ *
107
+ * In both cases a diagnostic is emitted when a sink is supplied so
108
+ * callers can surface the offending value to the schema author.
109
+ */
110
+ function validateRegexFormat(value, diagnostics, pointer) {
111
+ if (value.length > 500) {
112
+ emitDiagnostic(diagnostics, {
113
+ code: "pattern-invalid",
114
+ message: `Pattern length (${String(value.length)}) exceeds maximum (${String(500)}); treating as unmatched`,
115
+ pointer,
116
+ detail: {
117
+ reason: "length-exceeded",
118
+ length: value.length,
119
+ maxLength: 500
120
+ }
121
+ });
122
+ return false;
123
+ }
124
+ try {
125
+ new RegExp(value);
126
+ return true;
127
+ } catch (error) {
128
+ const message = error instanceof Error ? error.message : String(error);
129
+ emitDiagnostic(diagnostics, {
130
+ code: "pattern-invalid",
131
+ message: `Failed to compile pattern as RegExp: ${message}; treating as unmatched`,
132
+ pointer,
133
+ detail: {
134
+ reason: "compile-error",
135
+ error: message
136
+ }
137
+ });
138
+ return false;
139
+ }
140
+ }
141
+ /**
63
142
  * Validate a string value against format constraints.
64
143
  * Returns `true` when the value matches the format,
65
144
  * `false` when it does not, and `undefined` when the format
66
145
  * is not recognised (no validator available).
146
+ *
147
+ * `diagnostics` and `pointer` are forwarded to validators that can
148
+ * surface structured diagnostics — currently the `regex` format, which
149
+ * emits `pattern-invalid` for over-length or malformed input.
67
150
  */
68
- function validateFormat(value, format) {
151
+ function validateFormat(value, format, diagnostics, pointer = "") {
69
152
  const pattern = FORMAT_PATTERNS[format];
70
153
  if (pattern !== void 0) return pattern.test(value);
154
+ if (format === "regex") return validateRegexFormat(value, diagnostics, pointer);
71
155
  const predicate = PREDICATE_VALIDATORS[format];
72
156
  if (predicate !== void 0) return predicate(value);
73
157
  }
74
158
  //#endregion
75
- export { FORMAT_PATTERNS, validateFormat };
159
+ export { EMAIL_FORMAT_PATTERN, FORMAT_PATTERNS, MAX_REGEX_PATTERN_LENGTH, validateFormat };
@@ -1,4 +1,4 @@
1
- import { i as DiagnosticsOptions } from "../diagnostics-VgEKI_Ct.mjs";
1
+ import { i as DiagnosticsOptions } from "../diagnostics-CbBPsxSt.mjs";
2
2
 
3
3
  //#region src/core/merge.d.ts
4
4
  /**
@@ -25,8 +25,17 @@ declare function mergeRefSiblings(referencer: Record<string, unknown>, resolvedM
25
25
  * When a later branch redefines a keyword with a non-equal value the
26
26
  * later value is silently dropped — an `allof-conflict` diagnostic is
27
27
  * emitted so the loss is visible to consumers.
28
+ *
29
+ * Boolean branches (valid per Draft 06+) collapse the composite:
30
+ * - `false` makes the entire \`allOf\` unsatisfiable — return \`false\`,
31
+ * which the walker turns into a \`NeverField\`.
32
+ * - \`true\` is the always-valid schema and contributes no constraints —
33
+ * skip silently.
34
+ *
35
+ * Non-boolean, non-object entries (e.g. arrays, numbers) are malformed
36
+ * inputs that cannot represent a schema; skip them as before.
28
37
  */
29
- declare function mergeAllOf(schemas: unknown[], diagnostics?: DiagnosticsOptions, pointer?: string): Record<string, unknown>;
38
+ declare function mergeAllOf(schemas: unknown[], diagnostics?: DiagnosticsOptions, pointer?: string): Record<string, unknown> | false;
30
39
  interface NormalisedAnyOf {
31
40
  inner: Record<string, unknown>;
32
41
  isNullable: boolean;
@@ -109,12 +109,23 @@ function mergeRefSiblings(referencer, resolvedMeta) {
109
109
  * When a later branch redefines a keyword with a non-equal value the
110
110
  * later value is silently dropped — an `allof-conflict` diagnostic is
111
111
  * emitted so the loss is visible to consumers.
112
+ *
113
+ * Boolean branches (valid per Draft 06+) collapse the composite:
114
+ * - `false` makes the entire \`allOf\` unsatisfiable — return \`false\`,
115
+ * which the walker turns into a \`NeverField\`.
116
+ * - \`true\` is the always-valid schema and contributes no constraints —
117
+ * skip silently.
118
+ *
119
+ * Non-boolean, non-object entries (e.g. arrays, numbers) are malformed
120
+ * inputs that cannot represent a schema; skip them as before.
112
121
  */
113
122
  function mergeAllOf(schemas, diagnostics, pointer = "") {
114
123
  const merged = {};
115
124
  const properties = {};
116
125
  const required = [];
117
126
  for (const entry of schemas) {
127
+ if (entry === false) return false;
128
+ if (entry === true) continue;
118
129
  if (!isObject(entry)) continue;
119
130
  const props = getObject(entry, "properties");
120
131
  if (props !== void 0) for (const [key, value] of Object.entries(props)) properties[key] = value;