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