@nymphjs/nymph
Version:
Nymph.js - Nymph ORM
326 lines (325 loc) • 12.5 kB
TypeScript
import type Nymph from './Nymph.js';
import type { Options } from './Nymph.types.js';
import type { ACProperties, EntityConstructor, EntityData, EntityInterface, EntityJson, EntityObject, EntityPatch, EntityReference, SerializedEntityData } from './Entity.types.js';
export type EntityDataType<T> = T extends Entity<infer DataType> ? DataType : never;
export type EntityInstanceType<T extends EntityConstructor> = T extends new () => infer E ? E & EntityDataType<E> : never;
export type EntityObjectType<T extends EntityConstructor> = T extends new () => infer E ? EntityObject & EntityDataType<E> : never;
/**
* Database abstraction object.
*
* Provides a way to access, manipulate, and store data in Nymph.
*
* The GUID is not set until the entity is saved. GUIDs must be unique forever,
* even after deletion. It's the job of the Nymph DB driver to make sure no two
* entities ever have the same GUID. This is generally done by using a large
* randomly generated ID.
*
* Each entity class has an etype that determines which table(s) in the database
* it belongs to. If two entity classes have the same etype, their data will be
* stored in the same table(s). This isn't a good idea, however, because
* references to an entity store a class name, not an etype.
*
* Tags are used to classify entities. Where an etype is used to separate data
* by tables, tags can be used to separate entities within a table. You can
* define specific tags to be protected, meaning they cannot be added/removed
* from the client. It can be useful to allow user defined tags, such as for
* blog posts.
*
* Simply calling $delete() will not unset the entity. It will still take up
* memory. Likewise, simply calling unset will not delete the entity from the
* DB.
*
* Some notes about $equals() and $is(), the replacements for "==":
*
* The == operator will likely not give you the result you want, since two
* instances of the same entity will fail that check, even though they represent
* the same data in the database.
*
* $equals() performs a more strict comparison of the entity to another. Use
* $equals() instead of the == operator when you want to check both the entities
* they represent, and the data inside them. In order to return true for
* $equals(), the entity and object must meet the following criteria:
*
* - They must be entities.
* - They must have equal GUIDs, or both must have no GUID.
* - Their data and tags must be equal.
*
* $is() performs a less strict comparison of the entity to another. Use $is()
* instead of the == operator when the entity's data may have been changed, but
* you only care if they represent the same entity. In order to return true, the
* entity and object must meet the following criteria:
*
* - They must be entities.
* - They must have equal GUIDs, or both must have no GUID.
* - If they have no GUIDs, their data and tags must be equal.
*
* Some notes about saving entities in other entity's properties:
*
* Entities use references in the DB to store an entity in their properties. The
* reference is stored as an array with the values:
*
* - 0 => The string 'nymph_entity_reference'
* - 1 => The referenced entity's GUID.
* - 2 => The referenced entity's class name.
*
* Since the referenced entity's class name (meaning the `class` static
* property, not the name of the class itself) is stored in the reference on the
* parent entity, if you change the class name in an update, you need to
* reassign all referenced entities of that class and resave.
*
* When an entity is loaded, it does not request its referenced entities from
* Nymph. Instead, it creates instances without data called sleeping references.
* When you first access an entity's data, if it is a sleeping reference, it
* will fill its data from the DB. You can call clearCache() to turn all the
* entities back into sleeping references.
*/
export default class Entity<T extends EntityData = EntityData> implements EntityInterface {
/**
* The instance of Nymph to use for queries.
*/
static nymph: Nymph;
/**
* A unique name for this type of entity used to separate its data from other
* types of entities in the database.
*/
static ETYPE: string;
/**
* The lookup name for this entity.
*
* This is used for reference arrays (and sleeping references) and client
* requests.
*/
static class: string;
$nymph: Nymph;
guid: string | null;
cdate: number | null;
mdate: number | null;
tags: string[];
/**
* The data proxy handler.
*/
protected $dataHandler: Object;
/**
* The actual data store.
*/
protected $dataStore: T;
/**
* The actual sdata store.
*/
protected $sdata: SerializedEntityData;
/**
* The data proxy object.
*/
protected $data: T;
/**
* Whether this instance is a sleeping reference.
*/
protected $isASleepingReference: boolean;
/**
* The reference to use to wake.
*/
protected $sleepingReference: EntityReference | null;
/**
* A promise that resolved when the entity's data is wake.
*/
protected $wakePromise: Promise<Entity<T>> | null;
/**
* Properties that will not be serialized into JSON with toJSON(). This
* can be considered a denylist, because these properties will not be set
* with incoming JSON.
*
* Clients CAN still determine what is in these properties, unless they are
* also listed in searchRestrictedData.
*/
protected $privateData: string[];
/**
* Whether this entity should publish changes to PubSub servers.
*/
static pubSubEnabled: boolean;
/**
* Whether this entity should be accessible on the frontend through the REST
* server.
*
* If this is false, any request from the client that attempts to use this
* entity will fail.
*/
static restEnabled: boolean;
/**
* Properties that will not be searchable from the frontend. If the frontend
* includes any of these properties in any of their clauses, they will be
* filtered out before the search is executed.
*/
static searchRestrictedData: string[];
/**
* Properties that can only be modified by server side code. They will still
* be visible on the frontend, unlike $privateData, but any changes to them
* that come from the frontend will be ignored.
*
* In addition to what's listed here, all of the access control properties
* will be included when Tilmeld is being used. These are:
*
* - acUser
* - acGroup
* - acOther
* - acRead
* - acWrite
* - acFull
* - user
* - group
*
* You should modify these through client enabled methods or the $save method
* instead, for safety.
*/
protected $protectedData: string[];
/**
* If this is defined, then it lists the only properties that will be
* accepted from incoming JSON. Any other properties will be ignored.
*
* If you use an allowlist, you don't need to use protectedData, since you
* can simply leave those entries out of allowlistData.
*/
protected $allowlistData?: string[];
/**
* Tags that can only be added/removed by server side code. They will still be
* visible on the frontend, but any changes to them that come from the
* frontend will be ignored.
*/
protected $protectedTags: string[];
/**
* If this is defined, then it lists the only tags that will be accepted from
* incoming JSON. Any other tags will be ignored.
*/
protected $allowlistTags?: string[];
/**
* The names of methods allowed to be called by the frontend with serverCall.
*/
protected $clientEnabledMethods: string[];
/**
* The names of static methods allowed to be called by the frontend with
* serverCallStatic.
*/
static clientEnabledStaticMethods: string[];
/**
* Whether to use "skipAc" when accessing entity references.
*/
private $skipAc;
/**
* The AC properties' values when the entity was loaded.
*/
private $originalAcValues;
/**
* This is used to hold a generated GUID for a new entity.
*/
private $guaranteedGUID;
/**
* Alter the options for a query for this entity.
*
* @param options The current options.
* @returns The altered options.
*/
static alterOptions?<T extends Options>(options: T): T;
/**
* Initialize an entity.
*/
constructor(..._rest: any[]);
/**
* Create or retrieve a new entity instance.
*
* Note that this will always return an entity, even if the GUID is not found.
*
* @param guid An optional GUID to retrieve.
*/
static factory<E extends Entity>(this: {
new (): E;
}, guid?: string): Promise<E & EntityDataType<E>>;
/**
* Create a new entity instance.
*/
static factorySync<E extends Entity>(this: {
new (): E;
}): E & EntityDataType<E>;
/**
* Create a new sleeping reference instance.
*
* Sleeping references won't retrieve their data from the database until they
* are readied with `$wake()` or a parent's `$wakeAll()`.
*
* @param reference The Nymph Entity Reference to use to wake.
* @returns The new instance.
*/
static factoryReference<E extends Entity>(this: {
new (): E;
}, reference: EntityReference): E & EntityDataType<E>;
/**
* Get an array of strings that **must** be unique across the current etype.
*
* When you try to save another entity with any of the same unique strings,
* Nymph will throw an error.
*
* The default implementation of this static method instantiates the entity,
* assigns all of the given data, then calls `$getUniques` and returns its
* output. This can have a performance impact if a lot of extra processing
* happens during any of these steps. You can override this method to
* calculate the unique strings faster, but you must return the same strings
* that would be returned by `$getUniques`.
*
* @returns Resolves to an array of entity's unique constraint strings.
*/
static getUniques({ guid, cdate, mdate, tags, data, sdata, }: {
guid?: string;
cdate?: number;
mdate?: number;
tags: string[];
data: EntityData;
sdata?: SerializedEntityData;
}): Promise<string[]>;
toJSON(): EntityReference | EntityJson | null;
$getGuaranteedGUID(): string;
$addTag(...tags: string[]): void;
$arraySearch(array: any[], strict?: boolean): number;
$clearCache(): void;
$getClientEnabledMethods(): string[];
$delete(): Promise<boolean>;
$equals(object: any): boolean;
$getData(includeSData?: boolean, referenceOnlyExisting?: boolean): any;
$getSData(): SerializedEntityData;
$getUniques(): Promise<string[]>;
$getOriginalAcValues(): ACProperties;
$getCurrentAcValues(): ACProperties;
$getAcUid(): any;
$getAcGid(): any;
$getAcReadIds(): (string | null)[] | null;
$getAcWriteIds(): (string | null)[] | null;
$getAcFullIds(): (string | null)[] | null;
$getValidatable(): any;
$getTags(): string[];
$hasTag(...tags: string[]): boolean;
$inArray(array: any[], strict?: boolean): boolean;
$is(object: any): boolean;
$jsonAcceptData(input: EntityJson, allowConflict?: boolean): void;
$jsonAcceptPatch(patch: EntityPatch, allowConflict?: boolean): void;
$putData(data: EntityData, sdata?: SerializedEntityData, _source?: 'server'): void;
/**
* Set up a sleeping reference.
* @param array $reference The reference to use to wake.
*/
protected $referenceSleep(reference: EntityReference): void;
/**
* Check if this is a sleeping reference and throw an error if so.
*/
protected $check(): void;
/**
* Check if this is a sleeping reference.
*/
$asleep(): boolean;
/**
* Wake from a sleeping reference.
*/
$wake(): Promise<Entity<T>>;
$wakeAll(level?: number): Promise<Entity<T>>;
$refresh(): Promise<boolean | 0>;
$removeTag(...tags: string[]): void;
$save(): Promise<boolean>;
$toReference(existingOnly?: boolean): this | EntityReference;
$useSkipAc(skipAc: boolean): void;
}