@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
@@ -5,6 +5,7 @@ import DereferenceStrategy from "../DereferenceStrategy.mjs";
5
5
  import Reference from "../../../Reference.mjs";
6
6
  import ReferenceSet from "../../../ReferenceSet.mjs";
7
7
  import Arazzo1DereferenceVisitor from "./visitor.mjs";
8
+ import { detachedRootSchema$ids } from "../openapi-3-1/util.mjs";
8
9
  import { dereferenceSourceDescriptions } from "./source-descriptions.mjs";
9
10
  /**
10
11
  * @public
@@ -59,7 +60,8 @@ class Arazzo1DereferenceStrategy extends DereferenceStrategy {
59
60
  const shouldDetectCircular = ['error', 'replace'].includes(options.dereference.circular);
60
61
  const visitor = new Arazzo1DereferenceVisitor({
61
62
  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,
@@ -68,6 +70,17 @@ class Arazzo1DereferenceStrategy extends DereferenceStrategy {
68
70
  })
69
71
  });
70
72
 
73
+ /**
74
+ * If immutable option is set, replay refs from the refSet.
75
+ * Replayed before source descriptions are dereferenced, so they can reuse the refs.
76
+ */
77
+ if (options.dereference.immutable) {
78
+ mutableRefSet.refs.filter(ref => ref.uri.startsWith('immutable://')).map(ref => new Reference({
79
+ ...ref,
80
+ uri: ref.uri.replace(/^immutable:\/\//, '')
81
+ })).forEach(ref => immutableRefSet.add(ref));
82
+ }
83
+
71
84
  /**
72
85
  * Dereference source descriptions if option is enabled.
73
86
  */
@@ -82,16 +95,6 @@ class Arazzo1DereferenceStrategy extends DereferenceStrategy {
82
95
  parseResult.push(...sourceDescriptions);
83
96
  }
84
97
 
85
- /**
86
- * If immutable option is set, replay refs from the refSet.
87
- */
88
- if (options.dereference.immutable) {
89
- mutableRefSet.refs.filter(ref => ref.uri.startsWith('immutable://')).map(ref => new Reference({
90
- ...ref,
91
- uri: ref.uri.replace(/^immutable:\/\//, '')
92
- })).forEach(ref => immutableRefSet.add(ref));
93
- }
94
-
95
98
  /**
96
99
  * Release all memory if this refSet was not provided as a configuration option.
97
100
  * If provided as configuration option, then provider is responsible for cleanup.
@@ -33,6 +33,12 @@ Object.defineProperty(exports, "isAnchor", {
33
33
  return _$anchor.isAnchor;
34
34
  }
35
35
  });
36
+ Object.defineProperty(exports, "locate", {
37
+ enumerable: true,
38
+ get: function () {
39
+ return _$anchor.locate;
40
+ }
41
+ });
36
42
  Object.defineProperty(exports, "parse", {
37
43
  enumerable: true,
38
44
  get: function () {
@@ -1 +1 @@
1
- export { isAnchor, uriToAnchor, parse, evaluate, EvaluationJsonSchema$anchorError, InvalidJsonSchema$anchorError, JsonSchema$anchorError } from "../../openapi-3-1/selectors/$anchor.mjs";
1
+ export { isAnchor, uriToAnchor, parse, locate, evaluate, EvaluationJsonSchema$anchorError, InvalidJsonSchema$anchorError, JsonSchema$anchorError } from "../../openapi-3-1/selectors/$anchor.mjs";
@@ -21,4 +21,10 @@ Object.defineProperty(exports, "evaluate", {
21
21
  return _uri.evaluate;
22
22
  }
23
23
  });
24
+ Object.defineProperty(exports, "locate", {
25
+ enumerable: true,
26
+ get: function () {
27
+ return _uri.locate;
28
+ }
29
+ });
24
30
  var _uri = require("../../openapi-3-1/selectors/uri.cjs");
@@ -1 +1 @@
1
- export { evaluate, EvaluationJsonSchemaUriError, JsonSchemaUriError } from "../../openapi-3-1/selectors/uri.mjs";
1
+ export { locate, evaluate, EvaluationJsonSchemaUriError, JsonSchemaUriError } from "../../openapi-3-1/selectors/uri.mjs";
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
 
3
+ var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault").default;
3
4
  var _interopRequireWildcard = require("@babel/runtime-corejs3/helpers/interopRequireWildcard").default;
4
5
  Object.defineProperty(exports, "__esModule", {
5
6
  value: true
@@ -8,9 +9,13 @@ exports.dereferenceSourceDescriptions = dereferenceSourceDescriptions;
8
9
  var _apidomDatamodel = require("@speclynx/apidom-datamodel");
9
10
  var _apidomNsArazzo = require("@speclynx/apidom-ns-arazzo-1");
10
11
  var _apidomCore = require("@speclynx/apidom-core");
12
+ var _ramda = require("ramda");
11
13
  var url = _interopRequireWildcard(require("../../../util/url.cjs"));
14
+ var _Reference = _interopRequireDefault(require("../../../Reference.cjs"));
15
+ var _ReferenceSet = _interopRequireDefault(require("../../../ReferenceSet.cjs"));
12
16
  var _util = require("../../../options/util.cjs");
13
- var _index = _interopRequireWildcard(require("../../index.cjs"));
17
+ var _index = _interopRequireDefault(require("../../../parse/index.cjs"));
18
+ var _index2 = require("../../index.cjs");
14
19
  var _util2 = require("./util.cjs");
15
20
  /**
16
21
  * Dereferences a single source description element.
@@ -63,53 +68,64 @@ async function dereferenceSourceDescription(sourceDescription, ctx) {
63
68
 
64
69
  // check if source description was already parsed (e.g., during parse phase with sourceDescriptions: true)
65
70
  const existingParseResult = sourceDescription.meta.get('parseResult');
71
+ const parentRefSet = ctx.options.dereference.refSet;
72
+ let refSet = null;
66
73
  try {
67
- let sourceDescriptionDereferenced;
68
- if ((0, _apidomDatamodel.isParseResultElement)(existingParseResult)) {
69
- // use existing parsed result - just dereference it (no re-fetch/re-parse)
70
- sourceDescriptionDereferenced = await (0, _index.dereferenceApiDOM)(existingParseResult, (0, _util.merge)(ctx.options, {
71
- parse: {
72
- mediaType: 'text/plain' // allow dereference strategy detection via ApiDOM inspection
73
- },
74
- resolve: {
75
- baseURI: retrievalURI
76
- },
77
- dereference: {
78
- strategyOpts: {
79
- // nested documents should dereference all their source descriptions
80
- // (parent's name filter doesn't apply to nested documents)
81
- // set at strategy-specific level to override any inherited filters
82
- [ctx.strategyName]: {
83
- sourceDescriptions: true,
84
- sourceDescriptionsDepth: ctx.currentDepth + 1,
85
- sourceDescriptionsAncestors: ctx.ancestors,
86
- sourceDescriptionsDereferenced: ctx.dereferenced
87
- }
88
- }
89
- }
90
- }));
74
+ // source description document comes from, in order of preference:
75
+ // caller-supplied refSet, parse phase, or is fetched and parsed now
76
+ const cachedReference = parentRefSet?.find(ref => ref.uri === retrievalURI);
77
+ let sourceDescriptionParseResult;
78
+ let isFreshlyParsed = false;
79
+ if (cachedReference !== undefined) {
80
+ sourceDescriptionParseResult = cachedReference.value;
81
+ } else if ((0, _apidomDatamodel.isParseResultElement)(existingParseResult)) {
82
+ sourceDescriptionParseResult = existingParseResult;
91
83
  } else {
92
- // no existing parse result - fetch, parse, and dereference
93
- sourceDescriptionDereferenced = await (0, _index.default)(retrievalURI, (0, _util.merge)(ctx.options, {
84
+ sourceDescriptionParseResult = await (0, _index.default)(retrievalURI, (0, _util.merge)(ctx.options, {
94
85
  parse: {
95
- mediaType: 'text/plain' // allow parser plugin detection
96
- },
97
- dereference: {
98
- strategyOpts: {
99
- // nested documents should dereference all their source descriptions
100
- // (parent's name filter doesn't apply to nested documents)
101
- // set at strategy-specific level to override any inherited filters
102
- [ctx.strategyName]: {
103
- sourceDescriptions: true,
104
- sourceDescriptionsDepth: ctx.currentDepth + 1,
105
- sourceDescriptionsAncestors: ctx.ancestors,
106
- sourceDescriptionsDereferenced: ctx.dereferenced
107
- }
108
- }
109
- }
86
+ mediaType: 'text/plain'
87
+ } // allow parser plugin detection
110
88
  }));
89
+ isFreshlyParsed = true;
111
90
  }
112
91
 
92
+ // caller-supplied refSet is rooted at the entry document, but strategies dereference
93
+ // the root of the refSet; re-root its references at the source description
94
+ if (parentRefSet !== null) {
95
+ refSet = new _ReferenceSet.default({
96
+ refs: [new _Reference.default({
97
+ uri: retrievalURI,
98
+ value: sourceDescriptionParseResult
99
+ }), ...parentRefSet.refs.map(ref => new _Reference.default({
100
+ ...ref,
101
+ refSet: undefined
102
+ }))]
103
+ });
104
+ }
105
+ const sourceDescriptionDereferenced = await (0, _index2.dereferenceApiDOM)(sourceDescriptionParseResult, (0, _util.merge)((0, _ramda.assocPath)(['dereference', 'refSet'], refSet, ctx.options), {
106
+ parse: {
107
+ mediaType: 'text/plain' // allow dereference strategy detection via ApiDOM inspection
108
+ },
109
+ resolve: {
110
+ baseURI: retrievalURI
111
+ },
112
+ dereference: {
113
+ // freshly parsed document not reported to any refSet can be dereferenced in mutable mode
114
+ immutable: ctx.options.dereference.immutable && !(isFreshlyParsed && parentRefSet === null),
115
+ strategyOpts: {
116
+ // nested documents should dereference all their source descriptions
117
+ // (parent's name filter doesn't apply to nested documents)
118
+ // set at strategy-specific level to override any inherited filters
119
+ [ctx.strategyName]: {
120
+ sourceDescriptions: true,
121
+ sourceDescriptionsDepth: ctx.currentDepth + 1,
122
+ sourceDescriptionsAncestors: ctx.ancestors,
123
+ sourceDescriptionsDereferenced: ctx.dereferenced
124
+ }
125
+ }
126
+ }
127
+ }));
128
+
113
129
  // merge dereferenced result into our parse result
114
130
  for (const item of sourceDescriptionDereferenced) {
115
131
  parseResult.push(item);
@@ -121,6 +137,13 @@ async function dereferenceSourceDescription(sourceDescription, ctx) {
121
137
  annotation.classes.push('error');
122
138
  parseResult.push(annotation);
123
139
  return parseResult;
140
+ } finally {
141
+ // report documents reached through the source description to the caller-supplied refSet,
142
+ // even when dereferencing it failed
143
+ if (parentRefSet !== null && refSet !== null) {
144
+ parentRefSet.merge(refSet);
145
+ parentRefSet.circular ||= refSet.circular;
146
+ }
124
147
  }
125
148
 
126
149
  // register dereferenced document for later references to it
@@ -1,9 +1,13 @@
1
1
  import { ParseResultElement, AnnotationElement, isArrayElement, isStringElement, isParseResultElement } from '@speclynx/apidom-datamodel';
2
2
  import { isArazzoSpecification1Element, isSourceDescriptionElement } from '@speclynx/apidom-ns-arazzo-1';
3
3
  import { toValue } from '@speclynx/apidom-core';
4
+ import { assocPath } from 'ramda';
4
5
  import * as url from "../../../util/url.mjs";
6
+ import Reference from "../../../Reference.mjs";
7
+ import ReferenceSet from "../../../ReferenceSet.mjs";
5
8
  import { merge as mergeOptions } from "../../../options/util.mjs";
6
- import dereference, { dereferenceApiDOM } from "../../index.mjs";
9
+ import parse from "../../../parse/index.mjs";
10
+ import { dereferenceApiDOM } from "../../index.mjs";
7
11
  import { arazzoDocumentURIs, resolveArazzo$selfField, validateSourceDescriptionAPI } from "./util.mjs";
8
12
  /**
9
13
  * Dereferences a single source description element.
@@ -56,53 +60,64 @@ async function dereferenceSourceDescription(sourceDescription, ctx) {
56
60
 
57
61
  // check if source description was already parsed (e.g., during parse phase with sourceDescriptions: true)
58
62
  const existingParseResult = sourceDescription.meta.get('parseResult');
63
+ const parentRefSet = ctx.options.dereference.refSet;
64
+ let refSet = null;
59
65
  try {
60
- let sourceDescriptionDereferenced;
61
- if (isParseResultElement(existingParseResult)) {
62
- // use existing parsed result - just dereference it (no re-fetch/re-parse)
63
- sourceDescriptionDereferenced = await dereferenceApiDOM(existingParseResult, mergeOptions(ctx.options, {
64
- parse: {
65
- mediaType: 'text/plain' // allow dereference strategy detection via ApiDOM inspection
66
- },
67
- resolve: {
68
- baseURI: retrievalURI
69
- },
70
- dereference: {
71
- strategyOpts: {
72
- // nested documents should dereference all their source descriptions
73
- // (parent's name filter doesn't apply to nested documents)
74
- // set at strategy-specific level to override any inherited filters
75
- [ctx.strategyName]: {
76
- sourceDescriptions: true,
77
- sourceDescriptionsDepth: ctx.currentDepth + 1,
78
- sourceDescriptionsAncestors: ctx.ancestors,
79
- sourceDescriptionsDereferenced: ctx.dereferenced
80
- }
81
- }
82
- }
83
- }));
66
+ // source description document comes from, in order of preference:
67
+ // caller-supplied refSet, parse phase, or is fetched and parsed now
68
+ const cachedReference = parentRefSet?.find(ref => ref.uri === retrievalURI);
69
+ let sourceDescriptionParseResult;
70
+ let isFreshlyParsed = false;
71
+ if (cachedReference !== undefined) {
72
+ sourceDescriptionParseResult = cachedReference.value;
73
+ } else if (isParseResultElement(existingParseResult)) {
74
+ sourceDescriptionParseResult = existingParseResult;
84
75
  } else {
85
- // no existing parse result - fetch, parse, and dereference
86
- sourceDescriptionDereferenced = await dereference(retrievalURI, mergeOptions(ctx.options, {
76
+ sourceDescriptionParseResult = await parse(retrievalURI, mergeOptions(ctx.options, {
87
77
  parse: {
88
- mediaType: 'text/plain' // allow parser plugin detection
89
- },
90
- dereference: {
91
- strategyOpts: {
92
- // nested documents should dereference all their source descriptions
93
- // (parent's name filter doesn't apply to nested documents)
94
- // set at strategy-specific level to override any inherited filters
95
- [ctx.strategyName]: {
96
- sourceDescriptions: true,
97
- sourceDescriptionsDepth: ctx.currentDepth + 1,
98
- sourceDescriptionsAncestors: ctx.ancestors,
99
- sourceDescriptionsDereferenced: ctx.dereferenced
100
- }
101
- }
102
- }
78
+ mediaType: 'text/plain'
79
+ } // allow parser plugin detection
103
80
  }));
81
+ isFreshlyParsed = true;
104
82
  }
105
83
 
84
+ // caller-supplied refSet is rooted at the entry document, but strategies dereference
85
+ // the root of the refSet; re-root its references at the source description
86
+ if (parentRefSet !== null) {
87
+ refSet = new ReferenceSet({
88
+ refs: [new Reference({
89
+ uri: retrievalURI,
90
+ value: sourceDescriptionParseResult
91
+ }), ...parentRefSet.refs.map(ref => new Reference({
92
+ ...ref,
93
+ refSet: undefined
94
+ }))]
95
+ });
96
+ }
97
+ const sourceDescriptionDereferenced = await dereferenceApiDOM(sourceDescriptionParseResult, mergeOptions(assocPath(['dereference', 'refSet'], refSet, ctx.options), {
98
+ parse: {
99
+ mediaType: 'text/plain' // allow dereference strategy detection via ApiDOM inspection
100
+ },
101
+ resolve: {
102
+ baseURI: retrievalURI
103
+ },
104
+ dereference: {
105
+ // freshly parsed document not reported to any refSet can be dereferenced in mutable mode
106
+ immutable: ctx.options.dereference.immutable && !(isFreshlyParsed && parentRefSet === null),
107
+ strategyOpts: {
108
+ // nested documents should dereference all their source descriptions
109
+ // (parent's name filter doesn't apply to nested documents)
110
+ // set at strategy-specific level to override any inherited filters
111
+ [ctx.strategyName]: {
112
+ sourceDescriptions: true,
113
+ sourceDescriptionsDepth: ctx.currentDepth + 1,
114
+ sourceDescriptionsAncestors: ctx.ancestors,
115
+ sourceDescriptionsDereferenced: ctx.dereferenced
116
+ }
117
+ }
118
+ }
119
+ }));
120
+
106
121
  // merge dereferenced result into our parse result
107
122
  for (const item of sourceDescriptionDereferenced) {
108
123
  parseResult.push(item);
@@ -114,6 +129,13 @@ async function dereferenceSourceDescription(sourceDescription, ctx) {
114
129
  annotation.classes.push('error');
115
130
  parseResult.push(annotation);
116
131
  return parseResult;
132
+ } finally {
133
+ // report documents reached through the source description to the caller-supplied refSet,
134
+ // even when dereferencing it failed
135
+ if (parentRefSet !== null && refSet !== null) {
136
+ parentRefSet.merge(refSet);
137
+ parentRefSet.circular ||= refSet.circular;
138
+ }
117
139
  }
118
140
 
119
141
  // register dereferenced document for later references to it
@@ -17,6 +17,12 @@ Object.defineProperty(exports, "resolveSchema$refField", {
17
17
  return _util.resolveSchema$refField;
18
18
  }
19
19
  });
20
+ Object.defineProperty(exports, "resolveSchemaBaseURI", {
21
+ enumerable: true,
22
+ get: function () {
23
+ return _util.resolveSchemaBaseURI;
24
+ }
25
+ });
20
26
  exports.validateSourceDescriptionAPI = void 0;
21
27
  var _apidomDatamodel = require("@speclynx/apidom-datamodel");
22
28
  var _apidomCore = require("@speclynx/apidom-core");
@@ -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();
@@ -232,34 +252,38 @@ class Arazzo1DereferenceVisitor {
232
252
  return;
233
253
  }
234
254
  const runtimeExpression = (0, _apidomCore.toValue)(referencingElement.reference);
235
-
236
- // parse the runtime expression
237
- const {
238
- result,
239
- tree
240
- } = (0, _arazzoRuntimeExpression.parse)(runtimeExpression);
241
- if (!result.success) {
242
- throw new _apidomError.ApiDOMStructuredError(`Invalid Reusable Object reference format: "${runtimeExpression}"`, {
243
- runtimeExpression
244
- });
245
- }
246
-
247
- // ReusableElement can only reference components
248
- if (tree.type !== 'ComponentsExpression') {
249
- throw new _apidomError.ApiDOMStructuredError(`Reusable Object reference "${runtimeExpression}" must be a components expression`, {
250
- runtimeExpression
251
- });
252
- }
253
-
254
- // evaluate runtime expression as JSON Pointer to get the referenced element
255
- const jsonPointer = (0, _apidomJsonPointer.compile)(['components', tree.componentType, tree.componentName]);
256
255
  let referencedElement;
257
256
  try {
258
- referencedElement = (0, _apidomJsonPointer.evaluate)(this.reference.value.result, jsonPointer);
259
- } catch {
260
- throw new _apidomError.ApiDOMStructuredError(`Reusable Object reference "${runtimeExpression}" cannot be resolved`, {
261
- runtimeExpression
262
- });
257
+ // parse the runtime expression
258
+ const {
259
+ result,
260
+ tree
261
+ } = (0, _arazzoRuntimeExpression.parse)(runtimeExpression);
262
+ if (!result.success) {
263
+ throw new _apidomError.ApiDOMStructuredError(`Invalid Reusable Object reference format: "${runtimeExpression}"`, {
264
+ runtimeExpression
265
+ });
266
+ }
267
+
268
+ // ReusableElement can only reference components
269
+ if (tree.type !== 'ComponentsExpression') {
270
+ throw new _apidomError.ApiDOMStructuredError(`Reusable Object reference "${runtimeExpression}" must be a components expression`, {
271
+ runtimeExpression
272
+ });
273
+ }
274
+
275
+ // evaluate runtime expression as JSON Pointer to get the referenced element
276
+ const jsonPointer = (0, _apidomJsonPointer.compile)(['components', tree.componentType, tree.componentName]);
277
+ try {
278
+ referencedElement = (0, _apidomJsonPointer.evaluate)(this.reference.value.result, jsonPointer);
279
+ } catch {
280
+ throw new _apidomError.ApiDOMStructuredError(`Reusable Object reference "${runtimeExpression}" cannot be resolved`, {
281
+ runtimeExpression
282
+ });
283
+ }
284
+ } catch (error) {
285
+ this.handleError(`Error while dereferencing Reusable Object. Cannot resolve reference "${runtimeExpression}": ${error.message}`, error, referencingElement, 'reference', runtimeExpression, path);
286
+ return;
263
287
  }
264
288
 
265
289
  /**
@@ -307,7 +331,11 @@ class Arazzo1DereferenceVisitor {
307
331
  let {
308
332
  uri: retrievalURI
309
333
  } = reference;
310
- const $refBaseURI = (0, _util.resolveSchema$refField)(this.baseURI, referencingElement);
334
+ // base URI in effect at the traversal root: the document base refined by
335
+ // the $ids of the schemas enclosing the root when the root is a fragment
336
+ const rootBaseURI = (0, _util.resolveSchema$ids)(this.baseURI, this.ancestorSchema$ids);
337
+ const schemaBaseURI = (0, _util.resolveSchemaBaseURI)(rootBaseURI, path);
338
+ const $refBaseURI = (0, _util.resolveSchema$refField)(schemaBaseURI, referencingElement);
311
339
  const $refBaseURIStrippedHash = url.stripHash($refBaseURI);
312
340
  const file = new _File.default({
313
341
  uri: $refBaseURIStrippedHash
@@ -319,13 +347,32 @@ class Arazzo1DereferenceVisitor {
319
347
 
320
348
  // determining reference, proper evaluation and selection mechanism
321
349
  let referencedElement;
350
+ // $ids of the schemas enclosing the referenced element within its document
351
+ let ancestorSchema$ids;
322
352
  try {
323
353
  if (isUnknownURI || isURL) {
324
354
  // we're dealing with canonical URI or URL with possible fragment
325
355
  retrievalURI = this.toBaseURI($refBaseURI);
326
356
  const selector = $refBaseURI;
327
- const referenceAsSchema = (0, _util2.maybeRefractToJSONSchemaElement)(reference.value.result);
328
- referencedElement = (0, _uri.evaluate)(selector, referenceAsSchema);
357
+ const documentElement = reference.value.result;
358
+ const referenceAsSchema = (0, _util2.maybeRefractToJSONSchemaElement)(documentElement);
359
+ // a document that is itself a fragment detached from its source carries
360
+ // the $ids enclosing it only as `ancestorSchema$ids`: its $ids are then
361
+ // indexed against the base that chain leaves in effect, and the chain
362
+ // prefixes the one of the located schema. A nested visitor rooted at a
363
+ // fragment of a whole document indexes that document against its own base.
364
+ const rootNode = path.getAncestorNodes().at(-1) ?? path.node;
365
+ const isDocumentRoot = ((0, _apidomDatamodel.isParseResultElement)(rootNode) ? rootNode.result : rootNode) === documentElement;
366
+ ({
367
+ element: referencedElement,
368
+ ancestorSchema$ids
369
+ } = (0, _uri.locate)(selector, referenceAsSchema, {
370
+ baseURI: isDocumentRoot ? rootBaseURI : this.baseURI,
371
+ index: this.schema$idIndex
372
+ }));
373
+ if (isDocumentRoot) {
374
+ ancestorSchema$ids = [...this.ancestorSchema$ids, ...ancestorSchema$ids];
375
+ }
329
376
  referencedElement = (0, _util2.maybeRefractToJSONSchemaElement)(referencedElement);
330
377
 
331
378
  // ignore resolving internal Schema Objects
@@ -357,7 +404,10 @@ class Arazzo1DereferenceVisitor {
357
404
  reference = await this.toReference(url.unsanitize($refBaseURI));
358
405
  const selector = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference($refBaseURI);
359
406
  const referenceAsSchema = (0, _util2.maybeRefractToJSONSchemaElement)(reference.value.result);
360
- referencedElement = (0, _apidomJsonPointer.evaluate)(referenceAsSchema, selector);
407
+ ({
408
+ element: referencedElement,
409
+ ancestorSchema$ids
410
+ } = (0, _jsonPointer.locate)(referenceAsSchema, selector));
361
411
  referencedElement = (0, _util2.maybeRefractToJSONSchemaElement)(referencedElement);
362
412
  }
363
413
  } catch (error) {
@@ -384,7 +434,10 @@ class Arazzo1DereferenceVisitor {
384
434
  reference = await this.toReference(url.unsanitize($refBaseURI));
385
435
  const selector = (0, _$anchor.uriToAnchor)($refBaseURI);
386
436
  const referenceAsSchema = (0, _util2.maybeRefractToJSONSchemaElement)(reference.value.result);
387
- referencedElement = (0, _$anchor.evaluate)(selector, referenceAsSchema);
437
+ ({
438
+ element: referencedElement,
439
+ ancestorSchema$ids
440
+ } = (0, _$anchor.locate)(selector, referenceAsSchema));
388
441
  referencedElement = (0, _util2.maybeRefractToJSONSchemaElement)(referencedElement);
389
442
  } else {
390
443
  // we're assuming here that we're dealing with JSON Pointer here
@@ -405,7 +458,10 @@ class Arazzo1DereferenceVisitor {
405
458
  reference = await this.toReference(url.unsanitize($refBaseURI));
406
459
  const selector = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference($refBaseURI);
407
460
  const referenceAsSchema = (0, _util2.maybeRefractToJSONSchemaElement)(reference.value.result);
408
- referencedElement = (0, _apidomJsonPointer.evaluate)(referenceAsSchema, selector);
461
+ ({
462
+ element: referencedElement,
463
+ ancestorSchema$ids
464
+ } = (0, _jsonPointer.locate)(referenceAsSchema, selector));
409
465
  referencedElement = (0, _util2.maybeRefractToJSONSchemaElement)(referencedElement);
410
466
  }
411
467
  } else {
@@ -462,7 +518,11 @@ class Arazzo1DereferenceVisitor {
462
518
  */
463
519
  const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
464
520
  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)) {
521
+ // content transplanted into the referencing site is traversed under that
522
+ // site's $id chain; a fragment enclosed by a different chain has its own
523
+ // $refs resolved in place first, by a nested visitor seeded with that chain
524
+ 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()));
525
+ if ((isExternalReference || isNonEntryDocument || hasDifferentEnclosingBase || (0, _apidomNsArazzo.isJSONSchemaElement)(referencedElement) && (0, _apidomDatamodel.isStringElement)(referencedElement.$ref) || shouldDetectCircular && !this.visited.has(referencedElement)) && !ancestorsLineage.includesCycle(referencedElement)) {
466
526
  if (shouldDetectCircular) {
467
527
  this.visited.add(referencedElement);
468
528
  }
@@ -473,8 +533,10 @@ class Arazzo1DereferenceVisitor {
473
533
  reference,
474
534
  indirections: [...this.indirections],
475
535
  options: this.options,
536
+ schema$idIndex: this.schema$idIndex,
476
537
  visited: this.visited,
477
- ancestors: ancestorsLineage
538
+ ancestors: ancestorsLineage,
539
+ ancestorSchema$ids
478
540
  });
479
541
  referencedElement = await (0, _apidomTraverse.traverseAsync)(referencedElement, visitor, {
480
542
  mutable: true,