diff --git a/package-lock.json b/package-lock.json index 99a1e23..ed620d4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@themost/client", - "version": "2.14.2", + "version": "2.14.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index cd61868..0554a37 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@themost/client", - "version": "2.14.2", + "version": "2.14.3", "description": "MOST Web Framework Codename Blueshift - Client Common", "module": "dist/index.esm.js", "main": "dist/index.js", diff --git a/util/bin/cli.js b/util/bin/cli.js index 2ca57b6..67f2c74 100644 --- a/util/bin/cli.js +++ b/util/bin/cli.js @@ -13,6 +13,7 @@ async function main() { console.log('Usage: client-cli [options]'); console.log('Options:'); console.log(' --out-file The output file to write the rendered types to'); + console.log(' --classes Render classes instead of interfaces'); return; } const source = args._[0]; @@ -21,7 +22,10 @@ async function main() { return process.exit(-1); } const isURL = source.startsWith('http://') || source.startsWith('https://'); - const typeRenderer = isURL ? new TypeRenderer(source) : new FileSchemaRenderer(source); + const options = { + classes: args.classes + }; + const typeRenderer = isURL ? new TypeRenderer(source, options) : new FileSchemaRenderer(source, options); const result = await typeRenderer.renderAny(); if (args.outFile) { writeFileSync(args.outFile, result); diff --git a/util/spec/TypeRenderer.spec.ts b/util/spec/TypeRenderer.spec.ts index 3b983ef..c745d93 100644 --- a/util/spec/TypeRenderer.spec.ts +++ b/util/spec/TypeRenderer.spec.ts @@ -22,6 +22,12 @@ describe("TypeRenderer", () => { expect(typeDeclarations).toBeInstanceOf(String); }); - + it("should render any type as class", async () => { + const renderer = new TypeRenderer('http://localhost:8080/api/', { + classes: true + }); + const typeDeclarations = await renderer.renderAny(); + expect(typeDeclarations).toBeInstanceOf(String); + }); }); diff --git a/util/src/TypeRenderer.ts b/util/src/TypeRenderer.ts index b40ce5f..c4ed276 100644 --- a/util/src/TypeRenderer.ts +++ b/util/src/TypeRenderer.ts @@ -41,15 +41,20 @@ const EdmTypeMap = new Map([ ] ]); +const Space = ' '; +const OpeningBracket = '{'; +const ClosingBracket = '}'; +const NewLine = '\n'; +const Tab = '\t'; + class TypeRenderer { protected context: BasicDataContext; protected schema: EdmSchema; - /** - * @param {string} host - */ - constructor(host?: string) { + constructor(host?: string, protected options?: { + classes: boolean + }) { this.context = new BasicDataContext(host); } @@ -87,7 +92,6 @@ class TypeRenderer { * @returns string */ protected renderType(entityType: EdmEntityType) { - const extendsInterface = entityType.BaseType ? ` extends ${entityType.BaseType} ` : ''; const properties = entityType.Property.map((property) => { const { Name } = property; const Declaration = this.renderProperty(property); @@ -104,15 +108,32 @@ class TypeRenderer { Declaration } })); - const result = ` -export interface ${entityType.Name} ${extendsInterface}{ -${properties.sort( - (a, b) => { - if (a.Name < b.Name) return -1; - if (a.Name > b.Name) return 1; - return 0; - }).map((property) => `\t${property.Declaration}`).join('\n')} -}` + let result = ''; + if (this.options && this.options.classes) { + // find entity set and define annotation + const entitySet = this.schema.EntityContainer.EntitySet.find((s) => s.EntityType === entityType.Name); + if (entitySet) { + result += `@EdmSchema.entitySet('${entitySet.Name}')`; + result += NewLine; + } + } + result += 'export'; + result += Space; + result += this.options && this.options.classes ? 'class' : 'interface'; + result += Space; + result += entityType.Name; + result += Space; + result += entityType.BaseType ? `extends ${entityType.BaseType}` : ''; + result += Space; + result += OpeningBracket; + result += NewLine; + result += properties.sort((a, b) => { + if (a.Name < b.Name) return -1; + if (a.Name > b.Name) return 1; + return 0; + }).map((property) => Tab + `${property.Declaration}`).join(NewLine); + result += NewLine; + result += ClosingBracket; return result.replace(/(\n+)/g, '\n'); } @@ -132,20 +153,63 @@ ${properties.sort( if (this.schema == null) { this.schema = await this.getSchema(); } - const typeDeclarations = this.schema.EntityType.sort( - (a, b) => { - if (a.Name < b.Name) return -1; - if (a.Name > b.Name) return 1; - return 0; + + const sort = (a: string, b: string) => { + if (a < b) return -1; + if (a > b) return 1; + return 0; + } + + // sort entity types + const withoutBaseType = this.schema.EntityType + .filter((t) => t.BaseType == null) + .map((t) => t.Name) + .sort((a, b) => sort(a, b)); + + const names = []; + names.push(...withoutBaseType); + const withBaseType = this.schema.EntityType + .filter((t) => t.BaseType != null) + .map((t) => t.Name) + .sort((a, b) => sort(a, b)); + // sort entity types with base type and find base type path e.g. Action/RequestAction/StudentRequestAction + const withTypes = withBaseType.map((t) => { + let entityType = this.schema.EntityType.find((e) => e.Name === t) + let baseType = entityType.BaseType; + let types = [ + t + ]; + while (baseType) { + types.push(baseType); + entityType = this.schema.EntityType.find((e) => e.Name === baseType); + baseType = entityType ? entityType.BaseType : null; + } + return { + Name: t, + Types: types.reverse().join('/') } + }).sort((a, b) => sort(a.Types, b.Types)).map((t) => t.Name); + names.push(...withTypes); + const typeDeclarations = names.map( + (name: string) => this.schema.EntityType.find((t) => t.Name === name) ).map((entityType) => this.renderType(entityType)); - return typeDeclarations.join('\n'); + let result = ''; + if (this.options && this.options.classes) { + result += 'import { EdmSchema } from \'@themost/client\';'; + result += NewLine; + result += NewLine; + } + result += typeDeclarations.join(NewLine + NewLine); + return result; } } class FileSchemaRenderer extends TypeRenderer { - constructor(private file: string) { + constructor(private file: string, options?: { + classes: boolean + }) { super(); + this.options = options; } protected getSchema(): Promise {