schema-components 1.13.0 → 1.15.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 (71) hide show
  1. package/README.md +4 -0
  2. package/dist/core/adapter.d.mts +8 -3
  3. package/dist/core/adapter.mjs +25 -10
  4. package/dist/core/constraints.d.mts +3 -2
  5. package/dist/core/constraints.mjs +14 -2
  6. package/dist/core/diagnostics.d.mts +2 -0
  7. package/dist/core/diagnostics.mjs +33 -0
  8. package/dist/core/errors.d.mts +1 -1
  9. package/dist/core/formats.d.mts +33 -0
  10. package/dist/core/formats.mjs +67 -0
  11. package/dist/core/merge.d.mts +17 -1
  12. package/dist/core/merge.mjs +30 -1
  13. package/dist/core/normalise.d.mts +3 -2
  14. package/dist/core/normalise.mjs +1 -170
  15. package/dist/core/openapi30.d.mts +4 -1
  16. package/dist/core/openapi30.mjs +1 -222
  17. package/dist/core/ref.d.mts +2 -25
  18. package/dist/core/ref.mjs +102 -23
  19. package/dist/core/renderer.d.mts +1 -1
  20. package/dist/core/swagger2.d.mts +2 -1
  21. package/dist/core/swagger2.mjs +1 -293
  22. package/dist/core/typeInference.d.mts +2 -2
  23. package/dist/core/types.d.mts +2 -2
  24. package/dist/core/types.mjs +4 -1
  25. package/dist/core/version.d.mts +2 -2
  26. package/dist/core/version.mjs +84 -12
  27. package/dist/core/walkBuilders.d.mts +15 -1
  28. package/dist/core/walkBuilders.mjs +1 -1
  29. package/dist/core/walker.d.mts +1 -1
  30. package/dist/core/walker.mjs +122 -22
  31. package/dist/diagnostics-DzbZmcLI.d.mts +64 -0
  32. package/dist/html/a11y.d.mts +2 -2
  33. package/dist/html/renderToHtml.d.mts +2 -2
  34. package/dist/html/renderToHtmlStream.d.mts +2 -2
  35. package/dist/html/renderers.d.mts +2 -2
  36. package/dist/html/streamRenderers.d.mts +2 -2
  37. package/dist/normalise-tL9FckAk.mjs +748 -0
  38. package/dist/openapi/ApiCallbacks.d.mts +16 -0
  39. package/dist/openapi/ApiCallbacks.mjs +34 -0
  40. package/dist/openapi/ApiLinks.d.mts +16 -0
  41. package/dist/openapi/ApiLinks.mjs +42 -0
  42. package/dist/openapi/ApiResponseHeaders.d.mts +16 -0
  43. package/dist/openapi/ApiResponseHeaders.mjs +35 -0
  44. package/dist/openapi/ApiSecurity.d.mts +19 -0
  45. package/dist/openapi/ApiSecurity.mjs +33 -0
  46. package/dist/openapi/bundle.d.mts +47 -0
  47. package/dist/openapi/bundle.mjs +95 -0
  48. package/dist/openapi/components.d.mts +7 -2
  49. package/dist/openapi/components.mjs +30 -6
  50. package/dist/openapi/parser.d.mts +1 -1
  51. package/dist/react/SchemaComponent.d.mts +12 -5
  52. package/dist/react/SchemaComponent.mjs +13 -7
  53. package/dist/react/SchemaView.d.mts +10 -3
  54. package/dist/react/SchemaView.mjs +13 -7
  55. package/dist/react/fieldPath.d.mts +1 -1
  56. package/dist/react/headless.d.mts +1 -1
  57. package/dist/react/headlessRenderers.d.mts +1 -1
  58. package/dist/react/headlessRenderers.mjs +1 -1
  59. package/dist/ref-DvWoULcy.d.mts +44 -0
  60. package/dist/{renderer-DseHeliw.d.mts → renderer-BdSqllx5.d.mts} +1 -1
  61. package/dist/themes/mantine.d.mts +1 -1
  62. package/dist/themes/mui.d.mts +1 -1
  63. package/dist/themes/mui.mjs +1 -1
  64. package/dist/themes/radix.d.mts +1 -1
  65. package/dist/themes/shadcn.d.mts +1 -1
  66. package/dist/{typeInference-CRPqVwKu.d.mts → typeInference-k7FXfTVO.d.mts} +44 -8
  67. package/dist/{types-ag2jYLqQ.d.mts → types-D_5ST7SS.d.mts} +11 -3
  68. package/dist/version-B5NV-35j.d.mts +69 -0
  69. package/package.json +1 -1
  70. package/dist/version-CLchheaH.d.mts +0 -40
  71. /package/dist/{errors-DIKI2C78.d.mts → errors-C5zRC2PU.d.mts} +0 -0
package/README.md CHANGED
@@ -14,6 +14,10 @@ npm install schema-components
14
14
 
15
15
  Peer dependencies: `zod@^4.0.0`, `react@^18.0.0 || ^19.0.0`.
16
16
 
17
+ ### Zod version requirement
18
+
19
+ schema-components requires **Zod 4**. If you are on Zod 3, see the [Zod 4 migration guide](https://zod.dev/v4/migration). The library detects Zod 3 inputs and throws a descriptive error rather than silently misbehaving.
20
+
17
21
  ## `SchemaComponent`
18
22
 
19
23
  The single entry point. Accepts Zod schemas, JSON Schema objects, or OpenAPI documents:
@@ -1,4 +1,5 @@
1
- import { m as JsonObject, w as SchemaMeta } from "../types-ag2jYLqQ.mjs";
1
+ import { T as SchemaMeta, m as JsonObject } from "../types-D_5ST7SS.mjs";
2
+ import { i as DiagnosticsOptions } from "../diagnostics-DzbZmcLI.mjs";
2
3
 
3
4
  //#region src/core/adapter.d.ts
4
5
  type SchemaInput = Record<string, unknown>;
@@ -14,6 +15,10 @@ interface NormalisedSchema {
14
15
  /** The root document for $ref resolution. */
15
16
  rootDocument: JsonObject;
16
17
  }
17
- declare function normaliseSchema(input: unknown, ref?: string): NormalisedSchema;
18
+ interface NormaliseOptions {
19
+ /** Diagnostics channel for surfacing silent fallbacks. */
20
+ diagnostics?: DiagnosticsOptions;
21
+ }
22
+ declare function normaliseSchema(input: unknown, ref?: string, options?: NormaliseOptions): NormalisedSchema;
18
23
  //#endregion
19
- export { type JsonObject, NormalisedSchema, SchemaInput, SchemaKind, type SchemaMeta, detectSchemaKind, normaliseSchema };
24
+ export { type JsonObject, NormaliseOptions, NormalisedSchema, SchemaInput, SchemaKind, type SchemaMeta, detectSchemaKind, normaliseSchema };
@@ -1,7 +1,8 @@
1
1
  import { getProperty, hasProperty, isObject } from "./guards.mjs";
2
+ import { emitDiagnostic } from "./diagnostics.mjs";
2
3
  import { dereference } from "./ref.mjs";
3
- import { detectJsonSchemaDraft, detectOpenApiVersion, isSwagger2 } from "./version.mjs";
4
- import { normaliseJsonSchema as normaliseJsonSchema$1, normaliseOpenApiSchemas } from "./normalise.mjs";
4
+ import { detectJsonSchemaDraft, detectOpenApiVersion, inferJsonSchemaDraftWithReason, isSwagger2 } from "./version.mjs";
5
+ import { i as normaliseOpenApiSchemas, r as normaliseJsonSchema$1 } from "../normalise-tL9FckAk.mjs";
5
6
  import { z } from "zod";
6
7
  //#region src/core/adapter.ts
7
8
  /**
@@ -33,7 +34,7 @@ function detectSchemaKind(input) {
33
34
  function callToJsonSchema(schema) {
34
35
  return z.toJSONSchema(schema);
35
36
  }
36
- function normaliseSchema(input, ref) {
37
+ function normaliseSchema(input, ref, options) {
37
38
  if (ref === void 0 && isObject(input)) {
38
39
  const cached = schemaCache.get(input);
39
40
  if (cached !== void 0) return cached;
@@ -49,11 +50,11 @@ function normaliseSchema(input, ref) {
49
50
  break;
50
51
  case "openapi":
51
52
  if (!isObject(input)) throw new Error("Invalid OpenAPI document");
52
- result = normaliseOpenApi(input, ref);
53
+ result = normaliseOpenApi(input, ref, options);
53
54
  break;
54
55
  case "jsonSchema":
55
56
  if (!isObject(input)) throw new Error("Invalid JSON Schema");
56
- result = normaliseJsonSchema(input);
57
+ result = normaliseJsonSchema(input, options?.diagnostics);
57
58
  break;
58
59
  }
59
60
  if (ref === void 0 && isObject(input)) schemaCache.set(input, result);
@@ -72,8 +73,22 @@ function normaliseZod4(input) {
72
73
  rootDocument: jsonSchema
73
74
  };
74
75
  }
75
- function normaliseJsonSchema(jsonSchema) {
76
- const normalised = normaliseJsonSchema$1(jsonSchema, detectJsonSchemaDraft(jsonSchema));
76
+ function normaliseJsonSchema(jsonSchema, diagnostics) {
77
+ let draft;
78
+ if (typeof jsonSchema.$schema !== "string") {
79
+ const inferred = inferJsonSchemaDraftWithReason(jsonSchema);
80
+ draft = inferred.draft;
81
+ emitDiagnostic(diagnostics, {
82
+ code: "assumed-draft",
83
+ message: `No $schema present; inferred ${inferred.draft} from keywords (${inferred.inferredFrom})`,
84
+ pointer: "",
85
+ detail: {
86
+ inferredFrom: inferred.inferredFrom,
87
+ draft: inferred.draft
88
+ }
89
+ });
90
+ } else draft = detectJsonSchemaDraft(jsonSchema);
91
+ const normalised = normaliseJsonSchema$1(jsonSchema, draft);
77
92
  return {
78
93
  jsonSchema: normalised,
79
94
  rootMeta: extractRootMetaFromJson(normalised),
@@ -81,7 +96,7 @@ function normaliseJsonSchema(jsonSchema) {
81
96
  };
82
97
  }
83
98
  function normaliseZod3() {
84
- throw new Error("Zod 3 schemas are not yet supported. Convert to Zod 4 or provide JSON Schema directly.");
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");
85
100
  }
86
101
  /**
87
102
  * Mapping of Swagger 2.0 $ref prefixes to their OpenAPI 3.x equivalents.
@@ -93,9 +108,9 @@ const REF_REWRITES_ADAPTER = [
93
108
  ["#/parameters/", "#/components/parameters/"],
94
109
  ["#/responses/", "#/components/responses/"]
95
110
  ];
96
- function normaliseOpenApi(doc, ref) {
111
+ function normaliseOpenApi(doc, ref, options) {
97
112
  const version = detectOpenApiVersion(doc);
98
- const normalisedDoc = version !== void 0 ? normaliseOpenApiSchemas(doc, version) : doc;
113
+ const normalisedDoc = version !== void 0 ? normaliseOpenApiSchemas(doc, version, options?.diagnostics) : doc;
99
114
  let rewrittenRef = ref;
100
115
  if (rewrittenRef !== void 0 && version !== void 0 && isSwagger2(version)) {
101
116
  for (const [from, to] of REF_REWRITES_ADAPTER) if (rewrittenRef.startsWith(from)) {
@@ -1,7 +1,8 @@
1
- import { E as StringConstraints, b as ObjectConstraints, f as FileConstraints, t as ArrayConstraints, v as NumberConstraints } from "../types-ag2jYLqQ.mjs";
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-DzbZmcLI.mjs";
2
3
 
3
4
  //#region src/core/constraints.d.ts
4
- declare function extractStringConstraints(schema: Record<string, unknown>): StringConstraints;
5
+ declare function extractStringConstraints(schema: Record<string, unknown>, diagnostics?: DiagnosticsOptions, pointer?: string): StringConstraints;
5
6
  declare function extractNumberConstraints(schema: Record<string, unknown>): NumberConstraints;
6
7
  declare function extractArrayConstraints(schema: Record<string, unknown>): ArrayConstraints;
7
8
  declare function extractObjectConstraints(schema: Record<string, unknown>): ObjectConstraints;
@@ -1,4 +1,6 @@
1
1
  import { isObject } from "./guards.mjs";
2
+ import { emitDiagnostic } from "./diagnostics.mjs";
3
+ import { FORMAT_PATTERNS } from "./formats.mjs";
2
4
  //#region src/core/constraints.ts
3
5
  function getString(obj, key) {
4
6
  const value = obj[key];
@@ -12,7 +14,7 @@ function getObject(obj, key) {
12
14
  const value = obj[key];
13
15
  return isObject(value) ? value : void 0;
14
16
  }
15
- function extractStringConstraints(schema) {
17
+ function extractStringConstraints(schema, diagnostics, pointer = "") {
16
18
  const c = {};
17
19
  const minLength = getNumber(schema, "minLength");
18
20
  if (minLength !== void 0) c.minLength = minLength;
@@ -21,7 +23,17 @@ function extractStringConstraints(schema) {
21
23
  const pattern = getString(schema, "pattern");
22
24
  if (pattern !== void 0) c.pattern = pattern;
23
25
  const format = getString(schema, "format");
24
- if (format !== void 0) c.format = format;
26
+ if (format !== void 0) {
27
+ c.format = format;
28
+ const pattern = FORMAT_PATTERNS[format];
29
+ if (pattern !== void 0) c.formatPattern = pattern;
30
+ else if (format !== "binary") emitDiagnostic(diagnostics, {
31
+ code: "unknown-format",
32
+ message: `Unknown format: ${format}`,
33
+ pointer,
34
+ detail: { format }
35
+ });
36
+ }
25
37
  const contentEncoding = getString(schema, "contentEncoding");
26
38
  if (contentEncoding !== void 0) c.contentEncoding = contentEncoding;
27
39
  const contentMediaType = getString(schema, "contentMediaType");
@@ -0,0 +1,2 @@
1
+ import { a as appendPointer, i as DiagnosticsOptions, n as DiagnosticCode, o as emitDiagnostic, r as DiagnosticSink, t as Diagnostic } from "../diagnostics-DzbZmcLI.mjs";
2
+ export { Diagnostic, DiagnosticCode, DiagnosticSink, DiagnosticsOptions, appendPointer, emitDiagnostic };
@@ -0,0 +1,33 @@
1
+ import { SchemaNormalisationError } from "./errors.mjs";
2
+ //#region src/core/diagnostics.ts
3
+ /**
4
+ * Diagnostics channel for schema-components.
5
+ *
6
+ * Provides a structured way to surface silent fallbacks — unresolved `$ref`,
7
+ * unknown keywords, unknown `format` values, invalid `const` values,
8
+ * unsupported `type` entries, dropped Swagger 2.0 features, external
9
+ * `$ref`, type-negation fallbacks, and conditional fallbacks.
10
+ *
11
+ * Consumers pass a `DiagnosticSink` callback to receive diagnostics
12
+ * as they occur. By default, diagnostics are silently discarded.
13
+ * Setting `strict: true` converts any diagnostic into a thrown
14
+ * `SchemaCompatibilityError`.
15
+ */
16
+ /**
17
+ * Emit a diagnostic through the configured sink.
18
+ * When `strict` is enabled, throws a `SchemaCompatibilityError` instead.
19
+ */
20
+ function emitDiagnostic(opts, diagnostic) {
21
+ if (opts?.strict === true) throw new SchemaNormalisationError(`[${diagnostic.code}] ${diagnostic.message} (at ${diagnostic.pointer})`, diagnostic.detail, "unknown");
22
+ if (opts?.diagnostics !== void 0) opts.diagnostics(diagnostic);
23
+ }
24
+ /**
25
+ * Append a segment to a JSON Pointer.
26
+ * Encodes `/` and `~` per RFC 6901.
27
+ */
28
+ function appendPointer(base, segment) {
29
+ const escaped = segment.replace(/~/g, "~0").replace(/\//g, "~1");
30
+ return base === "" ? `/${escaped}` : `${base}/${escaped}`;
31
+ }
32
+ //#endregion
33
+ export { appendPointer, emitDiagnostic };
@@ -1,2 +1,2 @@
1
- import { i as SchemaRenderError, n as SchemaFieldError, r as SchemaNormalisationError, t as SchemaError } from "../errors-DIKI2C78.mjs";
1
+ import { i as SchemaRenderError, n as SchemaFieldError, r as SchemaNormalisationError, t as SchemaError } from "../errors-C5zRC2PU.mjs";
2
2
  export { SchemaError, SchemaFieldError, SchemaNormalisationError, SchemaRenderError };
@@ -0,0 +1,33 @@
1
+ //#region src/core/formats.d.ts
2
+ /**
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.
15
+ */
16
+ /**
17
+ * A format validator: either a RegExp pattern or a predicate function.
18
+ */
19
+ type FormatValidator = RegExp | ((value: string) => boolean);
20
+ /**
21
+ * Recognised JSON Schema formats with their validation patterns.
22
+ * Unknown formats emit an `unknown-format` diagnostic and skip derivation.
23
+ */
24
+ declare const FORMAT_PATTERNS: Readonly<Record<string, RegExp>>;
25
+ /**
26
+ * Validate a string value against format constraints.
27
+ * Returns `true` when the value matches the format,
28
+ * `false` when it does not, and `undefined` when the format
29
+ * is not recognised (no validator available).
30
+ */
31
+ declare function validateFormat(value: string, format: string): boolean | undefined;
32
+ //#endregion
33
+ export { FORMAT_PATTERNS, FormatValidator, validateFormat };
@@ -0,0 +1,67 @@
1
+ //#region src/core/formats.ts
2
+ /**
3
+ * Recognised JSON Schema formats with their validation patterns.
4
+ * Unknown formats emit an `unknown-format` diagnostic and skip derivation.
5
+ */
6
+ const FORMAT_PATTERNS = {
7
+ 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@]+$/,
9
+ "date-time": /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})$/,
10
+ date: /^\d{4}-\d{2}-\d{2}$/,
11
+ time: /^\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?$/,
12
+ ipv4: /^(\d{1,3}\.){3}\d{1,3}$/,
13
+ ipv6: /^([0-9a-f]{0,4}:){2,7}[0-9a-f]{0,4}$/i,
14
+ uri: /^[a-z][a-z0-9+\-.]*:/i,
15
+ hostname: /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/i,
16
+ "uri-reference": /^(|[a-z][a-z0-9+\-.]*:|[?#][^\s]*)/i,
17
+ "uri-template": /^([^{}]|[{][+#/.;?&=,!@|]([a-zA-Z0-9_%.]+)?(:[1-9][0-9]*)?(,[a-zA-Z0-9_%.]+)*[}])*$/,
18
+ "json-pointer": /^(([/]([^/~]|~0|~1)*)*|)$/,
19
+ "relative-json-pointer": /^(0|[1-9][0-9]*)(#?([/]([^/~]|~0|~1)*)*)?$/,
20
+ duration: /^P(?!$)(\d+Y)?(\d+M)?(\d+W)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+(\.\d+)?S)?)?$/,
21
+ "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
23
+ };
24
+ /**
25
+ * Format validators that use predicate functions instead of regex.
26
+ * These are checked in `validateFormat` when no regex pattern exists.
27
+ */
28
+ const PREDICATE_VALIDATORS = {
29
+ iri: (value) => {
30
+ try {
31
+ return new URL(value).protocol.length > 0;
32
+ } catch {
33
+ return false;
34
+ }
35
+ },
36
+ "iri-reference": (value) => {
37
+ if (value === "") return true;
38
+ try {
39
+ new URL(value);
40
+ return true;
41
+ } catch {
42
+ return !/\s/.test(value);
43
+ }
44
+ },
45
+ regex: (value) => {
46
+ try {
47
+ new RegExp(value);
48
+ return true;
49
+ } catch {
50
+ return false;
51
+ }
52
+ }
53
+ };
54
+ /**
55
+ * Validate a string value against format constraints.
56
+ * Returns `true` when the value matches the format,
57
+ * `false` when it does not, and `undefined` when the format
58
+ * is not recognised (no validator available).
59
+ */
60
+ function validateFormat(value, format) {
61
+ const pattern = FORMAT_PATTERNS[format];
62
+ if (pattern !== void 0) return pattern.test(value);
63
+ const predicate = PREDICATE_VALIDATORS[format];
64
+ if (predicate !== void 0) return predicate(value);
65
+ }
66
+ //#endregion
67
+ export { FORMAT_PATTERNS, validateFormat };
@@ -5,6 +5,22 @@
5
5
  * Used by the walker to handle `allOf`, `anyOf [T, null]`, and
6
6
  * `oneOf` with discriminator properties.
7
7
  */
8
+ /**
9
+ * Annotation keywords that can appear as siblings of `$ref` per
10
+ * Draft 2020-12 / OpenAPI 3.1. Structural keywords (type, properties,
11
+ * etc.) are NOT annotation siblings and should not be merged.
12
+ */
13
+ declare const ANNOTATION_SIBLINGS: ReadonlySet<string>;
14
+ /**
15
+ * Merge annotation siblings from the referencing node onto the
16
+ * resolved target's annotations. The referencer wins for annotations.
17
+ *
18
+ * Structural keywords on the referencer are NOT merged — per spec,
19
+ * `$ref` with structural siblings was invalid pre-2019-09.
20
+ *
21
+ * Returns a new meta object with the merged annotations.
22
+ */
23
+ declare function mergeRefSiblings(referencer: Record<string, unknown>, resolvedMeta: Record<string, unknown>): Record<string, unknown>;
8
24
  /**
9
25
  * Merge multiple JSON Schema objects from allOf into one.
10
26
  * Merges: properties, required, meta fields, and constraints.
@@ -29,4 +45,4 @@ interface Discriminated {
29
45
  */
30
46
  declare function detectDiscriminated(options: unknown[]): Discriminated | undefined;
31
47
  //#endregion
32
- export { Discriminated, NormalisedAnyOf, detectDiscriminated, mergeAllOf, normaliseAnyOf };
48
+ export { ANNOTATION_SIBLINGS, Discriminated, NormalisedAnyOf, detectDiscriminated, mergeAllOf, mergeRefSiblings, normaliseAnyOf };
@@ -19,6 +19,35 @@ function getObject(obj, key) {
19
19
  return isObject(value) ? value : void 0;
20
20
  }
21
21
  /**
22
+ * Annotation keywords that can appear as siblings of `$ref` per
23
+ * Draft 2020-12 / OpenAPI 3.1. Structural keywords (type, properties,
24
+ * etc.) are NOT annotation siblings and should not be merged.
25
+ */
26
+ const ANNOTATION_SIBLINGS = new Set([
27
+ "title",
28
+ "description",
29
+ "default",
30
+ "examples",
31
+ "deprecated",
32
+ "readOnly",
33
+ "writeOnly",
34
+ "$comment"
35
+ ]);
36
+ /**
37
+ * Merge annotation siblings from the referencing node onto the
38
+ * resolved target's annotations. The referencer wins for annotations.
39
+ *
40
+ * Structural keywords on the referencer are NOT merged — per spec,
41
+ * `$ref` with structural siblings was invalid pre-2019-09.
42
+ *
43
+ * Returns a new meta object with the merged annotations.
44
+ */
45
+ function mergeRefSiblings(referencer, resolvedMeta) {
46
+ const merged = { ...resolvedMeta };
47
+ for (const key of ANNOTATION_SIBLINGS) if (key in referencer) merged[key] = referencer[key];
48
+ return merged;
49
+ }
50
+ /**
22
51
  * Merge multiple JSON Schema objects from allOf into one.
23
52
  * Merges: properties, required, meta fields, and constraints.
24
53
  */
@@ -93,4 +122,4 @@ function detectDiscriminated(options) {
93
122
  };
94
123
  }
95
124
  //#endregion
96
- export { detectDiscriminated, mergeAllOf, normaliseAnyOf };
125
+ export { ANNOTATION_SIBLINGS, detectDiscriminated, mergeAllOf, mergeRefSiblings, normaliseAnyOf };
@@ -1,4 +1,5 @@
1
- import { n as OpenApiVersionInfo, t as JsonSchemaDraft } from "../version-CLchheaH.mjs";
1
+ import { i as DiagnosticsOptions } from "../diagnostics-DzbZmcLI.mjs";
2
+ import { n as JsonSchemaDraft, r as OpenApiVersionInfo } from "../version-B5NV-35j.mjs";
2
3
 
3
4
  //#region src/core/normalise.d.ts
4
5
  type NodeTransform = (node: Record<string, unknown>) => Record<string, unknown>;
@@ -35,6 +36,6 @@ declare function normaliseJsonSchema(schema: Record<string, unknown>, draft: Jso
35
36
  * Returns the same object reference if no normalisation is needed
36
37
  * (OpenAPI 3.1.x), or a deep-cloned normalised copy otherwise.
37
38
  */
38
- declare function normaliseOpenApiSchemas(doc: Record<string, unknown>, version: OpenApiVersionInfo): Record<string, unknown>;
39
+ declare function normaliseOpenApiSchemas(doc: Record<string, unknown>, version: OpenApiVersionInfo, diagnostics?: DiagnosticsOptions): Record<string, unknown>;
39
40
  //#endregion
40
41
  export { NodeTransform, deepNormalise, normaliseDraft04Node, normaliseJsonSchema, normaliseOpenApiSchemas };
@@ -1,171 +1,2 @@
1
- import { isObject } from "./guards.mjs";
2
- import { isOpenApi30, isSwagger2 } from "./version.mjs";
3
- import { deepNormaliseOpenApi30Doc } from "./openapi30.mjs";
4
- import { normaliseSwagger2Document } from "./swagger2.mjs";
5
- //#region src/core/normalise.ts
6
- /**
7
- * Keys whose values are `Record<string, SubSchema>` — objects where each
8
- * property is a sub-schema.
9
- */
10
- const OBJECT_SUBSCHEMA_KEYS = new Set([
11
- "properties",
12
- "patternProperties",
13
- "$defs",
14
- "definitions",
15
- "dependentSchemas"
16
- ]);
17
- /**
18
- * Keys whose values are `SubSchema[]` — arrays of sub-schemas.
19
- */
20
- const ARRAY_SUBSCHEMA_KEYS = new Set([
21
- "allOf",
22
- "anyOf",
23
- "oneOf",
24
- "prefixItems"
25
- ]);
26
- /**
27
- * Keys whose values are a single sub-schema object.
28
- */
29
- const SINGLE_SUBSCHEMA_KEYS = new Set([
30
- "additionalProperties",
31
- "not",
32
- "contains",
33
- "propertyNames",
34
- "if",
35
- "then",
36
- "else",
37
- "unevaluatedProperties",
38
- "unevaluatedItems"
39
- ]);
40
- /**
41
- * Normalise each element of an unknown array by applying deepNormalise
42
- * to object elements and passing others through unchanged.
43
- */
44
- function normaliseArray(items, transform) {
45
- const result = [];
46
- for (const item of items) result.push(isObject(item) ? deepNormalise(item, transform) : item);
47
- return result;
48
- }
49
- /**
50
- * Normalise each value of a sub-schema map (e.g. properties, $defs).
51
- */
52
- function normaliseSubSchemaMap(map, transform) {
53
- const result = {};
54
- for (const [k, v] of Object.entries(map)) result[k] = isObject(v) ? deepNormalise(v, transform) : v;
55
- return result;
56
- }
57
- /**
58
- * Deep-normalise a JSON Schema object by applying a per-node transform
59
- * and recursing into every sub-schema location.
60
- */
61
- function deepNormalise(schema, transform) {
62
- const node = transform({ ...schema });
63
- const result = {};
64
- for (const [key, value] of Object.entries(node)) if (isObject(value) && OBJECT_SUBSCHEMA_KEYS.has(key)) result[key] = normaliseSubSchemaMap(value, transform);
65
- else if (Array.isArray(value) && ARRAY_SUBSCHEMA_KEYS.has(key)) result[key] = normaliseArray(value, transform);
66
- else if (isObject(value) && SINGLE_SUBSCHEMA_KEYS.has(key)) result[key] = deepNormalise(value, transform);
67
- else if (key === "items") if (Array.isArray(value)) result[key] = normaliseArray(value, transform);
68
- else if (isObject(value)) result[key] = deepNormalise(value, transform);
69
- else result[key] = value;
70
- else result[key] = value;
71
- return result;
72
- }
73
- /**
74
- * Normalise Draft 04 `exclusiveMinimum`/`exclusiveMaximum` from boolean
75
- * to number form.
76
- *
77
- * In Draft 04:
78
- * - `exclusiveMinimum: true` + `minimum: 5` → value must be > 5
79
- * - `exclusiveMinimum: false` (or absent) + `minimum: 5` → value must be >= 5
80
- *
81
- * In Draft 06+:
82
- * - `exclusiveMinimum: 5` → value must be > 5 (no separate `minimum`)
83
- * - `minimum: 5` → value must be >= 5
84
- *
85
- * The transform converts boolean form to number form so the walker can
86
- * treat `exclusiveMinimum`/`exclusiveMaximum` uniformly as numbers.
87
- */
88
- function normaliseDraft04Node(node) {
89
- if (node.exclusiveMinimum === true && typeof node.minimum === "number") {
90
- node.exclusiveMinimum = node.minimum;
91
- delete node.minimum;
92
- } else if (node.exclusiveMinimum === false) delete node.exclusiveMinimum;
93
- if (node.exclusiveMaximum === true && typeof node.maximum === "number") {
94
- node.exclusiveMaximum = node.maximum;
95
- delete node.maximum;
96
- } else if (node.exclusiveMaximum === false) delete node.exclusiveMaximum;
97
- if (typeof node.id === "string" && !("$id" in node)) {
98
- node.$id = node.id;
99
- delete node.id;
100
- }
101
- if (Array.isArray(node.items) && !("prefixItems" in node)) {
102
- node.prefixItems = node.items;
103
- delete node.items;
104
- if ("additionalItems" in node) {
105
- node.items = node.additionalItems;
106
- delete node.additionalItems;
107
- }
108
- }
109
- return node;
110
- }
111
- /**
112
- * Normalise Draft 2019-09 `$recursiveRef` to `$ref`.
113
- *
114
- * `$recursiveRef` resolves to the nearest `$recursiveAnchor` in the
115
- * dynamic scope. For rendering, the common pattern is a root
116
- * `$recursiveAnchor: true` — the normaliser converts
117
- * `$recursiveRef: "#"` to `$ref: "#"` pointing to the root.
118
- *
119
- * If a `$recursiveAnchor` name is given (non-empty string), the ref
120
- * is converted to `$ref: "#<anchor>"` so the existing $anchor
121
- * resolution in ref.ts can find it.
122
- */
123
- function normaliseDraft201909Node(node) {
124
- if (typeof node.$recursiveRef === "string") {
125
- node.$ref = "#";
126
- delete node.$recursiveRef;
127
- }
128
- if (node.$recursiveAnchor === true) {
129
- if (typeof node.$anchor !== "string") node.$anchor = "__recursive__";
130
- delete node.$recursiveAnchor;
131
- }
132
- return node;
133
- }
134
- function normaliseDynamicRefNode(node) {
135
- if (typeof node.$dynamicRef === "string") {
136
- node.$ref = node.$dynamicRef;
137
- delete node.$dynamicRef;
138
- }
139
- if (typeof node.$dynamicAnchor === "string") {
140
- if (typeof node.$anchor !== "string") node.$anchor = node.$dynamicAnchor;
141
- delete node.$dynamicAnchor;
142
- }
143
- return node;
144
- }
145
- /**
146
- * Normalise a JSON Schema to canonical Draft 2020-12 form.
147
- * Deep-clones the input — the original is never mutated.
148
- */
149
- function normaliseJsonSchema(schema, draft) {
150
- switch (draft) {
151
- case "draft-04": return deepNormalise(schema, normaliseDraft04Node);
152
- case "draft-2019-09": return deepNormalise(schema, normaliseDraft201909Node);
153
- case "draft-2020-12": return deepNormalise(schema, normaliseDynamicRefNode);
154
- case "draft-06":
155
- case "draft-07": return schema;
156
- }
157
- }
158
- /**
159
- * Normalise an OpenAPI document's schemas for walker consumption.
160
- * Handles version-specific keyword transformations.
161
- *
162
- * Returns the same object reference if no normalisation is needed
163
- * (OpenAPI 3.1.x), or a deep-cloned normalised copy otherwise.
164
- */
165
- function normaliseOpenApiSchemas(doc, version) {
166
- if (isSwagger2(version)) return normaliseSwagger2Document(doc, deepNormalise, normaliseDraft04Node);
167
- if (isOpenApi30(version)) return deepNormaliseOpenApi30Doc(doc, deepNormalise);
168
- return doc;
169
- }
170
- //#endregion
1
+ import { i as normaliseOpenApiSchemas, n as normaliseDraft04Node, r as normaliseJsonSchema, t as deepNormalise } from "../normalise-tL9FckAk.mjs";
171
2
  export { deepNormalise, normaliseDraft04Node, normaliseJsonSchema, normaliseOpenApiSchemas };
@@ -24,8 +24,11 @@ declare function normaliseOpenApi30Node(node: Record<string, unknown>): Record<s
24
24
  */
25
25
  declare function normaliseOpenApi30Discriminator(node: Record<string, unknown>): Record<string, unknown>;
26
26
  /**
27
- * Combined OpenAPI 3.0.x node transform: nullable + discriminator.
27
+ * Combined OpenAPI 3.0.x node transform: Draft 04 + nullable + discriminator.
28
28
  * Applied to every schema node in an OpenAPI 3.0 document.
29
+ *
30
+ * Draft 04 normalisation is included because OpenAPI 3.0 inherits
31
+ * Draft 04/05 schema semantics including `exclusiveMinimum: boolean`.
29
32
  */
30
33
  declare function normaliseOpenApi30Combined(node: Record<string, unknown>): Record<string, unknown>;
31
34
  /**