@speclynx/apidom-reference 5.2.4 → 5.2.6

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 (53) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +37 -14
  3. package/dist/apidom-reference.browser.js +1234 -596
  4. package/dist/apidom-reference.browser.min.js +1 -1
  5. package/package.json +29 -29
  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 +20 -17
  13. package/src/dereference/strategies/arazzo-1/index.mjs +14 -11
  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/source-descriptions.cjs +65 -42
  19. package/src/dereference/strategies/arazzo-1/source-descriptions.mjs +64 -42
  20. package/src/dereference/strategies/arazzo-1/util.cjs +6 -0
  21. package/src/dereference/strategies/arazzo-1/util.mjs +1 -1
  22. package/src/dereference/strategies/arazzo-1/visitor.cjs +97 -35
  23. package/src/dereference/strategies/arazzo-1/visitor.mjs +101 -39
  24. package/src/dereference/strategies/openapi-3-1/index.cjs +2 -1
  25. package/src/dereference/strategies/openapi-3-1/index.mjs +3 -1
  26. package/src/dereference/strategies/openapi-3-1/selectors/$anchor.cjs +16 -4
  27. package/src/dereference/strategies/openapi-3-1/selectors/$anchor.mjs +14 -3
  28. package/src/dereference/strategies/openapi-3-1/selectors/json-pointer.cjs +29 -0
  29. package/src/dereference/strategies/openapi-3-1/selectors/json-pointer.mjs +22 -0
  30. package/src/dereference/strategies/openapi-3-1/selectors/uri.cjs +54 -27
  31. package/src/dereference/strategies/openapi-3-1/selectors/uri.mjs +51 -27
  32. package/src/dereference/strategies/openapi-3-1/util.cjs +75 -14
  33. package/src/dereference/strategies/openapi-3-1/util.mjs +68 -14
  34. package/src/dereference/strategies/openapi-3-1/visitor.cjs +85 -9
  35. package/src/dereference/strategies/openapi-3-1/visitor.mjs +89 -13
  36. package/src/util/url.cjs +35 -2
  37. package/src/util/url.mjs +32 -0
  38. package/types/apidom-reference.d.ts +12 -0
  39. package/types/bundle/strategies/arazzo-1/index.d.ts +1 -1
  40. package/types/bundle/strategies/arazzo-1/visitor.d.ts +51 -6
  41. package/types/bundle/strategies/openapi-3-1/index.d.ts +1 -1
  42. package/types/bundle/strategies/openapi-3-1/visitor.d.ts +59 -3
  43. package/types/bundle/util.d.ts +39 -0
  44. package/types/dereference/strategies/arazzo-1/selectors/$anchor.d.ts +1 -1
  45. package/types/dereference/strategies/arazzo-1/selectors/uri.d.ts +1 -1
  46. package/types/dereference/strategies/arazzo-1/util.d.ts +1 -1
  47. package/types/dereference/strategies/arazzo-1/visitor.d.ts +17 -1
  48. package/types/dereference/strategies/openapi-3-1/selectors/$anchor.d.ts +8 -0
  49. package/types/dereference/strategies/openapi-3-1/selectors/json-pointer.d.ts +9 -0
  50. package/types/dereference/strategies/openapi-3-1/selectors/uri.d.ts +37 -4
  51. package/types/dereference/strategies/openapi-3-1/util.d.ts +53 -2
  52. package/types/dereference/strategies/openapi-3-1/visitor.d.ts +17 -1
  53. package/types/util/url.d.ts +10 -0
@@ -1,14 +1,15 @@
1
1
  import { propEq, none } from 'ramda';
2
- import { isElement, isStringElement, RefElement, cloneShallow, cloneDeep } from '@speclynx/apidom-datamodel';
2
+ import { isElement, isStringElement, isParseResultElement, RefElement, cloneShallow, cloneDeep } from '@speclynx/apidom-datamodel';
3
3
  import { toValue, toYAML } from '@speclynx/apidom-core';
4
4
  import { ApiDOMStructuredError } from '@speclynx/apidom-error';
5
5
  import { traverse, traverseAsync } from '@speclynx/apidom-traverse';
6
6
  import { evaluate as jsonPointerEvaluate, compile as jsonPointerCompile, URIFragmentIdentifier } from '@speclynx/apidom-json-pointer';
7
7
  import { isParameterElement, isJSONSchemaElement, isBooleanJSONSchemaElement } from '@speclynx/apidom-ns-arazzo-1';
8
8
  import { parse as parseRuntimeExpression } from '@swaggerexpert/arazzo-runtime-expression';
9
- import { isAnchor, uriToAnchor, evaluate as $anchorEvaluate } from "./selectors/$anchor.mjs";
10
- import { evaluate as uriEvaluate } from "./selectors/uri.mjs";
11
- import { resolveSchema$refField } from "../openapi-3-1/util.mjs";
9
+ import { isAnchor, uriToAnchor, locate as $anchorLocate } from "./selectors/$anchor.mjs";
10
+ import { locate as uriLocate } from "./selectors/uri.mjs";
11
+ import { locate as jsonPointerLocate } from "../openapi-3-1/selectors/json-pointer.mjs";
12
+ import { schema$idsOf, resolveSchema$ids, resolveSchemaBaseURI, resolveSchema$refField } from "../openapi-3-1/util.mjs";
12
13
  import { maybeRefractToJSONSchemaElement, resolveArazzo$selfField, identifiedBy$self } from "./util.mjs";
13
14
  import UnresolvableReferenceError from "../../../errors/UnresolvableReferenceError.mjs";
14
15
  import MaximumDereferenceDepthError from "../../../errors/MaximumDereferenceDepthError.mjs";
@@ -36,8 +37,23 @@ class Arazzo1DereferenceVisitor {
36
37
  */
37
38
  baseURI;
38
39
  options;
40
+
41
+ /**
42
+ * Index of `$id`-bearing schemas per document, shared across nested visitors
43
+ * of a single dereference run so it never outlives mutations of the documents.
44
+ */
45
+ schema$idIndex;
39
46
  visited;
40
47
 
48
+ /**
49
+ * `$id`s of the JSON Schema elements enclosing the traversal root within its
50
+ * document (outermost first). Empty when the root is the document itself; a
51
+ * nested visitor traversing a fragment is seeded with the `$id`s of the
52
+ * schemas the fragment sits under, so the `$ref`s and `$id`s within resolve
53
+ * against the same base as they would from the document root.
54
+ */
55
+ ancestorSchema$ids;
56
+
41
57
  /**
42
58
  * Tracks element ancestors across dive-deep traversal boundaries.
43
59
  * Used for cycle detection: if a referenced element is found in
@@ -48,15 +64,19 @@ class Arazzo1DereferenceVisitor {
48
64
  reference,
49
65
  options,
50
66
  indirections = [],
67
+ schema$idIndex = new WeakMap(),
51
68
  ancestors = new AncestorLineage(),
52
- visited = new WeakSet()
69
+ visited = new WeakSet(),
70
+ ancestorSchema$ids = []
53
71
  }) {
54
72
  this.indirections = indirections;
55
73
  this.reference = reference;
56
74
  this.baseURI = resolveArazzo$selfField(reference.uri, reference.value?.result);
57
75
  this.options = options;
76
+ this.schema$idIndex = schema$idIndex;
58
77
  this.ancestors = new AncestorLineage(...ancestors);
59
78
  this.visited = visited;
79
+ this.ancestorSchema$ids = ancestorSchema$ids;
60
80
  }
61
81
  toAncestorLineage(path) {
62
82
  const ancestorNodes = path.getAncestorNodes();
@@ -223,34 +243,38 @@ class Arazzo1DereferenceVisitor {
223
243
  return;
224
244
  }
225
245
  const runtimeExpression = toValue(referencingElement.reference);
226
-
227
- // parse the runtime expression
228
- const {
229
- result,
230
- tree
231
- } = parseRuntimeExpression(runtimeExpression);
232
- if (!result.success) {
233
- throw new ApiDOMStructuredError(`Invalid Reusable Object reference format: "${runtimeExpression}"`, {
234
- runtimeExpression
235
- });
236
- }
237
-
238
- // ReusableElement can only reference components
239
- if (tree.type !== 'ComponentsExpression') {
240
- throw new ApiDOMStructuredError(`Reusable Object reference "${runtimeExpression}" must be a components expression`, {
241
- runtimeExpression
242
- });
243
- }
244
-
245
- // evaluate runtime expression as JSON Pointer to get the referenced element
246
- const jsonPointer = jsonPointerCompile(['components', tree.componentType, tree.componentName]);
247
246
  let referencedElement;
248
247
  try {
249
- referencedElement = jsonPointerEvaluate(this.reference.value.result, jsonPointer);
250
- } catch {
251
- throw new ApiDOMStructuredError(`Reusable Object reference "${runtimeExpression}" cannot be resolved`, {
252
- runtimeExpression
253
- });
248
+ // parse the runtime expression
249
+ const {
250
+ result,
251
+ tree
252
+ } = parseRuntimeExpression(runtimeExpression);
253
+ if (!result.success) {
254
+ throw new ApiDOMStructuredError(`Invalid Reusable Object reference format: "${runtimeExpression}"`, {
255
+ runtimeExpression
256
+ });
257
+ }
258
+
259
+ // ReusableElement can only reference components
260
+ if (tree.type !== 'ComponentsExpression') {
261
+ throw new ApiDOMStructuredError(`Reusable Object reference "${runtimeExpression}" must be a components expression`, {
262
+ runtimeExpression
263
+ });
264
+ }
265
+
266
+ // evaluate runtime expression as JSON Pointer to get the referenced element
267
+ const jsonPointer = jsonPointerCompile(['components', tree.componentType, tree.componentName]);
268
+ try {
269
+ referencedElement = jsonPointerEvaluate(this.reference.value.result, jsonPointer);
270
+ } catch {
271
+ throw new ApiDOMStructuredError(`Reusable Object reference "${runtimeExpression}" cannot be resolved`, {
272
+ runtimeExpression
273
+ });
274
+ }
275
+ } catch (error) {
276
+ this.handleError(`Error while dereferencing Reusable Object. Cannot resolve reference "${runtimeExpression}": ${error.message}`, error, referencingElement, 'reference', runtimeExpression, path);
277
+ return;
254
278
  }
255
279
 
256
280
  /**
@@ -298,7 +322,11 @@ class Arazzo1DereferenceVisitor {
298
322
  let {
299
323
  uri: retrievalURI
300
324
  } = reference;
301
- const $refBaseURI = resolveSchema$refField(this.baseURI, referencingElement);
325
+ // base URI in effect at the traversal root: the document base refined by
326
+ // the $ids of the schemas enclosing the root when the root is a fragment
327
+ const rootBaseURI = resolveSchema$ids(this.baseURI, this.ancestorSchema$ids);
328
+ const schemaBaseURI = resolveSchemaBaseURI(rootBaseURI, path);
329
+ const $refBaseURI = resolveSchema$refField(schemaBaseURI, referencingElement);
302
330
  const $refBaseURIStrippedHash = url.stripHash($refBaseURI);
303
331
  const file = new File({
304
332
  uri: $refBaseURIStrippedHash
@@ -310,13 +338,32 @@ class Arazzo1DereferenceVisitor {
310
338
 
311
339
  // determining reference, proper evaluation and selection mechanism
312
340
  let referencedElement;
341
+ // $ids of the schemas enclosing the referenced element within its document
342
+ let ancestorSchema$ids;
313
343
  try {
314
344
  if (isUnknownURI || isURL) {
315
345
  // we're dealing with canonical URI or URL with possible fragment
316
346
  retrievalURI = this.toBaseURI($refBaseURI);
317
347
  const selector = $refBaseURI;
318
- const referenceAsSchema = maybeRefractToJSONSchemaElement(reference.value.result);
319
- referencedElement = uriEvaluate(selector, referenceAsSchema);
348
+ const documentElement = reference.value.result;
349
+ const referenceAsSchema = maybeRefractToJSONSchemaElement(documentElement);
350
+ // a document that is itself a fragment detached from its source carries
351
+ // the $ids enclosing it only as `ancestorSchema$ids`: its $ids are then
352
+ // indexed against the base that chain leaves in effect, and the chain
353
+ // prefixes the one of the located schema. A nested visitor rooted at a
354
+ // fragment of a whole document indexes that document against its own base.
355
+ const rootNode = path.getAncestorNodes().at(-1) ?? path.node;
356
+ const isDocumentRoot = (isParseResultElement(rootNode) ? rootNode.result : rootNode) === documentElement;
357
+ ({
358
+ element: referencedElement,
359
+ ancestorSchema$ids
360
+ } = uriLocate(selector, referenceAsSchema, {
361
+ baseURI: isDocumentRoot ? rootBaseURI : this.baseURI,
362
+ index: this.schema$idIndex
363
+ }));
364
+ if (isDocumentRoot) {
365
+ ancestorSchema$ids = [...this.ancestorSchema$ids, ...ancestorSchema$ids];
366
+ }
320
367
  referencedElement = maybeRefractToJSONSchemaElement(referencedElement);
321
368
 
322
369
  // ignore resolving internal Schema Objects
@@ -348,7 +395,10 @@ class Arazzo1DereferenceVisitor {
348
395
  reference = await this.toReference(url.unsanitize($refBaseURI));
349
396
  const selector = URIFragmentIdentifier.fromURIReference($refBaseURI);
350
397
  const referenceAsSchema = maybeRefractToJSONSchemaElement(reference.value.result);
351
- referencedElement = jsonPointerEvaluate(referenceAsSchema, selector);
398
+ ({
399
+ element: referencedElement,
400
+ ancestorSchema$ids
401
+ } = jsonPointerLocate(referenceAsSchema, selector));
352
402
  referencedElement = maybeRefractToJSONSchemaElement(referencedElement);
353
403
  }
354
404
  } catch (error) {
@@ -375,7 +425,10 @@ class Arazzo1DereferenceVisitor {
375
425
  reference = await this.toReference(url.unsanitize($refBaseURI));
376
426
  const selector = uriToAnchor($refBaseURI);
377
427
  const referenceAsSchema = maybeRefractToJSONSchemaElement(reference.value.result);
378
- referencedElement = $anchorEvaluate(selector, referenceAsSchema);
428
+ ({
429
+ element: referencedElement,
430
+ ancestorSchema$ids
431
+ } = $anchorLocate(selector, referenceAsSchema));
379
432
  referencedElement = maybeRefractToJSONSchemaElement(referencedElement);
380
433
  } else {
381
434
  // we're assuming here that we're dealing with JSON Pointer here
@@ -396,7 +449,10 @@ class Arazzo1DereferenceVisitor {
396
449
  reference = await this.toReference(url.unsanitize($refBaseURI));
397
450
  const selector = URIFragmentIdentifier.fromURIReference($refBaseURI);
398
451
  const referenceAsSchema = maybeRefractToJSONSchemaElement(reference.value.result);
399
- referencedElement = jsonPointerEvaluate(referenceAsSchema, selector);
452
+ ({
453
+ element: referencedElement,
454
+ ancestorSchema$ids
455
+ } = jsonPointerLocate(referenceAsSchema, selector));
400
456
  referencedElement = maybeRefractToJSONSchemaElement(referencedElement);
401
457
  }
402
458
  } else {
@@ -453,7 +509,11 @@ class Arazzo1DereferenceVisitor {
453
509
  */
454
510
  const isNonEntryDocument = url.stripHash(reference.refSet.rootRef.uri) !== reference.uri;
455
511
  const shouldDetectCircular = ['error', 'replace'].includes(this.options.dereference.circular);
456
- if ((isExternalReference || isNonEntryDocument || isJSONSchemaElement(referencedElement) && isStringElement(referencedElement.$ref) || shouldDetectCircular && !this.visited.has(referencedElement)) && !ancestorsLineage.includesCycle(referencedElement)) {
512
+ // content transplanted into the referencing site is traversed under that
513
+ // site's $id chain; a fragment enclosed by a different chain has its own
514
+ // $refs resolved in place first, by a nested visitor seeded with that chain
515
+ const hasDifferentEnclosingBase = resolveSchema$ids(resolveArazzo$selfField(reference.uri, reference.value.result), ancestorSchema$ids) !== resolveSchema$ids(rootBaseURI, schema$idsOf(path.getAncestorNodes().reverse()));
516
+ if ((isExternalReference || isNonEntryDocument || hasDifferentEnclosingBase || isJSONSchemaElement(referencedElement) && isStringElement(referencedElement.$ref) || shouldDetectCircular && !this.visited.has(referencedElement)) && !ancestorsLineage.includesCycle(referencedElement)) {
457
517
  if (shouldDetectCircular) {
458
518
  this.visited.add(referencedElement);
459
519
  }
@@ -464,8 +524,10 @@ class Arazzo1DereferenceVisitor {
464
524
  reference,
465
525
  indirections: [...this.indirections],
466
526
  options: this.options,
527
+ schema$idIndex: this.schema$idIndex,
467
528
  visited: this.visited,
468
- ancestors: ancestorsLineage
529
+ ancestors: ancestorsLineage,
530
+ ancestorSchema$ids
469
531
  });
470
532
  referencedElement = await traverseAsync(referencedElement, visitor, {
471
533
  mutable: true,
@@ -92,7 +92,8 @@ class OpenAPI3_1DereferenceStrategy extends _DereferenceStrategy.default {
92
92
  const shouldDetectCircular = ['error', 'replace'].includes(options.dereference.circular);
93
93
  const visitor = new _visitor.default({
94
94
  reference: reference,
95
- options
95
+ options,
96
+ ancestorSchema$ids: (0, _util.detachedRootSchema$ids)(refSet.rootRef.value.result)
96
97
  });
97
98
  const dereferencedElement = await (0, _apidomTraverse.traverseAsync)(refSet.rootRef.value, visitor, {
98
99
  mutable: true,
@@ -6,6 +6,7 @@ import DereferenceStrategy from "../DereferenceStrategy.mjs";
6
6
  import Reference from "../../../Reference.mjs";
7
7
  import ReferenceSet from "../../../ReferenceSet.mjs";
8
8
  import OpenAPI3_1DereferenceVisitor from "./visitor.mjs";
9
+ import { detachedRootSchema$ids } from "./util.mjs";
9
10
  /**
10
11
  * @public
11
12
  */
@@ -59,7 +60,8 @@ class OpenAPI3_1DereferenceStrategy extends DereferenceStrategy {
59
60
  const shouldDetectCircular = ['error', 'replace'].includes(options.dereference.circular);
60
61
  const visitor = new OpenAPI3_1DereferenceVisitor({
61
62
  reference: 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,
@@ -22,12 +22,13 @@ Object.defineProperty(exports, "JsonSchema$anchorError", {
22
22
  return _JsonSchema$anchorError.default;
23
23
  }
24
24
  });
25
- exports.uriToAnchor = exports.parse = exports.isAnchor = exports.evaluate = void 0;
25
+ exports.uriToAnchor = exports.parse = exports.locate = exports.isAnchor = exports.evaluate = void 0;
26
26
  var _ramdaAdjunct = require("ramda-adjunct");
27
27
  var _apidomCore = require("@speclynx/apidom-core");
28
28
  var _apidomTraverse = require("@speclynx/apidom-traverse");
29
29
  var _apidomNsJsonSchema = require("@speclynx/apidom-ns-json-schema-2020-12");
30
30
  var _url = require("../../../../util/url.cjs");
31
+ var _util = require("../util.cjs");
31
32
  var _EvaluationJsonSchema$anchorError = _interopRequireDefault(require("../../../../errors/EvaluationJsonSchema$anchorError.cjs"));
32
33
  var _InvalidJsonSchema$anchorError = _interopRequireDefault(require("../../../../errors/InvalidJsonSchema$anchorError.cjs"));
33
34
  var _JsonSchema$anchorError = _interopRequireDefault(require("../../../../errors/JsonSchema$anchorError.cjs"));
@@ -65,11 +66,12 @@ const parse = anchor => {
65
66
  };
66
67
 
67
68
  /**
68
- * Evaluates JSON Schema $anchor against ApiDOM fragment.
69
+ * Locates the schema identified by JSON Schema $anchor within ApiDOM fragment,
70
+ * along with the `$id`s of the schemas enclosing it, the search root included.
69
71
  * @public
70
72
  */
71
73
  exports.parse = parse;
72
- const evaluate = (anchor, element) => {
74
+ const locate = (anchor, element) => {
73
75
  const token = parse(anchor);
74
76
  const resultPath = (0, _apidomTraverse.find)(element,
75
77
  // @ts-ignore
@@ -77,6 +79,16 @@ const evaluate = (anchor, element) => {
77
79
  if ((0, _ramdaAdjunct.isUndefined)(resultPath)) {
78
80
  throw new _EvaluationJsonSchema$anchorError.default(`Evaluation failed on token: "${token}"`);
79
81
  }
80
- return resultPath.node;
82
+ return {
83
+ element: resultPath.node,
84
+ ancestorSchema$ids: (0, _util.schema$idsOf)(resultPath.getAncestorNodes().reverse())
85
+ };
81
86
  };
87
+
88
+ /**
89
+ * Evaluates JSON Schema $anchor against ApiDOM fragment.
90
+ * @public
91
+ */
92
+ exports.locate = locate;
93
+ const evaluate = (anchor, element) => locate(anchor, element).element;
82
94
  exports.evaluate = evaluate;
@@ -3,6 +3,7 @@ import { toValue } from '@speclynx/apidom-core';
3
3
  import { find } from '@speclynx/apidom-traverse';
4
4
  import { isJSONSchemaElement } from '@speclynx/apidom-ns-json-schema-2020-12';
5
5
  import { getHash } from "../../../../util/url.mjs";
6
+ import { schema$idsOf } from "../util.mjs";
6
7
  import EvaluationJsonSchema$anchorError from "../../../../errors/EvaluationJsonSchema$anchorError.mjs";
7
8
  import InvalidJsonSchema$anchorError from "../../../../errors/InvalidJsonSchema$anchorError.mjs";
8
9
  /**
@@ -37,10 +38,11 @@ export const parse = anchor => {
37
38
  };
38
39
 
39
40
  /**
40
- * Evaluates JSON Schema $anchor against ApiDOM fragment.
41
+ * Locates the schema identified by JSON Schema $anchor within ApiDOM fragment,
42
+ * along with the `$id`s of the schemas enclosing it, the search root included.
41
43
  * @public
42
44
  */
43
- export const evaluate = (anchor, element) => {
45
+ export const locate = (anchor, element) => {
44
46
  const token = parse(anchor);
45
47
  const resultPath = find(element,
46
48
  // @ts-ignore
@@ -48,7 +50,16 @@ export const evaluate = (anchor, element) => {
48
50
  if (isUndefined(resultPath)) {
49
51
  throw new EvaluationJsonSchema$anchorError(`Evaluation failed on token: "${token}"`);
50
52
  }
51
- return resultPath.node;
53
+ return {
54
+ element: resultPath.node,
55
+ ancestorSchema$ids: schema$idsOf(resultPath.getAncestorNodes().reverse())
56
+ };
52
57
  };
58
+
59
+ /**
60
+ * Evaluates JSON Schema $anchor against ApiDOM fragment.
61
+ * @public
62
+ */
63
+ export const evaluate = (anchor, element) => locate(anchor, element).element;
53
64
  export { EvaluationJsonSchema$anchorError, InvalidJsonSchema$anchorError };
54
65
  export { default as JsonSchema$anchorError } from "../../../../errors/JsonSchema$anchorError.mjs";
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ exports.locate = void 0;
7
+ var _apidomJsonPointer = require("@speclynx/apidom-json-pointer");
8
+ var _util = require("../util.cjs");
9
+ /**
10
+ * Locates the element addressed by JSON Pointer within ApiDOM fragment, along
11
+ * with the `$id`s of the schemas enclosing it: the elements the pointer walks
12
+ * through on its way to the target, the search root included.
13
+ */
14
+ const locate = (element, jsonPointer) => {
15
+ const trace = {
16
+ steps: []
17
+ };
18
+ const result = (0, _apidomJsonPointer.evaluate)(element, jsonPointer, {
19
+ trace
20
+ });
21
+
22
+ // each step's input is the node the token was applied to, so the inputs are
23
+ // exactly the walked nodes minus the target
24
+ return {
25
+ element: result,
26
+ ancestorSchema$ids: (0, _util.schema$idsOf)(trace.steps.map(step => step.input))
27
+ };
28
+ };
29
+ exports.locate = locate;
@@ -0,0 +1,22 @@
1
+ import { evaluate as jsonPointerEvaluate } from '@speclynx/apidom-json-pointer';
2
+ import { schema$idsOf } from "../util.mjs";
3
+ /**
4
+ * Locates the element addressed by JSON Pointer within ApiDOM fragment, along
5
+ * with the `$id`s of the schemas enclosing it: the elements the pointer walks
6
+ * through on its way to the target, the search root included.
7
+ */
8
+ export const locate = (element, jsonPointer) => {
9
+ const trace = {
10
+ steps: []
11
+ };
12
+ const result = jsonPointerEvaluate(element, jsonPointer, {
13
+ trace
14
+ });
15
+
16
+ // each step's input is the node the token was applied to, so the inputs are
17
+ // exactly the walked nodes minus the target
18
+ return {
19
+ element: result,
20
+ ancestorSchema$ids: schema$idsOf(trace.steps.map(step => step.input))
21
+ };
22
+ };
@@ -17,46 +17,73 @@ Object.defineProperty(exports, "JsonSchemaUriError", {
17
17
  return _JsonSchemaUriError.default;
18
18
  }
19
19
  });
20
- exports.evaluate = void 0;
20
+ exports.locate = exports.evaluate = void 0;
21
21
  var _ramdaAdjunct = require("ramda-adjunct");
22
22
  var _apidomTraverse = require("@speclynx/apidom-traverse");
23
- var _apidomNsJsonSchema = require("@speclynx/apidom-ns-json-schema-2020-12");
24
23
  var _apidomJsonPointer = require("@speclynx/apidom-json-pointer");
25
24
  var url = _interopRequireWildcard(require("../../../../util/url.cjs"));
26
25
  var _EvaluationJsonSchemaUriError = _interopRequireDefault(require("../../../../errors/EvaluationJsonSchemaUriError.cjs"));
27
- var _$anchor = require("./$anchor.cjs");
28
26
  var _util = require("../util.cjs");
27
+ var _$anchor = require("./$anchor.cjs");
28
+ var _jsonPointer = require("./json-pointer.cjs");
29
29
  var _JsonSchemaUriError = _interopRequireDefault(require("../../../../errors/JsonSchemaUriError.cjs"));
30
30
  /**
31
- * Evaluates JSON Schema $ref containing unknown URI against ApiDOM fragment.
31
+ * Index of `$id`-bearing schemas per indexed element (a document root, or the
32
+ * fragment a visitor traverses). Each entry pairs a schema with the `$id`s of its
33
+ * enclosing schema resources (outermost first, including its own). The index is
34
+ * populated once per element and never refreshed, so callers scope it
35
+ * to a single dereference/bundle run instead of sharing it globally. Within a run the
36
+ * `$id` graph of an indexed document is stable: the bundle strategies mutate deep
37
+ * clones and place components only after the entry traversal ends, and the
38
+ * dereference strategies only replace `$ref`-bearing schemas.
32
39
  * @public
33
40
  */
34
- const evaluate = (uri, element) => {
35
- const {
36
- cache
37
- } = evaluate;
38
- const uriStrippedHash = url.stripHash(uri);
39
- const isJSONSchemaElementWith$id = path => (0, _apidomNsJsonSchema.isJSONSchemaElement)(path.node) && typeof path.node.$id !== 'undefined';
40
41
 
41
- // warm the cache
42
- if (!cache.has(element)) {
43
- const schemaObjectPaths = (0, _apidomTraverse.filter)(element, isJSONSchemaElementWith$id);
44
- cache.set(element, schemaObjectPaths.map(path => path.node));
42
+ /**
43
+ * @public
44
+ */
45
+
46
+ const indexSchema$ids = element => (0, _apidomTraverse.filter)(element, path => (0, _util.isJSONSchemaElementWith$id)(path.node)).map(path => ({
47
+ schema: path.node,
48
+ $ids: (0, _util.collectSchema$ids)(path)
49
+ }));
50
+
51
+ /**
52
+ * Locates the schema addressed by JSON Schema $ref containing unknown URI within
53
+ * ApiDOM fragment, along with the `$id`s of the schemas enclosing it.
54
+ * @public
55
+ */
56
+ const locate = (uri, element, options = {}) => {
57
+ const uriStrippedHash = url.stripHash(uri);
58
+ const {
59
+ baseURI = uriStrippedHash,
60
+ index = new WeakMap()
61
+ } = options;
62
+ if (!index.has(element)) {
63
+ index.set(element, indexSchema$ids(element));
45
64
  }
46
65
 
47
- // search for the matching schema
48
- const result = cache.get(element).find(e => {
49
- const $idBaseURI = (0, _util.resolveSchema$idField)(uriStrippedHash, e);
50
- return $idBaseURI === uriStrippedHash;
51
- });
52
- if ((0, _ramdaAdjunct.isUndefined)(result)) {
66
+ // search for the schema whose canonical URI matches
67
+ const entry = index.get(element).find(({
68
+ $ids
69
+ }) => (0, _util.resolveSchema$ids)(baseURI, $ids) === uriStrippedHash);
70
+ if ((0, _ramdaAdjunct.isUndefined)(entry)) {
53
71
  throw new _EvaluationJsonSchemaUriError.default(`Evaluation failed on URI: "${uri}"`);
54
72
  }
55
- if ((0, _$anchor.isAnchor)((0, _$anchor.uriToAnchor)(uri))) {
56
- // we're dealing with JSON Schema $anchor here
57
- return (0, _$anchor.evaluate)((0, _$anchor.uriToAnchor)(uri), result);
58
- }
59
- return (0, _apidomJsonPointer.evaluate)(result, _apidomJsonPointer.URIFragmentIdentifier.fromURIReference(uri));
73
+
74
+ // the fragment is resolved within the matched schema, whose own $id is the
75
+ // last of its chain; the fragment walk contributes it again when descending
76
+ const fragmentLocation = (0, _$anchor.isAnchor)((0, _$anchor.uriToAnchor)(uri)) ? (0, _$anchor.locate)((0, _$anchor.uriToAnchor)(uri), entry.schema) : (0, _jsonPointer.locate)(entry.schema, _apidomJsonPointer.URIFragmentIdentifier.fromURIReference(uri));
77
+ return {
78
+ element: fragmentLocation.element,
79
+ ancestorSchema$ids: [...entry.$ids.slice(0, -1), ...fragmentLocation.ancestorSchema$ids]
80
+ };
60
81
  };
61
- exports.evaluate = evaluate;
62
- evaluate.cache = new WeakMap();
82
+
83
+ /**
84
+ * Evaluates JSON Schema $ref containing unknown URI against ApiDOM fragment.
85
+ * @public
86
+ */
87
+ exports.locate = locate;
88
+ const evaluate = (uri, element, options = {}) => locate(uri, element, options).element;
89
+ exports.evaluate = evaluate;
@@ -1,42 +1,66 @@
1
1
  import { isUndefined } from 'ramda-adjunct';
2
2
  import { filter } from '@speclynx/apidom-traverse';
3
- import { isJSONSchemaElement } from '@speclynx/apidom-ns-json-schema-2020-12';
4
- import { URIFragmentIdentifier, evaluate as jsonPointerEvaluate } from '@speclynx/apidom-json-pointer';
3
+ import { URIFragmentIdentifier } from '@speclynx/apidom-json-pointer';
5
4
  import * as url from "../../../../util/url.mjs";
6
5
  import EvaluationJsonSchemaUriError from "../../../../errors/EvaluationJsonSchemaUriError.mjs";
7
- import { isAnchor, uriToAnchor, evaluate as $anchorEvaluate } from "./$anchor.mjs";
8
- import { resolveSchema$idField } from "../util.mjs";
6
+ import { isJSONSchemaElementWith$id, collectSchema$ids, resolveSchema$ids } from "../util.mjs";
7
+ import { isAnchor, uriToAnchor, locate as $anchorLocate } from "./$anchor.mjs";
8
+ import { locate as jsonPointerLocate } from "./json-pointer.mjs";
9
9
  /**
10
- * Evaluates JSON Schema $ref containing unknown URI against ApiDOM fragment.
10
+ * Index of `$id`-bearing schemas per indexed element (a document root, or the
11
+ * fragment a visitor traverses). Each entry pairs a schema with the `$id`s of its
12
+ * enclosing schema resources (outermost first, including its own). The index is
13
+ * populated once per element and never refreshed, so callers scope it
14
+ * to a single dereference/bundle run instead of sharing it globally. Within a run the
15
+ * `$id` graph of an indexed document is stable: the bundle strategies mutate deep
16
+ * clones and place components only after the entry traversal ends, and the
17
+ * dereference strategies only replace `$ref`-bearing schemas.
11
18
  * @public
12
19
  */
13
- export const evaluate = (uri, element) => {
14
- const {
15
- cache
16
- } = evaluate;
17
- const uriStrippedHash = url.stripHash(uri);
18
- const isJSONSchemaElementWith$id = path => isJSONSchemaElement(path.node) && typeof path.node.$id !== 'undefined';
20
+ /**
21
+ * @public
22
+ */
23
+ const indexSchema$ids = element => filter(element, path => isJSONSchemaElementWith$id(path.node)).map(path => ({
24
+ schema: path.node,
25
+ $ids: collectSchema$ids(path)
26
+ }));
19
27
 
20
- // warm the cache
21
- if (!cache.has(element)) {
22
- const schemaObjectPaths = filter(element, isJSONSchemaElementWith$id);
23
- cache.set(element, schemaObjectPaths.map(path => path.node));
28
+ /**
29
+ * Locates the schema addressed by JSON Schema $ref containing unknown URI within
30
+ * ApiDOM fragment, along with the `$id`s of the schemas enclosing it.
31
+ * @public
32
+ */
33
+ export const locate = (uri, element, options = {}) => {
34
+ const uriStrippedHash = url.stripHash(uri);
35
+ const {
36
+ baseURI = uriStrippedHash,
37
+ index = new WeakMap()
38
+ } = options;
39
+ if (!index.has(element)) {
40
+ index.set(element, indexSchema$ids(element));
24
41
  }
25
42
 
26
- // search for the matching schema
27
- const result = cache.get(element).find(e => {
28
- const $idBaseURI = resolveSchema$idField(uriStrippedHash, e);
29
- return $idBaseURI === uriStrippedHash;
30
- });
31
- if (isUndefined(result)) {
43
+ // search for the schema whose canonical URI matches
44
+ const entry = index.get(element).find(({
45
+ $ids
46
+ }) => resolveSchema$ids(baseURI, $ids) === uriStrippedHash);
47
+ if (isUndefined(entry)) {
32
48
  throw new EvaluationJsonSchemaUriError(`Evaluation failed on URI: "${uri}"`);
33
49
  }
34
- if (isAnchor(uriToAnchor(uri))) {
35
- // we're dealing with JSON Schema $anchor here
36
- return $anchorEvaluate(uriToAnchor(uri), result);
37
- }
38
- return jsonPointerEvaluate(result, URIFragmentIdentifier.fromURIReference(uri));
50
+
51
+ // the fragment is resolved within the matched schema, whose own $id is the
52
+ // last of its chain; the fragment walk contributes it again when descending
53
+ const fragmentLocation = isAnchor(uriToAnchor(uri)) ? $anchorLocate(uriToAnchor(uri), entry.schema) : jsonPointerLocate(entry.schema, URIFragmentIdentifier.fromURIReference(uri));
54
+ return {
55
+ element: fragmentLocation.element,
56
+ ancestorSchema$ids: [...entry.$ids.slice(0, -1), ...fragmentLocation.ancestorSchema$ids]
57
+ };
39
58
  };
40
- evaluate.cache = new WeakMap();
59
+
60
+ /**
61
+ * Evaluates JSON Schema $ref containing unknown URI against ApiDOM fragment.
62
+ * @public
63
+ */
64
+ export const evaluate = (uri, element, options = {}) => locate(uri, element, options).element;
41
65
  export { EvaluationJsonSchemaUriError };
42
66
  export { default as JsonSchemaUriError } from "../../../../errors/JsonSchemaUriError.mjs";