UNPKG

@nymphjs/nymph

Version:

Nymph.js - Nymph ORM

326 lines (325 loc) 12.5 kB
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; }