@jentic/arazzo-resolver 1.0.0-alpha.10

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.
package/README.md ADDED
@@ -0,0 +1,490 @@
1
+ # @jentic/arazzo-resolver
2
+
3
+ `@jentic/arazzo-resolver` is a resolver for [Arazzo Specification](https://spec.openapis.org/arazzo/latest.html) and [OpenAPI Specification](https://spec.openapis.org/oas/latest.html) documents.
4
+ It produces [SpecLynx ApiDOM](https://github.com/speclynx/apidom) data models using the appropriate namespace ([Arazzo 1.x](https://github.com/speclynx/apidom/tree/main/packages/apidom-ns-arazzo-1#readme), [OpenAPI 2.0](https://github.com/speclynx/apidom/tree/main/packages/apidom-ns-openapi-2#readme), [OpenAPI 3.0.x](https://github.com/speclynx/apidom/tree/main/packages/apidom-ns-openapi-3-0#readme), [OpenAPI 3.1.x](https://github.com/speclynx/apidom/tree/main/packages/apidom-ns-openapi-3-1#readme)).
5
+
6
+ ## Installation
7
+
8
+ You can install this package via [npm](https://npmjs.org/) CLI by running the following command:
9
+
10
+ ```sh
11
+ npm install @jentic/arazzo-resolver
12
+ ```
13
+
14
+ ## Dereferencing
15
+
16
+ Dereferencing is the process of replacing references with the actual content they point to.
17
+
18
+ **In Arazzo Documents**, this includes:
19
+
20
+ - **JSON Schemas** - resolves references within schemas
21
+ - **Reusable Object references** (`$components.*`) - references to reusable components like parameters and actions
22
+
23
+ **In OpenAPI Documents**, this includes:
24
+
25
+ - **Reference Objects** (`$ref`) - resolves references to components, external files, and URLs
26
+ - **JSON Schemas** - resolves references within schemas
27
+ - **Path Item Object** - resolves references to path items
28
+ - and others
29
+
30
+ After dereferencing, all references are resolved inline, making the document self-contained and easier to process programmatically.
31
+
32
+ ### Functions
33
+
34
+ **Arazzo:**
35
+ - **`dereferenceArazzo(uri)`** - Dereferences from a file system path or HTTP(S) URL
36
+ - **`dereferenceArazzoElement(element)`** - Dereferences a SpecLynx ApiDOM element
37
+
38
+ **OpenAPI:**
39
+ - **`dereferenceOpenAPI(uri)`** - Dereferences from a file system path or HTTP(S) URL
40
+ - **`dereferenceOpenAPIElement(element)`** - Dereferences a SpecLynx ApiDOM element
41
+
42
+ ### Arazzo Documents
43
+
44
+ #### From file
45
+
46
+ ```js
47
+ import { dereferenceArazzo } from '@jentic/arazzo-resolver';
48
+
49
+ const parseResult = await dereferenceArazzo('/path/to/arazzo.json');
50
+ // parseResult is ParseResultElement with all references resolved
51
+ ```
52
+
53
+ #### From URL
54
+
55
+ ```js
56
+ import { dereferenceArazzo } from '@jentic/arazzo-resolver';
57
+
58
+ const parseResult = await dereferenceArazzo('https://example.com/arazzo.yaml');
59
+ ```
60
+
61
+ #### From ApiDOM element
62
+
63
+ When you already have a parsed Arazzo Document (e.g., from `@jentic/arazzo-parser`), you can dereference the element directly:
64
+
65
+ ```js
66
+ import { parseArazzo } from '@jentic/arazzo-parser';
67
+ import { dereferenceArazzoElement } from '@jentic/arazzo-resolver';
68
+
69
+ // Parse first, then dereference
70
+ const parseResult = await parseArazzo('/path/to/arazzo.json');
71
+ const dereferenced = await dereferenceArazzoElement(parseResult);
72
+ ```
73
+
74
+ ##### Without retrievalURI
75
+
76
+ When dereferencing a ParseResultElement that was parsed from inline content (string or object), you must provide a `baseURI`:
77
+
78
+ ```js
79
+ import { parseArazzo } from '@jentic/arazzo-parser';
80
+ import { dereferenceArazzoElement } from '@jentic/arazzo-resolver';
81
+
82
+ const parseResult = await parseArazzo({ arazzo: '1.0.1', ... });
83
+ const dereferenced = await dereferenceArazzoElement(parseResult, {
84
+ resolve: { baseURI: 'https://example.com/arazzo.json' },
85
+ });
86
+ ```
87
+
88
+ ##### Child elements
89
+
90
+ You can dereference individual child elements (e.g., a specific workflow) by providing the parent parseResult in options:
91
+
92
+ ```js
93
+ import { parseArazzo } from '@jentic/arazzo-parser';
94
+ import { dereferenceArazzoElement } from '@jentic/arazzo-resolver';
95
+
96
+ const parseResult = await parseArazzo('/path/to/arazzo.json');
97
+ const workflow = parseResult.api.workflows.get(0);
98
+
99
+ const dereferencedWorkflow = await dereferenceArazzoElement(workflow, {
100
+ dereference: { strategyOpts: { parseResult } },
101
+ });
102
+ ```
103
+
104
+ ##### Source descriptions
105
+
106
+ Source descriptions referenced in the Arazzo Document can optionally be dereferenced using strategy options.
107
+
108
+ The following options can be passed via `dereference.strategyOpts` (globally) or `dereference.strategyOpts['arazzo-1']` (strategy-specific).
109
+ Strategy-specific options take precedence over global options.
110
+
111
+ - **sourceDescriptions** - Controls which external source descriptions are dereferenced and included in the result.
112
+ - `true` - dereference all source descriptions
113
+ - `string[]` - dereference only source descriptions with matching names (e.g., `['petStore', 'paymentApi']`)
114
+
115
+ Each dereferenced source description is added with a `'source-description'` class and metadata (`name`, `type`, `retrievalURI`).
116
+ Only [OpenAPI 2.0](https://spec.openapis.org/oas/v2.0), [OpenAPI 3.0.x](https://spec.openapis.org/oas/v3.0.4), [OpenAPI 3.1.x](https://spec.openapis.org/oas/v3.1.2), and [Arazzo 1.x](https://spec.openapis.org/arazzo/v1.0.1) documents are accepted as source descriptions.
117
+ - **sourceDescriptionsMaxDepth** - Maximum recursion depth for dereferencing nested Arazzo source descriptions.
118
+ Defaults to `+Infinity`. Circular references are automatically detected and skipped.
119
+
120
+ ###### Error handling
121
+
122
+ The source descriptions dereferencing uses annotations instead of throwing errors, allowing dereferencing to continue
123
+ even when individual source descriptions fail. Errors are reported as `AnnotationElement` instances
124
+ with an `'error'` class within the result:
125
+
126
+ - **Max depth exceeded** - When `sourceDescriptionsMaxDepth` is reached, an error annotation is returned
127
+ instead of the nested source descriptions
128
+ - **Dereference failures** - If a source description file cannot be dereferenced (e.g., file not found, invalid syntax),
129
+ an error annotation is returned for that specific source description while other source descriptions
130
+ continue to be processed
131
+ - **Validation warnings** - Warning annotations (with `'warning'` class) are returned when the dereferenced document
132
+ is not an OpenAPI or Arazzo specification
133
+
134
+ ```js
135
+ import { dereferenceArazzo } from '@jentic/arazzo-resolver';
136
+
137
+ // Dereference all source descriptions
138
+ const result = await dereferenceArazzo('/path/to/arazzo.json', {
139
+ dereference: {
140
+ strategyOpts: {
141
+ sourceDescriptions: true,
142
+ sourceDescriptionsMaxDepth: 10,
143
+ },
144
+ },
145
+ });
146
+
147
+ // Dereference only specific source descriptions by name
148
+ const resultFiltered = await dereferenceArazzo('/path/to/arazzo.json', {
149
+ dereference: {
150
+ strategyOpts: {
151
+ 'arazzo-1': {
152
+ sourceDescriptions: ['petStore', 'paymentApi'],
153
+ },
154
+ },
155
+ },
156
+ });
157
+ ```
158
+
159
+ ### OpenAPI Documents
160
+
161
+ Supports [OpenAPI 2.0 (Swagger)](https://spec.openapis.org/oas/v2.0), [OpenAPI 3.0.x](https://spec.openapis.org/oas/v3.0.4), and [OpenAPI 3.1.x](https://spec.openapis.org/oas/v3.1.2).
162
+
163
+ #### From file
164
+
165
+ ```js
166
+ import { dereferenceOpenAPI } from '@jentic/arazzo-resolver';
167
+
168
+ const parseResult = await dereferenceOpenAPI('/path/to/openapi.json');
169
+ // parseResult is ParseResultElement with all references resolved
170
+ ```
171
+
172
+ #### From URL
173
+
174
+ ```js
175
+ import { dereferenceOpenAPI } from '@jentic/arazzo-resolver';
176
+
177
+ const parseResult = await dereferenceOpenAPI('https://example.com/openapi.yaml');
178
+ ```
179
+
180
+ #### From ApiDOM element
181
+
182
+ When you already have a parsed OpenAPI Document (e.g., from `@jentic/arazzo-parser`), you can dereference the element directly:
183
+
184
+ ```js
185
+ import { parseOpenAPI } from '@jentic/arazzo-parser';
186
+ import { dereferenceOpenAPIElement } from '@jentic/arazzo-resolver';
187
+
188
+ // Parse first, then dereference
189
+ const parseResult = await parseOpenAPI('/path/to/openapi.json');
190
+ const dereferenced = await dereferenceOpenAPIElement(parseResult);
191
+ ```
192
+
193
+ ##### Without retrievalURI
194
+
195
+ When dereferencing a ParseResultElement that was parsed from inline content (string or object), you must provide a `baseURI`:
196
+
197
+ ```js
198
+ import { parseOpenAPI } from '@jentic/arazzo-parser';
199
+ import { dereferenceOpenAPIElement } from '@jentic/arazzo-resolver';
200
+
201
+ const parseResult = await parseOpenAPI({ openapi: '3.1.0', ... });
202
+ const dereferenced = await dereferenceOpenAPIElement(parseResult, {
203
+ resolve: { baseURI: 'https://example.com/openapi.json' },
204
+ });
205
+ ```
206
+
207
+ ##### Child elements
208
+
209
+ You can dereference individual child elements (e.g., a specific Operation Object) by providing the parent parseResult in options:
210
+
211
+ ```js
212
+ import { parseOpenAPI } from '@jentic/arazzo-parser';
213
+ import { dereferenceOpenAPIElement } from '@jentic/arazzo-resolver';
214
+
215
+ const parseResult = await parseOpenAPI('/path/to/openapi.json');
216
+ const operation = parseResult.api.paths.get('/users').get;
217
+
218
+ const dereferencedOperation = await dereferenceOpenAPIElement(operation, {
219
+ dereference: { strategyOpts: { parseResult } },
220
+ });
221
+ ```
222
+
223
+ ### Options
224
+
225
+ All dereference functions accept an optional options argument compatible with [SpecLynx ApiDOM Reference Options](https://github.com/speclynx/apidom/blob/main/packages/apidom-reference/src/options/index.ts):
226
+
227
+ ```js
228
+ import { dereferenceArazzo } from '@jentic/arazzo-resolver';
229
+
230
+ const parseResult = await dereferenceArazzo('/path/to/arazzo.json', {
231
+ resolve: {
232
+ baseURI: 'https://example.com/', // Base URI for relative references
233
+ },
234
+ parse: {
235
+ parserOpts: {
236
+ sourceMap: true, // Include source maps in parsed documents
237
+ },
238
+ },
239
+ });
240
+ ```
241
+
242
+ #### Default options
243
+
244
+ You can import and inspect the default options:
245
+
246
+ ```js
247
+ import {
248
+ defaultDereferenceArazzoOptions,
249
+ defaultDereferenceOpenAPIOptions,
250
+ } from '@jentic/arazzo-resolver';
251
+
252
+ console.log(defaultDereferenceArazzoOptions);
253
+ // {
254
+ // resolve: {
255
+ // resolvers: [FileResolver, HTTPResolverAxios],
256
+ // },
257
+ // parse: {
258
+ // parsers: [
259
+ // ArazzoJSON1Parser, ArazzoYAML1Parser,
260
+ // OpenApiJSON2Parser, OpenApiYAML2Parser,
261
+ // OpenApiJSON3_0Parser, OpenApiYAML3_0Parser,
262
+ // OpenApiJSON3_1Parser, OpenApiYAML3_1Parser,
263
+ // JSONParser, YAMLParser, BinaryParser
264
+ // ],
265
+ // },
266
+ // dereference: {
267
+ // strategies: [
268
+ // Arazzo1DereferenceStrategy,
269
+ // OpenAPI2DereferenceStrategy, OpenAPI3_0DereferenceStrategy, OpenAPI3_1DereferenceStrategy
270
+ // ],
271
+ // strategyOpts: {
272
+ // sourceDescriptions: false,
273
+ // },
274
+ // },
275
+ // }
276
+
277
+ console.log(defaultDereferenceOpenAPIOptions);
278
+ // {
279
+ // resolve: {
280
+ // resolvers: [FileResolver, HTTPResolverAxios],
281
+ // },
282
+ // parse: {
283
+ // parsers: [
284
+ // OpenApiJSON2Parser, OpenApiYAML2Parser,
285
+ // OpenApiJSON3_0Parser, OpenApiYAML3_0Parser,
286
+ // OpenApiJSON3_1Parser, OpenApiYAML3_1Parser,
287
+ // JSONParser, YAMLParser, BinaryParser
288
+ // ],
289
+ // },
290
+ // dereference: {
291
+ // strategies: [OpenAPI2DereferenceStrategy, OpenAPI3_0DereferenceStrategy, OpenAPI3_1DereferenceStrategy],
292
+ // },
293
+ // }
294
+ ```
295
+
296
+ ### Error handling
297
+
298
+ When dereferencing fails, a `DereferenceError` is thrown. The original error is available via the `cause` property:
299
+
300
+ ```js
301
+ import { dereferenceArazzo, dereferenceOpenAPI, DereferenceError } from '@jentic/arazzo-resolver';
302
+
303
+ try {
304
+ await dereferenceArazzo('/path/to/arazzo.json');
305
+ } catch (error) {
306
+ if (error instanceof DereferenceError) {
307
+ console.error(error.message); // 'Failed to dereference Arazzo Document at "/path/to/arazzo.json"'
308
+ console.error(error.cause); // Original error from underlying resolver
309
+ }
310
+ }
311
+
312
+ try {
313
+ await dereferenceOpenAPI('/path/to/openapi.json');
314
+ } catch (error) {
315
+ if (error instanceof DereferenceError) {
316
+ console.error(error.message); // 'Failed to dereference OpenAPI Document at "/path/to/openapi.json"'
317
+ console.error(error.cause); // Original error from underlying resolver
318
+ }
319
+ }
320
+ ```
321
+
322
+ ### Working with the result
323
+
324
+ Both `dereferenceArazzo` and `dereferenceOpenAPI` functions return a [ParseResultElement](https://github.com/speclynx/apidom/blob/main/packages/apidom-datamodel/README.md#parseresultelement) with all references resolved inline.
325
+
326
+ ```js
327
+ import { dereferenceArazzo } from '@jentic/arazzo-resolver';
328
+
329
+ const parseResult = await dereferenceArazzo('/path/to/arazzo.json');
330
+
331
+ // Access the main Arazzo specification element
332
+ const arazzoSpec = parseResult.api;
333
+
334
+ // Check if parsing produced any errors
335
+ const hasErrors = parseResult.errors.length > 0;
336
+
337
+ // Check if parseResult is empty
338
+ const isEmpty = parseResult.isEmpty;
339
+
340
+ // All references are now resolved inline
341
+ const firstWorkflow = arazzoSpec.workflows.get(0);
342
+ const firstStep = firstWorkflow.steps.get(0);
343
+ ```
344
+
345
+ #### Retrieval URI metadata
346
+
347
+ Both `dereferenceArazzo` and `dereferenceOpenAPI` functions automatically set `retrievalURI` metadata on the parse result:
348
+
349
+ ```js
350
+ import { dereferenceArazzo, dereferenceOpenAPI } from '@jentic/arazzo-resolver';
351
+ import { toValue } from '@speclynx/apidom-core';
352
+
353
+ const arazzoResult = await dereferenceArazzo('https://example.com/arazzo.yaml');
354
+ const arazzoUri = toValue(arazzoResult.meta.get('retrievalURI'));
355
+ // 'https://example.com/arazzo.yaml'
356
+
357
+ const openapiResult = await dereferenceOpenAPI('https://example.com/openapi.yaml');
358
+ const openapiUri = toValue(openapiResult.meta.get('retrievalURI'));
359
+ // 'https://example.com/openapi.yaml'
360
+ ```
361
+
362
+ Note: `dereferenceArazzoElement` and `dereferenceOpenAPIElement` do not set `retrievalURI` - they preserve whatever metadata was on the original element.
363
+
364
+ #### Source descriptions
365
+
366
+ When dereferencing with `sourceDescriptions` enabled, the result contains the entry Arazzo Document at index 0, followed by any dereferenced source descriptions.
367
+ Each source description is a `ParseResultElement` with `'source-description'` class and metadata.
368
+
369
+ ```js
370
+ import { dereferenceArazzo } from '@jentic/arazzo-resolver';
371
+ import { toValue } from '@speclynx/apidom-core';
372
+
373
+ const result = await dereferenceArazzo('/path/to/arazzo.json', {
374
+ dereference: { strategyOpts: { sourceDescriptions: true } },
375
+ });
376
+
377
+ // Access entry Arazzo Document
378
+ const entryArazzo = result.api; // ArazzoSpecification1Element
379
+
380
+ // Iterate over source descriptions (starting at index 1)
381
+ for (let i = 1; i < result.length; i++) {
382
+ const sdParseResult = result.get(i);
383
+
384
+ // Check if it's a source description
385
+ if (sdParseResult.classes.includes('source-description')) {
386
+ const name = toValue(sdParseResult.meta.get('name'));
387
+ const type = toValue(sdParseResult.meta.get('type')); // 'openapi' or 'arazzo'
388
+ const retrievalURI = toValue(sdParseResult.meta.get('retrievalURI'));
389
+
390
+ // Access the dereferenced API element
391
+ const api = sdParseResult.api; // OpenApi3_1Element, SwaggerElement, ArazzoSpecification1Element, etc.
392
+ console.log(`Source "${name}" (${type}) from ${retrievalURI}:`, api?.element);
393
+ }
394
+ }
395
+ ```
396
+
397
+ ##### Accessing via SourceDescriptionElement
398
+
399
+ An alternative way to access dereferenced source descriptions is through the `SourceDescriptionElement` metadata.
400
+ When source descriptions are dereferenced, a `ParseResultElement` is attached to each `SourceDescriptionElement`'s metadata under the key `'parseResult'`.
401
+
402
+ ```js
403
+ import { dereferenceArazzo } from '@jentic/arazzo-resolver';
404
+ import { toValue } from '@speclynx/apidom-core';
405
+
406
+ const result = await dereferenceArazzo('/path/to/arazzo.json', {
407
+ dereference: { strategyOpts: { sourceDescriptions: true } },
408
+ });
409
+
410
+ const arazzoSpec = result.api;
411
+
412
+ // Access dereferenced document via SourceDescriptionElement
413
+ const sourceDesc = arazzoSpec.sourceDescriptions.get(0);
414
+ const sdParseResult = sourceDesc.meta.get('parseResult');
415
+
416
+ // Check for errors before using
417
+ if (sdParseResult.errors.length === 0) {
418
+ // Access the dereferenced API
419
+ const api = sdParseResult.api;
420
+ console.log(`API type: ${api.element}`); // e.g., 'openApi3_1'
421
+
422
+ // Get the retrieval URI
423
+ const retrievalURI = toValue(sdParseResult.meta.get('retrievalURI'));
424
+ console.log(`Loaded from: ${retrievalURI}`);
425
+ }
426
+ ```
427
+
428
+ This approach is useful when you need to:
429
+ - Access a specific source description by its position in the `sourceDescriptions` array
430
+ - Get the `retrievalURI` metadata indicating where the document was fetched from
431
+ - Correlate dereferenced documents with their source description definitions
432
+
433
+ **Note:** When the `ParseResultElement` already contains parsed source descriptions (from parsing with `sourceDescriptions: true`), the dereferencer reuses them instead of re-fetching. This makes the parse-then-dereference workflow efficient.
434
+
435
+ ## SpecLynx ApiDOM tooling
436
+
437
+ Since `@jentic/arazzo-resolver` produces a SpecLynx ApiDOM data model, you have access to the full suite of ApiDOM tools for manipulating, traversing, and transforming the dereferenced document.
438
+
439
+ ### Core utilities
440
+
441
+ The [@speclynx/apidom-core](https://github.com/speclynx/apidom/tree/main/packages/apidom-core) package provides essential utilities for working with ApiDOM elements. Here are just a few examples:
442
+
443
+ ```js
444
+ import { dereferenceArazzo } from '@jentic/arazzo-resolver';
445
+ import { cloneDeep, cloneShallow } from '@speclynx/apidom-datamodel';
446
+ import { toValue, toJSON, toYAML, sexprs } from '@speclynx/apidom-core';
447
+
448
+ const parseResult = await dereferenceArazzo('/path/to/arazzo.json');
449
+ const arazzoSpec = parseResult.api;
450
+
451
+ // Convert to plain JavaScript object
452
+ const obj = toValue(arazzoSpec);
453
+
454
+ // Serialize to JSON string
455
+ const json = toJSON(arazzoSpec);
456
+
457
+ // Serialize to YAML string
458
+ const yaml = toYAML(arazzoSpec);
459
+
460
+ // Clone the element
461
+ const clonedShallow = cloneShallow(arazzoSpec);
462
+ const clonedDeep = cloneDeep(arazzoSpec);
463
+
464
+ // Get S-expression representation (useful for debugging)
465
+ const sexpr = sexprs(arazzoSpec);
466
+ ```
467
+
468
+ ### Traversal
469
+
470
+ The [@speclynx/apidom-traverse](https://github.com/speclynx/apidom/tree/main/packages/apidom-traverse) package provides powerful traversal capabilities. Here is a basic example:
471
+
472
+ ```js
473
+ import { dereferenceArazzo } from '@jentic/arazzo-resolver';
474
+ import { traverse } from '@speclynx/apidom-traverse';
475
+
476
+ const parseResult = await dereferenceArazzo('/path/to/arazzo.json');
477
+
478
+ // Traverse and collect steps using semantic visitor hook
479
+ const steps = [];
480
+ traverse(parseResult.api, {
481
+ StepElement(path) {
482
+ steps.push(path.node);
483
+ if (steps.length >= 10) {
484
+ path.stop(); // Stop traversal after collecting 10 steps
485
+ }
486
+ },
487
+ });
488
+ ```
489
+
490
+ For more information about available utilities, see the [SpecLynx ApiDOM documentation](https://github.com/speclynx/apidom).