docusaurus-theme-openapi-docs 0.0.0-1314 → 0.0.0-1332

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.
@@ -77,171 +77,14 @@ const DiscriminatorTabs_1 = __importDefault(
77
77
  require("@theme/DiscriminatorTabs")
78
78
  );
79
79
  const Markdown_1 = __importDefault(require("@theme/Markdown"));
80
+ const normalize_1 = require("@theme/Schema/normalize");
80
81
  const SchemaExpansion_1 = require("@theme/SchemaExpansion");
81
82
  const SchemaItem_1 = __importDefault(require("@theme/SchemaItem"));
82
83
  const SchemaTabs_1 = __importDefault(require("@theme/SchemaTabs"));
83
84
  const TabItem_1 = __importDefault(require("@theme/TabItem"));
84
- // eslint-disable-next-line import/no-extraneous-dependencies
85
- const allof_merge_1 = require("allof-merge");
86
85
  const clsx_1 = __importDefault(require("clsx"));
87
86
  const isEmpty_1 = __importDefault(require("lodash/isEmpty"));
88
87
  const schema_1 = require("../../markdown/schema");
89
- // eslint-disable-next-line import/no-extraneous-dependencies
90
- // const jsonSchemaMergeAllOf = require("json-schema-merge-allof");
91
- /**
92
- * Strip `additionalProperties: false` from sibling allOf members so the
93
- * strict-AND semantics of `allof-merge` don't collapse the result to an
94
- * unsatisfiable empty schema.
95
- *
96
- * Per JSON Schema, two allOf members that each set `additionalProperties: false`
97
- * with disjoint `properties` sets define an unsatisfiable schema (no value can
98
- * satisfy both — each member rejects the other's properties). `allof-merge` is
99
- * technically correct to drop all properties in that case, but it leaves the
100
- * rendered schema blank.
101
- *
102
- * NSwag and Swashbuckle emit this pattern by default whenever a model uses
103
- * inheritance/composition, so it's the dominant style for .NET-generated specs.
104
- * Redoc, Swagger UI, and Stoplight all union the properties and ignore the
105
- * conflicting flag — the approach this helper emulates by stripping the flag
106
- * before delegating to `allof-merge`. The flag is render-irrelevant anyway:
107
- * `additionalProperties: false` is treated identically to `undefined` by the
108
- * AdditionalProperties component below (line ~641).
109
- *
110
- * Strips from every allOf member whenever the parent has ≥2 members. The
111
- * collapse triggers symmetrically (both siblings strict) or asymmetrically
112
- * (one strict member rejects another's properties as "additional"), so the
113
- * presence of multiple members is the right gate. Single-member allOf is left
114
- * alone — it can't conflict with anything.
115
- *
116
- * See https://github.com/PaloAltoNetworks/docusaurus-openapi-docs/issues/1119
117
- * Mirrored in plugin: docusaurus-plugin-openapi-docs/src/markdown/createSchema.ts
118
- */
119
- const stripConflictingAdditionalProps = (node) => {
120
- if (Array.isArray(node)) return node.map(stripConflictingAdditionalProps);
121
- if (!node || typeof node !== "object") return node;
122
- let working = node;
123
- if (Array.isArray(node.allOf) && node.allOf.length > 1) {
124
- const hasStrictMember = node.allOf.some(
125
- (m) => m && m.additionalProperties === false
126
- );
127
- if (hasStrictMember) {
128
- working = {
129
- ...node,
130
- allOf: node.allOf.map((m) => {
131
- if (m && m.additionalProperties === false) {
132
- const { additionalProperties: _drop, ...rest } = m;
133
- return rest;
134
- }
135
- return m;
136
- }),
137
- };
138
- }
139
- }
140
- const result = {};
141
- for (const [k, v] of Object.entries(working)) {
142
- result[k] = stripConflictingAdditionalProps(v);
143
- }
144
- return result;
145
- };
146
- const mergeAllOf = (allOf) => {
147
- const onMergeError = (msg) => {
148
- console.warn(msg);
149
- };
150
- const mergedSchemas = (0, allof_merge_1.merge)(
151
- stripConflictingAdditionalProps(allOf),
152
- {
153
- onMergeError,
154
- }
155
- );
156
- return mergedSchemas ?? {};
157
- };
158
- /**
159
- * Fold sibling fields into each `oneOf`/`anyOf` branch via allOf-merge, so each
160
- * branch is self-contained. Mirrors Redoc's `SchemaModel.initOneOf` behavior.
161
- * Without this, when an `allOf` override redefines a nested property with
162
- * `oneOf`, the merged schema ends up with both `properties` and `oneOf` as
163
- * siblings — and the renderer prints the shared properties twice.
164
- *
165
- * See https://github.com/PaloAltoNetworks/docusaurus-openapi-docs/issues/1218
166
- */
167
- const foldSiblingsIntoBranches = (schema) => {
168
- const branchKey = schema?.oneOf
169
- ? "oneOf"
170
- : schema?.anyOf
171
- ? "anyOf"
172
- : undefined;
173
- if (!branchKey) return schema;
174
- const branches = schema[branchKey];
175
- if (!Array.isArray(branches) || branches.length === 0) return schema;
176
- const siblings = { ...schema };
177
- delete siblings[branchKey];
178
- if (Object.keys(siblings).length === 0) return schema;
179
- const folded = branches.map((branch) =>
180
- mergeAllOf({ allOf: [siblings, branch] })
181
- );
182
- return { [branchKey]: folded };
183
- };
184
- /**
185
- * Recursively searches for a property in a schema, including nested
186
- * oneOf, anyOf, and allOf structures. This is needed for discriminators
187
- * where the property definition may be in a nested schema.
188
- */
189
- const findProperty = (schema, propertyName) => {
190
- // Check direct properties first
191
- if (schema.properties?.[propertyName]) {
192
- return schema.properties[propertyName];
193
- }
194
- // Search in oneOf schemas
195
- if (schema.oneOf) {
196
- for (const subschema of schema.oneOf) {
197
- const found = findProperty(subschema, propertyName);
198
- if (found) return found;
199
- }
200
- }
201
- // Search in anyOf schemas
202
- if (schema.anyOf) {
203
- for (const subschema of schema.anyOf) {
204
- const found = findProperty(subschema, propertyName);
205
- if (found) return found;
206
- }
207
- }
208
- // Search in allOf schemas
209
- if (schema.allOf) {
210
- for (const subschema of schema.allOf) {
211
- const found = findProperty(subschema, propertyName);
212
- if (found) return found;
213
- }
214
- }
215
- return undefined;
216
- };
217
- /**
218
- * Recursively searches for a discriminator in a schema, including nested
219
- * oneOf, anyOf, and allOf structures.
220
- */
221
- const findDiscriminator = (schema) => {
222
- if (schema.discriminator) {
223
- return schema.discriminator;
224
- }
225
- if (schema.oneOf) {
226
- for (const subschema of schema.oneOf) {
227
- const found = findDiscriminator(subschema);
228
- if (found) return found;
229
- }
230
- }
231
- if (schema.anyOf) {
232
- for (const subschema of schema.anyOf) {
233
- const found = findDiscriminator(subschema);
234
- if (found) return found;
235
- }
236
- }
237
- if (schema.allOf) {
238
- for (const subschema of schema.allOf) {
239
- const found = findDiscriminator(subschema);
240
- if (found) return found;
241
- }
242
- }
243
- return undefined;
244
- };
245
88
  // Renders string as markdown, useful for descriptions and qualifiers
246
89
  const MarkdownWrapper = ({ text }) => {
247
90
  return react_1.default.createElement(
@@ -392,6 +235,14 @@ const AnyOneOf = ({ schema, schemaType, schemaPath }) => {
392
235
  react_1.default.createElement(
393
236
  TabItem_1.default,
394
237
  { key: index, label: label, value: `${uniqueId}-${index}-item` },
238
+ anyOneSchema.description &&
239
+ react_1.default.createElement(
240
+ "div",
241
+ { style: { marginLeft: "1rem" } },
242
+ react_1.default.createElement(MarkdownWrapper, {
243
+ text: anyOneSchema.description,
244
+ })
245
+ ),
395
246
  (isPrimitive(anyOneSchema) || anyOneSchema.const) &&
396
247
  react_1.default.createElement(SchemaItem_1.default, {
397
248
  collapsible: false,
@@ -570,6 +421,14 @@ const PropertyDiscriminator = ({
570
421
  react_1.default.createElement(
571
422
  TabItem_1.default,
572
423
  { key: index, label: key, value: `${index}-item-discriminator` },
424
+ discriminator.mapping[key]?.description &&
425
+ react_1.default.createElement(
426
+ "div",
427
+ { style: { marginLeft: "1rem" } },
428
+ react_1.default.createElement(MarkdownWrapper, {
429
+ text: discriminator.mapping[key].description,
430
+ })
431
+ ),
573
432
  react_1.default.createElement(SchemaNode, {
574
433
  schema: discriminator.mapping[key],
575
434
  schemaType: schemaType,
@@ -599,11 +458,13 @@ const PropertyDiscriminator = ({
599
458
  const DiscriminatorNode = ({ discriminator, schema, schemaType }) => {
600
459
  let discriminatedSchemas = {};
601
460
  let inferredMapping = {};
602
- // Search for the discriminator property in the schema, including nested structures
461
+ // Search for the discriminator property in the schema, including nested
462
+ // structures. Cached recursive lookup replaces the O(subtree) findProperty
463
+ // walk that caused #1525's O(N^2) render cost.
603
464
  const discriminatorProperty =
604
- findProperty(schema, discriminator.propertyName) ?? {};
465
+ (0, normalize_1.findPropertyDeep)(schema, discriminator.propertyName) ?? {};
605
466
  if (schema.allOf) {
606
- const mergedSchemas = mergeAllOf(schema);
467
+ const mergedSchemas = (0, normalize_1.mergeAllOf)(schema);
607
468
  if (mergedSchemas.oneOf || mergedSchemas.anyOf) {
608
469
  discriminatedSchemas = mergedSchemas.oneOf || mergedSchemas.anyOf;
609
470
  }
@@ -623,7 +484,7 @@ const DiscriminatorNode = ({ discriminator, schema, schemaType }) => {
623
484
  // Handle discriminated schema with allOf
624
485
  let mergedSubSchema = {};
625
486
  if (subSchema.allOf) {
626
- mergedSubSchema = mergeAllOf(subSchema);
487
+ mergedSubSchema = (0, normalize_1.mergeAllOf)(subSchema);
627
488
  }
628
489
  const subProperties = subSchema.properties || mergedSubSchema.properties;
629
490
  // Add a safeguard check to avoid referencing subProperties if it's undefined
@@ -664,6 +525,17 @@ const DiscriminatorNode = ({ discriminator, schema, schemaType }) => {
664
525
  const AdditionalProperties = ({ schema, schemaType }) => {
665
526
  const additionalProperties = schema.additionalProperties;
666
527
  if (!additionalProperties) return null;
528
+ if ((0, normalize_1.isCircularMarker)(additionalProperties)) {
529
+ return react_1.default.createElement(SchemaItem_1.default, {
530
+ name: "property name*",
531
+ required: false,
532
+ schemaName: additionalProperties,
533
+ schema: additionalProperties,
534
+ collapsible: false,
535
+ discriminator: false,
536
+ children: null,
537
+ });
538
+ }
667
539
  // Handle free-form objects
668
540
  if (
669
541
  additionalProperties === true ||
@@ -774,10 +646,26 @@ const SchemaNodeDetails = ({
774
646
  );
775
647
  };
776
648
  const Items = ({ schema, schemaType, schemaPath }) => {
649
+ if ((0, normalize_1.isCircularMarker)(schema.items)) {
650
+ return react_1.default.createElement(
651
+ "div",
652
+ { style: { marginLeft: ".5rem" } },
653
+ react_1.default.createElement(ArrayBrackets_1.OpeningArrayBracket, null),
654
+ react_1.default.createElement(SchemaItem_1.default, {
655
+ collapsible: false,
656
+ name: "",
657
+ schemaName: schema.items,
658
+ schema: schema.items,
659
+ discriminator: false,
660
+ children: null,
661
+ }),
662
+ react_1.default.createElement(ArrayBrackets_1.ClosingArrayBracket, null)
663
+ );
664
+ }
777
665
  // Process schema.items to handle allOf merging
778
666
  let itemsSchema = schema.items;
779
667
  if (schema.items?.allOf) {
780
- itemsSchema = mergeAllOf(schema.items);
668
+ itemsSchema = (0, normalize_1.mergeAllOf)(schema.items);
781
669
  }
782
670
  // Handle complex schemas with multiple schema types
783
671
  const hasOneOfAnyOf = itemsSchema?.oneOf || itemsSchema?.anyOf;
@@ -790,7 +678,7 @@ const Items = ({ schema, schemaType, schemaPath }) => {
790
678
  // property rendering. See issue #1218.
791
679
  const renderSchema =
792
680
  hasOneOfAnyOf && hasProperties
793
- ? foldSiblingsIntoBranches(itemsSchema)
681
+ ? (0, normalize_1.foldSiblingsIntoBranches)(itemsSchema)
794
682
  : itemsSchema;
795
683
  const renderHasProperties =
796
684
  hasOneOfAnyOf && hasProperties ? false : hasProperties;
@@ -874,6 +762,17 @@ const SchemaEdge = ({
874
762
  ) {
875
763
  return null;
876
764
  }
765
+ if ((0, normalize_1.isCircularMarker)(schema)) {
766
+ return react_1.default.createElement(SchemaItem_1.default, {
767
+ collapsible: false,
768
+ name: name,
769
+ required: Array.isArray(required) ? required.includes(name) : required,
770
+ schemaName: schema,
771
+ schema: schema,
772
+ discriminator: false,
773
+ children: null,
774
+ });
775
+ }
877
776
  const schemaName = (0, schema_1.getSchemaName)(schema);
878
777
  if (discriminator && discriminator.propertyName === name) {
879
778
  return react_1.default.createElement(PropertyDiscriminator, {
@@ -941,6 +840,17 @@ const SchemaEdge = ({
941
840
  schemaPath: schemaPath,
942
841
  });
943
842
  }
843
+ if ((0, normalize_1.isCircularMarker)(schema.items)) {
844
+ return react_1.default.createElement(SchemaNodeDetails, {
845
+ name: name,
846
+ schemaName: schemaName,
847
+ required: required,
848
+ nullable: schema.nullable,
849
+ schema: schema,
850
+ schemaType: schemaType,
851
+ schemaPath: schemaPath,
852
+ });
853
+ }
944
854
  if (schema.allOf) {
945
855
  // handle circular properties
946
856
  if (
@@ -959,7 +869,7 @@ const SchemaEdge = ({
959
869
  children: null,
960
870
  });
961
871
  }
962
- const mergedSchemas = mergeAllOf(schema);
872
+ const mergedSchemas = (0, normalize_1.mergeAllOf)(schema);
963
873
  if (
964
874
  (schemaType === "request" && mergedSchemas.readOnly) ||
965
875
  (schemaType === "response" && mergedSchemas.writeOnly)
@@ -1026,7 +936,9 @@ function renderChildren(schema, schemaType, schemaPath) {
1026
936
  const hasOneOfAnyOf = !!(schema.oneOf || schema.anyOf);
1027
937
  const hasProperties = !!schema.properties;
1028
938
  const folded =
1029
- hasOneOfAnyOf && hasProperties ? foldSiblingsIntoBranches(schema) : schema;
939
+ hasOneOfAnyOf && hasProperties
940
+ ? (0, normalize_1.foldSiblingsIntoBranches)(schema)
941
+ : schema;
1030
942
  const renderProperties =
1031
943
  hasOneOfAnyOf && hasProperties ? false : hasProperties;
1032
944
  return react_1.default.createElement(
@@ -1063,7 +975,11 @@ function renderChildren(schema, schemaType, schemaPath) {
1063
975
  })
1064
976
  );
1065
977
  }
1066
- const SchemaNode = ({ schema, schemaType, schemaPath }) => {
978
+ const SchemaNode = ({ schema: rawSchema, schemaType, schemaPath }) => {
979
+ const schema = (0, react_1.useMemo)(
980
+ () => (0, normalize_1.normalizeSchema)(rawSchema),
981
+ [rawSchema]
982
+ );
1067
983
  if (
1068
984
  (schemaType === "request" && schema.readOnly) ||
1069
985
  (schemaType === "response" && schema.writeOnly)
@@ -1071,12 +987,13 @@ const SchemaNode = ({ schema, schemaType, schemaPath }) => {
1071
987
  return null;
1072
988
  }
1073
989
  // Resolve discriminator recursively so nested oneOf/anyOf/allOf compositions
1074
- // can still render discriminator tabs.
990
+ // can still render discriminator tabs. Cached via getDiscriminator so per-
991
+ // render cost is O(1) amortized (see #1525).
1075
992
  let workingSchema = schema;
1076
993
  const resolvedDiscriminator =
1077
- schema.discriminator ?? findDiscriminator(schema);
994
+ schema.discriminator ?? (0, normalize_1.getDiscriminator)(schema);
1078
995
  if (schema.allOf && !schema.discriminator && resolvedDiscriminator) {
1079
- workingSchema = mergeAllOf(schema);
996
+ workingSchema = (0, normalize_1.mergeAllOf)(schema);
1080
997
  }
1081
998
  if (!workingSchema.discriminator && resolvedDiscriminator) {
1082
999
  workingSchema.discriminator = resolvedDiscriminator;
@@ -1096,7 +1013,7 @@ const SchemaNode = ({ schema, schemaType, schemaPath }) => {
1096
1013
  const hasMultipleChoices = oneOfItems.length > 1;
1097
1014
  if (hasMultipleChoices) {
1098
1015
  // Render each oneOf/anyOf constraint first, then shared properties
1099
- const mergedSchemas = mergeAllOf(schema);
1016
+ const mergedSchemas = (0, normalize_1.mergeAllOf)(schema);
1100
1017
  if (
1101
1018
  (schemaType === "request" && mergedSchemas.readOnly) ||
1102
1019
  (schemaType === "response" && mergedSchemas.writeOnly)
@@ -1138,7 +1055,7 @@ const SchemaNode = ({ schema, schemaType, schemaPath }) => {
1138
1055
  );
1139
1056
  }
1140
1057
  // For other allOf cases, use standard merge behavior
1141
- const mergedSchemas = mergeAllOf(schema);
1058
+ const mergedSchemas = (0, normalize_1.mergeAllOf)(schema);
1142
1059
  if (
1143
1060
  (schemaType === "request" && mergedSchemas.readOnly) ||
1144
1061
  (schemaType === "response" && mergedSchemas.writeOnly)
@@ -1151,7 +1068,7 @@ const SchemaNode = ({ schema, schemaType, schemaPath }) => {
1151
1068
  const hasProperties = !!mergedSchemas.properties;
1152
1069
  const folded =
1153
1070
  hasOneOfAnyOf && hasProperties
1154
- ? foldSiblingsIntoBranches(mergedSchemas)
1071
+ ? (0, normalize_1.foldSiblingsIntoBranches)(mergedSchemas)
1155
1072
  : mergedSchemas;
1156
1073
  const renderProperties =
1157
1074
  hasOneOfAnyOf && hasProperties ? false : hasProperties;
@@ -0,0 +1,41 @@
1
+ export declare function isCircularMarker(value: unknown): value is string;
2
+ export declare function mergeAllOf(schema: any): any;
3
+ /**
4
+ * Fold sibling fields into each oneOf/anyOf branch via allOf-merge so each
5
+ * branch is self-contained. See #1218.
6
+ *
7
+ * Skipped when a discriminator is present: DiscriminatorNode already renders
8
+ * sibling properties at the top level, and folding would duplicate the
9
+ * discriminator metadata into each branch (causing nested DiscriminatorNode
10
+ * renders inside tabs — see #1525 follow-up).
11
+ *
12
+ * Called by normalizeSchema after allOf resolution. Uses mergeAllOf internally
13
+ * to compose `{ allOf: [siblings, branch] }` per branch — the WeakMap caches
14
+ * in stripConflictingAdditionalProps prevent redundant work on shared subtrees.
15
+ */
16
+ export declare function foldSiblingsIntoBranches(schema: any): any;
17
+ export declare function getDiscriminator(schema: any): any | undefined;
18
+ /**
19
+ * Cached recursive property lookup. Searches schema.properties first, then
20
+ * into oneOf/anyOf/allOf branches. Returns O(1) on repeated calls with the
21
+ * same (schema, propertyName) pair — replaces the O(subtree) findProperty
22
+ * walk that caused #1525's O(N^2) render cost.
23
+ */
24
+ export declare function findPropertyDeep(schema: any, propertyName: string): any | undefined;
25
+ /**
26
+ * Identity-stable schema pass-through. Returns the input as-is so SchemaNode
27
+ * useMemo dependencies stay stable across renders.
28
+ *
29
+ * Earlier iterations of this function eagerly merged allOf and folded
30
+ * siblings into oneOf/anyOf branches, but those operations need to stay
31
+ * conditional inside SchemaNode and DiscriminatorNode — eager versions
32
+ * caused cartesian product expansion (allOf with multiple oneOfs) and
33
+ * double-folding regressions. The render-time helpers below
34
+ * (mergeAllOf, foldSiblingsIntoBranches) are still cached via the WeakMap
35
+ * inside stripConflictingAdditionalProps, so per-render cost stays bounded.
36
+ *
37
+ * The perf win for #1525 comes from getDiscriminator's WeakMap cache
38
+ * replacing the O(subtree) findDiscriminator walk, not from eager
39
+ * normalization here.
40
+ */
41
+ export declare function normalizeSchema(schema: any): any;
@@ -0,0 +1,210 @@
1
+ "use strict";
2
+ /* ============================================================================
3
+ * Copyright (c) Palo Alto Networks
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ * ========================================================================== */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.isCircularMarker = isCircularMarker;
10
+ exports.mergeAllOf = mergeAllOf;
11
+ exports.foldSiblingsIntoBranches = foldSiblingsIntoBranches;
12
+ exports.getDiscriminator = getDiscriminator;
13
+ exports.findPropertyDeep = findPropertyDeep;
14
+ exports.normalizeSchema = normalizeSchema;
15
+ // eslint-disable-next-line import/no-extraneous-dependencies
16
+ const allof_merge_1 = require("allof-merge");
17
+ function isCircularMarker(value) {
18
+ return typeof value === "string" && value.startsWith("circular(");
19
+ }
20
+ /**
21
+ * Strip `additionalProperties: false` from sibling allOf members so
22
+ * allof-merge doesn't collapse to an unsatisfiable empty schema. See #1119.
23
+ *
24
+ * NOT cached: DiscriminatorNode mutates raw subschemas (deletes the
25
+ * discriminator property from each branch to avoid duplicate rendering).
26
+ * A WeakMap cache would return the pre-mutation deep clone on subsequent
27
+ * mergeAllOf calls, causing deleted properties to reappear inside tab
28
+ * content. Matches prod's per-call behavior.
29
+ */
30
+ function stripConflictingAdditionalProps(node) {
31
+ if (Array.isArray(node)) return node.map(stripConflictingAdditionalProps);
32
+ if (!node || typeof node !== "object") return node;
33
+ let working = node;
34
+ if (Array.isArray(node.allOf) && node.allOf.length > 1) {
35
+ const hasStrictMember = node.allOf.some(
36
+ (m) => m && m.additionalProperties === false
37
+ );
38
+ if (hasStrictMember) {
39
+ working = {
40
+ ...node,
41
+ allOf: node.allOf.map((m) => {
42
+ if (m && m.additionalProperties === false) {
43
+ const { additionalProperties: _drop, ...rest } = m;
44
+ return rest;
45
+ }
46
+ return m;
47
+ }),
48
+ };
49
+ }
50
+ }
51
+ const result = {};
52
+ for (const [k, v] of Object.entries(working)) {
53
+ result[k] = stripConflictingAdditionalProps(v);
54
+ }
55
+ return result;
56
+ }
57
+ function mergeAllOf(schema) {
58
+ const onMergeError = (msg) => console.warn(msg);
59
+ const merged = (0, allof_merge_1.merge)(
60
+ stripConflictingAdditionalProps(schema),
61
+ {
62
+ onMergeError,
63
+ }
64
+ );
65
+ return merged ?? {};
66
+ }
67
+ // Keys that are parent-level metadata and should NOT be folded into branches.
68
+ const METADATA_KEYS = new Set([
69
+ "title",
70
+ "description",
71
+ "discriminator",
72
+ "deprecated",
73
+ "externalDocs",
74
+ "example",
75
+ "examples",
76
+ "xml",
77
+ "nullable",
78
+ "readOnly",
79
+ "writeOnly",
80
+ "default",
81
+ ]);
82
+ /**
83
+ * Fold sibling fields into each oneOf/anyOf branch via allOf-merge so each
84
+ * branch is self-contained. See #1218.
85
+ *
86
+ * Skipped when a discriminator is present: DiscriminatorNode already renders
87
+ * sibling properties at the top level, and folding would duplicate the
88
+ * discriminator metadata into each branch (causing nested DiscriminatorNode
89
+ * renders inside tabs — see #1525 follow-up).
90
+ *
91
+ * Called by normalizeSchema after allOf resolution. Uses mergeAllOf internally
92
+ * to compose `{ allOf: [siblings, branch] }` per branch — the WeakMap caches
93
+ * in stripConflictingAdditionalProps prevent redundant work on shared subtrees.
94
+ */
95
+ function foldSiblingsIntoBranches(schema) {
96
+ const branchKey = schema?.oneOf
97
+ ? "oneOf"
98
+ : schema?.anyOf
99
+ ? "anyOf"
100
+ : undefined;
101
+ if (!branchKey) return schema;
102
+ const branches = schema[branchKey];
103
+ if (!Array.isArray(branches) || branches.length === 0) return schema;
104
+ // Discriminator schemas rely on top-level properties being intact so
105
+ // DiscriminatorNode can locate the discriminator property and render
106
+ // shared siblings at the top level. Leave them alone.
107
+ if (schema.discriminator) return schema;
108
+ // Build siblings from non-metadata keys only. Metadata (title, description,
109
+ // examples, etc.) belongs at the top level and must not be folded.
110
+ const siblings = {};
111
+ for (const [key, value] of Object.entries(schema)) {
112
+ if (key !== branchKey && !METADATA_KEYS.has(key) && !key.startsWith("x-")) {
113
+ siblings[key] = value;
114
+ }
115
+ }
116
+ if (Object.keys(siblings).length === 0) return schema;
117
+ const folded = branches.map((branch) =>
118
+ mergeAllOf({ allOf: [siblings, branch] })
119
+ );
120
+ const result = { [branchKey]: folded };
121
+ for (const key of Object.keys(schema)) {
122
+ if (key !== branchKey && (METADATA_KEYS.has(key) || key.startsWith("x-"))) {
123
+ result[key] = schema[key];
124
+ }
125
+ }
126
+ return result;
127
+ }
128
+ // WeakMap caches keyed by object identity — shared-reference subtrees from
129
+ // $RefParser.dereference are looked up once, and cycles short-circuit.
130
+ const discriminatorCache = new WeakMap();
131
+ const findPropertyCache = new WeakMap();
132
+ /**
133
+ * Cached recursive discriminator lookup. Returns O(1) on repeated calls
134
+ * with the same object reference (common after normalizeSchema).
135
+ */
136
+ const BRANCH_KEYS = ["oneOf", "anyOf", "allOf"];
137
+ function getDiscriminator(schema) {
138
+ if (!schema || typeof schema !== "object") return undefined;
139
+ if (discriminatorCache.has(schema)) {
140
+ const cached = discriminatorCache.get(schema);
141
+ return cached === null ? undefined : cached;
142
+ }
143
+ discriminatorCache.set(schema, null);
144
+ let result = schema.discriminator;
145
+ if (!result) {
146
+ for (const key of BRANCH_KEYS) {
147
+ const branches = schema[key];
148
+ if (!Array.isArray(branches)) continue;
149
+ for (const branch of branches) {
150
+ result = getDiscriminator(branch);
151
+ if (result) break;
152
+ }
153
+ if (result) break;
154
+ }
155
+ }
156
+ discriminatorCache.set(schema, result ?? null);
157
+ return result;
158
+ }
159
+ /**
160
+ * Cached recursive property lookup. Searches schema.properties first, then
161
+ * into oneOf/anyOf/allOf branches. Returns O(1) on repeated calls with the
162
+ * same (schema, propertyName) pair — replaces the O(subtree) findProperty
163
+ * walk that caused #1525's O(N^2) render cost.
164
+ */
165
+ function findPropertyDeep(schema, propertyName) {
166
+ if (!schema || typeof schema !== "object") return undefined;
167
+ let cache = findPropertyCache.get(schema);
168
+ if (cache && cache.has(propertyName)) {
169
+ const cached = cache.get(propertyName);
170
+ return cached === null ? undefined : cached;
171
+ }
172
+ if (!cache) {
173
+ cache = new Map();
174
+ findPropertyCache.set(schema, cache);
175
+ }
176
+ cache.set(propertyName, null);
177
+ let result = schema.properties?.[propertyName];
178
+ if (!result) {
179
+ for (const key of BRANCH_KEYS) {
180
+ const branches = schema[key];
181
+ if (!Array.isArray(branches)) continue;
182
+ for (const branch of branches) {
183
+ result = findPropertyDeep(branch, propertyName);
184
+ if (result) break;
185
+ }
186
+ if (result) break;
187
+ }
188
+ }
189
+ cache.set(propertyName, result ?? null);
190
+ return result;
191
+ }
192
+ /**
193
+ * Identity-stable schema pass-through. Returns the input as-is so SchemaNode
194
+ * useMemo dependencies stay stable across renders.
195
+ *
196
+ * Earlier iterations of this function eagerly merged allOf and folded
197
+ * siblings into oneOf/anyOf branches, but those operations need to stay
198
+ * conditional inside SchemaNode and DiscriminatorNode — eager versions
199
+ * caused cartesian product expansion (allOf with multiple oneOfs) and
200
+ * double-folding regressions. The render-time helpers below
201
+ * (mergeAllOf, foldSiblingsIntoBranches) are still cached via the WeakMap
202
+ * inside stripConflictingAdditionalProps, so per-render cost stays bounded.
203
+ *
204
+ * The perf win for #1525 comes from getDiscriminator's WeakMap cache
205
+ * replacing the O(subtree) findDiscriminator walk, not from eager
206
+ * normalization here.
207
+ */
208
+ function normalizeSchema(schema) {
209
+ return schema;
210
+ }
@@ -0,0 +1 @@
1
+ export {};