@odata/metadata
Version:
OData(V4) Metadata Utilities
968 lines (794 loc) • 23.7 kB
text/typescript
import { LRUMap } from '@newdash/newdash/functional/LRUMap';
import { PrimitiveTypeEnum } from './enum';
import * as metacode from './metacode';
const { jsonProperty } = metacode;
/**
* Entity Data Model
*/
export namespace Edm {
export class PrimitiveType {
constructor(public className: string) {}
/**
* @returns edm type code
*/
toString() {
return this.className;
}
/**
* create value based current type
*
* @param value
* @returns
*/
createValue(value: any) {
return new PrimitiveTypeValue(value, this);
}
}
/**
* primitive literal with value
*/
export class PrimitiveTypeValue {
private type: PrimitiveType;
private value: any;
constructor(value: any, type: PrimitiveType) {
this.value = value;
this.type = type;
}
/**
* get the primitive literal type
*
* @returns
*/
public getType() {
return this.type;
}
public getValue() {
return this.value;
}
}
export const Binary = new PrimitiveType(PrimitiveTypeEnum.Binary);
export const Boolean = new PrimitiveType(PrimitiveTypeEnum.Boolean);
export const Byte = new PrimitiveType('Edm.Byte');
export const Date = new PrimitiveType('Edm.Date');
/**
* @since odata v2
*/
export const DateTime = new PrimitiveType('Edm.DateTime');
export const DateTimeOffset = new PrimitiveType('Edm.DateTimeOffset');
export const Decimal = new PrimitiveType('Edm.Decimal');
export const Double = new PrimitiveType('Edm.Double');
export const Duration = new PrimitiveType('Edm.Duration');
export const Guid = new PrimitiveType('Edm.Guid');
export const Int16 = new PrimitiveType('Edm.Int16');
export const Int32 = new PrimitiveType('Edm.Int32');
export const Int64 = new PrimitiveType('Edm.Int64');
export const SByte = new PrimitiveType('Edm.SByte');
export const Single = new PrimitiveType('Edm.Single');
export const Stream = new PrimitiveType('Edm.Stream');
export const String = new PrimitiveType('Edm.String');
export const TimeOfDay = new PrimitiveType('Edm.TimeOfDay');
export const Geography = new PrimitiveType('Edm.Geography');
export const GeographyPoint = new PrimitiveType('Edm.GeographyPoint');
export const GeographyLineString = new PrimitiveType(
'Edm.GeographyLineString'
);
export const GeographyPolygon = new PrimitiveType('Edm.GeographyPolygon');
export const GeographyMultiPoint = new PrimitiveType(
'Edm.GeographyMultiPoint'
);
export const GeographyMultiLineString = new PrimitiveType(
'Edm.GeographyMultiLineString'
);
export const GeographyMultiPolygon = new PrimitiveType(
'Edm.GeographyMultiPolygon'
);
export const GeographyCollection = new PrimitiveType(
'Edm.GeographyCollection'
);
export const Geometry = new PrimitiveType('Edm.Geometry');
export const GeometryPoint = new PrimitiveType('Edm.GeometryPoint');
export const GeometryLineString = new PrimitiveType('Edm.GeometryLineString');
export const GeometryPolygon = new PrimitiveType('Edm.GeometryPolygon');
export const GeometryMultiPoint = new PrimitiveType('Edm.GeometryMultiPoint');
export const GeometryMultiLineString = new PrimitiveType(
'Edm.GeometryMultiLineString'
);
export const GeometryMultiPolygon = new PrimitiveType(
'Edm.GeometryMultiPolygon'
);
export const GeometryCollection = new PrimitiveType('Edm.GeometryCollection');
const MemberAttribute = metacode.MemberAttribute;
const parse = metacode.parse;
const required = metacode.required;
const defaultValue = metacode.defaultValue;
const parseAs = metacode.parseAs;
const AttributeFunctionChain = metacode.AttributeFunctionChain;
const mapArray = (sourceField, factory) =>
new metacode.AttributeFunctionChain(
(d, i) => d[sourceField],
(props, i) => (Array.isArray(props) ? props : props ? [props] : []),
(props, i) => props.map((prop) => factory(prop, i))
);
const primitiveAnnotationValue = (sourceField) =>
new metacode.AttributeFunctionChain((d, i) => {
if (
d['collection'] &&
d['collection'][0] &&
Array.isArray(d['collection'][0][sourceField]) &&
!d[sourceField]
) {
return d['collection'][0][sourceField].map((x) => x.text);
}
const props = d[sourceField];
if (Array.isArray(props)) {
return props.filter((x) => 'text' in x).map((x) => x.text)[0];
}
return props;
});
const annotationTypeSelector = (source) => {
for (const i in AnnotationTypes) {
if (
i in source ||
(source['collection'] &&
source['collection'][0] &&
i in source['collection'][0])
) {
return AnnotationTypes[i];
}
}
return Annotation;
};
export class EdmItemBase {
constructor(definition?: any, public parent?: EdmItemBase) {
definition && this.loadFrom(definition);
}
private _cache: LRUMap;
protected _tryGetCache<T>(key: string, producer: () => T): T {
// lazy create cache
if (this._cache === undefined) {
this._cache = new LRUMap();
}
if (!this._cache.has(key)) {
this._cache.set(key, producer());
}
return this._cache.get(key);
}
public getAnnotationsByTerm(term: string) {
return this._tryGetCache(`_type_${term}_`, () => {
const rt = [];
this['annotations']?.map?.((annotation: Annotation) => {
if (annotation.term === term) {
rt.push(rt);
}
});
return rt;
});
}
loadFrom(definition) {
const proto = Object.getPrototypeOf(this);
MemberAttribute.getMembers(proto).forEach((membername) => {
const parser = MemberAttribute.getAttributeValue(
proto,
membername,
'serialize'
);
if (parser) {
const v = parser.invoke(definition, this);
this[membername] = v;
}
});
}
}
export class Property extends EdmItemBase {
public name: string;
public type: string;
public nullable: boolean;
public maxLength: number;
public precision: number;
public scale: number;
public unicode: boolean;
public SRID: number;
public defaultValue: any;
public concurrencyMode: String;
public annotations: Array<Edm.Annotation>;
}
export class NavigationProperty extends EdmItemBase {
public name: string;
public type: string;
public nullable: boolean;
public partner: string;
public containsTarget: boolean;
public referentialConstraints: Array<ReferentialConstraint>;
//TODO onDelete
public annotations: Array<Edm.Annotation>;
}
export class ReferentialConstraint extends EdmItemBase {
public property: string;
public referencedProperty: string;
}
export class PropertyRef extends EdmItemBase {
public name: string;
//@requiredIfContainerIsComplexType
public alias: string;
}
export class Key extends EdmItemBase {
public propertyRefs: Array<PropertyRef>;
}
export class EntityType extends EdmItemBase {
public name: string;
public key: Key;
public baseType: string;
public abstract: boolean;
public openType: boolean;
public hasStream: boolean;
public properties: Array<Property>;
public navigationProperties: Array<NavigationProperty>;
public annotations: Array<Edm.Annotation>;
public getPropertyByName(propertyName: string): Property {
return this._tryGetCache(`_prop_${propertyName}_`, () => {
for (const property of this.properties) {
if (property.name === propertyName) {
return property;
}
return null;
}
});
}
}
export class ComplexType extends EdmItemBase {
public name: string;
public baseType: string;
public abstract: boolean;
public openType: boolean;
public hasStream: boolean;
public properties: Array<Property>;
public navigationProperties: Array<NavigationProperty>;
public annotations: Array<Edm.Annotation>;
}
export class Parameter extends EdmItemBase {
public name: string;
public type: string;
public nullable: boolean;
public maxLength: number;
public precision: number;
public scale: number;
public unicode: boolean;
public SRID: number;
public annotations: Array<Edm.Annotation>;
// according to specs there is no default value for params. but is that right?
// @parse
// public defaultValue: any;
}
export class ReturnType extends EdmItemBase {
public type: string;
public nullable: boolean;
public annotations: Array<Edm.Annotation>;
}
export class Invokable extends EdmItemBase {}
export class Action extends Invokable {
public name: string;
public isBound: boolean;
public entitySetPath: string;
public parameters: Array<Parameter>;
public returnType: ReturnType;
public annotations: Array<Edm.Annotation>;
}
export class Function extends Invokable {
public name: string;
public isBound: boolean;
public entitySetPath: string;
public parameters: Array<Parameter>;
public returnType: ReturnType;
public isComposable: boolean;
public annotations: Array<Edm.Annotation>;
}
export class Member extends EdmItemBase {
public name: string;
public value: number;
public annotations: Array<Edm.Annotation>;
}
export class EnumType extends EdmItemBase {
public name: string;
public namespace: string;
//@oneOf(Edm.Byte, Edm.SByte, Edm.Int16, Edm.Int32, Edm.Int64)
public underlyingType: PrimitiveType;
public isFlags: boolean;
public members: Array<Member>;
public annotations: Array<Edm.Annotation>;
}
export class EntitySet extends EdmItemBase {
public name: string;
public entityType: string;
<EntitySet>('annotation')
public annotations: Array<Edm.Annotation>;
}
export class ActionImport extends EdmItemBase {
public name: string;
public action: string;
public annotations: Array<Edm.Annotation>;
}
export class FunctionImport extends EdmItemBase {
public name: string;
public function: string;
public includeInServiceDocument: boolean;
public annotations: Array<Edm.Annotation>;
}
export class EntityContainer extends EdmItemBase {
public name: string;
public entitySets: Array<EntitySet>;
public actionImports: Array<ActionImport>;
public functionImports: Array<FunctionImport>;
}
// "Name", "UnderlyingType", "MaxLength", "Unicode", "Precision", "Scale", "SRID"
export class TypeDefinition extends EdmItemBase {
public name: string;
public underlyingType: PrimitiveType;
public maxLength: number;
public unicode: boolean;
public precision: number;
public scale: number;
public SRID: number;
public annotations: Array<Edm.Annotation>;
}
export class Schema extends EdmItemBase {
public namespace: string;
//@noneOf(["Edm", "odata", "System", "Transient")
public alias: string;
public enumTypes: Array<EnumType>;
public typeDefinitions: Array<TypeDefinition>;
public complexTypes: Array<ComplexType>;
public entityTypes: Array<EntityType>;
public actions: Array<Action>;
public functions: Array<Edm.Function>;
public entityContainer: Array<Edm.EntityContainer>;
public annotations: Array<Edm.Annotations>;
}
export class DataServices extends EdmItemBase {
public schemas: Array<Schema>;
}
export class Reference extends EdmItemBase {
public uri: string;
public includes: Array<ReferenceInclude>;
}
export class ReferenceInclude extends EdmItemBase {
public namespace: string;
public alias: string;
}
export class Edmx extends EdmItemBase {
public version = '4.0';
public dataServices: DataServices;
public references: Array<Edm.Reference>;
}
export class Annotations extends EdmItemBase {
public target: string;
public qualifier: string;
public annotations: Array<Edm.Annotation>;
}
export class Annotation extends EdmItemBase {
public annotationType: string = 'Unknown';
public term: string;
public qualifier: string;
public path: string;
}
export class BinaryAnnotation extends Annotation {
public annotationType: string = 'Binary';
public binary: String;
}
export class BoolAnnotation extends Annotation {
public annotationType: string = 'Bool';
public bool: String;
}
export class DateAnnotation extends Annotation {
public annotationType: string = 'Date';
public date: String;
}
export class DateTimeOffsetAnnotation extends Annotation {
public annotationType: string = 'DateTimeOffset';
public dateTimeOffset: String;
}
export class DecimalAnnotation extends Annotation {
public annotationType: string = 'Decimal';
public decimal: String;
}
export class DurationAnnotation extends Annotation {
public annotationType: string = 'Duration';
public duration: String;
}
export class EnumMemberAnnotation extends Annotation {
public annotationType: string = 'EnumMember';
public enumMember: String;
}
export class FloatAnnotation extends Annotation {
public annotationType: string = 'Float';
public float: String;
}
export class GuidAnnotation extends Annotation {
public annotationType: string = 'Guid';
public guid: String;
}
export class IntAnnotation extends Annotation {
public annotationType: string = 'Int';
public int: String;
}
export class StringAnnotation extends Annotation {
public annotationType: string = 'String';
public string: String;
}
export class TimeOfDayAnnotation extends Annotation {
public annotationType: string = 'TimeOfDay';
public timeOfDay: String;
}
export class PropertyPathAnnotation extends Annotation {
public annotationType: string = 'PropertyPath';
<PropertyPathAnnotation>('propertyPath')
public propertyPaths: Array<string>;
public toJson() {
return {
term: this.annotationType,
collection: [
{ propertyPath: this.propertyPaths?.map((s) => ({ text: s })) }
]
};
}
}
export class NavigationPropertyPathAnnotation extends Annotation {
public annotationType: string = 'NavigationPropertyPath';
public navigationPropertyPaths: String;
}
export class AnnotationPathAnnotation extends Annotation {
public annotationType: string = 'AnnotationPath';
public annotationPaths: String;
}
export class NullAnnotation extends Annotation {
public annotationType: string = 'Null';
public null: Array<Object>;
}
export const AnnotationTypes = {
binary: BinaryAnnotation,
bool: BoolAnnotation,
date: DateAnnotation,
dateTimeOffset: DateTimeOffsetAnnotation,
decimal: DecimalAnnotation,
duration: DurationAnnotation,
enumMember: EnumMemberAnnotation,
float: FloatAnnotation,
guid: GuidAnnotation,
int: IntAnnotation,
string: StringAnnotation,
timeOfDay: TimeOfDayAnnotation,
propertyPath: PropertyPathAnnotation,
navigationPropertyPath: NavigationPropertyPathAnnotation,
annotationPath: AnnotationPathAnnotation,
null: NullAnnotation
};
export const toAnnotationTypeKey = (value) => {
for (const [key, type] of Object.entries(AnnotationTypes)) {
if (value instanceof type) {
return key;
}
}
};
}