@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@speclynx/apidom-reference",
3
- "version": "5.2.4",
3
+ "version": "5.2.5",
4
4
  "description": "Advanced algorithms for semantic ApiDOM manipulations like dereferencing or resolution.",
5
5
  "keywords": [
6
6
  "apidom",
@@ -299,32 +299,32 @@
299
299
  "license": "Apache-2.0",
300
300
  "dependencies": {
301
301
  "@babel/runtime-corejs3": "^8.0.0",
302
- "@speclynx/apidom-core": "5.2.4",
303
- "@speclynx/apidom-datamodel": "5.2.4",
304
- "@speclynx/apidom-error": "5.2.4",
305
- "@speclynx/apidom-json-pointer": "5.2.4",
306
- "@speclynx/apidom-ns-arazzo-1": "5.2.4",
307
- "@speclynx/apidom-ns-asyncapi-2": "5.2.4",
308
- "@speclynx/apidom-ns-json-schema-2020-12": "5.2.4",
309
- "@speclynx/apidom-ns-openapi-2": "5.2.4",
310
- "@speclynx/apidom-ns-openapi-3-0": "5.2.4",
311
- "@speclynx/apidom-ns-openapi-3-1": "5.2.4",
312
- "@speclynx/apidom-ns-overlay-1": "5.2.4",
313
- "@speclynx/apidom-parser-adapter-arazzo-json-1": "5.2.4",
314
- "@speclynx/apidom-parser-adapter-arazzo-yaml-1": "5.2.4",
315
- "@speclynx/apidom-parser-adapter-asyncapi-json-2": "5.2.4",
316
- "@speclynx/apidom-parser-adapter-asyncapi-yaml-2": "5.2.4",
317
- "@speclynx/apidom-parser-adapter-json": "5.2.4",
318
- "@speclynx/apidom-parser-adapter-openapi-json-2": "5.2.4",
319
- "@speclynx/apidom-parser-adapter-openapi-json-3-0": "5.2.4",
320
- "@speclynx/apidom-parser-adapter-openapi-json-3-1": "5.2.4",
321
- "@speclynx/apidom-parser-adapter-openapi-yaml-2": "5.2.4",
322
- "@speclynx/apidom-parser-adapter-openapi-yaml-3-0": "5.2.4",
323
- "@speclynx/apidom-parser-adapter-openapi-yaml-3-1": "5.2.4",
324
- "@speclynx/apidom-parser-adapter-overlay-json-1": "5.2.4",
325
- "@speclynx/apidom-parser-adapter-overlay-yaml-1": "5.2.4",
326
- "@speclynx/apidom-parser-adapter-yaml-1-2": "5.2.4",
327
- "@speclynx/apidom-traverse": "5.2.4",
302
+ "@speclynx/apidom-core": "5.2.5",
303
+ "@speclynx/apidom-datamodel": "5.2.5",
304
+ "@speclynx/apidom-error": "5.2.5",
305
+ "@speclynx/apidom-json-pointer": "5.2.5",
306
+ "@speclynx/apidom-ns-arazzo-1": "5.2.5",
307
+ "@speclynx/apidom-ns-asyncapi-2": "5.2.5",
308
+ "@speclynx/apidom-ns-json-schema-2020-12": "5.2.5",
309
+ "@speclynx/apidom-ns-openapi-2": "5.2.5",
310
+ "@speclynx/apidom-ns-openapi-3-0": "5.2.5",
311
+ "@speclynx/apidom-ns-openapi-3-1": "5.2.5",
312
+ "@speclynx/apidom-ns-overlay-1": "5.2.5",
313
+ "@speclynx/apidom-parser-adapter-arazzo-json-1": "5.2.5",
314
+ "@speclynx/apidom-parser-adapter-arazzo-yaml-1": "5.2.5",
315
+ "@speclynx/apidom-parser-adapter-asyncapi-json-2": "5.2.5",
316
+ "@speclynx/apidom-parser-adapter-asyncapi-yaml-2": "5.2.5",
317
+ "@speclynx/apidom-parser-adapter-json": "5.2.5",
318
+ "@speclynx/apidom-parser-adapter-openapi-json-2": "5.2.5",
319
+ "@speclynx/apidom-parser-adapter-openapi-json-3-0": "5.2.5",
320
+ "@speclynx/apidom-parser-adapter-openapi-json-3-1": "5.2.5",
321
+ "@speclynx/apidom-parser-adapter-openapi-yaml-2": "5.2.5",
322
+ "@speclynx/apidom-parser-adapter-openapi-yaml-3-0": "5.2.5",
323
+ "@speclynx/apidom-parser-adapter-openapi-yaml-3-1": "5.2.5",
324
+ "@speclynx/apidom-parser-adapter-overlay-json-1": "5.2.5",
325
+ "@speclynx/apidom-parser-adapter-overlay-yaml-1": "5.2.5",
326
+ "@speclynx/apidom-parser-adapter-yaml-1-2": "5.2.5",
327
+ "@speclynx/apidom-traverse": "5.2.5",
328
328
  "@swaggerexpert/arazzo-runtime-expression": "^3.2.0",
329
329
  "axios": "^1.20.0",
330
330
  "picomatch": "^4.0.7",
@@ -346,5 +346,5 @@
346
346
  "README.md",
347
347
  "CHANGELOG.md"
348
348
  ],
349
- "gitHead": "5b467dbfe42b1989fe64b4a83c226274de24d783"
349
+ "gitHead": "677f42e6af4e7b5311c74cd054283c943f8700a9"
350
350
  }
@@ -34,6 +34,12 @@ const cf = (0, _apidomCore.fixedFields)(_apidomNsArazzo.ComponentsElement, {
34
34
  indexed: true
35
35
  });
36
36
 
37
+ /**
38
+ * A bundled JSON Schema resource awaiting placement into the entry document's
39
+ * `components.inputs`.
40
+ * @public
41
+ */
42
+
37
43
  /**
38
44
  * @public
39
45
  */
@@ -81,17 +87,47 @@ class Arazzo1BundleVisitor {
81
87
  * components that are still being bundled.
82
88
  */
83
89
  reservedNames;
90
+
91
+ /**
92
+ * Embedded resources waiting to be placed into the entry document's
93
+ * `components.inputs`, shared across the entry document and every external
94
+ * document visitor. Placement is deferred until the entry document traversal
95
+ * completes: the traversal walks live content, so placing a resource mid-way
96
+ * would make it descend into the resource and re-bundle its `$ref`s against
97
+ * the entry document's base URI instead of the resource's own.
98
+ */
99
+ placements;
100
+
101
+ /**
102
+ * Index of `$id`-bearing schemas per document, shared across the entry document
103
+ * and every external document visitor of a single bundle run so it never
104
+ * outlives mutations of the documents (e.g. the final placement of inputs).
105
+ */
106
+ schema$idIndex;
84
107
  constructor({
85
108
  reference,
86
109
  options,
87
110
  assignments = new Map(),
88
- reservedNames = new Map()
111
+ reservedNames = new Map(),
112
+ placements = [],
113
+ schema$idIndex = new WeakMap()
89
114
  }) {
90
115
  this.reference = reference;
91
116
  this.baseURI = (0, _util.resolveArazzo$selfField)(reference.uri, reference.value?.result);
92
117
  this.options = options;
93
118
  this.assignments = assignments;
94
119
  this.reservedNames = reservedNames;
120
+ this.placements = placements;
121
+ this.schema$idIndex = schema$idIndex;
122
+ }
123
+
124
+ /**
125
+ * Base URI of the entry document — its `$self` when present, otherwise its
126
+ * retrieval URI. It is the base against which a relative `$id` written into
127
+ * `components.inputs` resolves.
128
+ */
129
+ get entryBaseURI() {
130
+ return (0, _util.resolveArazzo$selfField)(url.stripHash(this.reference.refSet.rootRef.uri), this.entryResult);
95
131
  }
96
132
  toBaseURI(uri) {
97
133
  return url.resolve(this.baseURI, url.sanitize(url.stripHash(uri)));
@@ -243,14 +279,17 @@ class Arazzo1BundleVisitor {
243
279
 
244
280
  /**
245
281
  * Computes a collision-free component name within `components.inputs`. A name
246
- * is taken if it's already placed in inputs or reserved by a component that is
247
- * still being bundled (reserved before recursion).
282
+ * is taken if it's already present in the entry document's inputs or reserved
283
+ * by a component that is still being bundled (reserved before recursion).
248
284
  */
249
285
  uniqueName(candidate) {
250
286
  const field = cf.inputs.name;
251
- const inputs = this.ensureInputsField();
287
+ const {
288
+ components
289
+ } = this.entryResult;
290
+ const inputs = (0, _apidomDatamodel.isObjectElement)(components) ? components.get(field) : undefined;
252
291
  const reserved = this.reservedNames.get(field) ?? new Set();
253
- return (0, _util2.uniqueName)(candidate, name => inputs.hasKey(name) || reserved.has(name));
292
+ return (0, _util2.uniqueName)(candidate, name => (0, _apidomDatamodel.isObjectElement)(inputs) && inputs.hasKey(name) || reserved.has(name));
254
293
  }
255
294
 
256
295
  /**
@@ -275,14 +314,36 @@ class Arazzo1BundleVisitor {
275
314
  this.entryParseResult.content.push(annotation);
276
315
  }
277
316
 
317
+ /**
318
+ * The entry document traversal is rooted at its Parse Result (nested
319
+ * traversals are rooted at schema resources), so leaving it marks the moment
320
+ * the whole entry document has been visited and the collected resources can
321
+ * be placed into `components.inputs` without the traversal descending into them.
322
+ * Guarded on the node so that a traversal rooted at any other Parse Result
323
+ * never flushes the shared placements early.
324
+ */
325
+ ParseResultElement = {
326
+ leave: path => {
327
+ if (path.node !== this.entryParseResult) return;
328
+ for (const {
329
+ name,
330
+ element
331
+ } of this.placements) {
332
+ this.ensureInputsField().set(name, element);
333
+ }
334
+ this.placements.length = 0;
335
+ }
336
+ };
337
+
278
338
  /**
279
339
  * JSON Schema Objects in Arazzo (used by Input Objects and inline schemas) are
280
340
  * a JSON Schema 2020-12 dialect — the only external reference type Arazzo
281
341
  * defines. They are bundled per the JSON Schema Compound Document rules: the
282
342
  * external schema RESOURCE is embedded verbatim into `components.inputs`
283
- * (carrying its `$id`), and the referencing `$ref` is left UNCHANGED — it
284
- * keeps resolving against the embedded resource's `$id`. This mirrors the
285
- * openapi-3-1 strategy's Schema Object handling.
343
+ * (carrying its `$id`), and the referencing `$ref` is rebased onto that `$id`
344
+ * when it resolves to a different URI (a location-based `$ref` to a
345
+ * self-identifying resource); otherwise it is left unchanged. This mirrors
346
+ * the openapi-3-1 strategy's Schema Object handling.
286
347
  */
287
348
  async JSONSchemaElement(path) {
288
349
  const referencingElement = path.node;
@@ -297,7 +358,8 @@ class Arazzo1BundleVisitor {
297
358
  // current document's base URI is derived directly (not via toReference)
298
359
  // so an internal $ref needs no resolution and never trips the
299
360
  // resolve.maxDepth guard.
300
- const $refBaseURI = (0, _util.resolveSchema$refField)(this.baseURI, referencingElement);
361
+ const schemaBaseURI = (0, _util.resolveSchemaBaseURI)(this.baseURI, path);
362
+ const $refBaseURI = (0, _util.resolveSchema$refField)(schemaBaseURI, referencingElement);
301
363
  const $refBaseURIStrippedHash = url.stripHash($refBaseURI);
302
364
  const file = new _File.default({
303
365
  uri: $refBaseURIStrippedHash
@@ -335,7 +397,10 @@ class Arazzo1BundleVisitor {
335
397
  let schemaReference = this.reference;
336
398
  try {
337
399
  const referenceAsSchema = (0, _util.maybeRefractToJSONSchemaElement)(schemaReference.value.result);
338
- (0, _uri.evaluate)($refBaseURI, referenceAsSchema);
400
+ (0, _uri.evaluate)($refBaseURI, referenceAsSchema, {
401
+ baseURI: this.baseURI,
402
+ index: this.schema$idIndex
403
+ });
339
404
  } catch (error) {
340
405
  if (isURL && error instanceof _EvaluationJsonSchemaUriError.default) {
341
406
  if ((0, _$anchor.isAnchor)((0, _$anchor.uriToAnchor)($refBaseURI))) {
@@ -374,20 +439,23 @@ class Arazzo1BundleVisitor {
374
439
  const field = cf.inputs.name;
375
440
  const canonicalKey = `${field}\t${resourceBaseURI}`;
376
441
 
377
- // resource already embedded (or being embedded) — leave the referencing
378
- // $ref untouched and stop. This also terminates circular external schema
442
+ // a location-based $ref to a self-identifying resource is rebased onto the
443
+ // resource's $id (keeping its fragment) — once embedded, the resource is
444
+ // reachable only by its $id. A $ref already resolving to that $id is left
445
+ // as written. See rebaseSchema$ref for the spec trade-off.
446
+ const rebased$ref = (0, _util2.rebaseSchema$ref)($refBaseURI, resourceBaseURI);
447
+
448
+ // resource already embedded (or being embedded) — point the referencing
449
+ // $ref at it and stop. This also terminates circular external schema
379
450
  // references, since the assignment is reserved BEFORE recursion.
380
451
  if (this.assignments.has(canonicalKey)) {
452
+ if (rebased$ref !== undefined) referencingElement.set('$ref', rebased$ref);
381
453
  path.skip();
382
454
  return;
383
455
  }
384
456
 
385
- // own a copy of the resource and ensure it carries a $id so the unchanged
386
- // $ref still resolves against it once embedded
457
+ // own a copy of the resource
387
458
  const embeddedElement = (0, _apidomDatamodel.cloneDeep)(resourceRoot);
388
- if (!(0, _apidomDatamodel.isStringElement)(embeddedElement.$id)) {
389
- embeddedElement.set('$id', resourceBaseURI);
390
- }
391
459
 
392
460
  // the embedded thing is the WHOLE resource (keyed by its $id), so its name
393
461
  // is derived from the resource URI
@@ -407,22 +475,37 @@ class Arazzo1BundleVisitor {
407
475
  reference: schemaReference,
408
476
  options: this.options,
409
477
  assignments: this.assignments,
410
- reservedNames: this.reservedNames
478
+ reservedNames: this.reservedNames,
479
+ placements: this.placements,
480
+ schema$idIndex: this.schema$idIndex
411
481
  });
412
482
  const bundledElement = await (0, _apidomTraverse.traverseAsync)(embeddedElement, visitor, {
413
483
  mutable: true
414
484
  });
415
485
 
486
+ // a resource without its own $id is identified by its retrieval URI; write
487
+ // it relative to the entry document (the base of components.inputs) so the
488
+ // bundle carries no absolute filesystem path and stays relocatable. Assigned
489
+ // after the nested traversal so a reader deriving a schema's base from live
490
+ // $ids (see #540) never resolves the resource's own $refs against the entry
491
+ // document.
492
+ if (!(0, _apidomDatamodel.isStringElement)(bundledElement.$id)) {
493
+ bundledElement.set('$id', url.relative(this.entryBaseURI, resourceBaseURI));
494
+ }
495
+
416
496
  // annotate the embedded resource with info about its origin
417
497
  if ((0, _apidomDatamodel.isElement)(bundledElement)) {
418
498
  bundledElement.meta.set('ref-origin', schemaReference.uri);
419
499
  }
420
500
 
421
- // place the embedded resource into the entry document's components.inputs
422
- this.ensureInputsField().set(componentName, bundledElement);
501
+ // queue the embedded resource for placement into the entry document's components.inputs
502
+ this.placements.push({
503
+ name: componentName,
504
+ element: bundledElement
505
+ });
423
506
 
424
- // the referencing $ref is left UNCHANGED — it resolves against the
425
- // embedded resource's $id
507
+ // point the referencing $ref at the embedded resource's $id
508
+ if (rebased$ref !== undefined) referencingElement.set('$ref', rebased$ref);
426
509
  path.skip();
427
510
  } catch (error) {
428
511
  this.handleError(`Error while bundling JSON Schema Object. Cannot resolve $ref "${$ref}": ${error.message}`, error, referencingElement, '$ref', $ref, path);
@@ -15,8 +15,8 @@ import Reference from "../../../Reference.mjs";
15
15
  import File from "../../../File.mjs";
16
16
  import { isAnchor, uriToAnchor, evaluate as $anchorEvaluate } from "../../../dereference/strategies/arazzo-1/selectors/$anchor.mjs";
17
17
  import { evaluate as uriEvaluate } from "../../../dereference/strategies/arazzo-1/selectors/uri.mjs";
18
- import { resolveSchema$refField, resolveSchema$idField, maybeRefractToJSONSchemaElement, resolveArazzo$selfField, identifiedBy$self } from "../../../dereference/strategies/arazzo-1/util.mjs";
19
- import { toPascalCase, sanitizeComponentName, uniqueName as resolveUniqueName } from "../../util.mjs";
18
+ import { resolveSchema$refField, resolveSchema$idField, resolveSchemaBaseURI, maybeRefractToJSONSchemaElement, resolveArazzo$selfField, identifiedBy$self } from "../../../dereference/strategies/arazzo-1/util.mjs";
19
+ import { rebaseSchema$ref, sanitizeComponentName, toPascalCase, uniqueName as resolveUniqueName } from "../../util.mjs";
20
20
  /**
21
21
  * The Components Object field where embedded JSON Schema resources are hoisted.
22
22
  * Sourced from the Components Object fixed fields so it cannot drift from the
@@ -26,6 +26,12 @@ const cf = fixedFields(ComponentsElement, {
26
26
  indexed: true
27
27
  });
28
28
 
29
+ /**
30
+ * A bundled JSON Schema resource awaiting placement into the entry document's
31
+ * `components.inputs`.
32
+ * @public
33
+ */
34
+
29
35
  /**
30
36
  * @public
31
37
  */
@@ -73,17 +79,47 @@ class Arazzo1BundleVisitor {
73
79
  * components that are still being bundled.
74
80
  */
75
81
  reservedNames;
82
+
83
+ /**
84
+ * Embedded resources waiting to be placed into the entry document's
85
+ * `components.inputs`, shared across the entry document and every external
86
+ * document visitor. Placement is deferred until the entry document traversal
87
+ * completes: the traversal walks live content, so placing a resource mid-way
88
+ * would make it descend into the resource and re-bundle its `$ref`s against
89
+ * the entry document's base URI instead of the resource's own.
90
+ */
91
+ placements;
92
+
93
+ /**
94
+ * Index of `$id`-bearing schemas per document, shared across the entry document
95
+ * and every external document visitor of a single bundle run so it never
96
+ * outlives mutations of the documents (e.g. the final placement of inputs).
97
+ */
98
+ schema$idIndex;
76
99
  constructor({
77
100
  reference,
78
101
  options,
79
102
  assignments = new Map(),
80
- reservedNames = new Map()
103
+ reservedNames = new Map(),
104
+ placements = [],
105
+ schema$idIndex = new WeakMap()
81
106
  }) {
82
107
  this.reference = reference;
83
108
  this.baseURI = resolveArazzo$selfField(reference.uri, reference.value?.result);
84
109
  this.options = options;
85
110
  this.assignments = assignments;
86
111
  this.reservedNames = reservedNames;
112
+ this.placements = placements;
113
+ this.schema$idIndex = schema$idIndex;
114
+ }
115
+
116
+ /**
117
+ * Base URI of the entry document — its `$self` when present, otherwise its
118
+ * retrieval URI. It is the base against which a relative `$id` written into
119
+ * `components.inputs` resolves.
120
+ */
121
+ get entryBaseURI() {
122
+ return resolveArazzo$selfField(url.stripHash(this.reference.refSet.rootRef.uri), this.entryResult);
87
123
  }
88
124
  toBaseURI(uri) {
89
125
  return url.resolve(this.baseURI, url.sanitize(url.stripHash(uri)));
@@ -235,14 +271,17 @@ class Arazzo1BundleVisitor {
235
271
 
236
272
  /**
237
273
  * Computes a collision-free component name within `components.inputs`. A name
238
- * is taken if it's already placed in inputs or reserved by a component that is
239
- * still being bundled (reserved before recursion).
274
+ * is taken if it's already present in the entry document's inputs or reserved
275
+ * by a component that is still being bundled (reserved before recursion).
240
276
  */
241
277
  uniqueName(candidate) {
242
278
  const field = cf.inputs.name;
243
- const inputs = this.ensureInputsField();
279
+ const {
280
+ components
281
+ } = this.entryResult;
282
+ const inputs = isObjectElement(components) ? components.get(field) : undefined;
244
283
  const reserved = this.reservedNames.get(field) ?? new Set();
245
- return resolveUniqueName(candidate, name => inputs.hasKey(name) || reserved.has(name));
284
+ return resolveUniqueName(candidate, name => isObjectElement(inputs) && inputs.hasKey(name) || reserved.has(name));
246
285
  }
247
286
 
248
287
  /**
@@ -267,14 +306,36 @@ class Arazzo1BundleVisitor {
267
306
  this.entryParseResult.content.push(annotation);
268
307
  }
269
308
 
309
+ /**
310
+ * The entry document traversal is rooted at its Parse Result (nested
311
+ * traversals are rooted at schema resources), so leaving it marks the moment
312
+ * the whole entry document has been visited and the collected resources can
313
+ * be placed into `components.inputs` without the traversal descending into them.
314
+ * Guarded on the node so that a traversal rooted at any other Parse Result
315
+ * never flushes the shared placements early.
316
+ */
317
+ ParseResultElement = {
318
+ leave: path => {
319
+ if (path.node !== this.entryParseResult) return;
320
+ for (const {
321
+ name,
322
+ element
323
+ } of this.placements) {
324
+ this.ensureInputsField().set(name, element);
325
+ }
326
+ this.placements.length = 0;
327
+ }
328
+ };
329
+
270
330
  /**
271
331
  * JSON Schema Objects in Arazzo (used by Input Objects and inline schemas) are
272
332
  * a JSON Schema 2020-12 dialect — the only external reference type Arazzo
273
333
  * defines. They are bundled per the JSON Schema Compound Document rules: the
274
334
  * external schema RESOURCE is embedded verbatim into `components.inputs`
275
- * (carrying its `$id`), and the referencing `$ref` is left UNCHANGED — it
276
- * keeps resolving against the embedded resource's `$id`. This mirrors the
277
- * openapi-3-1 strategy's Schema Object handling.
335
+ * (carrying its `$id`), and the referencing `$ref` is rebased onto that `$id`
336
+ * when it resolves to a different URI (a location-based `$ref` to a
337
+ * self-identifying resource); otherwise it is left unchanged. This mirrors
338
+ * the openapi-3-1 strategy's Schema Object handling.
278
339
  */
279
340
  async JSONSchemaElement(path) {
280
341
  const referencingElement = path.node;
@@ -289,7 +350,8 @@ class Arazzo1BundleVisitor {
289
350
  // current document's base URI is derived directly (not via toReference)
290
351
  // so an internal $ref needs no resolution and never trips the
291
352
  // resolve.maxDepth guard.
292
- const $refBaseURI = resolveSchema$refField(this.baseURI, referencingElement);
353
+ const schemaBaseURI = resolveSchemaBaseURI(this.baseURI, path);
354
+ const $refBaseURI = resolveSchema$refField(schemaBaseURI, referencingElement);
293
355
  const $refBaseURIStrippedHash = url.stripHash($refBaseURI);
294
356
  const file = new File({
295
357
  uri: $refBaseURIStrippedHash
@@ -327,7 +389,10 @@ class Arazzo1BundleVisitor {
327
389
  let schemaReference = this.reference;
328
390
  try {
329
391
  const referenceAsSchema = maybeRefractToJSONSchemaElement(schemaReference.value.result);
330
- uriEvaluate($refBaseURI, referenceAsSchema);
392
+ uriEvaluate($refBaseURI, referenceAsSchema, {
393
+ baseURI: this.baseURI,
394
+ index: this.schema$idIndex
395
+ });
331
396
  } catch (error) {
332
397
  if (isURL && error instanceof EvaluationJsonSchemaUriError) {
333
398
  if (isAnchor(uriToAnchor($refBaseURI))) {
@@ -366,20 +431,23 @@ class Arazzo1BundleVisitor {
366
431
  const field = cf.inputs.name;
367
432
  const canonicalKey = `${field}\t${resourceBaseURI}`;
368
433
 
369
- // resource already embedded (or being embedded) — leave the referencing
370
- // $ref untouched and stop. This also terminates circular external schema
434
+ // a location-based $ref to a self-identifying resource is rebased onto the
435
+ // resource's $id (keeping its fragment) — once embedded, the resource is
436
+ // reachable only by its $id. A $ref already resolving to that $id is left
437
+ // as written. See rebaseSchema$ref for the spec trade-off.
438
+ const rebased$ref = rebaseSchema$ref($refBaseURI, resourceBaseURI);
439
+
440
+ // resource already embedded (or being embedded) — point the referencing
441
+ // $ref at it and stop. This also terminates circular external schema
371
442
  // references, since the assignment is reserved BEFORE recursion.
372
443
  if (this.assignments.has(canonicalKey)) {
444
+ if (rebased$ref !== undefined) referencingElement.set('$ref', rebased$ref);
373
445
  path.skip();
374
446
  return;
375
447
  }
376
448
 
377
- // own a copy of the resource and ensure it carries a $id so the unchanged
378
- // $ref still resolves against it once embedded
449
+ // own a copy of the resource
379
450
  const embeddedElement = cloneDeep(resourceRoot);
380
- if (!isStringElement(embeddedElement.$id)) {
381
- embeddedElement.set('$id', resourceBaseURI);
382
- }
383
451
 
384
452
  // the embedded thing is the WHOLE resource (keyed by its $id), so its name
385
453
  // is derived from the resource URI
@@ -399,22 +467,37 @@ class Arazzo1BundleVisitor {
399
467
  reference: schemaReference,
400
468
  options: this.options,
401
469
  assignments: this.assignments,
402
- reservedNames: this.reservedNames
470
+ reservedNames: this.reservedNames,
471
+ placements: this.placements,
472
+ schema$idIndex: this.schema$idIndex
403
473
  });
404
474
  const bundledElement = await traverseAsync(embeddedElement, visitor, {
405
475
  mutable: true
406
476
  });
407
477
 
478
+ // a resource without its own $id is identified by its retrieval URI; write
479
+ // it relative to the entry document (the base of components.inputs) so the
480
+ // bundle carries no absolute filesystem path and stays relocatable. Assigned
481
+ // after the nested traversal so a reader deriving a schema's base from live
482
+ // $ids (see #540) never resolves the resource's own $refs against the entry
483
+ // document.
484
+ if (!isStringElement(bundledElement.$id)) {
485
+ bundledElement.set('$id', url.relative(this.entryBaseURI, resourceBaseURI));
486
+ }
487
+
408
488
  // annotate the embedded resource with info about its origin
409
489
  if (isElement(bundledElement)) {
410
490
  bundledElement.meta.set('ref-origin', schemaReference.uri);
411
491
  }
412
492
 
413
- // place the embedded resource into the entry document's components.inputs
414
- this.ensureInputsField().set(componentName, bundledElement);
493
+ // queue the embedded resource for placement into the entry document's components.inputs
494
+ this.placements.push({
495
+ name: componentName,
496
+ element: bundledElement
497
+ });
415
498
 
416
- // the referencing $ref is left UNCHANGED — it resolves against the
417
- // embedded resource's $id
499
+ // point the referencing $ref at the embedded resource's $id
500
+ if (rebased$ref !== undefined) referencingElement.set('$ref', rebased$ref);
418
501
  path.skip();
419
502
  } catch (error) {
420
503
  this.handleError(`Error while bundling JSON Schema Object. Cannot resolve $ref "${$ref}": ${error.message}`, error, referencingElement, '$ref', $ref, path);