schema-components 1.18.1 → 1.20.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 (68) hide show
  1. package/dist/core/adapter.d.mts +2 -2
  2. package/dist/core/adapter.mjs +128 -15
  3. package/dist/core/constraints.d.mts +2 -2
  4. package/dist/core/diagnostics.d.mts +1 -1
  5. package/dist/core/errors.d.mts +1 -1
  6. package/dist/core/errors.mjs +15 -1
  7. package/dist/core/fieldOrder.d.mts +1 -1
  8. package/dist/core/formats.d.mts +21 -14
  9. package/dist/core/formats.mjs +96 -4
  10. package/dist/core/merge.d.mts +1 -1
  11. package/dist/core/normalise.d.mts +38 -5
  12. package/dist/core/normalise.mjs +2 -2
  13. package/dist/core/openapi30.d.mts +33 -4
  14. package/dist/core/openapi30.mjs +2 -2
  15. package/dist/core/ref.d.mts +1 -1
  16. package/dist/core/renderer.d.mts +1 -1
  17. package/dist/core/renderer.mjs +7 -2
  18. package/dist/core/swagger2.d.mts +1 -1
  19. package/dist/core/swagger2.mjs +1 -1
  20. package/dist/core/typeInference.d.mts +2 -2
  21. package/dist/core/types.d.mts +1 -1
  22. package/dist/core/uri.d.mts +41 -0
  23. package/dist/core/uri.mjs +76 -0
  24. package/dist/core/version.d.mts +2 -2
  25. package/dist/core/version.mjs +43 -9
  26. package/dist/core/walkBuilders.d.mts +3 -3
  27. package/dist/core/walker.d.mts +1 -1
  28. package/dist/core/walker.mjs +50 -3
  29. package/dist/{diagnostics-BYk63jsC.d.mts → diagnostics-CbBPsxSt.d.mts} +1 -1
  30. package/dist/{errors-C5zRC2PU.d.mts → errors-C2iABcn9.d.mts} +14 -2
  31. package/dist/html/a11y.d.mts +2 -2
  32. package/dist/html/renderToHtml.d.mts +2 -2
  33. package/dist/html/renderToHtmlStream.d.mts +2 -2
  34. package/dist/html/renderers.d.mts +2 -2
  35. package/dist/html/renderers.mjs +37 -2
  36. package/dist/html/streamRenderers.d.mts +2 -2
  37. package/dist/normalise-CMMEl4cd.mjs +1306 -0
  38. package/dist/openapi/ApiCallbacks.d.mts +1 -1
  39. package/dist/openapi/ApiLinks.d.mts +1 -1
  40. package/dist/openapi/ApiResponseHeaders.d.mts +1 -1
  41. package/dist/openapi/ApiSecurity.d.mts +1 -1
  42. package/dist/openapi/bundle.mjs +2 -0
  43. package/dist/openapi/components.d.mts +2 -2
  44. package/dist/openapi/components.mjs +20 -5
  45. package/dist/openapi/parser.d.mts +1 -1
  46. package/dist/openapi/parser.mjs +6 -1
  47. package/dist/openapi/resolve.d.mts +17 -6
  48. package/dist/openapi/resolve.mjs +45 -7
  49. package/dist/react/SchemaComponent.d.mts +21 -9
  50. package/dist/react/SchemaComponent.mjs +3 -13
  51. package/dist/react/SchemaView.d.mts +3 -3
  52. package/dist/react/SchemaView.mjs +1 -0
  53. package/dist/react/fieldPath.d.mts +1 -1
  54. package/dist/react/headless.d.mts +7 -1
  55. package/dist/react/headless.mjs +13 -1
  56. package/dist/react/headlessRenderers.d.mts +54 -3
  57. package/dist/react/headlessRenderers.mjs +153 -3
  58. package/dist/{ref-Ckt5liZs.d.mts → ref-C8JbwfiS.d.mts} +1 -1
  59. package/dist/{renderer-BAGoX4AK.d.mts → renderer-SOIbJBtk.d.mts} +9 -3
  60. package/dist/themes/mantine.d.mts +1 -1
  61. package/dist/themes/mui.d.mts +1 -1
  62. package/dist/themes/radix.d.mts +1 -1
  63. package/dist/themes/shadcn.d.mts +1 -1
  64. package/dist/{typeInference-5JiqIZ8t.d.mts → typeInference-CDoD_LZ_.d.mts} +187 -42
  65. package/dist/{types-D_5ST7SS.d.mts → types-C9zw9wbX.d.mts} +6 -0
  66. package/dist/{version-B5NV-35j.d.mts → version-D-u7aMfy.d.mts} +43 -1
  67. package/package.json +1 -1
  68. package/dist/normalise-tL9FckAk.mjs +0 -748
@@ -1,5 +1,5 @@
1
- import { T as SchemaMeta, m as JsonObject } from "../types-D_5ST7SS.mjs";
2
- import { i as DiagnosticsOptions } from "../diagnostics-BYk63jsC.mjs";
1
+ import { T as SchemaMeta, m as JsonObject } from "../types-C9zw9wbX.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>;
@@ -1,8 +1,9 @@
1
1
  import { getProperty, hasProperty, isObject } from "./guards.mjs";
2
+ import { SchemaNormalisationError } from "./errors.mjs";
2
3
  import { emitDiagnostic } from "./diagnostics.mjs";
3
4
  import { dereference } from "./ref.mjs";
4
- import { detectJsonSchemaDraft, detectOpenApiVersion, inferJsonSchemaDraftWithReason, isSwagger2 } from "./version.mjs";
5
- import { i as normaliseOpenApiSchemas, r as normaliseJsonSchema$1 } from "../normalise-tL9FckAk.mjs";
5
+ import { detectOpenApiVersion, inferJsonSchemaDraftWithReason, isSwagger2, matchJsonSchemaDraftUri } from "./version.mjs";
6
+ import { a as normaliseOpenApiSchemas, i as normaliseJsonSchema$1 } from "../normalise-CMMEl4cd.mjs";
6
7
  import { z } from "zod";
7
8
  //#region src/core/adapter.ts
8
9
  /**
@@ -30,9 +31,105 @@ function detectSchemaKind(input) {
30
31
  * but TypeScript cannot represent "has _zod.def" as the $ZodType parameter
31
32
  * that z.toJSONSchema expects. This is the library boundary equivalent of
32
33
  * object → Record<string, unknown> — the type mismatch is genuinely unavoidable.
34
+ *
35
+ * Any exception thrown by z.toJSONSchema is classified into a
36
+ * SchemaNormalisationError so the caller does not have to re-parse error
37
+ * message strings. The classification covers:
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"
44
+ * - Unrepresentable types — bigint, date, map, set, symbol, function, custom,
45
+ * undefined, void, NaN, and the literal-only forms `z.literal(undefined)`
46
+ * ("undefined-literal") and `z.literal(<bigint>)` ("bigint-literal") →
47
+ * zod-type-unrepresentable
48
+ * - The catch-all "Non-representable type encountered: <type>" fallback Zod
49
+ * 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
52
+ *
53
+ * The original error is preserved on each classified error via the `cause`
54
+ * field so consumers can still inspect the Zod stack trace.
33
55
  */
34
56
  function callToJsonSchema(schema) {
35
- return z.toJSONSchema(schema);
57
+ try {
58
+ return z.toJSONSchema(schema);
59
+ } catch (err) {
60
+ throw classifyZodConversionError(err, schema);
61
+ }
62
+ }
63
+ /**
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).
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.
84
+ */
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
+ ];
99
+ /**
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}`
105
+ */
106
+ const NON_REPRESENTABLE_TYPE_MARKER = "[toJSONSchema]: Non-representable type encountered:";
107
+ /**
108
+ * Marker for dynamic catch failures — Zod throws when `def.catchValue(...)`
109
+ * itself throws while building the JSON Schema default.
110
+ *
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.
118
+ */
119
+ const NESTED_ZOD3_MARKER = "Cannot read properties of undefined";
120
+ function classifyZodConversionError(err, schema) {
121
+ 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);
131
+ }
132
+ return new SchemaNormalisationError(`z.toJSONSchema() failed: ${message}`, schema, "zod-conversion-failed", void 0, err);
36
133
  }
37
134
  function normaliseSchema(input, ref, options) {
38
135
  if (ref === void 0 && isObject(input)) {
@@ -46,14 +143,14 @@ function normaliseSchema(input, ref, options) {
46
143
  result = normaliseZod4(input);
47
144
  break;
48
145
  case "zod3":
49
- result = normaliseZod3();
146
+ result = normaliseZod3(input);
50
147
  break;
51
148
  case "openapi":
52
- if (!isObject(input)) throw new Error("Invalid OpenAPI document");
149
+ if (!isObject(input)) throw new SchemaNormalisationError("Invalid OpenAPI document", input, "openapi-invalid");
53
150
  result = normaliseOpenApi(input, ref, options);
54
151
  break;
55
152
  case "jsonSchema":
56
- if (!isObject(input)) throw new Error("Invalid JSON Schema");
153
+ if (!isObject(input)) throw new SchemaNormalisationError("Invalid JSON Schema", input, "invalid-json-schema");
57
154
  result = normaliseJsonSchema(input, options?.diagnostics);
58
155
  break;
59
156
  }
@@ -62,10 +159,10 @@ function normaliseSchema(input, ref, options) {
62
159
  }
63
160
  function normaliseZod4(input) {
64
161
  const zod = getProperty(input, "_zod");
65
- if (!isObject(zod)) throw new Error("Invalid Zod 4 schema: missing _zod property");
66
- if (!("def" in zod)) throw new Error("Invalid Zod 4 schema: missing _zod.def");
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");
67
164
  const jsonSchema = callToJsonSchema(input);
68
- if (!isObject(jsonSchema)) throw new Error("z.toJSONSchema() did not produce an object");
165
+ if (!isObject(jsonSchema)) throw new SchemaNormalisationError("z.toJSONSchema() did not produce an object", input, "invalid-zod");
69
166
  return {
70
167
  jsonSchema,
71
168
  zodSchema: input,
@@ -75,7 +172,8 @@ function normaliseZod4(input) {
75
172
  }
76
173
  function normaliseJsonSchema(jsonSchema, diagnostics) {
77
174
  let draft;
78
- if (typeof jsonSchema.$schema !== "string") {
175
+ const $schema = jsonSchema.$schema;
176
+ if (typeof $schema !== "string") {
79
177
  const inferred = inferJsonSchemaDraftWithReason(jsonSchema);
80
178
  draft = inferred.draft;
81
179
  emitDiagnostic(diagnostics, {
@@ -87,16 +185,31 @@ function normaliseJsonSchema(jsonSchema, diagnostics) {
87
185
  draft: inferred.draft
88
186
  }
89
187
  });
90
- } else draft = detectJsonSchemaDraft(jsonSchema);
91
- const normalised = normaliseJsonSchema$1(jsonSchema, draft);
188
+ } else {
189
+ const matched = matchJsonSchemaDraftUri($schema);
190
+ if (matched === void 0) {
191
+ draft = "draft-2020-12";
192
+ emitDiagnostic(diagnostics, {
193
+ code: "assumed-draft",
194
+ message: `Unknown $schema URI "${$schema}"; assuming draft-2020-12`,
195
+ pointer: "",
196
+ detail: {
197
+ inferredFrom: "unknown-uri",
198
+ draft,
199
+ uri: $schema
200
+ }
201
+ });
202
+ } else draft = matched;
203
+ }
204
+ const normalised = normaliseJsonSchema$1(jsonSchema, draft, diagnostics);
92
205
  return {
93
206
  jsonSchema: normalised,
94
207
  rootMeta: extractRootMetaFromJson(normalised),
95
208
  rootDocument: normalised
96
209
  };
97
210
  }
98
- function normaliseZod3() {
99
- throw new Error("Zod 3 schemas are not supported. schema-components requires Zod 4. Detected: Zod 3 (has _def without _zod). See the Zod 4 migration guide at https://zod.dev/v4/migration or run: pnpm add zod@^4");
211
+ function normaliseZod3(input) {
212
+ throw new SchemaNormalisationError("Zod 3 schemas are not supported. schema-components requires Zod 4. Detected: Zod 3 (has _def without _zod). See the Zod 4 migration guide at https://zod.dev/v4/migration or run: pnpm add zod@^4", input, "zod3-unsupported");
100
213
  }
101
214
  /**
102
215
  * Mapping of Swagger 2.0 $ref prefixes to their OpenAPI 3.x equivalents.
@@ -143,7 +256,7 @@ function resolveOpenApiRef(doc, ref) {
143
256
  if (!isObject(resolved)) throw new Error(`OpenAPI ref not found: ${ref}`);
144
257
  return resolved;
145
258
  }
146
- const pathMatch = /^\/(.+)\/(get|post|put|patch|delete)$/.exec(ref);
259
+ const pathMatch = /^\/(.+)\/(get|post|put|patch|delete|head|options|trace)$/.exec(ref);
147
260
  if (pathMatch?.[1] !== void 0 && pathMatch[2] !== void 0) {
148
261
  const pathStr = pathMatch[1];
149
262
  const method = pathMatch[2];
@@ -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-BYk63jsC.mjs";
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";
3
3
 
4
4
  //#region src/core/constraints.d.ts
5
5
  declare function extractStringConstraints(schema: Record<string, unknown>, diagnostics?: DiagnosticsOptions, pointer?: string): StringConstraints;
@@ -1,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-BYk63jsC.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-C5zRC2PU.mjs";
1
+ import { i as SchemaRenderError, n as SchemaFieldError, r as SchemaNormalisationError, t as SchemaError } from "../errors-C2iABcn9.mjs";
2
2
  export { SchemaError, SchemaFieldError, SchemaNormalisationError, SchemaRenderError };
@@ -32,10 +32,24 @@ var SchemaError = class extends Error {
32
32
  */
33
33
  var SchemaNormalisationError = class extends SchemaError {
34
34
  kind;
35
- constructor(message, schema, kind) {
35
+ /**
36
+ * For `zod-type-unrepresentable`, the offending Zod type name
37
+ * (e.g. "bigint", "date", "map", "set"). `undefined` for other kinds.
38
+ */
39
+ 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
+ constructor(message, schema, kind, zodType, cause) {
36
48
  super(message, schema);
37
49
  this.name = "SchemaNormalisationError";
38
50
  this.kind = kind;
51
+ this.zodType = zodType;
52
+ this.cause = cause;
39
53
  }
40
54
  };
41
55
  /**
@@ -1,4 +1,4 @@
1
- import { M as WalkedField } from "../types-D_5ST7SS.mjs";
1
+ import { M as WalkedField } from "../types-C9zw9wbX.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})?$/,
@@ -19,11 +35,30 @@ const FORMAT_PATTERNS = {
19
35
  "relative-json-pointer": /^(0|[1-9][0-9]*)(#?([/]([^/~]|~0|~1)*)*)?$/,
20
36
  duration: /^P(?!$)(\d+Y)?(\d+M)?(\d+W)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+(\.\d+)?S)?)?$/,
21
37
  "idn-email": /^[^\s@]+@[^\s@]+\.[^\s@]+$/u,
22
- "idn-hostname": /^[a-z0-9\u00a1-\uffff]([a-z0-9\u00a1-\uffff-]{0,61}[a-z0-9\u00a1-\uffff])?(\.[a-z0-9\u00a1-\uffff]([a-z0-9\u00a1-\uffff-]{0,61}[a-z0-9\u00a1-\uffff])?)*$/iu
38
+ "idn-hostname": /^[a-z0-9\u00a1-\uffff]([a-z0-9\u00a1-\uffff-]{0,61}[a-z0-9\u00a1-\uffff])?(\.[a-z0-9\u00a1-\uffff]([a-z0-9\u00a1-\uffff-]{0,61}[a-z0-9\u00a1-\uffff])?)*$/iu,
39
+ cuid: /^[cC][0-9a-z]{6,}$/,
40
+ cuid2: /^[0-9a-z]+$/,
41
+ nanoid: /^[a-zA-Z0-9_-]{21}$/,
42
+ cidrv4: /^((25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\.){3}(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\/([0-9]|[1-2][0-9]|3[0-2])$/,
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])$/,
44
+ base64: /^$|^(?:[0-9a-zA-Z+/]{4})*(?:(?:[0-9a-zA-Z+/]{2}==)|(?:[0-9a-zA-Z+/]{3}=))?$/,
45
+ base64url: /^[A-Za-z0-9_-]*$/,
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_-]*$/
23
54
  };
24
55
  /**
25
56
  * Format validators that use predicate functions instead of regex.
26
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}.
27
62
  */
28
63
  const PREDICATE_VALIDATORS = {
29
64
  iri: (value) => {
@@ -49,19 +84,76 @@ const PREDICATE_VALIDATORS = {
49
84
  } catch {
50
85
  return false;
51
86
  }
87
+ },
88
+ "json-string": (value) => {
89
+ try {
90
+ JSON.parse(value);
91
+ return true;
92
+ } catch {
93
+ return false;
94
+ }
52
95
  }
53
96
  };
54
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
+ /**
55
142
  * Validate a string value against format constraints.
56
143
  * Returns `true` when the value matches the format,
57
144
  * `false` when it does not, and `undefined` when the format
58
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.
59
150
  */
60
- function validateFormat(value, format) {
151
+ function validateFormat(value, format, diagnostics, pointer = "") {
61
152
  const pattern = FORMAT_PATTERNS[format];
62
153
  if (pattern !== void 0) return pattern.test(value);
154
+ if (format === "regex") return validateRegexFormat(value, diagnostics, pointer);
63
155
  const predicate = PREDICATE_VALIDATORS[format];
64
156
  if (predicate !== void 0) return predicate(value);
65
157
  }
66
158
  //#endregion
67
- 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-BYk63jsC.mjs";
1
+ import { i as DiagnosticsOptions } from "../diagnostics-CbBPsxSt.mjs";
2
2
 
3
3
  //#region src/core/merge.d.ts
4
4
  /**
@@ -1,5 +1,5 @@
1
- import { i as DiagnosticsOptions } from "../diagnostics-BYk63jsC.mjs";
2
- import { n as JsonSchemaDraft, r as OpenApiVersionInfo } from "../version-B5NV-35j.mjs";
1
+ import { i as DiagnosticsOptions } from "../diagnostics-CbBPsxSt.mjs";
2
+ import { i as OpenApiVersionInfo, r as JsonSchemaDraft } from "../version-D-u7aMfy.mjs";
3
3
 
4
4
  //#region src/core/normalise.d.ts
5
5
  type NodeTransform = (node: Record<string, unknown>) => Record<string, unknown>;
@@ -8,9 +8,32 @@ type NodeTransform = (node: Record<string, unknown>) => Record<string, unknown>;
8
8
  * and recursing into every sub-schema location.
9
9
  */
10
10
  declare function deepNormalise(schema: Record<string, unknown>, transform: NodeTransform): Record<string, unknown>;
11
+ /**
12
+ * Per-node context threaded through `deepNormaliseWithContext`.
13
+ *
14
+ * Carries the diagnostics sink and the JSON Pointer to the current
15
+ * node so per-node transforms can emit pointer-accurate diagnostics
16
+ * when they translate or reject legacy constructs.
17
+ */
18
+ interface NodeContext {
19
+ diagnostics: DiagnosticsOptions | undefined;
20
+ pointer: string;
21
+ }
22
+ type NodeTransformWithContext = (node: Record<string, unknown>, ctx: NodeContext) => Record<string, unknown>;
23
+ /**
24
+ * Deep-normalise a JSON Schema object, threading a context (diagnostics
25
+ * sink + JSON Pointer) through each recursive call. Used by the JSON
26
+ * Schema normalisation path so per-node transforms can emit diagnostics
27
+ * with accurate pointers.
28
+ *
29
+ * Mirrors `deepNormalise` structurally — keep the two in sync when
30
+ * adding new sub-schema locations.
31
+ */
32
+ declare function deepNormaliseWithContext(schema: Record<string, unknown>, transform: NodeTransformWithContext, ctx: NodeContext): Record<string, unknown>;
11
33
  /**
12
34
  * Normalise Draft 04 `exclusiveMinimum`/`exclusiveMaximum` from boolean
13
- * to number form.
35
+ * to number form, plus the other Draft 04 translations applied to a
36
+ * single node.
14
37
  *
15
38
  * In Draft 04:
16
39
  * - `exclusiveMinimum: true` + `minimum: 5` → value must be > 5
@@ -22,13 +45,23 @@ declare function deepNormalise(schema: Record<string, unknown>, transform: NodeT
22
45
  *
23
46
  * The transform converts boolean form to number form so the walker can
24
47
  * treat `exclusiveMinimum`/`exclusiveMaximum` uniformly as numbers.
48
+ *
49
+ * This function preserves the no-context signature for the OpenAPI 3.0
50
+ * and Swagger 2.0 normalisers that compose it directly. The JSON Schema
51
+ * normalisation path uses {@link normaliseDraft04NodeWithContext} via
52
+ * {@link deepNormaliseWithContext} to thread diagnostics.
25
53
  */
26
54
  declare function normaliseDraft04Node(node: Record<string, unknown>): Record<string, unknown>;
27
55
  /**
28
56
  * Normalise a JSON Schema to canonical Draft 2020-12 form.
29
57
  * Deep-clones the input — the original is never mutated.
58
+ *
59
+ * When `diagnostics` is supplied, per-node transforms emit diagnostics
60
+ * for legacy-keyword rewrites and invalid constructs (e.g. `divisibleBy`
61
+ * conflicts, non-string entries in a `dependentRequired` array, legacy
62
+ * `dependencies` reaching the 2020-12 path).
30
63
  */
31
- declare function normaliseJsonSchema(schema: Record<string, unknown>, draft: JsonSchemaDraft): Record<string, unknown>;
64
+ declare function normaliseJsonSchema(schema: Record<string, unknown>, draft: JsonSchemaDraft, diagnostics?: DiagnosticsOptions): Record<string, unknown>;
32
65
  /**
33
66
  * Normalise an OpenAPI document's schemas for walker consumption.
34
67
  * Handles version-specific keyword transformations.
@@ -38,4 +71,4 @@ declare function normaliseJsonSchema(schema: Record<string, unknown>, draft: Jso
38
71
  */
39
72
  declare function normaliseOpenApiSchemas(doc: Record<string, unknown>, version: OpenApiVersionInfo, diagnostics?: DiagnosticsOptions): Record<string, unknown>;
40
73
  //#endregion
41
- export { NodeTransform, deepNormalise, normaliseDraft04Node, normaliseJsonSchema, normaliseOpenApiSchemas };
74
+ export { NodeContext, NodeTransform, NodeTransformWithContext, deepNormalise, deepNormaliseWithContext, normaliseDraft04Node, normaliseJsonSchema, normaliseOpenApiSchemas };
@@ -1,2 +1,2 @@
1
- import { i as normaliseOpenApiSchemas, n as normaliseDraft04Node, r as normaliseJsonSchema, t as deepNormalise } from "../normalise-tL9FckAk.mjs";
2
- export { deepNormalise, normaliseDraft04Node, normaliseJsonSchema, normaliseOpenApiSchemas };
1
+ import { a as normaliseOpenApiSchemas, i as normaliseJsonSchema, n as deepNormaliseWithContext, r as normaliseDraft04Node, t as deepNormalise } from "../normalise-CMMEl4cd.mjs";
2
+ export { deepNormalise, deepNormaliseWithContext, normaliseDraft04Node, normaliseJsonSchema, normaliseOpenApiSchemas };
@@ -32,10 +32,39 @@ declare function normaliseOpenApi30Discriminator(node: Record<string, unknown>):
32
32
  */
33
33
  declare function normaliseOpenApi30Combined(node: Record<string, unknown>): Record<string, unknown>;
34
34
  /**
35
- * Deep-normalise all schemas in an OpenAPI 3.0.x document.
36
- * Walks components/schemas, path operations, parameters, request bodies,
37
- * and responses applying `nullable` normalisation to each schema.
35
+ * Per-schema normaliser supplied by the caller. Given a Schema Object,
36
+ * returns the normalised (deep-cloned) Schema Object. The visitor is
37
+ * agnostic to which transforms run inside.
38
+ */
39
+ type SchemaNormaliser = (schema: Record<string, unknown>) => Record<string, unknown>;
40
+ /**
41
+ * Deep-clone the parent first, then patch back any keys whose values were
42
+ * rewritten by the visitor. This preserves immutability of the original
43
+ * document while keeping the visitor straightforward to write.
44
+ */
45
+ /**
46
+ * Deep-normalise every Schema Object in an OpenAPI document.
47
+ *
48
+ * Walks: `paths.*` (operations + path-level parameters), `webhooks.*`
49
+ * (3.1), `components.schemas`, `components.parameters`,
50
+ * `components.responses`, `components.requestBodies`,
51
+ * `components.headers`, `components.callbacks`, `components.pathItems`
52
+ * (3.1). For each Schema-bearing location, applies the supplied
53
+ * `normaliseSchema` function.
54
+ *
55
+ * The walker is structural (it understands OAS document shapes) and
56
+ * delegates the per-schema transformation. For OAS 3.0 the caller
57
+ * passes a full Draft 04 + nullable + discriminator + example
58
+ * normaliser; for OAS 3.1 the caller passes a discriminator-only
59
+ * normaliser so the walker's discriminated-union detection sees the
60
+ * injected `const`s regardless of OAS minor version.
61
+ */
62
+ declare function deepNormaliseOpenApiDoc(doc: Record<string, unknown>, normaliseSchema: SchemaNormaliser): Record<string, unknown>;
63
+ /**
64
+ * Backwards-compatible wrapper retaining the historic `deepNormalise`
65
+ * signature used by callers in `normalise.ts`. Always applies the full
66
+ * 3.0 combined transform via `deepNormalise(schema, normaliseOpenApi30Combined)`.
38
67
  */
39
68
  declare function deepNormaliseOpenApi30Doc(doc: Record<string, unknown>, deepNormalise: (schema: Record<string, unknown>, transform: NodeTransform) => Record<string, unknown>): Record<string, unknown>;
40
69
  //#endregion
41
- export { deepNormaliseOpenApi30Doc, normaliseOpenApi30Combined, normaliseOpenApi30Discriminator, normaliseOpenApi30Node };
70
+ export { deepNormaliseOpenApi30Doc, deepNormaliseOpenApiDoc, normaliseOpenApi30Combined, normaliseOpenApi30Discriminator, normaliseOpenApi30Node };
@@ -1,2 +1,2 @@
1
- import { c as normaliseOpenApi30Discriminator, l as normaliseOpenApi30Node, o as deepNormaliseOpenApi30Doc, s as normaliseOpenApi30Combined } from "../normalise-tL9FckAk.mjs";
2
- export { deepNormaliseOpenApi30Doc, normaliseOpenApi30Combined, normaliseOpenApi30Discriminator, normaliseOpenApi30Node };
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,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-Ckt5liZs.mjs";
1
+ import { a as findAnchor, i as dereference, n as RefOptions, o as resolveRef, r as countDistinctRefs, t as ExternalResolver } from "../ref-C8JbwfiS.mjs";
2
2
  export { ExternalResolver, RefOptions, countDistinctRefs, dereference, findAnchor, resolveRef };
@@ -1,2 +1,2 @@
1
- import { a as HtmlRenderProps, c as RenderFunction, d as getHtmlRenderFn, f as getRenderFunction, h as typeToKey, i as HtmlRenderFunction, l as RenderProps, m as mergeResolvers, n as BaseFieldProps, o as HtmlResolver, p as mergeHtmlResolvers, r as ComponentResolver, s as RESOLVER_KEYS, t as AllConstraints, u as buildRenderProps } from "../renderer-BAGoX4AK.mjs";
1
+ import { a as HtmlRenderProps, c as RenderFunction, d as getHtmlRenderFn, f as getRenderFunction, h as typeToKey, i as HtmlRenderFunction, l as RenderProps, m as mergeResolvers, n as BaseFieldProps, o as HtmlResolver, p as mergeHtmlResolvers, r as ComponentResolver, s as RESOLVER_KEYS, t as AllConstraints, u as buildRenderProps } from "../renderer-SOIbJBtk.mjs";
2
2
  export { AllConstraints, BaseFieldProps, ComponentResolver, HtmlRenderFunction, HtmlRenderProps, HtmlResolver, RESOLVER_KEYS, RenderFunction, RenderProps, buildRenderProps, getHtmlRenderFn, getRenderFunction, mergeHtmlResolvers, mergeResolvers, typeToKey };
@@ -33,6 +33,7 @@ const RESOLVER_KEYS = [
33
33
  "string",
34
34
  "number",
35
35
  "boolean",
36
+ "null",
36
37
  "enum",
37
38
  "object",
38
39
  "array",
@@ -45,17 +46,21 @@ const RESOLVER_KEYS = [
45
46
  "recursive",
46
47
  "literal",
47
48
  "file",
49
+ "never",
48
50
  "unknown"
49
51
  ];
50
52
  /**
51
53
  * Map a schema type to the resolver key that handles it.
52
- * `discriminatedUnion` `union`. Unknown types `unknown`.
54
+ * Every WalkedField variant has a direct resolver key — exhaustive switch
55
+ * ensures new variants surface as a type error rather than silently
56
+ * falling through to "unknown".
53
57
  */
54
58
  function typeToKey(type) {
55
59
  switch (type) {
56
60
  case "string":
57
61
  case "number":
58
62
  case "boolean":
63
+ case "null":
59
64
  case "enum":
60
65
  case "object":
61
66
  case "array":
@@ -68,8 +73,8 @@ function typeToKey(type) {
68
73
  case "recursive":
69
74
  case "literal":
70
75
  case "file":
76
+ case "never":
71
77
  case "unknown": return type;
72
- default: return "unknown";
73
78
  }
74
79
  }
75
80
  /**