@speclynx/apidom-reference 5.2.4 → 5.2.5

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 (51) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +30 -11
  3. package/dist/apidom-reference.browser.js +1121 -514
  4. package/dist/apidom-reference.browser.min.js +1 -1
  5. package/package.json +28 -28
  6. package/src/bundle/strategies/arazzo-1/visitor.cjs +105 -22
  7. package/src/bundle/strategies/arazzo-1/visitor.mjs +107 -24
  8. package/src/bundle/strategies/openapi-3-1/visitor.cjs +197 -47
  9. package/src/bundle/strategies/openapi-3-1/visitor.mjs +200 -50
  10. package/src/bundle/util.cjs +57 -2
  11. package/src/bundle/util.mjs +52 -0
  12. package/src/dereference/strategies/arazzo-1/index.cjs +9 -7
  13. package/src/dereference/strategies/arazzo-1/index.mjs +3 -1
  14. package/src/dereference/strategies/arazzo-1/selectors/$anchor.cjs +6 -0
  15. package/src/dereference/strategies/arazzo-1/selectors/$anchor.mjs +1 -1
  16. package/src/dereference/strategies/arazzo-1/selectors/uri.cjs +6 -0
  17. package/src/dereference/strategies/arazzo-1/selectors/uri.mjs +1 -1
  18. package/src/dereference/strategies/arazzo-1/util.cjs +6 -0
  19. package/src/dereference/strategies/arazzo-1/util.mjs +1 -1
  20. package/src/dereference/strategies/arazzo-1/visitor.cjs +67 -9
  21. package/src/dereference/strategies/arazzo-1/visitor.mjs +71 -13
  22. package/src/dereference/strategies/openapi-3-1/index.cjs +2 -1
  23. package/src/dereference/strategies/openapi-3-1/index.mjs +3 -1
  24. package/src/dereference/strategies/openapi-3-1/selectors/$anchor.cjs +16 -4
  25. package/src/dereference/strategies/openapi-3-1/selectors/$anchor.mjs +14 -3
  26. package/src/dereference/strategies/openapi-3-1/selectors/json-pointer.cjs +29 -0
  27. package/src/dereference/strategies/openapi-3-1/selectors/json-pointer.mjs +22 -0
  28. package/src/dereference/strategies/openapi-3-1/selectors/uri.cjs +54 -27
  29. package/src/dereference/strategies/openapi-3-1/selectors/uri.mjs +51 -27
  30. package/src/dereference/strategies/openapi-3-1/util.cjs +75 -14
  31. package/src/dereference/strategies/openapi-3-1/util.mjs +68 -14
  32. package/src/dereference/strategies/openapi-3-1/visitor.cjs +85 -9
  33. package/src/dereference/strategies/openapi-3-1/visitor.mjs +89 -13
  34. package/src/util/url.cjs +35 -2
  35. package/src/util/url.mjs +32 -0
  36. package/types/apidom-reference.d.ts +12 -0
  37. package/types/bundle/strategies/arazzo-1/index.d.ts +1 -1
  38. package/types/bundle/strategies/arazzo-1/visitor.d.ts +51 -6
  39. package/types/bundle/strategies/openapi-3-1/index.d.ts +1 -1
  40. package/types/bundle/strategies/openapi-3-1/visitor.d.ts +59 -3
  41. package/types/bundle/util.d.ts +39 -0
  42. package/types/dereference/strategies/arazzo-1/selectors/$anchor.d.ts +1 -1
  43. package/types/dereference/strategies/arazzo-1/selectors/uri.d.ts +1 -1
  44. package/types/dereference/strategies/arazzo-1/util.d.ts +1 -1
  45. package/types/dereference/strategies/arazzo-1/visitor.d.ts +17 -1
  46. package/types/dereference/strategies/openapi-3-1/selectors/$anchor.d.ts +8 -0
  47. package/types/dereference/strategies/openapi-3-1/selectors/json-pointer.d.ts +9 -0
  48. package/types/dereference/strategies/openapi-3-1/selectors/uri.d.ts +37 -4
  49. package/types/dereference/strategies/openapi-3-1/util.d.ts +53 -2
  50. package/types/dereference/strategies/openapi-3-1/visitor.d.ts +17 -1
  51. package/types/util/url.d.ts +10 -0
@@ -1,5 +1,5 @@
1
1
  import { propEq, none } from 'ramda';
2
- import { isElement, isObjectElement, isStringElement, ObjectElement, AnnotationElement, cloneDeep, cloneShallow } from '@speclynx/apidom-datamodel';
2
+ import { isElement, isObjectElement, isStringElement, isParseResultElement, ObjectElement, AnnotationElement, cloneDeep, cloneShallow } from '@speclynx/apidom-datamodel';
3
3
  import { toValue, toYAML, fixedFields } from '@speclynx/apidom-core';
4
4
  import { ApiDOMStructuredError } from '@speclynx/apidom-error';
5
5
  import { traverseAsync } from '@speclynx/apidom-traverse';
@@ -16,8 +16,8 @@ import Reference from "../../../Reference.mjs";
16
16
  import File from "../../../File.mjs";
17
17
  import { isAnchor, uriToAnchor, evaluate as $anchorEvaluate } from "../../../dereference/strategies/openapi-3-1/selectors/$anchor.mjs";
18
18
  import { evaluate as uriEvaluate } from "../../../dereference/strategies/openapi-3-1/selectors/uri.mjs";
19
- import { resolveSchema$refField, resolveSchema$idField, maybeRefractToSchemaElement } from "../../../dereference/strategies/openapi-3-1/util.mjs";
20
- import { toPascalCase, sanitizeComponentName, uniqueName as resolveUniqueName } from "../../util.mjs";
19
+ import { resolveSchema$refField, resolveSchema$idField, resolveSchemaBaseURI, maybeRefractToSchemaElement } from "../../../dereference/strategies/openapi-3-1/util.mjs";
20
+ import { rebaseSchema$ref, relocateSchema$ref, sanitizeComponentName, toPascalCase, uniqueName as resolveUniqueName } from "../../util.mjs";
21
21
  /**
22
22
  * Maps the `referenced-element` meta value (set during refraction) to the
23
23
  * Components Object field where the referenced element should be hoisted.
@@ -59,6 +59,12 @@ const componentFieldElementByField = {
59
59
  [cf.pathItems.name]: ComponentsPathItemsElement
60
60
  };
61
61
 
62
+ /**
63
+ * A bundled element awaiting placement into the entry document's Components
64
+ * Object field.
65
+ * @public
66
+ */
67
+
62
68
  /**
63
69
  * @public
64
70
  */
@@ -116,18 +122,50 @@ class OpenAPI3_1BundleVisitor {
116
122
  * generic object referenced once as a Parameter and once as a Response).
117
123
  */
118
124
  refractCache;
125
+
126
+ /**
127
+ * Index of `$id`-bearing schemas per document, shared across the entry document
128
+ * and every external document visitor of a single bundle run so it never
129
+ * outlives mutations of the documents (e.g. the final placement of components).
130
+ */
131
+ schema$idIndex;
132
+
133
+ /**
134
+ * Bundled fragments waiting to be placed into the entry document's
135
+ * Components Object, shared across the entry document and every external
136
+ * document visitor. Placement is deferred until the entry document traversal
137
+ * completes: the traversal walks live content, so placing a fragment mid-way
138
+ * would make it descend into the fragment and re-bundle its `$ref`s against
139
+ * the entry document's base URI instead of the fragment's own.
140
+ */
141
+ placements;
142
+
143
+ /**
144
+ * The base URI the traversed content will have once placed into the entry
145
+ * document, when placement changes it: content hoisted out of a non-schema
146
+ * document (a Response, Parameter, Path Item, ...) trades that document's
147
+ * base URI for the entry document's. Unset for the entry document itself and
148
+ * for embedded schema resources, whose `$id` carries their base along.
149
+ */
150
+ relocationBaseURI;
119
151
  constructor({
120
152
  reference,
121
153
  options,
122
154
  assignments = new Map(),
123
155
  reservedNames = new Map(),
124
- refractCache = new WeakMap()
156
+ refractCache = new WeakMap(),
157
+ schema$idIndex = new WeakMap(),
158
+ placements = [],
159
+ relocationBaseURI
125
160
  }) {
126
161
  this.reference = reference;
127
162
  this.options = options;
128
163
  this.assignments = assignments;
129
164
  this.reservedNames = reservedNames;
130
165
  this.refractCache = refractCache;
166
+ this.schema$idIndex = schema$idIndex;
167
+ this.placements = placements;
168
+ this.relocationBaseURI = relocationBaseURI;
131
169
  }
132
170
  getRefracted(source, type) {
133
171
  return this.refractCache.get(source)?.get(type);
@@ -185,6 +223,22 @@ class OpenAPI3_1BundleVisitor {
185
223
  return ref.startsWith('#') ? ref : url.getHash(ref);
186
224
  }
187
225
 
226
+ /**
227
+ * The base URI the given Schema Object's `$ref` will be resolved against once
228
+ * the content is placed into the entry document, or `undefined` when the
229
+ * `$ref` as written resolves to `$refBaseURI` from there as well: the content
230
+ * isn't relocated, an absolute `$id` on the schema's ancestor chain pins its
231
+ * base, or the hoisted document sits next to the entry document.
232
+ */
233
+ relocatedSchemaBaseURI(path, $refBaseURI) {
234
+ if (this.relocationBaseURI === undefined) return undefined;
235
+ const relocatedBaseURI = resolveSchemaBaseURI(this.relocationBaseURI, path);
236
+ if (resolveSchema$refField(relocatedBaseURI, path.node) === $refBaseURI) {
237
+ return undefined;
238
+ }
239
+ return relocatedBaseURI;
240
+ }
241
+
188
242
  /**
189
243
  * Handles an error according to the `bundle.continueOnError` option.
190
244
  *
@@ -303,13 +357,16 @@ class OpenAPI3_1BundleVisitor {
303
357
 
304
358
  /**
305
359
  * Computes a collision-free component name within the target field. A name is
306
- * taken if it's already placed in components or reserved by a component that
307
- * is still being bundled (reserved before recursion).
360
+ * taken if it's already present in the entry document's components or reserved
361
+ * by a component that is still being bundled (reserved before recursion).
308
362
  */
309
363
  uniqueName(candidate, field) {
310
- const fieldElement = this.ensureComponentsField(field);
364
+ const {
365
+ components
366
+ } = this.entryResult;
367
+ const fieldElement = isObjectElement(components) ? components.get(field) : undefined;
311
368
  const reserved = this.reservedNames.get(field) ?? new Set();
312
- return resolveUniqueName(candidate, name => fieldElement.hasKey(name) || reserved.has(name));
369
+ return resolveUniqueName(candidate, name => isObjectElement(fieldElement) && fieldElement.hasKey(name) || reserved.has(name));
313
370
  }
314
371
 
315
372
  /**
@@ -333,6 +390,28 @@ class OpenAPI3_1BundleVisitor {
333
390
  // append (never prepend) so the result element stays ahead of annotations
334
391
  this.entryParseResult.content.push(annotation);
335
392
  }
393
+
394
+ /**
395
+ * The entry document traversal is rooted at its Parse Result (nested
396
+ * traversals are rooted at fragments), so leaving it marks the moment the
397
+ * whole entry document has been visited and the collected fragments can be
398
+ * placed into its Components Object without the traversal descending into them.
399
+ * Guarded on the node so that a traversal rooted at any other Parse Result
400
+ * never flushes the shared placements early.
401
+ */
402
+ ParseResultElement = {
403
+ leave: path => {
404
+ if (path.node !== this.entryParseResult) return;
405
+ for (const {
406
+ field,
407
+ name,
408
+ element
409
+ } of this.placements) {
410
+ this.ensureComponentsField(field).set(name, element);
411
+ }
412
+ this.placements.length = 0;
413
+ }
414
+ };
336
415
  async ReferenceElement(path) {
337
416
  const referencingElement = path.node;
338
417
  const $ref = toValue(referencingElement.$ref);
@@ -420,14 +499,19 @@ class OpenAPI3_1BundleVisitor {
420
499
  if (!this.reservedNames.has(field)) this.reservedNames.set(field, new Set());
421
500
  this.reservedNames.get(field).add(componentName);
422
501
 
423
- // own a copy of the fragment and bundle its own external references
502
+ // own a copy of the fragment and bundle its own external references; the
503
+ // fragment ends up in the entry document, so that is where its content
504
+ // resolves relative references from
424
505
  const hoistedElement = cloneDeep(referencedElement);
425
506
  const visitor = new OpenAPI3_1BundleVisitor({
426
507
  reference,
427
508
  options: this.options,
428
509
  assignments: this.assignments,
429
510
  reservedNames: this.reservedNames,
430
- refractCache: this.refractCache
511
+ refractCache: this.refractCache,
512
+ schema$idIndex: this.schema$idIndex,
513
+ placements: this.placements,
514
+ relocationBaseURI: this.entryURI
431
515
  });
432
516
  const bundledElement = await traverseAsync(hoistedElement, visitor, {
433
517
  mutable: true
@@ -438,8 +522,12 @@ class OpenAPI3_1BundleVisitor {
438
522
  bundledElement.meta.set('ref-origin', reference.uri);
439
523
  }
440
524
 
441
- // place the bundled fragment into the entry document's components
442
- this.ensureComponentsField(field).set(componentName, bundledElement);
525
+ // queue the bundled fragment for placement into the entry document's components
526
+ this.placements.push({
527
+ field,
528
+ name: componentName,
529
+ element: bundledElement
530
+ });
443
531
 
444
532
  // rewrite the referencing element to point at the hoisted fragment
445
533
  referencingElement.set('$ref', internalPointer);
@@ -528,14 +616,19 @@ class OpenAPI3_1BundleVisitor {
528
616
  if (!this.reservedNames.has(field)) this.reservedNames.set(field, new Set());
529
617
  this.reservedNames.get(field).add(componentName);
530
618
 
531
- // own a copy of the fragment and bundle its own external references
619
+ // own a copy of the fragment and bundle its own external references; the
620
+ // Path Item ends up in the entry document, so that is where its content
621
+ // resolves relative references from
532
622
  const hoistedElement = cloneDeep(referencedElement);
533
623
  const visitor = new OpenAPI3_1BundleVisitor({
534
624
  reference,
535
625
  options: this.options,
536
626
  assignments: this.assignments,
537
627
  reservedNames: this.reservedNames,
538
- refractCache: this.refractCache
628
+ refractCache: this.refractCache,
629
+ schema$idIndex: this.schema$idIndex,
630
+ placements: this.placements,
631
+ relocationBaseURI: this.entryURI
539
632
  });
540
633
  const bundledElement = await traverseAsync(hoistedElement, visitor, {
541
634
  mutable: true
@@ -546,8 +639,12 @@ class OpenAPI3_1BundleVisitor {
546
639
  // internal `#/components/pathItems/...` pointer by the child traversal
547
640
  bundledElement.meta.set('ref-origin', reference.uri);
548
641
 
549
- // place the bundled Path Item into the entry document's components
550
- this.ensureComponentsField(field).set(componentName, bundledElement);
642
+ // queue the bundled Path Item for placement into the entry document's components
643
+ this.placements.push({
644
+ field,
645
+ name: componentName,
646
+ element: bundledElement
647
+ });
551
648
 
552
649
  // rewrite the referencing Path Item to point at the hoisted one, keeping
553
650
  // any sibling fields (e.g. summary, description) next to the $ref
@@ -582,7 +679,8 @@ class OpenAPI3_1BundleVisitor {
582
679
  // toReference) so an internal $ref needs no resolution and never trips the
583
680
  // resolve.maxDepth guard, matching how ReferenceElement returns early.
584
681
  const retrievalURI = this.reference.uri;
585
- const $refBaseURI = resolveSchema$refField(retrievalURI, referencingElement);
682
+ const schemaBaseURI = resolveSchemaBaseURI(retrievalURI, path);
683
+ const $refBaseURI = resolveSchema$refField(schemaBaseURI, referencingElement);
586
684
  const $refBaseURIStrippedHash = url.stripHash($refBaseURI);
587
685
  const file = new File({
588
686
  uri: $refBaseURIStrippedHash
@@ -618,59 +716,95 @@ class OpenAPI3_1BundleVisitor {
618
716
  // as a canonical URI / URL against the current document's $id graph; if
619
717
  // that fails, fall back to fetching the external document and resolving
620
718
  // the fragment there as a $anchor or JSON Pointer.
719
+ //
720
+ // the fragment being traversed is searched before the whole document:
721
+ // content hoisted out of an external document (a Response, Parameter, Path
722
+ // Item, ...) is refracted with its own semantics, so the Schema Objects it
723
+ // contains are indexed, whereas the external document as a whole is parsed
724
+ // generically and refracted as a bare JSON Schema, which leaves those Schema
725
+ // Objects (under `content`, `schema`, ...) unrecognized and their `$id`s out
726
+ // of the index
727
+ const documentElement = this.reference.value.result;
728
+ const rootNode = path.getAncestorNodes().at(-1) ?? path.node;
729
+ const fragmentElement = isParseResultElement(rootNode) ? rootNode.result : rootNode;
730
+ const uriEvaluateOptions = {
731
+ baseURI: retrievalURI,
732
+ index: this.schema$idIndex
733
+ };
621
734
  let schemaReference = this.reference;
735
+ let resourceRoot;
736
+ let resourceBaseURI;
622
737
  try {
623
- const referenceAsSchema = maybeRefractToSchemaElement(schemaReference.value.result);
624
- uriEvaluate($refBaseURI, referenceAsSchema);
738
+ try {
739
+ uriEvaluate($refBaseURI, maybeRefractToSchemaElement(fragmentElement), uriEvaluateOptions);
740
+ // a $ref that resolves (by $id/$anchor/pointer) within the traversed
741
+ // content is internal even when its URI form (e.g. a URN or absolute
742
+ // $id) is not a bare fragment: the $id resource travels with the content
743
+ // wherever it is placed. Leave it untouched — it resolves against the
744
+ // in-content $id graph, and embedding the content into itself would be
745
+ // wrong.
746
+ return;
747
+ } catch (error) {
748
+ if (!(error instanceof EvaluationJsonSchemaUriError) || fragmentElement === documentElement) {
749
+ throw error;
750
+ }
751
+ const documentAsSchema = maybeRefractToSchemaElement(documentElement);
752
+ uriEvaluate($refBaseURI, documentAsSchema, uriEvaluateOptions);
753
+ // the $id resource sits in the current document but outside the
754
+ // traversed fragment, so it stays behind once the fragment is placed
755
+ // into the entry document. The resource to embed is that $id-bearing
756
+ // schema, addressed by its canonical URI (the $ref without fragment).
757
+ resourceRoot = uriEvaluate($refBaseURIStrippedHash, documentAsSchema, uriEvaluateOptions);
758
+ resourceBaseURI = $refBaseURIStrippedHash;
759
+ }
625
760
  } catch (error) {
626
761
  if (isURL && error instanceof EvaluationJsonSchemaUriError) {
762
+ schemaReference = await this.toReference(url.unsanitize($refBaseURI));
763
+ const referenceAsSchema = maybeRefractToSchemaElement(schemaReference.value.result);
627
764
  if (isAnchor(uriToAnchor($refBaseURI))) {
628
- schemaReference = await this.toReference(url.unsanitize($refBaseURI));
629
765
  const selector = uriToAnchor($refBaseURI);
630
- const referenceAsSchema = maybeRefractToSchemaElement(schemaReference.value.result);
631
766
  $anchorEvaluate(selector, referenceAsSchema);
632
767
  } else {
633
- schemaReference = await this.toReference(url.unsanitize($refBaseURI));
634
768
  const selector = URIFragmentIdentifier.fromURIReference($refBaseURI);
635
- const referenceAsSchema = maybeRefractToSchemaElement(schemaReference.value.result);
636
769
  jsonPointerEvaluate(referenceAsSchema, selector);
637
770
  }
771
+
772
+ // the resource to embed is the WHOLE external document (its root
773
+ // schema), identified by its $id. A $ref into a fragment of the
774
+ // document embeds the entire document resource once; the fragment
775
+ // keeps resolving against the embedded $id.
776
+ resourceRoot = referenceAsSchema;
777
+ resourceBaseURI = resolveSchema$idField(url.stripHash(schemaReference.uri), resourceRoot) ?? url.stripHash(schemaReference.uri);
638
778
  } else {
639
779
  throw error;
640
780
  }
641
781
  }
642
-
643
- // a $ref that resolves (by $id/$anchor/pointer) within the current document
644
- // is internal even when its URI form (e.g. a URN or absolute $id) is not a
645
- // bare fragment. Leave it untouched — it resolves against the in-document
646
- // $id graph, and embedding the current document into itself would be wrong.
647
- if (url.stripHash(schemaReference.uri) === url.stripHash(this.reference.uri)) {
648
- return;
649
- }
650
-
651
- // the resource to embed is the WHOLE external document (its root schema),
652
- // identified by its $id. A $ref into a fragment of the document embeds the
653
- // entire document resource once; the fragment keeps resolving against the
654
- // embedded $id.
655
- const resourceRoot = maybeRefractToSchemaElement(schemaReference.value.result);
656
- const resourceBaseURI = resolveSchema$idField(url.stripHash(schemaReference.uri), resourceRoot) ?? url.stripHash(schemaReference.uri);
657
782
  const field = cf.schemas.name;
658
783
  const canonicalKey = `${field}\t${resourceBaseURI}`;
659
784
 
660
- // resource already embedded (or being embedded) — leave the referencing
661
- // $ref untouched and stop. This also terminates circular external schema
785
+ // a location-based $ref to a self-identifying resource is rebased onto the
786
+ // resource's $id (keeping its fragment) — once embedded, the resource is
787
+ // reachable only by its $id. A $ref already resolving to that $id is left
788
+ // as written. See rebaseSchema$ref for the spec trade-off.
789
+ //
790
+ // a $ref whose base URI changes on relocation into the entry document
791
+ // (inside a hoisted Response, Parameter, Path Item, ...) is instead
792
+ // rewritten to address the embedded resource from the base it will have
793
+ // there, otherwise it dangles once placed. See relocateSchema$ref.
794
+ const relocatedBaseURI = this.relocatedSchemaBaseURI(path, $refBaseURI);
795
+ const rebased$ref = relocatedBaseURI === undefined ? rebaseSchema$ref($refBaseURI, resourceBaseURI) : relocateSchema$ref($refBaseURI, resourceBaseURI, relocatedBaseURI, isStringElement(resourceRoot.$id));
796
+
797
+ // resource already embedded (or being embedded) — point the referencing
798
+ // $ref at it and stop. This also terminates circular external schema
662
799
  // references, since the assignment is reserved BEFORE recursion.
663
800
  if (this.assignments.has(canonicalKey)) {
801
+ if (rebased$ref !== undefined) referencingElement.set('$ref', rebased$ref);
664
802
  path.skip();
665
803
  return;
666
804
  }
667
805
 
668
- // own a copy of the resource and ensure it carries a $id so the unchanged
669
- // $ref still resolves against it once embedded
806
+ // own a copy of the resource
670
807
  const embeddedElement = cloneDeep(resourceRoot);
671
- if (!isStringElement(embeddedElement.$id)) {
672
- embeddedElement.set('$id', resourceBaseURI);
673
- }
674
808
 
675
809
  // the embedded thing is the WHOLE resource (keyed by its $id), so its name
676
810
  // is derived from the resource URI, not the $ref fragment — passing a root
@@ -692,20 +826,36 @@ class OpenAPI3_1BundleVisitor {
692
826
  options: this.options,
693
827
  assignments: this.assignments,
694
828
  reservedNames: this.reservedNames,
695
- refractCache: this.refractCache
829
+ refractCache: this.refractCache,
830
+ schema$idIndex: this.schema$idIndex,
831
+ placements: this.placements
696
832
  });
697
833
  const bundledElement = await traverseAsync(embeddedElement, visitor, {
698
834
  mutable: true
699
835
  });
700
836
 
837
+ // a resource without its own $id is identified by its retrieval URI; write
838
+ // it relative to the entry document (the base of components.schemas) so the
839
+ // bundle carries no absolute filesystem path and stays relocatable. Assigned
840
+ // after the nested traversal so a reader deriving a schema's base from live
841
+ // $ids (see #540) never resolves the resource's own $refs against the entry
842
+ // document.
843
+ if (!isStringElement(bundledElement.$id)) {
844
+ bundledElement.set('$id', url.relative(this.entryURI, resourceBaseURI));
845
+ }
846
+
701
847
  // annotate the embedded resource with info about its origin
702
848
  bundledElement.meta.set('ref-origin', schemaReference.uri);
703
849
 
704
- // place the embedded resource into the entry document's components
705
- this.ensureComponentsField(field).set(componentName, bundledElement);
850
+ // queue the embedded resource for placement into the entry document's components
851
+ this.placements.push({
852
+ field,
853
+ name: componentName,
854
+ element: bundledElement
855
+ });
706
856
 
707
- // the referencing $ref is left UNCHANGED — it resolves against the
708
- // embedded resource's $id
857
+ // point the referencing $ref at the embedded resource's $id
858
+ if (rebased$ref !== undefined) referencingElement.set('$ref', rebased$ref);
709
859
  path.skip();
710
860
  } catch (error) {
711
861
  this.handleError(`Error while bundling Schema Object. Cannot resolve $ref "${$ref}": ${error.message}`, error, referencingElement, '$ref', $ref, path);
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
 
3
+ var _interopRequireWildcard = require("@babel/runtime-corejs3/helpers/interopRequireWildcard").default;
3
4
  Object.defineProperty(exports, "__esModule", {
4
5
  value: true
5
6
  });
6
- exports.uniqueName = exports.toPascalCase = exports.sanitizeComponentName = void 0;
7
+ exports.uniqueName = exports.toPascalCase = exports.sanitizeComponentName = exports.relocateSchema$ref = exports.rebaseSchema$ref = void 0;
8
+ var url = _interopRequireWildcard(require("../util/url.cjs"));
7
9
  /**
8
10
  * Upper-cases the first letter of each whitespace-separated word and joins
9
11
  * them, e.g. `pet model` -> `PetModel`.
@@ -39,4 +41,57 @@ const uniqueName = (candidate, isTaken) => {
39
41
  }
40
42
  return `${candidate}-${counter}`;
41
43
  };
42
- exports.uniqueName = uniqueName;
44
+
45
+ /**
46
+ * Computes the `$ref` value addressing an embedded JSON Schema resource by its
47
+ * canonical `$id`. `$refURI` is the `$ref` resolved against the referencing
48
+ * document's base URI; `resourceURI` is the embedded resource's canonical URI
49
+ * (its `$id`, or the retrieval URI when it has none).
50
+ *
51
+ * When the two differ — a location-based `$ref` (e.g. `./ex.json#/$defs/Pet`)
52
+ * to a resource that identifies itself differently (`$id: https://example.com/pets`)
53
+ * — the `$ref` is rebased onto the resource URI, keeping its `$anchor` or JSON
54
+ * Pointer fragment. Once embedded, the resource is reachable only by its `$id`,
55
+ * so an unchanged `$ref` would dangle. When they already agree, `undefined` is
56
+ * returned and the `$ref` is to be left as written.
57
+ *
58
+ * This knowingly departs from the letter of JSON Schema 2020-12 §9.3.1
59
+ * ("references ... MUST NOT be changed"), which presumes `$ref`s are written
60
+ * against `$id`s. The target schema — and thus evaluation output — is unchanged;
61
+ * only the alias the loader knew the resource by is normalized to its canonical URI.
62
+ *
63
+ * @public
64
+ */
65
+ exports.uniqueName = uniqueName;
66
+ const rebaseSchema$ref = ($refURI, resourceURI) => {
67
+ if (url.stripHash($refURI) === resourceURI) {
68
+ return undefined;
69
+ }
70
+ const fragment = url.getHash($refURI);
71
+ return fragment === '#' ? resourceURI : `${resourceURI}${fragment}`;
72
+ };
73
+
74
+ /**
75
+ * Computes the `$ref` value addressing an embedded JSON Schema resource from a
76
+ * Schema Object whose base URI changes when it is relocated into the entry
77
+ * document — one carried inside a hoisted Response, Parameter, Path Item, etc.
78
+ * `$refURI` and `resourceURI` are as in `rebaseSchema$ref`; `baseURI` is the
79
+ * base the `$ref` resolves against after relocation.
80
+ *
81
+ * A resource that identifies itself (`isResourceIdentified`, i.e. it declares a
82
+ * `$id`) is addressed by that `$id`, exactly as `rebaseSchema$ref` does. A
83
+ * resource identified only by its retrieval URI is addressed by a URI reference
84
+ * relative to `baseURI`: it then resolves to the `$id` assigned from that
85
+ * retrieval URI wherever the bundle is placed, and no absolute retrieval
86
+ * location leaks into the `$ref`. The `$anchor` or JSON Pointer fragment is
87
+ * kept in both cases.
88
+ *
89
+ * @public
90
+ */
91
+ exports.rebaseSchema$ref = rebaseSchema$ref;
92
+ const relocateSchema$ref = ($refURI, resourceURI, baseURI, isResourceIdentified) => {
93
+ const target = isResourceIdentified ? resourceURI : url.relative(baseURI, resourceURI);
94
+ const fragment = url.getHash($refURI);
95
+ return fragment === '#' ? target : `${target}${fragment}`;
96
+ };
97
+ exports.relocateSchema$ref = relocateSchema$ref;
@@ -1,3 +1,4 @@
1
+ import * as url from "../util/url.mjs";
1
2
  /**
2
3
  * Upper-cases the first letter of each whitespace-separated word and joins
3
4
  * them, e.g. `pet model` -> `PetModel`.
@@ -30,4 +31,55 @@ export const uniqueName = (candidate, isTaken) => {
30
31
  counter += 1;
31
32
  }
32
33
  return `${candidate}-${counter}`;
34
+ };
35
+
36
+ /**
37
+ * Computes the `$ref` value addressing an embedded JSON Schema resource by its
38
+ * canonical `$id`. `$refURI` is the `$ref` resolved against the referencing
39
+ * document's base URI; `resourceURI` is the embedded resource's canonical URI
40
+ * (its `$id`, or the retrieval URI when it has none).
41
+ *
42
+ * When the two differ — a location-based `$ref` (e.g. `./ex.json#/$defs/Pet`)
43
+ * to a resource that identifies itself differently (`$id: https://example.com/pets`)
44
+ * — the `$ref` is rebased onto the resource URI, keeping its `$anchor` or JSON
45
+ * Pointer fragment. Once embedded, the resource is reachable only by its `$id`,
46
+ * so an unchanged `$ref` would dangle. When they already agree, `undefined` is
47
+ * returned and the `$ref` is to be left as written.
48
+ *
49
+ * This knowingly departs from the letter of JSON Schema 2020-12 §9.3.1
50
+ * ("references ... MUST NOT be changed"), which presumes `$ref`s are written
51
+ * against `$id`s. The target schema — and thus evaluation output — is unchanged;
52
+ * only the alias the loader knew the resource by is normalized to its canonical URI.
53
+ *
54
+ * @public
55
+ */
56
+ export const rebaseSchema$ref = ($refURI, resourceURI) => {
57
+ if (url.stripHash($refURI) === resourceURI) {
58
+ return undefined;
59
+ }
60
+ const fragment = url.getHash($refURI);
61
+ return fragment === '#' ? resourceURI : `${resourceURI}${fragment}`;
62
+ };
63
+
64
+ /**
65
+ * Computes the `$ref` value addressing an embedded JSON Schema resource from a
66
+ * Schema Object whose base URI changes when it is relocated into the entry
67
+ * document — one carried inside a hoisted Response, Parameter, Path Item, etc.
68
+ * `$refURI` and `resourceURI` are as in `rebaseSchema$ref`; `baseURI` is the
69
+ * base the `$ref` resolves against after relocation.
70
+ *
71
+ * A resource that identifies itself (`isResourceIdentified`, i.e. it declares a
72
+ * `$id`) is addressed by that `$id`, exactly as `rebaseSchema$ref` does. A
73
+ * resource identified only by its retrieval URI is addressed by a URI reference
74
+ * relative to `baseURI`: it then resolves to the `$id` assigned from that
75
+ * retrieval URI wherever the bundle is placed, and no absolute retrieval
76
+ * location leaks into the `$ref`. The `$anchor` or JSON Pointer fragment is
77
+ * kept in both cases.
78
+ *
79
+ * @public
80
+ */
81
+ export const relocateSchema$ref = ($refURI, resourceURI, baseURI, isResourceIdentified) => {
82
+ const target = isResourceIdentified ? resourceURI : url.relative(baseURI, resourceURI);
83
+ const fragment = url.getHash($refURI);
84
+ return fragment === '#' ? target : `${target}${fragment}`;
33
85
  };
@@ -20,31 +20,31 @@ Object.defineProperty(exports, "dereferenceSourceDescriptions", {
20
20
  Object.defineProperty(exports, "identifiedBy$self", {
21
21
  enumerable: true,
22
22
  get: function () {
23
- return _util.identifiedBy$self;
23
+ return _util2.identifiedBy$self;
24
24
  }
25
25
  });
26
26
  Object.defineProperty(exports, "maybeRefractToJSONSchemaElement", {
27
27
  enumerable: true,
28
28
  get: function () {
29
- return _util.maybeRefractToJSONSchemaElement;
29
+ return _util2.maybeRefractToJSONSchemaElement;
30
30
  }
31
31
  });
32
32
  Object.defineProperty(exports, "resolveArazzo$selfField", {
33
33
  enumerable: true,
34
34
  get: function () {
35
- return _util.resolveArazzo$selfField;
35
+ return _util2.resolveArazzo$selfField;
36
36
  }
37
37
  });
38
38
  Object.defineProperty(exports, "resolveSchema$idField", {
39
39
  enumerable: true,
40
40
  get: function () {
41
- return _util.resolveSchema$idField;
41
+ return _util2.resolveSchema$idField;
42
42
  }
43
43
  });
44
44
  Object.defineProperty(exports, "resolveSchema$refField", {
45
45
  enumerable: true,
46
46
  get: function () {
47
- return _util.resolveSchema$refField;
47
+ return _util2.resolveSchema$refField;
48
48
  }
49
49
  });
50
50
  var _apidomDatamodel = require("@speclynx/apidom-datamodel");
@@ -54,8 +54,9 @@ var _DereferenceStrategy = _interopRequireDefault(require("../DereferenceStrateg
54
54
  var _Reference = _interopRequireDefault(require("../../../Reference.cjs"));
55
55
  var _ReferenceSet = _interopRequireDefault(require("../../../ReferenceSet.cjs"));
56
56
  var _visitor = _interopRequireDefault(require("./visitor.cjs"));
57
+ var _util = require("../openapi-3-1/util.cjs");
57
58
  var _sourceDescriptions = require("./source-descriptions.cjs");
58
- var _util = require("./util.cjs");
59
+ var _util2 = require("./util.cjs");
59
60
  /**
60
61
  * @public
61
62
  */
@@ -110,7 +111,8 @@ class Arazzo1DereferenceStrategy extends _DereferenceStrategy.default {
110
111
  const shouldDetectCircular = ['error', 'replace'].includes(options.dereference.circular);
111
112
  const visitor = new _visitor.default({
112
113
  reference,
113
- options
114
+ options,
115
+ ancestorSchema$ids: (0, _util.detachedRootSchema$ids)(refSet.rootRef.value.result)
114
116
  });
115
117
  const dereferencedElement = await (0, _apidomTraverse.traverseAsync)(refSet.rootRef.value, visitor, {
116
118
  mutable: true,
@@ -5,6 +5,7 @@ import DereferenceStrategy from "../DereferenceStrategy.mjs";
5
5
  import Reference from "../../../Reference.mjs";
6
6
  import ReferenceSet from "../../../ReferenceSet.mjs";
7
7
  import Arazzo1DereferenceVisitor from "./visitor.mjs";
8
+ import { detachedRootSchema$ids } from "../openapi-3-1/util.mjs";
8
9
  import { dereferenceSourceDescriptions } from "./source-descriptions.mjs";
9
10
  /**
10
11
  * @public
@@ -59,7 +60,8 @@ class Arazzo1DereferenceStrategy extends DereferenceStrategy {
59
60
  const shouldDetectCircular = ['error', 'replace'].includes(options.dereference.circular);
60
61
  const visitor = new Arazzo1DereferenceVisitor({
61
62
  reference,
62
- options
63
+ options,
64
+ ancestorSchema$ids: detachedRootSchema$ids(refSet.rootRef.value.result)
63
65
  });
64
66
  const dereferencedElement = await traverseAsync(refSet.rootRef.value, visitor, {
65
67
  mutable: true,
@@ -33,6 +33,12 @@ Object.defineProperty(exports, "isAnchor", {
33
33
  return _$anchor.isAnchor;
34
34
  }
35
35
  });
36
+ Object.defineProperty(exports, "locate", {
37
+ enumerable: true,
38
+ get: function () {
39
+ return _$anchor.locate;
40
+ }
41
+ });
36
42
  Object.defineProperty(exports, "parse", {
37
43
  enumerable: true,
38
44
  get: function () {
@@ -1 +1 @@
1
- export { isAnchor, uriToAnchor, parse, evaluate, EvaluationJsonSchema$anchorError, InvalidJsonSchema$anchorError, JsonSchema$anchorError } from "../../openapi-3-1/selectors/$anchor.mjs";
1
+ export { isAnchor, uriToAnchor, parse, locate, evaluate, EvaluationJsonSchema$anchorError, InvalidJsonSchema$anchorError, JsonSchema$anchorError } from "../../openapi-3-1/selectors/$anchor.mjs";
@@ -21,4 +21,10 @@ Object.defineProperty(exports, "evaluate", {
21
21
  return _uri.evaluate;
22
22
  }
23
23
  });
24
+ Object.defineProperty(exports, "locate", {
25
+ enumerable: true,
26
+ get: function () {
27
+ return _uri.locate;
28
+ }
29
+ });
24
30
  var _uri = require("../../openapi-3-1/selectors/uri.cjs");
@@ -1 +1 @@
1
- export { evaluate, EvaluationJsonSchemaUriError, JsonSchemaUriError } from "../../openapi-3-1/selectors/uri.mjs";
1
+ export { locate, evaluate, EvaluationJsonSchemaUriError, JsonSchemaUriError } from "../../openapi-3-1/selectors/uri.mjs";
@@ -17,6 +17,12 @@ Object.defineProperty(exports, "resolveSchema$refField", {
17
17
  return _util.resolveSchema$refField;
18
18
  }
19
19
  });
20
+ Object.defineProperty(exports, "resolveSchemaBaseURI", {
21
+ enumerable: true,
22
+ get: function () {
23
+ return _util.resolveSchemaBaseURI;
24
+ }
25
+ });
20
26
  exports.validateSourceDescriptionAPI = void 0;
21
27
  var _apidomDatamodel = require("@speclynx/apidom-datamodel");
22
28
  var _apidomCore = require("@speclynx/apidom-core");