UNPKG

baqend

Version:

Baqend JavaScript SDK

257 lines (218 loc) 6.25 kB
import { Acl } from '../Acl'; import { Lockable } from '../util'; import type { EntityManager } from '../EntityManager'; import type { Entity, Managed } from '../binding'; import { PersistentError } from '../error'; import type { EntityType, ManagedType } from '../metamodel'; export interface ManagedState { db?: EntityManager; type: ManagedType<any>; /** * Indicates the the object is modified by the user */ setDirty(): void; } export enum MetadataState { UNAVAILABLE = -1, PERSISTENT = 0, DIRTY = 1, } /** * The Metadata instance tracks the state of an object and checks if the object state was changed since last * load/update. The metadata keeps therefore the state of: * - in which state the object currently is * - which db managed the instance * - the metadata of the object (id, version, bucket) * - which is the owning object (root object) of an embedded object * * {@link Metadata#get(object)} can be used on any managed object to retrieve the metadata of the root object */ export class Metadata extends Lockable implements ManagedState { entityManager: EntityManager | null = null; type: EntityType<any>; decodedKey: string | null = null; id: string | null = null; state: MetadataState; version: number | null; enabled: boolean; acl: Acl; /** * Creates temporary metadata instance for the given embeddable type * * @param type The type of the object * @param db a EntityManager which will be attached to the state * @return The created metadata for the object */ static create<T extends Managed>(type: ManagedType<T>, db?: EntityManager): ManagedState; /** * Creates a metadata instance for the given entity type * * @param type The type of the object * @param db a optional EntityManager which will be attached to the state * @return The created metadata for the object */ static create<T extends Entity>(type: EntityType<T>, db?: EntityManager): ManagedState | Metadata; static create<T extends Entity>(type: ManagedType<T>, db?: EntityManager): ManagedState | Metadata { if (type.isEntity) { return new Metadata(type as EntityType<T>); } if (type.isEmbeddable) { return { type, db, setDirty() {} }; } throw new Error(`Illegal type ${type}`); } /** * Returns the metadata of the managed object * @param managed * @return */ static get(managed: Entity): Metadata { // eslint-disable-next-line no-underscore-dangle return managed._metadata; } /** * @type EntityManager */ get db(): EntityManager { if (this.entityManager) { return this.entityManager; } this.entityManager = require('../baqend').db; // eslint-disable-line global-require return this.entityManager!; } /** * @param db */ set db(db: EntityManager) { if (!this.entityManager) { this.entityManager = db; } else { throw new Error('DB has already been set.'); } } /** * @type string * @readonly */ get bucket() { return this.type.name; } /** * @type string * @readonly */ get key() { if (!this.decodedKey && this.id) { const index = this.id.lastIndexOf('/'); this.decodedKey = decodeURIComponent(this.id.substring(index + 1)); } return this.decodedKey; } /** * @param value */ set key(value: string | null) { const val = `${value}`; if (this.id) { throw new Error('The id can\'t be set twice.'); } this.id = `/db/${this.bucket}/${encodeURIComponent(val)}`; this.decodedKey = val; } /** * Indicates if this object already belongs to an db * <code>true</code> if this object belongs already to an db otherwise <code>false</code> * @type boolean * @readonly */ get isAttached() { return !!this.entityManager; } /** * Indicates if this object is represents a db object, but was not loaded up to now * @type boolean * @readonly */ get isAvailable() { return this.state > MetadataState.UNAVAILABLE; } /** * Indicates if this object represents the state of the db and was not modified in any manner * @type boolean * @readonly */ get isPersistent() { return this.state === MetadataState.PERSISTENT; } /** * Indicates that this object was modified and the object was not written back to the db * @type boolean * @readonly */ get isDirty() { return this.state === MetadataState.DIRTY; } /** * @param type */ constructor(type: EntityType<any>) { super(); this.state = MetadataState.DIRTY; this.enabled = true; this.id = null; this.version = null; this.type = type; this.acl = new Acl(); } /** * Enable/Disable state change tracking of this object * @param newStateTrackingState The new change tracking state * @return */ enable(newStateTrackingState: boolean): void { this.enabled = newStateTrackingState; } /** * Throws the corresponding error if a property is accessed before the owning object is loaded * @throws an exception if the object properties aren't available and the object is enabled */ throwUnloadedPropertyAccess(property: string) { if (this.enabled && !this.isAvailable) { throw new PersistentError(`Illegal property access on ${this.id}#${property} , ensure that this reference is loaded before it's properties are accessed.`); } } /** * Indicates that the associated object isn't available * @return */ setUnavailable(): void { this.state = MetadataState.UNAVAILABLE; } /** * Indicates that the associated object is not stale * * An object is stale if it correlates the database state and is not modified by the user. * * @return */ setPersistent(): void { this.state = MetadataState.PERSISTENT; } /** * Indicates the the object is modified by the user * @return */ setDirty(): void { this.state = MetadataState.DIRTY; } /** * Indicates the the object is removed * @return */ setRemoved(): void { // mark the object only as dirty if it was already available if (this.isAvailable) { this.setDirty(); this.version = null; } } }