Skip to content

Commit

Permalink
feat: migration of the codebase to TypeScript (#56)
Browse files Browse the repository at this point in the history
  • Loading branch information
aeworxet committed Jan 1, 1970
1 parent f42c445 commit 606fa42
Show file tree
Hide file tree
Showing 8 changed files with 294 additions and 1 deletion.
15 changes: 14 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,28 @@
"meow": "^10.1.1"
},
"devDependencies": {
"@jest/types": "^29.0.3",
"@semantic-release/commit-analyzer": "^9.0.2",
"@semantic-release/github": "^8.0.2",
"@semantic-release/npm": "^8.0.3",
"@semantic-release/release-notes-generator": "^10.0.3",
"@types/jest": "^29.0.1",
"@types/js-yaml": "^4.0.5",
"@types/json-schema": "^7.0.11",
"@types/lodash": "^4.14.185",
"@typescript-eslint/eslint-plugin": "^5.37.0",
"@typescript-eslint/parser": "^5.37.0",
"conventional-changelog-conventionalcommits": "^4.6.3",
"eslint": "^8.23.1",
"eslint-plugin-github": "^4.3.7",
"eslint-plugin-security": "^1.5.0",
"eslint-plugin-sonarjs": "^0.15.0",
"jest": "^27.2.5",
"jsdoc": "^3.6.7",
"jsdoc-to-markdown": "^7.1.0",
"markdown-toc": "^1.2.0",
"semantic-release": "^18.0.1"
"semantic-release": "^18.0.1",
"ts-node": "^10.9.1",
"typescript": "^4.8.3"
}
}
59 changes: 59 additions & 0 deletions src/document.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { merge } from 'lodash';
import yaml from 'js-yaml';

//import type { Schema } from '@asyncapi/parser';
import { JSONSchema7 } from 'json-schema';

export interface Document {
_doc: JSONSchema7;
}

/**
* @class
*
* @example
*
* const document = new Document(parsedJSONList, base);
*
* console.log(document.json()); // get json object
* console.log(document.yml()); // get yaml string
* console.log(document.string()); // get json string
*/

export class Document {
/**
*
* @param {Object[]} parsedJSONList
* @param {Object} base
*/
constructor(parsedJSONList: JSONSchema7[], base: JSONSchema7) {
for (const resolvedJSON of parsedJSONList) {
this._doc = merge(this._doc, resolvedJSON);
}

if (typeof base !== "undefined") {
this._doc = merge(this._doc, base);
}
}

/**
* @return {Object}
*/
json() {
return this._doc;
}

/**
* @return {string}
*/
yml() {
return yaml.dump(this._doc);
}

/**
* @return {string}
*/
string(){
return JSON.stringify(this._doc);
}
}
3 changes: 3 additions & 0 deletions src/errors/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { ParserError } from './parsererror';

export { ParserError };
7 changes: 7 additions & 0 deletions src/errors/parsererror.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export class ParserError extends Error {
constructor({ type, title }: { type: string; title: string }) {
super();
this.message = title;
this.name = type;
}
}
40 changes: 40 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { toJS, resolve } from "./util";
import { Document } from "./document";

//import { Schema } from '@asyncapi/parser';
import { JSONSchema7 } from 'json-schema';

/**
*
* @param {string[] | Object[]} files files that are to be bundled
* @param {Object} options
* @param {string | object} options.base base object whose prperties will be retained.
* @param {boolean} options.referenceIntoComponents pass true value to resolve references into component
*
* @return {Document}
*
* @example
*
* const bundle = requrie('@asyncapi/bundler');
* const fs = require('fs');
* const path = requrie('path');
*
* const document = await bundle(fs.readFileSync(
* path.resolve('./asyncapi.yaml', 'utf-8')
* ));
*
* console.log(document.yml());
*/
export const bundle = async (files: JSONSchema7[], options: any = {}) => {
if (typeof options.base !== "undefined") {
options.base = toJS(options.base);
}

const parsedJsons = files.map(file => toJS(file)) as JSONSchema7[];
/**
* Bundle all external references for each files.
*/
const resolvedJsons = await resolve(parsedJsons, {referenceIntoComponents: options.referenceIntoComponents});

return new Document(resolvedJsons as JSONSchema7[], options.base);
};
76 changes: 76 additions & 0 deletions src/parser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import $RefParser from '@apidevtools/json-schema-ref-parser';
import { JSONPath } from 'jsonpath-plus';
import { merge } from 'lodash';

//import { Schema } from '@asyncapi/parser';
import { JSONSchema7 } from 'json-schema';

class ExternalComponents {
ref;
resolvedJSON;
constructor(ref: string, resolvedJSON: string) {
this.ref = ref;
this.resolvedJSON = resolvedJSON;
}

getKey() {
const keys = this.ref.split('/');
return keys[keys.length - 1]
}

getValue() {
return this.resolvedJSON;
}
}

/**
* resolves external references and updates $refs
* @param {Object[]} JSONSchema
*/
export async function parse(JSONSchema: JSONSchema7) {
const $ref = await $RefParser.resolve(JSONSchema);
const refs = crawlChanelPropertiesForRefs(JSONSchema);
for (let ref of refs) {
if (isExternalReference(ref)) {
const componentObject = await resolveExternalRefs(JSONSchema, $ref);
if (JSONSchema.components) {
merge(JSONSchema.components, componentObject);
} else {
JSONSchema.components = componentObject
}
}
}
}

function crawlChanelPropertiesForRefs(JSONSchema: JSONSchema7) {
return JSONPath({ json: JSONSchema, path: `$.channels.*.*.message['$ref']` });
}

/**
* This function checks for external reference.
* @param {string} ref
* @returns {boolean}
*/
function isExternalReference(ref: string) {
return !ref.startsWith('#')
}

/**
*
* @param {Object[]} parsedJSON
* @param {$RefParser} $refs
* @returns {ExternalComponents}
*/
async function resolveExternalRefs(parsedJSON: JSONSchema7, $refs: JSONSchema7) {
const componentObj = { messages: {} };
JSONPath({ json: parsedJSON, resultType: 'all', path: '$.channels.*.*.message' }).forEach(({ parent, parentProperty }) => {
const ref = parent[parentProperty]['$ref'];
if (isExternalReference(ref)) {
const value = $refs.get(ref);
const component = new ExternalComponents(ref, value);
componentObj.messages[String(component.getKey())] = component.getValue()
parent[parentProperty]['$ref'] = `#/components/messages/${component.getKey()}`;
}
})
return componentObj
}
70 changes: 70 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { ParserError } from "./errors";
import $RefParser from '@apidevtools/json-schema-ref-parser';
import { parse } from './parser';
import yaml from "js-yaml";
import { cloneDeep } from 'lodash';

//import { Schema } from '@asyncapi/parser';
import { JSONSchema7 } from 'json-schema';

export const toJS = (asyncapiYAMLorJSON: JSONSchema7) => {

if (!asyncapiYAMLorJSON) {
throw new ParserError({
type: "null-or-falsy-document",
title: "Document can't be null or falsey.",
});
}

if (
asyncapiYAMLorJSON.constructor &&
asyncapiYAMLorJSON.constructor.name === "Object"
) {
return asyncapiYAMLorJSON
}

if (typeof asyncapiYAMLorJSON !== "string") {
throw new ParserError({
type: "invalid-document-type",
title: "The AsyncAPI document has to be either a string or a JS object.",
});
}

if (JSON.stringify(asyncapiYAMLorJSON).trimStart().startsWith("{")) {
throw new ParserError({
type: 'invalid-yaml',
title: 'The provided yaml is not valid.'
})
}
return yaml.load(asyncapiYAMLorJSON);
};

export const validate = async (
parsedJSONs: JSONSchema7[],
parser: { parse(asyncapi: string | any): Promise<any> }
) => {
for (const parsedJSON of parsedJSONs) {
await parser.parse(cloneDeep(parsedJSON));
}
};

/**
*
* @param {Object} asyncapiDocuments
* @param {Object} options
* @param {boolean} options.referenceIntoComponents
* @returns {Array<Object>}
*/
export const resolve = async (asyncapiDocuments: JSONSchema7[], options: any) => {
let docs = [];

for (const asyncapiDocument of asyncapiDocuments) {
if (options.referenceIntoComponents) {
await parse(asyncapiDocument as JSONSchema7);
}
const bundledAsyncAPIDocument = await $RefParser.bundle(asyncapiDocument as JSONSchema7);
docs.push(bundledAsyncAPIDocument);
}

return docs;
}
25 changes: 25 additions & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"compilerOptions": {
"outDir": "./lib",
"baseUrl": "./src",
"target": "es6",
"lib": [
"esnext"
],
"declaration": true,
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
},
"include": [
"src"
]
}

0 comments on commit 606fa42

Please sign in to comment.