@speclynx/apidom-reference 4.7.0 → 4.8.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 (29) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/apidom-reference.browser.js +167 -37
  3. package/dist/apidom-reference.browser.min.js +1 -1
  4. package/package.json +28 -28
  5. package/src/dereference/strategies/arazzo-1/index.cjs +5 -1
  6. package/src/dereference/strategies/arazzo-1/index.mjs +5 -1
  7. package/src/dereference/strategies/arazzo-1/visitor.cjs +15 -3
  8. package/src/dereference/strategies/arazzo-1/visitor.mjs +15 -3
  9. package/src/dereference/strategies/asyncapi-2/index.cjs +5 -1
  10. package/src/dereference/strategies/asyncapi-2/index.mjs +5 -1
  11. package/src/dereference/strategies/asyncapi-2/visitor.cjs +23 -5
  12. package/src/dereference/strategies/asyncapi-2/visitor.mjs +23 -5
  13. package/src/dereference/strategies/openapi-2/index.cjs +5 -1
  14. package/src/dereference/strategies/openapi-2/index.mjs +5 -1
  15. package/src/dereference/strategies/openapi-2/visitor.cjs +33 -7
  16. package/src/dereference/strategies/openapi-2/visitor.mjs +33 -7
  17. package/src/dereference/strategies/openapi-3-0/index.cjs +5 -1
  18. package/src/dereference/strategies/openapi-3-0/index.mjs +5 -1
  19. package/src/dereference/strategies/openapi-3-0/visitor.cjs +23 -5
  20. package/src/dereference/strategies/openapi-3-0/visitor.mjs +23 -5
  21. package/src/dereference/strategies/openapi-3-1/index.cjs +5 -1
  22. package/src/dereference/strategies/openapi-3-1/index.mjs +5 -1
  23. package/src/dereference/strategies/openapi-3-1/visitor.cjs +29 -7
  24. package/src/dereference/strategies/openapi-3-1/visitor.mjs +29 -7
  25. package/types/dereference/strategies/arazzo-1/visitor.d.ts +3 -1
  26. package/types/dereference/strategies/asyncapi-2/visitor.d.ts +3 -1
  27. package/types/dereference/strategies/openapi-2/visitor.d.ts +3 -1
  28. package/types/dereference/strategies/openapi-3-0/visitor.d.ts +3 -1
  29. package/types/dereference/strategies/openapi-3-1/visitor.d.ts +3 -1
@@ -30,6 +30,7 @@ class OpenAPI2DereferenceVisitor {
30
30
  reference;
31
31
  options;
32
32
  refractCache;
33
+ visited;
33
34
 
34
35
  /**
35
36
  * Tracks element ancestors across dive-deep traversal boundaries.
@@ -42,13 +43,15 @@ class OpenAPI2DereferenceVisitor {
42
43
  options,
43
44
  indirections = [],
44
45
  ancestors = new _util.AncestorLineage(),
45
- refractCache = new WeakMap()
46
+ refractCache = new WeakMap(),
47
+ visited = new WeakSet()
46
48
  }) {
47
49
  this.indirections = indirections;
48
50
  this.reference = reference;
49
51
  this.options = options;
50
52
  this.refractCache = refractCache;
51
53
  this.ancestors = new _util.AncestorLineage(...ancestors);
54
+ this.visited = visited;
52
55
  }
53
56
  toAncestorLineage(path) {
54
57
  const ancestorNodes = path.getAncestorNodes();
@@ -281,6 +284,7 @@ class OpenAPI2DereferenceVisitor {
281
284
  const replacer = this.options.dereference.strategyOpts['openapi-2']?.circularReplacer ?? this.options.dereference.circularReplacer;
282
285
  const replacement = replacer(refElement);
283
286
  path.replaceWith(replacement);
287
+ path.skip();
284
288
  return;
285
289
  }
286
290
  }
@@ -296,7 +300,11 @@ class OpenAPI2DereferenceVisitor {
296
300
  */
297
301
  const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
298
302
  const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
299
- if ((isExternalReference || isNonEntryDocument || (0, _apidomNsOpenapi.isReferenceElement)(referencedElement) || shouldDetectCircular) && !ancestorsLineage.includesCycle(referencedElement)) {
303
+ if ((isExternalReference || isNonEntryDocument || (0, _apidomNsOpenapi.isReferenceElement)(referencedElement) || shouldDetectCircular && !this.visited.has(referencedElement)) && !ancestorsLineage.includesCycle(referencedElement)) {
304
+ if (shouldDetectCircular) {
305
+ this.visited.add(referencedElement);
306
+ }
307
+
300
308
  // append referencing reference to ancestors lineage
301
309
  directAncestors.add(referencingElement);
302
310
  const visitor = new OpenAPI2DereferenceVisitor({
@@ -304,10 +312,12 @@ class OpenAPI2DereferenceVisitor {
304
312
  indirections: [...this.indirections],
305
313
  options: this.options,
306
314
  refractCache: this.refractCache,
315
+ visited: this.visited,
307
316
  ancestors: ancestorsLineage
308
317
  });
309
318
  referencedElement = await (0, _apidomTraverse.traverseAsync)(referencedElement, visitor, {
310
- mutable: true
319
+ mutable: true,
320
+ skipVisited: true
311
321
  });
312
322
  directAncestors.delete(referencingElement);
313
323
  }
@@ -328,6 +338,7 @@ class OpenAPI2DereferenceVisitor {
328
338
  * Transclude referencing element with merged referenced element.
329
339
  */
330
340
  path.replaceWith(mergedElement);
341
+ path.skip();
331
342
  } catch (error) {
332
343
  const $ref = (0, _apidomCore.toValue)(referencingElement.$ref);
333
344
  this.handleError(`Error while dereferencing Reference Object. Cannot resolve $ref "${$ref}": ${error.message}`, error, referencingElement, '$ref', $ref, path);
@@ -415,6 +426,7 @@ class OpenAPI2DereferenceVisitor {
415
426
  const replacer = this.options.dereference.strategyOpts['openapi-2']?.circularReplacer ?? this.options.dereference.circularReplacer;
416
427
  const replacement = replacer(refElement);
417
428
  path.replaceWith(replacement);
429
+ path.skip();
418
430
  return;
419
431
  }
420
432
  }
@@ -430,7 +442,11 @@ class OpenAPI2DereferenceVisitor {
430
442
  */
431
443
  const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
432
444
  const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
433
- if ((isExternalReference || isNonEntryDocument || (0, _apidomNsOpenapi.isPathItemElement)(referencedElement) && (0, _apidomDatamodel.isStringElement)(referencedElement.$ref) || shouldDetectCircular) && !ancestorsLineage.includesCycle(referencedElement)) {
445
+ if ((isExternalReference || isNonEntryDocument || (0, _apidomNsOpenapi.isPathItemElement)(referencedElement) && (0, _apidomDatamodel.isStringElement)(referencedElement.$ref) || shouldDetectCircular && !this.visited.has(referencedElement)) && !ancestorsLineage.includesCycle(referencedElement)) {
446
+ if (shouldDetectCircular) {
447
+ this.visited.add(referencedElement);
448
+ }
449
+
434
450
  // append referencing reference to ancestors lineage
435
451
  directAncestors.add(referencingElement);
436
452
  const visitor = new OpenAPI2DereferenceVisitor({
@@ -438,10 +454,12 @@ class OpenAPI2DereferenceVisitor {
438
454
  indirections: [...this.indirections],
439
455
  options: this.options,
440
456
  refractCache: this.refractCache,
457
+ visited: this.visited,
441
458
  ancestors: ancestorsLineage
442
459
  });
443
460
  referencedElement = await (0, _apidomTraverse.traverseAsync)(referencedElement, visitor, {
444
- mutable: true
461
+ mutable: true,
462
+ skipVisited: true
445
463
  });
446
464
 
447
465
  // remove referencing reference from ancestors lineage
@@ -569,6 +587,7 @@ class OpenAPI2DereferenceVisitor {
569
587
  const replacer = this.options.dereference.strategyOpts['openapi-2']?.circularReplacer ?? this.options.dereference.circularReplacer;
570
588
  const replacement = replacer(refElement);
571
589
  path.replaceWith(replacement);
590
+ path.skip();
572
591
  return;
573
592
  }
574
593
  }
@@ -584,7 +603,11 @@ class OpenAPI2DereferenceVisitor {
584
603
  */
585
604
  const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
586
605
  const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
587
- if ((isExternalReference || isNonEntryDocument || (0, _apidomNsOpenapi.isJSONReferenceElement)(referencedElement) || shouldDetectCircular) && !ancestorsLineage.includesCycle(referencedElement)) {
606
+ if ((isExternalReference || isNonEntryDocument || (0, _apidomNsOpenapi.isJSONReferenceElement)(referencedElement) || shouldDetectCircular && !this.visited.has(referencedElement)) && !ancestorsLineage.includesCycle(referencedElement)) {
607
+ if (shouldDetectCircular) {
608
+ this.visited.add(referencedElement);
609
+ }
610
+
588
611
  // append referencing reference to ancestors lineage
589
612
  directAncestors.add(referencingElement);
590
613
  const visitor = new OpenAPI2DereferenceVisitor({
@@ -592,10 +615,12 @@ class OpenAPI2DereferenceVisitor {
592
615
  indirections: [...this.indirections],
593
616
  options: this.options,
594
617
  refractCache: this.refractCache,
618
+ visited: this.visited,
595
619
  ancestors: ancestorsLineage
596
620
  });
597
621
  referencedElement = await (0, _apidomTraverse.traverseAsync)(referencedElement, visitor, {
598
- mutable: true
622
+ mutable: true,
623
+ skipVisited: true
599
624
  });
600
625
 
601
626
  // remove referencing reference from ancestors lineage
@@ -618,6 +643,7 @@ class OpenAPI2DereferenceVisitor {
618
643
  * Transclude referencing element with merged referenced element.
619
644
  */
620
645
  path.replaceWith(mergedElement);
646
+ path.skip();
621
647
  } catch (error) {
622
648
  const $ref = (0, _apidomCore.toValue)(referencingElement.$ref);
623
649
  this.handleError(`Error while dereferencing JSON Reference Object. Cannot resolve $ref "${$ref}": ${error.message}`, error, referencingElement, '$ref', $ref, path);
@@ -23,6 +23,7 @@ class OpenAPI2DereferenceVisitor {
23
23
  reference;
24
24
  options;
25
25
  refractCache;
26
+ visited;
26
27
 
27
28
  /**
28
29
  * Tracks element ancestors across dive-deep traversal boundaries.
@@ -35,13 +36,15 @@ class OpenAPI2DereferenceVisitor {
35
36
  options,
36
37
  indirections = [],
37
38
  ancestors = new AncestorLineage(),
38
- refractCache = new WeakMap()
39
+ refractCache = new WeakMap(),
40
+ visited = new WeakSet()
39
41
  }) {
40
42
  this.indirections = indirections;
41
43
  this.reference = reference;
42
44
  this.options = options;
43
45
  this.refractCache = refractCache;
44
46
  this.ancestors = new AncestorLineage(...ancestors);
47
+ this.visited = visited;
45
48
  }
46
49
  toAncestorLineage(path) {
47
50
  const ancestorNodes = path.getAncestorNodes();
@@ -274,6 +277,7 @@ class OpenAPI2DereferenceVisitor {
274
277
  const replacer = this.options.dereference.strategyOpts['openapi-2']?.circularReplacer ?? this.options.dereference.circularReplacer;
275
278
  const replacement = replacer(refElement);
276
279
  path.replaceWith(replacement);
280
+ path.skip();
277
281
  return;
278
282
  }
279
283
  }
@@ -289,7 +293,11 @@ class OpenAPI2DereferenceVisitor {
289
293
  */
290
294
  const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
291
295
  const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
292
- if ((isExternalReference || isNonEntryDocument || isReferenceElement(referencedElement) || shouldDetectCircular) && !ancestorsLineage.includesCycle(referencedElement)) {
296
+ if ((isExternalReference || isNonEntryDocument || isReferenceElement(referencedElement) || shouldDetectCircular && !this.visited.has(referencedElement)) && !ancestorsLineage.includesCycle(referencedElement)) {
297
+ if (shouldDetectCircular) {
298
+ this.visited.add(referencedElement);
299
+ }
300
+
293
301
  // append referencing reference to ancestors lineage
294
302
  directAncestors.add(referencingElement);
295
303
  const visitor = new OpenAPI2DereferenceVisitor({
@@ -297,10 +305,12 @@ class OpenAPI2DereferenceVisitor {
297
305
  indirections: [...this.indirections],
298
306
  options: this.options,
299
307
  refractCache: this.refractCache,
308
+ visited: this.visited,
300
309
  ancestors: ancestorsLineage
301
310
  });
302
311
  referencedElement = await traverseAsync(referencedElement, visitor, {
303
- mutable: true
312
+ mutable: true,
313
+ skipVisited: true
304
314
  });
305
315
  directAncestors.delete(referencingElement);
306
316
  }
@@ -321,6 +331,7 @@ class OpenAPI2DereferenceVisitor {
321
331
  * Transclude referencing element with merged referenced element.
322
332
  */
323
333
  path.replaceWith(mergedElement);
334
+ path.skip();
324
335
  } catch (error) {
325
336
  const $ref = toValue(referencingElement.$ref);
326
337
  this.handleError(`Error while dereferencing Reference Object. Cannot resolve $ref "${$ref}": ${error.message}`, error, referencingElement, '$ref', $ref, path);
@@ -408,6 +419,7 @@ class OpenAPI2DereferenceVisitor {
408
419
  const replacer = this.options.dereference.strategyOpts['openapi-2']?.circularReplacer ?? this.options.dereference.circularReplacer;
409
420
  const replacement = replacer(refElement);
410
421
  path.replaceWith(replacement);
422
+ path.skip();
411
423
  return;
412
424
  }
413
425
  }
@@ -423,7 +435,11 @@ class OpenAPI2DereferenceVisitor {
423
435
  */
424
436
  const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
425
437
  const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
426
- if ((isExternalReference || isNonEntryDocument || isPathItemElement(referencedElement) && isStringElement(referencedElement.$ref) || shouldDetectCircular) && !ancestorsLineage.includesCycle(referencedElement)) {
438
+ if ((isExternalReference || isNonEntryDocument || isPathItemElement(referencedElement) && isStringElement(referencedElement.$ref) || shouldDetectCircular && !this.visited.has(referencedElement)) && !ancestorsLineage.includesCycle(referencedElement)) {
439
+ if (shouldDetectCircular) {
440
+ this.visited.add(referencedElement);
441
+ }
442
+
427
443
  // append referencing reference to ancestors lineage
428
444
  directAncestors.add(referencingElement);
429
445
  const visitor = new OpenAPI2DereferenceVisitor({
@@ -431,10 +447,12 @@ class OpenAPI2DereferenceVisitor {
431
447
  indirections: [...this.indirections],
432
448
  options: this.options,
433
449
  refractCache: this.refractCache,
450
+ visited: this.visited,
434
451
  ancestors: ancestorsLineage
435
452
  });
436
453
  referencedElement = await traverseAsync(referencedElement, visitor, {
437
- mutable: true
454
+ mutable: true,
455
+ skipVisited: true
438
456
  });
439
457
 
440
458
  // remove referencing reference from ancestors lineage
@@ -562,6 +580,7 @@ class OpenAPI2DereferenceVisitor {
562
580
  const replacer = this.options.dereference.strategyOpts['openapi-2']?.circularReplacer ?? this.options.dereference.circularReplacer;
563
581
  const replacement = replacer(refElement);
564
582
  path.replaceWith(replacement);
583
+ path.skip();
565
584
  return;
566
585
  }
567
586
  }
@@ -577,7 +596,11 @@ class OpenAPI2DereferenceVisitor {
577
596
  */
578
597
  const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
579
598
  const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
580
- if ((isExternalReference || isNonEntryDocument || isJSONReferenceElement(referencedElement) || shouldDetectCircular) && !ancestorsLineage.includesCycle(referencedElement)) {
599
+ if ((isExternalReference || isNonEntryDocument || isJSONReferenceElement(referencedElement) || shouldDetectCircular && !this.visited.has(referencedElement)) && !ancestorsLineage.includesCycle(referencedElement)) {
600
+ if (shouldDetectCircular) {
601
+ this.visited.add(referencedElement);
602
+ }
603
+
581
604
  // append referencing reference to ancestors lineage
582
605
  directAncestors.add(referencingElement);
583
606
  const visitor = new OpenAPI2DereferenceVisitor({
@@ -585,10 +608,12 @@ class OpenAPI2DereferenceVisitor {
585
608
  indirections: [...this.indirections],
586
609
  options: this.options,
587
610
  refractCache: this.refractCache,
611
+ visited: this.visited,
588
612
  ancestors: ancestorsLineage
589
613
  });
590
614
  referencedElement = await traverseAsync(referencedElement, visitor, {
591
- mutable: true
615
+ mutable: true,
616
+ skipVisited: true
592
617
  });
593
618
 
594
619
  // remove referencing reference from ancestors lineage
@@ -611,6 +636,7 @@ class OpenAPI2DereferenceVisitor {
611
636
  * Transclude referencing element with merged referenced element.
612
637
  */
613
638
  path.replaceWith(mergedElement);
639
+ path.skip();
614
640
  } catch (error) {
615
641
  const $ref = toValue(referencingElement.$ref);
616
642
  this.handleError(`Error while dereferencing JSON Reference Object. Cannot resolve $ref "${$ref}": ${error.message}`, error, referencingElement, '$ref', $ref, path);
@@ -64,12 +64,16 @@ class OpenAPI3_0DereferenceStrategy extends _DereferenceStrategy.default {
64
64
  reference = mutableRefSet.find(ref => ref.uri === file.uri);
65
65
  refSet = mutableRefSet;
66
66
  }
67
+ const shouldDetectCircular = ['error', 'replace'].includes(options.dereference.circular);
67
68
  const visitor = new _visitor.default({
68
69
  reference: reference,
69
70
  options
70
71
  });
71
72
  const dereferencedElement = await (0, _apidomTraverse.traverseAsync)(refSet.rootRef.value, visitor, {
72
- mutable: true
73
+ mutable: true,
74
+ ...(shouldDetectCircular && {
75
+ skipVisited: true
76
+ })
73
77
  });
74
78
 
75
79
  /**
@@ -57,12 +57,16 @@ class OpenAPI3_0DereferenceStrategy extends DereferenceStrategy {
57
57
  reference = mutableRefSet.find(ref => ref.uri === file.uri);
58
58
  refSet = mutableRefSet;
59
59
  }
60
+ const shouldDetectCircular = ['error', 'replace'].includes(options.dereference.circular);
60
61
  const visitor = new OpenAPI3_0DereferenceVisitor({
61
62
  reference: reference,
62
63
  options
63
64
  });
64
65
  const dereferencedElement = await traverseAsync(refSet.rootRef.value, visitor, {
65
- mutable: true
66
+ mutable: true,
67
+ ...(shouldDetectCircular && {
68
+ skipVisited: true
69
+ })
66
70
  });
67
71
 
68
72
  /**
@@ -31,6 +31,7 @@ class OpenAPI3_0DereferenceVisitor {
31
31
  reference;
32
32
  options;
33
33
  refractCache;
34
+ visited;
34
35
 
35
36
  /**
36
37
  * Tracks element ancestors across dive-deep traversal boundaries.
@@ -43,13 +44,15 @@ class OpenAPI3_0DereferenceVisitor {
43
44
  options,
44
45
  indirections = [],
45
46
  ancestors = new _util.AncestorLineage(),
46
- refractCache = new WeakMap()
47
+ refractCache = new WeakMap(),
48
+ visited = new WeakSet()
47
49
  }) {
48
50
  this.indirections = indirections;
49
51
  this.reference = reference;
50
52
  this.options = options;
51
53
  this.ancestors = new _util.AncestorLineage(...ancestors);
52
54
  this.refractCache = refractCache;
55
+ this.visited = visited;
53
56
  }
54
57
  toAncestorLineage(path) {
55
58
  const ancestorNodes = path.getAncestorNodes();
@@ -282,6 +285,7 @@ class OpenAPI3_0DereferenceVisitor {
282
285
  const replacer = this.options.dereference.strategyOpts['openapi-3-0']?.circularReplacer ?? this.options.dereference.circularReplacer;
283
286
  const replacement = replacer(refElement);
284
287
  path.replaceWith(replacement);
288
+ path.skip();
285
289
  return;
286
290
  }
287
291
  }
@@ -297,7 +301,11 @@ class OpenAPI3_0DereferenceVisitor {
297
301
  */
298
302
  const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
299
303
  const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
300
- if ((isExternalReference || isNonEntryDocument || (0, _apidomNsOpenapi.isReferenceElement)(referencedElement) || shouldDetectCircular) && !ancestorsLineage.includesCycle(referencedElement)) {
304
+ if ((isExternalReference || isNonEntryDocument || (0, _apidomNsOpenapi.isReferenceElement)(referencedElement) || shouldDetectCircular && !this.visited.has(referencedElement)) && !ancestorsLineage.includesCycle(referencedElement)) {
305
+ if (shouldDetectCircular) {
306
+ this.visited.add(referencedElement);
307
+ }
308
+
301
309
  // append referencing reference to ancestors lineage
302
310
  directAncestors.add(referencingElement);
303
311
  const visitor = new OpenAPI3_0DereferenceVisitor({
@@ -305,10 +313,12 @@ class OpenAPI3_0DereferenceVisitor {
305
313
  indirections: [...this.indirections],
306
314
  options: this.options,
307
315
  refractCache: this.refractCache,
316
+ visited: this.visited,
308
317
  ancestors: ancestorsLineage
309
318
  });
310
319
  referencedElement = await (0, _apidomTraverse.traverseAsync)(referencedElement, visitor, {
311
- mutable: true
320
+ mutable: true,
321
+ skipVisited: true
312
322
  });
313
323
 
314
324
  // remove referencing reference from ancestors lineage
@@ -331,6 +341,7 @@ class OpenAPI3_0DereferenceVisitor {
331
341
  * Transclude referencing element with merged referenced element.
332
342
  */
333
343
  path.replaceWith(mergedElement);
344
+ path.skip();
334
345
  } catch (error) {
335
346
  const $ref = (0, _apidomCore.toValue)(referencingElement.$ref);
336
347
  this.handleError(`Error while dereferencing Reference Object. Cannot resolve $ref "${$ref}": ${error.message}`, error, referencingElement, '$ref', $ref, path);
@@ -418,6 +429,7 @@ class OpenAPI3_0DereferenceVisitor {
418
429
  const replacer = this.options.dereference.strategyOpts['openapi-3-0']?.circularReplacer ?? this.options.dereference.circularReplacer;
419
430
  const replacement = replacer(refElement);
420
431
  path.replaceWith(replacement);
432
+ path.skip();
421
433
  return;
422
434
  }
423
435
  }
@@ -433,7 +445,11 @@ class OpenAPI3_0DereferenceVisitor {
433
445
  */
434
446
  const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
435
447
  const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
436
- if ((isExternalReference || isNonEntryDocument || (0, _apidomNsOpenapi.isPathItemElement)(referencedElement) && (0, _apidomDatamodel.isStringElement)(referencedElement.$ref) || shouldDetectCircular) && !ancestorsLineage.includesCycle(referencedElement)) {
448
+ if ((isExternalReference || isNonEntryDocument || (0, _apidomNsOpenapi.isPathItemElement)(referencedElement) && (0, _apidomDatamodel.isStringElement)(referencedElement.$ref) || shouldDetectCircular && !this.visited.has(referencedElement)) && !ancestorsLineage.includesCycle(referencedElement)) {
449
+ if (shouldDetectCircular) {
450
+ this.visited.add(referencedElement);
451
+ }
452
+
437
453
  // append referencing reference to ancestors lineage
438
454
  directAncestors.add(referencingElement);
439
455
  const visitor = new OpenAPI3_0DereferenceVisitor({
@@ -441,10 +457,12 @@ class OpenAPI3_0DereferenceVisitor {
441
457
  indirections: [...this.indirections],
442
458
  options: this.options,
443
459
  refractCache: this.refractCache,
460
+ visited: this.visited,
444
461
  ancestors: ancestorsLineage
445
462
  });
446
463
  referencedElement = await (0, _apidomTraverse.traverseAsync)(referencedElement, visitor, {
447
- mutable: true
464
+ mutable: true,
465
+ skipVisited: true
448
466
  });
449
467
 
450
468
  // remove referencing reference from ancestors lineage
@@ -24,6 +24,7 @@ class OpenAPI3_0DereferenceVisitor {
24
24
  reference;
25
25
  options;
26
26
  refractCache;
27
+ visited;
27
28
 
28
29
  /**
29
30
  * Tracks element ancestors across dive-deep traversal boundaries.
@@ -36,13 +37,15 @@ class OpenAPI3_0DereferenceVisitor {
36
37
  options,
37
38
  indirections = [],
38
39
  ancestors = new AncestorLineage(),
39
- refractCache = new WeakMap()
40
+ refractCache = new WeakMap(),
41
+ visited = new WeakSet()
40
42
  }) {
41
43
  this.indirections = indirections;
42
44
  this.reference = reference;
43
45
  this.options = options;
44
46
  this.ancestors = new AncestorLineage(...ancestors);
45
47
  this.refractCache = refractCache;
48
+ this.visited = visited;
46
49
  }
47
50
  toAncestorLineage(path) {
48
51
  const ancestorNodes = path.getAncestorNodes();
@@ -275,6 +278,7 @@ class OpenAPI3_0DereferenceVisitor {
275
278
  const replacer = this.options.dereference.strategyOpts['openapi-3-0']?.circularReplacer ?? this.options.dereference.circularReplacer;
276
279
  const replacement = replacer(refElement);
277
280
  path.replaceWith(replacement);
281
+ path.skip();
278
282
  return;
279
283
  }
280
284
  }
@@ -290,7 +294,11 @@ class OpenAPI3_0DereferenceVisitor {
290
294
  */
291
295
  const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
292
296
  const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
293
- if ((isExternalReference || isNonEntryDocument || isReferenceElement(referencedElement) || shouldDetectCircular) && !ancestorsLineage.includesCycle(referencedElement)) {
297
+ if ((isExternalReference || isNonEntryDocument || isReferenceElement(referencedElement) || shouldDetectCircular && !this.visited.has(referencedElement)) && !ancestorsLineage.includesCycle(referencedElement)) {
298
+ if (shouldDetectCircular) {
299
+ this.visited.add(referencedElement);
300
+ }
301
+
294
302
  // append referencing reference to ancestors lineage
295
303
  directAncestors.add(referencingElement);
296
304
  const visitor = new OpenAPI3_0DereferenceVisitor({
@@ -298,10 +306,12 @@ class OpenAPI3_0DereferenceVisitor {
298
306
  indirections: [...this.indirections],
299
307
  options: this.options,
300
308
  refractCache: this.refractCache,
309
+ visited: this.visited,
301
310
  ancestors: ancestorsLineage
302
311
  });
303
312
  referencedElement = await traverseAsync(referencedElement, visitor, {
304
- mutable: true
313
+ mutable: true,
314
+ skipVisited: true
305
315
  });
306
316
 
307
317
  // remove referencing reference from ancestors lineage
@@ -324,6 +334,7 @@ class OpenAPI3_0DereferenceVisitor {
324
334
  * Transclude referencing element with merged referenced element.
325
335
  */
326
336
  path.replaceWith(mergedElement);
337
+ path.skip();
327
338
  } catch (error) {
328
339
  const $ref = toValue(referencingElement.$ref);
329
340
  this.handleError(`Error while dereferencing Reference Object. Cannot resolve $ref "${$ref}": ${error.message}`, error, referencingElement, '$ref', $ref, path);
@@ -411,6 +422,7 @@ class OpenAPI3_0DereferenceVisitor {
411
422
  const replacer = this.options.dereference.strategyOpts['openapi-3-0']?.circularReplacer ?? this.options.dereference.circularReplacer;
412
423
  const replacement = replacer(refElement);
413
424
  path.replaceWith(replacement);
425
+ path.skip();
414
426
  return;
415
427
  }
416
428
  }
@@ -426,7 +438,11 @@ class OpenAPI3_0DereferenceVisitor {
426
438
  */
427
439
  const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
428
440
  const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
429
- if ((isExternalReference || isNonEntryDocument || isPathItemElement(referencedElement) && isStringElement(referencedElement.$ref) || shouldDetectCircular) && !ancestorsLineage.includesCycle(referencedElement)) {
441
+ if ((isExternalReference || isNonEntryDocument || isPathItemElement(referencedElement) && isStringElement(referencedElement.$ref) || shouldDetectCircular && !this.visited.has(referencedElement)) && !ancestorsLineage.includesCycle(referencedElement)) {
442
+ if (shouldDetectCircular) {
443
+ this.visited.add(referencedElement);
444
+ }
445
+
430
446
  // append referencing reference to ancestors lineage
431
447
  directAncestors.add(referencingElement);
432
448
  const visitor = new OpenAPI3_0DereferenceVisitor({
@@ -434,10 +450,12 @@ class OpenAPI3_0DereferenceVisitor {
434
450
  indirections: [...this.indirections],
435
451
  options: this.options,
436
452
  refractCache: this.refractCache,
453
+ visited: this.visited,
437
454
  ancestors: ancestorsLineage
438
455
  });
439
456
  referencedElement = await traverseAsync(referencedElement, visitor, {
440
- mutable: true
457
+ mutable: true,
458
+ skipVisited: true
441
459
  });
442
460
 
443
461
  // remove referencing reference from ancestors lineage
@@ -67,12 +67,16 @@ class OpenAPI3_1DereferenceStrategy extends _DereferenceStrategy.default {
67
67
  reference = mutableRefSet.find(ref => ref.uri === file.uri);
68
68
  refSet = mutableRefSet;
69
69
  }
70
+ const shouldDetectCircular = ['error', 'replace'].includes(options.dereference.circular);
70
71
  const visitor = new _visitor.default({
71
72
  reference: reference,
72
73
  options
73
74
  });
74
75
  const dereferencedElement = await (0, _apidomTraverse.traverseAsync)(refSet.rootRef.value, visitor, {
75
- mutable: true
76
+ mutable: true,
77
+ ...(shouldDetectCircular && {
78
+ skipVisited: true
79
+ })
76
80
  });
77
81
 
78
82
  /**
@@ -56,12 +56,16 @@ class OpenAPI3_1DereferenceStrategy extends DereferenceStrategy {
56
56
  reference = mutableRefSet.find(ref => ref.uri === file.uri);
57
57
  refSet = mutableRefSet;
58
58
  }
59
+ const shouldDetectCircular = ['error', 'replace'].includes(options.dereference.circular);
59
60
  const visitor = new OpenAPI3_1DereferenceVisitor({
60
61
  reference: reference,
61
62
  options
62
63
  });
63
64
  const dereferencedElement = await traverseAsync(refSet.rootRef.value, visitor, {
64
- mutable: true
65
+ mutable: true,
66
+ ...(shouldDetectCircular && {
67
+ skipVisited: true
68
+ })
65
69
  });
66
70
 
67
71
  /**