An official library that lets you bundle/dereference or merge into one your AsyncAPI Documents.
AsyncAPI Bundler can help you if:
your specification file is divided into different smaller files and is using JSON `$ref` property to reference components
# asyncapi.yaml
asyncapi: '2.4.0'
info:
title: Account Service
version: 1.0.0
description: This service is in charge of processing user signups
channels:
user/signup:
subscribe:
message:
$ref: './messages.yaml#/messages/UserSignedUp'
# messages.yaml
messages:
UserSignedUp:
payload:
type: object
properties:
displayName:
type: string
description: Name of the user
email:
type: string
format: email
description: Email of the user
# After combining
asyncapi: 2.4.0
info:
title: Account Service
version: 1.0.0
description: This service is in charge of processing user signups
channels:
user/signedup:
subscribe:
message:
payload:
type: object
properties:
displayName:
type: string
description: Name of the user
email:
type: string
format: email
description: Email of the user
you have different standalone specification files that define a larger system, see examples here
# signup.yaml
asyncapi: '2.4.0'
info:
title: Account Service
version: 1.0.0
description: This service is in charge of processing user Signup
channels:
user/signedup:
subscribe:
message:
payload:
type: object
properties:
displayName:
type: string
email:
type: string
format: email
# login.yaml
asyncapi: '2.4.0'
info:
title: Account Service
version: 1.0.0
description: This service is in charge of processing user signup
channels:
user/loggenin:
subscribe:
message:
payload:
type: object
properties:
displayName:
type: string
# After combining
# asyncapi.yaml
asyncapi: '2.4.0'
info:
title: Account Service
version: 1.0.0
description: This service is in charge for processing user authentication
channles:
user/signedup:
subscribe:
message:
payload:
type: object
properties:
displayName:
type: string
email:
type: string
format: email
user/loggedin:
subscribe:
message:
payload:
type: object
properties:
displayName:
type: string
npm install @asyncapi/bundler
AsyncAPI Bundler can be easily used within your JavaScript projects as a Node.js module:
'use strict';
const { writeFileSync } = require('fs');
const bundle = require('@asyncapi/bundler');
async function main() {
const document = await bundle(['social-media/comments-service/main.yaml'], {
baseDir: 'example-data',
xOrigin: true,
});
if (document.yml()) {
console.log(document.yml()); // the complete bundled AsyncAPI document
writeFileSync('asyncapi.yaml', document.yml()); // the complete bundled AsyncAPI document
}
}
main().catch(e => console.error(e));
Bundler
dereferences the provided AsyncAPI Document to the maximum possible extent, leaving intact only those internal references that MUST be Reference Object
s according to the AsyncAPI Specification (thus, should never be dereferenced):
- AsyncAPI Specification v2.6.0
There are no internal references that MUST be Reference Object
s.
- AsyncAPI Specification v3.0.0
Regexes of internal references that MUST be Reference Object
s:
/#\/channels\/.*\/servers/
/#\/operations\/.*\/channel/
/#\/operations\/.*\/messages/
/#\/operations\/.*\/reply\/channel/
/#\/operations\/.*\/reply\/messages/
/#\/components\/channels\/.*\/servers/
/#\/components\/operations\/.*\/channel/
/#\/components\/operations\/.*\/messages/
/#\/components\/operations\/.*\/reply\/channel/
/#\/components\/operations\/.*\/reply\/messages/
Option baseDir
represents the main working directory of the program, "root directory," relatively to which will be resolved all paths of AsyncAPI Documents passed to the Bundler
.
Starting from Bundler
v0.5.0, option baseDir
is reimplemented with changed logic, and Bundler
accepts only paths of AsyncAPI Documents, which will be read with readFileSync()
internally.
In a nutshell, the process looks like this:
-
Paths of AsyncAPI Documents are passed as
'main.yaml'
|'./main.yaml'
|'../main.yaml'
|['./main.yaml']
|['main.yaml', 'audio.yaml']
, etc. -
Path/paths are assured to have an
Array
type withArray.from()
to make them iterable. -
Working directory of the program is changed to the
baseDir
withprocess.chdir()
. -
And only then are the paths of the AsyncAPI Documents starting to be read from the array the are currently in, one by one, resolving paths and
$ref
s relatively to thebaseDir
.
Take a look at ./example/bundle-cjs.cjs
, which demonstrates working with baseDir
and $ref
s of different levels of nesting.
Property x-origin
is used for origin tracing in Bundler
and component naming in Optimizer
.
It originated from this comment in a year-long discussion:
The $ref usually also carries a semantical meaning to understand easier what it is (example "$ref : financial-system.yaml#/components/schemas/bankAccountIdentifier"). If the bundling just resolves this ref inline, the semantical meaning of the $ref pointer gets lost and cannot be recovered in later steps. The optimizer would need to invent an artificial component name for the "bankAccountIdentifier" when moving it to the components section.
Thus, property x-origin
contains historical values of dereferenced $ref
s, which are also used by Optimizer
to give meaningful names to components it moves through the AsyncAPI Document.
However, if a user doesn't need / doesn't want x-origin
properties to be present in the structure of the AsyncAPI Document (values of the x-origin
property may leak internal details about how the system described by the AsyncAPI Document is structured,) they can pass { xOrigin: false }
(or omit passing xOrigin
at all) to the Bundler
in the options object.
The movement of all AsyncAPI Specification-valid components to the components
section of the AsyncAPI Document starting from Bundler
v0.5.0 is done by the Optimizer
v1.0.0+.
To get in CI/code an AsyncAPI Document, that is dereferenced to its maximum possible extent with all of its components moved to the components
section, the original AsyncAPI Document must be run through chain Bundler -> Optimizer
.
If Optimizer
is not able to find x-origin
properties during optimization of the provided AsyncAPI Document, the existing names of components are used as a fallback mechanism, but keep in mind that components' names may lack semantic meaning in this case.
TypeScript
import { writeFileSync } from 'fs';
import bundle from '@asyncapi/bundler';
async function main() {
const document = await bundle(['social-media/comments-service/main.yaml'], {
baseDir: 'example-data',
xOrigin: true,
});
if (document.yml()) {
writeFileSync('asyncapi.yaml', document.yml());
}
main().catch(e => console.error(e));
JavaScript CJS module system
'use strict';
const { writeFileSync } = require('fs');
const bundle = require('@asyncapi/bundler');
async function main() {
const document = await bundle(['social-media/comments-service/main.yaml'], {
baseDir: 'example-data',
xOrigin: true,
});
if (document.yml()) {
writeFileSync('asyncapi.yaml', document.yml());
}
main().catch(e => console.error(e));
JavaScript ESM module system
'use strict';
import { writeFileSync } from 'fs';
import bundle from '@asyncapi/bundler';
async function main() {
const document = await bundle(['social-media/comments-service/main.yaml'], {
baseDir: 'example-data',
xOrigin: true,
});
if (document.yml()) {
writeFileSync('asyncapi.yaml', document.yml());
}
main().catch(e => console.error(e));
Kind: global function
Param | Type | Description |
---|---|---|
files | string | Array.<string> |
One or more relative/absolute paths to AsyncAPI Documents that should be bundled. |
[options] | Object |
|
[options.base] | string |
One relative/absolute path to base object whose properties will be retained. |
[options.baseDir] | string |
One relative/absolute path to directory relative to which paths to AsyncAPI Documents that should be bundled will be resolved. |
[options.xOrigin] | boolean |
Pass |
Thanks goes to these wonderful people (emoji key):
This project follows the all-contributors specification. Contributions of any kind welcome!