@nymphjs/nymph
Version:
Nymph.js - Nymph ORM
502 lines (501 loc) • 25.2 kB
TypeScript
import { Config } from './conf/index.js';
import type { NymphDriver } from './driver/index.js';
import Entity, { type EntityInstanceType, type EntityObjectType } from './Entity.js';
import type { EntityConstructor, EntityInterface } from './Entity.types.js';
import type { Selector, Options, NymphConnectCallback, NymphDisconnectCallback, NymphBeforeGetEntityCallback, NymphBeforeGetEntitiesCallback, NymphBeforeSaveEntityCallback, NymphAfterSaveEntityCallback, NymphFailedSaveEntityCallback, NymphBeforeDeleteEntityCallback, NymphAfterDeleteEntityCallback, NymphFailedDeleteEntityCallback, NymphBeforeDeleteEntityByIDCallback, NymphAfterDeleteEntityByIDCallback, NymphFailedDeleteEntityByIDCallback, NymphBeforeNewUIDCallback, NymphAfterNewUIDCallback, NymphFailedNewUIDCallback, NymphBeforeSetUIDCallback, NymphAfterSetUIDCallback, NymphFailedSetUIDCallback, NymphBeforeRenameUIDCallback, NymphAfterRenameUIDCallback, NymphFailedRenameUIDCallback, NymphBeforeDeleteUIDCallback, NymphAfterDeleteUIDCallback, NymphFailedDeleteUIDCallback, NymphBeforeStartTransactionCallback, NymphAfterStartTransactionCallback, NymphBeforeCommitTransactionCallback, NymphAfterCommitTransactionCallback, NymphBeforeRollbackTransactionCallback, NymphAfterRollbackTransactionCallback, NymphEventType, NymphQueryCallback, FormattedSelector, TilmeldInterface } from './Nymph.types.js';
/**
* An object relational mapper for Node.js.
*
* Written by Hunter Perrin for SciActive.
*
* @author Hunter Perrin <hperrin@gmail.com>
* @copyright SciActive Inc
* @see http://nymph.io/
*/
export default class Nymph {
/**
* The Nymph config.
*/
config: Config;
/**
* The Nymph instance that this one was cloned from, or null if it's not a
* clone.
*/
parent: Nymph | null;
/**
* The Nymph database driver.
*/
driver: NymphDriver;
/**
* An optional Tilmeld user/group manager instance.
*/
tilmeld?: TilmeldInterface;
/**
* A simple map of names to Entity classes.
*/
private entityClasses;
/**
* The entity class for this instance of Nymph.
*/
Entity: typeof Entity;
private connectCallbacks;
private disconnectCallbacks;
private queryCallbacks;
private beforeGetEntityCallbacks;
private beforeGetEntitiesCallbacks;
private beforeSaveEntityCallbacks;
private afterSaveEntityCallbacks;
private failedSaveEntityCallbacks;
private beforeDeleteEntityCallbacks;
private afterDeleteEntityCallbacks;
private failedDeleteEntityCallbacks;
private beforeDeleteEntityByIDCallbacks;
private afterDeleteEntityByIDCallbacks;
private failedDeleteEntityByIDCallbacks;
private beforeNewUIDCallbacks;
private afterNewUIDCallbacks;
private failedNewUIDCallbacks;
private beforeSetUIDCallbacks;
private afterSetUIDCallbacks;
private failedSetUIDCallbacks;
private beforeRenameUIDCallbacks;
private afterRenameUIDCallbacks;
private failedRenameUIDCallbacks;
private beforeDeleteUIDCallbacks;
private afterDeleteUIDCallbacks;
private failedDeleteUIDCallbacks;
private beforeStartTransactionCallbacks;
private afterStartTransactionCallbacks;
private beforeCommitTransactionCallbacks;
private afterCommitTransactionCallbacks;
private beforeRollbackTransactionCallbacks;
private afterRollbackTransactionCallbacks;
/**
* Initialize Nymph.
*
* @param config The Nymph configuration.
* @param driver The Nymph database driver.
* @param tilmeld The Tilmeld user/group manager instance, if you want to use it.
* @param parent Used internally by Nymph. Don't set this.
*/
constructor(config: Partial<Config>, driver: NymphDriver, tilmeld?: TilmeldInterface, parent?: Nymph);
/**
* Add your class to this instance.
*
* This will create a class that extends your class within this instance of
* Nymph and return it. You can then use this class's constructor and methods,
* which will use this instance of Nymph.
*
* Because this creates a subclass, don't use the class
* returned from `getEntityClass` to check with `instanceof`.
*/
addEntityClass<T extends EntityConstructor>(EntityClass: T): T;
/**
* Get the class that uses the specified class name.
*/
getEntityClass(className: string): EntityConstructor;
getEntityClass<T extends EntityConstructor>(className: T): T;
/**
* Get the class that uses the specified etype.
*
* Note that it is fine, though unusual, for two classes to use the same
* etype. However, this can lead to very hard to diagnose bugs, so is
* generally discouraged.
*/
getEntityClassByEtype(etype: string): EntityConstructor;
/**
* Get a clone of this instance with cloned classes and event listeners.
*
* @returns A clone of this instance.
*/
clone(): Nymph;
/**
* Connect to the database.
*
* @returns Whether the instance is connected to the database.
*/
connect(): Promise<boolean>;
/**
* Disconnect from the database.
*
* @returns Whether the instance is connected to the database.
*/
disconnect(): Promise<boolean>;
/**
* Run all the query callbacks on a query.
*/
runQueryCallbacks(options: Options, selectors: FormattedSelector[]): void;
/**
* Start an atomic transaction and returns a new instance of Nymph.
*
* All proceeding changes using this new instance will wait to be written to
* the database's permanent storage until commit() is called. You can also
* undo all the changes since this function ran with rollback().
*
* Transactions will nest as long as every name is unique. Internally, Nymph
* uses names prefixed with "nymph-".
*
* @returns A new instance of Nymph that should be used for the transaction.
*/
startTransaction(name: string): Promise<Nymph>;
/**
* Commit the named transaction.
*
* After this is called, the transaction instance should be discarded.
*
* @returns True on success, false on failure.
*/
commit(name: string): Promise<boolean>;
/**
* Rollback the named transaction.
*
* After this is called, the transaction instance should be discarded.
*
* @returns True on success, false on failure.
*/
rollback(name: string): Promise<boolean>;
/**
* Check if there is any open transaction.
*
* @returns True if there is a transaction.
*/
inTransaction(): Promise<boolean>;
/**
* Increment or create a unique ID and return the new value.
*
* Unique IDs, or UIDs are similar to GUIDs, but numeric and sequential.
*
* A UID can be used to identify an object when the GUID doesn't suffice. On
* a system where a new entity is created many times per second, referring
* to something by its GUID may be unintuitive. However, the component
* designer is responsible for assigning UIDs to the component's entities.
* Beware that if a UID is incremented for an entity, and the entity cannot
* be saved, there is no safe, and therefore, no recommended way to
* decrement the UID back to its previous value.
*
* If newUID() is passed the name of a UID which does not exist yet, one
* will be created with that name, and assigned the value 1. If the UID
* already exists, its value will be incremented. The new value will be
* returned.
*
* @param name The UID's name.
* @returns The UID's new value, or null on failure.
*/
newUID(name: string): Promise<number | null>;
/**
* Get the current value of a unique ID.
* @param name The UID's name.
* @returns The UID's value, or null on failure and if it doesn't exist.
*/
getUID(name: string): Promise<number | null>;
/**
* Set the value of a UID.
*
* @param name The UID's name.
* @param value The value.
* @returns True on success, false on failure.
*/
setUID(name: string, value: number): Promise<boolean>;
/**
* Delete a unique ID.
*
* @param name The UID's name.
* @returns True on success, false on failure.
*/
deleteUID(name: string): Promise<boolean>;
/**
* Rename a unique ID.
*
* @param oldName The old name.
* @param newName The new name.
* @returns True on success, false on failure.
*/
renameUID(oldName: string, newName: string): Promise<boolean>;
/**
* Save an entity to the database.
*
* If the entity has never been saved (has no GUID), a variable "cdate"
* is set on it with the current Unix timestamp.
*
* The variable "mdate" is set to the current Unix timestamp.
*
* @param entity The entity.
* @returns True on success, false on failure.
*/
saveEntity(entity: EntityInterface): Promise<boolean>;
/**
* Get an array of entities.
*
* `options` is an object, which contains any of the following settings:
*
* - class - The class to create each entity with.
* - limit - The limit of entities to be returned.
* - offset - The offset from the oldest matching entity to start retrieving.
* - reverse - If true, entities will be retrieved from newest to oldest.
* Therefore, offset will be from the newest entity.
* - sort - How to sort the entities. Accepts "cdate", "mdate", or the name of
* a top-level property. The method of sorting properties other than cdate
* and mdate is driver dependent. The only hard rule is that numbers should
* be sorted numerically (2 before 10). Defaults to "cdate".
* - return - What to return. "entity", "object", "guid", or "count". Defaults
* to "entity".
* - source - Will be 'client' if the query came from a REST call.
* - skipCache - If true, Nymph will skip the cache and retrieve the entity
* from the DB.
* - skipAc - If true, Tilmeld will not filter returned entities according to
* access controls. (If Tilmeld is installed.) (This is *always* set to
* false by the REST server.)
*
* If a class is specified, it must have a factory() static method that
* returns a new instance.
*
* Selectors are objects. Any amount of selectors can be provided. Empty
* selectors will be ignored. The `type` property of a selector is required
* and can be one of the following strings:
*
* - & - (and) All values in the selector must be true.
* - | - (or) At least one value in the selector must be true.
* - !& - (not and) All values in the selector must be false.
* - !| - (not or) At least one value in the selector must be false.
*
* The rest of the properties in the selectors are called selector clauses,
* which can be any of the following (in the form selector.name = value, or
* selector.name = [value1, value2,...]):
*
* - guid - A GUID. True if the entity's GUID is equal.
* - tag - A tag. True if the entity has the tag.
* - defined - A name. True if the named property exists.
* - truthy - A name. True if the named property is defined and truthy.
* - equal - An array with a name, then value. True if the named property is
* defined and equal (their JSON strings are identical).
* - contain - An array with a name, then value. True if the named property
* contains the value (its JSON string is found within the property's JSON
* string).
* - match - An array with a name, then regular expression. True if the
* named property matches. Uses POSIX RegExp. Case sensitive. Must *not* be
* surrounded by any delimiters.
* - imatch - An array with a name, then regular expression. True if the
* named property matches. Uses POSIX RegExp. Case insensitive. Must *not*
* be surrounded by any delimiters.
* - like - An array with a name, then pattern. True if the named property
* matches. Uses % for variable length wildcard and _ for single character
* wildcard. Case sensitive.
* - ilike - An array with a name, then pattern. True if the named property
* matches. Uses % for variable length wildcard and _ for single character
* wildcard. Case insensitive.
* - gt - An array with a name, then value. True if the named property is
* greater than the value.
* - gte - An array with a name, then value. True if the named property is
* greater than or equal to the value.
* - lt - An array with a name, then value. True if the named property is
* less than the value.
* - lte - An array with a name, then value. True if the named property is
* less than or equal to the value.
* - ref - An array with a name, then either an entity, or a GUID. True if
* the named property is the entity or contains the entity.
* - qref - An array with a name, then a full query (including options) in an
* array. True if the named property is an entity that matches the query or
* contains an entity that matches the query.
* - selector - A selector. (Keep in mind, you can also use an array of these,
* just like any other clause.)
*
* These clauses can all be negated, by prefixing them with an exclamation
* point, such as "!truthy" to mean falsy (or undefined).
*
* Any clause that accepts an array of name and value can also accept a third
* element. If value is null and the third element is a string, the third
* element will be used with Locutus' strtotime function to set value to a
* relative timestamp. For example, the following selector will look for all
* entities that were created in the last day:
*
* ```
* {
* type: '&',
* gte: ['cdate', null, '-1 day']
* }
* ```
*
* Locutus' implementation: https://locutus.io/php/datetime/strtotime/
* PHP's documentation: https://www.php.net/manual/en/function.strtotime.php
*
* This example will retrieve the last two entities where:
*
* - It has 'person' tag.
* - spouse is defined.
* - gender is male and lname is Smith.
* - warnings is not an integer 0.
* - It has 'level1' and 'level2' tags, or it has 'access1' and 'access2'
* tags.
* - It has either 'employee' or 'manager' tag.
* - name is either Clark, James, Chris, Christopher, Jake, or Jacob.
* - If age is 22 or more, then pay is not greater than 8.
*
* ```
* const entities = Nymph.getEntities(
* { class: Entity, reverse: true, limit: 2 },
* {
* type: '&', // all must be true
* tag: 'person',
* defined: 'spouse',
* equal: [
* ['gender', 'male'],
* ['lname', 'Smith']
* ],
* '!equal': ['warnings', 0]
* },
* {
* type: '|', // at least one of the selectors in this must match
* selector: [
* {
* type: '&',
* tag: ['level1', 'level2']
* },
* {
* type: '&',
* tag: ['access1', 'access2']
* }
* ]
* },
* {
* type: '|', // at least one must be true
* tag: ['employee', 'manager']
* },
* {
* type: '|',
* equal: [
* ['name', 'Clark'],
* ['name', 'James']
* ],
* match: [
* ['name', 'Chris(topher)?'],
* ['name', 'Ja(ke|cob)']
* ]
* },
* {
* type: '!|', // at least one must be false
* gte: ['age', 22],
* gt: ['pay', 8]
* }
* );
* ```
*
* @param options The options.
* @param selectors Unlimited optional selectors to search for. If none are given, all entities are retrieved for the given options.
* @returns An array of entities or guids, or a count.
* @todo Use an asterisk to specify any variable.
*/
getEntities<T extends EntityConstructor = EntityConstructor>(options: Options<T> & {
return: 'count';
}, ...selectors: Selector[]): Promise<number>;
getEntities<T extends EntityConstructor = EntityConstructor>(options: Options<T> & {
return: 'guid';
}, ...selectors: Selector[]): Promise<string[]>;
getEntities<T extends EntityConstructor = EntityConstructor>(options: Options<T> & {
return: 'object';
}, ...selectors: Selector[]): Promise<EntityObjectType<T>[]>;
getEntities<T extends EntityConstructor = EntityConstructor>(options?: Options<T>, ...selectors: Selector[]): Promise<EntityInstanceType<T>[]>;
/**
* Get the first entity to match all options/selectors.
*
* options and selectors are the same as in getEntities().
*
* This function is equivalent to setting options.limit to 1 for
* getEntities(), except that it will return null if no entity is found.
* getEntities() would return an empty array.
*
* @param options The options.
* @param selectors Unlimited optional selectors to search for, or a single GUID. If none are given, all entities are searched for the given options.
* @returns An entity or guid, or null on failure or nothing found, or a number.
*/
getEntity<T extends EntityConstructor = EntityConstructor>(options: Options<T> & {
return: 'count';
}, ...selectors: Selector[]): Promise<number>;
getEntity<T extends EntityConstructor = EntityConstructor>(options: Options<T> & {
return: 'guid';
}, ...selectors: Selector[]): Promise<string | null>;
getEntity<T extends EntityConstructor = EntityConstructor>(options: Options<T> & {
return: 'object';
}, ...selectors: Selector[]): Promise<EntityObjectType<T> | null>;
getEntity<T extends EntityConstructor = EntityConstructor>(options: Options<T>, ...selectors: Selector[]): Promise<EntityInstanceType<T> | null>;
getEntity<T extends EntityConstructor = EntityConstructor>(options: Options<T> & {
return: 'guid';
}, guid: string): Promise<string | null>;
getEntity<T extends EntityConstructor = EntityConstructor>(options: Options<T> & {
return: 'object';
}, guid: string): Promise<EntityObjectType<T> | null>;
getEntity<T extends EntityConstructor = EntityConstructor>(options: Options<T>, guid: string): Promise<EntityInstanceType<T> | null>;
/**
* Delete an entity from the database.
*
* @param entity The entity.
* @returns True on success, false on failure.
*/
deleteEntity(entity: EntityInterface): Promise<boolean>;
/**
* Delete an entity from the database by its GUID.
*
* @param guid The entity's GUID.
* @param className The entity's class name.
* @returns True on success, false on failure.
*/
deleteEntityByID(guid: string, className?: string): Promise<boolean>;
/**
* Export entities to a local file.
*
* This is the file format:
*
* ```
* #nex2
* # The above line must be the first thing in the file.
* # Comments begin with #
* # And can have white space before them.
* # This defines a UID.
* <name/of/uid>[5]
* <another uid>[8000]
* # For UIDs, the name is in angle brackets (<>) and the value follows
* # in square brackets ([]).
* # This starts a new entity.
* {1234abcd}<etype>[tag,list,with,commas]
* # For entities, the GUID is in curly brackets ({}), then the etype in
* # angle brackets, then the comma separated tag list follows in square
* # brackets ([]).
* # Properties are stored like this:
* # propname=JSON.stringify(value)
* abilities=["system/admin"]
* groups=[]
* inheritAbilities=false
* name="admin"
* # White space before/after "=" and at beginning/end of line is ignored.
* username = "admin"
* {2}<etype>[tag,list]
* another="This is another entity."
* newline="\n"
* ```
*
* @param filename The file to export to.
* @returns True on success, false on failure.
*/
export(filename: string): Promise<boolean>;
/**
* Export entities to the console.
*
* @returns True on success, false on failure.
*/
exportPrint(): Promise<boolean>;
/**
* Import entities from a file.
*
* @param filename The file to import from.
* @returns True on success, false on failure.
*/
import(filename: string): Promise<boolean>;
/**
* Detect whether the database needs to be migrated.
*
* If true, the database should be exported with an old version of Nymph, then
* imported into a fresh database with this version.
*/
needsMigration(): Promise<boolean>;
on<T extends NymphEventType>(event: T, callback: T extends 'connect' ? NymphConnectCallback : T extends 'disconnect' ? NymphDisconnectCallback : T extends 'query' ? NymphQueryCallback : T extends 'beforeGetEntity' ? NymphBeforeGetEntityCallback : T extends 'beforeGetEntities' ? NymphBeforeGetEntitiesCallback : T extends 'beforeSaveEntity' ? NymphBeforeSaveEntityCallback : T extends 'afterSaveEntity' ? NymphAfterSaveEntityCallback : T extends 'failedSaveEntity' ? NymphFailedSaveEntityCallback : T extends 'beforeDeleteEntity' ? NymphBeforeDeleteEntityCallback : T extends 'afterDeleteEntity' ? NymphAfterDeleteEntityCallback : T extends 'failedDeleteEntity' ? NymphFailedDeleteEntityCallback : T extends 'beforeDeleteEntityByID' ? NymphBeforeDeleteEntityByIDCallback : T extends 'afterDeleteEntityByID' ? NymphAfterDeleteEntityByIDCallback : T extends 'failedDeleteEntityByID' ? NymphFailedDeleteEntityByIDCallback : T extends 'beforeNewUID' ? NymphBeforeNewUIDCallback : T extends 'afterNewUID' ? NymphAfterNewUIDCallback : T extends 'failedNewUID' ? NymphFailedNewUIDCallback : T extends 'beforeSetUID' ? NymphBeforeSetUIDCallback : T extends 'afterSetUID' ? NymphAfterSetUIDCallback : T extends 'failedSetUID' ? NymphFailedSetUIDCallback : T extends 'beforeRenameUID' ? NymphBeforeRenameUIDCallback : T extends 'afterRenameUID' ? NymphAfterRenameUIDCallback : T extends 'failedRenameUID' ? NymphFailedRenameUIDCallback : T extends 'beforeDeleteUID' ? NymphBeforeDeleteUIDCallback : T extends 'afterDeleteUID' ? NymphAfterDeleteUIDCallback : T extends 'failedDeleteUID' ? NymphFailedDeleteUIDCallback : T extends 'beforeStartTransaction' ? NymphBeforeStartTransactionCallback : T extends 'afterStartTransaction' ? NymphAfterStartTransactionCallback : T extends 'beforeCommitTransaction' ? NymphBeforeCommitTransactionCallback : T extends 'afterCommitTransaction' ? NymphAfterCommitTransactionCallback : T extends 'beforeRollbackTransaction' ? NymphBeforeRollbackTransactionCallback : T extends 'afterRollbackTransaction' ? NymphAfterRollbackTransactionCallback : never): () => boolean;
off<T extends NymphEventType>(event: T, callback: T extends 'connect' ? NymphConnectCallback : T extends 'disconnect' ? NymphDisconnectCallback : T extends 'query' ? NymphQueryCallback : T extends 'beforeGetEntity' ? NymphBeforeGetEntityCallback : T extends 'beforeGetEntities' ? NymphBeforeGetEntitiesCallback : T extends 'beforeSaveEntity' ? NymphBeforeSaveEntityCallback : T extends 'afterSaveEntity' ? NymphAfterSaveEntityCallback : T extends 'failedSaveEntity' ? NymphFailedSaveEntityCallback : T extends 'beforeDeleteEntity' ? NymphBeforeDeleteEntityCallback : T extends 'afterDeleteEntity' ? NymphAfterDeleteEntityCallback : T extends 'failedDeleteEntity' ? NymphFailedDeleteEntityCallback : T extends 'beforeDeleteEntityByID' ? NymphBeforeDeleteEntityByIDCallback : T extends 'afterDeleteEntityByID' ? NymphAfterDeleteEntityByIDCallback : T extends 'failedDeleteEntityByID' ? NymphFailedDeleteEntityByIDCallback : T extends 'beforeNewUID' ? NymphBeforeNewUIDCallback : T extends 'afterNewUID' ? NymphAfterNewUIDCallback : T extends 'failedNewUID' ? NymphFailedNewUIDCallback : T extends 'beforeSetUID' ? NymphBeforeSetUIDCallback : T extends 'afterSetUID' ? NymphAfterSetUIDCallback : T extends 'failedSetUID' ? NymphFailedSetUIDCallback : T extends 'beforeRenameUID' ? NymphBeforeRenameUIDCallback : T extends 'afterRenameUID' ? NymphAfterRenameUIDCallback : T extends 'failedRenameUID' ? NymphFailedRenameUIDCallback : T extends 'beforeDeleteUID' ? NymphBeforeDeleteUIDCallback : T extends 'afterDeleteUID' ? NymphAfterDeleteUIDCallback : T extends 'failedDeleteUID' ? NymphFailedDeleteUIDCallback : T extends 'beforeStartTransaction' ? NymphBeforeStartTransactionCallback : T extends 'afterStartTransaction' ? NymphAfterStartTransactionCallback : T extends 'beforeCommitTransaction' ? NymphBeforeCommitTransactionCallback : T extends 'afterCommitTransaction' ? NymphAfterCommitTransactionCallback : T extends 'beforeRollbackTransaction' ? NymphBeforeRollbackTransactionCallback : T extends 'afterRollbackTransaction' ? NymphAfterRollbackTransactionCallback : never): boolean;
}