@speclynx/apidom-reference 4.15.0 → 5.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.
@@ -0,0 +1,571 @@
1
+ "use strict";
2
+
3
+ var _interopRequireWildcard = require("@babel/runtime-corejs3/helpers/interopRequireWildcard").default;
4
+ var _interopRequireDefault = require("@babel/runtime-corejs3/helpers/interopRequireDefault").default;
5
+ Object.defineProperty(exports, "__esModule", {
6
+ value: true
7
+ });
8
+ exports.default = void 0;
9
+ var _ramda = require("ramda");
10
+ var _apidomDatamodel = require("@speclynx/apidom-datamodel");
11
+ var _apidomCore = require("@speclynx/apidom-core");
12
+ var _apidomTraverse = require("@speclynx/apidom-traverse");
13
+ var _apidomJsonPointer = require("@speclynx/apidom-json-pointer");
14
+ var _apidomNsAsyncapi = require("@speclynx/apidom-ns-asyncapi-2");
15
+ var _BundleError = _interopRequireDefault(require("../../../errors/BundleError.cjs"));
16
+ var _UnresolvableReferenceError = _interopRequireDefault(require("../../../errors/UnresolvableReferenceError.cjs"));
17
+ var _MaximumBundleDepthError = _interopRequireDefault(require("../../../errors/MaximumBundleDepthError.cjs"));
18
+ var _MaximumResolveDepthError = _interopRequireDefault(require("../../../errors/MaximumResolveDepthError.cjs"));
19
+ var url = _interopRequireWildcard(require("../../../util/url.cjs"));
20
+ var _index = _interopRequireDefault(require("../../../parse/index.cjs"));
21
+ var _Reference = _interopRequireDefault(require("../../../Reference.cjs"));
22
+ var _util = require("../../util.cjs");
23
+ /**
24
+ * Maps the `referenced-element` meta value (set during refraction) to the
25
+ * Components Object field where the referenced element should be hoisted.
26
+ * Field names are sourced from the Components Object fixed fields so they
27
+ * cannot drift from the namespace definition.
28
+ */
29
+ const cf = (0, _apidomCore.fixedFields)(_apidomNsAsyncapi.ComponentsElement, {
30
+ indexed: true
31
+ });
32
+ const componentFieldByReferencedElement = {
33
+ schema: cf.schemas.name,
34
+ server: cf.servers.name,
35
+ serverVariable: cf.serverVariables.name,
36
+ channelItem: cf.channels.name,
37
+ message: cf.messages.name,
38
+ securityScheme: cf.securitySchemes.name,
39
+ parameter: cf.parameters.name,
40
+ correlationID: cf.correlationIds.name,
41
+ operationTrait: cf.operationTraits.name,
42
+ messageTrait: cf.messageTraits.name,
43
+ serverBindings: cf.serverBindings.name,
44
+ channelBindings: cf.channelBindings.name,
45
+ operationBindings: cf.operationBindings.name,
46
+ messageBindings: cf.messageBindings.name
47
+ };
48
+
49
+ /**
50
+ * Maps a Components Object field to the named-content element the namespace
51
+ * uses for it, so a bundle-created field matches a parsed/refracted one
52
+ * (e.g. carries the `components-schemas` class) rather than being a generic
53
+ * object.
54
+ */
55
+ const componentFieldElementByField = {
56
+ [cf.schemas.name]: _apidomNsAsyncapi.ComponentsSchemasElement,
57
+ [cf.servers.name]: _apidomNsAsyncapi.ComponentsServersElement,
58
+ [cf.serverVariables.name]: _apidomNsAsyncapi.ComponentsServerVariablesElement,
59
+ [cf.channels.name]: _apidomNsAsyncapi.ComponentsChannelsElement,
60
+ [cf.messages.name]: _apidomNsAsyncapi.ComponentsMessagesElement,
61
+ [cf.securitySchemes.name]: _apidomNsAsyncapi.ComponentsSecuritySchemesElement,
62
+ [cf.parameters.name]: _apidomNsAsyncapi.ComponentsParametersElement,
63
+ [cf.correlationIds.name]: _apidomNsAsyncapi.ComponentsCorrelationIDsElement,
64
+ [cf.operationTraits.name]: _apidomNsAsyncapi.ComponentsOperationTraitsElement,
65
+ [cf.messageTraits.name]: _apidomNsAsyncapi.ComponentsMessageTraitsElement,
66
+ [cf.serverBindings.name]: _apidomNsAsyncapi.ComponentsServerBindingsElement,
67
+ [cf.channelBindings.name]: _apidomNsAsyncapi.ComponentsChannelBindingsElement,
68
+ [cf.operationBindings.name]: _apidomNsAsyncapi.ComponentsOperationBindingsElement,
69
+ [cf.messageBindings.name]: _apidomNsAsyncapi.ComponentsMessageBindingsElement
70
+ };
71
+
72
+ /**
73
+ * @public
74
+ */
75
+
76
+ /**
77
+ * @public
78
+ */
79
+ class AsyncAPI2BundleVisitor {
80
+ reference;
81
+ options;
82
+
83
+ /**
84
+ * The base URI (without hash) of the entry document. A reference is
85
+ * considered internal when it resolves to this URI and external otherwise.
86
+ * Derived from the shared refSet's root reference.
87
+ */
88
+ get entryURI() {
89
+ return url.stripHash(this.reference.refSet?.rootRef?.uri ?? '');
90
+ }
91
+
92
+ /**
93
+ * The entry parse result. Derived from the shared refSet's root reference.
94
+ */
95
+ get entryParseResult() {
96
+ return this.reference.refSet.rootRef.value;
97
+ }
98
+
99
+ /**
100
+ * The entry document element. Hoisted external fragments are placed into its
101
+ * `components` object. The `canBundle` guard guarantees it is an
102
+ * AsyncApi2Element.
103
+ */
104
+ get entryResult() {
105
+ return this.entryParseResult.result;
106
+ }
107
+
108
+ /**
109
+ * Shared across the entry document and every external document visitor.
110
+ * Guarantees each external fragment is hoisted exactly once and gives us a
111
+ * stable internal pointer to rewrite every referencing element to.
112
+ */
113
+ assignments;
114
+
115
+ /**
116
+ * Component names already reserved per Components Object field. Reserved
117
+ * before recursion so collision suffixing (`Pet`, `Pet-2`, ...) accounts for
118
+ * components that are still being bundled.
119
+ */
120
+ reservedNames;
121
+
122
+ /**
123
+ * Caches refracted fragments keyed by source element and target element type.
124
+ * The type is part of the key because the same source can be refracted into
125
+ * different semantic elements depending on the referencing context (e.g. a
126
+ * generic object referenced once as a Parameter and once as a Schema).
127
+ */
128
+ refractCache;
129
+ constructor({
130
+ reference,
131
+ options,
132
+ assignments = new Map(),
133
+ reservedNames = new Map(),
134
+ refractCache = new WeakMap()
135
+ }) {
136
+ this.reference = reference;
137
+ this.options = options;
138
+ this.assignments = assignments;
139
+ this.reservedNames = reservedNames;
140
+ this.refractCache = refractCache;
141
+ }
142
+ getRefracted(source, type) {
143
+ return this.refractCache.get(source)?.get(type);
144
+ }
145
+ setRefracted(source, type, refracted) {
146
+ let byType = this.refractCache.get(source);
147
+ if (byType === undefined) {
148
+ byType = new Map();
149
+ this.refractCache.set(source, byType);
150
+ }
151
+ byType.set(type, refracted);
152
+ }
153
+ toBaseURI(uri) {
154
+ return url.resolve(this.reference.uri, url.sanitize(url.stripHash(uri)));
155
+ }
156
+ async toReference(uri) {
157
+ // detect maximum depth of resolution
158
+ if (this.reference.depth >= this.options.resolve.maxDepth) {
159
+ throw new _MaximumResolveDepthError.default(`Maximum resolution depth of ${this.options.resolve.maxDepth} has been exceeded by file "${this.reference.uri}"`, {
160
+ maxDepth: this.options.resolve.maxDepth,
161
+ uri: this.reference.uri
162
+ });
163
+ }
164
+ const baseURI = this.toBaseURI(uri);
165
+ const {
166
+ refSet
167
+ } = this.reference;
168
+
169
+ // we've already processed this Reference in past
170
+ if (refSet.has(baseURI)) {
171
+ return refSet.find((0, _ramda.propEq)(baseURI, 'uri'));
172
+ }
173
+ const parseResult = await (0, _index.default)(url.unsanitize(baseURI), {
174
+ ...this.options,
175
+ parse: {
176
+ ...this.options.parse,
177
+ mediaType: 'text/plain'
178
+ }
179
+ });
180
+ const reference = new _Reference.default({
181
+ uri: baseURI,
182
+ value: parseResult,
183
+ depth: this.reference.depth + 1
184
+ });
185
+ refSet.add(reference);
186
+ return reference;
187
+ }
188
+
189
+ /**
190
+ * Normalizes a self-reference (e.g. `./root.json#/components/schemas/Pet`)
191
+ * to a bare fragment (`#/components/schemas/Pet`) so the bundled document stays
192
+ * transferable. Bare fragments are returned unchanged.
193
+ */
194
+ normalizeSelfReference(ref) {
195
+ return ref.startsWith('#') ? ref : url.getHash(ref);
196
+ }
197
+
198
+ /**
199
+ * Handles an error according to the `bundle.continueOnError` option.
200
+ *
201
+ * For new errors: wraps in UnresolvableReferenceError with structured
202
+ * context. For errors already wrapped by a nested visitor: prepends the
203
+ * current hop to the trace.
204
+ *
205
+ * Inner/intermediate visitors always throw to let the trace accumulate. Only
206
+ * the entry document visitor respects continueOnError (callback/swallow/throw).
207
+ */
208
+ handleError(message, error, referencingElement, refFieldName, refFieldValue, visitorPath) {
209
+ // a collision reported as `error` is a deliberate hard stop with no
210
+ // resolution-failure analog, so pass it through unchanged. A maxDepth
211
+ // breach (MaximumBundleDepthError, which also extends BundleError) is a
212
+ // resolution failure and flows through below — wrapped and subject to
213
+ // continueOnError, like dereferencing.
214
+ if (error instanceof _BundleError.default && !(error instanceof _MaximumBundleDepthError.default)) {
215
+ throw error;
216
+ }
217
+ const {
218
+ continueOnError
219
+ } = this.options.bundle;
220
+ const isEntryDocument = url.stripHash(this.reference.refSet?.rootRef?.uri ?? '') === this.reference.uri;
221
+ const uri = this.reference.uri;
222
+ const type = referencingElement.element;
223
+ const codeFrame = (0, _apidomCore.toYAML)(referencingElement);
224
+ const location = visitorPath.formatPath();
225
+ const hop = {
226
+ uri,
227
+ type,
228
+ refFieldName,
229
+ refFieldValue,
230
+ location,
231
+ codeFrame
232
+ };
233
+ let unresolvedError;
234
+ if (error instanceof _UnresolvableReferenceError.default) {
235
+ // @ts-ignore
236
+ error.trace = [hop, ...error.trace];
237
+ unresolvedError = error;
238
+ } else {
239
+ unresolvedError = new _UnresolvableReferenceError.default(message, {
240
+ cause: error,
241
+ type,
242
+ uri,
243
+ location,
244
+ codeFrame,
245
+ refFieldName,
246
+ refFieldValue,
247
+ trace: []
248
+ });
249
+ }
250
+ if (!isEntryDocument || continueOnError === false) throw unresolvedError;
251
+ if (typeof continueOnError === 'function') continueOnError(unresolvedError);
252
+ }
253
+
254
+ /**
255
+ * Lazily creates the Components Object (and the requested field within it)
256
+ * on the entry document, returning the field where a fragment is hoisted.
257
+ */
258
+ ensureComponentsField(field) {
259
+ const entryResult = this.entryResult;
260
+ let components = entryResult.components;
261
+ if (!(0, _apidomDatamodel.isObjectElement)(components)) {
262
+ components = new _apidomNsAsyncapi.ComponentsElement();
263
+ entryResult.components = components;
264
+ }
265
+ let fieldElement = components.get(field);
266
+ if (!(0, _apidomDatamodel.isObjectElement)(fieldElement)) {
267
+ const FieldElement = componentFieldElementByField[field] ?? _apidomDatamodel.ObjectElement;
268
+ fieldElement = new FieldElement();
269
+ components.set(field, fieldElement);
270
+ }
271
+ return fieldElement;
272
+ }
273
+
274
+ /**
275
+ * Derives a component name from the referenced JSON Pointer's last token,
276
+ * falling back to the referenced file's basename.
277
+ */
278
+ basenameOf(jsonPointer, baseURI) {
279
+ const tokens = (0, _apidomJsonPointer.parse)(jsonPointer).tree;
280
+ const lastToken = tokens.length > 0 ? tokens[tokens.length - 1] : '';
281
+ return lastToken || url.getBasename(baseURI) || 'Schema';
282
+ }
283
+
284
+ /**
285
+ * Computes the base component name (before collision suffixing) according to
286
+ * the configured naming strategy.
287
+ */
288
+ baseName(element, field, jsonPointer, baseURI) {
289
+ // strategy specific options take precedence over the top-level bundle options
290
+ const componentNamesStrategy = this.options.bundle.strategyOpts['asyncapi-2']?.componentNamesStrategy ?? this.options.bundle.componentNamesStrategy;
291
+ if (typeof componentNamesStrategy === 'function') {
292
+ const resolved = componentNamesStrategy({
293
+ element,
294
+ field,
295
+ jsonPointer,
296
+ baseURI
297
+ });
298
+ if (typeof resolved === 'string' && resolved !== '') {
299
+ return resolved;
300
+ }
301
+ return this.basenameOf(jsonPointer, baseURI);
302
+ }
303
+ if (componentNamesStrategy === 'title') {
304
+ const title = (0, _apidomDatamodel.isObjectElement)(element) ? (0, _apidomCore.toValue)(element.get('title')) : undefined;
305
+ if (typeof title === 'string' && title.trim() !== '') {
306
+ const name = (0, _util.sanitizeComponentName)((0, _util.toPascalCase)(title));
307
+ if (name !== '') return name;
308
+ }
309
+ // fall back to basename when no usable title is present
310
+ }
311
+ return this.basenameOf(jsonPointer, baseURI);
312
+ }
313
+
314
+ /**
315
+ * Computes a collision-free component name within the target field. A name is
316
+ * taken if it's already placed in components or reserved by a component that
317
+ * is still being bundled (reserved before recursion).
318
+ */
319
+ uniqueName(candidate, field) {
320
+ const fieldElement = this.ensureComponentsField(field);
321
+ const reserved = this.reservedNames.get(field) ?? new Set();
322
+ return (0, _util.uniqueName)(candidate, name => fieldElement.hasKey(name) || reserved.has(name));
323
+ }
324
+
325
+ /**
326
+ * Reports a collision-forced rename according to the configured severity.
327
+ * A rename means two distinct targets resolved to the same name; each keeps
328
+ * its own component (and origin), so report the rename per severity.
329
+ */
330
+ reportNameCollision(preferredName, componentName, field) {
331
+ // strategy specific options take precedence over the top-level bundle options
332
+ const onComponentNameCollision = this.options.bundle.strategyOpts['asyncapi-2']?.onComponentNameCollision ?? this.options.bundle.onComponentNameCollision;
333
+ if (componentName === preferredName || onComponentNameCollision === 'off') {
334
+ return;
335
+ }
336
+ const message = `Component "${preferredName}" in components/${field} is referenced with the same name but different content. Renamed to "${componentName}".`;
337
+ if (onComponentNameCollision === 'error') {
338
+ throw new _BundleError.default(message);
339
+ }
340
+ const annotation = new _apidomDatamodel.AnnotationElement(message);
341
+ annotation.classes.push('warning');
342
+ annotation.code = 'bundle-component-name-collision';
343
+ // append (never prepend) so the result element stays ahead of annotations
344
+ this.entryParseResult.content.push(annotation);
345
+ }
346
+ async ReferenceElement(path) {
347
+ const referencingElement = path.node;
348
+ const $ref = (0, _apidomCore.toValue)(referencingElement.$ref);
349
+ const retrievalURI = this.toBaseURI($ref);
350
+ const isInternalReference = this.entryURI === retrievalURI;
351
+ const isExternalReference = !isInternalReference;
352
+ if (isInternalReference) {
353
+ // normalize self-references to a bare fragment so the bundled
354
+ // document stays transferable; bare fragments are left untouched
355
+ referencingElement.set('$ref', this.normalizeSelfReference($ref));
356
+ path.skip();
357
+ return;
358
+ }
359
+
360
+ // honor the resolve.external switch
361
+ if (!this.options.resolve.external && isExternalReference) {
362
+ path.skip();
363
+ return;
364
+ }
365
+ const $refBaseURI = url.resolve(retrievalURI, $ref);
366
+ const jsonPointer = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference($refBaseURI);
367
+
368
+ // the target bucket depends on the referencing context, so it's part of the
369
+ // identity: the same external target referenced as e.g. both a Schema and a
370
+ // Message must be hoisted into both components fields. The refractor sets
371
+ // `referenced-element` for every Reference Object position; the `schema`
372
+ // fallback is a defensive default for the (unexpected) untagged case.
373
+ const referencedElementType = referencingElement.meta.get('referenced-element');
374
+ const field = componentFieldByReferencedElement[referencedElementType] ?? componentFieldByReferencedElement.schema;
375
+ const canonicalKey = `${field}\t${retrievalURI}#${jsonPointer}`;
376
+
377
+ // already hoisted (or being hoisted) — just rewrite the pointer.
378
+ //
379
+ // this is also how circular references terminate: the assignment is
380
+ // reserved BEFORE recursing into the fragment (see below), so a reference
381
+ // that cycles back (directly or indirectly) finds its pointer already
382
+ // reserved here and is rewritten without recursing. Cycles are deliberately
383
+ // preserved as internal `$ref`s pointing at each other — that is valid,
384
+ // serializable output; resolving the cycle (if desired) is dereferencing's
385
+ // job, not bundling's.
386
+ if (this.assignments.has(canonicalKey)) {
387
+ referencingElement.set('$ref', this.assignments.get(canonicalKey));
388
+ path.skip();
389
+ return;
390
+ }
391
+ try {
392
+ // detect maximum depth of bundling
393
+ if (this.reference.depth >= this.options.bundle.maxDepth) {
394
+ throw new _MaximumBundleDepthError.default(`Maximum bundle depth of "${this.options.bundle.maxDepth}" has been exceeded in file "${this.reference.uri}"`, {
395
+ maxDepth: this.options.bundle.maxDepth,
396
+ uri: this.reference.uri
397
+ });
398
+ }
399
+ const reference = await this.toReference($ref);
400
+
401
+ // possibly non-semantic fragment
402
+ let referencedElement = (0, _apidomJsonPointer.evaluate)(reference.value.result, jsonPointer);
403
+
404
+ // apply semantics to the fragment so nested references become Reference Objects
405
+ if (referencedElement.element !== referencedElementType && !(0, _apidomNsAsyncapi.isReferenceElement)(referencedElement)) {
406
+ const cached = this.getRefracted(referencedElement, referencedElementType);
407
+ if (cached !== undefined) {
408
+ referencedElement = cached;
409
+ } else if ((0, _apidomNsAsyncapi.isReferenceLikeElement)(referencedElement)) {
410
+ const sourceElement = referencedElement;
411
+ referencedElement = (0, _apidomNsAsyncapi.refractReference)(referencedElement);
412
+ referencedElement.meta.set('referenced-element', referencedElementType);
413
+ this.setRefracted(sourceElement, referencedElementType, referencedElement);
414
+ } else {
415
+ const sourceElement = referencedElement;
416
+ referencedElement = (0, _apidomNsAsyncapi.refract)(referencedElement, {
417
+ element: referencedElementType
418
+ });
419
+ this.setRefracted(sourceElement, referencedElementType, referencedElement);
420
+ }
421
+ }
422
+ const preferredName = this.baseName(referencedElement, field, jsonPointer, retrievalURI);
423
+ const componentName = this.uniqueName(preferredName, field);
424
+ const internalPointer = `#/components/${field}/${(0, _apidomJsonPointer.escape)(componentName)}`;
425
+ this.reportNameCollision(preferredName, componentName, field);
426
+
427
+ // reserve the assignment and component name before recursing so cyclic
428
+ // references terminate
429
+ this.assignments.set(canonicalKey, internalPointer);
430
+ if (!this.reservedNames.has(field)) this.reservedNames.set(field, new Set());
431
+ this.reservedNames.get(field).add(componentName);
432
+
433
+ // own a copy of the fragment and bundle its own external references
434
+ const hoistedElement = (0, _apidomDatamodel.cloneDeep)(referencedElement);
435
+ const visitor = new AsyncAPI2BundleVisitor({
436
+ reference,
437
+ options: this.options,
438
+ assignments: this.assignments,
439
+ reservedNames: this.reservedNames,
440
+ refractCache: this.refractCache
441
+ });
442
+ const bundledElement = await (0, _apidomTraverse.traverseAsync)(hoistedElement, visitor, {
443
+ mutable: true
444
+ });
445
+
446
+ // annotate the hoisted fragment with info about its origin
447
+ if ((0, _apidomDatamodel.isElement)(bundledElement)) {
448
+ bundledElement.meta.set('ref-origin', reference.uri);
449
+ }
450
+
451
+ // place the bundled fragment into the entry document's components
452
+ this.ensureComponentsField(field).set(componentName, bundledElement);
453
+
454
+ // rewrite the referencing element to point at the hoisted fragment
455
+ referencingElement.set('$ref', internalPointer);
456
+ path.skip();
457
+ } catch (error) {
458
+ this.handleError(`Error while bundling Reference Object. Cannot resolve $ref "${$ref}": ${error.message}`, error, referencingElement, '$ref', $ref, path);
459
+ path.skip();
460
+ }
461
+ }
462
+
463
+ /**
464
+ * AsyncAPI 2.0 has a `components.channels` field, so an external Channel Item
465
+ * Object is hoisted there and the referencing `$ref` is rewritten to an
466
+ * internal pointer — mirroring how Reference Objects are bundled. Sibling
467
+ * fields next to the `$ref` (e.g. `description`) are kept on the referencing
468
+ * element.
469
+ */
470
+ async ChannelItemElement(path) {
471
+ const channelItemElement = path.node;
472
+
473
+ // ignore Channel Item Objects without a $ref field
474
+ if (!(0, _apidomDatamodel.isStringElement)(channelItemElement.$ref)) {
475
+ return;
476
+ }
477
+ const $ref = (0, _apidomCore.toValue)(channelItemElement.$ref);
478
+ const retrievalURI = this.toBaseURI($ref);
479
+ const isInternalReference = this.entryURI === retrievalURI;
480
+ const isExternalReference = !isInternalReference;
481
+ if (isInternalReference) {
482
+ // normalize self-references to a bare fragment so the bundled
483
+ // document stays transferable; bare fragments are left untouched
484
+ channelItemElement.set('$ref', this.normalizeSelfReference($ref));
485
+ path.skip();
486
+ return;
487
+ }
488
+
489
+ // honor the resolve.external switch
490
+ if (!this.options.resolve.external && isExternalReference) {
491
+ path.skip();
492
+ return;
493
+ }
494
+ const $refBaseURI = url.resolve(retrievalURI, $ref);
495
+ const jsonPointer = _apidomJsonPointer.URIFragmentIdentifier.fromURIReference($refBaseURI);
496
+ const field = cf.channels.name;
497
+ const canonicalKey = `${field}\t${retrievalURI}#${jsonPointer}`;
498
+
499
+ // already hoisted (or being hoisted) — just rewrite the pointer. The
500
+ // assignment is reserved BEFORE recursing, so a circular Channel Item chain
501
+ // terminates here with an internal pointer instead of recursing forever.
502
+ if (this.assignments.has(canonicalKey)) {
503
+ channelItemElement.set('$ref', this.assignments.get(canonicalKey));
504
+ path.skip();
505
+ return;
506
+ }
507
+ try {
508
+ // detect maximum depth of bundling
509
+ if (this.reference.depth >= this.options.bundle.maxDepth) {
510
+ throw new _MaximumBundleDepthError.default(`Maximum bundle depth of "${this.options.bundle.maxDepth}" has been exceeded in file "${this.reference.uri}"`, {
511
+ maxDepth: this.options.bundle.maxDepth,
512
+ uri: this.reference.uri
513
+ });
514
+ }
515
+ const reference = await this.toReference($ref);
516
+ let referencedElement = (0, _apidomJsonPointer.evaluate)(reference.value.result, jsonPointer);
517
+
518
+ // apply Channel Item semantics to the referenced fragment
519
+ if (!(0, _apidomNsAsyncapi.isChannelItemElement)(referencedElement)) {
520
+ const cached = this.getRefracted(referencedElement, 'channelItem');
521
+ if (cached !== undefined) {
522
+ referencedElement = cached;
523
+ } else {
524
+ const sourceElement = referencedElement;
525
+ referencedElement = (0, _apidomNsAsyncapi.refractChannelItem)(referencedElement);
526
+ this.setRefracted(sourceElement, 'channelItem', referencedElement);
527
+ }
528
+ }
529
+ const preferredName = this.baseName(referencedElement, field, jsonPointer, retrievalURI);
530
+ const componentName = this.uniqueName(preferredName, field);
531
+ const internalPointer = `#/components/${field}/${(0, _apidomJsonPointer.escape)(componentName)}`;
532
+ this.reportNameCollision(preferredName, componentName, field);
533
+
534
+ // reserve the assignment and component name before recursing so cyclic
535
+ // references terminate
536
+ this.assignments.set(canonicalKey, internalPointer);
537
+ if (!this.reservedNames.has(field)) this.reservedNames.set(field, new Set());
538
+ this.reservedNames.get(field).add(componentName);
539
+
540
+ // own a copy of the fragment and bundle its own external references
541
+ const hoistedElement = (0, _apidomDatamodel.cloneDeep)(referencedElement);
542
+ const visitor = new AsyncAPI2BundleVisitor({
543
+ reference,
544
+ options: this.options,
545
+ assignments: this.assignments,
546
+ reservedNames: this.reservedNames,
547
+ refractCache: this.refractCache
548
+ });
549
+ const bundledElement = await (0, _apidomTraverse.traverseAsync)(hoistedElement, visitor, {
550
+ mutable: true
551
+ });
552
+
553
+ // a `$ref` surviving on the hoisted Channel Item means it pointed at
554
+ // another Channel Item (a chain or cycle); it has already been rewritten
555
+ // to an internal `#/components/channels/...` pointer by the child traversal
556
+ bundledElement.meta.set('ref-origin', reference.uri);
557
+
558
+ // place the bundled Channel Item into the entry document's components
559
+ this.ensureComponentsField(field).set(componentName, bundledElement);
560
+
561
+ // rewrite the referencing Channel Item to point at the hoisted one, keeping
562
+ // any sibling fields (e.g. description) next to the $ref
563
+ channelItemElement.set('$ref', internalPointer);
564
+ path.skip();
565
+ } catch (error) {
566
+ this.handleError(`Error while bundling Channel Item Object. Cannot resolve $ref "${$ref}": ${error.message}`, error, channelItemElement, '$ref', $ref, path);
567
+ path.skip();
568
+ }
569
+ }
570
+ }
571
+ var _default = exports.default = AsyncAPI2BundleVisitor;