baqend
Version:
Baqend JavaScript SDK
162 lines (141 loc) • 5.51 kB
text/typescript
import { BasicType } from './BasicType';
import { EntityType } from './EntityType';
import { EmbeddableType } from './EmbeddableType';
import { ListAttribute } from './ListAttribute';
import { MapAttribute } from './MapAttribute';
import { SetAttribute } from './SetAttribute';
import { SingularAttribute } from './SingularAttribute';
import { PersistentError } from '../error';
import { JsonMap } from '../util';
import { Type } from './Type';
import { ManagedType } from './ManagedType';
import { Attribute } from './Attribute';
import { Permission, Validator } from '../intersection';
export class ModelBuilder {
private models: { [name: string]: Type<any> } = {};
private modelDescriptors: { [name: string]: JsonMap } | null = null;
constructor() {
(Object.keys(BasicType) as (keyof typeof BasicType)[]).forEach((typeName) => {
const basicType = BasicType[typeName];
if (basicType instanceof BasicType) {
this.models[basicType.ref] = basicType;
}
});
}
/**
* @param ref
* @return
*/
getModel(ref: string): ManagedType<any> {
if (ref in this.models) {
return this.models[ref] as ManagedType<any>;
}
const model = this.buildModel(ref);
this.models[ref] = model;
return model;
}
/**
* @param modelDescriptors
* @return
*/
buildModels(modelDescriptors: JsonMap[]): { [name: string]: Type<any> } {
this.modelDescriptors = {};
modelDescriptors.forEach((modelDescriptor: JsonMap) => {
this.modelDescriptors![modelDescriptor.class as string] = modelDescriptor;
});
Object.keys(this.modelDescriptors).forEach((ref) => {
try {
const model = this.getModel(ref);
this.buildAttributes(model);
} catch (e: any) {
throw new PersistentError(`Can't create model for entity class ${ref}`, e);
}
});
// ensure at least an object entity
this.getModel(EntityType.Object.ref);
return this.models;
}
/**
* @param ref
* @return
*/
buildModel(ref: string): ManagedType<any> {
const modelDescriptor = this.modelDescriptors![ref];
let type: ManagedType<any>;
if (ref === EntityType.Object.ref) {
type = new EntityType.Object();
} else if (modelDescriptor) {
if (modelDescriptor.embedded) {
type = new EmbeddableType(ref);
} else {
const superTypeIdentifier = modelDescriptor.superClass as string || EntityType.Object.ref;
type = new EntityType(ref, this.getModel(superTypeIdentifier) as EntityType<any>);
}
} else {
throw new TypeError(`No model available for ${ref}`);
}
type.metadata = {};
if (modelDescriptor) {
type.metadata = modelDescriptor.metadata as { [key: string]: string } || {};
const permissions = modelDescriptor.acl || {};
(Object.keys(permissions) as Array<keyof typeof permissions>).forEach((permission) => {
const permissionProperty = `${permission}Permission`;
((type as any)[permissionProperty] as Permission).fromJSON(permissions[permission]);
});
}
return type;
}
/**
* @param model
* @return
*/
buildAttributes(model: ManagedType<any>): void {
const modelDescriptor = this.modelDescriptors![model.ref];
const fields = modelDescriptor.fields as JsonMap;
Object.keys(fields).forEach((name) => {
const field = fields[name] as JsonMap;
if (!model.getAttribute(name)) { // skip predefined attributes
model.addAttribute(this.buildAttribute(field as any), field.order as number);
}
});
if (typeof modelDescriptor.validationCode === 'string') {
// eslint-disable-next-line no-param-reassign
(model as EntityType<any>).validationCode = Validator.compile(model, modelDescriptor.validationCode);
}
}
/**
* @param field The field metadata
* @param field.name The name of zhe field
* @param field.type The type reference of the field
* @param field.order The order number of the field
* @param field.metadata Additional metadata of the field
* @return
*/
buildAttribute(field: { name: string, type: string, order: number, metadata: { [key: string]: string },
flags: string[] }): Attribute<any> {
// TODO: remove readonly if createdAt and updatedAt becomes real metadata fields in the schema
const isMetadata = field.flags && (field.flags.indexOf('METADATA') !== -1 || field.flags.indexOf('READONLY') !== -1);
const { name } = field;
const ref = field.type;
if (ref.indexOf('/db/collection.') !== 0) {
const singularAttribute = new SingularAttribute(name, this.getModel(ref), isMetadata);
singularAttribute.metadata = field.metadata;
return singularAttribute;
}
const collectionType = ref.substring(0, ref.indexOf('['));
const elementType = ref.substring(ref.indexOf('[') + 1, ref.indexOf(']')).trim();
switch (collectionType) {
case ListAttribute.ref:
return new ListAttribute(name, this.getModel(elementType));
case SetAttribute.ref:
return new SetAttribute(name, this.getModel(elementType));
case MapAttribute.ref: {
const keyType = elementType.substring(0, elementType.indexOf(',')).trim();
const valueType = elementType.substring(elementType.indexOf(',') + 1).trim();
return new MapAttribute(name, this.getModel(keyType), this.getModel(valueType));
}
default:
throw new TypeError(`No collection available for ${ref}`);
}
}
}