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