diff --git a/data-application.d.ts b/data-application.d.ts index c75641f..8b73a3a 100644 --- a/data-application.d.ts +++ b/data-application.d.ts @@ -2,8 +2,10 @@ import { IApplication, ConfigurationBase, ApplicationService, ApplicationBase, ApplicationServiceConstructor, SequentialEventEmitter } from "@themost/common"; import {DataContext} from "./types"; +import {SyncSeriesEventEmitter} from '@themost/events'; export declare class DataApplication extends SequentialEventEmitter implements ApplicationBase { + serviceLoaded: SyncSeriesEventEmitter<{ target: DataApplication, serviceType: ApplicationServiceConstructor, service: any }>; constructor(cwd?: string); configuration: ConfigurationBase; useStrategy(serviceCtor: ApplicationServiceConstructor, strategyCtor: ApplicationServiceConstructor): this; @@ -12,4 +14,4 @@ export declare class DataApplication extends SequentialEventEmitter implements A getService(serviceCtor: ApplicationServiceConstructor): T; getConfiguration(): ConfigurationBase; createContext(): DataContext; -} \ No newline at end of file +} diff --git a/data-application.js b/data-application.js index 7be373c..8d0250a 100644 --- a/data-application.js +++ b/data-application.js @@ -2,6 +2,7 @@ var {Args, PathUtils, SequentialEventEmitter} = require('@themost/common'); var {DataConfiguration} = require('./data-configuration'); var {DefaultDataContext} = require('./data-context'); +var { SyncSeriesEventEmitter } = require('@themost/events') /** * @class * @param {string} cwd - A string which defines application root directory @@ -9,6 +10,7 @@ var {DefaultDataContext} = require('./data-context'); class DataApplication extends SequentialEventEmitter { constructor(cwd) { super(); + this.serviceLoaded = new SyncSeriesEventEmitter(); Object.defineProperty(this, '_services', { configurable: true, enumerable: false, @@ -51,6 +53,11 @@ class DataApplication extends SequentialEventEmitter { writable: true, value: new strategyCtor(this) }); + this.serviceLoaded.emit({ + target: this, + serviceType: serviceCtor, + service: this._services[serviceCtor.name] + }); return this; } useService(serviceCtor) { diff --git a/jest.config.js b/jest.config.js index 95dfff8..cd7cb8f 100644 --- a/jest.config.js +++ b/jest.config.js @@ -81,7 +81,9 @@ module.exports = { // ], // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module - // moduleNameMapper: {}, + moduleNameMapper: { + "@themost/data": "/index", + }, // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader // modulePathIgnorePatterns: [], diff --git a/odata.d.ts b/odata.d.ts index 98c7f9f..af1aa42 100644 --- a/odata.d.ts +++ b/odata.d.ts @@ -1,6 +1,7 @@ // MOST Web Framework 2.0 Codename Blueshift BSD-3-Clause license Copyright (c) 2017-2022, THEMOST LP All rights reserved import {DataContext} from "./types"; import {ConfigurationBase} from "@themost/common"; +import {AsyncSeriesEventEmitter} from '@themost/events'; export declare interface SystemQueryOptions { $filter?: string; @@ -73,15 +74,22 @@ export declare interface ProcedureParameter { fromBody?: boolean; } +export declare interface Annotation { + term: string; + value: any; +} + export declare interface EntityTypeProperty { name: string; type: string; nullable?: boolean; + annotations?: Annotation[]; } export declare interface EntityTypeNavigationProperty { name: string; type: string; + annotations?: Annotation[]; } export declare interface EntityContainerConfiguration { @@ -131,6 +139,7 @@ export declare class EntityTypeConfiguration { navigationProperty: Array; actions: Array; functions: Array; + annotations?: Annotation[]; collection: any; ignore(name: string): EntityTypeConfiguration; derivesFrom(name: string): EntityTypeConfiguration; @@ -182,6 +191,7 @@ export declare interface ModelBuilderJsonFormatterOptions { } export declare class ODataModelBuilder { + loaded: AsyncSeriesEventEmitter<{target: ODataModelBuilder}>; constructor(configuration: ConfigurationBase); serviceRoot: string; defaultNamespace: string; @@ -214,3 +224,26 @@ export declare class ODataConventionModelBuilder extends ODataModelBuilder{ } export declare function defineDecorator(proto: Object|Function, key: string, decorator:Function): void; + +export declare interface ComplexTypeProperty extends EntityTypeProperty { +} + +export declare interface ComplexTypeNavigationProperty extends EntityTypeNavigationProperty { +} + +export declare class ComplexTypeConfiguration { + constructor(builder: any, name: string); + getBuilder(): any; + name: string; + openType: boolean; + abstract: boolean; + baseType: string; + property: Array; + navigationProperty: Array; + annotations?: Annotation[]; + addProperty (name: string, type: string, nullable?: boolean): ComplexTypeConfiguration; + removeProperty(name: string): ComplexTypeConfiguration; + addNavigationProperty(name: string, type: string, multiplicity?: 'Many' | 'ZeroOrOne' | 'Unknown' | 'One'): ComplexTypeConfiguration; + removeNavigationProperty(name: string): ComplexTypeConfiguration; + +} diff --git a/odata.js b/odata.js index 700ea26..ebcee9c 100644 --- a/odata.js +++ b/odata.js @@ -27,6 +27,7 @@ var {DefaultSchemaLoaderStrategy} = require('./data-configuration'); var {instanceOf} = require('./instance-of'); var {Args} = require('@themost/common'); var {hasOwnProperty} = require('./has-own-property'); +var { AsyncSeriesEventEmitter } = require('@themost/events'); /** * @enum */ @@ -342,7 +343,7 @@ EntityTypeConfiguration.prototype.getBuilder = function() { * @returns EntityTypeConfiguration */ EntityTypeConfiguration.prototype.derivesFrom = function(name) { - Args.notString(name,'Enity type name'); + Args.notString(name,'Entity type name'); this.baseType = name; return this; }; @@ -1211,7 +1212,7 @@ function schemaToEdmDocument(schema) { /** * @classdesc Represents the OData model builder of an HTTP application * @property {string} serviceRoot - Gets or sets the service root URI - * @param {ConfigurationBase} configuration + * @param {import('@themost/common').ConfigurationBase} configuration * @class */ function ODataModelBuilder(configuration) { @@ -1221,8 +1222,9 @@ function ODataModelBuilder(configuration) { this[entityContainerProperty] = []; this.defaultNamespace = null; this.defaultAlias = null; + this.loaded = new AsyncSeriesEventEmitter(); /** - * @returns {ConfigurationBase} + * @returns {import('@themost/common').ConfigurationBase} */ this.getConfiguration = function() { return configuration; @@ -1495,7 +1497,7 @@ ODataModelBuilder.prototype.getEdmSync = function() { */ ODataModelBuilder.prototype.getEdmDocument = function() { var self = this; - return Q.promise(function(resolve, reject) { + return new Q.promise(function(resolve, reject) { try{ return self.getEdm().then(function(schema) { var doc = schemaToEdmDocument.bind(self)(schema); @@ -2386,6 +2388,118 @@ EdmMapping.getOwnActions = function(obj) { }); }; +/** + * @param {ODataModelBuilder} builder + * @param {string} name + * @constructor + */ +function ComplexTypeConfiguration(builder, name) { + this.name = name; + this.abstract = false; + this.openType = false; + this.property = []; + this.navigationProperty = []; + // set builder + this[builderProperty] = builder; +} + +/** + * @param {string} name + * @param {string} type + * @param {boolean=} nullable + * @returns {ComplexTypeConfiguration} + */ +ComplexTypeConfiguration.prototype.addProperty = function (name, type, nullable) { + const property = this.property.find(function(property) { return property.name === name; }); + if (property) { + Object.assign(property, { type, nullable }); + return this; + } + this.property.push({ + 'name': name, + 'type': type, + 'nullable': nullable + }); + return this; +} +/** + * @param {string} name + * @param {string} type + * @param {('Many' | 'One' | 'Unknown' | 'ZeroOrOne')=} multiplicity + * @return {ComplexTypeConfiguration} + */ +ComplexTypeConfiguration.prototype.addNavigationProperty = function (name, type, multiplicity) { + const navigationProperty = this.navigationProperty.find(function(property) { return property.name === name; }); + if (navigationProperty) { + Object.assign(navigationProperty, { + 'type': multiplicity === 'Many' ? sprintf('Collection(%s)', type) : type + }); + return this; + } + this.navigationProperty.push({ + 'name': name, + 'type': multiplicity === 'Many' ? sprintf('Collection(%s)', type) : type, + }); + return this; +} +/** + * @param {string} name + * @return {ComplexTypeConfiguration} + */ +ComplexTypeConfiguration.prototype.removeProperty = function(name) { + Args.notString(name,'Property name'); + var hasProperty =this.property.findIndex( function(x) { + return x.name === name; + }); + if (hasProperty>-1) { + this.property.splice(hasProperty, 1); + } + return this; +}; +/** + * @param {string} name + * @return {ComplexTypeConfiguration} + */ +ComplexTypeConfiguration.prototype.removeNavigationProperty = function(name) { + Args.notString(name,'Property name'); + var hasProperty =this.navigationProperty.findIndex( function(x) { + return x.name === name; + }); + if (hasProperty>-1) { + this.navigationProperty.splice(hasProperty, 1); + } + return this; +}; +/** + * + * @param {import('@themost/xml').XNode} element + */ +ComplexTypeConfiguration.prototype.writeXml = function (element) { + /** + * @type {import('@themost/xml').XDocument} + */ + const document = element.ownerDocument; + const complextType = document.createElement('ComplexType'); + complextType.setAttribute('Name', this.name); + complextType.setAttribute('Abstract', this.abstract); + complextType.setAttribute('OpenType', this.openType);!Paro + // add properties + this.property.forEach(function(property) { + const propertyElement = document.createElement('Property'); + propertyElement.setAttribute('Name', property.name); + propertyElement.setAttribute('Type', property.type); + propertyElement.setAttribute('Nullable', property.nullable); + complextType.appendChild(propertyElement); + }); + // add navigation properties + this.navigationProperty.forEach(function(property) { + const propertyElement = document.createElement('NavigationProperty'); + propertyElement.setAttribute('Name', property.name); + propertyElement.setAttribute('Type', property.type); + complextType.appendChild(propertyElement); + }); +} + //exports module.exports = { @@ -2401,6 +2515,7 @@ module.exports = { ODataModelBuilder, ODataConventionModelBuilder, EdmMapping, - defineDecorator + defineDecorator, + ComplexTypeConfiguration } diff --git a/package-lock.json b/package-lock.json index 0aaaffb..e3f9c39 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@themost/promise-sequence": "^1.0.1", "async": "^2.6.4", "lodash": "^4.17.21", + "moment": "^2.30.1", "node-cache": "^1.1.0", "pluralize": "^7.0.0", "q": "^1.4.1", @@ -43,7 +44,6 @@ "eslint-plugin-node": "^11.1.0", "jest": "^29.7.0", "jest-standard-reporter": "^2.0.0", - "moment": "^2.30.1", "sql.js": "^1.4.0", "ts-node": "^9.1.1", "typescript": "^4.2.3" @@ -10320,12 +10320,13 @@ "dev": true }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -10503,7 +10504,7 @@ "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", - "dev": true, + "license": "MIT", "engines": { "node": "*" } @@ -12975,8 +12976,7 @@ "version": "7.21.0-placeholder-for-preset-env.2", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", - "dev": true, - "requires": {} + "dev": true }, "@babel/plugin-syntax-async-generators": { "version": "7.8.4", @@ -16293,8 +16293,7 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} + "dev": true }, "agent-base": { "version": "6.0.2", @@ -16967,8 +16966,7 @@ "version": "1.5.1", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", - "dev": true, - "requires": {} + "dev": true }, "deep-extend": { "version": "0.6.0", @@ -18861,8 +18859,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "requires": {} + "dev": true }, "jest-regex-util": { "version": "29.6.3", @@ -19915,12 +19912,12 @@ "dev": true }, "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "requires": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" } }, @@ -20054,8 +20051,7 @@ "moment": { "version": "2.30.1", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", - "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", - "dev": true + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==" }, "ms": { "version": "2.1.2", diff --git a/package.json b/package.json index 1cd356f..17fd1c6 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@themost/promise-sequence": "^1.0.1", "async": "^2.6.4", "lodash": "^4.17.21", + "moment": "^2.30.1", "node-cache": "^1.1.0", "pluralize": "^7.0.0", "q": "^1.4.1", @@ -60,7 +61,6 @@ "eslint-plugin-node": "^11.1.0", "jest": "^29.7.0", "jest-standard-reporter": "^2.0.0", - "moment": "^2.30.1", "sql.js": "^1.4.0", "ts-node": "^9.1.1", "typescript": "^4.2.3" diff --git a/spec/TestTemplate.spec.ts b/spec/TestTemplate.spec.ts new file mode 100644 index 0000000..fcc690b --- /dev/null +++ b/spec/TestTemplate.spec.ts @@ -0,0 +1,22 @@ +import { TestApplication } from './TestApplication'; +import { DataContext } from '@themost/data'; +import { resolve } from 'path'; +describe('TestTemplate', () => { + let app: TestApplication; + let context: DataContext; + beforeAll((done) => { + app = new TestApplication(resolve(__dirname, 'test2')); + context = app.createContext(); + return done(); + }); + afterAll(async () => { + await context.finalizeAsync(); + await app.finalize(); + }); + it('should use this test', async () => { + await context.executeInTransactionAsync(async () => { + expect(true).toBeTruthy(); + }); + }); + +}); diff --git a/tsconfig.json b/tsconfig.json index 67a825e..44f71ad 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,7 @@ "sourceMap": true, "noImplicitAny": true, "outDir": "./dist", + "baseUrl": ".", "lib": [ "es2015", "es2016", @@ -16,6 +17,7 @@ "dom" ], "paths": { + "@themost/data": ["./index"] } }, "exclude": [