@hey-api/json-schema-ref-parser 1.2.4 → 1.3.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 (92) hide show
  1. package/README.md +9 -84
  2. package/dist/index.d.mts +629 -0
  3. package/dist/index.d.mts.map +1 -0
  4. package/dist/index.mjs +1887 -0
  5. package/dist/index.mjs.map +1 -0
  6. package/package.json +42 -78
  7. package/src/__tests__/bundle.test.ts +59 -0
  8. package/src/__tests__/index.test.ts +43 -0
  9. package/src/__tests__/pointer.test.ts +34 -0
  10. package/src/__tests__/utils.ts +3 -0
  11. package/{lib → src}/bundle.ts +143 -229
  12. package/{lib → src}/dereference.ts +20 -43
  13. package/{lib → src}/index.ts +103 -125
  14. package/{lib → src}/options.ts +13 -9
  15. package/{lib → src}/parse.ts +19 -15
  16. package/src/parsers/binary.ts +13 -0
  17. package/{lib → src}/parsers/json.ts +5 -6
  18. package/src/parsers/text.ts +21 -0
  19. package/{lib → src}/parsers/yaml.ts +9 -9
  20. package/{lib → src}/pointer.ts +42 -23
  21. package/{lib → src}/ref.ts +25 -21
  22. package/{lib → src}/refs.ts +23 -26
  23. package/{lib → src}/resolve-external.ts +91 -60
  24. package/{lib → src}/resolvers/file.ts +7 -10
  25. package/{lib → src}/resolvers/url.ts +12 -8
  26. package/{lib → src}/types/index.ts +9 -2
  27. package/src/util/convert-path-to-posix.ts +8 -0
  28. package/{lib → src}/util/errors.ts +38 -36
  29. package/{lib → src}/util/is-windows.ts +1 -1
  30. package/{lib → src}/util/plugins.ts +7 -8
  31. package/{lib → src}/util/url.ts +41 -42
  32. package/dist/lib/__tests__/bundle.test.d.ts +0 -1
  33. package/dist/lib/__tests__/bundle.test.js +0 -50
  34. package/dist/lib/__tests__/index.test.d.ts +0 -1
  35. package/dist/lib/__tests__/index.test.js +0 -43
  36. package/dist/lib/__tests__/pointer.test.d.ts +0 -1
  37. package/dist/lib/__tests__/pointer.test.js +0 -27
  38. package/dist/lib/bundle.d.ts +0 -26
  39. package/dist/lib/bundle.js +0 -600
  40. package/dist/lib/dereference.d.ts +0 -11
  41. package/dist/lib/dereference.js +0 -226
  42. package/dist/lib/index.d.ts +0 -92
  43. package/dist/lib/index.js +0 -525
  44. package/dist/lib/options.d.ts +0 -61
  45. package/dist/lib/options.js +0 -45
  46. package/dist/lib/parse.d.ts +0 -13
  47. package/dist/lib/parse.js +0 -87
  48. package/dist/lib/parsers/binary.d.ts +0 -2
  49. package/dist/lib/parsers/binary.js +0 -12
  50. package/dist/lib/parsers/json.d.ts +0 -2
  51. package/dist/lib/parsers/json.js +0 -38
  52. package/dist/lib/parsers/text.d.ts +0 -2
  53. package/dist/lib/parsers/text.js +0 -18
  54. package/dist/lib/parsers/yaml.d.ts +0 -2
  55. package/dist/lib/parsers/yaml.js +0 -28
  56. package/dist/lib/pointer.d.ts +0 -88
  57. package/dist/lib/pointer.js +0 -297
  58. package/dist/lib/ref.d.ts +0 -180
  59. package/dist/lib/ref.js +0 -226
  60. package/dist/lib/refs.d.ts +0 -127
  61. package/dist/lib/refs.js +0 -232
  62. package/dist/lib/resolve-external.d.ts +0 -13
  63. package/dist/lib/resolve-external.js +0 -151
  64. package/dist/lib/resolvers/file.d.ts +0 -6
  65. package/dist/lib/resolvers/file.js +0 -61
  66. package/dist/lib/resolvers/url.d.ts +0 -17
  67. package/dist/lib/resolvers/url.js +0 -62
  68. package/dist/lib/types/index.d.ts +0 -43
  69. package/dist/lib/types/index.js +0 -2
  70. package/dist/lib/util/convert-path-to-posix.d.ts +0 -1
  71. package/dist/lib/util/convert-path-to-posix.js +0 -14
  72. package/dist/lib/util/errors.d.ts +0 -56
  73. package/dist/lib/util/errors.js +0 -112
  74. package/dist/lib/util/is-windows.d.ts +0 -1
  75. package/dist/lib/util/is-windows.js +0 -6
  76. package/dist/lib/util/plugins.d.ts +0 -16
  77. package/dist/lib/util/plugins.js +0 -45
  78. package/dist/lib/util/url.d.ts +0 -79
  79. package/dist/lib/util/url.js +0 -285
  80. package/dist/vite.config.d.ts +0 -2
  81. package/dist/vite.config.js +0 -19
  82. package/lib/__tests__/bundle.test.ts +0 -52
  83. package/lib/__tests__/index.test.ts +0 -45
  84. package/lib/__tests__/pointer.test.ts +0 -26
  85. package/lib/__tests__/spec/circular-ref-with-description.json +0 -11
  86. package/lib/__tests__/spec/multiple-refs.json +0 -34
  87. package/lib/__tests__/spec/openapi-paths-ref.json +0 -46
  88. package/lib/__tests__/spec/path-parameter.json +0 -16
  89. package/lib/parsers/binary.ts +0 -13
  90. package/lib/parsers/text.ts +0 -21
  91. package/lib/util/convert-path-to-posix.ts +0 -11
  92. /package/{LICENSE → LICENSE.md} +0 -0
@@ -1,21 +1,11 @@
1
- import $Ref from "./ref.js";
2
- import type { ParserOptions } from "./options.js";
3
- import Pointer from "./pointer.js";
4
- import * as url from "./util/url.js";
5
- import type $Refs from "./refs.js";
6
- import type { $RefParser } from "./index";
7
- import type { JSONSchema } from "./types/index.js";
8
-
9
- const DEBUG_PERFORMANCE =
10
- process.env.DEBUG === "true" ||
11
- (typeof globalThis !== "undefined" && (globalThis as any).DEBUG_BUNDLE_PERFORMANCE === true);
12
-
13
- const perf = {
14
- mark: (name: string) => DEBUG_PERFORMANCE && performance.mark(name),
15
- measure: (name: string, start: string, end: string) => DEBUG_PERFORMANCE && performance.measure(name, start, end),
16
- log: (message: string, ...args: any[]) => DEBUG_PERFORMANCE && console.log("[PERF] " + message, ...args),
17
- warn: (message: string, ...args: any[]) => DEBUG_PERFORMANCE && console.warn("[PERF] " + message, ...args),
18
- };
1
+ import type { $RefParser } from '.';
2
+ import type { ParserOptions } from './options';
3
+ import Pointer from './pointer';
4
+ import $Ref from './ref';
5
+ import type $Refs from './refs';
6
+ import type { JSONSchema } from './types';
7
+ import { MissingPointerError } from './util/errors';
8
+ import * as url from './util/url';
19
9
 
20
10
  export interface InventoryEntry {
21
11
  $ref: any;
@@ -27,10 +17,10 @@ export interface InventoryEntry {
27
17
  hash: any;
28
18
  indirections: any;
29
19
  key: any;
20
+ originalContainerType?: 'schemas' | 'parameters' | 'requestBodies' | 'responses' | 'headers';
30
21
  parent: any;
31
22
  pathFromRoot: any;
32
23
  value: any;
33
- originalContainerType?: "schemas" | "parameters" | "requestBodies" | "responses" | "headers";
34
24
  }
35
25
 
36
26
  /**
@@ -40,8 +30,6 @@ const createInventoryLookup = () => {
40
30
  const lookup = new Map<string, InventoryEntry>();
41
31
  const objectIds = new WeakMap<object, string>(); // Use WeakMap to avoid polluting objects
42
32
  let idCounter = 0;
43
- let lookupCount = 0;
44
- let addCount = 0;
45
33
 
46
34
  const getObjectId = (obj: any) => {
47
35
  if (!objectIds.has(obj)) {
@@ -50,34 +38,24 @@ const createInventoryLookup = () => {
50
38
  return objectIds.get(obj)!;
51
39
  };
52
40
 
53
- const createInventoryKey = ($refParent: any, $refKey: any) => {
41
+ const createInventoryKey = ($refParent: any, $refKey: any) =>
54
42
  // Use WeakMap-based lookup to avoid polluting the actual schema objects
55
- return `${getObjectId($refParent)}_${$refKey}`;
56
- };
43
+ `${getObjectId($refParent)}_${$refKey}`;
57
44
 
58
45
  return {
59
46
  add: (entry: InventoryEntry) => {
60
- addCount++;
61
47
  const key = createInventoryKey(entry.parent, entry.key);
62
48
  lookup.set(key, entry);
63
- if (addCount % 100 === 0) {
64
- perf.log(`Inventory lookup: Added ${addCount} entries, map size: ${lookup.size}`);
65
- }
66
49
  },
67
50
  find: ($refParent: any, $refKey: any) => {
68
- lookupCount++;
69
51
  const key = createInventoryKey($refParent, $refKey);
70
52
  const result = lookup.get(key);
71
- if (lookupCount % 100 === 0) {
72
- perf.log(`Inventory lookup: ${lookupCount} lookups performed`);
73
- }
74
53
  return result;
75
54
  },
76
55
  remove: (entry: InventoryEntry) => {
77
56
  const key = createInventoryKey(entry.parent, entry.key);
78
57
  lookup.delete(key);
79
58
  },
80
- getStats: () => ({ lookupCount, addCount, mapSize: lookup.size }),
81
59
  };
82
60
  };
83
61
 
@@ -90,27 +68,27 @@ const createInventoryLookup = () => {
90
68
  */
91
69
  const getContainerTypeFromPath = (
92
70
  path: string,
93
- ): "schemas" | "parameters" | "requestBodies" | "responses" | "headers" => {
71
+ ): 'schemas' | 'parameters' | 'requestBodies' | 'responses' | 'headers' => {
94
72
  const tokens = Pointer.parse(path);
95
73
  const has = (t: string) => tokens.includes(t);
96
74
  // Prefer more specific containers first
97
- if (has("parameters")) {
98
- return "parameters";
75
+ if (has('parameters')) {
76
+ return 'parameters';
99
77
  }
100
- if (has("requestBody")) {
101
- return "requestBodies";
78
+ if (has('requestBody')) {
79
+ return 'requestBodies';
102
80
  }
103
- if (has("headers")) {
104
- return "headers";
81
+ if (has('headers')) {
82
+ return 'headers';
105
83
  }
106
- if (has("responses")) {
107
- return "responses";
84
+ if (has('responses')) {
85
+ return 'responses';
108
86
  }
109
- if (has("schema")) {
110
- return "schemas";
87
+ if (has('schema')) {
88
+ return 'schemas';
111
89
  }
112
90
  // default: treat as schema-like
113
- return "schemas";
91
+ return 'schemas';
114
92
  };
115
93
 
116
94
  /**
@@ -127,8 +105,8 @@ const inventory$Ref = <S extends object = JSONSchema>({
127
105
  options,
128
106
  path,
129
107
  pathFromRoot,
130
- visitedObjects = new WeakSet(),
131
108
  resolvedRefs = new Map(),
109
+ visitedObjects = new WeakSet(),
132
110
  }: {
133
111
  /**
134
112
  * The key in `$refParent` that is a JSON Reference
@@ -160,38 +138,38 @@ const inventory$Ref = <S extends object = JSONSchema>({
160
138
  * The path of the JSON Reference at `$refKey`, from the schema root
161
139
  */
162
140
  pathFromRoot: string;
163
- /**
164
- * Set of already visited objects to avoid infinite loops and redundant processing
165
- */
166
- visitedObjects?: WeakSet<object>;
167
141
  /**
168
142
  * Cache for resolved $ref targets to avoid redundant resolution
169
143
  */
170
144
  resolvedRefs?: Map<string, any>;
145
+ /**
146
+ * Set of already visited objects to avoid infinite loops and redundant processing
147
+ */
148
+ visitedObjects?: WeakSet<object>;
171
149
  }) => {
172
- perf.mark("inventory-ref-start");
173
150
  const $ref = $refKey === null ? $refParent : $refParent[$refKey];
174
151
  const $refPath = url.resolve(path, $ref.$ref);
175
152
 
176
153
  // Check cache first to avoid redundant resolution
177
154
  let pointer = resolvedRefs.get($refPath);
178
155
  if (!pointer) {
179
- perf.mark("resolve-start");
180
- pointer = $refs._resolve($refPath, pathFromRoot, options);
181
- perf.mark("resolve-end");
182
- perf.measure("resolve-time", "resolve-start", "resolve-end");
156
+ try {
157
+ pointer = $refs._resolve($refPath, pathFromRoot, options);
158
+ } catch (error) {
159
+ if (error instanceof MissingPointerError) {
160
+ // Log warning but continue - common in complex schema ecosystems
161
+ console.warn(`Skipping unresolvable $ref: ${$refPath}`);
162
+ return;
163
+ }
164
+ throw error; // Re-throw unexpected errors
165
+ }
183
166
 
184
167
  if (pointer) {
185
168
  resolvedRefs.set($refPath, pointer);
186
- perf.log(`Cached resolved $ref: ${$refPath}`);
187
169
  }
188
170
  }
189
171
 
190
- if (pointer === null) {
191
- perf.mark("inventory-ref-end");
192
- perf.measure("inventory-ref-time", "inventory-ref-start", "inventory-ref-end");
193
- return;
194
- }
172
+ if (pointer === null) return;
195
173
 
196
174
  const parsed = Pointer.parse(pathFromRoot);
197
175
  const depth = parsed.length;
@@ -202,10 +180,7 @@ const inventory$Ref = <S extends object = JSONSchema>({
202
180
  indirections += pointer.indirections;
203
181
 
204
182
  // Check if this exact location (parent + key + pathFromRoot) has already been inventoried
205
- perf.mark("lookup-start");
206
183
  const existingEntry = inventoryLookup.find($refParent, $refKey);
207
- perf.mark("lookup-end");
208
- perf.measure("lookup-time", "lookup-start", "lookup-end");
209
184
 
210
185
  if (existingEntry && existingEntry.pathFromRoot === pathFromRoot) {
211
186
  // This exact location has already been inventoried, so we don't need to process it again
@@ -213,8 +188,6 @@ const inventory$Ref = <S extends object = JSONSchema>({
213
188
  removeFromInventory(inventory, existingEntry);
214
189
  inventoryLookup.remove(existingEntry);
215
190
  } else {
216
- perf.mark("inventory-ref-end");
217
- perf.measure("inventory-ref-time", "inventory-ref-start", "inventory-ref-end");
218
191
  return;
219
192
  }
220
193
  }
@@ -228,40 +201,38 @@ const inventory$Ref = <S extends object = JSONSchema>({
228
201
  file, // The file that the $ref pointer resolves to
229
202
  hash, // The hash within `file` that the $ref pointer resolves to
230
203
  indirections, // The number of indirect references that were traversed to resolve the value
231
- key: $refKey, // The key in `parent` that is the $ref pointer
232
- parent: $refParent, // The object that contains this $ref pointer
233
- pathFromRoot, // The path to the $ref pointer, from the JSON Schema root
234
- value: pointer.value, // The resolved value of the $ref pointer
235
- originalContainerType: external ? getContainerTypeFromPath(pointer.path) : undefined, // The original container type in the external file
204
+ key: $refKey,
205
+ // The resolved value of the $ref pointer
206
+ originalContainerType: external ? getContainerTypeFromPath(pointer.path) : undefined,
207
+
208
+ // The key in `parent` that is the $ref pointer
209
+ parent: $refParent,
210
+
211
+ // The object that contains this $ref pointer
212
+ pathFromRoot,
213
+ // The path to the $ref pointer, from the JSON Schema root
214
+ value: pointer.value, // The original container type in the external file
236
215
  };
237
216
 
238
217
  inventory.push(newEntry);
239
218
  inventoryLookup.add(newEntry);
240
219
 
241
- perf.log(`Inventoried $ref: ${$ref.$ref} -> ${file}${hash} (external: ${external}, depth: ${depth})`);
242
-
243
220
  // Recursively crawl the resolved value
244
221
  if (!existingEntry || external) {
245
- perf.mark("crawl-recursive-start");
246
222
  crawl({
247
- parent: pointer.value,
248
- key: null,
249
- path: pointer.path,
250
- pathFromRoot,
223
+ $refs,
251
224
  indirections: indirections + 1,
252
225
  inventory,
253
226
  inventoryLookup,
254
- $refs,
227
+ key: null,
255
228
  options,
256
- visitedObjects,
229
+ parent: pointer.value,
230
+ path: pointer.path,
231
+ pathFromRoot,
257
232
  resolvedRefs,
233
+ visitedObjects,
258
234
  });
259
- perf.mark("crawl-recursive-end");
260
- perf.measure("crawl-recursive-time", "crawl-recursive-start", "crawl-recursive-end");
261
235
  }
262
-
263
- perf.mark("inventory-ref-end");
264
- perf.measure("inventory-ref-time", "inventory-ref-start", "inventory-ref-end");
265
236
  };
266
237
 
267
238
  /**
@@ -277,8 +248,8 @@ const crawl = <S extends object = JSONSchema>({
277
248
  parent,
278
249
  path,
279
250
  pathFromRoot,
280
- visitedObjects = new WeakSet(),
281
251
  resolvedRefs = new Map(),
252
+ visitedObjects = new WeakSet(),
282
253
  }: {
283
254
  $refs: $Refs<S>;
284
255
  indirections: number;
@@ -307,38 +278,34 @@ const crawl = <S extends object = JSONSchema>({
307
278
  * The path of the property being crawled, from the schema root
308
279
  */
309
280
  pathFromRoot: string;
310
- /**
311
- * Set of already visited objects to avoid infinite loops and redundant processing
312
- */
313
- visitedObjects?: WeakSet<object>;
314
281
  /**
315
282
  * Cache for resolved $ref targets to avoid redundant resolution
316
283
  */
317
284
  resolvedRefs?: Map<string, any>;
285
+ /**
286
+ * Set of already visited objects to avoid infinite loops and redundant processing
287
+ */
288
+ visitedObjects?: WeakSet<object>;
318
289
  }) => {
319
290
  const obj = key === null ? parent : parent[key as keyof typeof parent];
320
291
 
321
- if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj)) {
292
+ if (obj && typeof obj === 'object' && !ArrayBuffer.isView(obj)) {
322
293
  // Early exit if we've already processed this exact object
323
- if (visitedObjects.has(obj)) {
324
- perf.log(`Skipping already visited object at ${pathFromRoot}`);
325
- return;
326
- }
294
+ if (visitedObjects.has(obj)) return;
327
295
 
328
296
  if ($Ref.isAllowed$Ref(obj)) {
329
- perf.log(`Found $ref at ${pathFromRoot}: ${(obj as any).$ref}`);
330
297
  inventory$Ref({
331
- $refParent: parent,
332
298
  $refKey: key,
333
- path,
334
- pathFromRoot,
299
+ $refParent: parent,
300
+ $refs,
335
301
  indirections,
336
302
  inventory,
337
303
  inventoryLookup,
338
- $refs,
339
304
  options,
340
- visitedObjects,
305
+ path,
306
+ pathFromRoot,
341
307
  resolvedRefs,
308
+ visitedObjects,
342
309
  });
343
310
  } else {
344
311
  // Mark this object as visited BEFORE processing its children
@@ -350,16 +317,16 @@ const crawl = <S extends object = JSONSchema>({
350
317
  const keys = Object.keys(obj).sort((a, b) => {
351
318
  // Most people will expect references to be bundled into the "definitions" property,
352
319
  // so we always crawl that property first, if it exists.
353
- if (a === "definitions") {
320
+ if (a === 'definitions') {
354
321
  return -1;
355
- } else if (b === "definitions") {
322
+ } else if (b === 'definitions') {
356
323
  return 1;
357
324
  } else {
358
325
  // Otherwise, crawl the keys based on their length.
359
326
  // This produces the shortest possible bundled references
360
327
  return a.length - b.length;
361
328
  }
362
- }) as (keyof typeof obj)[];
329
+ }) as Array<keyof typeof obj>;
363
330
 
364
331
  for (const key of keys) {
365
332
  const keyPath = Pointer.join(path, key);
@@ -368,31 +335,31 @@ const crawl = <S extends object = JSONSchema>({
368
335
 
369
336
  if ($Ref.isAllowed$Ref(value)) {
370
337
  inventory$Ref({
371
- $refParent: obj,
372
338
  $refKey: key,
373
- path,
374
- pathFromRoot: keyPathFromRoot,
339
+ $refParent: obj,
340
+ $refs,
375
341
  indirections,
376
342
  inventory,
377
343
  inventoryLookup,
378
- $refs,
379
344
  options,
380
- visitedObjects,
345
+ path,
346
+ pathFromRoot: keyPathFromRoot,
381
347
  resolvedRefs,
348
+ visitedObjects,
382
349
  });
383
350
  } else {
384
351
  crawl({
385
- parent: obj,
386
- key,
387
- path: keyPath,
388
- pathFromRoot: keyPathFromRoot,
352
+ $refs,
389
353
  indirections,
390
354
  inventory,
391
355
  inventoryLookup,
392
- $refs,
356
+ key,
393
357
  options,
394
- visitedObjects,
358
+ parent: obj,
359
+ path: keyPath,
360
+ pathFromRoot: keyPathFromRoot,
395
361
  resolvedRefs,
362
+ visitedObjects,
396
363
  });
397
364
  }
398
365
  }
@@ -404,13 +371,10 @@ const crawl = <S extends object = JSONSchema>({
404
371
  * Remap external refs by hoisting resolved values into a shared container in the root schema
405
372
  * and pointing all occurrences to those internal definitions. Internal refs remain internal.
406
373
  */
407
- function remap(parser: $RefParser, inventory: InventoryEntry[]) {
408
- perf.log(`Starting remap with ${inventory.length} inventory entries`);
409
- perf.mark("remap-start");
374
+ function remap(parser: $RefParser, inventory: Array<InventoryEntry>) {
410
375
  const root = parser.schema as any;
411
376
 
412
377
  // Group & sort all the $ref pointers, so they're in the order that we need to dereference/remap them
413
- perf.mark("sort-inventory-start");
414
378
  inventory.sort((a: InventoryEntry, b: InventoryEntry) => {
415
379
  if (a.file !== b.file) {
416
380
  // Group all the $refs that point to the same file
@@ -433,8 +397,8 @@ function remap(parser: $RefParser, inventory: InventoryEntry[]) {
433
397
  } else {
434
398
  // Determine how far each $ref is from the "definitions" property.
435
399
  // Most people will expect references to be bundled into the the "definitions" property if possible.
436
- const aDefinitionsIndex = a.pathFromRoot.lastIndexOf("/definitions");
437
- const bDefinitionsIndex = b.pathFromRoot.lastIndexOf("/definitions");
400
+ const aDefinitionsIndex = a.pathFromRoot.lastIndexOf('/definitions');
401
+ const bDefinitionsIndex = b.pathFromRoot.lastIndexOf('/definitions');
438
402
  if (aDefinitionsIndex !== bDefinitionsIndex) {
439
403
  // Give higher priority to the $ref that's closer to the "definitions" property
440
404
  return bDefinitionsIndex - aDefinitionsIndex;
@@ -445,69 +409,66 @@ function remap(parser: $RefParser, inventory: InventoryEntry[]) {
445
409
  }
446
410
  });
447
411
 
448
- perf.mark("sort-inventory-end");
449
- perf.measure("sort-inventory-time", "sort-inventory-start", "sort-inventory-end");
450
-
451
- perf.log(`Sorted ${inventory.length} inventory entries`);
452
-
453
412
  // Ensure or return a container by component type. Prefer OpenAPI-aware placement;
454
413
  // otherwise use existing root containers; otherwise create components/*.
455
- const ensureContainer = (type: "schemas" | "parameters" | "requestBodies" | "responses" | "headers") => {
456
- const isOas3 = !!(root && typeof root === "object" && typeof root.openapi === "string");
457
- const isOas2 = !!(root && typeof root === "object" && typeof root.swagger === "string");
414
+ const ensureContainer = (
415
+ type: 'schemas' | 'parameters' | 'requestBodies' | 'responses' | 'headers',
416
+ ) => {
417
+ const isOas3 = !!(root && typeof root === 'object' && typeof root.openapi === 'string');
418
+ const isOas2 = !!(root && typeof root === 'object' && typeof root.swagger === 'string');
458
419
 
459
420
  if (isOas3) {
460
- if (!root.components || typeof root.components !== "object") {
421
+ if (!root.components || typeof root.components !== 'object') {
461
422
  root.components = {};
462
423
  }
463
- if (!root.components[type] || typeof root.components[type] !== "object") {
424
+ if (!root.components[type] || typeof root.components[type] !== 'object') {
464
425
  root.components[type] = {};
465
426
  }
466
427
  return { obj: root.components[type], prefix: `#/components/${type}` } as const;
467
428
  }
468
429
 
469
430
  if (isOas2) {
470
- if (type === "schemas") {
471
- if (!root.definitions || typeof root.definitions !== "object") {
431
+ if (type === 'schemas') {
432
+ if (!root.definitions || typeof root.definitions !== 'object') {
472
433
  root.definitions = {};
473
434
  }
474
- return { obj: root.definitions, prefix: "#/definitions" } as const;
435
+ return { obj: root.definitions, prefix: '#/definitions' } as const;
475
436
  }
476
- if (type === "parameters") {
477
- if (!root.parameters || typeof root.parameters !== "object") {
437
+ if (type === 'parameters') {
438
+ if (!root.parameters || typeof root.parameters !== 'object') {
478
439
  root.parameters = {};
479
440
  }
480
- return { obj: root.parameters, prefix: "#/parameters" } as const;
441
+ return { obj: root.parameters, prefix: '#/parameters' } as const;
481
442
  }
482
- if (type === "responses") {
483
- if (!root.responses || typeof root.responses !== "object") {
443
+ if (type === 'responses') {
444
+ if (!root.responses || typeof root.responses !== 'object') {
484
445
  root.responses = {};
485
446
  }
486
- return { obj: root.responses, prefix: "#/responses" } as const;
447
+ return { obj: root.responses, prefix: '#/responses' } as const;
487
448
  }
488
449
  // requestBodies/headers don't exist as reusable containers in OAS2; fallback to definitions
489
- if (!root.definitions || typeof root.definitions !== "object") {
450
+ if (!root.definitions || typeof root.definitions !== 'object') {
490
451
  root.definitions = {};
491
452
  }
492
- return { obj: root.definitions, prefix: "#/definitions" } as const;
453
+ return { obj: root.definitions, prefix: '#/definitions' } as const;
493
454
  }
494
455
 
495
456
  // No explicit version: prefer existing containers
496
- if (root && typeof root === "object") {
497
- if (root.components && typeof root.components === "object") {
498
- if (!root.components[type] || typeof root.components[type] !== "object") {
457
+ if (root && typeof root === 'object') {
458
+ if (root.components && typeof root.components === 'object') {
459
+ if (!root.components[type] || typeof root.components[type] !== 'object') {
499
460
  root.components[type] = {};
500
461
  }
501
462
  return { obj: root.components[type], prefix: `#/components/${type}` } as const;
502
463
  }
503
- if (root.definitions && typeof root.definitions === "object") {
504
- return { obj: root.definitions, prefix: "#/definitions" } as const;
464
+ if (root.definitions && typeof root.definitions === 'object') {
465
+ return { obj: root.definitions, prefix: '#/definitions' } as const;
505
466
  }
506
467
  // Create components/* by default if nothing exists
507
- if (!root.components || typeof root.components !== "object") {
468
+ if (!root.components || typeof root.components !== 'object') {
508
469
  root.components = {};
509
470
  }
510
- if (!root.components[type] || typeof root.components[type] !== "object") {
471
+ if (!root.components[type] || typeof root.components[type] !== 'object') {
511
472
  root.components[type] = {};
512
473
  }
513
474
  return { obj: root.components[type], prefix: `#/components/${type}` } as const;
@@ -515,7 +476,7 @@ function remap(parser: $RefParser, inventory: InventoryEntry[]) {
515
476
 
516
477
  // Fallback
517
478
  root.definitions = root.definitions || {};
518
- return { obj: root.definitions, prefix: "#/definitions" } as const;
479
+ return { obj: root.definitions, prefix: '#/definitions' } as const;
519
480
  };
520
481
 
521
482
  /**
@@ -539,24 +500,24 @@ function remap(parser: $RefParser, inventory: InventoryEntry[]) {
539
500
  const targetToNameByPrefix = new Map<string, Map<string, string>>();
540
501
  const usedNamesByObj = new Map<any, Set<string>>();
541
502
 
542
- const sanitize = (name: string) => name.replace(/[^A-Za-z0-9_-]/g, "_");
503
+ const sanitize = (name: string) => name.replace(/[^A-Za-z0-9_-]/g, '_');
543
504
  const baseName = (filePath: string) => {
544
505
  try {
545
- const withoutHash = filePath.split("#")[0];
546
- const parts = withoutHash.split("/");
547
- const filename = parts[parts.length - 1] || "schema";
548
- const dot = filename.lastIndexOf(".");
506
+ const withoutHash = filePath.split('#')[0]!;
507
+ const parts = withoutHash.split('/');
508
+ const filename = parts[parts.length - 1] || 'schema';
509
+ const dot = filename.lastIndexOf('.');
549
510
  return sanitize(dot > 0 ? filename.substring(0, dot) : filename);
550
511
  } catch {
551
- return "schema";
512
+ return 'schema';
552
513
  }
553
514
  };
554
515
  const lastToken = (hash: string) => {
555
- if (!hash || hash === "#") {
556
- return "root";
516
+ if (!hash || hash === '#') {
517
+ return 'root';
557
518
  }
558
- const tokens = hash.replace(/^#\//, "").split("/");
559
- return sanitize(tokens[tokens.length - 1] || "root");
519
+ const tokens = hash.replace(/^#\//, '').split('/');
520
+ return sanitize(tokens[tokens.length - 1] || 'root');
560
521
  };
561
522
  const uniqueName = (containerObj: any, proposed: string) => {
562
523
  if (!usedNamesByObj.has(containerObj)) {
@@ -571,11 +532,9 @@ function remap(parser: $RefParser, inventory: InventoryEntry[]) {
571
532
  used.add(name);
572
533
  return name;
573
534
  };
574
- perf.mark("remap-loop-start");
575
535
  for (const entry of inventory) {
576
536
  // Safety check: ensure entry and entry.$ref are valid objects
577
- if (!entry || !entry.$ref || typeof entry.$ref !== "object") {
578
- perf.warn(`Skipping invalid inventory entry:`, entry);
537
+ if (!entry || !entry.$ref || typeof entry.$ref !== 'object') {
579
538
  continue;
580
539
  }
581
540
 
@@ -583,7 +542,7 @@ function remap(parser: $RefParser, inventory: InventoryEntry[]) {
583
542
  // (i.e. it has additional properties in addition to "$ref"), then we must
584
543
  // preserve the original $ref rather than rewriting it to the resolved hash.
585
544
  if (!entry.external) {
586
- if (!entry.extended && entry.$ref && typeof entry.$ref === "object") {
545
+ if (!entry.extended && entry.$ref && typeof entry.$ref === 'object') {
587
546
  entry.$ref.$ref = entry.hash;
588
547
  }
589
548
  continue;
@@ -591,7 +550,7 @@ function remap(parser: $RefParser, inventory: InventoryEntry[]) {
591
550
 
592
551
  // Avoid changing direct self-references; keep them internal
593
552
  if (entry.circular) {
594
- if (entry.$ref && typeof entry.$ref === "object") {
553
+ if (entry.$ref && typeof entry.$ref === 'object') {
595
554
  entry.$ref.$ref = entry.pathFromRoot;
596
555
  }
597
556
  continue;
@@ -613,10 +572,14 @@ function remap(parser: $RefParser, inventory: InventoryEntry[]) {
613
572
  let proposedBase = baseName(entry.file);
614
573
  try {
615
574
  const parserAny: any = parser as any;
616
- if (parserAny && parserAny.sourcePathToPrefix && typeof parserAny.sourcePathToPrefix.get === "function") {
617
- const withoutHash = (entry.file || "").split("#")[0];
575
+ if (
576
+ parserAny &&
577
+ parserAny.sourcePathToPrefix &&
578
+ typeof parserAny.sourcePathToPrefix.get === 'function'
579
+ ) {
580
+ const withoutHash = (entry.file || '').split('#')[0];
618
581
  const mapped = parserAny.sourcePathToPrefix.get(withoutHash);
619
- if (mapped && typeof mapped === "string") {
582
+ if (mapped && typeof mapped === 'string') {
620
583
  proposedBase = mapped;
621
584
  }
622
585
  }
@@ -632,22 +595,15 @@ function remap(parser: $RefParser, inventory: InventoryEntry[]) {
632
595
 
633
596
  // Point the occurrence to the internal definition, preserving extensions
634
597
  const refPath = `${prefix}/${defName}`;
635
- if (entry.extended && entry.$ref && typeof entry.$ref === "object") {
598
+ if (entry.extended && entry.$ref && typeof entry.$ref === 'object') {
636
599
  entry.$ref.$ref = refPath;
637
600
  } else {
638
601
  entry.parent[entry.key] = { $ref: refPath };
639
602
  }
640
603
  }
641
- perf.mark("remap-loop-end");
642
- perf.measure("remap-loop-time", "remap-loop-start", "remap-loop-end");
643
-
644
- perf.mark("remap-end");
645
- perf.measure("remap-total-time", "remap-start", "remap-end");
646
-
647
- perf.log(`Completed remap of ${inventory.length} entries`);
648
604
  }
649
605
 
650
- function removeFromInventory(inventory: InventoryEntry[], entry: any) {
606
+ function removeFromInventory(inventory: Array<InventoryEntry>, entry: any) {
651
607
  const index = inventory.indexOf(entry);
652
608
  inventory.splice(index, 1);
653
609
  }
@@ -660,68 +616,26 @@ function removeFromInventory(inventory: InventoryEntry[], entry: any) {
660
616
  * @param parser
661
617
  * @param options
662
618
  */
663
- export const bundle = (parser: $RefParser, options: ParserOptions) => {
664
- // console.log('Bundling $ref pointers in %s', parser.$refs._root$Ref.path);
665
- perf.mark("bundle-start");
666
-
667
- // Build an inventory of all $ref pointers in the JSON Schema
668
- const inventory: InventoryEntry[] = [];
619
+ export function bundle(parser: $RefParser, options: ParserOptions): void {
620
+ const inventory: Array<InventoryEntry> = [];
669
621
  const inventoryLookup = createInventoryLookup();
670
622
 
671
- perf.log("Starting crawl phase");
672
- perf.mark("crawl-phase-start");
673
-
674
623
  const visitedObjects = new WeakSet<object>();
675
- const resolvedRefs = new Map<string, any>(); // Cache for resolved $ref targets
624
+ const resolvedRefs = new Map<string, any>();
676
625
 
677
626
  crawl<JSONSchema>({
678
- parent: parser,
679
- key: "schema",
680
- path: parser.$refs._root$Ref.path + "#",
681
- pathFromRoot: "#",
627
+ $refs: parser.$refs,
682
628
  indirections: 0,
683
629
  inventory,
684
630
  inventoryLookup,
685
- $refs: parser.$refs,
631
+ key: 'schema',
686
632
  options,
687
- visitedObjects,
633
+ parent: parser,
634
+ path: parser.$refs._root$Ref.path + '#',
635
+ pathFromRoot: '#',
688
636
  resolvedRefs,
637
+ visitedObjects,
689
638
  });
690
639
 
691
- perf.mark("crawl-phase-end");
692
- perf.measure("crawl-phase-time", "crawl-phase-start", "crawl-phase-end");
693
-
694
- const stats = inventoryLookup.getStats();
695
- perf.log(`Crawl phase complete. Found ${inventory.length} $refs. Lookup stats:`, stats);
696
-
697
- // Remap all $ref pointers
698
- perf.log("Starting remap phase");
699
- perf.mark("remap-phase-start");
700
640
  remap(parser, inventory);
701
- perf.mark("remap-phase-end");
702
- perf.measure("remap-phase-time", "remap-phase-start", "remap-phase-end");
703
-
704
- perf.mark("bundle-end");
705
- perf.measure("bundle-total-time", "bundle-start", "bundle-end");
706
-
707
- perf.log("Bundle complete. Performance summary:");
708
-
709
- // Log final stats
710
- const finalStats = inventoryLookup.getStats();
711
- perf.log(`Final inventory stats:`, finalStats);
712
- perf.log(`Resolved refs cache size: ${resolvedRefs.size}`);
713
-
714
- if (DEBUG_PERFORMANCE) {
715
- // Log all performance measures
716
- const measures = performance.getEntriesByType("measure");
717
- measures.forEach((measure) => {
718
- if (measure.name.includes("time")) {
719
- console.log(`${measure.name}: ${measure.duration.toFixed(2)}ms`);
720
- }
721
- });
722
-
723
- // Clear performance marks and measures for next run
724
- performance.clearMarks();
725
- performance.clearMeasures();
726
- }
727
- };
641
+ }