@speclynx/apidom-reference 5.2.4 → 5.2.5

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 (51) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +30 -11
  3. package/dist/apidom-reference.browser.js +1121 -514
  4. package/dist/apidom-reference.browser.min.js +1 -1
  5. package/package.json +28 -28
  6. package/src/bundle/strategies/arazzo-1/visitor.cjs +105 -22
  7. package/src/bundle/strategies/arazzo-1/visitor.mjs +107 -24
  8. package/src/bundle/strategies/openapi-3-1/visitor.cjs +197 -47
  9. package/src/bundle/strategies/openapi-3-1/visitor.mjs +200 -50
  10. package/src/bundle/util.cjs +57 -2
  11. package/src/bundle/util.mjs +52 -0
  12. package/src/dereference/strategies/arazzo-1/index.cjs +9 -7
  13. package/src/dereference/strategies/arazzo-1/index.mjs +3 -1
  14. package/src/dereference/strategies/arazzo-1/selectors/$anchor.cjs +6 -0
  15. package/src/dereference/strategies/arazzo-1/selectors/$anchor.mjs +1 -1
  16. package/src/dereference/strategies/arazzo-1/selectors/uri.cjs +6 -0
  17. package/src/dereference/strategies/arazzo-1/selectors/uri.mjs +1 -1
  18. package/src/dereference/strategies/arazzo-1/util.cjs +6 -0
  19. package/src/dereference/strategies/arazzo-1/util.mjs +1 -1
  20. package/src/dereference/strategies/arazzo-1/visitor.cjs +67 -9
  21. package/src/dereference/strategies/arazzo-1/visitor.mjs +71 -13
  22. package/src/dereference/strategies/openapi-3-1/index.cjs +2 -1
  23. package/src/dereference/strategies/openapi-3-1/index.mjs +3 -1
  24. package/src/dereference/strategies/openapi-3-1/selectors/$anchor.cjs +16 -4
  25. package/src/dereference/strategies/openapi-3-1/selectors/$anchor.mjs +14 -3
  26. package/src/dereference/strategies/openapi-3-1/selectors/json-pointer.cjs +29 -0
  27. package/src/dereference/strategies/openapi-3-1/selectors/json-pointer.mjs +22 -0
  28. package/src/dereference/strategies/openapi-3-1/selectors/uri.cjs +54 -27
  29. package/src/dereference/strategies/openapi-3-1/selectors/uri.mjs +51 -27
  30. package/src/dereference/strategies/openapi-3-1/util.cjs +75 -14
  31. package/src/dereference/strategies/openapi-3-1/util.mjs +68 -14
  32. package/src/dereference/strategies/openapi-3-1/visitor.cjs +85 -9
  33. package/src/dereference/strategies/openapi-3-1/visitor.mjs +89 -13
  34. package/src/util/url.cjs +35 -2
  35. package/src/util/url.mjs +32 -0
  36. package/types/apidom-reference.d.ts +12 -0
  37. package/types/bundle/strategies/arazzo-1/index.d.ts +1 -1
  38. package/types/bundle/strategies/arazzo-1/visitor.d.ts +51 -6
  39. package/types/bundle/strategies/openapi-3-1/index.d.ts +1 -1
  40. package/types/bundle/strategies/openapi-3-1/visitor.d.ts +59 -3
  41. package/types/bundle/util.d.ts +39 -0
  42. package/types/dereference/strategies/arazzo-1/selectors/$anchor.d.ts +1 -1
  43. package/types/dereference/strategies/arazzo-1/selectors/uri.d.ts +1 -1
  44. package/types/dereference/strategies/arazzo-1/util.d.ts +1 -1
  45. package/types/dereference/strategies/arazzo-1/visitor.d.ts +17 -1
  46. package/types/dereference/strategies/openapi-3-1/selectors/$anchor.d.ts +8 -0
  47. package/types/dereference/strategies/openapi-3-1/selectors/json-pointer.d.ts +9 -0
  48. package/types/dereference/strategies/openapi-3-1/selectors/uri.d.ts +37 -4
  49. package/types/dereference/strategies/openapi-3-1/util.d.ts +53 -2
  50. package/types/dereference/strategies/openapi-3-1/visitor.d.ts +17 -1
  51. package/types/util/url.d.ts +10 -0
@@ -5,7 +5,7 @@ import { isSwaggerElement } from '@speclynx/apidom-ns-openapi-2';
5
5
  import { isOpenApi3_0Element } from '@speclynx/apidom-ns-openapi-3-0';
6
6
  import { isOpenApi3_1Element } from '@speclynx/apidom-ns-openapi-3-1';
7
7
  import * as url from "../../../util/url.mjs";
8
- export { resolveSchema$refField, resolveSchema$idField } from "../openapi-3-1/util.mjs";
8
+ export { resolveSchema$refField, resolveSchema$idField, resolveSchemaBaseURI } from "../openapi-3-1/util.mjs";
9
9
  /**
10
10
  * Computes the base URI of an Arazzo document per Arazzo 1.1.0 rules.
11
11
  *
@@ -16,6 +16,7 @@ var _apidomNsArazzo = require("@speclynx/apidom-ns-arazzo-1");
16
16
  var _arazzoRuntimeExpression = require("@swaggerexpert/arazzo-runtime-expression");
17
17
  var _$anchor = require("./selectors/$anchor.cjs");
18
18
  var _uri = require("./selectors/uri.cjs");
19
+ var _jsonPointer = require("../openapi-3-1/selectors/json-pointer.cjs");
19
20
  var _util = require("../openapi-3-1/util.cjs");
20
21
  var _util2 = require("./util.cjs");
21
22
  var _UnresolvableReferenceError = _interopRequireDefault(require("../../../errors/UnresolvableReferenceError.cjs"));
@@ -45,8 +46,23 @@ class Arazzo1DereferenceVisitor {
45
46
  */
46
47
  baseURI;
47
48
  options;
49
+
50
+ /**
51
+ * Index of `$id`-bearing schemas per document, shared across nested visitors
52
+ * of a single dereference run so it never outlives mutations of the documents.
53
+ */
54
+ schema$idIndex;
48
55
  visited;
49
56
 
57
+ /**
58
+ * `$id`s of the JSON Schema elements enclosing the traversal root within its
59
+ * document (outermost first). Empty when the root is the document itself; a
60
+ * nested visitor traversing a fragment is seeded with the `$id`s of the
61
+ * schemas the fragment sits under, so the `$ref`s and `$id`s within resolve
62
+ * against the same base as they would from the document root.
63
+ */
64
+ ancestorSchema$ids;
65
+
50
66
  /**
51
67
  * Tracks element ancestors across dive-deep traversal boundaries.
52
68
  * Used for cycle detection: if a referenced element is found in
@@ -57,15 +73,19 @@ class Arazzo1DereferenceVisitor {
57
73
  reference,
58
74
  options,
59
75
  indirections = [],
76
+ schema$idIndex = new WeakMap(),
60
77
  ancestors = new _util3.AncestorLineage(),
61
- visited = new WeakSet()
78
+ visited = new WeakSet(),
79
+ ancestorSchema$ids = []
62
80
  }) {
63
81
  this.indirections = indirections;
64
82
  this.reference = reference;
65
83
  this.baseURI = (0, _util2.resolveArazzo$selfField)(reference.uri, reference.value?.result);
66
84
  this.options = options;
85
+ this.schema$idIndex = schema$idIndex;
67
86
  this.ancestors = new _util3.AncestorLineage(...ancestors);
68
87
  this.visited = visited;
88
+ this.ancestorSchema$ids = ancestorSchema$ids;
69
89
  }
70
90
  toAncestorLineage(path) {
71
91
  const ancestorNodes = path.getAncestorNodes();
@@ -307,7 +327,11 @@ class Arazzo1DereferenceVisitor {
307
327
  let {
308
328
  uri: retrievalURI
309
329
  } = reference;
310
- const $refBaseURI = (0, _util.resolveSchema$refField)(this.baseURI, referencingElement);
330
+ // base URI in effect at the traversal root: the document base refined by
331
+ // the $ids of the schemas enclosing the root when the root is a fragment
332
+ const rootBaseURI = (0, _util.resolveSchema$ids)(this.baseURI, this.ancestorSchema$ids);
333
+ const schemaBaseURI = (0, _util.resolveSchemaBaseURI)(rootBaseURI, path);
334
+ const $refBaseURI = (0, _util.resolveSchema$refField)(schemaBaseURI, referencingElement);
311
335
  const $refBaseURIStrippedHash = url.stripHash($refBaseURI);
312
336
  const file = new _File.default({
313
337
  uri: $refBaseURIStrippedHash
@@ -319,13 +343,32 @@ class Arazzo1DereferenceVisitor {
319
343
 
320
344
  // determining reference, proper evaluation and selection mechanism
321
345
  let referencedElement;
346
+ // $ids of the schemas enclosing the referenced element within its document
347
+ let ancestorSchema$ids;
322
348
  try {
323
349
  if (isUnknownURI || isURL) {
324
350
  // we're dealing with canonical URI or URL with possible fragment
325
351
  retrievalURI = this.toBaseURI($refBaseURI);
326
352
  const selector = $refBaseURI;
327
- const referenceAsSchema = (0, _util2.maybeRefractToJSONSchemaElement)(reference.value.result);
328
- referencedElement = (0, _uri.evaluate)(selector, referenceAsSchema);
353
+ const documentElement = reference.value.result;
354
+ const referenceAsSchema = (0, _util2.maybeRefractToJSONSchemaElement)(documentElement);
355
+ // a document that is itself a fragment detached from its source carries
356
+ // the $ids enclosing it only as `ancestorSchema$ids`: its $ids are then
357
+ // indexed against the base that chain leaves in effect, and the chain
358
+ // prefixes the one of the located schema. A nested visitor rooted at a
359
+ // fragment of a whole document indexes that document against its own base.
360
+ const rootNode = path.getAncestorNodes().at(-1) ?? path.node;
361
+ const isDocumentRoot = ((0, _apidomDatamodel.isParseResultElement)(rootNode) ? rootNode.result : rootNode) === documentElement;
362
+ ({
363
+ element: referencedElement,
364
+ ancestorSchema$ids
365
+ } = (0, _uri.locate)(selector, referenceAsSchema, {
366
+ baseURI: isDocumentRoot ? rootBaseURI : this.baseURI,
367
+ index: this.schema$idIndex
368
+ }));
369
+ if (isDocumentRoot) {
370
+ ancestorSchema$ids = [...this.ancestorSchema$ids, ...ancestorSchema$ids];
371
+ }
329
372
  referencedElement = (0, _util2.maybeRefractToJSONSchemaElement)(referencedElement);
330
373
 
331
374
  // ignore resolving internal Schema Objects
@@ -357,7 +400,10 @@ class Arazzo1DereferenceVisitor {
357
400
  reference = await this.toReference(url.unsanitize($refBaseURI));
358
401
  const selector = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference($refBaseURI);
359
402
  const referenceAsSchema = (0, _util2.maybeRefractToJSONSchemaElement)(reference.value.result);
360
- referencedElement = (0, _apidomJsonPointer.evaluate)(referenceAsSchema, selector);
403
+ ({
404
+ element: referencedElement,
405
+ ancestorSchema$ids
406
+ } = (0, _jsonPointer.locate)(referenceAsSchema, selector));
361
407
  referencedElement = (0, _util2.maybeRefractToJSONSchemaElement)(referencedElement);
362
408
  }
363
409
  } catch (error) {
@@ -384,7 +430,10 @@ class Arazzo1DereferenceVisitor {
384
430
  reference = await this.toReference(url.unsanitize($refBaseURI));
385
431
  const selector = (0, _$anchor.uriToAnchor)($refBaseURI);
386
432
  const referenceAsSchema = (0, _util2.maybeRefractToJSONSchemaElement)(reference.value.result);
387
- referencedElement = (0, _$anchor.evaluate)(selector, referenceAsSchema);
433
+ ({
434
+ element: referencedElement,
435
+ ancestorSchema$ids
436
+ } = (0, _$anchor.locate)(selector, referenceAsSchema));
388
437
  referencedElement = (0, _util2.maybeRefractToJSONSchemaElement)(referencedElement);
389
438
  } else {
390
439
  // we're assuming here that we're dealing with JSON Pointer here
@@ -405,7 +454,10 @@ class Arazzo1DereferenceVisitor {
405
454
  reference = await this.toReference(url.unsanitize($refBaseURI));
406
455
  const selector = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference($refBaseURI);
407
456
  const referenceAsSchema = (0, _util2.maybeRefractToJSONSchemaElement)(reference.value.result);
408
- referencedElement = (0, _apidomJsonPointer.evaluate)(referenceAsSchema, selector);
457
+ ({
458
+ element: referencedElement,
459
+ ancestorSchema$ids
460
+ } = (0, _jsonPointer.locate)(referenceAsSchema, selector));
409
461
  referencedElement = (0, _util2.maybeRefractToJSONSchemaElement)(referencedElement);
410
462
  }
411
463
  } else {
@@ -462,7 +514,11 @@ class Arazzo1DereferenceVisitor {
462
514
  */
463
515
  const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
464
516
  const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
465
- if ((isExternalReference || isNonEntryDocument || (0, _apidomNsArazzo.isJSONSchemaElement)(referencedElement) && (0, _apidomDatamodel.isStringElement)(referencedElement.$ref) || shouldDetectCircular && !this.visited.has(referencedElement)) && !ancestorsLineage.includesCycle(referencedElement)) {
517
+ // content transplanted into the referencing site is traversed under that
518
+ // site's $id chain; a fragment enclosed by a different chain has its own
519
+ // $refs resolved in place first, by a nested visitor seeded with that chain
520
+ const hasDifferentEnclosingBase = (0, _util.resolveSchema$ids)((0, _util2.resolveArazzo$selfField)(reference.uri, reference.value.result), ancestorSchema$ids) !== (0, _util.resolveSchema$ids)(rootBaseURI, (0, _util.schema$idsOf)(path.getAncestorNodes().reverse()));
521
+ if ((isExternalReference || isNonEntryDocument || hasDifferentEnclosingBase || (0, _apidomNsArazzo.isJSONSchemaElement)(referencedElement) && (0, _apidomDatamodel.isStringElement)(referencedElement.$ref) || shouldDetectCircular && !this.visited.has(referencedElement)) && !ancestorsLineage.includesCycle(referencedElement)) {
466
522
  if (shouldDetectCircular) {
467
523
  this.visited.add(referencedElement);
468
524
  }
@@ -473,8 +529,10 @@ class Arazzo1DereferenceVisitor {
473
529
  reference,
474
530
  indirections: [...this.indirections],
475
531
  options: this.options,
532
+ schema$idIndex: this.schema$idIndex,
476
533
  visited: this.visited,
477
- ancestors: ancestorsLineage
534
+ ancestors: ancestorsLineage,
535
+ ancestorSchema$ids
478
536
  });
479
537
  referencedElement = await (0, _apidomTraverse.traverseAsync)(referencedElement, visitor, {
480
538
  mutable: true,
@@ -1,14 +1,15 @@
1
1
  import { propEq, none } from 'ramda';
2
- import { isElement, isStringElement, RefElement, cloneShallow, cloneDeep } from '@speclynx/apidom-datamodel';
2
+ import { isElement, isStringElement, isParseResultElement, RefElement, cloneShallow, cloneDeep } from '@speclynx/apidom-datamodel';
3
3
  import { toValue, toYAML } from '@speclynx/apidom-core';
4
4
  import { ApiDOMStructuredError } from '@speclynx/apidom-error';
5
5
  import { traverse, traverseAsync } from '@speclynx/apidom-traverse';
6
6
  import { evaluate as jsonPointerEvaluate, compile as jsonPointerCompile, URIFragmentIdentifier } from '@speclynx/apidom-json-pointer';
7
7
  import { isParameterElement, isJSONSchemaElement, isBooleanJSONSchemaElement } from '@speclynx/apidom-ns-arazzo-1';
8
8
  import { parse as parseRuntimeExpression } from '@swaggerexpert/arazzo-runtime-expression';
9
- import { isAnchor, uriToAnchor, evaluate as $anchorEvaluate } from "./selectors/$anchor.mjs";
10
- import { evaluate as uriEvaluate } from "./selectors/uri.mjs";
11
- import { resolveSchema$refField } from "../openapi-3-1/util.mjs";
9
+ import { isAnchor, uriToAnchor, locate as $anchorLocate } from "./selectors/$anchor.mjs";
10
+ import { locate as uriLocate } from "./selectors/uri.mjs";
11
+ import { locate as jsonPointerLocate } from "../openapi-3-1/selectors/json-pointer.mjs";
12
+ import { schema$idsOf, resolveSchema$ids, resolveSchemaBaseURI, resolveSchema$refField } from "../openapi-3-1/util.mjs";
12
13
  import { maybeRefractToJSONSchemaElement, resolveArazzo$selfField, identifiedBy$self } from "./util.mjs";
13
14
  import UnresolvableReferenceError from "../../../errors/UnresolvableReferenceError.mjs";
14
15
  import MaximumDereferenceDepthError from "../../../errors/MaximumDereferenceDepthError.mjs";
@@ -36,8 +37,23 @@ class Arazzo1DereferenceVisitor {
36
37
  */
37
38
  baseURI;
38
39
  options;
40
+
41
+ /**
42
+ * Index of `$id`-bearing schemas per document, shared across nested visitors
43
+ * of a single dereference run so it never outlives mutations of the documents.
44
+ */
45
+ schema$idIndex;
39
46
  visited;
40
47
 
48
+ /**
49
+ * `$id`s of the JSON Schema elements enclosing the traversal root within its
50
+ * document (outermost first). Empty when the root is the document itself; a
51
+ * nested visitor traversing a fragment is seeded with the `$id`s of the
52
+ * schemas the fragment sits under, so the `$ref`s and `$id`s within resolve
53
+ * against the same base as they would from the document root.
54
+ */
55
+ ancestorSchema$ids;
56
+
41
57
  /**
42
58
  * Tracks element ancestors across dive-deep traversal boundaries.
43
59
  * Used for cycle detection: if a referenced element is found in
@@ -48,15 +64,19 @@ class Arazzo1DereferenceVisitor {
48
64
  reference,
49
65
  options,
50
66
  indirections = [],
67
+ schema$idIndex = new WeakMap(),
51
68
  ancestors = new AncestorLineage(),
52
- visited = new WeakSet()
69
+ visited = new WeakSet(),
70
+ ancestorSchema$ids = []
53
71
  }) {
54
72
  this.indirections = indirections;
55
73
  this.reference = reference;
56
74
  this.baseURI = resolveArazzo$selfField(reference.uri, reference.value?.result);
57
75
  this.options = options;
76
+ this.schema$idIndex = schema$idIndex;
58
77
  this.ancestors = new AncestorLineage(...ancestors);
59
78
  this.visited = visited;
79
+ this.ancestorSchema$ids = ancestorSchema$ids;
60
80
  }
61
81
  toAncestorLineage(path) {
62
82
  const ancestorNodes = path.getAncestorNodes();
@@ -298,7 +318,11 @@ class Arazzo1DereferenceVisitor {
298
318
  let {
299
319
  uri: retrievalURI
300
320
  } = reference;
301
- const $refBaseURI = resolveSchema$refField(this.baseURI, referencingElement);
321
+ // base URI in effect at the traversal root: the document base refined by
322
+ // the $ids of the schemas enclosing the root when the root is a fragment
323
+ const rootBaseURI = resolveSchema$ids(this.baseURI, this.ancestorSchema$ids);
324
+ const schemaBaseURI = resolveSchemaBaseURI(rootBaseURI, path);
325
+ const $refBaseURI = resolveSchema$refField(schemaBaseURI, referencingElement);
302
326
  const $refBaseURIStrippedHash = url.stripHash($refBaseURI);
303
327
  const file = new File({
304
328
  uri: $refBaseURIStrippedHash
@@ -310,13 +334,32 @@ class Arazzo1DereferenceVisitor {
310
334
 
311
335
  // determining reference, proper evaluation and selection mechanism
312
336
  let referencedElement;
337
+ // $ids of the schemas enclosing the referenced element within its document
338
+ let ancestorSchema$ids;
313
339
  try {
314
340
  if (isUnknownURI || isURL) {
315
341
  // we're dealing with canonical URI or URL with possible fragment
316
342
  retrievalURI = this.toBaseURI($refBaseURI);
317
343
  const selector = $refBaseURI;
318
- const referenceAsSchema = maybeRefractToJSONSchemaElement(reference.value.result);
319
- referencedElement = uriEvaluate(selector, referenceAsSchema);
344
+ const documentElement = reference.value.result;
345
+ const referenceAsSchema = maybeRefractToJSONSchemaElement(documentElement);
346
+ // a document that is itself a fragment detached from its source carries
347
+ // the $ids enclosing it only as `ancestorSchema$ids`: its $ids are then
348
+ // indexed against the base that chain leaves in effect, and the chain
349
+ // prefixes the one of the located schema. A nested visitor rooted at a
350
+ // fragment of a whole document indexes that document against its own base.
351
+ const rootNode = path.getAncestorNodes().at(-1) ?? path.node;
352
+ const isDocumentRoot = (isParseResultElement(rootNode) ? rootNode.result : rootNode) === documentElement;
353
+ ({
354
+ element: referencedElement,
355
+ ancestorSchema$ids
356
+ } = uriLocate(selector, referenceAsSchema, {
357
+ baseURI: isDocumentRoot ? rootBaseURI : this.baseURI,
358
+ index: this.schema$idIndex
359
+ }));
360
+ if (isDocumentRoot) {
361
+ ancestorSchema$ids = [...this.ancestorSchema$ids, ...ancestorSchema$ids];
362
+ }
320
363
  referencedElement = maybeRefractToJSONSchemaElement(referencedElement);
321
364
 
322
365
  // ignore resolving internal Schema Objects
@@ -348,7 +391,10 @@ class Arazzo1DereferenceVisitor {
348
391
  reference = await this.toReference(url.unsanitize($refBaseURI));
349
392
  const selector = URIFragmentIdentifier.fromURIReference($refBaseURI);
350
393
  const referenceAsSchema = maybeRefractToJSONSchemaElement(reference.value.result);
351
- referencedElement = jsonPointerEvaluate(referenceAsSchema, selector);
394
+ ({
395
+ element: referencedElement,
396
+ ancestorSchema$ids
397
+ } = jsonPointerLocate(referenceAsSchema, selector));
352
398
  referencedElement = maybeRefractToJSONSchemaElement(referencedElement);
353
399
  }
354
400
  } catch (error) {
@@ -375,7 +421,10 @@ class Arazzo1DereferenceVisitor {
375
421
  reference = await this.toReference(url.unsanitize($refBaseURI));
376
422
  const selector = uriToAnchor($refBaseURI);
377
423
  const referenceAsSchema = maybeRefractToJSONSchemaElement(reference.value.result);
378
- referencedElement = $anchorEvaluate(selector, referenceAsSchema);
424
+ ({
425
+ element: referencedElement,
426
+ ancestorSchema$ids
427
+ } = $anchorLocate(selector, referenceAsSchema));
379
428
  referencedElement = maybeRefractToJSONSchemaElement(referencedElement);
380
429
  } else {
381
430
  // we're assuming here that we're dealing with JSON Pointer here
@@ -396,7 +445,10 @@ class Arazzo1DereferenceVisitor {
396
445
  reference = await this.toReference(url.unsanitize($refBaseURI));
397
446
  const selector = URIFragmentIdentifier.fromURIReference($refBaseURI);
398
447
  const referenceAsSchema = maybeRefractToJSONSchemaElement(reference.value.result);
399
- referencedElement = jsonPointerEvaluate(referenceAsSchema, selector);
448
+ ({
449
+ element: referencedElement,
450
+ ancestorSchema$ids
451
+ } = jsonPointerLocate(referenceAsSchema, selector));
400
452
  referencedElement = maybeRefractToJSONSchemaElement(referencedElement);
401
453
  }
402
454
  } else {
@@ -453,7 +505,11 @@ class Arazzo1DereferenceVisitor {
453
505
  */
454
506
  const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
455
507
  const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
456
- if ((isExternalReference || isNonEntryDocument || isJSONSchemaElement(referencedElement) && isStringElement(referencedElement.$ref) || shouldDetectCircular && !this.visited.has(referencedElement)) && !ancestorsLineage.includesCycle(referencedElement)) {
508
+ // content transplanted into the referencing site is traversed under that
509
+ // site's $id chain; a fragment enclosed by a different chain has its own
510
+ // $refs resolved in place first, by a nested visitor seeded with that chain
511
+ const hasDifferentEnclosingBase = resolveSchema$ids(resolveArazzo$selfField(reference.uri, reference.value.result), ancestorSchema$ids) !== resolveSchema$ids(rootBaseURI, schema$idsOf(path.getAncestorNodes().reverse()));
512
+ if ((isExternalReference || isNonEntryDocument || hasDifferentEnclosingBase || isJSONSchemaElement(referencedElement) && isStringElement(referencedElement.$ref) || shouldDetectCircular && !this.visited.has(referencedElement)) && !ancestorsLineage.includesCycle(referencedElement)) {
457
513
  if (shouldDetectCircular) {
458
514
  this.visited.add(referencedElement);
459
515
  }
@@ -464,8 +520,10 @@ class Arazzo1DereferenceVisitor {
464
520
  reference,
465
521
  indirections: [...this.indirections],
466
522
  options: this.options,
523
+ schema$idIndex: this.schema$idIndex,
467
524
  visited: this.visited,
468
- ancestors: ancestorsLineage
525
+ ancestors: ancestorsLineage,
526
+ ancestorSchema$ids
469
527
  });
470
528
  referencedElement = await traverseAsync(referencedElement, visitor, {
471
529
  mutable: true,
@@ -92,7 +92,8 @@ class OpenAPI3_1DereferenceStrategy extends _DereferenceStrategy.default {
92
92
  const shouldDetectCircular = ['error', 'replace'].includes(options.dereference.circular);
93
93
  const visitor = new _visitor.default({
94
94
  reference: reference,
95
- options
95
+ options,
96
+ ancestorSchema$ids: (0, _util.detachedRootSchema$ids)(refSet.rootRef.value.result)
96
97
  });
97
98
  const dereferencedElement = await (0, _apidomTraverse.traverseAsync)(refSet.rootRef.value, visitor, {
98
99
  mutable: true,
@@ -6,6 +6,7 @@ import DereferenceStrategy from "../DereferenceStrategy.mjs";
6
6
  import Reference from "../../../Reference.mjs";
7
7
  import ReferenceSet from "../../../ReferenceSet.mjs";
8
8
  import OpenAPI3_1DereferenceVisitor from "./visitor.mjs";
9
+ import { detachedRootSchema$ids } from "./util.mjs";
9
10
  /**
10
11
  * @public
11
12
  */
@@ -59,7 +60,8 @@ class OpenAPI3_1DereferenceStrategy extends DereferenceStrategy {
59
60
  const shouldDetectCircular = ['error', 'replace'].includes(options.dereference.circular);
60
61
  const visitor = new OpenAPI3_1DereferenceVisitor({
61
62
  reference: reference,
62
- options
63
+ options,
64
+ ancestorSchema$ids: detachedRootSchema$ids(refSet.rootRef.value.result)
63
65
  });
64
66
  const dereferencedElement = await traverseAsync(refSet.rootRef.value, visitor, {
65
67
  mutable: true,
@@ -22,12 +22,13 @@ Object.defineProperty(exports, "JsonSchema$anchorError", {
22
22
  return _JsonSchema$anchorError.default;
23
23
  }
24
24
  });
25
- exports.uriToAnchor = exports.parse = exports.isAnchor = exports.evaluate = void 0;
25
+ exports.uriToAnchor = exports.parse = exports.locate = exports.isAnchor = exports.evaluate = void 0;
26
26
  var _ramdaAdjunct = require("ramda-adjunct");
27
27
  var _apidomCore = require("@speclynx/apidom-core");
28
28
  var _apidomTraverse = require("@speclynx/apidom-traverse");
29
29
  var _apidomNsJsonSchema = require("@speclynx/apidom-ns-json-schema-2020-12");
30
30
  var _url = require("../../../../util/url.cjs");
31
+ var _util = require("../util.cjs");
31
32
  var _EvaluationJsonSchema$anchorError = _interopRequireDefault(require("../../../../errors/EvaluationJsonSchema$anchorError.cjs"));
32
33
  var _InvalidJsonSchema$anchorError = _interopRequireDefault(require("../../../../errors/InvalidJsonSchema$anchorError.cjs"));
33
34
  var _JsonSchema$anchorError = _interopRequireDefault(require("../../../../errors/JsonSchema$anchorError.cjs"));
@@ -65,11 +66,12 @@ const parse = anchor => {
65
66
  };
66
67
 
67
68
  /**
68
- * Evaluates JSON Schema $anchor against ApiDOM fragment.
69
+ * Locates the schema identified by JSON Schema $anchor within ApiDOM fragment,
70
+ * along with the `$id`s of the schemas enclosing it, the search root included.
69
71
  * @public
70
72
  */
71
73
  exports.parse = parse;
72
- const evaluate = (anchor, element) => {
74
+ const locate = (anchor, element) => {
73
75
  const token = parse(anchor);
74
76
  const resultPath = (0, _apidomTraverse.find)(element,
75
77
  // @ts-ignore
@@ -77,6 +79,16 @@ const evaluate = (anchor, element) => {
77
79
  if ((0, _ramdaAdjunct.isUndefined)(resultPath)) {
78
80
  throw new _EvaluationJsonSchema$anchorError.default(`Evaluation failed on token: "${token}"`);
79
81
  }
80
- return resultPath.node;
82
+ return {
83
+ element: resultPath.node,
84
+ ancestorSchema$ids: (0, _util.schema$idsOf)(resultPath.getAncestorNodes().reverse())
85
+ };
81
86
  };
87
+
88
+ /**
89
+ * Evaluates JSON Schema $anchor against ApiDOM fragment.
90
+ * @public
91
+ */
92
+ exports.locate = locate;
93
+ const evaluate = (anchor, element) => locate(anchor, element).element;
82
94
  exports.evaluate = evaluate;
@@ -3,6 +3,7 @@ import { toValue } from '@speclynx/apidom-core';
3
3
  import { find } from '@speclynx/apidom-traverse';
4
4
  import { isJSONSchemaElement } from '@speclynx/apidom-ns-json-schema-2020-12';
5
5
  import { getHash } from "../../../../util/url.mjs";
6
+ import { schema$idsOf } from "../util.mjs";
6
7
  import EvaluationJsonSchema$anchorError from "../../../../errors/EvaluationJsonSchema$anchorError.mjs";
7
8
  import InvalidJsonSchema$anchorError from "../../../../errors/InvalidJsonSchema$anchorError.mjs";
8
9
  /**
@@ -37,10 +38,11 @@ export const parse = anchor => {
37
38
  };
38
39
 
39
40
  /**
40
- * Evaluates JSON Schema $anchor against ApiDOM fragment.
41
+ * Locates the schema identified by JSON Schema $anchor within ApiDOM fragment,
42
+ * along with the `$id`s of the schemas enclosing it, the search root included.
41
43
  * @public
42
44
  */
43
- export const evaluate = (anchor, element) => {
45
+ export const locate = (anchor, element) => {
44
46
  const token = parse(anchor);
45
47
  const resultPath = find(element,
46
48
  // @ts-ignore
@@ -48,7 +50,16 @@ export const evaluate = (anchor, element) => {
48
50
  if (isUndefined(resultPath)) {
49
51
  throw new EvaluationJsonSchema$anchorError(`Evaluation failed on token: "${token}"`);
50
52
  }
51
- return resultPath.node;
53
+ return {
54
+ element: resultPath.node,
55
+ ancestorSchema$ids: schema$idsOf(resultPath.getAncestorNodes().reverse())
56
+ };
52
57
  };
58
+
59
+ /**
60
+ * Evaluates JSON Schema $anchor against ApiDOM fragment.
61
+ * @public
62
+ */
63
+ export const evaluate = (anchor, element) => locate(anchor, element).element;
53
64
  export { EvaluationJsonSchema$anchorError, InvalidJsonSchema$anchorError };
54
65
  export { default as JsonSchema$anchorError } from "../../../../errors/JsonSchema$anchorError.mjs";
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.locate = void 0;
7
+ var _apidomJsonPointer = require("@speclynx/apidom-json-pointer");
8
+ var _util = require("../util.cjs");
9
+ /**
10
+ * Locates the element addressed by JSON Pointer within ApiDOM fragment, along
11
+ * with the `$id`s of the schemas enclosing it: the elements the pointer walks
12
+ * through on its way to the target, the search root included.
13
+ */
14
+ const locate = (element, jsonPointer) => {
15
+ const trace = {
16
+ steps: []
17
+ };
18
+ const result = (0, _apidomJsonPointer.evaluate)(element, jsonPointer, {
19
+ trace
20
+ });
21
+
22
+ // each step's input is the node the token was applied to, so the inputs are
23
+ // exactly the walked nodes minus the target
24
+ return {
25
+ element: result,
26
+ ancestorSchema$ids: (0, _util.schema$idsOf)(trace.steps.map(step => step.input))
27
+ };
28
+ };
29
+ exports.locate = locate;
@@ -0,0 +1,22 @@
1
+ import { evaluate as jsonPointerEvaluate } from '@speclynx/apidom-json-pointer';
2
+ import { schema$idsOf } from "../util.mjs";
3
+ /**
4
+ * Locates the element addressed by JSON Pointer within ApiDOM fragment, along
5
+ * with the `$id`s of the schemas enclosing it: the elements the pointer walks
6
+ * through on its way to the target, the search root included.
7
+ */
8
+ export const locate = (element, jsonPointer) => {
9
+ const trace = {
10
+ steps: []
11
+ };
12
+ const result = jsonPointerEvaluate(element, jsonPointer, {
13
+ trace
14
+ });
15
+
16
+ // each step's input is the node the token was applied to, so the inputs are
17
+ // exactly the walked nodes minus the target
18
+ return {
19
+ element: result,
20
+ ancestorSchema$ids: schema$idsOf(trace.steps.map(step => step.input))
21
+ };
22
+ };
@@ -17,46 +17,73 @@ Object.defineProperty(exports, "JsonSchemaUriError", {
17
17
  return _JsonSchemaUriError.default;
18
18
  }
19
19
  });
20
- exports.evaluate = void 0;
20
+ exports.locate = exports.evaluate = void 0;
21
21
  var _ramdaAdjunct = require("ramda-adjunct");
22
22
  var _apidomTraverse = require("@speclynx/apidom-traverse");
23
- var _apidomNsJsonSchema = require("@speclynx/apidom-ns-json-schema-2020-12");
24
23
  var _apidomJsonPointer = require("@speclynx/apidom-json-pointer");
25
24
  var url = _interopRequireWildcard(require("../../../../util/url.cjs"));
26
25
  var _EvaluationJsonSchemaUriError = _interopRequireDefault(require("../../../../errors/EvaluationJsonSchemaUriError.cjs"));
27
- var _$anchor = require("./$anchor.cjs");
28
26
  var _util = require("../util.cjs");
27
+ var _$anchor = require("./$anchor.cjs");
28
+ var _jsonPointer = require("./json-pointer.cjs");
29
29
  var _JsonSchemaUriError = _interopRequireDefault(require("../../../../errors/JsonSchemaUriError.cjs"));
30
30
  /**
31
- * Evaluates JSON Schema $ref containing unknown URI against ApiDOM fragment.
31
+ * Index of `$id`-bearing schemas per indexed element (a document root, or the
32
+ * fragment a visitor traverses). Each entry pairs a schema with the `$id`s of its
33
+ * enclosing schema resources (outermost first, including its own). The index is
34
+ * populated once per element and never refreshed, so callers scope it
35
+ * to a single dereference/bundle run instead of sharing it globally. Within a run the
36
+ * `$id` graph of an indexed document is stable: the bundle strategies mutate deep
37
+ * clones and place components only after the entry traversal ends, and the
38
+ * dereference strategies only replace `$ref`-bearing schemas.
32
39
  * @public
33
40
  */
34
- const evaluate = (uri, element) => {
35
- const {
36
- cache
37
- } = evaluate;
38
- const uriStrippedHash = url.stripHash(uri);
39
- const isJSONSchemaElementWith$id = path => (0, _apidomNsJsonSchema.isJSONSchemaElement)(path.node) && typeof path.node.$id !== 'undefined';
40
41
 
41
- // warm the cache
42
- if (!cache.has(element)) {
43
- const schemaObjectPaths = (0, _apidomTraverse.filter)(element, isJSONSchemaElementWith$id);
44
- cache.set(element, schemaObjectPaths.map(path => path.node));
42
+ /**
43
+ * @public
44
+ */
45
+
46
+ const indexSchema$ids = element => (0, _apidomTraverse.filter)(element, path => (0, _util.isJSONSchemaElementWith$id)(path.node)).map(path => ({
47
+ schema: path.node,
48
+ $ids: (0, _util.collectSchema$ids)(path)
49
+ }));
50
+
51
+ /**
52
+ * Locates the schema addressed by JSON Schema $ref containing unknown URI within
53
+ * ApiDOM fragment, along with the `$id`s of the schemas enclosing it.
54
+ * @public
55
+ */
56
+ const locate = (uri, element, options = {}) => {
57
+ const uriStrippedHash = url.stripHash(uri);
58
+ const {
59
+ baseURI = uriStrippedHash,
60
+ index = new WeakMap()
61
+ } = options;
62
+ if (!index.has(element)) {
63
+ index.set(element, indexSchema$ids(element));
45
64
  }
46
65
 
47
- // search for the matching schema
48
- const result = cache.get(element).find(e => {
49
- const $idBaseURI = (0, _util.resolveSchema$idField)(uriStrippedHash, e);
50
- return $idBaseURI === uriStrippedHash;
51
- });
52
- if ((0, _ramdaAdjunct.isUndefined)(result)) {
66
+ // search for the schema whose canonical URI matches
67
+ const entry = index.get(element).find(({
68
+ $ids
69
+ }) => (0, _util.resolveSchema$ids)(baseURI, $ids) === uriStrippedHash);
70
+ if ((0, _ramdaAdjunct.isUndefined)(entry)) {
53
71
  throw new _EvaluationJsonSchemaUriError.default(`Evaluation failed on URI: "${uri}"`);
54
72
  }
55
- if ((0, _$anchor.isAnchor)((0, _$anchor.uriToAnchor)(uri))) {
56
- // we're dealing with JSON Schema $anchor here
57
- return (0, _$anchor.evaluate)((0, _$anchor.uriToAnchor)(uri), result);
58
- }
59
- return (0, _apidomJsonPointer.evaluate)(result, _apidomJsonPointer.URIFragmentIdentifier.fromURIReference(uri));
73
+
74
+ // the fragment is resolved within the matched schema, whose own $id is the
75
+ // last of its chain; the fragment walk contributes it again when descending
76
+ const fragmentLocation = (0, _$anchor.isAnchor)((0, _$anchor.uriToAnchor)(uri)) ? (0, _$anchor.locate)((0, _$anchor.uriToAnchor)(uri), entry.schema) : (0, _jsonPointer.locate)(entry.schema, _apidomJsonPointer.URIFragmentIdentifier.fromURIReference(uri));
77
+ return {
78
+ element: fragmentLocation.element,
79
+ ancestorSchema$ids: [...entry.$ids.slice(0, -1), ...fragmentLocation.ancestorSchema$ids]
80
+ };
60
81
  };
61
- exports.evaluate = evaluate;
62
- evaluate.cache = new WeakMap();
82
+
83
+ /**
84
+ * Evaluates JSON Schema $ref containing unknown URI against ApiDOM fragment.
85
+ * @public
86
+ */
87
+ exports.locate = locate;
88
+ const evaluate = (uri, element, options = {}) => locate(uri, element, options).element;
89
+ exports.evaluate = evaluate;