UNPKG

lux-framework

Version:

Build scalable, Node.js-powered REST APIs with almost no code.

1,494 lines (1,367 loc) 36.7 kB
// @flow import { pluralize } from 'inflection'; import Query from '../query'; import ChangeSet from '../change-set'; import { updateRelationship } from '../relationship'; import { createTransactionResultProxy, createStaticTransactionProxy, createInstanceTransactionProxy } from '../transaction'; import pick from '../../../utils/pick'; import underscore from '../../../utils/underscore'; import { compose } from '../../../utils/compose'; import { map as diffMap } from '../../../utils/diff'; import mapToObject from '../../../utils/map-to-object'; import type Logger from '../../logger'; import type Database from '../../database'; import type Serializer from '../../serializer'; /* eslint-disable no-duplicate-imports */ import type { Relationship$opts } from '../relationship'; import type { Transaction$ResultProxy } from '../transaction'; /* eslint-enable no-duplicate-imports */ import { create, update, destroy, createRunner } from './utils/persistence'; import initializeClass from './initialize-class'; import validate from './utils/validate'; import runHooks from './utils/run-hooks'; import type { Model$Hooks } from './interfaces'; /** * @class Model * @public */ class Model { /** * The name of the corresponding database table for a `Model` instance's * constructor. * * @property tableName * @type {String} * @public */ tableName: string; /** * The canonical name of a `Model`'s constructor. * * @property modelName * @type {String} * @public */ modelName: string; /** * The name of the API resource a `Model` instance's constructor represents. * * @property resourceName * @type {String} * @public */ resourceName: string; /** * A timestamp representing when the Model instance was created. * * @property createdAt * @type {Date} * @public */ createdAt: Date; /** * A timestamp representing the last time the Model instance was updated. * * @property updatedAt * @type {Date} * @public */ updatedAt: Date; /** * @property initialized * @type {Boolean} * @private */ initialized: boolean; /** * @property rawColumnData * @type {Object} * @private */ rawColumnData: Object; /** * @property isModelInstance * @type {Boolean} * @private */ isModelInstance: boolean; /** * @property prevAssociations * @type {Set} * @private */ prevAssociations: Set<Model>; /** * @property changeSets * @type {Array} * @private */ changeSets: Array<ChangeSet>; /** * An object where you declare `hasOne` relationships. * * When declaring a relationship you must specify the inverse of the * relationship. * * ```javascript * class User extends Model { * static hasOne = { * profile: { * inverse: 'user' * // The line above lets Lux know that this relationship is accessible * // on profile instances via `profile.user`. * } * }; * } * * class Profile extends Model { * static belongsTo = { * user: { * inverse: 'profile' * // The line above lets Lux know that this relationship is accessible * // on user instances via `user.profile`. * } * }; * } * ``` * * If the name of the model is different than the key of the relationship, you * must specify it in the relationship object. * * ```javascript * class Profile extends Model { * static belongsTo = { * owner: { * inverse: 'profile', * model: 'user' * // The line above lets Lux know that this is a relationship with the * // `User` model and not a non-existent `Owner` model. * } * }; * } * ``` * * @property hasOne * @type {Object} * @default {} * @static * @public */ static hasOne: Object; /** * An object where you declare `hasMany` relationships. * * When declaring a relationship you must specify the inverse of the * relationship. * * ```javascript * class Author extends Model { * static hasMany = { * books: { * inverse: 'author' * // The line above lets Lux know that this relationship is accessible * // on book instances via `book.author`. * } * }; * } * * class Book extends Model { * static belongsTo = { * author: { * inverse: 'books' * // The line above lets Lux know that this relationship is accessible * // on author instances via `author.books`. * } * }; * } * ``` * * If the name of the model is different than the key of the relationship, you * must specify it in the relationship object. * * ```javascript * class Author extends Model { * static hasMany = { * publications: { * inverse: 'author', * model: 'book' * // The line above lets Lux know that this is a relationship with the * // `Book` model and not a non-existent `Publication` model. * } * }; * } * ``` * * ##### Many to Many * * In the examples above there is only one owner of relationship. Sometimes we * need to express a many to many relationship. Typically in relational * databases, this is done with a join table. When declaring a many to many * relationship that uses a join table, you must specify the join model. * * ```javascript * class Categorization extends Model { * static belongsTo = { * tag: { * inverse: 'categorization' * }, * post: { * inverse: 'categorization' * } * } * } * * class Tag extends Model { * static hasMany = { * posts: { * inverse: 'tags', * through: 'categorizations' * } * }; * } * * class Post extends Model { * static hasMany = { * tags: { * inverse: 'posts', * through: 'categorizations' * } * }; * } * ``` * * @property hasMany * @type {Object} * @default {} * @static * @public */ static hasMany: Object; /** * An object where you declare `belongsTo` relationships. * * When declaring a relationship you must specify the inverse of the * relationship. * * ```javascript * class Book extends Model { * static belongsTo = { * author: { * inverse: 'books' * // The line above lets Lux know that this relationship is accessible * // on author instances via `author.books`. * } * }; * } * * class Author extends Model { * static hasMany = { * books: { * inverse: 'book' * // The line above lets Lux know that this relationship is accessible * // on book instances via `book.author`. * } * }; * } * ``` * * If the name of the model is different than the key of the relationship, you * must specify it in the relationship object. * * ```javascript * class Book extends Model { * static belongsTo = { * writer: { * inverse: 'books', * model: 'author' * // The line above lets Lux know that this is a relationship with the * // `Author` model and not a non-existent `Writer` model. * } * }; * } * ``` * * Sometimes our foreign keys in the database do not follow conventions (i.e * `author_id`). You have the option to manually specify foreign keys when a * situation like this occurs. * * ```javascript * class Book extends Model { * static belongsTo = { * author: { * inverse: 'books', * foreignKey: 'SoMe_UnCoNvEnTiOnAl_FoReIgN_KeY' * } * }; * } * ``` * * @property belongsTo * @type {Object} * @default {} * @static * @public */ static belongsTo: Object; /** * An object where you declare validations for an instance's attributes. * * Before a model instance is saved, validations declared in this block are * executed. To declare a validation for a model attribute, simply add the * attribute name as a key to the validates object. The value for the * attribute key should be a function that takes a single argument (the value * to validate against) and return a boolean value represent whether or not * the attribute is valid. * * ```javascript * class User extends Model { * static validates { * username: value => /^\w{2,30}$/.test(value), * password: value => String(value).length >= 8 * }; * } * ``` * * In the spirit of have a small api surface area, Lux provides no validation * helper functions. You can roll your own helpers with or use one of the many * excellent validation libraries like [validator](https://goo.gl/LWaHBB). * * ```javascript * import { isEmail } from 'validator'; * * class User extends Model { * static validates { * email: isEmail * }; * } * ``` * * @property validates * @type {Object} * @default {} * @static * @public */ static validates: Object; /** * An object where you declare custom query scopes for the model. * * Scopes allow you to DRY up query logic by chaining custom set's of queries * with built-in query method such as `where`, `not`, `page`, etc. To declare * a scope, add it as a method on the scopes object. * * ```javascript * class Post extends Model { * static hasMany = { * tags: { * inverse: 'posts' * }, * comments: { * inverse: 'post' * } * }; * * static belongsTo = { * user: { * inverse: 'posts' * } * }; * * static scopes = { * isPublic() { * return this.where({ * isPublic: true * }); * }, * * byUser(user) { * return this.where({ * userId: user.id * }); * }, * * withEverything() { * return this.includes('tags', 'user', 'comments'); * } * }; * } * ``` * * Given the scopes declared in the example above, here is how we could return * all the public posts with relationships eager loaded for the user with the * id of 1. * * ```javascript * const user = await User.find(1); * * return Post * .byUser(user) * .isPublic() * .withEverything(); * ``` * * Since scopes can be chained with built-in query methods, we can easily * paginate this collection. * * ```javascript * const user = await User.find(1); * * return Post * .byUser(user) * .isPublic() * .withEverything() * .page(1); * ``` * * @property scopes * @type {Object} * @default {} * @static * @public */ static scopes: Object; /** * An object where you declare hooks to execute at certain times in a model * instance's lifecycle. * * There are many lifecycle hooks that are executed through out a model * instance's lifetime. The have many use cases such as sanitization of * attributes, creating dependent relationships, hashing passwords, and much * more. * * ##### Execution Order * * When creating a record. * * 1. beforeValidation * 2. afterValidation * 3. beforeCreate * 4. beforeSave * 5. afterCreate * 6. afterSave * * When updating a record. * * 1. beforeValidation * 2. afterValidation * 3. beforeUpdate * 4. beforeSave * 5. afterUpdate * 6. afterSave * * When deleting a record. * * 1. beforeDestroy * 2. afterDestroy * * ##### Anatomy * * Hooks are async functions that are called with two arguments. The first * argument is the record that the hook applies to and the second argument is * the transaction object relevant to the method from which the hook was * called. * * The only time you will need to use the transaction object is if you are * creating, updating, or deleting different record(s) within the hook. Using * the transaction object when modifying the database in a hook ensures that * any modifications made within the hook will be rolled back if the function * that initiated the transaction fails. * * ```javascript * import Notification from 'app/models/notification'; * * class Comment extends Model { * static belongsTo = { * post: { * inverse: 'comments' * }, * user: { * inverse: 'comments' * } * }; * * static hooks = { * async afterCreate(comment, trx) { * let [post, commenter] = await Promise.all([ * comment.post, * comment.user * ]); * * const commentee = await post.user; * * post = post.title; * commenter = commenter.name; * * // Calling .transacting(trx) prevents the commentee from getting a * // notification if the comment fails to be persisted in the database. * await Notification * .transacting(trx) * .create({ * user: commentee, * message: `${commenter} commented on your post "${post}"` * }); * }, * * async afterSave() { * // Good thing you called transacting in afterCreate. * throw new Error('Fatal Error'); * } * }; * } * ``` * * @property hooks * @type {Object} * @default {} * @static * @public */ static hooks: Model$Hooks; /** * A reference to the application's logger. * * @property logger * @type {Logger} * @static * @public */ static logger: Logger; /** * The name of the corresponding database table for the model. * * @property tableName * @type {String} * @static * @public */ static tableName: string; /** * The canonical name of the model. * * @property modelName * @type {String} * @static * @public */ static modelName: string; /** * The name of the resource the model represents. * * @property resourceName * @type {String} * @static * @public */ static resourceName: string; /** * The column name to use for a model's primary key. * * @property primaryKey * @type {String} * @default 'id' * @static * @public */ static primaryKey: string = 'id'; /** * @property table * @type {Function} * @static * @private */ static table: () => Object; /** * @property store * @type {Database} * @static * @private */ static store: Database; /** * @property initialized * @type {Boolean} * @static * @private */ static initialized: boolean; /** * @property serializer * @type {Serializer} * @static * @private */ static serializer: Serializer<this>; /** * @property attributes * @type {Object} * @static * @private */ static attributes: Object; /** * @property attributeNames * @type {Array} * @static * @private */ static attributeNames: Array<string>; /** * @property relationships * @type {Object} * @static * @private */ static relationships: Object; /** * @property relationshipNames * @type {Array} * @static * @private */ static relationshipNames: Array<string>; constructor(attrs: Object = {}, initialize: boolean = true): this { Object.defineProperties(this, { changeSets: { value: [new ChangeSet()], writable: false, enumerable: false, configurable: false }, rawColumnData: { value: attrs, writable: false, enumerable: false, configurable: false }, prevAssociations: { value: new Set(), writable: false, enumerable: false, configurable: false } }); const { constructor: { attributeNames, relationshipNames } } = this; const props = pick(attrs, ...attributeNames.concat(relationshipNames)); Object.assign(this, props); if (initialize) { Reflect.defineProperty(this, 'initialized', { value: true, writable: false, enumerable: false, configurable: false }); } return this; } /** * Indicates if the model is new. * * ```javascript * import Post from 'app/models/post'; * * let post = new Post({ * body: '', * title: 'New Post', * isPublic: false * }); * * post.isNew; * // => true * * Post.create({ * body: '', * title: 'New Post', * isPublic: false * }).then(post => { * post.isNew; * // => false; * }); * ``` * * @property isNew * @type {Boolean} * @public */ get isNew(): boolean { return !this.persistedChangeSet; } /** * Indicates if the model is dirty. * * ```javascript * import Post from 'app/models/post'; * * Post * .find(1) * .then(post => { * post.isDirty; * // => false * * post.isPublic = true; * * post.isDirty; * // => true * * return post.save(); * }) * .then(post => { * post.isDirty; * // => false * }); * ``` * * @property isDirty * @type {Boolean} * @public */ get isDirty(): boolean { return Boolean(this.dirtyProperties.size); } /** * Indicates if the model is persisted. * * ```javascript * import Post from 'app/models/post'; * * Post * .find(1) * .then(post => { * post.persisted; * // => true * * post.isPublic = true; * * post.persisted; * // => false * * return post.save(); * }) * .then(post => { * post.persisted; * // => true * }); * ``` * * @property persisted * @type {Boolean} * @public */ get persisted(): boolean { return !this.isNew && !this.isDirty; } /** * @property dirtyAttributes * @type {Map} * @public */ get dirtyAttributes(): Map<string, any> { const { dirtyProperties, constructor: { relationshipNames } } = this; Array .from(dirtyProperties.keys()) .forEach(key => { if (relationshipNames.indexOf(key) >= 0) { dirtyProperties.delete(key); } }); return dirtyProperties; } /** * @property dirtyRelationships * @type {Map} * @public */ get dirtyRelationships(): Map<string, any> { const { dirtyProperties, constructor: { attributeNames } } = this; Array .from(dirtyProperties.keys()) .forEach(key => { if (attributeNames.indexOf(key) >= 0) { dirtyProperties.delete(key); } }); return dirtyProperties; } /** * @property dirtyProperties * @type {Map} * @private */ get dirtyProperties(): Map<string, any> { const { currentChangeSet, persistedChangeSet } = this; if (!persistedChangeSet) { return new Map(currentChangeSet); } return diffMap(persistedChangeSet, currentChangeSet); } /** * @property currentChangeSet * @type {ChangeSet} * @private */ get currentChangeSet(): ChangeSet { return this.changeSets[0]; } /** * @property currentChangeSet * @type {void | ChangeSet} * @private */ get persistedChangeSet(): void | ChangeSet { return this.changeSets.find(({ isPersisted }) => isPersisted); } /** * Specify the transaction object to use for following save, update, or * destroy method calls. * * When you call a method like update or destroy, lux will create a * transaction and wrap the internals of the method and other downstream * method calls like model hooks within. In some edge cases it can be more * useful to manually initiate the transaction. Bulk updating or destroying * are good examples of this. When you manually begin a transaction, you can * call this method to specify the transaction object that you would like to * use for subsequent mutation methods (save, update, destroy, etc.) so lux * knows not to automatically begin a new transaction if/when a mutation * method is called. * * ```javascript * const post = await Post.first(); * * // This call to update uses the transaction that lux will initiate. * await post.update({ * // updates to post... * }); * * await post.transaction(trx => { * // This call to update uses the transaction that we created with the * // call to the transaction method. * return post * .transacting(trx) * .update({ * // updates to post... * }); * }); * ``` * * @method transacting * @param {Transaction} transaction - A transaction object to forward to save, * update, or destroy method calls. * @return {Model} - Returns a proxied version of `this` that delagates the * transaction param to subsquent save, update, or destroy method calls. * @public */ transacting(trx: Object): this { return createInstanceTransactionProxy(this, trx); } /** * Manually begin a new transaction. * * Most of the time, you don't need to start transactions yourself. However, * if you need to do something like implement bulk updating of related records * the transaction method can be useful. * * ```javascript * const post = await Post.first().include('user'); * const user = await post.user; * * await post.transaction(trx => { * return Promise.all([ * post.transacting(trx).update({ * // updates to post... * }), * user.transacting(trx).update({ * // updates to user... * }) * ]); * }); * ``` * * @method transaction * @param {Function} fn - The function used for executing the tranasction. * This function is called with a new transaction object as it's only argument * and is expected to return a promise. * @return {Promise} Resolves with the resolved value of the fn param. * @public */ transaction<T>(fn: (...args: Array<any>) => Promise<T>): Promise<T> { return this.constructor.transaction(fn); } /** * Persist any unsaved changes to the database. * * ```javascript * const post = await Post.first(); * * console.log(post.title, post.isDirty); * // => 'New Post' false * * post.title = 'How to Save a Lux Model'; * * console.log(post.title, post.isDirty); * // => 'How to Update a Lux Model' true * * await post.save(); * * console.log(post.title, post.isDirty); * // => 'How to Save a Lux Model' false * ``` * * @method save * @return {Promise} Resolves with `this`. * @public */ save(transaction?: Object): Promise<Transaction$ResultProxy<this, *>> { return this.update(mapToObject(this.dirtyProperties), transaction); } /** * Assign values to the instance and persist any changes to the database. * * ```javascript * const post = await Post.first(); * * console.log(post.title, post.isPublic, post.isDirty); * // => 'New Post' false false * * await post.update({ * title: 'How to Update a Lux Model', * isPublic: true * }); * * console.log(post.title, post.isPublic, post.isDirty); * // => 'How to Update a Lux Model' true false * ``` * * @method update * @param {Object} properties - An object containing key, value pairs of the * attributes and/or relationships you would like to assign to the instance. * @return {Promise} Resolves with `this`. * @public */ update( props: Object = {}, transaction?: Object ): Promise<Transaction$ResultProxy<this, *>> { const run = async (trx: Object) => { const { constructor: { hooks, logger } } = this; let statements = []; let promise = Promise.resolve([]); let hadDirtyAttrs = false; let hadDirtyAssoc = false; const associations = Object .keys(props) .filter(key => ( Boolean(this.constructor.relationshipFor(key)) )); Object.assign(this, props); if (associations.length) { hadDirtyAssoc = true; statements = associations.reduce((arr, key) => [ ...arr, ...updateRelationship(this, key, trx) ], []); } if (this.isDirty) { hadDirtyAttrs = true; await runHooks(this, trx, hooks.beforeValidation); validate(this); await runHooks(this, trx, hooks.afterValidation, hooks.beforeUpdate, hooks.beforeSave ); promise = update(this, trx); } await createRunner(logger, statements)(await promise); this.prevAssociations.clear(); this.currentChangeSet.persist(this.changeSets); if (hadDirtyAttrs) { await runHooks(this, trx, hooks.afterUpdate, hooks.afterSave ); } return createTransactionResultProxy(this, hadDirtyAttrs || hadDirtyAssoc); }; if (transaction) { return run(transaction); } return this.transaction(run); } /** * Permanently delete the instance from the database. * * @method destroy * @return {Promise} Resolves with `this`. * @public */ destroy(transaction?: Object): Promise<Transaction$ResultProxy<this, true>> { const run = async (trx: Object) => { const { constructor: { hooks, logger } } = this; await runHooks(this, trx, hooks.beforeDestroy); await createRunner(logger, [])(await destroy(this, trx)); await runHooks(this, trx, hooks.afterDestroy); return createTransactionResultProxy(this, true); }; if (transaction) { return run(transaction); } return this.transaction(run); } /** * Reload the record from the database. * * @method reload * @return {Promise} Resolves with `this`. * @public */ reload(): Promise<this> { if (this.isNew) { return Promise.resolve(this); } return this.constructor.find(this.getPrimaryKey()); } /** * Rollback attributes and relationships to the last known persisted set of * values. * * @method rollback * @return {Model} Returns `this`. * @public */ rollback(): this { const { persistedChangeSet } = this; if (persistedChangeSet && !this.currentChangeSet.isPersisted) { persistedChangeSet .applyTo(this) .persist(this.changeSets); } return this; } /** * @method getAttributes * @param {String} [...keys] - The keys of the properties to return. * @return {Object} An object containing keys that were passed in as agruments * and their associated values. * @private */ getAttributes(...keys: Array<string>): Object { return pick(this, ...keys); } /** * @method getPrimaryKey * @return {Number} The value of the primary key for the instance. * @private */ getPrimaryKey(): number { return Reflect.get(this, this.constructor.primaryKey); } /** * Create and persist a new instance of the model. * * @method create * @param {Object} properties - An object containing key, value pairs of the * attributes and/or relationships you would like to assign to the instance. * @return {Promise} Resolves with the newly created model. * @static * @public */ static create( props: Object = {}, transaction?: Object ): Promise<Transaction$ResultProxy<this, true>> { const run = async (trx: Object) => { const { hooks, logger, primaryKey } = this; const instance = Reflect.construct(this, [props, false]); let statements = []; const associations = Object .keys(props) .filter(key => ( Boolean(this.relationshipFor(key)) )); if (associations.length) { statements = associations.reduce((arr, key) => [ ...arr, ...updateRelationship(instance, key, trx) ], []); } await runHooks(instance, trx, hooks.beforeValidation); validate(instance); await runHooks(instance, trx, hooks.afterValidation, hooks.beforeCreate, hooks.beforeSave ); const runner = createRunner(logger, statements); const [[primaryKeyValue]] = await runner(await create(instance, trx)); Reflect.set(instance, primaryKey, primaryKeyValue); Reflect.set(instance.rawColumnData, primaryKey, primaryKeyValue); Reflect.defineProperty(instance, 'initialized', { value: true, writable: false, enumerable: false, configurable: false }); instance.currentChangeSet.persist(instance.changeSets); await runHooks(instance, trx, hooks.afterCreate, hooks.afterSave ); return createTransactionResultProxy(instance, true); }; if (transaction) { return run(transaction); } return this.transaction(run); } /** * Specify the transaction object to use for following save, update, or * destroy method calls. * * When you call a method like update or destroy, lux will create a * transaction and wrap the internals of the method and other downstream * method calls like model hooks within. In some edge cases it can be more * useful to manually initiate the transaction. Bulk updating or destroying * are good examples of this. When you manually begin a transaction, you can * call this method to specify the transaction object that you would like to * use for calls to the static create method so lux knows not to automatically * begin a new transaction if/when the static create method is called. * * ```javascript * // This call to create uses the transaction that lux will initiate. * await Post.create(); * * await Post.transaction(trx => { * // This call to create uses the transaction that we created with the * // call to the transaction method. * return Post * .transacting(trx) * .create(); * }); * ``` * * @method transacting * @param {Transaction} transaction - A transaction object to forward to * create method calls. * @return {Model} - Returns a proxied version of `this` that delagates the * transaction param to subsquent create method calls. * @static * @public */ static transacting(trx: Object): Class<this> { return createStaticTransactionProxy(this, trx); } /** * Manually begin a new transaction. * * Most of the time, you don't need to start transactions yourself. However, * the transaction method can be useful if you need to do something like * bulk creating records. * * ```javascript * await Post.transaction(trx => { * return Promise.all([ * Post.transacting(trx).create({ * // ...props * }), * Post.transacting(trx).create({ * // ...props * }) * ]); * }); * ``` * * @method transaction * @param {Function} fn - The function used for executing the tranasction. * This function is called with a new transaction object as it's only argument * and is expected to return a promise. * @return {Promise} Resolves with the resolved value of the fn param. * @static * @public */ static transaction<T>(fn: (...args: Array<any>) => Promise<T>): Promise<T> { return new Promise((resolve, reject) => { const { store: { connection } } = this; let result: T; connection .transaction(trx => { fn(trx) .then(data => { result = data; return trx.commit(); }) .catch(trx.rollback); }) .then(() => { resolve(result); }) .catch(err => { reject(err); }); }); } static all(): Query<Array<this>> { return new Query(this).all(); } static find(primaryKey: any): Query<this> { return new Query(this).find(primaryKey); } static page(num: number): Query<Array<this>> { return new Query(this).page(num); } static limit(amount: number): Query<Array<this>> { return new Query(this).limit(amount); } static offset(amount: number): Query<Array<this>> { return new Query(this).offset(amount); } static count(): Query<number> { return new Query(this).count(); } static order(attr: string, direction?: string): Query<Array<this>> { return new Query(this).order(attr, direction); } static where(conditions: Object): Query<Array<this>> { return new Query(this).where(conditions); } static whereBetween(conditions: Object): Query<Array<this>> { return new Query(this).whereBetween(conditions); } static whereRaw( query: string, bindings: Array<any> = []): Query<Array<this>> { return new Query(this).whereRaw(query, bindings); } static not(conditions: Object): Query<Array<this>> { return new Query(this).not(conditions); } static first(): Query<this> { return new Query(this).first(); } static last(): Query<this> { return new Query(this).last(); } static select(...params: Array<string>): Query<Array<this>> { return new Query(this).select(...params); } static distinct(...params: Array<string>): Query<Array<this>> { return new Query(this).distinct(...params); } static include(...relationships: Array<string | Object>): Query<Array<this>> { return new Query(this).include(...relationships); } static unscope(...scopes: Array<string>): Query<Array<this>> { return new Query(this).unscope(...scopes); } /** * Check if a model has a scope. * * @method hasScope * @param {String} name - The name of the scope to look for. * @return {Boolean} * @static * @public */ static hasScope(name: string): boolean { return Boolean(Reflect.get(this.scopes, name)); } /** * Check if a value is an instance of a model. * * @method isInstance * @param {any} value - The value in question. * @return {Boolean} * @static * @public */ static isInstance(value: any): boolean { return value instanceof this; } /** * Bind the model's connection to the database and get inferred data from the * schema upon application boot. * * @method initialize * @param {Database} store - A reference of the applications database * instance. * @param {Table} table - A function that returns a knex query builder bound * to the model's table name. * @return {Promise} Resolves with the model class. * @static * @private */ static initialize(store, table): Promise<Class<this>> { if (this.initialized) { return Promise.resolve(this); } if (!this.tableName) { const getTableName = compose(pluralize, underscore); const tableName = getTableName(this.name); Reflect.defineProperty(this, 'tableName', { value: tableName, writable: false, enumerable: true, configurable: false }); Reflect.defineProperty(this.prototype, 'tableName', { value: tableName, writable: false, enumerable: false, configurable: false }); } return initializeClass({ store, table, model: this }); } /** * @method columnFor * @param {String} key - The respective attribute name of the column. * @return {void | Object} An object containing metadata about the column if a * match is found. * @static * @private */ static columnFor(key: string): void | Object { return Reflect.get(this.attributes, key); } /** * @method columnNameFor * @param {String} key - The respective attribute name of the column. * @return {void | String} The name of the column in the database if a match * is found. * @static * @private */ static columnNameFor(key: string): void | string { const column = this.columnFor(key); return column ? column.columnName : undefined; } /** * @method relationshipFor * @param {String} key - The name of the relationship to match against. * @return {void | Object} An object containing relationship metadata if a * match is found. * @static * @private */ static relationshipFor(key: string): void | Relationship$opts { return Reflect.get(this.relationships, key); } } export default Model; export type { Model$Hook, Model$Hooks } from './interfaces';