UNPKG

baqend

Version:

Baqend JavaScript SDK

358 lines (303 loc) 9.74 kB
import { Type } from './Type'; import { Class, Json, JsonMap } from '../util'; import { Enhancer, Entity, Managed, ManagedFactory, } from '../binding'; import type { Attribute } from './Attribute'; import type { EntityManager } from '../EntityManager'; import type { EntityType } from './EntityType'; import type { PluralAttribute } from './PluralAttribute'; import type { SingularAttribute } from './SingularAttribute'; import type { EmbeddableType } from './index'; import { Permission, ManagedState } from '../intersection'; export abstract class ManagedType<T extends Managed> extends Type<T> { public enhancer: Enhancer | null = null; public declaredAttributes: Attribute<any>[] = []; public schemaAddPermission: Permission = new Permission(); public schemaReplacePermission: Permission = new Permission(); public metadata: { [key: string]: string } | null = null; public superType: EntityType<any> | null = null; public _validationCode: Function | null = null; /** * @type Function */ get validationCode(): Function | null { return this._validationCode; } /** * @param code */ set validationCode(code: Function | null) { this._validationCode = code; } /** * The Managed class */ get typeConstructor(): Class<T> { if (!this._typeConstructor) { this.typeConstructor = this.createProxyClass(); } return this._typeConstructor!!; } /** * The Managed class constructor * @param typeConstructor The managed class constructor */ set typeConstructor(typeConstructor: Class<T>) { if (this._typeConstructor) { throw new Error('Type constructor has already been set.'); } const isEntity = typeConstructor.prototype instanceof Entity; if (this.isEntity) { if (!isEntity) { throw new TypeError('Entity classes must extends the Entity class.'); } } else if (!(typeConstructor.prototype instanceof Managed) || isEntity) { throw new TypeError('Embeddable classes must extends the Managed class.'); } this.enhancer!!.enhance(this, typeConstructor); this._typeConstructor = typeConstructor; } /** * @param ref or full class name * @param typeConstructor The type constructor of the managed lass */ constructor(ref: string, typeConstructor?: Class<T>) { super(ref.indexOf('/db/') !== 0 ? `/db/${ref}` : ref, typeConstructor); } /** * Initialize this type * @param enhancer The class enhancer used to instantiate an instance of this managed class */ init(enhancer: Enhancer): void { this.enhancer = enhancer; if (this._typeConstructor && !Enhancer.getIdentifier(this._typeConstructor!!)) { Enhancer.setIdentifier(this._typeConstructor!!, this.ref); } } /** * Creates an ProxyClass for this type * @return the crated proxy class for this type */ abstract createProxyClass(): Class<T>; /** * Creates an ObjectFactory for this type and the given EntityManager * @param db The created instances will be attached to this EntityManager * @return the crated object factory for the given EntityManager */ abstract createObjectFactory(db: EntityManager): ManagedFactory<T>; /** * Creates a new instance of the managed type, without invoking any constructors * * This method is used to create object instances which are loaded form the backend. * * @return The created instance */ create(): T { const instance = Object.create(this.typeConstructor.prototype); Managed.init(instance); return instance; } /** * An iterator which returns all attributes declared by this type and inherited form all super types * @return */ attributes(): IterableIterator<Attribute<any>> { let iter: Iterator<Attribute<any>> | null; let index = 0; const type = this; if (this.superType) { iter = this.superType.attributes(); } return { [Symbol.iterator]() { return this; }, next() { if (iter) { const item = iter.next(); if (!item.done) { return item; } iter = null; } if (index < type.declaredAttributes.length) { const value = type.declaredAttributes[index]; index += 1; return { value, done: false }; } return { done: true, value: undefined }; }, }; } /** * Adds an attribute to this type * @param attr The attribute to add * @param order Position of the attribute * @return */ addAttribute(attr: Attribute<any>, order?: number): void { if (this.getAttribute(attr.name)) { throw new Error(`An attribute with the name ${attr.name} is already declared.`); } let initOrder; if (!attr.order) { initOrder = typeof order === 'undefined' ? this.declaredAttributes.length : order; } else { initOrder = attr.order; } attr.init(this, initOrder); this.declaredAttributes.push(attr); if (this._typeConstructor && this.name !== 'Object') { this.enhancer!!.enhanceProperty(this._typeConstructor, attr); } } /** * Removes an attribute from this type * @param name The Name of the attribute which will be removed * @return */ removeAttribute(name: string): void { const { length } = this.declaredAttributes; this.declaredAttributes = this.declaredAttributes.filter((val) => val.name !== name); if (length === this.declaredAttributes.length) { throw new Error(`An Attribute with the name ${name} is not declared.`); } } /** * @param name * @return */ getAttribute(name: string): Attribute<any> | null { let attr = this.getDeclaredAttribute(name); if (!attr && this.superType) { attr = this.superType.getAttribute(name); } return attr; } /** * @param val Name or order of the attribute * @return */ getDeclaredAttribute(val: string | number): Attribute<any> | null { return this.declaredAttributes.filter((attr) => attr.name === val || attr.order === val)[0] || null; } /** * @inheritDoc */ fromJsonValue(state: ManagedState, jsonObject: Json, currentObject: T | null, options: { onlyMetadata?: boolean, persisting: boolean }) { if (!jsonObject || !currentObject) { return null; } const iter = this.attributes(); for (let el = iter.next(); !el.done; el = iter.next()) { const attribute = el.value; if (!options.onlyMetadata || attribute.isMetadata) { attribute.setJsonValue(state, currentObject, (jsonObject as JsonMap)[attribute.name], options); } } return currentObject; } /** * @inheritDoc */ toJsonValue(state: ManagedState, object: T | null, options: { excludeMetadata?: boolean; depth?: number | boolean, persisting: boolean }): Json { if (!(object instanceof this.typeConstructor)) { return null; } const value: { [attr: string]: any } = {}; const iter = this.attributes(); for (let el = iter.next(); !el.done; el = iter.next()) { const attribute = el.value; if (!options.excludeMetadata || !attribute.isMetadata) { value[attribute.name] = attribute.getJsonValue(state, object, options); } } return value; } /** * Converts ths type schema to json * @return */ toJSON(): JsonMap { const fields: { [attr: string]: any } = {}; this.declaredAttributes.forEach((attribute) => { if (!attribute.isMetadata) { fields[attribute.name] = attribute; } }); return { class: this.ref, fields, acl: { schemaAdd: this.schemaAddPermission.toJSON(), schemaReplace: this.schemaReplacePermission.toJSON(), }, ...(this.superType && { superClass: this.superType.ref }), ...(this.isEmbeddable && { embedded: true }), ...(this.metadata && { metadata: this.metadata }), }; } /** * Returns iterator to get all referenced entities * @return */ references(): IterableIterator<{ path: string[] }> { const attributes = this.attributes(); let attribute: Attribute<any>; let embeddedAttributes: IterableIterator<{ path: string[] }> | null; return { [Symbol.iterator]() { return this; }, next() { for (;;) { if (embeddedAttributes) { const item = embeddedAttributes.next(); if (!item.done) { return { value: { path: [attribute.name].concat(item.value.path) } }; } embeddedAttributes = null; } const item = attributes.next(); if (item.done) { // currently TS requires a undefined value here https://github.com/microsoft/TypeScript/issues/38479 return { done: true, value: undefined }; } attribute = item.value; const type = attribute.isCollection ? (attribute as PluralAttribute<any, any>).elementType : (attribute as SingularAttribute<any>).type; if (type.isEntity) { return { value: { path: [attribute.name] } }; } if (type.isEmbeddable) { embeddedAttributes = (type as EmbeddableType<any>).references(); } } }, }; } /** * Retrieves whether this type has specific metadata * * @param key * @return */ hasMetadata(key: string): boolean { return !!this.metadata && !!this.metadata[key]; } /** * Gets some metadata of this type * * @param key * @return */ getMetadata(key: string): string | null { if (!this.hasMetadata(key)) { return null; } return this.metadata!![key]; } }