@speclynx/apidom-reference 5.2.4 → 5.2.6

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 (53) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +37 -14
  3. package/dist/apidom-reference.browser.js +1234 -596
  4. package/dist/apidom-reference.browser.min.js +1 -1
  5. package/package.json +29 -29
  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 +20 -17
  13. package/src/dereference/strategies/arazzo-1/index.mjs +14 -11
  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/source-descriptions.cjs +65 -42
  19. package/src/dereference/strategies/arazzo-1/source-descriptions.mjs +64 -42
  20. package/src/dereference/strategies/arazzo-1/util.cjs +6 -0
  21. package/src/dereference/strategies/arazzo-1/util.mjs +1 -1
  22. package/src/dereference/strategies/arazzo-1/visitor.cjs +97 -35
  23. package/src/dereference/strategies/arazzo-1/visitor.mjs +101 -39
  24. package/src/dereference/strategies/openapi-3-1/index.cjs +2 -1
  25. package/src/dereference/strategies/openapi-3-1/index.mjs +3 -1
  26. package/src/dereference/strategies/openapi-3-1/selectors/$anchor.cjs +16 -4
  27. package/src/dereference/strategies/openapi-3-1/selectors/$anchor.mjs +14 -3
  28. package/src/dereference/strategies/openapi-3-1/selectors/json-pointer.cjs +29 -0
  29. package/src/dereference/strategies/openapi-3-1/selectors/json-pointer.mjs +22 -0
  30. package/src/dereference/strategies/openapi-3-1/selectors/uri.cjs +54 -27
  31. package/src/dereference/strategies/openapi-3-1/selectors/uri.mjs +51 -27
  32. package/src/dereference/strategies/openapi-3-1/util.cjs +75 -14
  33. package/src/dereference/strategies/openapi-3-1/util.mjs +68 -14
  34. package/src/dereference/strategies/openapi-3-1/visitor.cjs +85 -9
  35. package/src/dereference/strategies/openapi-3-1/visitor.mjs +89 -13
  36. package/src/util/url.cjs +35 -2
  37. package/src/util/url.mjs +32 -0
  38. package/types/apidom-reference.d.ts +12 -0
  39. package/types/bundle/strategies/arazzo-1/index.d.ts +1 -1
  40. package/types/bundle/strategies/arazzo-1/visitor.d.ts +51 -6
  41. package/types/bundle/strategies/openapi-3-1/index.d.ts +1 -1
  42. package/types/bundle/strategies/openapi-3-1/visitor.d.ts +59 -3
  43. package/types/bundle/util.d.ts +39 -0
  44. package/types/dereference/strategies/arazzo-1/selectors/$anchor.d.ts +1 -1
  45. package/types/dereference/strategies/arazzo-1/selectors/uri.d.ts +1 -1
  46. package/types/dereference/strategies/arazzo-1/util.d.ts +1 -1
  47. package/types/dereference/strategies/arazzo-1/visitor.d.ts +17 -1
  48. package/types/dereference/strategies/openapi-3-1/selectors/$anchor.d.ts +8 -0
  49. package/types/dereference/strategies/openapi-3-1/selectors/json-pointer.d.ts +9 -0
  50. package/types/dereference/strategies/openapi-3-1/selectors/uri.d.ts +37 -4
  51. package/types/dereference/strategies/openapi-3-1/util.d.ts +53 -2
  52. package/types/dereference/strategies/openapi-3-1/visitor.d.ts +17 -1
  53. package/types/util/url.d.ts +10 -0
@@ -4,39 +4,100 @@ var _interopRequireWildcard = require("@babel/runtime-corejs3/helpers/interopReq
4
4
  Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
- exports.resolveSchema$refField = exports.resolveSchema$idField = exports.refractToSchemaElement = exports.maybeRefractToSchemaElement = void 0;
7
+ exports.schema$idsOf = exports.resolveSchemaBaseURI = exports.resolveSchema$refField = exports.resolveSchema$ids = exports.resolveSchema$idField = exports.refractToSchemaElement = exports.maybeRefractToSchemaElement = exports.isJSONSchemaElementWith$id = exports.detachedRootSchema$ids = exports.collectSchema$ids = void 0;
8
8
  var _ramda = require("ramda");
9
+ var _ramdaAdjunct = require("ramda-adjunct");
9
10
  var _apidomDatamodel = require("@speclynx/apidom-datamodel");
10
11
  var _apidomCore = require("@speclynx/apidom-core");
12
+ var _apidomNsJsonSchema = require("@speclynx/apidom-ns-json-schema-2020-12");
11
13
  var _apidomNsOpenapi = require("@speclynx/apidom-ns-openapi-3-1");
12
14
  var url = _interopRequireWildcard(require("../../../util/url.cjs"));
13
15
  /**
16
+ * A schema located within a document, together with the `$id`s of the JSON Schema
17
+ * elements enclosing it (outermost first; the located schema's own `$id` excluded).
14
18
  * @public
15
19
  */
16
- const resolveSchema$refField = (retrievalURI, schemaElement) => {
17
- if (typeof schemaElement.$ref === 'undefined') {
20
+
21
+ /**
22
+ * Whether the element is a JSON Schema element declaring a `$id`.
23
+ */
24
+ const isJSONSchemaElementWith$id = element => (0, _apidomNsJsonSchema.isJSONSchemaElement)(element) && (0, _apidomDatamodel.isStringElement)(element.$id);
25
+
26
+ /**
27
+ * `$id`s declared by the JSON Schema elements among `nodes`, in order.
28
+ */
29
+ exports.isJSONSchemaElementWith$id = isJSONSchemaElementWith$id;
30
+ const schema$idsOf = nodes => nodes.filter(isJSONSchemaElementWith$id).map(node => (0, _apidomCore.toValue)(node.$id));
31
+
32
+ /**
33
+ * `$id`s of the JSON Schema elements on `path` (outermost first, the node itself
34
+ * included). The `$id`s are read from the elements themselves rather than from
35
+ * refraction-time metadata, so a `$id` assigned after refraction (e.g. by the
36
+ * bundler) is honored.
37
+ */
38
+ exports.schema$idsOf = schema$idsOf;
39
+ const collectSchema$ids = path => schema$idsOf([...path.getAncestorNodes().reverse(), path.node]);
40
+
41
+ /**
42
+ * `$id`s of the schemas enclosing a traversal root that is a fragment detached
43
+ * from its document, read from the `ancestorsSchemaIdentifiers` meta the JSON
44
+ * Schema refractors record. The meta also carries the root's own `$id`, which
45
+ * the ancestor walk contributes itself, so it is dropped. A detached fragment
46
+ * has no live ancestors, so the meta is the only carrier of its lexical
47
+ * context; this is the one reader of it the refractors must keep serving.
48
+ */
49
+ exports.collectSchema$ids = collectSchema$ids;
50
+ const detachedRootSchema$ids = root => {
51
+ const meta = root?.meta.get('ancestorsSchemaIdentifiers');
52
+ const $ids = Array.isArray(meta) ? meta.filter(_ramdaAdjunct.isString) : [];
53
+ const own$id = isJSONSchemaElementWith$id(root) ? (0, _apidomCore.toValue)(root.$id) : undefined;
54
+ return $ids.length > 0 && $ids.at(-1) === own$id ? $ids.slice(0, -1) : $ids;
55
+ };
56
+
57
+ /**
58
+ * Resolves a chain of `$id`s (outermost first) against `baseURI`, each `$id`
59
+ * refining the base the next one resolves against.
60
+ */
61
+ exports.detachedRootSchema$ids = detachedRootSchema$ids;
62
+ const resolveSchema$ids = (baseURI, $ids) => (0, _ramda.reduce)((acc, $id) => url.resolve(acc, url.sanitize(url.stripHash($id))), baseURI, $ids);
63
+
64
+ /**
65
+ * Resolves the base URI of the schema at `path`: `baseURI` (the base URI in
66
+ * effect at the traversal root) refined by each `$id` on the schema's ancestor
67
+ * chain in turn, the schema's own `$id` included.
68
+ */
69
+ exports.resolveSchema$ids = resolveSchema$ids;
70
+ const resolveSchemaBaseURI = (baseURI, path) => resolveSchema$ids(baseURI, collectSchema$ids(path));
71
+
72
+ /**
73
+ * Resolves the `$ref` of the schema against its base URI (see
74
+ * `resolveSchemaBaseURI`), or returns `undefined` when the schema has no string `$ref`.
75
+ *
76
+ * @public
77
+ */
78
+ exports.resolveSchemaBaseURI = resolveSchemaBaseURI;
79
+ const resolveSchema$refField = (schemaBaseURI, schemaElement) => {
80
+ if (!(0, _apidomDatamodel.isStringElement)(schemaElement.$ref)) {
18
81
  return undefined;
19
82
  }
20
- const hash = url.getHash((0, _apidomCore.toValue)(schemaElement.$ref));
21
- const ancestorsSchemaIdentifiers = schemaElement.meta.get('ancestorsSchemaIdentifiers');
22
- const $refBaseURI = (0, _ramda.reduce)((acc, uri) => {
23
- return url.resolve(acc, url.sanitize(url.stripHash(uri)));
24
- }, retrievalURI, [...ancestorsSchemaIdentifiers, (0, _apidomCore.toValue)(schemaElement.$ref)]);
83
+ const $ref = (0, _apidomCore.toValue)(schemaElement.$ref);
84
+ const hash = url.getHash($ref);
85
+ const $refBaseURI = resolveSchema$ids(schemaBaseURI, [$ref]);
25
86
  return `${$refBaseURI}${hash === '#' ? '' : hash}`;
26
87
  };
27
88
 
28
89
  /**
90
+ * Resolves the `$id` of the schema against `baseURI`, the base URI in effect
91
+ * where the schema sits, or returns `undefined` when the schema declares no string `$id`.
92
+ *
29
93
  * @public
30
94
  */
31
95
  exports.resolveSchema$refField = resolveSchema$refField;
32
- const resolveSchema$idField = (retrievalURI, schemaElement) => {
33
- if (typeof schemaElement.$id === 'undefined') {
96
+ const resolveSchema$idField = (baseURI, schemaElement) => {
97
+ if (!(0, _apidomDatamodel.isStringElement)(schemaElement.$id)) {
34
98
  return undefined;
35
99
  }
36
- const ancestorsSchemaIdentifiers = schemaElement.meta.get('ancestorsSchemaIdentifiers');
37
- return (0, _ramda.reduce)((acc, $id) => {
38
- return url.resolve(acc, url.sanitize(url.stripHash($id)));
39
- }, retrievalURI, ancestorsSchemaIdentifiers);
100
+ return resolveSchema$ids(baseURI, [(0, _apidomCore.toValue)(schemaElement.$id)]);
40
101
  };
41
102
 
42
103
  /**
@@ -1,34 +1,88 @@
1
1
  import { reduce } from 'ramda';
2
- import { isPrimitiveElement } from '@speclynx/apidom-datamodel';
2
+ import { isString } from 'ramda-adjunct';
3
+ import { isPrimitiveElement, isStringElement } from '@speclynx/apidom-datamodel';
3
4
  import { toValue } from '@speclynx/apidom-core';
5
+ import { isJSONSchemaElement } from '@speclynx/apidom-ns-json-schema-2020-12';
4
6
  import { refractSchema } from '@speclynx/apidom-ns-openapi-3-1';
5
7
  import * as url from "../../../util/url.mjs";
6
8
  /**
9
+ * A schema located within a document, together with the `$id`s of the JSON Schema
10
+ * elements enclosing it (outermost first; the located schema's own `$id` excluded).
7
11
  * @public
8
12
  */
9
- export const resolveSchema$refField = (retrievalURI, schemaElement) => {
10
- if (typeof schemaElement.$ref === 'undefined') {
13
+ /**
14
+ * Whether the element is a JSON Schema element declaring a `$id`.
15
+ */
16
+ export const isJSONSchemaElementWith$id = element => isJSONSchemaElement(element) && isStringElement(element.$id);
17
+
18
+ /**
19
+ * `$id`s declared by the JSON Schema elements among `nodes`, in order.
20
+ */
21
+ export const schema$idsOf = nodes => nodes.filter(isJSONSchemaElementWith$id).map(node => toValue(node.$id));
22
+
23
+ /**
24
+ * `$id`s of the JSON Schema elements on `path` (outermost first, the node itself
25
+ * included). The `$id`s are read from the elements themselves rather than from
26
+ * refraction-time metadata, so a `$id` assigned after refraction (e.g. by the
27
+ * bundler) is honored.
28
+ */
29
+ export const collectSchema$ids = path => schema$idsOf([...path.getAncestorNodes().reverse(), path.node]);
30
+
31
+ /**
32
+ * `$id`s of the schemas enclosing a traversal root that is a fragment detached
33
+ * from its document, read from the `ancestorsSchemaIdentifiers` meta the JSON
34
+ * Schema refractors record. The meta also carries the root's own `$id`, which
35
+ * the ancestor walk contributes itself, so it is dropped. A detached fragment
36
+ * has no live ancestors, so the meta is the only carrier of its lexical
37
+ * context; this is the one reader of it the refractors must keep serving.
38
+ */
39
+ export const detachedRootSchema$ids = root => {
40
+ const meta = root?.meta.get('ancestorsSchemaIdentifiers');
41
+ const $ids = Array.isArray(meta) ? meta.filter(isString) : [];
42
+ const own$id = isJSONSchemaElementWith$id(root) ? toValue(root.$id) : undefined;
43
+ return $ids.length > 0 && $ids.at(-1) === own$id ? $ids.slice(0, -1) : $ids;
44
+ };
45
+
46
+ /**
47
+ * Resolves a chain of `$id`s (outermost first) against `baseURI`, each `$id`
48
+ * refining the base the next one resolves against.
49
+ */
50
+ export const resolveSchema$ids = (baseURI, $ids) => reduce((acc, $id) => url.resolve(acc, url.sanitize(url.stripHash($id))), baseURI, $ids);
51
+
52
+ /**
53
+ * Resolves the base URI of the schema at `path`: `baseURI` (the base URI in
54
+ * effect at the traversal root) refined by each `$id` on the schema's ancestor
55
+ * chain in turn, the schema's own `$id` included.
56
+ */
57
+ export const resolveSchemaBaseURI = (baseURI, path) => resolveSchema$ids(baseURI, collectSchema$ids(path));
58
+
59
+ /**
60
+ * Resolves the `$ref` of the schema against its base URI (see
61
+ * `resolveSchemaBaseURI`), or returns `undefined` when the schema has no string `$ref`.
62
+ *
63
+ * @public
64
+ */
65
+ export const resolveSchema$refField = (schemaBaseURI, schemaElement) => {
66
+ if (!isStringElement(schemaElement.$ref)) {
11
67
  return undefined;
12
68
  }
13
- const hash = url.getHash(toValue(schemaElement.$ref));
14
- const ancestorsSchemaIdentifiers = schemaElement.meta.get('ancestorsSchemaIdentifiers');
15
- const $refBaseURI = reduce((acc, uri) => {
16
- return url.resolve(acc, url.sanitize(url.stripHash(uri)));
17
- }, retrievalURI, [...ancestorsSchemaIdentifiers, toValue(schemaElement.$ref)]);
69
+ const $ref = toValue(schemaElement.$ref);
70
+ const hash = url.getHash($ref);
71
+ const $refBaseURI = resolveSchema$ids(schemaBaseURI, [$ref]);
18
72
  return `${$refBaseURI}${hash === '#' ? '' : hash}`;
19
73
  };
20
74
 
21
75
  /**
76
+ * Resolves the `$id` of the schema against `baseURI`, the base URI in effect
77
+ * where the schema sits, or returns `undefined` when the schema declares no string `$id`.
78
+ *
22
79
  * @public
23
80
  */
24
- export const resolveSchema$idField = (retrievalURI, schemaElement) => {
25
- if (typeof schemaElement.$id === 'undefined') {
81
+ export const resolveSchema$idField = (baseURI, schemaElement) => {
82
+ if (!isStringElement(schemaElement.$id)) {
26
83
  return undefined;
27
84
  }
28
- const ancestorsSchemaIdentifiers = schemaElement.meta.get('ancestorsSchemaIdentifiers');
29
- return reduce((acc, $id) => {
30
- return url.resolve(acc, url.sanitize(url.stripHash($id)));
31
- }, retrievalURI, ancestorsSchemaIdentifiers);
85
+ return resolveSchema$ids(baseURI, [toValue(schemaElement.$id)]);
32
86
  };
33
87
 
34
88
  /**
@@ -16,6 +16,7 @@ var _apidomJsonPointer = require("@speclynx/apidom-json-pointer");
16
16
  var _apidomNsOpenapi = require("@speclynx/apidom-ns-openapi-3-1");
17
17
  var _$anchor = require("./selectors/$anchor.cjs");
18
18
  var _uri = require("./selectors/uri.cjs");
19
+ var _jsonPointer = require("./selectors/json-pointer.cjs");
19
20
  var _MaximumDereferenceDepthError = _interopRequireDefault(require("../../../errors/MaximumDereferenceDepthError.cjs"));
20
21
  var _MaximumResolveDepthError = _interopRequireDefault(require("../../../errors/MaximumResolveDepthError.cjs"));
21
22
  var _UnresolvableReferenceError = _interopRequireDefault(require("../../../errors/UnresolvableReferenceError.cjs"));
@@ -38,8 +39,23 @@ class OpenAPI3_1DereferenceVisitor {
38
39
  reference;
39
40
  options;
40
41
  refractCache;
42
+
43
+ /**
44
+ * Index of `$id`-bearing schemas per document, shared across nested visitors
45
+ * of a single dereference run so it never outlives mutations of the documents.
46
+ */
47
+ schema$idIndex;
41
48
  visited;
42
49
 
50
+ /**
51
+ * `$id`s of the JSON Schema elements enclosing the traversal root within its
52
+ * document (outermost first). Empty when the root is the document itself; a
53
+ * nested visitor traversing a fragment is seeded with the `$id`s of the
54
+ * schemas the fragment sits under, so the `$ref`s and `$id`s within resolve
55
+ * against the same base as they would from the document root.
56
+ */
57
+ ancestorSchema$ids;
58
+
43
59
  /**
44
60
  * Tracks element ancestors across dive-deep traversal boundaries.
45
61
  * Used for cycle detection: if a referenced element is found in
@@ -51,15 +67,19 @@ class OpenAPI3_1DereferenceVisitor {
51
67
  options,
52
68
  indirections = [],
53
69
  refractCache = new WeakMap(),
70
+ schema$idIndex = new WeakMap(),
54
71
  ancestors = new _util2.AncestorLineage(),
55
- visited = new WeakSet()
72
+ visited = new WeakSet(),
73
+ ancestorSchema$ids = []
56
74
  }) {
57
75
  this.indirections = indirections;
58
76
  this.reference = reference;
59
77
  this.options = options;
60
78
  this.refractCache = refractCache;
79
+ this.schema$idIndex = schema$idIndex;
61
80
  this.ancestors = new _util2.AncestorLineage(...ancestors);
62
81
  this.visited = visited;
82
+ this.ancestorSchema$ids = ancestorSchema$ids;
63
83
  }
64
84
  toAncestorLineage(path) {
65
85
  const ancestorNodes = path.getAncestorNodes();
@@ -318,6 +338,7 @@ class OpenAPI3_1DereferenceVisitor {
318
338
  indirections: [...this.indirections],
319
339
  options: this.options,
320
340
  refractCache: this.refractCache,
341
+ schema$idIndex: this.schema$idIndex,
321
342
  visited: this.visited,
322
343
  ancestors: ancestorsLineage
323
344
  });
@@ -476,6 +497,7 @@ class OpenAPI3_1DereferenceVisitor {
476
497
  indirections: [...this.indirections],
477
498
  options: this.options,
478
499
  refractCache: this.refractCache,
500
+ schema$idIndex: this.schema$idIndex,
479
501
  visited: this.visited,
480
502
  ancestors: ancestorsLineage
481
503
  });
@@ -673,7 +695,11 @@ class OpenAPI3_1DereferenceVisitor {
673
695
  let {
674
696
  uri: retrievalURI
675
697
  } = reference;
676
- const $refBaseURI = (0, _util.resolveSchema$refField)(retrievalURI, referencingElement);
698
+ // base URI in effect at the traversal root: the document base refined by
699
+ // the $ids of the schemas enclosing the root when the root is a fragment
700
+ const rootBaseURI = (0, _util.resolveSchema$ids)(retrievalURI, this.ancestorSchema$ids);
701
+ const schemaBaseURI = (0, _util.resolveSchemaBaseURI)(rootBaseURI, path);
702
+ const $refBaseURI = (0, _util.resolveSchema$refField)(schemaBaseURI, referencingElement);
677
703
  const $refBaseURIStrippedHash = url.stripHash($refBaseURI);
678
704
  const file = new _File.default({
679
705
  uri: $refBaseURIStrippedHash
@@ -685,13 +711,48 @@ class OpenAPI3_1DereferenceVisitor {
685
711
 
686
712
  // determining reference, proper evaluation and selection mechanism
687
713
  let referencedElement;
714
+ // $ids of the schemas enclosing the referenced element within its document
715
+ let ancestorSchema$ids;
688
716
  try {
689
717
  if (isUnknownURI || isURL) {
690
718
  // we're dealing with canonical URI or URL with possible fragment
691
719
  retrievalURI = this.toBaseURI($refBaseURI);
692
720
  const selector = $refBaseURI;
693
- const referenceAsSchema = (0, _util.maybeRefractToSchemaElement)(reference.value.result);
694
- referencedElement = (0, _uri.evaluate)(selector, referenceAsSchema);
721
+ // the fragment being traversed is searched before the whole document: a
722
+ // fragment referenced from another document (a Response, Parameter, Path
723
+ // Item, ...) is refracted with its own semantics, so the Schema Objects it
724
+ // contains are indexed, whereas the referenced document as a whole is
725
+ // parsed generically and refracted as a bare JSON Schema, which leaves
726
+ // those Schema Objects (under `content`, `schema`, ...) unrecognized and
727
+ // their `$id`s out of the index
728
+ const documentElement = reference.value.result;
729
+ const rootNode = path.getAncestorNodes().at(-1) ?? path.node;
730
+ const fragmentElement = (0, _apidomDatamodel.isParseResultElement)(rootNode) ? rootNode.result : rootNode;
731
+ try {
732
+ // the fragment's $ids resolve against the base its enclosing chain
733
+ // leaves in effect, which then also prefixes the located schema's chain
734
+ ({
735
+ element: referencedElement,
736
+ ancestorSchema$ids
737
+ } = (0, _uri.locate)(selector, (0, _util.maybeRefractToSchemaElement)(fragmentElement), {
738
+ baseURI: rootBaseURI,
739
+ index: this.schema$idIndex
740
+ }));
741
+ ancestorSchema$ids = [...this.ancestorSchema$ids, ...ancestorSchema$ids];
742
+ } catch (error) {
743
+ if (!(error instanceof _EvaluationJsonSchemaUriError.default) || fragmentElement === documentElement) {
744
+ throw error;
745
+ }
746
+ // the document index is rooted at the document, so the located chain
747
+ // is already document-relative and takes no prefix
748
+ ({
749
+ element: referencedElement,
750
+ ancestorSchema$ids
751
+ } = (0, _uri.locate)(selector, (0, _util.maybeRefractToSchemaElement)(documentElement), {
752
+ baseURI: reference.uri,
753
+ index: this.schema$idIndex
754
+ }));
755
+ }
695
756
  referencedElement = (0, _util.maybeRefractToSchemaElement)(referencedElement);
696
757
 
697
758
  // ignore resolving internal Schema Objects
@@ -723,7 +784,10 @@ class OpenAPI3_1DereferenceVisitor {
723
784
  reference = await this.toReference(url.unsanitize($refBaseURI));
724
785
  const selector = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference($refBaseURI);
725
786
  const referenceAsSchema = (0, _util.maybeRefractToSchemaElement)(reference.value.result);
726
- referencedElement = (0, _apidomJsonPointer.evaluate)(referenceAsSchema, selector);
787
+ ({
788
+ element: referencedElement,
789
+ ancestorSchema$ids
790
+ } = (0, _jsonPointer.locate)(referenceAsSchema, selector));
727
791
  referencedElement = (0, _util.maybeRefractToSchemaElement)(referencedElement);
728
792
  }
729
793
  } catch (error) {
@@ -750,7 +814,10 @@ class OpenAPI3_1DereferenceVisitor {
750
814
  reference = await this.toReference(url.unsanitize($refBaseURI));
751
815
  const selector = (0, _$anchor.uriToAnchor)($refBaseURI);
752
816
  const referenceAsSchema = (0, _util.maybeRefractToSchemaElement)(reference.value.result);
753
- referencedElement = (0, _$anchor.evaluate)(selector, referenceAsSchema);
817
+ ({
818
+ element: referencedElement,
819
+ ancestorSchema$ids
820
+ } = (0, _$anchor.locate)(selector, referenceAsSchema));
754
821
  referencedElement = (0, _util.maybeRefractToSchemaElement)(referencedElement);
755
822
  } else {
756
823
  // we're assuming here that we're dealing with JSON Pointer here
@@ -771,7 +838,10 @@ class OpenAPI3_1DereferenceVisitor {
771
838
  reference = await this.toReference(url.unsanitize($refBaseURI));
772
839
  const selector = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference($refBaseURI);
773
840
  const referenceAsSchema = (0, _util.maybeRefractToSchemaElement)(reference.value.result);
774
- referencedElement = (0, _apidomJsonPointer.evaluate)(referenceAsSchema, selector);
841
+ ({
842
+ element: referencedElement,
843
+ ancestorSchema$ids
844
+ } = (0, _jsonPointer.locate)(referenceAsSchema, selector));
775
845
  referencedElement = (0, _util.maybeRefractToSchemaElement)(referencedElement);
776
846
  }
777
847
  } else {
@@ -828,7 +898,11 @@ class OpenAPI3_1DereferenceVisitor {
828
898
  */
829
899
  const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
830
900
  const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
831
- if ((isExternalReference || isNonEntryDocument || (0, _apidomNsOpenapi.isSchemaElement)(referencedElement) && (0, _apidomDatamodel.isStringElement)(referencedElement.$ref) || shouldDetectCircular && !this.visited.has(referencedElement)) && !ancestorsLineage.includesCycle(referencedElement)) {
901
+ // content transplanted into the referencing site is traversed under that
902
+ // site's $id chain; a fragment enclosed by a different chain has its own
903
+ // $refs resolved in place first, by a nested visitor seeded with that chain
904
+ const hasDifferentEnclosingBase = (0, _util.resolveSchema$ids)(reference.uri, ancestorSchema$ids) !== (0, _util.resolveSchema$ids)(rootBaseURI, (0, _util.schema$idsOf)(path.getAncestorNodes().reverse()));
905
+ if ((isExternalReference || isNonEntryDocument || hasDifferentEnclosingBase || (0, _apidomNsOpenapi.isSchemaElement)(referencedElement) && (0, _apidomDatamodel.isStringElement)(referencedElement.$ref) || shouldDetectCircular && !this.visited.has(referencedElement)) && !ancestorsLineage.includesCycle(referencedElement)) {
832
906
  if (shouldDetectCircular) {
833
907
  this.visited.add(referencedElement);
834
908
  }
@@ -838,8 +912,10 @@ class OpenAPI3_1DereferenceVisitor {
838
912
  indirections: [...this.indirections],
839
913
  options: this.options,
840
914
  refractCache: this.refractCache,
915
+ schema$idIndex: this.schema$idIndex,
841
916
  visited: this.visited,
842
- ancestors: ancestorsLineage
917
+ ancestors: ancestorsLineage,
918
+ ancestorSchema$ids
843
919
  });
844
920
  referencedElement = await (0, _apidomTraverse.traverseAsync)(referencedElement, visitor, {
845
921
  mutable: true,
@@ -1,13 +1,14 @@
1
1
  import { propEq, none } from 'ramda';
2
2
  import { isUndefined } from 'ramda-adjunct';
3
- import { isElement, isStringElement, isObjectElement, RefElement, cloneShallow, cloneDeep } from '@speclynx/apidom-datamodel';
3
+ import { isElement, isStringElement, isObjectElement, isParseResultElement, RefElement, cloneShallow, cloneDeep } from '@speclynx/apidom-datamodel';
4
4
  import { toValue, fixedFields, toYAML } from '@speclynx/apidom-core';
5
5
  import { ApiDOMStructuredError } from '@speclynx/apidom-error';
6
6
  import { traverse, traverseAsync, find } from '@speclynx/apidom-traverse';
7
7
  import { evaluate as jsonPointerEvaluate, URIFragmentIdentifier } from '@speclynx/apidom-json-pointer';
8
8
  import { isReferenceLikeElement, isPathItemElement, isReferenceElement, isSchemaElement, isOperationElement, isBooleanJSONSchemaElement, refract, refractReference, refractPathItem, refractOperation } from '@speclynx/apidom-ns-openapi-3-1';
9
- import { isAnchor, uriToAnchor, evaluate as $anchorEvaluate } from "./selectors/$anchor.mjs";
10
- import { evaluate as uriEvaluate } from "./selectors/uri.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 "./selectors/json-pointer.mjs";
11
12
  import MaximumDereferenceDepthError from "../../../errors/MaximumDereferenceDepthError.mjs";
12
13
  import MaximumResolveDepthError from "../../../errors/MaximumResolveDepthError.mjs";
13
14
  import UnresolvableReferenceError from "../../../errors/UnresolvableReferenceError.mjs";
@@ -16,7 +17,7 @@ import * as url from "../../../util/url.mjs";
16
17
  import parse from "../../../parse/index.mjs";
17
18
  import Reference from "../../../Reference.mjs";
18
19
  import File from "../../../File.mjs";
19
- import { resolveSchema$refField, maybeRefractToSchemaElement } from "./util.mjs";
20
+ import { schema$idsOf, resolveSchema$ids, resolveSchemaBaseURI, resolveSchema$refField, maybeRefractToSchemaElement } from "./util.mjs";
20
21
  import { AncestorLineage } from "../../util.mjs";
21
22
  /**
22
23
  * @public
@@ -29,8 +30,23 @@ class OpenAPI3_1DereferenceVisitor {
29
30
  reference;
30
31
  options;
31
32
  refractCache;
33
+
34
+ /**
35
+ * Index of `$id`-bearing schemas per document, shared across nested visitors
36
+ * of a single dereference run so it never outlives mutations of the documents.
37
+ */
38
+ schema$idIndex;
32
39
  visited;
33
40
 
41
+ /**
42
+ * `$id`s of the JSON Schema elements enclosing the traversal root within its
43
+ * document (outermost first). Empty when the root is the document itself; a
44
+ * nested visitor traversing a fragment is seeded with the `$id`s of the
45
+ * schemas the fragment sits under, so the `$ref`s and `$id`s within resolve
46
+ * against the same base as they would from the document root.
47
+ */
48
+ ancestorSchema$ids;
49
+
34
50
  /**
35
51
  * Tracks element ancestors across dive-deep traversal boundaries.
36
52
  * Used for cycle detection: if a referenced element is found in
@@ -42,15 +58,19 @@ class OpenAPI3_1DereferenceVisitor {
42
58
  options,
43
59
  indirections = [],
44
60
  refractCache = new WeakMap(),
61
+ schema$idIndex = new WeakMap(),
45
62
  ancestors = new AncestorLineage(),
46
- visited = new WeakSet()
63
+ visited = new WeakSet(),
64
+ ancestorSchema$ids = []
47
65
  }) {
48
66
  this.indirections = indirections;
49
67
  this.reference = reference;
50
68
  this.options = options;
51
69
  this.refractCache = refractCache;
70
+ this.schema$idIndex = schema$idIndex;
52
71
  this.ancestors = new AncestorLineage(...ancestors);
53
72
  this.visited = visited;
73
+ this.ancestorSchema$ids = ancestorSchema$ids;
54
74
  }
55
75
  toAncestorLineage(path) {
56
76
  const ancestorNodes = path.getAncestorNodes();
@@ -309,6 +329,7 @@ class OpenAPI3_1DereferenceVisitor {
309
329
  indirections: [...this.indirections],
310
330
  options: this.options,
311
331
  refractCache: this.refractCache,
332
+ schema$idIndex: this.schema$idIndex,
312
333
  visited: this.visited,
313
334
  ancestors: ancestorsLineage
314
335
  });
@@ -467,6 +488,7 @@ class OpenAPI3_1DereferenceVisitor {
467
488
  indirections: [...this.indirections],
468
489
  options: this.options,
469
490
  refractCache: this.refractCache,
491
+ schema$idIndex: this.schema$idIndex,
470
492
  visited: this.visited,
471
493
  ancestors: ancestorsLineage
472
494
  });
@@ -664,7 +686,11 @@ class OpenAPI3_1DereferenceVisitor {
664
686
  let {
665
687
  uri: retrievalURI
666
688
  } = reference;
667
- const $refBaseURI = resolveSchema$refField(retrievalURI, referencingElement);
689
+ // base URI in effect at the traversal root: the document base refined by
690
+ // the $ids of the schemas enclosing the root when the root is a fragment
691
+ const rootBaseURI = resolveSchema$ids(retrievalURI, this.ancestorSchema$ids);
692
+ const schemaBaseURI = resolveSchemaBaseURI(rootBaseURI, path);
693
+ const $refBaseURI = resolveSchema$refField(schemaBaseURI, referencingElement);
668
694
  const $refBaseURIStrippedHash = url.stripHash($refBaseURI);
669
695
  const file = new File({
670
696
  uri: $refBaseURIStrippedHash
@@ -676,13 +702,48 @@ class OpenAPI3_1DereferenceVisitor {
676
702
 
677
703
  // determining reference, proper evaluation and selection mechanism
678
704
  let referencedElement;
705
+ // $ids of the schemas enclosing the referenced element within its document
706
+ let ancestorSchema$ids;
679
707
  try {
680
708
  if (isUnknownURI || isURL) {
681
709
  // we're dealing with canonical URI or URL with possible fragment
682
710
  retrievalURI = this.toBaseURI($refBaseURI);
683
711
  const selector = $refBaseURI;
684
- const referenceAsSchema = maybeRefractToSchemaElement(reference.value.result);
685
- referencedElement = uriEvaluate(selector, referenceAsSchema);
712
+ // the fragment being traversed is searched before the whole document: a
713
+ // fragment referenced from another document (a Response, Parameter, Path
714
+ // Item, ...) is refracted with its own semantics, so the Schema Objects it
715
+ // contains are indexed, whereas the referenced document as a whole is
716
+ // parsed generically and refracted as a bare JSON Schema, which leaves
717
+ // those Schema Objects (under `content`, `schema`, ...) unrecognized and
718
+ // their `$id`s out of the index
719
+ const documentElement = reference.value.result;
720
+ const rootNode = path.getAncestorNodes().at(-1) ?? path.node;
721
+ const fragmentElement = isParseResultElement(rootNode) ? rootNode.result : rootNode;
722
+ try {
723
+ // the fragment's $ids resolve against the base its enclosing chain
724
+ // leaves in effect, which then also prefixes the located schema's chain
725
+ ({
726
+ element: referencedElement,
727
+ ancestorSchema$ids
728
+ } = uriLocate(selector, maybeRefractToSchemaElement(fragmentElement), {
729
+ baseURI: rootBaseURI,
730
+ index: this.schema$idIndex
731
+ }));
732
+ ancestorSchema$ids = [...this.ancestorSchema$ids, ...ancestorSchema$ids];
733
+ } catch (error) {
734
+ if (!(error instanceof EvaluationJsonSchemaUriError) || fragmentElement === documentElement) {
735
+ throw error;
736
+ }
737
+ // the document index is rooted at the document, so the located chain
738
+ // is already document-relative and takes no prefix
739
+ ({
740
+ element: referencedElement,
741
+ ancestorSchema$ids
742
+ } = uriLocate(selector, maybeRefractToSchemaElement(documentElement), {
743
+ baseURI: reference.uri,
744
+ index: this.schema$idIndex
745
+ }));
746
+ }
686
747
  referencedElement = maybeRefractToSchemaElement(referencedElement);
687
748
 
688
749
  // ignore resolving internal Schema Objects
@@ -714,7 +775,10 @@ class OpenAPI3_1DereferenceVisitor {
714
775
  reference = await this.toReference(url.unsanitize($refBaseURI));
715
776
  const selector = URIFragmentIdentifier.fromURIReference($refBaseURI);
716
777
  const referenceAsSchema = maybeRefractToSchemaElement(reference.value.result);
717
- referencedElement = jsonPointerEvaluate(referenceAsSchema, selector);
778
+ ({
779
+ element: referencedElement,
780
+ ancestorSchema$ids
781
+ } = jsonPointerLocate(referenceAsSchema, selector));
718
782
  referencedElement = maybeRefractToSchemaElement(referencedElement);
719
783
  }
720
784
  } catch (error) {
@@ -741,7 +805,10 @@ class OpenAPI3_1DereferenceVisitor {
741
805
  reference = await this.toReference(url.unsanitize($refBaseURI));
742
806
  const selector = uriToAnchor($refBaseURI);
743
807
  const referenceAsSchema = maybeRefractToSchemaElement(reference.value.result);
744
- referencedElement = $anchorEvaluate(selector, referenceAsSchema);
808
+ ({
809
+ element: referencedElement,
810
+ ancestorSchema$ids
811
+ } = $anchorLocate(selector, referenceAsSchema));
745
812
  referencedElement = maybeRefractToSchemaElement(referencedElement);
746
813
  } else {
747
814
  // we're assuming here that we're dealing with JSON Pointer here
@@ -762,7 +829,10 @@ class OpenAPI3_1DereferenceVisitor {
762
829
  reference = await this.toReference(url.unsanitize($refBaseURI));
763
830
  const selector = URIFragmentIdentifier.fromURIReference($refBaseURI);
764
831
  const referenceAsSchema = maybeRefractToSchemaElement(reference.value.result);
765
- referencedElement = jsonPointerEvaluate(referenceAsSchema, selector);
832
+ ({
833
+ element: referencedElement,
834
+ ancestorSchema$ids
835
+ } = jsonPointerLocate(referenceAsSchema, selector));
766
836
  referencedElement = maybeRefractToSchemaElement(referencedElement);
767
837
  }
768
838
  } else {
@@ -819,7 +889,11 @@ class OpenAPI3_1DereferenceVisitor {
819
889
  */
820
890
  const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
821
891
  const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
822
- if ((isExternalReference || isNonEntryDocument || isSchemaElement(referencedElement) && isStringElement(referencedElement.$ref) || shouldDetectCircular && !this.visited.has(referencedElement)) && !ancestorsLineage.includesCycle(referencedElement)) {
892
+ // content transplanted into the referencing site is traversed under that
893
+ // site's $id chain; a fragment enclosed by a different chain has its own
894
+ // $refs resolved in place first, by a nested visitor seeded with that chain
895
+ const hasDifferentEnclosingBase = resolveSchema$ids(reference.uri, ancestorSchema$ids) !== resolveSchema$ids(rootBaseURI, schema$idsOf(path.getAncestorNodes().reverse()));
896
+ if ((isExternalReference || isNonEntryDocument || hasDifferentEnclosingBase || isSchemaElement(referencedElement) && isStringElement(referencedElement.$ref) || shouldDetectCircular && !this.visited.has(referencedElement)) && !ancestorsLineage.includesCycle(referencedElement)) {
823
897
  if (shouldDetectCircular) {
824
898
  this.visited.add(referencedElement);
825
899
  }
@@ -829,8 +903,10 @@ class OpenAPI3_1DereferenceVisitor {
829
903
  indirections: [...this.indirections],
830
904
  options: this.options,
831
905
  refractCache: this.refractCache,
906
+ schema$idIndex: this.schema$idIndex,
832
907
  visited: this.visited,
833
- ancestors: ancestorsLineage
908
+ ancestors: ancestorsLineage,
909
+ ancestorSchema$ids
834
910
  });
835
911
  referencedElement = await traverseAsync(referencedElement, visitor, {
836
912
  mutable: true,