baqend
Version:
Baqend JavaScript SDK
257 lines (218 loc) • 6.25 kB
text/typescript
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;
}
}
}