UNPKG

@nymphjs/nymph

Version:

Nymph.js - Nymph ORM

502 lines (501 loc) 25.2 kB
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; }