@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
@@ -0,0 +1,580 @@
1
+ import { propEq } from 'ramda';
2
+ import { isElement, RefElement, ParseResultElement, ObjectElement, ArrayElement, MemberElement, StringElement, NumberElement, BooleanElement, NullElement, cloneDeep, cloneShallow, isObjectElement } from '@speclynx/apidom-datamodel';
3
+ import { toYAML, toGeneric } from '@speclynx/apidom-core';
4
+ import { ApiDOMStructuredError } from '@speclynx/apidom-error';
5
+ import { traverse, traverseAsync } from '@speclynx/apidom-traverse';
6
+ import { URIFragmentIdentifier } from '@speclynx/apidom-json-pointer';
7
+ import UnresolvableReferenceError from "../../errors/UnresolvableReferenceError.mjs";
8
+ import MaximumDereferenceDepthError from "../../errors/MaximumDereferenceDepthError.mjs";
9
+ import MaximumResolveDepthError from "../../errors/MaximumResolveDepthError.mjs";
10
+ import * as url from "../../util/url.mjs";
11
+ import parse from "../../parse/index.mjs";
12
+ import Reference from "../../Reference.mjs";
13
+ const genericClasses = new Set([ObjectElement, ArrayElement, MemberElement, StringElement, NumberElement, BooleanElement, NullElement]);
14
+
15
+ /**
16
+ * Whether the element is generic ApiDOM: an instance of a primitive element class itself.
17
+ * Semantic elements may carry a primitive element name (e.g. a map of components named
18
+ * `object`), so the name alone doesn't tell.
19
+ * @internal
20
+ */
21
+ export const isGenericElement = element => genericClasses.has(element.constructor);
22
+
23
+ /**
24
+ * A source dereferenced as one type: the source as that semantic type, with every reference
25
+ * within it transcluded. `element` exists from the start, so references leading back to
26
+ * an element still being dereferenced (cycles) can be transcluded with it.
27
+ * @internal
28
+ */
29
+
30
+ /**
31
+ * How a referencing element is transcluded with the dereferenced target of its reference.
32
+ * @internal
33
+ */
34
+
35
+ /**
36
+ * A transclusion to perform.
37
+ * @internal
38
+ */
39
+
40
+ /**
41
+ * The target of a reference: the source element, the type the reference expects, and the
42
+ * document the source belongs to. `context` is passed to the visitor dereferencing the source.
43
+ * @internal
44
+ */
45
+
46
+ /**
47
+ * State shared by all the visitors of a single dereference run.
48
+ * @internal
49
+ */
50
+
51
+ /**
52
+ * @internal
53
+ */
54
+
55
+ // errors and trace hops whose location is relative to the source a nested visitor traversed
56
+ const relativeLocations = new WeakSet();
57
+
58
+ /**
59
+ * Dereferences a document by the type each reference expects of its target. With the immutable
60
+ * option the documents aren't edited and the result is a new tree; otherwise they're
61
+ * dereferenced in place.
62
+ *
63
+ * Every referenced structure is dereferenced once per type the references reaching it expect.
64
+ * A source is dereferenced as a type in two steps. First it's traversed, and for each reference
65
+ * within it the target is located, dereferenced as the expected type, and recorded as
66
+ * a transclusion. Then the referencing elements are transcluded with the dereferenced targets.
67
+ * A dereferenced element is shared by all the references leading to it; a reference leading
68
+ * to an element still being dereferenced is a cycle.
69
+ *
70
+ * Strategies provide the handlers locating the targets of their references, the refraction to
71
+ * their semantic types and the visitors dereferencing the targets.
72
+ * @internal
73
+ */
74
+ class DereferenceVisitor {
75
+ reference;
76
+ options;
77
+
78
+ /**
79
+ * Base URI of the current document for resolving relative references.
80
+ */
81
+ baseURI;
82
+ run;
83
+
84
+ /**
85
+ * Map from the elements of the source the visitor traverses to their copies.
86
+ */
87
+ cloneMap;
88
+
89
+ /**
90
+ * Source the visitor traverses a refraction of, when it's dereferenced as another type.
91
+ */
92
+ refractedFrom;
93
+
94
+ /**
95
+ * Transclusions to perform, keyed by the (copy of the) referencing element.
96
+ */
97
+ transclusions = new Map();
98
+
99
+ /**
100
+ * Number of references followed to reach the source the visitor traverses.
101
+ */
102
+ depth = 0;
103
+
104
+ /**
105
+ * The source the visitor traverses, dereferenced.
106
+ */
107
+ current;
108
+ constructor({
109
+ reference,
110
+ options
111
+ }) {
112
+ this.reference = reference;
113
+ this.options = options;
114
+ this.baseURI = this.toDocumentBaseURI(reference);
115
+ this.run = {
116
+ dereferenced: new WeakMap(),
117
+ refracted: new WeakMap(),
118
+ inPlace: !options.dereference.immutable,
119
+ copies: new WeakMap(),
120
+ transcluded: new WeakSet(),
121
+ failed: new WeakSet(),
122
+ reported: new Set(),
123
+ undo: []
124
+ };
125
+ }
126
+
127
+ /**
128
+ * Name of the strategy, for strategy specific options.
129
+ */
130
+
131
+ /**
132
+ * Creates the visitor dereferencing a source of the given document.
133
+ */
134
+
135
+ /**
136
+ * Refracts a generic element to the given type.
137
+ */
138
+
139
+ /**
140
+ * Whether the element is of the given type, so it needs no refraction.
141
+ */
142
+ isOfType(element, type) {
143
+ return element.element === type;
144
+ }
145
+
146
+ /**
147
+ * Base URI of a document: its retrieval URI unless the document identifies itself otherwise.
148
+ */
149
+ toDocumentBaseURI(reference) {
150
+ return reference.uri;
151
+ }
152
+
153
+ /**
154
+ * A document already loaded in the run identified by the URI other than by its retrieval URI.
155
+ */
156
+ findReferenceByIdentity(_uri) {
157
+ return undefined;
158
+ }
159
+
160
+ /**
161
+ * Dereferences the element, which belongs to the document of the visitor's reference.
162
+ * With the immutable option the element isn't edited and its dereferenced copy is returned;
163
+ * otherwise it's dereferenced in place and returned.
164
+ */
165
+ async dereference(element, context) {
166
+ // frozen elements are left as they are: they're dereferenced as with the immutable option
167
+ const isFrozen = value => isElement(value) && (value.isFrozen || value instanceof ParseResultElement && value.result?.isFrozen === true);
168
+ if (this.run.inPlace && (isFrozen(element) || this.reference.refSet?.refs.some(ref => isFrozen(ref.value)))) {
169
+ this.run = {
170
+ ...this.run,
171
+ inPlace: false
172
+ };
173
+ }
174
+ const dereferenced = await this.dereferenceAs({
175
+ source: element,
176
+ type: element.element,
177
+ reference: this.reference,
178
+ context
179
+ }, 0);
180
+ return dereferenced.element;
181
+ }
182
+ toBaseURI(uri) {
183
+ return url.resolve(this.baseURI, url.sanitize(url.stripHash(uri)));
184
+ }
185
+ async toReference(uri) {
186
+ // detect maximum depth of resolution
187
+ if (this.reference.depth >= this.options.resolve.maxDepth) {
188
+ throw new MaximumResolveDepthError(`Maximum resolution depth of ${this.options.resolve.maxDepth} has been exceeded by file "${this.reference.uri}"`, {
189
+ maxDepth: this.options.resolve.maxDepth,
190
+ uri: this.reference.uri
191
+ });
192
+ }
193
+ const baseURI = this.toBaseURI(uri);
194
+ const {
195
+ refSet
196
+ } = this.reference;
197
+
198
+ // we've already processed this Reference in past
199
+ if (refSet.has(baseURI)) {
200
+ return refSet.find(propEq(baseURI, 'uri'));
201
+ }
202
+ const referenceByIdentity = this.findReferenceByIdentity(baseURI);
203
+ if (referenceByIdentity !== undefined) {
204
+ return referenceByIdentity;
205
+ }
206
+ const parseResult = await parse(url.unsanitize(baseURI), {
207
+ ...this.options,
208
+ parse: {
209
+ ...this.options.parse,
210
+ mediaType: 'text/plain'
211
+ }
212
+ });
213
+
214
+ // registered as parsed: with the immutable option it's only ever copied, never edited
215
+ const reference = new Reference({
216
+ uri: baseURI,
217
+ value: parseResult,
218
+ depth: this.reference.depth + 1
219
+ });
220
+ refSet.add(reference);
221
+ return reference;
222
+ }
223
+
224
+ /**
225
+ * Handles an error according to the continueOnError option.
226
+ *
227
+ * For new errors: wraps in UnresolvableReferenceError with structured context
228
+ * (type, uri, location, codeFrame, refFieldName, refFieldValue, trace).
229
+ * For errors already wrapped by a nested visitor: prepends the current hop to the trace.
230
+ *
231
+ * Inner/intermediate visitors always throw to let the trace accumulate.
232
+ * Only the entry document visitor respects continueOnError (callback/swallow/throw),
233
+ * and reports the failure of a referencing element once.
234
+ */
235
+ handleError(message, error, referencingElement, refFieldName, refFieldValue, visitorPath) {
236
+ const {
237
+ continueOnError
238
+ } = this.options.dereference;
239
+ const isEntryDocument = url.stripHash(this.reference.refSet?.rootRef?.uri ?? '') === this.reference.uri;
240
+ const uri = this.reference.uri;
241
+ const type = referencingElement.element;
242
+ const codeFrame = toYAML(referencingElement);
243
+
244
+ // find element location by identity in the document tree.
245
+ // guarded: this.reference.value may not be a ParseResultElement or may lack a result.
246
+ // falls back to visitorPath, which is relative to the source being traversed
247
+ let location;
248
+ let refractedFromLocation;
249
+ const root = this.reference.value.result;
250
+ if (isElement(root)) {
251
+ traverse(root, {
252
+ enter: p => {
253
+ if (p.node === referencingElement) {
254
+ location = p.formatPath();
255
+ p.stop();
256
+ } else if (p.node === this.refractedFrom) {
257
+ refractedFromLocation = p.formatPath();
258
+ } else if (this.isTranscluded(p.node)) {
259
+ p.skip();
260
+ }
261
+ }
262
+ });
263
+ }
264
+ // not found, the location is relative to the traversed source: an enclosing visitor prefixes it
265
+ const isRelative = location === undefined && refractedFromLocation === undefined;
266
+ // within a refraction, the visitor path is relative to the refracted source
267
+ location ??= (refractedFromLocation ?? '') + visitorPath.formatPath();
268
+ const hop = {
269
+ uri,
270
+ type,
271
+ refFieldName,
272
+ refFieldValue,
273
+ location,
274
+ codeFrame
275
+ };
276
+ if (isRelative) relativeLocations.add(hop);
277
+
278
+ // enrich existing error from nested visitor or create new one
279
+ let unresolvedError;
280
+ if (error instanceof UnresolvableReferenceError) {
281
+ // prefix relative locations for entries belonging to the referenced document
282
+ const refBaseURI = this.toBaseURI(refFieldValue);
283
+ const fragment = URIFragmentIdentifier.fromURIReference(refFieldValue);
284
+ if (fragment) {
285
+ for (const entry of [error, ...error.trace]) {
286
+ if (relativeLocations.has(entry) && entry.uri === refBaseURI && entry.location) {
287
+ entry.location = fragment + entry.location;
288
+ relativeLocations.delete(entry);
289
+ }
290
+ }
291
+ }
292
+ // @ts-ignore
293
+ error.trace = [hop, ...error.trace];
294
+ unresolvedError = error;
295
+ } else {
296
+ unresolvedError = new UnresolvableReferenceError(message, {
297
+ cause: error,
298
+ type,
299
+ uri,
300
+ location,
301
+ codeFrame,
302
+ refFieldName,
303
+ refFieldValue,
304
+ trace: []
305
+ });
306
+ if (isRelative) relativeLocations.add(unresolvedError);
307
+ }
308
+ if (!isEntryDocument || continueOnError === false) throw unresolvedError;
309
+ // a referencing element is traversed again by every source containing it: its failure is reported once
310
+ this.run.failed.add(referencingElement);
311
+ const failure = JSON.stringify([uri, location, refFieldName, refFieldValue]);
312
+ if (this.run.reported.has(failure)) return;
313
+ this.run.reported.add(failure);
314
+ if (typeof continueOnError === 'function') continueOnError(unresolvedError);
315
+ }
316
+
317
+ /**
318
+ * The source as the given semantic type: the source itself when it's of that type, otherwise
319
+ * its refraction to that type, made once per dereference run. The refraction is made from
320
+ * the source dereferenced as its own type, so it doesn't depend on which reference
321
+ * reaches the source first.
322
+ */
323
+ async toSemantic(target, depth) {
324
+ const {
325
+ source,
326
+ type
327
+ } = target;
328
+ if (this.isOfType(source, type)) return source;
329
+ let byType = this.run.refracted.get(source);
330
+ if (byType === undefined) {
331
+ byType = new Map();
332
+ this.run.refracted.set(source, byType);
333
+ }
334
+ const cached = byType.get(type);
335
+ if (cached !== undefined) return cached;
336
+ const dereferenced = isGenericElement(source) ? source : (await this.dereferenceAs({
337
+ ...target,
338
+ type: source.element
339
+ }, depth)).element;
340
+ // dereferencing the source may have led back to this very refraction
341
+ const reached = byType.get(type);
342
+ if (reached !== undefined) return reached;
343
+ const semantic = this.refractTo(toGeneric(dereferenced), type);
344
+ byType.set(type, semantic);
345
+ this.run.undo.push(() => byType.delete(type));
346
+ return semantic;
347
+ }
348
+
349
+ /**
350
+ * Dereferences the source as the given type, once per dereference run. `depth` is the number
351
+ * of references followed to reach the source.
352
+ */
353
+ async dereferenceAs(target, depth) {
354
+ const {
355
+ source,
356
+ type,
357
+ reference,
358
+ context
359
+ } = target;
360
+ let byType = this.run.dereferenced.get(source);
361
+ if (byType === undefined) {
362
+ byType = new Map();
363
+ this.run.dereferenced.set(source, byType);
364
+ }
365
+ const existing = byType.get(type);
366
+ if (existing !== undefined) return existing;
367
+ const semantic = await this.toSemantic(target, depth);
368
+ // a refraction is made for the run, so it's dereferenced as is
369
+ const isOwned = semantic !== source || this.run.inPlace;
370
+ const cloneMap = isOwned ? undefined : this.run.copies;
371
+ const dereferenced = {
372
+ element: isOwned ? semantic : cloneDeep(semantic, {
373
+ visited: cloneMap
374
+ }),
375
+ done: false,
376
+ height: 0,
377
+ alias: false,
378
+ unresolved: false
379
+ };
380
+ // a failure discards whatever got dereferenced or refracted meanwhile, as it may lead to this element
381
+ const undoSize = this.run.undo.length;
382
+ byType.set(type, dereferenced);
383
+ this.run.undo.push(() => byType.delete(type));
384
+ const visitor = this.createVisitor(reference, context);
385
+ visitor.run = this.run;
386
+ visitor.cloneMap = cloneMap;
387
+ visitor.refractedFrom = semantic !== source ? source : undefined;
388
+ visitor.depth = depth;
389
+ visitor.current = dereferenced;
390
+ try {
391
+ // the handlers only record transclusions
392
+ await traverseAsync(semantic, visitor);
393
+ } catch (error) {
394
+ this.run.undo.splice(undoSize).reverse().forEach(undo => undo());
395
+ throw error;
396
+ }
397
+ for (const transclusion of visitor.transclusions.values()) {
398
+ if (!transclusion.back && !transclusion.attachment) {
399
+ dereferenced.height = Math.max(dereferenced.height, transclusion.target.height + 1);
400
+ }
401
+ }
402
+ const rootTransclusion = visitor.transclusions.get(dereferenced.element);
403
+ dereferenced.unresolved = rootTransclusion !== undefined && (rootTransclusion.keep || rootTransclusion.target.unresolved);
404
+ dereferenced.element = visitor.transclude(dereferenced.element);
405
+ dereferenced.done = true;
406
+ return dereferenced;
407
+ }
408
+
409
+ /**
410
+ * Dereferences the target of a reference as the expected type and records the transclusion.
411
+ */
412
+ async addTransclusion(path, target, spec) {
413
+ const referencingCopy = this.cloneMap?.get(path.node) ?? path.node;
414
+ const {
415
+ maxDepth
416
+ } = this.options.dereference;
417
+
418
+ // the source being traversed is itself a referencing element: an alias of the target
419
+ if (path.parentPath === null && this.current !== undefined) this.current.alias = true;
420
+
421
+ // detect maximum depth of dereferencing
422
+ if (this.depth + 1 > maxDepth) {
423
+ throw new MaximumDereferenceDepthError(`Maximum dereference depth of "${maxDepth}" has been exceeded in file "${this.reference.uri}"`, {
424
+ maxDepth,
425
+ uri: this.reference.uri
426
+ });
427
+ }
428
+ const existing = this.run.dereferenced.get(target.source)?.get(target.type);
429
+
430
+ // a target dereferenced earlier counts the references followed within it as well
431
+ if (existing?.done && this.depth + 1 + existing.height > maxDepth) {
432
+ throw new MaximumDereferenceDepthError(`Maximum dereference depth of "${maxDepth}" has been exceeded in file "${this.reference.uri}"`, {
433
+ maxDepth,
434
+ uri: this.reference.uri
435
+ });
436
+ }
437
+
438
+ // a reference leading to an element still being dereferenced is a cycle
439
+ if (existing !== undefined && !existing.done) {
440
+ this.reference.refSet.circular = true;
441
+ if (this.options.dereference.circular === 'error') {
442
+ throw new ApiDOMStructuredError('Circular reference detected', spec.circular.details);
443
+ }
444
+ // an alias has no value before its own reference is transcluded: the referencing element
445
+ // is kept, to be transcluded by an enclosing dereferenced element, if any
446
+ const keep = existing.alias && this.options.dereference.circular === 'ignore';
447
+ this.transclusions.set(referencingCopy, {
448
+ ...spec,
449
+ target: existing,
450
+ back: true,
451
+ keep,
452
+ attachment: false
453
+ });
454
+ return;
455
+ }
456
+ const dereferenced = existing ?? (await this.dereferenceAs(target, this.depth + 1));
457
+ // the referencing element failed while its target was dereferenced, e.g. as a circular reference
458
+ if (this.run.failed.has(path.node)) return;
459
+ this.transclusions.set(referencingCopy, {
460
+ ...spec,
461
+ target: dereferenced,
462
+ back: false,
463
+ keep: false,
464
+ attachment: false
465
+ });
466
+ }
467
+
468
+ /**
469
+ * Records the transclusion of a referencing element with a target that isn't dereferenced:
470
+ * the target as the expected type is attached as it is, e.g. an Operation Object to the Link
471
+ * Object pointing at it. Attached targets never form cycles.
472
+ */
473
+ async addAttachment(path, target, toTranscludedElement) {
474
+ const referencingCopy = this.cloneMap?.get(path.node) ?? path.node;
475
+ const semantic = await this.toSemantic(target, this.depth + 1);
476
+ // with the immutable option, the attached target is its copy in the result when it has one
477
+ const element = this.run.inPlace ? semantic : this.run.copies.get(semantic) ?? semantic;
478
+ this.transclusions.set(referencingCopy, {
479
+ toTranscludedElement,
480
+ circular: {
481
+ uri: '',
482
+ meta: {},
483
+ details: {}
484
+ },
485
+ target: {
486
+ element,
487
+ done: true,
488
+ height: 0,
489
+ alias: false,
490
+ unresolved: false
491
+ },
492
+ back: false,
493
+ keep: false,
494
+ attachment: true
495
+ });
496
+ }
497
+
498
+ /**
499
+ * Transcludes every referencing element within the element with its dereferenced target.
500
+ */
501
+ transclude(element) {
502
+ if (this.transclusions.size === 0) return element;
503
+
504
+ // replaced on leave: nested referencing elements first, so merges see them replaced
505
+ traverse(element, {
506
+ enter: path => {
507
+ if (this.isTranscluded(path.node)) path.skip();
508
+ },
509
+ leave: path => {
510
+ const transclusion = this.transclusions.get(path.node);
511
+ if (path.node !== element && transclusion !== undefined) {
512
+ const transcludedElement = this.toTranscludedElement(path.node, transclusion);
513
+ // a kept referencing element is left to be transcluded by an enclosing element
514
+ if (transcludedElement === path.node) return;
515
+ this.run.transcluded.add(transcludedElement);
516
+ path.replaceWith(transcludedElement);
517
+ }
518
+ }
519
+ }, {
520
+ mutable: true
521
+ });
522
+ const rootTransclusion = this.transclusions.get(element);
523
+ if (rootTransclusion === undefined) return element;
524
+
525
+ // references leading back to the element may already be transcluded with it, so it's filled in place
526
+ const transcludedElement = this.toTranscludedElement(element, rootTransclusion);
527
+ if (transcludedElement === element) return element;
528
+ if (transcludedElement.constructor !== element.constructor) return transcludedElement;
529
+ element.element = transcludedElement.element;
530
+ element.content = transcludedElement.content;
531
+ element.meta = transcludedElement.meta;
532
+ element.attributes = transcludedElement.attributes;
533
+ return element;
534
+ }
535
+
536
+ /**
537
+ * The element transcluded in place of a referencing element.
538
+ */
539
+ toTranscludedElement(referencingElement, transclusion) {
540
+ // the referencing element waits for an enclosing element to transclude it
541
+ if (transclusion.keep) return referencingElement;
542
+ // an alias whose reference never reaches a value
543
+ if (transclusion.target.unresolved) {
544
+ const {
545
+ aliasField
546
+ } = transclusion;
547
+ const alias = transclusion.target.element;
548
+ if (aliasField === undefined || !isObjectElement(alias)) return referencingElement;
549
+ const fields = cloneShallow(alias);
550
+ fields.remove(aliasField);
551
+ return transclusion.toTranscludedElement(referencingElement, fields);
552
+ }
553
+ if (transclusion.back && this.options.dereference.circular === 'replace') {
554
+ const refElement = new RefElement(transclusion.circular.uri, {
555
+ type: referencingElement.element,
556
+ ...transclusion.circular.meta
557
+ });
558
+ const replacer = this.options.dereference.strategyOpts[this.strategyName]?.circularReplacer ?? this.options.dereference.circularReplacer;
559
+ return replacer(refElement);
560
+ }
561
+ return transclusion.toTranscludedElement(referencingElement, transclusion.target.element);
562
+ }
563
+
564
+ /**
565
+ * Whether the element is transcluded in place of a referencing element, in this run or,
566
+ * when dereferencing in place, in an earlier one: transcluded elements carry the `ref-type` meta,
567
+ * which only dereferencing sets (bundling annotates hoisted elements with `ref-origin` only).
568
+ */
569
+ isTranscluded(element) {
570
+ return this.run.transcluded.has(element) || !element.isMetaEmpty && element.meta.hasKey('ref-type');
571
+ }
572
+
573
+ /**
574
+ * Dereferenced in place, a source may already hold transcluded elements.
575
+ */
576
+ enter(path) {
577
+ if (this.isTranscluded(path.node)) path.skip();
578
+ }
579
+ }
580
+ export default DereferenceVisitor;
@@ -48,8 +48,8 @@ Object.defineProperty(exports, "resolveSchema$refField", {
48
48
  }
49
49
  });
50
50
  var _apidomDatamodel = require("@speclynx/apidom-datamodel");
51
- var _apidomTraverse = require("@speclynx/apidom-traverse");
52
51
  var _apidomNsArazzo = require("@speclynx/apidom-ns-arazzo-1");
52
+ var _mediaTypes = require("@swaggerexpert/media-types");
53
53
  var _DereferenceStrategy = _interopRequireDefault(require("../DereferenceStrategy.cjs"));
54
54
  var _Reference = _interopRequireDefault(require("../../../Reference.cjs"));
55
55
  var _ReferenceSet = _interopRequireDefault(require("../../../ReferenceSet.cjs"));
@@ -73,65 +73,41 @@ class Arazzo1DereferenceStrategy extends _DereferenceStrategy.default {
73
73
  }
74
74
  canDereference(file) {
75
75
  // assert by media type
76
- if (file.mediaType !== 'text/plain') {
77
- return _apidomNsArazzo.mediaTypes.includes(file.mediaType);
76
+ if (!(0, _mediaTypes.matches)('text/plain', file.mediaType)) {
77
+ return (0, _mediaTypes.selectBestMatch)(_apidomNsArazzo.mediaTypes, file.mediaType) !== null;
78
78
  }
79
79
 
80
80
  // assert by inspecting ApiDOM
81
81
  return (0, _apidomNsArazzo.isArazzoSpecification1Element)(file.parseResult?.result);
82
82
  }
83
83
  async dereference(file, options) {
84
- const immutableRefSet = options.dereference.refSet ?? new _ReferenceSet.default();
85
- const mutableRefSet = new _ReferenceSet.default();
86
- let refSet = immutableRefSet;
84
+ const refSet = options.dereference.refSet ?? new _ReferenceSet.default();
87
85
  let reference;
88
- if (!immutableRefSet.has(file.uri)) {
86
+ if (!refSet.has(file.uri)) {
89
87
  reference = new _Reference.default({
90
88
  uri: file.uri,
91
89
  value: file.parseResult
92
90
  });
93
- immutableRefSet.add(reference);
91
+ refSet.add(reference);
94
92
  } else {
95
93
  // pre-computed refSet was provided as configuration option
96
- reference = immutableRefSet.find(ref => ref.uri === file.uri);
94
+ reference = refSet.find(ref => ref.uri === file.uri);
97
95
  }
98
96
 
99
97
  /**
100
- * Clone refSet due the dereferencing process being mutable.
101
- * We don't want to mutate the original refSet and the references.
98
+ * With the immutable option the dereferenced element is a new tree,
99
+ * otherwise the dereferenced root is dereferenced in place.
102
100
  */
103
- if (options.dereference.immutable) {
104
- immutableRefSet.refs.map(ref => new _Reference.default({
105
- ...ref,
106
- value: (0, _apidomDatamodel.cloneDeep)(ref.value)
107
- })).forEach(ref => mutableRefSet.add(ref));
108
- reference = mutableRefSet.find(ref => ref.uri === file.uri);
109
- refSet = mutableRefSet;
110
- }
111
- const shouldDetectCircular = ['error', 'replace'].includes(options.dereference.circular);
101
+ const root = refSet.rootRef.value;
112
102
  const visitor = new _visitor.default({
113
103
  reference,
114
104
  options,
115
- ancestorSchema$ids: (0, _util.detachedRootSchema$ids)(refSet.rootRef.value.result)
105
+ ancestorSchema$ids: (0, _util.detachedRootSchema$ids)(root.result)
116
106
  });
117
- const dereferencedElement = await (0, _apidomTraverse.traverseAsync)(refSet.rootRef.value, visitor, {
118
- mutable: true,
119
- ...(shouldDetectCircular && {
120
- skipVisited: 'skip'
121
- })
107
+ const dereferencedElement = await visitor.dereference(root, {
108
+ ancestorSchema$ids: (0, _util.detachedRootSchema$ids)(root.result)
122
109
  });
123
110
 
124
- /**
125
- * If immutable option is set, replay refs from the refSet.
126
- * Replayed before source descriptions are dereferenced, so they can reuse the refs.
127
- */
128
- if (options.dereference.immutable) {
129
- mutableRefSet.refs.filter(ref => ref.uri.startsWith('immutable://')).map(ref => new _Reference.default({
130
- ...ref,
131
- uri: ref.uri.replace(/^immutable:\/\//, '')
132
- })).forEach(ref => immutableRefSet.add(ref));
133
- }
134
-
135
111
  /**
136
112
  * Dereference source descriptions if option is enabled.
137
113
  */
@@ -151,9 +127,8 @@ class Arazzo1DereferenceStrategy extends _DereferenceStrategy.default {
151
127
  * If provided as configuration option, then provider is responsible for cleanup.
152
128
  */
153
129
  if (options.dereference.refSet === null) {
154
- immutableRefSet.clean();
130
+ refSet.clean();
155
131
  }
156
- mutableRefSet.clean();
157
132
  return dereferencedElement;
158
133
  }
159
134
  }