schema-components 1.20.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 (58) hide show
  1. package/dist/core/adapter.d.mts +9 -2
  2. package/dist/core/adapter.mjs +220 -64
  3. package/dist/core/constraints.d.mts +1 -1
  4. package/dist/core/constraints.mjs +0 -2
  5. package/dist/core/errors.d.mts +1 -1
  6. package/dist/core/errors.mjs +9 -15
  7. package/dist/core/fieldOrder.d.mts +1 -1
  8. package/dist/core/merge.d.mts +10 -1
  9. package/dist/core/merge.mjs +11 -0
  10. package/dist/core/normalise.d.mts +7 -1
  11. package/dist/core/normalise.mjs +1 -1
  12. package/dist/core/openapi30.d.mts +24 -1
  13. package/dist/core/openapi30.mjs +2 -2
  14. package/dist/core/ref.d.mts +1 -1
  15. package/dist/core/ref.mjs +34 -9
  16. package/dist/core/renderer.d.mts +1 -1
  17. package/dist/core/swagger2.mjs +1 -1
  18. package/dist/core/typeInference.d.mts +1 -1
  19. package/dist/core/types.d.mts +1 -1
  20. package/dist/core/walkBuilders.d.mts +12 -4
  21. package/dist/core/walkBuilders.mjs +11 -3
  22. package/dist/core/walker.d.mts +1 -1
  23. package/dist/core/walker.mjs +32 -25
  24. package/dist/{errors-C2iABcn9.d.mts → errors-QEwOtQAA.d.mts} +7 -11
  25. package/dist/html/a11y.d.mts +2 -2
  26. package/dist/html/renderToHtml.d.mts +2 -2
  27. package/dist/html/renderToHtmlStream.d.mts +2 -2
  28. package/dist/html/renderers.d.mts +2 -2
  29. package/dist/html/renderers.mjs +1 -1
  30. package/dist/html/streamRenderers.d.mts +2 -2
  31. package/dist/{normalise-CMMEl4cd.mjs → normalise-DaSrnr8g.mjs} +325 -28
  32. package/dist/openapi/ApiCallbacks.d.mts +1 -1
  33. package/dist/openapi/ApiLinks.d.mts +1 -1
  34. package/dist/openapi/ApiResponseHeaders.d.mts +1 -1
  35. package/dist/openapi/ApiSecurity.d.mts +1 -1
  36. package/dist/openapi/ApiSecurity.mjs +113 -7
  37. package/dist/openapi/components.d.mts +32 -10
  38. package/dist/openapi/components.mjs +22 -12
  39. package/dist/openapi/parser.d.mts +1 -1
  40. package/dist/openapi/parser.mjs +39 -4
  41. package/dist/openapi/resolve.d.mts +60 -9
  42. package/dist/openapi/resolve.mjs +86 -23
  43. package/dist/react/SchemaComponent.d.mts +4 -4
  44. package/dist/react/SchemaComponent.mjs +32 -4
  45. package/dist/react/SchemaView.d.mts +2 -2
  46. package/dist/react/fieldPath.d.mts +1 -1
  47. package/dist/react/headless.d.mts +1 -1
  48. package/dist/react/headlessRenderers.d.mts +2 -2
  49. package/dist/react/headlessRenderers.mjs +1 -1
  50. package/dist/{ref-C8JbwfiS.d.mts → ref-si8ViYun.d.mts} +6 -1
  51. package/dist/{renderer-SOIbJBtk.d.mts → renderer-DI6ZYf7a.d.mts} +1 -1
  52. package/dist/themes/mantine.d.mts +1 -1
  53. package/dist/themes/mui.d.mts +1 -1
  54. package/dist/themes/radix.d.mts +1 -1
  55. package/dist/themes/shadcn.d.mts +1 -1
  56. package/dist/{typeInference-CDoD_LZ_.d.mts → typeInference-Bxw3NOG1.d.mts} +162 -48
  57. package/dist/{types-C9zw9wbX.d.mts → types-BnxPEElk.d.mts} +12 -2
  58. package/package.json +1 -1
@@ -1,10 +1,17 @@
1
- import { T as SchemaMeta, m as JsonObject } from "../types-C9zw9wbX.mjs";
1
+ import { T as SchemaMeta, m as JsonObject } from "../types-BnxPEElk.mjs";
2
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-CMMEl4cd.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,19 +36,33 @@ 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
- * - Dynamic catch values whose handler throws ("Dynamic catch values are not
43
- * supported") zod-type-unrepresentable with zodType "dynamic-catch"
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".
44
52
  * - Unrepresentable types — bigint, date, map, set, symbol, function, custom,
45
53
  * undefined, void, NaN, and the literal-only forms `z.literal(undefined)`
46
54
  * ("undefined-literal") and `z.literal(<bigint>)` ("bigint-literal") →
47
- * zod-type-unrepresentable
55
+ * zod-type-unrepresentable.
48
56
  * - The catch-all "Non-representable type encountered: <type>" fallback Zod
49
57
  * 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
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.
52
66
  *
53
67
  * The original error is preserved on each classified error via the `cause`
54
68
  * field so consumers can still inspect the Zod stack trace.
@@ -61,76 +75,218 @@ function callToJsonSchema(schema) {
61
75
  }
62
76
  }
63
77
  /**
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.
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.
67
108
  *
68
- * Sources (verbatim message prefixes):
109
+ * Verbatim sources (kept aligned with `tests/zod-error-wording-contract.unit.test.ts`):
69
110
  * - zod/src/v4/core/json-schema-processors.ts L104 (bigint), L110 (symbol),
70
111
  * L126 (undefined), L132 (void), L150 (date), L169 (literal-undefined),
71
112
  * 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).
74
- *
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.
79
- *
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.
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).
84
117
  */
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"]
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
+ }
98
237
  ];
99
238
  /**
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).
102
- *
103
- * Source: zod/src/v4/core/to-json-schema.ts L182
104
- * `[toJSONSchema]: Non-representable type encountered: ${def.type}`
239
+ * Compiled regex form of {@link CLASSIFIER_RULES} built once at module
240
+ * load. Avoids per-error compilation.
105
241
  */
106
- const NON_REPRESENTABLE_TYPE_MARKER = "[toJSONSchema]: Non-representable type encountered:";
242
+ const COMPILED_CLASSIFIER_RULES = CLASSIFIER_RULES.map((rule) => ({
243
+ rule,
244
+ pattern: anchored(rule.prefix)
245
+ }));
107
246
  /**
108
- * Marker for dynamic catch failures Zod throws when `def.catchValue(...)`
109
- * itself throws while building the JSON Schema default.
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.
110
252
  *
111
- * Source: zod/src/v4/core/json-schema-processors.ts L521
112
- */
113
- const DYNAMIC_CATCH_MARKER = "Dynamic catch values are not supported";
114
- /**
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.
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.
118
261
  */
119
- 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
+ }
120
276
  function classifyZodConversionError(err, schema) {
121
277
  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);
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);
131
282
  }
132
283
  return new SchemaNormalisationError(`z.toJSONSchema() failed: ${message}`, schema, "zod-conversion-failed", void 0, err);
133
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;
134
290
  function normaliseSchema(input, ref, options) {
135
291
  if (ref === void 0 && isObject(input)) {
136
292
  const cached = schemaCache.get(input);
@@ -294,4 +450,4 @@ function extractRootMetaFromJson(jsonSchema) {
294
450
  return Object.keys(meta).length > 0 ? meta : void 0;
295
451
  }
296
452
  //#endregion
297
- export { detectSchemaKind, normaliseSchema };
453
+ export { __CLASSIFIER_RULES_FOR_TEST, detectSchemaKind, normaliseSchema };
@@ -1,4 +1,4 @@
1
- import { D as StringConstraints, f as FileConstraints, t as ArrayConstraints, x as ObjectConstraints, y as NumberConstraints } from "../types-C9zw9wbX.mjs";
1
+ import { D as StringConstraints, f as FileConstraints, t as ArrayConstraints, x as ObjectConstraints, y as NumberConstraints } from "../types-BnxPEElk.mjs";
2
2
  import { i as DiagnosticsOptions } from "../diagnostics-CbBPsxSt.mjs";
3
3
 
4
4
  //#region src/core/constraints.d.ts
@@ -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 { i as SchemaRenderError, n as SchemaFieldError, r as SchemaNormalisationError, t as SchemaError } from "../errors-C2iABcn9.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,19 +42,11 @@ var SchemaNormalisationError = class extends SchemaError {
37
42
  * (e.g. "bigint", "date", "map", "set"). `undefined` for other kinds.
38
43
  */
39
44
  zodType;
40
- /**
41
- * The original underlying error, when this normalisation error wraps
42
- * another exception (typically the error thrown by `z.toJSONSchema()`).
43
- * Preserves the source stack trace so the root cause is not lost when
44
- * the classifier translates the message.
45
- */
46
- cause;
47
45
  constructor(message, schema, kind, zodType, cause) {
48
- super(message, schema);
46
+ super(message, schema, cause);
49
47
  this.name = "SchemaNormalisationError";
50
48
  this.kind = kind;
51
49
  this.zodType = zodType;
52
- this.cause = cause;
53
50
  }
54
51
  };
55
52
  /**
@@ -60,13 +57,10 @@ var SchemaNormalisationError = class extends SchemaError {
60
57
  var SchemaRenderError = class extends SchemaError {
61
58
  /** The schema type being rendered when the error occurred. */
62
59
  schemaType;
63
- /** The original error from the render function. */
64
- cause;
65
60
  constructor(message, schema, schemaType, cause) {
66
- super(message, schema);
61
+ super(message, schema, cause);
67
62
  this.name = "SchemaRenderError";
68
63
  this.schemaType = schemaType;
69
- this.cause = cause;
70
64
  }
71
65
  };
72
66
  /**
@@ -1,4 +1,4 @@
1
- import { M as WalkedField } from "../types-C9zw9wbX.mjs";
1
+ import { M as WalkedField } from "../types-BnxPEElk.mjs";
2
2
 
3
3
  //#region src/core/fieldOrder.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;
@@ -6,8 +6,14 @@ type NodeTransform = (node: Record<string, unknown>) => Record<string, unknown>;
6
6
  /**
7
7
  * Deep-normalise a JSON Schema object by applying a per-node transform
8
8
  * and recursing into every sub-schema location.
9
+ *
10
+ * The optional `visited` set guards against shared object references and
11
+ * cycles introduced upstream (e.g. by the OpenAPI bundler's
12
+ * `structuredClone`-based inlining of external refs). The walk skips
13
+ * already-seen nodes by returning the original reference rather than
14
+ * recursing forever.
9
15
  */
10
- declare function deepNormalise(schema: Record<string, unknown>, transform: NodeTransform): Record<string, unknown>;
16
+ declare function deepNormalise(schema: Record<string, unknown>, transform: NodeTransform, visited?: WeakSet<object>): Record<string, unknown>;
11
17
  /**
12
18
  * Per-node context threaded through `deepNormaliseWithContext`.
13
19
  *
@@ -1,2 +1,2 @@
1
- import { a as normaliseOpenApiSchemas, i as normaliseJsonSchema, n as deepNormaliseWithContext, r as normaliseDraft04Node, t as deepNormalise } from "../normalise-CMMEl4cd.mjs";
1
+ import { a as normaliseOpenApiSchemas, i as normaliseJsonSchema, n as deepNormaliseWithContext, r as normaliseDraft04Node, t as deepNormalise } from "../normalise-DaSrnr8g.mjs";
2
2
  export { deepNormalise, deepNormaliseWithContext, normaliseDraft04Node, normaliseJsonSchema, normaliseOpenApiSchemas };
@@ -23,6 +23,29 @@ declare function normaliseOpenApi30Node(node: Record<string, unknown>): Record<s
23
23
  * `mapping` or infers them from `$ref` fragment names.
24
24
  */
25
25
  declare function normaliseOpenApi30Discriminator(node: Record<string, unknown>): Record<string, unknown>;
26
+ /**
27
+ * Document-level pre-pass for OpenAPI discriminators that are declared
28
+ * on a base schema and inherited by subtypes via `allOf`.
29
+ *
30
+ * The per-node {@link normaliseOpenApi30Discriminator} only handles
31
+ * discriminators that already sit alongside `oneOf`/`anyOf`. For the
32
+ * canonical "Cat extends Pet" pattern — where `Pet` carries the
33
+ * discriminator and `Cat`/`Dog` reference `Pet` via `allOf` — the
34
+ * discriminator is silently lost. This pre-pass:
35
+ *
36
+ * 1. Injects the discriminator `const` on each subtype's local
37
+ * `properties` (so a direct render of the subtype validates the
38
+ * discriminator value correctly).
39
+ * 2. Synthesises a `oneOf` on the base whenever it lacks one, listing
40
+ * each subtype as `{ $ref, properties: { propertyName: { const } } }`.
41
+ * The per-node discriminator transform then sees `oneOf` and clears
42
+ * the `discriminator` keyword, and the walker's
43
+ * `detectDiscriminated` finds the per-option `const`s.
44
+ *
45
+ * Mutates a shallow clone of `components/schemas` — the input document
46
+ * is never modified.
47
+ */
48
+ declare function applyDiscriminatorAllOfPrepass(doc: Record<string, unknown>): Record<string, unknown>;
26
49
  /**
27
50
  * Combined OpenAPI 3.0.x node transform: Draft 04 + nullable + discriminator.
28
51
  * Applied to every schema node in an OpenAPI 3.0 document.
@@ -67,4 +90,4 @@ declare function deepNormaliseOpenApiDoc(doc: Record<string, unknown>, normalise
67
90
  */
68
91
  declare function deepNormaliseOpenApi30Doc(doc: Record<string, unknown>, deepNormalise: (schema: Record<string, unknown>, transform: NodeTransform) => Record<string, unknown>): Record<string, unknown>;
69
92
  //#endregion
70
- export { deepNormaliseOpenApi30Doc, deepNormaliseOpenApiDoc, normaliseOpenApi30Combined, normaliseOpenApi30Discriminator, normaliseOpenApi30Node };
93
+ export { applyDiscriminatorAllOfPrepass, deepNormaliseOpenApi30Doc, deepNormaliseOpenApiDoc, normaliseOpenApi30Combined, normaliseOpenApi30Discriminator, normaliseOpenApi30Node };
@@ -1,2 +1,2 @@
1
- import { c as deepNormaliseOpenApiDoc, d as normaliseOpenApi30Node, l as normaliseOpenApi30Combined, s as deepNormaliseOpenApi30Doc, u as normaliseOpenApi30Discriminator } from "../normalise-CMMEl4cd.mjs";
2
- export { deepNormaliseOpenApi30Doc, deepNormaliseOpenApiDoc, normaliseOpenApi30Combined, normaliseOpenApi30Discriminator, normaliseOpenApi30Node };
1
+ import { c as deepNormaliseOpenApi30Doc, d as normaliseOpenApi30Discriminator, f as normaliseOpenApi30Node, l as deepNormaliseOpenApiDoc, s as applyDiscriminatorAllOfPrepass, u as normaliseOpenApi30Combined } from "../normalise-DaSrnr8g.mjs";
2
+ export { applyDiscriminatorAllOfPrepass, deepNormaliseOpenApi30Doc, deepNormaliseOpenApiDoc, normaliseOpenApi30Combined, normaliseOpenApi30Discriminator, normaliseOpenApi30Node };
@@ -1,2 +1,2 @@
1
- import { a as findAnchor, i as dereference, n as RefOptions, o as resolveRef, r as countDistinctRefs, t as ExternalResolver } from "../ref-C8JbwfiS.mjs";
1
+ import { a as findAnchor, i as dereference, n as RefOptions, o as resolveRef, r as countDistinctRefs, t as ExternalResolver } from "../ref-si8ViYun.mjs";
2
2
  export { ExternalResolver, RefOptions, countDistinctRefs, dereference, findAnchor, resolveRef };
package/dist/core/ref.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  import { isObject } from "./guards.mjs";
2
2
  import { emitDiagnostic } from "./diagnostics.mjs";
3
+ import { isPrototypePollutingKey } from "./uri.mjs";
3
4
  //#region src/core/ref.ts
4
5
  /**
5
6
  * $ref resolution for JSON Schema.
@@ -18,15 +19,27 @@ function getString(obj, key) {
18
19
  */
19
20
  function countDistinctRefs(root) {
20
21
  const refs = /* @__PURE__ */ new Set();
21
- collectRefs(root, refs);
22
+ collectRefs(root, refs, /* @__PURE__ */ new WeakSet());
22
23
  return Math.max(refs.size, 1);
23
24
  }
24
- function collectRefs(node, refs) {
25
+ /**
26
+ * The OpenAPI bundler (`bundleOpenApiDoc`) inlines external refs via
27
+ * `structuredClone`, which preserves shared object references and cycles.
28
+ * Without the `visited` set this walk would recurse forever on cyclic or
29
+ * diamond-shaped input. The set is a no-op for tree-shaped documents.
30
+ */
31
+ function collectRefs(node, refs, visited) {
25
32
  if (!isObject(node)) return;
33
+ if (visited.has(node)) return;
34
+ visited.add(node);
26
35
  const ref = node.$ref;
27
36
  if (typeof ref === "string") refs.add(ref);
28
- for (const value of Object.values(node)) if (isObject(value)) collectRefs(value, refs);
29
- else if (Array.isArray(value)) for (const item of value) collectRefs(item, refs);
37
+ for (const value of Object.values(node)) if (isObject(value)) collectRefs(value, refs, visited);
38
+ else if (Array.isArray(value)) {
39
+ if (visited.has(value)) continue;
40
+ visited.add(value);
41
+ for (const item of value) collectRefs(item, refs, visited);
42
+ }
30
43
  }
31
44
  /**
32
45
  * Resolve a `$ref` in a schema against a root document.
@@ -134,6 +147,7 @@ function dereference(ref, root) {
134
147
  for (const part of parts) {
135
148
  if (!isObject(current)) return void 0;
136
149
  const decoded = part.replace(/~1/g, "/").replace(/~0/g, "~");
150
+ if (isPrototypePollutingKey(decoded)) return void 0;
137
151
  current = current[decoded];
138
152
  }
139
153
  return isObject(current) ? current : void 0;
@@ -146,18 +160,29 @@ function dereference(ref, root) {
146
160
  /**
147
161
  * Recursively scan a schema document for a `$anchor` matching the given name.
148
162
  * Returns the schema object containing the anchor, or undefined.
163
+ *
164
+ * The optional `visited` set guards against shared object references and
165
+ * cycles introduced by the OpenAPI bundler's `structuredClone`-based
166
+ * inlining of external refs. Without it a recursive document would stack
167
+ * overflow before reaching the matching anchor.
149
168
  */
150
- function findAnchor(node, anchorName) {
169
+ function findAnchor(node, anchorName, visited = /* @__PURE__ */ new WeakSet()) {
151
170
  if (!isObject(node)) return void 0;
171
+ if (visited.has(node)) return void 0;
172
+ visited.add(node);
152
173
  if (node.$anchor === anchorName) return node;
153
174
  for (const value of Object.values(node)) {
154
175
  if (isObject(value)) {
155
- const found = findAnchor(value, anchorName);
176
+ const found = findAnchor(value, anchorName, visited);
156
177
  if (found !== void 0) return found;
157
178
  }
158
- if (Array.isArray(value)) for (const item of value) {
159
- const found = findAnchor(item, anchorName);
160
- if (found !== void 0) return found;
179
+ if (Array.isArray(value)) {
180
+ if (visited.has(value)) continue;
181
+ visited.add(value);
182
+ for (const item of value) {
183
+ const found = findAnchor(item, anchorName, visited);
184
+ if (found !== void 0) return found;
185
+ }
161
186
  }
162
187
  }
163
188
  }