@speclynx/apidom-reference 6.1.2 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +57 -0
  3. package/dist/apidom-reference.browser.js +5783 -3741
  4. package/dist/apidom-reference.browser.min.js +1 -1
  5. package/package.json +31 -30
  6. package/src/File.cjs +5 -2
  7. package/src/File.mjs +5 -2
  8. package/src/bundle/index.cjs +6 -4
  9. package/src/bundle/index.mjs +6 -4
  10. package/src/bundle/strategies/arazzo-1/index.cjs +3 -2
  11. package/src/bundle/strategies/arazzo-1/index.mjs +3 -2
  12. package/src/bundle/strategies/asyncapi-2/index.cjs +3 -2
  13. package/src/bundle/strategies/asyncapi-2/index.mjs +3 -2
  14. package/src/bundle/strategies/openapi-2/index.cjs +3 -2
  15. package/src/bundle/strategies/openapi-2/index.mjs +3 -2
  16. package/src/bundle/strategies/openapi-3-0/index.cjs +3 -2
  17. package/src/bundle/strategies/openapi-3-0/index.mjs +3 -2
  18. package/src/bundle/strategies/openapi-3-1/index.cjs +3 -2
  19. package/src/bundle/strategies/openapi-3-1/index.mjs +3 -2
  20. package/src/dereference/index.cjs +8 -5
  21. package/src/dereference/index.mjs +8 -5
  22. package/src/dereference/strategies/DereferenceVisitor.cjs +588 -0
  23. package/src/dereference/strategies/DereferenceVisitor.mjs +580 -0
  24. package/src/dereference/strategies/arazzo-1/index.cjs +14 -39
  25. package/src/dereference/strategies/arazzo-1/index.mjs +15 -40
  26. package/src/dereference/strategies/arazzo-1/visitor.cjs +207 -331
  27. package/src/dereference/strategies/arazzo-1/visitor.mjs +215 -338
  28. package/src/dereference/strategies/asyncapi-2/index.cjs +11 -39
  29. package/src/dereference/strategies/asyncapi-2/index.mjs +11 -39
  30. package/src/dereference/strategies/asyncapi-2/visitor.cjs +133 -425
  31. package/src/dereference/strategies/asyncapi-2/visitor.mjs +137 -429
  32. package/src/dereference/strategies/openapi-2/index.cjs +12 -42
  33. package/src/dereference/strategies/openapi-2/index.mjs +12 -42
  34. package/src/dereference/strategies/openapi-2/visitor.cjs +168 -532
  35. package/src/dereference/strategies/openapi-2/visitor.mjs +172 -536
  36. package/src/dereference/strategies/openapi-3-0/index.cjs +12 -42
  37. package/src/dereference/strategies/openapi-3-0/index.mjs +12 -42
  38. package/src/dereference/strategies/openapi-3-0/visitor.cjs +194 -468
  39. package/src/dereference/strategies/openapi-3-0/visitor.mjs +199 -473
  40. package/src/dereference/strategies/openapi-3-1/index.cjs +16 -42
  41. package/src/dereference/strategies/openapi-3-1/index.mjs +16 -42
  42. package/src/dereference/strategies/openapi-3-1/visitor.cjs +350 -602
  43. package/src/dereference/strategies/openapi-3-1/visitor.mjs +357 -608
  44. package/src/dereference/strategies/overlay-1/index.cjs +3 -2
  45. package/src/dereference/strategies/overlay-1/index.mjs +3 -2
  46. package/src/index.cjs +0 -7
  47. package/src/index.mjs +0 -1
  48. package/src/options/index.cjs +4 -0
  49. package/src/options/index.mjs +4 -0
  50. package/src/options/util.cjs +7 -1
  51. package/src/options/util.mjs +8 -2
  52. package/src/parse/index.cjs +9 -1
  53. package/src/parse/index.mjs +9 -1
  54. package/src/parse/parsers/apidom-json/index.cjs +2 -1
  55. package/src/parse/parsers/apidom-json/index.mjs +2 -1
  56. package/src/parse/parsers/arazzo-json-1/index.cjs +2 -1
  57. package/src/parse/parsers/arazzo-json-1/index.mjs +2 -1
  58. package/src/parse/parsers/arazzo-yaml-1/index.cjs +2 -1
  59. package/src/parse/parsers/arazzo-yaml-1/index.mjs +2 -1
  60. package/src/parse/parsers/asyncapi-json-2/index.cjs +2 -1
  61. package/src/parse/parsers/asyncapi-json-2/index.mjs +2 -1
  62. package/src/parse/parsers/asyncapi-yaml-2/index.cjs +2 -1
  63. package/src/parse/parsers/asyncapi-yaml-2/index.mjs +2 -1
  64. package/src/parse/parsers/json/index.cjs +2 -1
  65. package/src/parse/parsers/json/index.mjs +2 -1
  66. package/src/parse/parsers/openapi-json-2/index.cjs +2 -1
  67. package/src/parse/parsers/openapi-json-2/index.mjs +2 -1
  68. package/src/parse/parsers/openapi-json-3-0/index.cjs +2 -1
  69. package/src/parse/parsers/openapi-json-3-0/index.mjs +2 -1
  70. package/src/parse/parsers/openapi-json-3-1/index.cjs +2 -1
  71. package/src/parse/parsers/openapi-json-3-1/index.mjs +2 -1
  72. package/src/parse/parsers/openapi-yaml-2/index.cjs +2 -1
  73. package/src/parse/parsers/openapi-yaml-2/index.mjs +2 -1
  74. package/src/parse/parsers/openapi-yaml-3-0/index.cjs +2 -1
  75. package/src/parse/parsers/openapi-yaml-3-0/index.mjs +2 -1
  76. package/src/parse/parsers/openapi-yaml-3-1/index.cjs +2 -1
  77. package/src/parse/parsers/openapi-yaml-3-1/index.mjs +2 -1
  78. package/src/parse/parsers/overlay-json-1/index.cjs +2 -1
  79. package/src/parse/parsers/overlay-json-1/index.mjs +2 -1
  80. package/src/parse/parsers/overlay-yaml-1/index.cjs +2 -1
  81. package/src/parse/parsers/overlay-yaml-1/index.mjs +2 -1
  82. package/src/parse/parsers/yaml-1-2/index.cjs +2 -1
  83. package/src/parse/parsers/yaml-1-2/index.mjs +2 -1
  84. package/src/resolve/index.cjs +6 -3
  85. package/src/resolve/index.mjs +6 -3
  86. package/src/util/url.cjs +38 -8
  87. package/src/util/url.mjs +35 -6
  88. package/types/apidom-reference.d.ts +13 -9
  89. package/types/dereference/strategies/DereferenceVisitor.d.ts +224 -0
  90. package/types/dereference/strategies/arazzo-1/index.d.ts +0 -1
  91. package/types/dereference/strategies/arazzo-1/visitor.d.ts +38 -38
  92. package/types/dereference/strategies/asyncapi-2/index.d.ts +0 -1
  93. package/types/dereference/strategies/asyncapi-2/visitor.d.ts +13 -29
  94. package/types/dereference/strategies/openapi-2/index.d.ts +0 -1
  95. package/types/dereference/strategies/openapi-2/visitor.d.ts +14 -29
  96. package/types/dereference/strategies/openapi-3-0/index.d.ts +0 -1
  97. package/types/dereference/strategies/openapi-3-0/visitor.d.ts +13 -28
  98. package/types/dereference/strategies/openapi-3-1/index.d.ts +0 -1
  99. package/types/dereference/strategies/openapi-3-1/visitor.d.ts +37 -33
  100. package/types/index.d.ts +0 -1
  101. package/types/util/url.d.ts +11 -0
  102. package/LICENSES/AFL-3.0.txt +0 -182
  103. package/LICENSES/BSD-3-Clause.txt +0 -26
  104. package/src/dereference/util.cjs +0 -33
  105. package/src/dereference/util.mjs +0 -27
  106. package/types/dereference/util.d.ts +0 -9
@@ -6,520 +6,228 @@ Object.defineProperty(exports, "__esModule", {
6
6
  value: true
7
7
  });
8
8
  exports.default = void 0;
9
- var _ramda = require("ramda");
10
9
  var _apidomDatamodel = require("@speclynx/apidom-datamodel");
11
10
  var _apidomCore = require("@speclynx/apidom-core");
12
11
  var _apidomError = require("@speclynx/apidom-error");
13
- var _apidomTraverse = require("@speclynx/apidom-traverse");
14
12
  var _apidomJsonPointer = require("@speclynx/apidom-json-pointer");
15
13
  var _apidomNsAsyncapi = require("@speclynx/apidom-ns-asyncapi-2");
16
- var _UnresolvableReferenceError = _interopRequireDefault(require("../../../errors/UnresolvableReferenceError.cjs"));
17
- var _MaximumDereferenceDepthError = _interopRequireDefault(require("../../../errors/MaximumDereferenceDepthError.cjs"));
18
- var _MaximumResolveDepthError = _interopRequireDefault(require("../../../errors/MaximumResolveDepthError.cjs"));
19
- var _util = require("../../util.cjs");
14
+ var _DereferenceVisitor = _interopRequireDefault(require("../DereferenceVisitor.cjs"));
20
15
  var url = _interopRequireWildcard(require("../../../util/url.cjs"));
21
- var _index = _interopRequireDefault(require("../../../parse/index.cjs"));
22
- var _Reference = _interopRequireDefault(require("../../../Reference.cjs"));
23
16
  /**
24
17
  * @public
25
18
  */
26
19
 
27
20
  /**
21
+ * Dereferences an AsyncAPI 2 document. Reference Objects expect the type of their
22
+ * `referenced-element` meta, Channel Item `$ref`s a Channel Item Object.
23
+ *
28
24
  * @public
29
25
  */
30
- class AsyncAPI2DereferenceVisitor {
31
- indirections;
32
- reference;
33
- options;
34
- refractCache;
35
- visited;
36
-
37
- /**
38
- * Tracks element ancestors across dive-deep traversal boundaries.
39
- * Used for cycle detection: if a referenced element is found in
40
- * the ancestor lineage, a circular reference is detected.
41
- */
42
- ancestors;
43
- constructor({
44
- reference,
45
- options,
46
- indirections = [],
47
- ancestors = new _util.AncestorLineage(),
48
- refractCache = new WeakMap(),
49
- visited = new WeakSet()
50
- }) {
51
- this.indirections = indirections;
52
- this.reference = reference;
53
- this.options = options;
54
- this.ancestors = new _util.AncestorLineage(...ancestors);
55
- this.refractCache = refractCache;
56
- this.visited = visited;
26
+ class AsyncAPI2DereferenceVisitor extends _DereferenceVisitor.default {
27
+ get strategyName() {
28
+ return 'asyncapi-2';
57
29
  }
58
- toAncestorLineage(path) {
59
- const ancestorNodes = path.getAncestorNodes();
60
- const directAncestors = new Set(ancestorNodes.filter(_apidomDatamodel.isElement));
61
- const ancestorsLineage = new _util.AncestorLineage(...this.ancestors, directAncestors);
62
- return [ancestorsLineage, directAncestors];
63
- }
64
- toBaseURI(uri) {
65
- return url.resolve(this.reference.uri, url.sanitize(url.stripHash(uri)));
66
- }
67
- async toReference(uri) {
68
- // detect maximum depth of resolution
69
- if (this.reference.depth >= this.options.resolve.maxDepth) {
70
- throw new _MaximumResolveDepthError.default(`Maximum resolution depth of ${this.options.resolve.maxDepth} has been exceeded by file "${this.reference.uri}"`, {
71
- maxDepth: this.options.resolve.maxDepth,
72
- uri: this.reference.uri
73
- });
74
- }
75
- const baseURI = this.toBaseURI(uri);
76
- const {
77
- refSet
78
- } = this.reference;
79
-
80
- // we've already processed this Reference in past
81
- if (refSet.has(baseURI)) {
82
- return refSet.find((0, _ramda.propEq)(baseURI, 'uri'));
83
- }
84
- const parseResult = await (0, _index.default)(url.unsanitize(baseURI), {
85
- ...this.options,
86
- parse: {
87
- ...this.options.parse,
88
- mediaType: 'text/plain'
89
- }
90
- });
91
-
92
- // register new mutable reference with a refSet
93
- //
94
- // NOTE(known limitation): the mutable reference is mutated in place during traversal
95
- // (via `{ mutable: true }`). When an external document evaluates a JSON pointer back
96
- // into this document, it may receive an already-resolved element instead of the original
97
- // $ref. That resolved element was produced using the entry document's resolution context
98
- // (ancestors, indirections), which may differ from the external document's context.
99
- // This can affect cycle detection in rare cross-document circular reference patterns.
100
- //
101
- // Remediation: evaluate JSON pointers against the immutable (original) parse tree
102
- // instead of the mutable working copy. The `immutable://` reference below preserves
103
- // the original tree and could be used for pointer evaluation, ensuring every resolution
104
- // context always sees raw, unresolved elements and processes them with its own
105
- // ancestors/indirections. The trade-off is that elements referenced by multiple
106
- // documents would be resolved once per context instead of being reused.
107
- const mutableReference = new _Reference.default({
108
- uri: baseURI,
109
- value: this.options.dereference.immutable ? (0, _apidomDatamodel.cloneDeep)(parseResult) : parseResult,
110
- depth: this.reference.depth + 1
30
+ createVisitor(reference) {
31
+ return new AsyncAPI2DereferenceVisitor({
32
+ reference,
33
+ options: this.options
111
34
  });
112
- refSet.add(mutableReference);
113
- if (this.options.dereference.immutable) {
114
- // register new immutable reference with a refSet
115
- const immutableReference = new _Reference.default({
116
- uri: `immutable://${baseURI}`,
117
- value: parseResult,
118
- depth: this.reference.depth + 1
119
- });
120
- refSet.add(immutableReference);
121
- }
122
- return mutableReference;
123
35
  }
124
36
 
125
37
  /**
126
- * Handles an error according to the continueOnError option.
127
- *
128
- * For new errors: wraps in UnresolvableReferenceError with structured context
129
- * (type, uri, location, codeFrame, refFieldName, refFieldValue, trace).
130
- * For errors already wrapped by a nested visitor: prepends the current hop to the trace.
131
- *
132
- * Inner/intermediate visitors always throw to let the trace accumulate.
133
- * Only the entry document visitor respects continueOnError (callback/swallow/throw).
38
+ * A Reference Object stands for the type its reference expects: it's dereferenced as that type.
134
39
  */
135
- handleError(message, error, referencingElement, refFieldName, refFieldValue, visitorPath) {
136
- const {
137
- continueOnError
138
- } = this.options.dereference;
139
- const isEntryDocument = url.stripHash(this.reference.refSet?.rootRef?.uri ?? '') === this.reference.uri;
140
- const uri = this.reference.uri;
141
- const type = referencingElement.element;
142
- const codeFrame = (0, _apidomCore.toYAML)(referencingElement);
143
-
144
- // find element location by identity in the document tree.
145
- // guarded: this.reference.value may not be a ParseResultElement or may lack a result.
146
- // falls back to visitorPath which may produce an incomplete path when
147
- // dereferenceApiDOM is called with a fragment (cloneShallow creates a new root identity).
148
- let location;
149
- const root = this.reference.value.result;
150
- if ((0, _apidomDatamodel.isElement)(root)) {
151
- (0, _apidomTraverse.traverse)(root, {
152
- enter: p => {
153
- if (p.node === referencingElement || this.refractCache.get(p.node) === referencingElement) {
154
- location = p.formatPath();
155
- p.stop();
156
- }
157
- }
158
- });
159
- }
160
- location ??= visitorPath.formatPath();
161
- const hop = {
162
- uri,
163
- type,
164
- refFieldName,
165
- refFieldValue,
166
- location,
167
- codeFrame
168
- };
40
+ isOfType(element, type) {
41
+ if ((0, _apidomNsAsyncapi.isReferenceElement)(element)) return true;
42
+ return type === 'schema' ? (0, _apidomNsAsyncapi.isSchemaElement)(element) || (0, _apidomNsAsyncapi.isBooleanJSONSchemaElement)(element) : element.element === type;
43
+ }
169
44
 
170
- // enrich existing error from nested visitor or create new one
171
- let unresolvedError;
172
- if (error instanceof _UnresolvableReferenceError.default) {
173
- // prefix relative locations for entries belonging to the referenced document
174
- const refBaseURI = this.toBaseURI(refFieldValue);
175
- const fragment = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference(refFieldValue);
176
- if (fragment) {
177
- if (refBaseURI === error.uri && error.location) {
178
- error.location = fragment + error.location;
179
- }
180
- for (const h of error.trace) {
181
- if (h.uri === refBaseURI && h.location) h.location = fragment + h.location;
182
- }
183
- }
184
- // @ts-ignore
185
- error.trace = [hop, ...error.trace];
186
- unresolvedError = error;
187
- } else {
188
- unresolvedError = new _UnresolvableReferenceError.default(message, {
189
- cause: error,
190
- type,
191
- uri,
192
- location,
193
- codeFrame,
194
- refFieldName,
195
- refFieldValue,
196
- trace: []
197
- });
45
+ /**
46
+ * A generic structure holding a `$ref` is a Reference Object to the expected type,
47
+ * unless the type has its own `$ref` field (Channel Item Object).
48
+ */
49
+ refractTo(element, type) {
50
+ if (type !== 'channelItem' && (0, _apidomNsAsyncapi.isReferenceLikeElement)(element)) {
51
+ const referenceElement = (0, _apidomNsAsyncapi.refractReference)(element);
52
+ referenceElement.meta.set('referenced-element', type);
53
+ return referenceElement;
198
54
  }
199
- if (!isEntryDocument || continueOnError === false) throw unresolvedError;
200
- if (typeof continueOnError === 'function') continueOnError(unresolvedError);
55
+ return (0, _apidomNsAsyncapi.refract)(element, {
56
+ element: type
57
+ });
201
58
  }
202
59
  async ReferenceElement(path) {
203
60
  const referencingElement = path.node;
204
61
 
205
- // ignore Reference Object without a string $ref, e.g. an empty YAML value
206
- if (!(0, _apidomDatamodel.isStringElement)(referencingElement.$ref)) {
207
- return;
208
- }
62
+ // the fields of a Reference Object hold no references
63
+ path.skip();
209
64
 
210
- // skip current referencing element as it's already been access
211
- if (this.indirections.includes(referencingElement)) {
212
- path.skip();
65
+ // ignore Reference Object without a string $ref, e.g. an empty YAML value
66
+ if (this.isTranscluded(referencingElement) || !(0, _apidomDatamodel.isStringElement)(referencingElement.$ref)) {
213
67
  return;
214
68
  }
215
- const retrievalURI = this.toBaseURI((0, _apidomCore.toValue)(referencingElement.$ref));
69
+ const $ref = (0, _apidomCore.toValue)(referencingElement.$ref);
70
+ const retrievalURI = this.toBaseURI($ref);
216
71
  const isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
217
72
  const isExternalReference = !isInternalReference;
218
73
 
219
74
  // ignore resolving internal Reference Objects
220
75
  if (!this.options.resolve.internal && isInternalReference) {
221
- // skip traversing this reference and all it's child elements
222
- path.skip();
223
76
  return;
224
77
  }
225
78
  // ignore resolving external Reference Objects
226
79
  if (!this.options.resolve.external && isExternalReference) {
227
- // skip traversing this reference and all it's child elements
228
- path.skip();
229
80
  return;
230
81
  }
231
- const $refBaseURI = url.resolve(retrievalURI, (0, _apidomCore.toValue)(referencingElement.$ref));
232
- const indirectionsSize = this.indirections.length;
82
+ const $refBaseURI = url.resolve(retrievalURI, $ref);
233
83
  try {
234
- const reference = await this.toReference((0, _apidomCore.toValue)(referencingElement.$ref));
235
- this.indirections.push(referencingElement);
84
+ const reference = await this.toReference($ref);
236
85
  const jsonPointer = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference($refBaseURI);
237
-
238
86
  // possibly non-semantic fragment
239
- let referencedElement = (0, _apidomJsonPointer.evaluate)(reference.value.result, jsonPointer);
240
-
241
- // applying semantics to a fragment
242
- const referencedElementType = referencingElement.meta.get('referenced-element');
243
- if (referencedElement.element !== referencedElementType && !(0, _apidomNsAsyncapi.isReferenceElement)(referencedElement)) {
244
- if (this.refractCache.has(referencedElement)) {
245
- referencedElement = this.refractCache.get(referencedElement);
246
- } else if ((0, _apidomNsAsyncapi.isReferenceLikeElement)(referencedElement)) {
247
- // handling generic indirect references
248
- const sourceElement = referencedElement;
249
- referencedElement = (0, _apidomNsAsyncapi.refractReference)(referencedElement);
250
- referencedElement.meta.set('referenced-element', referencedElementType);
251
- this.refractCache.set(sourceElement, referencedElement);
252
- } else {
253
- // handling direct references
254
- const sourceElement = referencedElement;
255
- referencedElement = (0, _apidomNsAsyncapi.refract)(referencedElement, {
256
- element: referencedElementType
257
- });
258
- this.refractCache.set(sourceElement, referencedElement);
259
- }
260
- }
87
+ const referencedElement = (0, _apidomJsonPointer.evaluate)(reference.value.result, jsonPointer);
261
88
 
262
- // detect direct or circular reference
89
+ // detect direct reference
263
90
  if (referencingElement === referencedElement) {
264
91
  throw new _apidomError.ApiDOMStructuredError('Recursive Reference Object detected', {
265
- $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
92
+ $ref
266
93
  });
267
94
  }
268
95
 
269
- // detect maximum depth of dereferencing
270
- if (this.indirections.length > this.options.dereference.maxDepth) {
271
- throw new _MaximumDereferenceDepthError.default(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`, {
272
- maxDepth: this.options.dereference.maxDepth,
273
- uri: this.reference.uri
274
- });
275
- }
96
+ // a reference without an expected type reads its target as its own type
97
+ const type = (0, _apidomCore.toValue)(referencingElement.meta.get('referenced-element')) ?? referencedElement.element;
98
+ await this.addTransclusion(path, {
99
+ source: referencedElement,
100
+ type,
101
+ reference,
102
+ context: undefined
103
+ }, {
104
+ toTranscludedElement: (element, target) => {
105
+ // a chain of Reference Objects leading back to itself never reaches a value
106
+ if ((0, _apidomNsAsyncapi.isReferenceElement)(target)) return element;
107
+
108
+ // Boolean JSON Schemas
109
+ if ((0, _apidomNsAsyncapi.isBooleanJSONSchemaElement)(target)) {
110
+ const booleanJsonSchemaElement = (0, _apidomDatamodel.cloneDeep)(target);
111
+ // annotate referenced element with info about original referencing element
112
+ booleanJsonSchemaElement.meta.set('ref-fields', {
113
+ $ref
114
+ });
115
+ // annotate referenced element with info about origin
116
+ booleanJsonSchemaElement.meta.set('ref-origin', reference.uri);
117
+ booleanJsonSchemaElement.meta.set('ref-type', element.element);
118
+ return booleanJsonSchemaElement;
119
+ }
276
120
 
277
- // detect second deep dive into the same fragment and avoid it
278
- const [ancestorsLineage, directAncestors] = this.toAncestorLineage(path);
279
- if (ancestorsLineage.includes(referencedElement)) {
280
- reference.refSet.circular = true;
281
- if (this.options.dereference.circular === 'error') {
282
- throw new _apidomError.ApiDOMStructuredError('Circular reference detected', {
283
- $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
121
+ /**
122
+ * Creating a new version of referenced element to avoid modifying the original one.
123
+ */
124
+ const mergedElement = (0, _apidomDatamodel.cloneShallow)(target);
125
+ // annotate referenced element with info about original referencing element
126
+ mergedElement.meta.set('ref-fields', {
127
+ $ref
284
128
  });
285
- } else if (this.options.dereference.circular === 'replace') {
286
- const refElement = new _apidomDatamodel.RefElement($refBaseURI, {
287
- type: referencingElement.element,
129
+ // annotate fragment with info about origin
130
+ mergedElement.meta.set('ref-origin', reference.uri);
131
+ mergedElement.meta.set('ref-type', element.element);
132
+ return mergedElement;
133
+ },
134
+ circular: {
135
+ uri: $refBaseURI,
136
+ meta: {
288
137
  uri: reference.uri,
289
- $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
290
- });
291
- const replacer = this.options.dereference.strategyOpts['asyncapi-2']?.circularReplacer ?? this.options.dereference.circularReplacer;
292
- const replacement = replacer(refElement);
293
- path.replaceWith(replacement);
294
- path.skip();
295
- return;
296
- }
297
- }
298
-
299
- /**
300
- * Dive deep into the fragment.
301
- *
302
- * Cases to consider:
303
- * 1. We're crossing document boundary
304
- * 2. Fragment is from non-entry document
305
- * 3. Fragment is a Reference Object. We need to follow it to get the eventual value
306
- * 4. We are dereferencing the fragment lazily/eagerly depending on circular mode
307
- */
308
- const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
309
- const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
310
- if ((isExternalReference || isNonEntryDocument || (0, _apidomNsAsyncapi.isReferenceElement)(referencedElement) || shouldDetectCircular && !this.visited.has(referencedElement)) && !ancestorsLineage.includesCycle(referencedElement)) {
311
- if (shouldDetectCircular) {
312
- this.visited.add(referencedElement);
138
+ $ref
139
+ },
140
+ details: {
141
+ $ref
142
+ }
313
143
  }
314
-
315
- // append referencing reference to ancestors lineage
316
- directAncestors.add(referencingElement);
317
- const visitor = new AsyncAPI2DereferenceVisitor({
318
- reference,
319
- indirections: [...this.indirections],
320
- options: this.options,
321
- refractCache: this.refractCache,
322
- visited: this.visited,
323
- ancestors: ancestorsLineage
324
- });
325
- referencedElement = await (0, _apidomTraverse.traverseAsync)(referencedElement, visitor, {
326
- mutable: true,
327
- skipVisited: 'skip'
328
- });
329
-
330
- // remove referencing reference from ancestors lineage
331
- directAncestors.delete(referencingElement);
332
- }
333
-
334
- // Boolean JSON Schemas
335
- if ((0, _apidomNsAsyncapi.isBooleanJSONSchemaElement)(referencedElement)) {
336
- const booleanJsonSchemaElement = (0, _apidomDatamodel.cloneDeep)(referencedElement);
337
- // annotate referenced element with info about original referencing element
338
- booleanJsonSchemaElement.meta.set('ref-fields', {
339
- $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
340
- });
341
- // annotate referenced element with info about origin
342
- booleanJsonSchemaElement.meta.set('ref-origin', reference.uri);
343
- booleanJsonSchemaElement.meta.set('ref-type', referencingElement.element);
344
- path.replaceWith(booleanJsonSchemaElement);
345
- path.skip();
346
- return;
347
- }
348
-
349
- /**
350
- * Creating a new version of referenced element to avoid modifying the original one.
351
- */
352
- const mergedElement = (0, _apidomDatamodel.cloneShallow)(referencedElement);
353
- // annotate referenced element with info about original referencing element
354
- mergedElement.meta.set('ref-fields', {
355
- $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
356
144
  });
357
- // annotate fragment with info about origin
358
- mergedElement.meta.set('ref-origin', reference.uri);
359
- mergedElement.meta.set('ref-type', referencingElement.element);
360
-
361
- /**
362
- * Transclude referencing element with merged referenced element.
363
- */
364
- path.replaceWith(mergedElement);
365
- path.skip();
366
145
  } catch (error) {
367
- const $ref = (0, _apidomCore.toValue)(referencingElement.$ref);
368
146
  this.handleError(`Error while dereferencing Reference Object. Cannot resolve $ref "${$ref}": ${error.message}`, error, referencingElement, '$ref', $ref, path);
369
- } finally {
370
- if (this.indirections.length > indirectionsSize) this.indirections.pop();
371
147
  }
372
148
  }
373
149
  async ChannelItemElement(path) {
374
150
  const referencingElement = path.node;
375
-
376
- // ignore ChannelItemElement without $ref field
377
- if (!(0, _apidomDatamodel.isStringElement)(referencingElement.$ref)) {
151
+ if (this.isTranscluded(referencingElement)) {
152
+ path.skip();
378
153
  return;
379
154
  }
380
155
 
381
- // skip current referencing element as it's already been access
382
- if (this.indirections.includes(referencingElement)) {
383
- path.skip();
156
+ // ignore ChannelItemElement without $ref field
157
+ if (!(0, _apidomDatamodel.isStringElement)(referencingElement.$ref)) {
384
158
  return;
385
159
  }
386
- const retrievalURI = this.toBaseURI((0, _apidomCore.toValue)(referencingElement.$ref));
160
+ const $ref = (0, _apidomCore.toValue)(referencingElement.$ref);
161
+ const retrievalURI = this.toBaseURI($ref);
387
162
  const isInternalReference = url.stripHash(this.reference.uri) === retrievalURI;
388
163
  const isExternalReference = !isInternalReference;
389
164
 
390
165
  // ignore resolving internal Channel Item Objects
391
166
  if (!this.options.resolve.internal && isInternalReference) {
392
- // skip traversing this channel item but traverse all it's child elements
167
+ // skip traversing this Channel Item element but traverse all it's child elements
393
168
  return;
394
169
  }
395
170
  // ignore resolving external Channel Item Objects
396
171
  if (!this.options.resolve.external && isExternalReference) {
397
- // skip traversing this channel item but traverse all it's child elements
172
+ // skip traversing this Channel Item element but traverse all it's child elements
398
173
  return;
399
174
  }
400
- const $refBaseURI = url.resolve(retrievalURI, (0, _apidomCore.toValue)(referencingElement.$ref));
401
- const indirectionsSize = this.indirections.length;
175
+ const $refBaseURI = url.resolve(retrievalURI, $ref);
402
176
  try {
403
- const reference = await this.toReference((0, _apidomCore.toValue)(referencingElement.$ref));
404
- this.indirections.push(referencingElement);
177
+ const reference = await this.toReference($ref);
405
178
  const jsonPointer = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference($refBaseURI);
406
-
407
179
  // possibly non-semantic referenced element
408
- let referencedElement = (0, _apidomJsonPointer.evaluate)(reference.value.result, jsonPointer);
409
-
410
- // applying semantics to a referenced element
411
- if (!(0, _apidomNsAsyncapi.isChannelItemElement)(referencedElement)) {
412
- if (this.refractCache.has(referencedElement)) {
413
- referencedElement = this.refractCache.get(referencedElement);
414
- } else {
415
- const sourceElement = referencedElement;
416
- referencedElement = (0, _apidomNsAsyncapi.refractChannelItem)(referencedElement);
417
- this.refractCache.set(sourceElement, referencedElement);
418
- }
419
- }
180
+ const referencedElement = (0, _apidomJsonPointer.evaluate)(reference.value.result, jsonPointer);
420
181
 
421
- // detect direct or indirect reference
182
+ // detect direct reference
422
183
  if (referencingElement === referencedElement) {
423
184
  throw new _apidomError.ApiDOMStructuredError('Recursive Channel Item Object reference detected', {
424
- $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
425
- });
426
- }
427
-
428
- // detect maximum depth of dereferencing
429
- if (this.indirections.length > this.options.dereference.maxDepth) {
430
- throw new _MaximumDereferenceDepthError.default(`Maximum dereference depth of "${this.options.dereference.maxDepth}" has been exceeded in file "${this.reference.uri}"`, {
431
- maxDepth: this.options.dereference.maxDepth,
432
- uri: this.reference.uri
185
+ $ref
433
186
  });
434
187
  }
188
+ await this.addTransclusion(path, {
189
+ source: referencedElement,
190
+ type: 'channelItem',
191
+ reference,
192
+ context: undefined
193
+ }, {
194
+ toTranscludedElement: (element, target) => {
195
+ if (!(0, _apidomNsAsyncapi.isChannelItemElement)(target)) return target;
196
+
197
+ /**
198
+ * Creating a new version of Channel Item by merging fields from referenced Channel Item with referencing one.
199
+ */
200
+ const mergedElement = (0, _apidomDatamodel.cloneShallow)(target);
201
+ // existing keywords from referencing ChannelItemElement overrides ones from referenced element
202
+ element.forEach((value, keyElement, item) => {
203
+ mergedElement.remove((0, _apidomCore.toValue)(keyElement));
204
+ mergedElement.content.push(item);
205
+ });
206
+ mergedElement.remove('$ref');
435
207
 
436
- // detect second deep dive into the same fragment and avoid it
437
- const [ancestorsLineage, directAncestors] = this.toAncestorLineage(path);
438
- if (ancestorsLineage.includes(referencedElement)) {
439
- reference.refSet.circular = true;
440
- if (this.options.dereference.circular === 'error') {
441
- throw new _apidomError.ApiDOMStructuredError('Circular reference detected', {
442
- $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
208
+ // annotate referenced element with info about original referencing element
209
+ mergedElement.meta.set('ref-fields', {
210
+ $ref
443
211
  });
444
- } else if (this.options.dereference.circular === 'replace') {
445
- const refElement = new _apidomDatamodel.RefElement($refBaseURI, {
446
- type: referencingElement.element,
212
+ // annotate referenced element with info about origin and type
213
+ mergedElement.meta.set('ref-origin', reference.uri);
214
+ mergedElement.meta.set('ref-type', element.element);
215
+ return mergedElement;
216
+ },
217
+ aliasField: '$ref',
218
+ circular: {
219
+ uri: $refBaseURI,
220
+ meta: {
447
221
  uri: reference.uri,
448
- $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
449
- });
450
- const replacer = this.options.dereference.strategyOpts['asyncapi-2']?.circularReplacer ?? this.options.dereference.circularReplacer;
451
- const replacement = replacer(refElement);
452
- path.replaceWith(replacement);
453
- return;
454
- }
455
- }
456
-
457
- /**
458
- * Dive deep into the fragment.
459
- *
460
- * Cases to consider:
461
- * 1. We're crossing document boundary
462
- * 2. Fragment is from non-entry document
463
- * 3. Fragment is a Channel Item Object with $ref field. We need to follow it to get the eventual value
464
- * 4. We are dereferencing the fragment lazily/eagerly depending on circular mode
465
- */
466
- const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
467
- const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
468
- if ((isExternalReference || isNonEntryDocument || (0, _apidomNsAsyncapi.isChannelItemElement)(referencedElement) && (0, _apidomDatamodel.isStringElement)(referencedElement.$ref) || shouldDetectCircular && !this.visited.has(referencedElement)) && !ancestorsLineage.includesCycle(referencedElement)) {
469
- if (shouldDetectCircular) {
470
- this.visited.add(referencedElement);
222
+ $ref
223
+ },
224
+ details: {
225
+ $ref
226
+ }
471
227
  }
472
-
473
- // append referencing reference to ancestors lineage
474
- directAncestors.add(referencingElement);
475
- const visitor = new AsyncAPI2DereferenceVisitor({
476
- reference,
477
- indirections: [...this.indirections],
478
- options: this.options,
479
- refractCache: this.refractCache,
480
- visited: this.visited,
481
- ancestors: ancestorsLineage
482
- });
483
- referencedElement = await (0, _apidomTraverse.traverseAsync)(referencedElement, visitor, {
484
- mutable: true,
485
- skipVisited: 'skip'
486
- });
487
-
488
- // remove referencing reference from ancestors lineage
489
- directAncestors.delete(referencingElement);
490
- }
491
-
492
- /**
493
- * Creating a new version of Channel Item by merging fields from referenced Channel Item with referencing one.
494
- */
495
- if ((0, _apidomNsAsyncapi.isChannelItemElement)(referencedElement)) {
496
- const mergedElement = (0, _apidomDatamodel.cloneShallow)(referencedElement);
497
- // existing keywords from referencing ChannelItemElement overrides ones from referenced ChannelItemElement
498
- referencingElement.forEach((value, keyElement, item) => {
499
- mergedElement.remove((0, _apidomCore.toValue)(keyElement));
500
- mergedElement.content.push(item);
501
- });
502
- mergedElement.remove('$ref');
503
-
504
- // annotate referenced element with info about original referencing element
505
- mergedElement.meta.set('ref-fields', {
506
- $ref: (0, _apidomCore.toValue)(referencingElement.$ref)
507
- });
508
- // annotate referenced with info about origin
509
- mergedElement.meta.set('ref-origin', reference.uri);
510
- mergedElement.meta.set('ref-type', referencingElement.element);
511
- referencedElement = mergedElement;
512
- }
513
-
514
- /**
515
- * Transclude referencing element with merged referenced element.
516
- */
517
- path.replaceWith(referencedElement);
228
+ });
518
229
  } catch (error) {
519
- const $ref = (0, _apidomCore.toValue)(referencingElement.$ref);
520
230
  this.handleError(`Error while dereferencing Channel Item Object. Cannot resolve $ref "${$ref}": ${error.message}`, error, referencingElement, '$ref', $ref, path);
521
- } finally {
522
- if (this.indirections.length > indirectionsSize) this.indirections.pop();
523
231
  }
524
232
  }
525
233
  }