schema-components 1.22.0 → 1.23.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 (82) hide show
  1. package/README.md +3 -1
  2. package/dist/core/adapter.d.mts +97 -3
  3. package/dist/core/adapter.mjs +260 -111
  4. package/dist/core/constraints.d.mts +2 -2
  5. package/dist/core/constraints.mjs +0 -7
  6. package/dist/core/cssClasses.d.mts +52 -0
  7. package/dist/core/cssClasses.mjs +51 -0
  8. package/dist/core/diagnostics.d.mts +1 -1
  9. package/dist/core/errors.d.mts +1 -1
  10. package/dist/core/errors.mjs +5 -13
  11. package/dist/core/fieldOrder.d.mts +1 -1
  12. package/dist/core/formats.d.mts +9 -2
  13. package/dist/core/formats.mjs +12 -1
  14. package/dist/core/idPath.d.mts +54 -0
  15. package/dist/core/idPath.mjs +66 -0
  16. package/dist/core/merge.d.mts +10 -1
  17. package/dist/core/merge.mjs +49 -10
  18. package/dist/core/normalise.d.mts +14 -3
  19. package/dist/core/normalise.mjs +2 -2
  20. package/dist/core/openapi30.d.mts +15 -1
  21. package/dist/core/openapi30.mjs +2 -2
  22. package/dist/core/openapiConstants.d.mts +67 -0
  23. package/dist/core/openapiConstants.mjs +90 -0
  24. package/dist/core/ref.d.mts +2 -2
  25. package/dist/core/ref.mjs +84 -6
  26. package/dist/core/refChain.d.mts +70 -0
  27. package/dist/core/refChain.mjs +44 -0
  28. package/dist/core/renderer.d.mts +1 -1
  29. package/dist/core/swagger2.d.mts +1 -1
  30. package/dist/core/swagger2.mjs +1 -1
  31. package/dist/core/typeInference.d.mts +982 -2
  32. package/dist/core/types.d.mts +1 -1
  33. package/dist/core/unionMatch.d.mts +36 -0
  34. package/dist/core/unionMatch.mjs +53 -0
  35. package/dist/core/version.d.mts +1 -1
  36. package/dist/core/version.mjs +29 -17
  37. package/dist/core/walkBuilders.d.mts +23 -4
  38. package/dist/core/walkBuilders.mjs +27 -7
  39. package/dist/core/walker.d.mts +1 -1
  40. package/dist/core/walker.mjs +44 -45
  41. package/dist/{diagnostics-D0QCYGv0.d.mts → diagnostics-BS2kaUyE.d.mts} +1 -1
  42. package/dist/{errors-DpFwqs5C.d.mts → errors-g_MCTQel.d.mts} +9 -15
  43. package/dist/html/a11y.d.mts +9 -4
  44. package/dist/html/a11y.mjs +10 -19
  45. package/dist/html/renderToHtml.d.mts +2 -2
  46. package/dist/html/renderToHtmlStream.d.mts +2 -2
  47. package/dist/html/renderToHtmlStream.mjs +12 -1
  48. package/dist/html/renderers.d.mts +43 -8
  49. package/dist/html/renderers.mjs +136 -111
  50. package/dist/html/streamRenderers.d.mts +4 -5
  51. package/dist/html/streamRenderers.mjs +40 -61
  52. package/dist/{normalise-DVEJQmF7.mjs → normalise-DCYp06Sr.mjs} +352 -162
  53. package/dist/openapi/ApiCallbacks.d.mts +1 -1
  54. package/dist/openapi/ApiLinks.d.mts +1 -1
  55. package/dist/openapi/ApiResponseHeaders.d.mts +1 -1
  56. package/dist/openapi/ApiSecurity.d.mts +1 -1
  57. package/dist/openapi/components.d.mts +116 -37
  58. package/dist/openapi/components.mjs +54 -37
  59. package/dist/openapi/parser.d.mts +9 -8
  60. package/dist/openapi/parser.mjs +234 -84
  61. package/dist/openapi/resolve.d.mts +20 -11
  62. package/dist/openapi/resolve.mjs +133 -73
  63. package/dist/react/SchemaComponent.d.mts +32 -7
  64. package/dist/react/SchemaComponent.mjs +45 -21
  65. package/dist/react/SchemaView.d.mts +30 -10
  66. package/dist/react/a11y.d.mts +21 -0
  67. package/dist/react/a11y.mjs +24 -0
  68. package/dist/react/fieldPath.d.mts +1 -1
  69. package/dist/react/headless.d.mts +1 -1
  70. package/dist/react/headlessRenderers.d.mts +8 -9
  71. package/dist/react/headlessRenderers.mjs +41 -72
  72. package/dist/{ref-D-_JBZkF.d.mts → ref-DjLEKa_E.d.mts} +38 -3
  73. package/dist/{renderer-BaRlQIuN.d.mts → renderer-CXJ8y0qw.d.mts} +1 -1
  74. package/dist/themes/mantine.d.mts +1 -1
  75. package/dist/themes/mui.d.mts +1 -1
  76. package/dist/themes/radix.d.mts +1 -1
  77. package/dist/themes/shadcn.d.mts +1 -1
  78. package/dist/themes/shadcn.mjs +2 -1
  79. package/dist/{types-BrRMV0en.d.mts → types-BTB73MB8.d.mts} +32 -4
  80. package/dist/{version-D2jfdX6E.d.mts → version-BFTVLsdb.d.mts} +7 -1
  81. package/package.json +1 -1
  82. package/dist/typeInference-DkcUHfaM.d.mts +0 -982
@@ -0,0 +1,52 @@
1
+ //#region src/core/cssClasses.d.ts
2
+ /**
3
+ * Shared CSS class names and visual placeholders used by every render
4
+ * pipeline (React, HTML-sync, HTML-stream). Centralising avoids drift
5
+ * between renderers and the bundled default stylesheet (`styles.css`).
6
+ *
7
+ * Class names are intentionally namespaced under `sc-` so consumer themes
8
+ * can pattern-match or override without collision risk.
9
+ */
10
+ /** Common em-dash placeholder for empty / unset values. */
11
+ declare const EM_DASH = "\u2014";
12
+ /** Single-character ellipsis used in placeholders like "Select…". */
13
+ declare const ELLIPSIS = "\u2026";
14
+ /** Prefix applied to every generated DOM id by `buildInputId`. */
15
+ declare const SC_ID_PREFIX = "sc-";
16
+ /**
17
+ * Canonical CSS class names exposed by the default render pipelines.
18
+ * Keys are stable identifiers; values are the raw class strings emitted
19
+ * to the DOM. Add new entries here rather than embedding class literals
20
+ * in renderers — `styles.css` cross-references the same names.
21
+ */
22
+ declare const SC_CLASSES: {
23
+ readonly value: "sc-value";
24
+ readonly valueEmpty: "sc-value sc-value--empty";
25
+ readonly field: "sc-field";
26
+ readonly label: "sc-label";
27
+ readonly input: "sc-input";
28
+ readonly hint: "sc-hint";
29
+ readonly required: "sc-required";
30
+ readonly object: "sc-object";
31
+ readonly array: "sc-array";
32
+ readonly record: "sc-record";
33
+ readonly tuple: "sc-tuple";
34
+ readonly tupleItem: "sc-tuple-item";
35
+ readonly tupleRest: "sc-tuple-rest";
36
+ readonly tupleIndex: "sc-tuple-index";
37
+ readonly discriminatedUnion: "sc-discriminated-union";
38
+ readonly tabs: "sc-tabs";
39
+ readonly tab: "sc-tab";
40
+ readonly tabActive: "sc-tab sc-tab--active";
41
+ readonly tabPanel: "sc-tab-panel";
42
+ readonly conditional: "sc-conditional";
43
+ readonly conditionalIf: "sc-conditional-if";
44
+ readonly conditionalThen: "sc-conditional-then";
45
+ readonly conditionalElse: "sc-conditional-else";
46
+ readonly negation: "sc-negation";
47
+ readonly never: "sc-never";
48
+ readonly recursive: "sc-recursive";
49
+ };
50
+ type ScClassKey = keyof typeof SC_CLASSES;
51
+ //#endregion
52
+ export { ELLIPSIS, EM_DASH, SC_CLASSES, SC_ID_PREFIX, ScClassKey };
@@ -0,0 +1,51 @@
1
+ //#region src/core/cssClasses.ts
2
+ /**
3
+ * Shared CSS class names and visual placeholders used by every render
4
+ * pipeline (React, HTML-sync, HTML-stream). Centralising avoids drift
5
+ * between renderers and the bundled default stylesheet (`styles.css`).
6
+ *
7
+ * Class names are intentionally namespaced under `sc-` so consumer themes
8
+ * can pattern-match or override without collision risk.
9
+ */
10
+ /** Common em-dash placeholder for empty / unset values. */
11
+ const EM_DASH = "—";
12
+ /** Single-character ellipsis used in placeholders like "Select…". */
13
+ const ELLIPSIS = "…";
14
+ /** Prefix applied to every generated DOM id by `buildInputId`. */
15
+ const SC_ID_PREFIX = "sc-";
16
+ /**
17
+ * Canonical CSS class names exposed by the default render pipelines.
18
+ * Keys are stable identifiers; values are the raw class strings emitted
19
+ * to the DOM. Add new entries here rather than embedding class literals
20
+ * in renderers — `styles.css` cross-references the same names.
21
+ */
22
+ const SC_CLASSES = {
23
+ value: "sc-value",
24
+ valueEmpty: "sc-value sc-value--empty",
25
+ field: "sc-field",
26
+ label: "sc-label",
27
+ input: "sc-input",
28
+ hint: "sc-hint",
29
+ required: "sc-required",
30
+ object: "sc-object",
31
+ array: "sc-array",
32
+ record: "sc-record",
33
+ tuple: "sc-tuple",
34
+ tupleItem: "sc-tuple-item",
35
+ tupleRest: "sc-tuple-rest",
36
+ tupleIndex: "sc-tuple-index",
37
+ discriminatedUnion: "sc-discriminated-union",
38
+ tabs: "sc-tabs",
39
+ tab: "sc-tab",
40
+ tabActive: "sc-tab sc-tab--active",
41
+ tabPanel: "sc-tab-panel",
42
+ conditional: "sc-conditional",
43
+ conditionalIf: "sc-conditional-if",
44
+ conditionalThen: "sc-conditional-then",
45
+ conditionalElse: "sc-conditional-else",
46
+ negation: "sc-negation",
47
+ never: "sc-never",
48
+ recursive: "sc-recursive"
49
+ };
50
+ //#endregion
51
+ export { ELLIPSIS, EM_DASH, SC_CLASSES, SC_ID_PREFIX };
@@ -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-D0QCYGv0.mjs";
1
+ import { a as appendPointer, i as DiagnosticsOptions, n as DiagnosticCode, o as emitDiagnostic, r as DiagnosticSink, t as Diagnostic } from "../diagnostics-BS2kaUyE.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-DpFwqs5C.mjs";
1
+ import { i as SchemaRenderError, n as SchemaFieldError, r as SchemaNormalisationError, t as SchemaError } from "../errors-g_MCTQel.mjs";
2
2
  export { SchemaError, SchemaFieldError, SchemaNormalisationError, SchemaRenderError };
@@ -1,17 +1,5 @@
1
1
  //#region src/core/errors.ts
2
2
  /**
3
- * Structured error types for schema-components.
4
- *
5
- * Every error produced by the library is one of these three types:
6
- *
7
- * - SchemaNormalisationError — the adapter failed to convert the input
8
- * to JSON Schema (invalid Zod, bad OpenAPI ref, malformed schema)
9
- * - SchemaRenderError — a theme adapter's render function threw
10
- * - SchemaFieldError — a field path couldn't be resolved
11
- *
12
- * All extend `SchemaError` so consumers can catch the base class.
13
- */
14
- /**
15
3
  * Base class for all schema-components errors.
16
4
  * Catch this to handle any library error uniformly.
17
5
  *
@@ -55,7 +43,11 @@ var SchemaNormalisationError = class extends SchemaError {
55
43
  * The `cause` is the original error from the render function.
56
44
  */
57
45
  var SchemaRenderError = class extends SchemaError {
58
- /** The schema type being rendered when the error occurred. */
46
+ /**
47
+ * The schema type being rendered when the error occurred. Drawn from
48
+ * the walker's discriminant union so consumers can switch on it
49
+ * exhaustively without a wider `string` fallback.
50
+ */
59
51
  schemaType;
60
52
  constructor(message, schema, schemaType, cause) {
61
53
  super(message, schema, cause);
@@ -1,4 +1,4 @@
1
- import { j as WalkedField } from "../types-BrRMV0en.mjs";
1
+ import { j as WalkedField } from "../types-BTB73MB8.mjs";
2
2
 
3
3
  //#region src/core/fieldOrder.d.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { i as DiagnosticsOptions } from "../diagnostics-D0QCYGv0.mjs";
1
+ import { i as DiagnosticsOptions } from "../diagnostics-BS2kaUyE.mjs";
2
2
 
3
3
  //#region src/core/formats.d.ts
4
4
  /**
@@ -9,6 +9,13 @@ import { i as DiagnosticsOptions } from "../diagnostics-D0QCYGv0.mjs";
9
9
  * it reaches the regex engine.
10
10
  */
11
11
  declare const MAX_REGEX_PATTERN_LENGTH = 500;
12
+ /**
13
+ * Map a JSON Schema string `format` to the matching HTML `<input type="…">`
14
+ * value, or `undefined` when the format does not correspond to a date/time
15
+ * input. Shared by the HTML and headless React renderers so a single
16
+ * mapping table governs both pipelines.
17
+ */
18
+ declare function dateInputType(format: string | undefined): string | undefined;
12
19
  /**
13
20
  * A format validator: either a RegExp pattern or a predicate function.
14
21
  */
@@ -58,4 +65,4 @@ declare const FORMAT_PATTERNS: Readonly<Record<string, RegExp>>;
58
65
  */
59
66
  declare function validateFormat(value: string, format: string, diagnostics?: DiagnosticsOptions, pointer?: string): boolean | undefined;
60
67
  //#endregion
61
- export { EMAIL_FORMAT_PATTERN, FORMAT_PATTERNS, FormatValidator, MAX_REGEX_PATTERN_LENGTH, validateFormat };
68
+ export { EMAIL_FORMAT_PATTERN, FORMAT_PATTERNS, FormatValidator, MAX_REGEX_PATTERN_LENGTH, dateInputType, validateFormat };
@@ -9,6 +9,17 @@ import { emitDiagnostic } from "./diagnostics.mjs";
9
9
  */
10
10
  const MAX_REGEX_PATTERN_LENGTH = 500;
11
11
  /**
12
+ * Map a JSON Schema string `format` to the matching HTML `<input type="…">`
13
+ * value, or `undefined` when the format does not correspond to a date/time
14
+ * input. Shared by the HTML and headless React renderers so a single
15
+ * mapping table governs both pipelines.
16
+ */
17
+ function dateInputType(format) {
18
+ if (format === "date") return "date";
19
+ if (format === "time") return "time";
20
+ if (format === "date-time" || format === "datetime") return "datetime-local";
21
+ }
22
+ /**
12
23
  * Recognised JSON Schema formats with their validation patterns.
13
24
  * Unknown formats emit an `unknown-format` diagnostic and skip derivation.
14
25
  *
@@ -177,4 +188,4 @@ function validateFormat(value, format, diagnostics, pointer = "") {
177
188
  if (predicate !== void 0) return predicate(value);
178
189
  }
179
190
  //#endregion
180
- export { EMAIL_FORMAT_PATTERN, FORMAT_PATTERNS, MAX_REGEX_PATTERN_LENGTH, validateFormat };
191
+ export { EMAIL_FORMAT_PATTERN, FORMAT_PATTERNS, MAX_REGEX_PATTERN_LENGTH, dateInputType, validateFormat };
@@ -0,0 +1,54 @@
1
+ //#region src/core/idPath.d.ts
2
+ /**
3
+ * Canonical DOM-id generation from structural paths.
4
+ *
5
+ * Every render pipeline (React headless, HTML sync, HTML stream) needs to
6
+ * derive stable DOM ids from the same path so `aria-controls`, `aria-labelledby`,
7
+ * and `htmlFor` references resolve consistently across pipelines.
8
+ *
9
+ * Previously each pipeline carried its own copy with subtly different
10
+ * normalisation (raw path in one place, dot/bracket-collapsed in another,
11
+ * whitelist-collapsed in a third). The streaming renderer's tab panel ids
12
+ * silently diverged from the sync renderer's because of that drift.
13
+ *
14
+ * Pipelines should import the helpers below rather than re-deriving them.
15
+ */
16
+ /**
17
+ * Normalise a structural path into the id segment used after the `sc-`
18
+ * prefix. Whitelist-based: any run of characters outside `[A-Za-z0-9_-]`
19
+ * collapses to a single hyphen, with trailing hyphens stripped.
20
+ *
21
+ * Whitelist (not blacklist) so unexpected characters from free-text sources
22
+ * — `meta.description`, label-derived suffixes, encoded JSON Pointers —
23
+ * cannot leak into ids and break CSS selectors or aria associations.
24
+ */
25
+ declare function normaliseIdSegment(value: string): string;
26
+ /**
27
+ * Build the canonical `sc-`-prefixed DOM id for a structural path.
28
+ * Use this as the base id for an input element; derived ids (panel, tab,
29
+ * hint) compose suffixes onto the returned string.
30
+ *
31
+ * Throws on an empty path: a previous "sc-field" fallback caused every
32
+ * input across a form to share the same id, breaking label-input pairing
33
+ * and screen reader navigation.
34
+ */
35
+ declare function fieldDomId(path: string): string;
36
+ /**
37
+ * Derive the constraint-hint element id for a given field id.
38
+ * The hint element is wired to inputs via `aria-describedby`.
39
+ */
40
+ declare function hintIdFor(fieldId: string): string;
41
+ /**
42
+ * Derive the tab panel id for a discriminated-union container at `path`.
43
+ * Used by every renderer that emits a WAI-ARIA tabs widget so that the
44
+ * `aria-controls` on each tab and the `id` on the matching panel match.
45
+ */
46
+ declare function panelIdFor(path: string): string;
47
+ /**
48
+ * Derive the id for tab `i` within a discriminated-union container at `path`.
49
+ * Used to pair `aria-labelledby` on the active panel with the active tab's
50
+ * `id` across all renderers.
51
+ */
52
+ declare function tabIdFor(path: string, index: number): string;
53
+ //#endregion
54
+ export { fieldDomId, hintIdFor, normaliseIdSegment, panelIdFor, tabIdFor };
@@ -0,0 +1,66 @@
1
+ import "./cssClasses.mjs";
2
+ //#region src/core/idPath.ts
3
+ /**
4
+ * Canonical DOM-id generation from structural paths.
5
+ *
6
+ * Every render pipeline (React headless, HTML sync, HTML stream) needs to
7
+ * derive stable DOM ids from the same path so `aria-controls`, `aria-labelledby`,
8
+ * and `htmlFor` references resolve consistently across pipelines.
9
+ *
10
+ * Previously each pipeline carried its own copy with subtly different
11
+ * normalisation (raw path in one place, dot/bracket-collapsed in another,
12
+ * whitelist-collapsed in a third). The streaming renderer's tab panel ids
13
+ * silently diverged from the sync renderer's because of that drift.
14
+ *
15
+ * Pipelines should import the helpers below rather than re-deriving them.
16
+ */
17
+ /**
18
+ * Normalise a structural path into the id segment used after the `sc-`
19
+ * prefix. Whitelist-based: any run of characters outside `[A-Za-z0-9_-]`
20
+ * collapses to a single hyphen, with trailing hyphens stripped.
21
+ *
22
+ * Whitelist (not blacklist) so unexpected characters from free-text sources
23
+ * — `meta.description`, label-derived suffixes, encoded JSON Pointers —
24
+ * cannot leak into ids and break CSS selectors or aria associations.
25
+ */
26
+ function normaliseIdSegment(value) {
27
+ return value.replace(/[^A-Za-z0-9_-]+/g, "-").replace(/-+$/g, "");
28
+ }
29
+ /**
30
+ * Build the canonical `sc-`-prefixed DOM id for a structural path.
31
+ * Use this as the base id for an input element; derived ids (panel, tab,
32
+ * hint) compose suffixes onto the returned string.
33
+ *
34
+ * Throws on an empty path: a previous "sc-field" fallback caused every
35
+ * input across a form to share the same id, breaking label-input pairing
36
+ * and screen reader navigation.
37
+ */
38
+ function fieldDomId(path) {
39
+ if (path.length === 0) throw new Error("fieldDomId requires a non-empty path. Thread a root path from the renderer entry point and derive children via joinPath().");
40
+ return `sc-${normaliseIdSegment(path)}`;
41
+ }
42
+ /**
43
+ * Derive the constraint-hint element id for a given field id.
44
+ * The hint element is wired to inputs via `aria-describedby`.
45
+ */
46
+ function hintIdFor(fieldId) {
47
+ return `${fieldId}-hint`;
48
+ }
49
+ /**
50
+ * Derive the tab panel id for a discriminated-union container at `path`.
51
+ * Used by every renderer that emits a WAI-ARIA tabs widget so that the
52
+ * `aria-controls` on each tab and the `id` on the matching panel match.
53
+ */
54
+ function panelIdFor(path) {
55
+ return `${fieldDomId(path)}-panel`;
56
+ }
57
+ /**
58
+ * Derive the id for tab `i` within a discriminated-union container at `path`.
59
+ * Used to pair `aria-labelledby` on the active panel with the active tab's
60
+ * `id` across all renderers.
61
+ */
62
+ function tabIdFor(path, index) {
63
+ return `${fieldDomId(path)}-tab-${String(index)}`;
64
+ }
65
+ //#endregion
66
+ export { fieldDomId, hintIdFor, normaliseIdSegment, panelIdFor, tabIdFor };
@@ -1,4 +1,4 @@
1
- import { i as DiagnosticsOptions } from "../diagnostics-D0QCYGv0.mjs";
1
+ import { i as DiagnosticsOptions } from "../diagnostics-BS2kaUyE.mjs";
2
2
 
3
3
  //#region src/core/merge.d.ts
4
4
  /**
@@ -32,6 +32,15 @@ declare function mergeRefSiblings(referencer: Record<string, unknown>, resolvedM
32
32
  * - \`true\` is the always-valid schema and contributes no constraints —
33
33
  * skip silently.
34
34
  *
35
+ * Type compatibility is enforced rather than papered over: two
36
+ * branches asserting incompatible primitive `type` keywords
37
+ * (e.g. `string` ∩ `number`) describe an unsatisfiable conjunction.
38
+ * `mergeAllOf` returns `false` and emits
39
+ * `schema-allof-incompatible` so the walker produces the same
40
+ * `NeverField` shape it gives a top-level `false` schema. Pretending
41
+ * the first type wins silently would silently weaken the constraint
42
+ * for any consumer that reads the merged result.
43
+ *
35
44
  * Non-boolean, non-object entries (e.g. arrays, numbers) are malformed
36
45
  * inputs that cannot represent a schema; skip them as before.
37
46
  */
@@ -116,6 +116,15 @@ function mergeRefSiblings(referencer, resolvedMeta) {
116
116
  * - \`true\` is the always-valid schema and contributes no constraints —
117
117
  * skip silently.
118
118
  *
119
+ * Type compatibility is enforced rather than papered over: two
120
+ * branches asserting incompatible primitive `type` keywords
121
+ * (e.g. `string` ∩ `number`) describe an unsatisfiable conjunction.
122
+ * `mergeAllOf` returns `false` and emits
123
+ * `schema-allof-incompatible` so the walker produces the same
124
+ * `NeverField` shape it gives a top-level `false` schema. Pretending
125
+ * the first type wins silently would silently weaken the constraint
126
+ * for any consumer that reads the merged result.
127
+ *
119
128
  * Non-boolean, non-object entries (e.g. arrays, numbers) are malformed
120
129
  * inputs that cannot represent a schema; skip them as before.
121
130
  */
@@ -150,16 +159,15 @@ function mergeAllOf(schemas, diagnostics, pointer = "") {
150
159
  const entryType = getString(entry, "type");
151
160
  if (entryType !== void 0) {
152
161
  if (!("type" in merged)) merged.type = entryType;
153
- else if (!deepEqual(merged.type, entryType)) emitDiagnostic(diagnostics, {
154
- code: "allof-conflict",
155
- message: `allOf branches define conflicting values for "type"; keeping the first occurrence and discarding subsequent values`,
156
- pointer,
157
- detail: {
158
- key: "type",
159
- kept: merged.type,
160
- discarded: entryType
161
- }
162
- });
162
+ else if (!areCompatibleTypes(merged.type, entryType)) {
163
+ emitDiagnostic(diagnostics, {
164
+ code: "schema-allof-incompatible",
165
+ message: `allOf branches assert incompatible \`type\` keywords (${describeType(merged.type)} ∩ ${describeType(entryType)}); the conjunction is unsatisfiable`,
166
+ pointer,
167
+ detail: { types: [merged.type, entryType] }
168
+ });
169
+ return false;
170
+ }
163
171
  }
164
172
  }
165
173
  if (Object.keys(properties).length > 0) merged.properties = properties;
@@ -167,6 +175,37 @@ function mergeAllOf(schemas, diagnostics, pointer = "") {
167
175
  return merged;
168
176
  }
169
177
  /**
178
+ * Two `type` keyword values are compatible when their intersection is
179
+ * non-empty. Identical strings always agree; `"integer"` is a subset
180
+ * of `"number"` per JSON Schema 2020-12 §6.1.1 so the conjunction
181
+ * collapses to `"integer"` and is treated as compatible here.
182
+ * Mismatched primitives (`"string"` ∩ `"number"`, `"boolean"` ∩
183
+ * `"object"`, etc.) describe an unsatisfiable intersection.
184
+ *
185
+ * `kept` carries the running merged value, which may already be an
186
+ * array form produced by an earlier merge — in that case we conservatively
187
+ * require deep equality to count as compatible, since narrowing the
188
+ * intersection of two arbitrary type-array sets is out of scope here.
189
+ */
190
+ function areCompatibleTypes(kept, incoming) {
191
+ if (typeof kept === "string") {
192
+ if (kept === incoming) return true;
193
+ if (kept === "integer" && incoming === "number" || kept === "number" && incoming === "integer") return true;
194
+ return false;
195
+ }
196
+ return deepEqual(kept, incoming);
197
+ }
198
+ /**
199
+ * Human-readable label for a `type` keyword value used in the
200
+ * `schema-allof-incompatible` diagnostic message. Strings render
201
+ * quoted; non-strings (array forms, malformed values) render via
202
+ * `JSON.stringify` so the message still carries useful context.
203
+ */
204
+ function describeType(value) {
205
+ if (typeof value === "string") return `"${value}"`;
206
+ return JSON.stringify(value);
207
+ }
208
+ /**
170
209
  * Detect `anyOf: [T, { type: "null" }]` → nullable T.
171
210
  * Returns the non-null schema and a nullable flag.
172
211
  */
@@ -1,5 +1,5 @@
1
- import { i as DiagnosticsOptions } from "../diagnostics-D0QCYGv0.mjs";
2
- import { i as OpenApiVersionInfo, r as JsonSchemaDraft } from "../version-D2jfdX6E.mjs";
1
+ import { i as DiagnosticsOptions } from "../diagnostics-BS2kaUyE.mjs";
2
+ import { i as OpenApiVersionInfo, r as JsonSchemaDraft } from "../version-BFTVLsdb.mjs";
3
3
 
4
4
  //#region src/core/normalise.d.ts
5
5
  type NodeTransform = (node: Record<string, unknown>) => Record<string, unknown>;
@@ -84,6 +84,17 @@ declare function normaliseDraft04Node(node: Record<string, unknown>): Record<str
84
84
  * transform without re-implementing the dispatch.
85
85
  */
86
86
  declare function selectDraftTransform(draft: JsonSchemaDraft): NodeTransformWithContext;
87
+ /**
88
+ * Scan a JSON document body for the presence of a named keyword
89
+ * anywhere in the structure. Walks both arrays and objects without
90
+ * regard to schema-vs-data position — the caller is responsible for
91
+ * passing a keyword whose presence is meaningful at any depth.
92
+ *
93
+ * Cycle-safe: cyclic references introduced by the OpenAPI bundler's
94
+ * `structuredClone` of external refs are short-circuited via the
95
+ * `visited` set so the scan terminates.
96
+ */
97
+ declare function documentContainsKeyword(value: unknown, keyword: string, visited?: WeakSet<object>): boolean;
87
98
  /**
88
99
  * Normalise a JSON Schema to canonical Draft 2020-12 form.
89
100
  * Deep-clones the input — the original is never mutated.
@@ -103,4 +114,4 @@ declare function normaliseJsonSchema(schema: Record<string, unknown>, draft: Jso
103
114
  */
104
115
  declare function normaliseOpenApiSchemas(doc: Record<string, unknown>, version: OpenApiVersionInfo, diagnostics?: DiagnosticsOptions): Record<string, unknown>;
105
116
  //#endregion
106
- export { NodeContext, NodeTransform, NodeTransformWithContext, deepNormalise, deepNormaliseWithContext, normaliseDraft04Node, normaliseJsonSchema, normaliseOpenApiSchemas, selectDraftTransform };
117
+ export { NodeContext, NodeTransform, NodeTransformWithContext, deepNormalise, deepNormaliseWithContext, documentContainsKeyword, normaliseDraft04Node, normaliseJsonSchema, normaliseOpenApiSchemas, selectDraftTransform };
@@ -1,2 +1,2 @@
1
- import { a as normaliseOpenApiSchemas, i as normaliseJsonSchema, n as deepNormaliseWithContext, o as selectDraftTransform, r as normaliseDraft04Node, t as deepNormalise } from "../normalise-DVEJQmF7.mjs";
2
- export { deepNormalise, deepNormaliseWithContext, normaliseDraft04Node, normaliseJsonSchema, normaliseOpenApiSchemas, selectDraftTransform };
1
+ import { a as normaliseJsonSchema, i as normaliseDraft04Node, n as deepNormaliseWithContext, o as normaliseOpenApiSchemas, r as documentContainsKeyword, s as selectDraftTransform, t as deepNormalise } from "../normalise-DCYp06Sr.mjs";
2
+ export { deepNormalise, deepNormaliseWithContext, documentContainsKeyword, normaliseDraft04Node, normaliseJsonSchema, normaliseOpenApiSchemas, selectDraftTransform };
@@ -1,6 +1,20 @@
1
1
  import { NodeTransform } from "./normalise.mjs";
2
2
 
3
3
  //#region src/core/openapi30.d.ts
4
+ /**
5
+ * Lift OpenAPI 3.x singular `example` onto the plural `examples` key.
6
+ *
7
+ * Two output shapes are spec-correct depending on the parent object type:
8
+ * - `"array"` — Schema Object: `examples: [example]` (Draft 2020-12 plural).
9
+ * - `"map"` — Parameter / Header / Media Type Object: an Examples Map
10
+ * keyed by name. The single value is wrapped under the
11
+ * synthetic key `default` to produce a valid one-entry map
12
+ * of one Example Object.
13
+ *
14
+ * When both `example` and `examples` coexist the spec declares them mutually
15
+ * exclusive — `example` is dropped and `examples` wins.
16
+ */
17
+ declare function liftExampleToExamples(node: Record<string, unknown>, shape: "array" | "map"): void;
4
18
  /**
5
19
  * Normalise OpenAPI 3.0.x `nullable` keyword to `anyOf [T, null]`.
6
20
  *
@@ -90,4 +104,4 @@ declare function deepNormaliseOpenApiDoc(doc: Record<string, unknown>, normalise
90
104
  */
91
105
  declare function deepNormaliseOpenApi30Doc(doc: Record<string, unknown>, deepNormalise: (schema: Record<string, unknown>, transform: NodeTransform) => Record<string, unknown>): Record<string, unknown>;
92
106
  //#endregion
93
- export { applyDiscriminatorAllOfPrepass, deepNormaliseOpenApi30Doc, deepNormaliseOpenApiDoc, normaliseOpenApi30Combined, normaliseOpenApi30Discriminator, normaliseOpenApi30Node };
107
+ export { applyDiscriminatorAllOfPrepass, deepNormaliseOpenApi30Doc, deepNormaliseOpenApiDoc, liftExampleToExamples, normaliseOpenApi30Combined, normaliseOpenApi30Discriminator, normaliseOpenApi30Node };
@@ -1,2 +1,2 @@
1
- import { c as applyDiscriminatorAllOfPrepass, d as normaliseOpenApi30Combined, f as normaliseOpenApi30Discriminator, l as deepNormaliseOpenApi30Doc, p as normaliseOpenApi30Node, u as deepNormaliseOpenApiDoc } from "../normalise-DVEJQmF7.mjs";
2
- export { applyDiscriminatorAllOfPrepass, deepNormaliseOpenApi30Doc, deepNormaliseOpenApiDoc, normaliseOpenApi30Combined, normaliseOpenApi30Discriminator, normaliseOpenApi30Node };
1
+ import { d as deepNormaliseOpenApiDoc, f as liftExampleToExamples, h as normaliseOpenApi30Node, l as applyDiscriminatorAllOfPrepass, m as normaliseOpenApi30Discriminator, p as normaliseOpenApi30Combined, u as deepNormaliseOpenApi30Doc } from "../normalise-DCYp06Sr.mjs";
2
+ export { applyDiscriminatorAllOfPrepass, deepNormaliseOpenApi30Doc, deepNormaliseOpenApiDoc, liftExampleToExamples, normaliseOpenApi30Combined, normaliseOpenApi30Discriminator, normaliseOpenApi30Node };
@@ -0,0 +1,67 @@
1
+ //#region src/core/openapiConstants.d.ts
2
+ /**
3
+ * Shared OpenAPI / Swagger constants and helpers.
4
+ *
5
+ * Single source of truth for HTTP method tuples, default content types, and
6
+ * the Swagger 2.0 → OpenAPI 3.x reference-prefix rewriting table. Consumers
7
+ * import the named exports rather than hand-maintaining sibling copies that
8
+ * silently drift when methods or prefixes change.
9
+ */
10
+ /**
11
+ * Canonical OpenAPI 3.x HTTP method tuple in path-item iteration order.
12
+ * Spec: https://spec.openapis.org/oas/v3.1.1#path-item-object
13
+ */
14
+ declare const HTTP_METHODS: readonly ["get", "put", "post", "delete", "options", "head", "patch", "trace"];
15
+ type HttpMethod = (typeof HTTP_METHODS)[number];
16
+ /**
17
+ * Swagger 2.0 omits `trace` — the keyword was introduced in OpenAPI 3.0.
18
+ * Derived from `HTTP_METHODS` so adding a method to the canonical tuple
19
+ * automatically propagates here (after which the filter may need updating).
20
+ */
21
+ declare const SWAGGER_2_METHODS: readonly Exclude<HttpMethod, "trace">[];
22
+ /**
23
+ * Default media type used when an OpenAPI document elides `consumes` /
24
+ * `produces` or a Schema Object stands alone (no parent Media Type).
25
+ */
26
+ declare const DEFAULT_OPENAPI_CONTENT_TYPE = "application/json";
27
+ /**
28
+ * Canonical `$ref` prefix table for the JSON Pointer locations used by
29
+ * OpenAPI 3.x and Swagger 2.0 documents.
30
+ */
31
+ declare const REF_PREFIXES: {
32
+ /** OpenAPI 3.x — most schemas live under `components.schemas`. */readonly components: {
33
+ readonly schemas: "#/components/schemas/";
34
+ readonly parameters: "#/components/parameters/";
35
+ readonly responses: "#/components/responses/";
36
+ readonly requestBodies: "#/components/requestBodies/";
37
+ readonly headers: "#/components/headers/";
38
+ readonly examples: "#/components/examples/";
39
+ readonly links: "#/components/links/";
40
+ readonly callbacks: "#/components/callbacks/";
41
+ readonly securitySchemes: "#/components/securitySchemes/";
42
+ readonly pathItems: "#/components/pathItems/";
43
+ }; /** Swagger 2.0 legacy prefixes. */
44
+ readonly swagger2: {
45
+ readonly definitions: "#/definitions/";
46
+ readonly parameters: "#/parameters/";
47
+ readonly responses: "#/responses/";
48
+ };
49
+ };
50
+ /**
51
+ * Swagger 2.0 → OpenAPI 3.x `$ref` prefix mapping. Applied during the
52
+ * 2.0 → 3.x lift to rewrite legacy pointer prefixes onto their components
53
+ * counterparts. Order matters: longer prefixes first prevents `#/parameters/`
54
+ * from shadowing `#/components/parameters/` during a partial migration.
55
+ */
56
+ declare const REF_REWRITES: readonly {
57
+ readonly from: string;
58
+ readonly to: string;
59
+ }[];
60
+ /**
61
+ * Rewrite a Swagger 2.0 ref prefix onto the equivalent OpenAPI 3.x location.
62
+ * Returns the ref unchanged when no prefix matches. Pure string operation —
63
+ * does not validate that the target exists.
64
+ */
65
+ declare function rewriteSwaggerRefPrefix(ref: string): string;
66
+ //#endregion
67
+ export { DEFAULT_OPENAPI_CONTENT_TYPE, HTTP_METHODS, HttpMethod, REF_PREFIXES, REF_REWRITES, SWAGGER_2_METHODS, rewriteSwaggerRefPrefix };
@@ -0,0 +1,90 @@
1
+ //#region src/core/openapiConstants.ts
2
+ /**
3
+ * Shared OpenAPI / Swagger constants and helpers.
4
+ *
5
+ * Single source of truth for HTTP method tuples, default content types, and
6
+ * the Swagger 2.0 → OpenAPI 3.x reference-prefix rewriting table. Consumers
7
+ * import the named exports rather than hand-maintaining sibling copies that
8
+ * silently drift when methods or prefixes change.
9
+ */
10
+ /**
11
+ * Canonical OpenAPI 3.x HTTP method tuple in path-item iteration order.
12
+ * Spec: https://spec.openapis.org/oas/v3.1.1#path-item-object
13
+ */
14
+ const HTTP_METHODS = [
15
+ "get",
16
+ "put",
17
+ "post",
18
+ "delete",
19
+ "options",
20
+ "head",
21
+ "patch",
22
+ "trace"
23
+ ];
24
+ /**
25
+ * Swagger 2.0 omits `trace` — the keyword was introduced in OpenAPI 3.0.
26
+ * Derived from `HTTP_METHODS` so adding a method to the canonical tuple
27
+ * automatically propagates here (after which the filter may need updating).
28
+ */
29
+ const SWAGGER_2_METHODS = HTTP_METHODS.filter((m) => m !== "trace");
30
+ /**
31
+ * Default media type used when an OpenAPI document elides `consumes` /
32
+ * `produces` or a Schema Object stands alone (no parent Media Type).
33
+ */
34
+ const DEFAULT_OPENAPI_CONTENT_TYPE = "application/json";
35
+ /**
36
+ * Canonical `$ref` prefix table for the JSON Pointer locations used by
37
+ * OpenAPI 3.x and Swagger 2.0 documents.
38
+ */
39
+ const REF_PREFIXES = {
40
+ /** OpenAPI 3.x — most schemas live under `components.schemas`. */
41
+ components: {
42
+ schemas: "#/components/schemas/",
43
+ parameters: "#/components/parameters/",
44
+ responses: "#/components/responses/",
45
+ requestBodies: "#/components/requestBodies/",
46
+ headers: "#/components/headers/",
47
+ examples: "#/components/examples/",
48
+ links: "#/components/links/",
49
+ callbacks: "#/components/callbacks/",
50
+ securitySchemes: "#/components/securitySchemes/",
51
+ pathItems: "#/components/pathItems/"
52
+ },
53
+ /** Swagger 2.0 legacy prefixes. */
54
+ swagger2: {
55
+ definitions: "#/definitions/",
56
+ parameters: "#/parameters/",
57
+ responses: "#/responses/"
58
+ }
59
+ };
60
+ /**
61
+ * Swagger 2.0 → OpenAPI 3.x `$ref` prefix mapping. Applied during the
62
+ * 2.0 → 3.x lift to rewrite legacy pointer prefixes onto their components
63
+ * counterparts. Order matters: longer prefixes first prevents `#/parameters/`
64
+ * from shadowing `#/components/parameters/` during a partial migration.
65
+ */
66
+ const REF_REWRITES = [
67
+ {
68
+ from: REF_PREFIXES.swagger2.definitions,
69
+ to: REF_PREFIXES.components.schemas
70
+ },
71
+ {
72
+ from: REF_PREFIXES.swagger2.parameters,
73
+ to: REF_PREFIXES.components.parameters
74
+ },
75
+ {
76
+ from: REF_PREFIXES.swagger2.responses,
77
+ to: REF_PREFIXES.components.responses
78
+ }
79
+ ];
80
+ /**
81
+ * Rewrite a Swagger 2.0 ref prefix onto the equivalent OpenAPI 3.x location.
82
+ * Returns the ref unchanged when no prefix matches. Pure string operation —
83
+ * does not validate that the target exists.
84
+ */
85
+ function rewriteSwaggerRefPrefix(ref) {
86
+ for (const { from, to } of REF_REWRITES) if (ref.startsWith(from)) return `${to}${ref.slice(from.length)}`;
87
+ return ref;
88
+ }
89
+ //#endregion
90
+ export { DEFAULT_OPENAPI_CONTENT_TYPE, HTTP_METHODS, REF_PREFIXES, REF_REWRITES, SWAGGER_2_METHODS, rewriteSwaggerRefPrefix };