Skip to content

Commit

Permalink
Define EdmSchema annotations (#17)
Browse files Browse the repository at this point in the history
* define entity type annotation

* add test classes

* define entity type constructor

* define annotations

* use superagent instead of axios (#18)

* use superagent instead of axios

* 2.13.0

* include original error for futher processing

* Add type renderer (#19)

* add type renderer

* render type declarations

* sort exported entity types

* fix build

* implement client-cli

* 2.14.0

* add file schema renderer (#20)

* add file schema renderer

* 2.14.1

* test entity annotations

* 2.14.2
  • Loading branch information
kbarbounakis authored Apr 2, 2024
1 parent a60db70 commit eff55db
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 21 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@themost/client",
"version": "2.14.1",
"version": "2.14.2",
"description": "MOST Web Framework Codename Blueshift - Client Common",
"module": "dist/index.esm.js",
"main": "dist/index.js",
Expand Down
28 changes: 21 additions & 7 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {ClientDataServiceBase, ClientDataContextBase, TextUtils, DataServiceQuer
configurable,
enumerable,
DataServiceHeaders} from './common';
import {EdmSchema} from './metadata';
import {EdmSchema, EntityConstructor, EntitySetAnnotation, EntityTypeAnnotation} from './metadata';
import { OpenDataQuery, OpenDataQueryFormatter, QueryFunc } from '@themost/query'
import {SyncSeriesEventEmitter} from '@themost/events';

Expand Down Expand Up @@ -826,8 +826,23 @@ export class ClientDataModel {

private readonly _name: string;

constructor(name: string, service: ClientDataServiceBase) {
this._name = name;
constructor(model: string | EntityConstructor<any>, service: ClientDataServiceBase) {
if (model instanceof Function) {
const entitySetAnnotation = model as unknown as EntitySetAnnotation;
if (entitySetAnnotation.EntitySet) {
// get entity set name as model
const { name } = entitySetAnnotation.EntitySet;
this._name = name || model.name;
}
const entityTypeAnnotation = model as unknown as EntityTypeAnnotation;
if (entityTypeAnnotation.Entity) {
// get entity type name as model
const { name } = entityTypeAnnotation.Entity;
this._name = name || model.name;
}
} else if (typeof model === 'string') {
this._name = model;
}
Object.defineProperty(this, '_service', {
configurable: false,
enumerable: false,
Expand Down Expand Up @@ -1067,12 +1082,11 @@ export class ClientDataContext implements ClientDataContextBase {

/**
* Gets an instance of ClientDataModel class
* @param name {string|*} - A string which represents the name of the data model.
* @param model {string|*} - A string which represents the name of the data model.
* @returns {ClientDataModel}
*/
public model(name: string): ClientDataModel {
Args.notEmpty(name, 'Model name');
return new ClientDataModel(name, this.service);
public model(model: string | EntityConstructor<any>): ClientDataModel {
return new ClientDataModel(model, this.service);
}

public getMetadata(force = false): Promise<EdmSchema> {
Expand Down
65 changes: 53 additions & 12 deletions src/metadata.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,18 @@
import {XDocument, XNode, XSerializer} from '@themost/xml';
import { XDocument, XNode, XSerializer } from '@themost/xml';

export declare type EntityConstructor<T> = new(...args: any[]) => T;

export declare interface EntityTypeAnnotation {
Entity: {
name?: string
}
}

export declare interface EntitySetAnnotation {
EntitySet: {
name?: string
}
}

/**
* Represents an OData service metadata document
Expand All @@ -19,20 +33,45 @@ export class EdmSchema {
public Action: EdmAction[] = [];
public Function: EdmFunction[] = [];
public readXml(node: XNode) {
this.EntityType = node.selectNodes('EntityType').map( (x) => {
this.EntityType = node.selectNodes('EntityType').map((x) => {
return XSerializer.deserialize(x, EdmEntityType);
});
const entityContainerNode = node.selectSingleNode('EntityContainer');
if (entityContainerNode) {
this.EntityContainer = XSerializer.deserialize(entityContainerNode, EdmEntityContainer);
}
this.Action = node.selectNodes('Action').map( (x) => {
this.Action = node.selectNodes('Action').map((x) => {
return XSerializer.deserialize(x, EdmAction);
});
this.Function = node.selectNodes('Function').map( (x) => {
this.Function = node.selectNodes('Function').map((x) => {
return XSerializer.deserialize(x, EdmFunction);
});
}

static entityType(name?: string) {
return (target: EntityConstructor<any>) => {
// get entity type
const entityType = target as unknown as EntityTypeAnnotation;
// use name or target entity name
const entityName = name || target.name;
// assign entity type annotation
entityType.Entity = Object.assign({
name: entityName
});
}
}

static entitySet(name: string) {
return (target: EntityConstructor<any>) => {
// get entity type
const entitySet = target as unknown as EntitySetAnnotation;
// assign entity type annotation
entitySet.EntitySet = Object.assign({
name
});
}
}

}

/**
Expand All @@ -41,7 +80,7 @@ export class EdmSchema {
export class EdmEntityContainer {
public EntitySet: EdmEntitySet[] = [];
public readXml(node: XNode) {
this.EntitySet = node.selectNodes('EntitySet').map( (x) => {
this.EntitySet = node.selectNodes('EntitySet').map((x) => {
return XSerializer.deserialize(x, EdmEntitySet);
});
}
Expand All @@ -55,7 +94,7 @@ export class EdmProcedure {
public readXml(node: XNode) {
this.Name = node.getAttribute('Name');
this.IsBound = node.getAttribute('IsBound') === 'true';
this.Parameter = node.selectNodes('Parameter').map( (x) => {
this.Parameter = node.selectNodes('Parameter').map((x) => {
return XSerializer.deserialize(x, EdmParameter);
});
const returnTypeNode = node.selectSingleNode('ReturnType');
Expand Down Expand Up @@ -148,7 +187,7 @@ export class EdmProperty {
if (longDescription) {
this.LongDescription = longDescription.getAttribute('String');
}
this.Annotations = node.selectNodes('Annotation').map( (annotationNode) => {
this.Annotations = node.selectNodes('Annotation').map((annotationNode) => {
return XSerializer.deserialize(annotationNode, EdmAnnotation);
});
}
Expand Down Expand Up @@ -187,7 +226,7 @@ export class EdmNavigationProperty {
if (longDescription) {
this.LongDescription = longDescription.getAttribute('String');
}
this.Annotations = node.selectNodes('Annotation').map( (annotationNode) => {
this.Annotations = node.selectNodes('Annotation').map((annotationNode) => {
return XSerializer.deserialize(annotationNode, EdmAnnotation);
});
}
Expand All @@ -199,7 +238,7 @@ export class EdmNavigationProperty {
export class EdmKey {
public PropertyRef: EdmPropertyRef[] = [];
public readXml(node: XNode) {
this.PropertyRef = node.selectNodes('PropertyRef').map( (x) => {
this.PropertyRef = node.selectNodes('PropertyRef').map((x) => {
return XSerializer.deserialize(x, EdmPropertyRef);
});
}
Expand Down Expand Up @@ -238,21 +277,22 @@ export class EdmEntityType {
if (keyNode) {
this.Key = XSerializer.deserialize(keyNode, EdmKey);
}
this.Property = node.selectNodes('Property').map( (x) => {
this.Property = node.selectNodes('Property').map((x) => {
return XSerializer.deserialize(x, EdmProperty);
});
this.NavigationProperty = node.selectNodes('NavigationProperty').map( (x) => {
this.NavigationProperty = node.selectNodes('NavigationProperty').map((x) => {
return XSerializer.deserialize(x, EdmNavigationProperty);
});
// get implements annotation
const implementsAnnotation = node.selectSingleNode('Annotation[@Term="DataModel.OData.Core.V1.Implements"]');
if (implementsAnnotation) {
this.ImplementsType = implementsAnnotation.getAttribute('String');
}
this.Annotations = node.selectNodes('Annotation').map( (annotationNode) => {
this.Annotations = node.selectNodes('Annotation').map((annotationNode) => {
return XSerializer.deserialize(annotationNode, EdmAnnotation);
});
}

}

/**
Expand All @@ -274,6 +314,7 @@ export class EdmEntitySet {
this.ResourcePath = resourcePathNode.getAttribute('String');
}
}

}

export class EdmAnnotation {
Expand Down
88 changes: 88 additions & 0 deletions src/test/EdmSchema.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import {EdmSchema} from '@themost/client';
import {TestContext} from './TestUtils';

class Thing {
id?: number;
name?: string;
description?: string;
createdBy?: number;
modifiedBy?: number;
dateCreated?: Date;
dateModified?: Date;
sameAs?: string;
url?: string;
image?: string;
additionalType?: string;
identifier?: string;
alternateName?: string;
disambiguatingDescription?: string;
}

@EdmSchema.entitySet('Products')
class Product extends Thing {
model?: string;
productID?: string;
category?: string;
releaseDate?: Date;
discontinued?: boolean;
price?: number;
isRelatedTo?: Product | number;
isSimilarTo?: Product | number;
}

@EdmSchema.entityType('Order')
class Order extends Thing {
orderDate?: Date;
customer?: Person | number;
orderedItem?: Product | number;
}

@EdmSchema.entityType()
class Person extends Thing {

}

describe('EdmSchema', () => {

let context: TestContext;
beforeAll(async () => {
context = new TestContext();
await context.authenticate();
});

it('should define entity set annotation', () => {
const annotation = Product as unknown as {
EntitySet: {
name: string
}
}
expect(annotation.EntitySet).toBeTruthy();
expect(annotation.EntitySet.name).toEqual('Products');
});

it('should define entity type annotation', () => {
const annotation = Order as unknown as {
Entity: {
name: string
}
}
expect(annotation.Entity).toBeTruthy();
expect(annotation.Entity.name).toEqual('Order');
});

it('should define entity type annotation from class', () => {
const annotation = Person as unknown as {
Entity: {
name: string
}
}
expect(annotation.Entity).toBeTruthy();
expect(annotation.Entity.name).toEqual('Person');
});

it('should get items by using class', async () => {
const items = await context.model(Product).where<Product>((x) => x.category === 'Laptops').getItems();
expect(items).toBeTruthy();
expect(items.length).toBeTruthy();
});
});

0 comments on commit eff55db

Please sign in to comment.