diff --git a/API.md b/API.md index 6e8ba90..f77ebe7 100644 --- a/API.md +++ b/API.md @@ -5,13 +5,6 @@
-## Members - -boolean
Object
* [.yml()](#Document+yml) ⇒ string
* [.string()](#Document+string) ⇒ string
-### new Document(parsedJSONList, base)
+### new Document(AsyncAPIObject)
| Param | Type |
| --- | --- |
-| parsedJSONList | Array.<Object>
|
-| base | Object
|
+| AsyncAPIObject | Object
|
**Example**
```js
-const document = new Document(parsedJSONList, base);
+const document = new Document(bundledDocument);
console.log(document.json()); // get JSON object
console.log(document.yml()); // get YAML string
@@ -59,15 +51,6 @@ console.log(document.string()); // get JSON string
### document.string() ⇒ string
**Kind**: instance method of [Document
](#Document)
-
-
-## resolve ⇒ boolean
-**Kind**: global variable
-
-| Param | Type |
-| --- | --- |
-| asyncapiDocument | AsyncAPIObject
|
-
## bundle(files, [options]) ⇒ [Document
](#Document)
diff --git a/example/bundle-esm.js b/example/bundle-esm.js
index df285ea..dd92f79 100644
--- a/example/bundle-esm.js
+++ b/example/bundle-esm.js
@@ -11,11 +11,19 @@ import { writeFileSync } from 'fs';
import bundle from '@asyncapi/bundler';
async function main() {
- const document = await bundle(['./main.yaml'], {
- xOrigin: true,
+ const files = [
+ 'send/lightTurnOn/asyncapi.yaml',
+ 'send/lightTurnOff/asyncapi.yaml',
+ 'receive/lightingMeasured/asyncapi.yaml',
+ ];
+
+ const document = await bundle(files, {
+ base: 'index.yaml',
+ baseDir: 'example-with-nested-dirs/asyncapi',
+ xOrigin: false,
});
if (document.yml()) {
- writeFileSync('asyncapi.yaml', document.yml());
+ writeFileSync('bundled.yaml', document.yml());
}
}
diff --git a/example/example-with-nested-dirs/asyncapi/index.yaml b/example/example-with-nested-dirs/asyncapi/index.yaml
new file mode 100644
index 0000000..728830c
--- /dev/null
+++ b/example/example-with-nested-dirs/asyncapi/index.yaml
@@ -0,0 +1,21 @@
+asyncapi: 3.0.0
+info:
+ title: Streetlights MQTT API
+ version: 1.0.0
+ description: "The Smartylighting Streetlights API allows you to remotely manage the city lights.\n\n### Check out its awesome features:\n\n* Turn a specific streetlight on/off \U0001F303\n* Dim a specific streetlight \U0001F60E\n* Receive real-time information about environmental lighting conditions \U0001F4C8\n"
+ license:
+ name: Apache 2.0
+ url: 'https://www.apache.org/licenses/LICENSE-2.0'
+defaultContentType: application/json
+servers:
+ production:
+ host: 'test.mosquitto.org:{port}'
+ protocol: mqtt
+ description: Test broker
+ variables:
+ port:
+ description: Secure connection (TLS) is available through port 8883.
+ default: '1883'
+ enum:
+ - '1883'
+ - '8883'
diff --git a/example/example-with-nested-dirs/asyncapi/receive/lightingMeasured/README.md b/example/example-with-nested-dirs/asyncapi/receive/lightingMeasured/README.md
new file mode 100644
index 0000000..56bb556
--- /dev/null
+++ b/example/example-with-nested-dirs/asyncapi/receive/lightingMeasured/README.md
@@ -0,0 +1,46 @@
+# Lighting Measured 1.0.0 documentation
+
+
+## Operations
+
+### RECEIVE `smartylighting/streetlights/1/0/event/{streetlightId}/lighting/measured` Operation
+
+*Inform about environmental lighting conditions of a particular streetlight.*
+
+* Operation ID: `receiveLightMeasurement`
+
+The topic on which measured values may be produced and consumed.
+
+#### Parameters
+
+| Name | Type | Description | Value | Constraints | Notes |
+|---|---|---|---|---|---|
+| streetlightId | string | The ID of the streetlight. | - | - | **required** |
+
+
+#### Message Light measured `lightMeasured`
+
+*Inform about environmental lighting conditions of a particular streetlight.*
+
+* Message ID: `lightMeasured`
+* Content type: [application/json](https://www.iana.org/assignments/media-types/application/json)
+
+##### Payload
+
+| Name | Type | Description | Value | Constraints | Notes |
+|---|---|---|---|---|---|
+| (root) | object | - | - | - | **additional properties are allowed** |
+| lumens | integer | Light intensity measured in lumens. | - | >= 0 | - |
+| sentAt | string | Date and time when the message was sent. | - | format (`date-time`) | - |
+
+> Examples of payload _(generated)_
+
+```json
+{
+ "lumens": 0,
+ "sentAt": "2019-08-24T14:15:22Z"
+}
+```
+
+
+
diff --git a/example/example-with-nested-dirs/asyncapi/receive/lightingMeasured/asyncapi.yaml b/example/example-with-nested-dirs/asyncapi/receive/lightingMeasured/asyncapi.yaml
new file mode 100644
index 0000000..b465ad6
--- /dev/null
+++ b/example/example-with-nested-dirs/asyncapi/receive/lightingMeasured/asyncapi.yaml
@@ -0,0 +1,35 @@
+asyncapi: 3.0.0
+info:
+ title: Lighting Measured
+ version: 1.0.0
+channels:
+ lightingMeasured:
+ address: 'smartylighting/streetlights/1/0/event/{streetlightId}/lighting/measured'
+ messages:
+ lightMeasured:
+ $ref: '#/components/messages/lightMeasured'
+ description: The topic on which measured values may be produced and consumed.
+ parameters:
+ streetlightId:
+ description: The ID of the streetlight.
+operations:
+ receiveLightMeasurement:
+ action: receive
+ channel:
+ $ref: '#/channels/lightingMeasured'
+ summary: >-
+ Inform about environmental lighting conditions of a particular
+ streetlight.
+ messages:
+ - $ref: '#/channels/lightingMeasured/messages/lightMeasured'
+components:
+ messages:
+ lightMeasured:
+ name: lightMeasured
+ title: Light measured
+ summary: >-
+ Inform about environmental lighting conditions of a particular
+ streetlight.
+ contentType: application/json
+ payload:
+ $ref: ./schema.json
diff --git a/example/example-with-nested-dirs/asyncapi/receive/lightingMeasured/schema.json b/example/example-with-nested-dirs/asyncapi/receive/lightingMeasured/schema.json
new file mode 100644
index 0000000..6207762
--- /dev/null
+++ b/example/example-with-nested-dirs/asyncapi/receive/lightingMeasured/schema.json
@@ -0,0 +1,16 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "lumens": {
+ "type": "integer",
+ "minimum": 0,
+ "description": "Light intensity measured in lumens."
+ },
+ "sentAt": {
+ "type": "string",
+ "format": "date-time",
+ "description": "Date and time when the message was sent."
+ }
+ }
+}
diff --git a/example/example-with-nested-dirs/asyncapi/send/lightTurnOff/README.md b/example/example-with-nested-dirs/asyncapi/send/lightTurnOff/README.md
new file mode 100644
index 0000000..4896f61
--- /dev/null
+++ b/example/example-with-nested-dirs/asyncapi/send/lightTurnOff/README.md
@@ -0,0 +1,32 @@
+# Light Turn Off 1.0.0 documentation
+
+
+## Operations
+
+### SEND `smartylighting/streetlights/1/0/action/{streetlightId}/turn/off` Operation
+
+* Operation ID: `turnOff`
+
+#### Message Turn on/off `turnOnOff`
+
+*Command a particular streetlight to turn the lights on or off.*
+
+##### Payload
+
+| Name | Type | Description | Value | Constraints | Notes |
+|---|---|---|---|---|---|
+| (root) | object | - | - | - | **additional properties are allowed** |
+| command | string | Whether to turn on or off the light. | allowed (`"on"`, `"off"`) | - | - |
+| sentAt | string | Date and time when the message was sent. | - | format (`date-time`) | - |
+
+> Examples of payload _(generated)_
+
+```json
+{
+ "command": "on",
+ "sentAt": "2019-08-24T14:15:22Z"
+}
+```
+
+
+
diff --git a/example/example-with-nested-dirs/asyncapi/send/lightTurnOff/asyncapi.yaml b/example/example-with-nested-dirs/asyncapi/send/lightTurnOff/asyncapi.yaml
new file mode 100644
index 0000000..5c8566f
--- /dev/null
+++ b/example/example-with-nested-dirs/asyncapi/send/lightTurnOff/asyncapi.yaml
@@ -0,0 +1,25 @@
+asyncapi: 3.0.0
+info:
+ title: Light Turn Off
+ version: 1.0.0
+channels:
+ lightTurnOff:
+ address: 'smartylighting/streetlights/1/0/action/{streetlightId}/turn/off'
+ messages:
+ turnOff:
+ $ref: '#/components/messages/turnOnOff'
+operations:
+ turnOff:
+ action: send
+ channel:
+ $ref: '#/channels/lightTurnOff'
+ messages:
+ - $ref: '#/channels/lightTurnOff/messages/turnOff'
+components:
+ messages:
+ turnOnOff:
+ name: turnOnOff
+ title: Turn on/off
+ summary: Command a particular streetlight to turn the lights on or off.
+ payload:
+ $ref: ./schema.json
diff --git a/example/example-with-nested-dirs/asyncapi/send/lightTurnOff/schema.json b/example/example-with-nested-dirs/asyncapi/send/lightTurnOff/schema.json
new file mode 100644
index 0000000..65804f3
--- /dev/null
+++ b/example/example-with-nested-dirs/asyncapi/send/lightTurnOff/schema.json
@@ -0,0 +1,20 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "command": {
+ "type": "string",
+ "enum": [
+ "on",
+ "off"
+ ],
+ "description": "Whether to turn on or off the light."
+ },
+ "sentAt": {
+ "type": "string",
+ "format": "date-time",
+ "description": "Date and time when the message was sent."
+ }
+ }
+ }
+
\ No newline at end of file
diff --git a/example/example-with-nested-dirs/asyncapi/send/lightTurnOn/README.md b/example/example-with-nested-dirs/asyncapi/send/lightTurnOn/README.md
new file mode 100644
index 0000000..5b0c2a1
--- /dev/null
+++ b/example/example-with-nested-dirs/asyncapi/send/lightTurnOn/README.md
@@ -0,0 +1,32 @@
+# Light Turn On 1.0.0 documentation
+
+
+## Operations
+
+### SEND `smartylighting/streetlights/1/0/action/{streetlightId}/turn/on` Operation
+
+* Operation ID: `turnOn`
+
+#### Message Turn on/off `turnOnOff`
+
+*Command a particular streetlight to turn the lights on or off.*
+
+##### Payload
+
+| Name | Type | Description | Value | Constraints | Notes |
+|---|---|---|---|---|---|
+| (root) | object | - | - | - | **additional properties are allowed** |
+| command | string | Whether to turn on or off the light. | allowed (`"on"`, `"off"`) | - | - |
+| sentAt | string | Date and time when the message was sent. | - | format (`date-time`) | - |
+
+> Examples of payload _(generated)_
+
+```json
+{
+ "command": "on",
+ "sentAt": "2019-08-24T14:15:22Z"
+}
+```
+
+
+
diff --git a/example/example-with-nested-dirs/asyncapi/send/lightTurnOn/asyncapi.yaml b/example/example-with-nested-dirs/asyncapi/send/lightTurnOn/asyncapi.yaml
new file mode 100644
index 0000000..8d0061c
--- /dev/null
+++ b/example/example-with-nested-dirs/asyncapi/send/lightTurnOn/asyncapi.yaml
@@ -0,0 +1,25 @@
+asyncapi: 3.0.0
+info:
+ title: Light Turn On
+ version: 1.0.0
+channels:
+ lightTurnOn:
+ address: 'smartylighting/streetlights/1/0/action/{streetlightId}/turn/on'
+ messages:
+ turnOn:
+ $ref: '#/components/messages/turnOnOff'
+operations:
+ turnOn:
+ action: send
+ channel:
+ $ref: '#/channels/lightTurnOn'
+ messages:
+ - $ref: '#/channels/lightTurnOn/messages/turnOn'
+components:
+ messages:
+ turnOnOff:
+ name: turnOnOff
+ title: Turn on/off
+ summary: Command a particular streetlight to turn the lights on or off.
+ payload:
+ $ref: ./schema.json
diff --git a/example/example-with-nested-dirs/asyncapi/send/lightTurnOn/schema.json b/example/example-with-nested-dirs/asyncapi/send/lightTurnOn/schema.json
new file mode 100644
index 0000000..8128dfc
--- /dev/null
+++ b/example/example-with-nested-dirs/asyncapi/send/lightTurnOn/schema.json
@@ -0,0 +1,19 @@
+{
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
+ "type": "object",
+ "properties": {
+ "command": {
+ "type": "string",
+ "enum": [
+ "on",
+ "off"
+ ],
+ "description": "Whether to turn on or off the light."
+ },
+ "sentAt": {
+ "type": "string",
+ "format": "date-time",
+ "description": "Date and time when the message was sent."
+ }
+ }
+}
diff --git a/example/package.json b/example/package.json
index 921d537..cf94b16 100644
--- a/example/package.json
+++ b/example/package.json
@@ -3,6 +3,7 @@
"version": "1.0.0",
"description": "",
"main": "index.js",
+ "type": "module",
"scripts": {
"start": "npm run start:cjs && npm run start:esm && npm run start:ts",
"start:cjs": "node bundle-cjs.cjs",
diff --git a/src/document.ts b/src/document.ts
index 5ecb5d2..2d48f6c 100644
--- a/src/document.ts
+++ b/src/document.ts
@@ -1,4 +1,3 @@
-import { merge } from 'lodash';
import yaml from 'js-yaml';
import type { AsyncAPIObject } from './spec-types';
@@ -8,7 +7,7 @@ import type { AsyncAPIObject } from './spec-types';
*
* @example
*
- * const document = new Document(parsedJSONList, base);
+ * const document = new Document(bundledDocument);
*
* console.log(document.json()); // get JSON object
* console.log(document.yml()); // get YAML string
@@ -16,21 +15,13 @@ import type { AsyncAPIObject } from './spec-types';
*/
export class Document {
- private _doc: AsyncAPIObject = {} as any;
+ private _doc: AsyncAPIObject;
/**
- *
- * @param {Object[]} parsedJSONList
- * @param {Object} base
+ * @param {Object} AsyncAPIObject
*/
- constructor(parsedJSONList: AsyncAPIObject[], base: AsyncAPIObject) {
- for (const resolvedJSON of parsedJSONList) {
- this._doc = merge(this._doc, resolvedJSON);
- }
-
- if (typeof base !== 'undefined') {
- this._doc = merge(this._doc, base);
- }
+ constructor(bundledDocument: AsyncAPIObject) {
+ this._doc = bundledDocument;
}
/**
diff --git a/src/index.ts b/src/index.ts
index 7b8e480..81df8ce 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,8 +1,14 @@
-import { readFileSync } from 'fs';
import path from 'path';
-import { toJS, resolve, versionCheck } from './util';
+import { merge } from 'lodash';
+import { Parser } from '@asyncapi/parser';
+import {
+ resolve,
+ versionCheck,
+ orderPropsAccToAsyncAPISpec,
+ mergeIntoBaseFile,
+} from './util';
+
import { Document } from './document';
-import { parse } from './parser';
import type { AsyncAPIObject } from './spec-types';
@@ -85,6 +91,11 @@ export default async function bundle(
files: string[] | string,
options: any = {}
) {
+ let bundledDocument: any = {};
+ let validationResult: any = [];
+
+ const parser = new Parser();
+
// if one string was passed, convert it to an array
if (typeof files === 'string') {
files = Array.from(files.split(' '));
@@ -96,34 +107,58 @@ export default async function bundle(
process.chdir(path.resolve(originDir, String(options.baseDir[0]))); // guard against passing an array
}
- const readFiles = files.map(file => readFileSync(file, 'utf-8')); // eslint-disable-line
-
- const parsedJsons = readFiles.map(file => toJS(file)) as AsyncAPIObject[];
+ const parsedJsons: AsyncAPIObject[] = await resolve(files, options);
const majorVersion = versionCheck(parsedJsons);
- if (typeof options.base !== 'undefined') {
- if (typeof options.base === 'string') {
- options.base = readFileSync(options.base, 'utf-8'); // eslint-disable-line
- } else if (Array.isArray(options.base)) {
- options.base = readFileSync(String(options.base[0]), 'utf-8'); // eslint-disable-line
- }
- options.base = toJS(options.base);
- await parse(options.base, majorVersion, options);
+ for (const parsedJson of parsedJsons) {
+ bundledDocument = merge(bundledDocument, parsedJson);
+ }
+
+ if (options.base) {
+ bundledDocument = await mergeIntoBaseFile(
+ options.base,
+ bundledDocument,
+ majorVersion,
+ options
+ );
}
- const resolvedJsons: AsyncAPIObject[] = await resolve(
- parsedJsons,
- majorVersion,
- options
- );
+ // Purely decorative stuff, just to bring the order of the AsyncAPI Document's
+ // properties into a familiar form.
+ bundledDocument = orderPropsAccToAsyncAPISpec(bundledDocument);
+
+ // Option `noValidation: true` is used by the testing system, which
+ // intentionally feeds Bundler wrong AsyncAPI Documents, thus it is not
+ // documented.
+ if (!options.noValidation) {
+ validationResult = await parser.validate(
+ JSON.parse(JSON.stringify(bundledDocument))
+ );
+ }
+
+ // If Parser's `validate()` function returns a non-empty array with at least
+ // one `severity: 0`, that means there was at least one error during
+ // validation, not a `warning: 1`, `info: 2`, or `hint: 3`. Thus, array's
+ // elements with `severity: 0` are outputted as a list of remarks, and the
+ // program throws.
+ if (
+ validationResult.length !== 0 &&
+ validationResult.map((element: any) => element.severity).includes(0)
+ ) {
+ console.log(
+ 'Validation of the resulting AsyncAPI Document failed.\nList of remarks:\n',
+ validationResult.filter((element: any) => element.severity === 0)
+ );
+ throw new Error();
+ }
// return to the starting directory before finishing the execution
if (options.baseDir) {
process.chdir(originDir);
}
- return new Document(resolvedJsons, options.base);
+ return new Document(bundledDocument as AsyncAPIObject);
}
// 'module.exports' is added to maintain backward compatibility with Node.js
diff --git a/src/parser.ts b/src/parser.ts
index fcfcabe..b139ba9 100644
--- a/src/parser.ts
+++ b/src/parser.ts
@@ -1,11 +1,8 @@
import $RefParser from '@apidevtools/json-schema-ref-parser';
-import { Parser } from '@asyncapi/parser';
import type { ParserOptions as $RefParserOptions } from '@apidevtools/json-schema-ref-parser';
import type { AsyncAPIObject } from 'spec-types';
-const parser = new Parser();
-
let RefParserOptions: $RefParserOptions;
/**
@@ -20,8 +17,6 @@ export async function parse(
specVersion: number,
options: any = {}
) {
- let validationResult: any[] = [];
-
/* eslint-disable indent */
// It is assumed that there will be major Spec versions 4, 5 and on.
switch (specVersion) {
@@ -74,35 +69,5 @@ export async function parse(
);
}
- const dereferencedJSONSchema = await $RefParser.dereference(
- JSONSchema,
- RefParserOptions
- );
-
- // Option `noValidation: true` is used by the testing system, which
- // intentionally feeds Bundler wrong AsyncAPI Documents, thus it is not
- // documented.
- if (!options.noValidation) {
- validationResult = await parser.validate(
- JSON.parse(JSON.stringify(dereferencedJSONSchema))
- );
- }
-
- // If Parser's `validate()` function returns a non-empty array with at least
- // one `severity: 0`, that means there was at least one error during
- // validation, not a `warning: 1`, `info: 2`, or `hint: 3`. Thus, array's
- // elements with `severity: 0` are outputted as a list of remarks, and the
- // program exits without doing anything further.
- if (
- validationResult.length !== 0 &&
- validationResult.map(element => element.severity).includes(0)
- ) {
- console.log(
- 'Validation of the resulting AsyncAPI Document failed.\nList of remarks:\n',
- validationResult.filter(element => element.severity === 0)
- );
- throw new Error();
- }
-
- return dereferencedJSONSchema;
+ return await $RefParser.dereference(JSONSchema, RefParserOptions) as AsyncAPIObject;
}
diff --git a/src/util.ts b/src/util.ts
index 2f136f3..0bc7398 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -1,4 +1,7 @@
+import { readFileSync } from 'fs';
+import path from 'path';
import yaml from 'js-yaml';
+import { merge } from 'lodash';
import { parse } from './parser';
import { ParserError } from './errors';
@@ -19,7 +22,7 @@ export const toJS = (asyncapiYAMLorJSON: string | object) => {
asyncapiYAMLorJSON.constructor &&
asyncapiYAMLorJSON.constructor.name === 'Object'
) {
- return asyncapiYAMLorJSON;
+ return asyncapiYAMLorJSON as AsyncAPIObject;
}
if (typeof asyncapiYAMLorJSON !== 'string') {
@@ -36,50 +39,27 @@ export const toJS = (asyncapiYAMLorJSON: string | object) => {
});
}
- return yaml.load(asyncapiYAMLorJSON);
+ return yaml.load(asyncapiYAMLorJSON) as AsyncAPIObject;
};
-/**
- *
- * @param {Object} asyncapiDocuments
- * @param {Object} options
- * @param {boolean} options.xOrigin
- * @returns {Array