diff --git a/.gitignore b/.gitignore index 9aae3818..235ad3f1 100644 --- a/.gitignore +++ b/.gitignore @@ -13,5 +13,5 @@ npm-debug.log .project test/memory.json .nyc_output - +dist diff --git a/.npmignore b/.npmignore index 2a6ae8b6..73367352 100644 --- a/.npmignore +++ b/.npmignore @@ -11,4 +11,5 @@ docs/html npm-debug.log .travis.yml .nyc_output +dist diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 00000000..641ee8be --- /dev/null +++ b/index.d.ts @@ -0,0 +1,36 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: loopback-datasource-juggler +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +// Type definitions for loopback-datasource-juggler 3.x +// Project: https://github.com/strongloop/loopback-datasource-juggler +// Definitions by: Raymond Feng +// TypeScript Version: 2.8 + +/** + * Experimental TypeScript definitions to capture types of the key artifacts + * from `loopback-datasource-juggler` module. One of the main purposes is to + * leverage such types in `LoopBack 4`'s bridge to juggler. + * + * Please note some of the classes, properties, methods, and functions are + * intentionally not included in the definitions because of one of the following + * factors: + * + * - They are internal + * - They are to be deprecated + */ +export * from './types/common'; +export * from './types/model'; +export * from './types/relation'; +export * from './types/query'; +export * from './types/datasource'; +export * from './types/kv-model'; +export * from './types/persisted-model'; +export * from './types/scope'; +export * from './types/transaction-mixin'; +export * from './types/relation-mixin'; +export * from './types/observer-mixin'; +export * from './types/validation-mixin'; +export * from './types/inclusion-mixin'; +export * from './types/connector'; diff --git a/package.json b/package.json index 706893f2..6cf46293 100644 --- a/package.json +++ b/package.json @@ -22,14 +22,16 @@ "url": "https://github.com/strongloop/loopback-datasource-juggler" }, "main": "index.js", + "types": "index.d.ts", "browser": { "depd": "./lib/browser.depd.js" }, "scripts": { "coverage": "nyc report --reporter=text-lcov | coveralls", "lint": "eslint .", + "tsc": "tsc -p tsconfig.json --outDir dist", "test": "nyc mocha", - "posttest": "npm run lint" + "posttest": "npm run tsc && npm run lint" }, "devDependencies": { "async-iterators": "^0.2.2", @@ -40,9 +42,11 @@ "loopback-connector-throwing": "file:./test/fixtures/loopback-connector-throwing", "mocha": "^3.2.0", "nyc": "^11.1.0", - "should": "^8.4.0" + "should": "^8.4.0", + "typescript": "^2.8.3" }, "dependencies": { + "@types/node": "^10.0.3", "async": "~2.1.4", "bluebird": "^3.1.1", "debug": "^3.1.0", diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..e3cfa08b --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,18 @@ +{ + "$schema": "http://json.schemastore.org/tsconfig", + "compilerOptions": { + "emitDecoratorMetadata": true, + "experimentalDecorators": true, + "noImplicitAny": true, + "strictNullChecks": true, + + "lib": ["es2018", "dom"], + "module": "commonjs", + "moduleResolution": "node", + "target": "es2017", + "sourceMap": true, + "declaration": true + }, + "include": ["types/", "index.d.ts"], + "exclude": ["node_modules/**"] +} diff --git a/types/common.d.ts b/types/common.d.ts new file mode 100644 index 00000000..3d31389d --- /dev/null +++ b/types/common.d.ts @@ -0,0 +1,28 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: loopback-datasource-juggler +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +/** + * Objects with open properties + */ +export interface AnyObject { + [property: string]: T; +} + +/** + * Type alias for options object + */ +export type Options = AnyObject; + +/** + * Type alias for Node.js callback functions + */ +export type Callback = (err: any, result?: T) => void; + +/** + * Return export type for promisified Node.js async methods. + * + * Note that juggler uses Bluebird, not the native Promise. + */ +export type PromiseOrVoid = PromiseLike | void; diff --git a/types/connector.d.ts b/types/connector.d.ts new file mode 100644 index 00000000..8a708b18 --- /dev/null +++ b/types/connector.d.ts @@ -0,0 +1,46 @@ +import {Callback, DataSource, Options, PromiseOrVoid} from '..'; + +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: loopback-datasource-juggler +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +/** + * Connector from `loopback-connector` module + */ +export interface Connector { + name: string; // Name/type of the connector + dataSource?: DataSource; + connect(callback?: Callback): PromiseOrVoid; // Connect to the underlying system + disconnect(callback?: Callback): PromiseOrVoid; // Disconnect from the underlying system + ping(callback?: Callback): PromiseOrVoid; // Ping the underlying system + execute?(...args: any[]): Promise; +} + +/** + * Base connector class + */ +export declare class ConnectorBase implements Connector { + name: string; // Name/type of the connector; + dataSource?: DataSource; + connect(callback?: Callback): PromiseOrVoid; // Connect to the underlying system + disconnect(callback?: Callback): PromiseOrVoid; // Disconnect from the underlying system + ping(callback?: Callback): PromiseOrVoid; // Ping the underlying system + execute?(...args: any[]): Promise; + + /** + * Initialize the connector against the given data source + * + * @param {DataSource} dataSource The dataSource + * @param {Function} [callback] The callback function + */ + static initialize(dataSource: DataSource, callback?: Callback): void; + + constructor(settings?: Options); +} + +export declare class Memory extends ConnectorBase {} + +export declare class KeyValueMemoryConnector extends ConnectorBase {} + +export declare class Transient extends ConnectorBase {} diff --git a/types/datasource.d.ts b/types/datasource.d.ts new file mode 100644 index 00000000..382f4cbf --- /dev/null +++ b/types/datasource.d.ts @@ -0,0 +1,177 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: loopback-datasource-juggler +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {AnyObject, Callback, Options, PromiseOrVoid} from './common'; +import {Connector} from './connector'; +import { + ModelBaseClass, + ModelBuilder, + ModelDefinition, + PropertyDefinition, +} from './model'; + +/** + * LoopBack models can manipulate data via the DataSource object. + * Attaching a `DataSource` to a `Model` adds instance methods and static methods to the `Model`. + * + * Define a data source to persist model data. + * To create a DataSource programmatically, call `createDataSource()` on the LoopBack object; for example: + * ```js + * var oracle = loopback.createDataSource({ + * connector: 'oracle', + * host: '111.22.333.44', + * database: 'MYDB', + * username: 'username', + * password: 'password' + * }); + * ``` + * + * All classes in single dataSource share same the connector type and + * one database connection. + * + * For example, the following creates a DataSource, and waits for a connection callback. + * + * ``` + * var dataSource = new DataSource('mysql', { database: 'myapp_test' }); + * dataSource.define(...); + * dataSource.on('connected', function () { + * // work with database + * }); + * ``` + * @class DataSource + * @param {String} [name] Optional name for datasource. + * @options {Object} settings Database-specific settings to establish connection (settings depend on specific connector). + * The table below lists a typical set for a relational database. + * @property {String} connector Database connector to use. For any supported connector, can be any of: + * + * - The connector module from `require(connectorName)`. + * - The full name of the connector module, such as 'loopback-connector-oracle'. + * - The short name of the connector module, such as 'oracle'. + * - A local module under `./connectors/` folder. + * @property {String} host Database server host name. + * @property {String} port Database server port number. + * @property {String} username Database user name. + * @property {String} password Database password. + * @property {String} database Name of the database to use. + * @property {Boolean} debug Display debugging information. Default is false. + * + * The constructor allows the following styles: + * + * 1. new DataSource(dataSourceName, settings). For example: + * - new DataSource('myDataSource', {connector: 'memory'}); + * - new DataSource('myDataSource', {name: 'myDataSource', connector: 'memory'}); + * - new DataSource('myDataSource', {name: 'anotherDataSource', connector: 'memory'}); + * + * 2. new DataSource(settings). For example: + * - new DataSource({name: 'myDataSource', connector: 'memory'}); + * - new DataSource({connector: 'memory'}); + * + * 3. new DataSource(connectorModule, settings). For example: + * - new DataSource(connectorModule, {name: 'myDataSource}) + * - new DataSource(connectorModule) + */ +export declare class DataSource { + name: string; + settings: Options; + + connected?: boolean; + connecting?: boolean; + + connector?: Connector; + + DataAccessObject: AnyObject & {prototype: AnyObject}; + + constructor(name?: string, settings?: Options, modelBuilder?: ModelBuilder); + + constructor( + connectorModule: Connector, + settings?: Options, + modelBuilder?: ModelBuilder, + ); + + /** + * Create a model class + * @param name Name of the model + * @param properties An object of property definitions + * @param options Options for model settings + */ + createModel( + name: string, + properties?: AnyObject, + options?: Options, + ): T; + + getModel(modelName: string): ModelBaseClass | undefined; + + /** + * Attach an existing model to a data source. + * This will mixin all of the data access object functions (DAO) into your + * modelClass definition. + * @param {ModelBaseClass} modelClass The model constructor that will be enhanced + * by DataAccessObject mixins. + */ + attach(modelClass: ModelBaseClass): ModelBaseClass; + + automigrate(models: string | string[], callback?: Callback): PromiseOrVoid; + + autoupdate(models: string | string[], callback?: Callback): PromiseOrVoid; + + discoverModelDefinitions( + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + discoverModelProperties( + modelName: string, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + discoverPrimaryKeys( + modelName: string, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + discoverForeignKeys( + modelName: string, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + discoverExportedForeignKeys( + modelName: string, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + discoverAndBuildModels( + modelName: string, + options?: Options, + callback?: Callback<{[name: string]: ModelBaseClass}>, + ): PromiseOrVoid<{[name: string]: ModelBaseClass}>; + + discoverSchema( + tableName: string, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + discoverSchemas( + tableName: string, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + buildModelFromInstance( + modelName: string, + jsonObject: AnyObject, + options?: Options, + ): ModelBaseClass; + + connect(callback?: Callback): PromiseOrVoid; + disconnect(callback?: Callback): PromiseOrVoid; + ping(callback?: Callback): PromiseOrVoid; +} diff --git a/types/inclusion-mixin.d.ts b/types/inclusion-mixin.d.ts new file mode 100644 index 00000000..0b5b13c6 --- /dev/null +++ b/types/inclusion-mixin.d.ts @@ -0,0 +1,44 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: loopback-datasource-juggler +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {PersistedData, PersistedModel} from '..'; +import {Callback, Options, PromiseOrVoid} from './common'; +import {Inclusion} from './query'; + +/** + * Inclusion mixin + */ +export interface InclusionMixin { + /** + * Enables you to load relations of several objects and optimize numbers of requests. + * + * Examples: + * + * Load all users' posts with only one additional request: + * `User.include(users, 'posts', function() {});` + * Or + * `User.include(users, ['posts'], function() {});` + * + * Load all users posts and passports with two additional requests: + * `User.include(users, ['posts', 'passports'], function() {});` + * + * Load all passports owner (users), and all posts of each owner loaded: + *```Passport.include(passports, {owner: 'posts'}, function() {}); + *``` Passport.include(passports, {owner: ['posts', 'passports']}); + *``` Passport.include(passports, {owner: [{posts: 'images'}, 'passports']}); + * + * @param {Array} objects Array of instances + * @param {String|Object|Array} include Which relations to load. + * @param {Object} [options] Options for CRUD + * @param {Function} cb Callback called when relations are loaded + * + */ + include( + objects: PersistedData[], + include: Inclusion, + options?: Options, + callback?: Callback[]>, + ): PromiseOrVoid[]>; +} diff --git a/types/kv-model.d.ts b/types/kv-model.d.ts new file mode 100644 index 00000000..e0fa359f --- /dev/null +++ b/types/kv-model.d.ts @@ -0,0 +1,168 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: loopback-datasource-juggler +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {Callback, Options, PromiseOrVoid} from './common'; +import {ModelBase, ModelData} from './model'; +import {Filter} from './query'; + +/** + * Data object for KV models + */ +export type KVData = ModelData; + +/** + * Key/Value model + */ +export declare class KeyValueModel extends ModelBase { + /** + * Return the value associated with a given key. + * + * @param {String} key Key to use when searching the database. + * @options {Object} options + * @callback {Function} callback + * @param {Error} err Error object. + * @param {Any} result Value associated with the given key. + * @promise + * + * @header KeyValueModel.get(key, cb) + */ + get( + key: string, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + /** + * Persist a value and associate it with the given key. + * + * @param {String} key Key to associate with the given value. + * @param {Any} value Value to persist. + * @options {Number|Object} options Optional settings for the key-value + * pair. If a Number is provided, it is set as the TTL (time to live) in ms + * (milliseconds) for the key-value pair. + * @property {Number} ttl TTL for the key-value pair in ms. + * @callback {Function} callback + * @param {Error} err Error object. + * @promise + * + * @header KeyValueModel.set(key, value, cb) + */ + set( + key: string, + value: KVData, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + /** + * Set the TTL (time to live) in ms (milliseconds) for a given key. TTL is the + * remaining time before a key-value pair is discarded from the database. + * + * @param {String} key Key to use when searching the database. + * @param {Number} ttl TTL in ms to set for the key. + * @options {Object} options + * @callback {Function} callback + * @param {Error} err Error object. + * @promise + * + * @header KeyValueModel.expire(key, ttl, cb) + */ + expire( + key: string, + ttl: number, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + /** + * Return the TTL (time to live) for a given key. TTL is the remaining time + * before a key-value pair is discarded from the database. + * + * @param {String} key Key to use when searching the database. + * @options {Object} options + * @callback {Function} callback + * @param {Error} error + * @param {Number} ttl Expiration time for the key-value pair. `undefined` if + * TTL was not initially set. + * @promise + * + * @header KeyValueModel.ttl(key, cb) + */ + ttl( + key: string, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + /** + * Return all keys in the database. + * + * **WARNING**: This method is not suitable for large data sets as all + * key-values pairs are loaded into memory at once. For large data sets, + * use `iterateKeys()` instead. + * + * @param {Object} filter An optional filter object with the following + * @param {String} filter.match Glob string used to filter returned + * keys (i.e. `userid.*`). All connectors are required to support `*` and + * `?`, but may also support additional special characters specific to the + * database. + * @param {Object} options + * @callback {Function} callback + * @promise + * + * @header KeyValueModel.keys(filter, cb) + */ + keys( + filter?: Filter, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + /** + * Asynchronously iterate all keys in the database. Similar to `.keys()` but + * instead allows for iteration over large data sets without having to load + * everything into memory at once. + * + * Callback example: + * ```js + * // Given a model named `Color` with two keys `red` and `blue` + * var iterator = Color.iterateKeys(); + * it.next(function(err, key) { + * // key contains `red` + * it.next(function(err, key) { + * // key contains `blue` + * }); + * }); + * ``` + * + * Promise example: + * ```js + * // Given a model named `Color` with two keys `red` and `blue` + * var iterator = Color.iterateKeys(); + * Promise.resolve().then(function() { + * return it.next(); + * }) + * .then(function(key) { + * // key contains `red` + * return it.next(); + * }); + * .then(function(key) { + * // key contains `blue` + * }); + * ``` + * + * @param {Object} filter An optional filter object with the following + * @param {String} filter.match Glob string to use to filter returned + * keys (i.e. `userid.*`). All connectors are required to support `*` and + * `?`. They may also support additional special characters that are + * specific to the backing database. + * @param {Object} options + * @returns {AsyncIterator} An Object implementing `next(cb) -> Promise` + * function that can be used to iterate all keys. + * + * @header KeyValueModel.iterateKeys(filter) + */ + iterateKeys(filter?: Filter, options?: Options): Iterator>; +} diff --git a/types/model.d.ts b/types/model.d.ts new file mode 100644 index 00000000..97743b69 --- /dev/null +++ b/types/model.d.ts @@ -0,0 +1,253 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: loopback-datasource-juggler +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +/// + +import {EventEmitter} from 'events'; +import {AnyObject, Options} from './common'; +import {DataSource} from './datasource'; + +/** + * Property types + */ +export type PropertyType = + | string + | Function + | {[property: string]: PropertyType}; + +/** + * Property definition + */ +export interface PropertyDefinition extends AnyObject { + name: string; + type?: PropertyType; +} + +/** + * Schema definition + */ +export interface Schema { + name: string; + properties: {[property: string]: PropertyDefinition}; + settings?: AnyObject; +} + +/** + * ID definition + */ +export interface IdDefinition { + name: string; + id: number; + property: AnyObject; +} + +/** + * Index definition + */ +export interface IndexDefinition extends AnyObject {} + +/** + * Column metadata + */ +export interface ColumnMetadata extends AnyObject { + name: string; +} + +/** + * Model definition + */ +export declare class ModelDefinition extends EventEmitter implements Schema { + name: string; + properties: AnyObject; + rawProperties: AnyObject; + settings?: AnyObject; + relations?: AnyObject[]; + + constructor( + modelBuilder: ModelBuilder | null | undefined, + name: string, + properties?: {[name: string]: PropertyDefinition}, + settings?: AnyObject, + ); + constructor(modelBuilder: ModelBuilder | null | undefined, schema: Schema); + + tableName(connectorType: string): string; + columnName(connectorType: string, propertyName: string): string; + columnNames(connectorType: string): string[]; + columnMetadata(connectorType: string, propertyName: string): ColumnMetadata; + + ids(): IdDefinition[]; + idName(): string; + idNames(): string[]; + + defineProperty( + propertyName: string, + propertyDefinition: PropertyDefinition, + ): void; + indexes(): {[name: string]: IndexDefinition}; + build(forceRebuild?: boolean): AnyObject; + toJSON(forceRebuild?: boolean): AnyObject; +} + +/** + * Base model class + */ +export declare class ModelBase { + static dataSource?: DataSource; + static modelName: string; + static definition: ModelDefinition; + + /** + * Attach the model class to a data source + * @param ds The data source + */ + static attachTo(ds: DataSource): void; + + /** + * Get model property type. + * @param {string} propName Property name + * @returns {string} Name of property type + */ + static getPropertyType(propName: string): string | null; + + /** + * Checks if property is hidden. + * @param {string} propertyName Property name + * @returns {Boolean} true or false if hidden or not. + */ + static isHiddenProperty(propertyName: string): boolean; + + /** + * Checks if property is protected. + * @param {string} propertyName Property name + * @returns {Boolean} true or false if protected or not. + */ + static isProtectedProperty(propertyName: string): boolean; + + /** + * Gets properties defined with 'updateOnly' flag set to true from the model. + * This flag is also set to true internally for the id property, if this + * property is generated and IdInjection is true. + * @returns {updateOnlyProps} List of properties with updateOnly set to true. + */ + static getUpdateOnlyProperties(): string[]; + + /** + * Model class: base class for all persistent objects. + * + * `ModelBaseClass` mixes `Validatable` and `Hookable` classes methods + * + * @class + * @param {AnyObject} data Initial object data + * @param {Options} options An object to control the instantiation + */ + constructor(data: AnyObject, options?: Options); + + /** + * Convert the model instance to a plain json object + */ + toJSON(): AnyObject; + + /** + * Populate properties from a JSON object + * @param obj The source object + */ + fromObject(obj: AnyObject): void; + + /** + * Convert model instance to a plain JSON object. + * Returns a canonical object representation (no getters and setters). + * + * @param {boolean} onlySchema Restrict properties to dataSource only. + * Default is false. If true, the function returns only properties defined + * in the schema; Otherwise it returns all enumerable properties. + * @param {boolean} removeHidden Boolean flag as part of the transformation. + * If true, then hidden properties should not be brought out. + * @param {boolean} removeProtected Boolean flag as part of the transformation. + * If true, then protected properties should not be brought out. + * @returns {object} returns Plain JSON object + */ + toObject( + onlySchema?: boolean, + removeHidden?: boolean, + removeProtected?: boolean, + ): AnyObject; + + /** + * Define a property on the model. + * @param {string} prop Property name + * @param definition Various property configuration + */ + defineProperty( + propertyName: string, + definition: Partial, + ): void; + + getDataSource(): DataSource; + + /** + * + * @param {string} anotherClass could be string or class. Name of the class + * or the class itself + * @param {Object} options An object to control the instantiation + * @returns {ModelClass} + */ + static mixin( + anotherClass: string | ModelBaseClass | object, + options?: Options, + ): ModelBaseClass; +} + +export type ModelBaseClass = typeof ModelBase; + +export declare class ModelBuilder extends EventEmitter { + static defaultInstance: ModelBuilder; + + models: {[name: string]: ModelBaseClass}; + definitions: {[name: string]: ModelDefinition}; + settings: AnyObject; + + getModel(name: string, forceCreate?: boolean): ModelBaseClass; + + getModelDefinition(name: string): ModelDefinition | undefined; + + define( + className: string, + properties?: AnyObject, + settings?: AnyObject, + parent?: ModelBaseClass, + ): ModelBaseClass; + + defineProperty( + modelName: string, + propertyName: string, + propertyDefinition: AnyObject, + ): void; + + defineValueType(type: string, aliases?: string[]): void; + + extendModel(modelName: string, properties: AnyObject): void; + + getSchemaName(name?: string): string; + + resolveType(type: any): any; + + buildModels( + schemas: AnyObject, + createModel?: Function, + ): {[name: string]: ModelBaseClass}; + + buildModelFromInstance( + name: string, + json: AnyObject, + options: Options, + ): ModelBaseClass; +} + +/** + * Union export type for model instance or plain object representing the model + * instance + */ +export type ModelData = T | AnyObject; diff --git a/types/observer-mixin.d.ts b/types/observer-mixin.d.ts new file mode 100644 index 00000000..769a00bc --- /dev/null +++ b/types/observer-mixin.d.ts @@ -0,0 +1,157 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: loopback-datasource-juggler +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {Callback, PromiseOrVoid} from './common'; + +export type Listener = (ctx: object, next: (err?: any) => void) => void; + +export interface ObserverMixin { + /** + * Register an asynchronous observer for the given operation (event). + * + * Example: + * + * Registers a `before save` observer for a given model. + * + * ```javascript + * MyModel.observe('before save', function filterProperties(ctx, next) { + * if (ctx.options && ctx.options.skipPropertyFilter) return next(); + * if (ctx.instance) { + * FILTERED_PROPERTIES.forEach(function(p) { + * ctx.instance.unsetAttribute(p); + * }); + * } else { + * FILTERED_PROPERTIES.forEach(function(p) { + * delete ctx.data[p]; + * }); + * } + * next(); + * }); + * ``` + * + * @param {String} operation The operation name. + * @callback {function} listener The listener function. It will be invoked with + * `this` set to the model constructor, e.g. `User`. + * @end + */ + observe(operation: string, listener: Listener): void; + + /** + * Unregister an asynchronous observer for the given operation (event). + * + * Example: + * + * ```javascript + * MyModel.removeObserver('before save', function removedObserver(ctx, next) { + * // some logic user want to apply to the removed observer... + * next(); + * }); + * ``` + * + * @param {String} operation The operation name. + * @callback {function} listener The listener function. + * @end + */ + removeObserver(operation: string, listener: Listener): Listener | undefined; + + /** + * Unregister all asynchronous observers for the given operation (event). + * + * Example: + * + * Remove all observers connected to the `before save` operation. + * + * ```javascript + * MyModel.clearObservers('before save'); + * ``` + * + * @param {String} operation The operation name. + * @end + */ + clearObservers(operation: string): void; + + /** + * Invoke all async observers for the given operation(s). + * + * Example: + * + * Notify all async observers for the `before save` operation. + * + * ```javascript + * var context = { + * Model: Model, + * instance: obj, + * isNewInstance: true, + * hookState: hookState, + * options: options, + * }; + * Model.notifyObserversOf('before save', context, function(err) { + * if (err) return cb(err); + * // user can specify the logic after the observers have been notified + * }); + * ``` + * + * @param {String|String[]} operation The operation name(s). + * @param {Object} context Operation-specific context. + * @callback {function(Error=)} callback The callback to call when all observers + * have finished. + */ + notifyObserversOf( + operation: string, + context: object, + callback?: Callback, + ): PromiseOrVoid; + + _notifyBaseObservers( + operation: string, + context: object, + callback?: Callback, + ): PromiseOrVoid; + + /** + * Run the given function with before/after observers. + * + * It's done in three serial asynchronous steps: + * + * - Notify the registered observers under 'before ' + operation + * - Execute the function + * - Notify the registered observers under 'after ' + operation + * + * If an error happens, it fails first and calls the callback with err. + * + * Example: + * + * ```javascript + * var context = { + * Model: Model, + * instance: obj, + * isNewInstance: true, + * hookState: hookState, + * options: options, + * }; + * function work(done) { + * process.nextTick(function() { + * done(null, 1); + * }); + * } + * Model.notifyObserversAround('execute', context, work, function(err) { + * if (err) return cb(err); + * // user can specify the logic after the observers have been notified + * }); + * ``` + * + * @param {String} operation The operation name + * @param {Context} context The context object + * @param {Function} fn The task to be invoked as fn(done) or fn(context, done) + * @callback {Function} callback The callback function + * @returns {*} + */ + notifyObserversAround( + operation: string, + context: object, + fn: Function, + callback?: Callback, + ): PromiseOrVoid; +} diff --git a/types/persisted-model.d.ts b/types/persisted-model.d.ts new file mode 100644 index 00000000..c5442c93 --- /dev/null +++ b/types/persisted-model.d.ts @@ -0,0 +1,518 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: loopback-datasource-juggler +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {Callback, Options, PromiseOrVoid} from './common'; +import {ModelBase, ModelData} from './model'; +import {Filter, Where} from './query'; + +/** + * Data object for persisted models + */ +export type PersistedData< + T extends PersistedModel = PersistedModel +> = ModelData; + +/** + * Type of affected count + */ +export interface Count { + count: number; +} + +/** + * Persisted model + */ +export declare class PersistedModel extends ModelBase { + /** + * Create new instance of Model, and save to database. + * + * @param {Object|Object[]} [data] Optional data argument. Can be either a + * single model instance or an array of instances. + * + * @callback {Function} callback Callback function called with `cb(err, obj)` signature. + * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html). + * @param {Object} models Model instances or null. + */ + static create( + data: PersistedData, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + /** + * Update or insert a model instance + * @param {Object} data The model instance data to insert. + * @callback {Function} callback Callback function called with `cb(err, obj)` signature. + * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html). + * @param {Object} model Updated model instance. + */ + static upsert( + data: PersistedData, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + static updateOrCreate( + data: PersistedData, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + static patchOrCreate( + data: PersistedData, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + /** + * Update or insert a model instance based on the search criteria. + * If there is a single instance retrieved, update the retrieved model. + * Creates a new model if no model instances were found. + * Returns an error if multiple instances are found. + * @param {Object} [where] `where` filter, like + * ``` + * { key: val, key2: {gt: 'val2'}, ...} + * ``` + *
see + * [Where filter](http://loopback.io/doc/en/lb2/Where-filter.html#where-clause-for-other-methods). + * @param {Object} data The model instance data to insert. + * @callback {Function} callback Callback function called with `cb(err, obj)` signature. + * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html). + * @param {Object} model Updated model instance. + */ + static upsertWithWhere( + where: Where, + data: PersistedData, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + static patchOrCreateWithWhere( + where: Where, + data: PersistedData, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + /** + * Replace or insert a model instance; replace existing record if one is found, + * such that parameter `data.id` matches `id` of model instance; otherwise, + * insert a new record. + * @param {Object} data The model instance data. + * @options {Object} [options] Options for replaceOrCreate + * @property {Boolean} validate Perform validation before saving. Default is true. + * @callback {Function} callback Callback function called with `cb(err, obj)` signature. + * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html). + * @param {Object} model Replaced model instance. + */ + static replaceOrCreate( + data: PersistedData, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + /** + * Finds one record matching the optional filter object. If not found, creates + * the object using the data provided as second argument. In this sense it is + * the same as `find`, but limited to one object. Returns an object, not + * collection. If you don't provide the filter object argument, it tries to + * locate an existing object that matches the `data` argument. + * + * @options {Object} [filter] Optional Filter object; see below. + * @property {String|Object|Array} fields Identify fields to include in return result. + *
See [Fields filter](http://loopback.io/doc/en/lb2/Fields-filter.html). + * @property {String|Object|Array} include See PersistedModel.include documentation. + *
See [Include filter](http://loopback.io/doc/en/lb2/Include-filter.html). + * @property {Number} limit Maximum number of instances to return. + *
See [Limit filter](http://loopback.io/doc/en/lb2/Limit-filter.html). + * @property {String} order Sort order: either "ASC" for ascending or "DESC" for descending. + *
See [Order filter](http://loopback.io/doc/en/lb2/Order-filter.html). + * @property {Number} skip Number of results to skip. + *
See [Skip filter](http://loopback.io/doc/en/lb2/Skip-filter.html). + * @property {Object} where Where clause, like + * ``` + * {where: {key: val, key2: {gt: val2}, ...}} + * ``` + *
See + * [Where filter](http://loopback.io/doc/en/lb2/Where-filter.html#where-clause-for-queries). + * @param {Object} data Data to insert if object matching the `where` filter is not found. + * @callback {Function} callback Callback function called with `cb(err, instance, created)` arguments. Required. + * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html). + * @param {Object} instance Model instance matching the `where` filter, if found. + * @param {Boolean} created True if the instance does not exist and gets created. + */ + static findOrCreate( + filter: Filter, + data: PersistedData, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + /** + * Check whether a model instance exists in database. + * + * @param {id} id Identifier of object (primary key value). + * + * @callback {Function} callback Callback function called with `(err, exists)` arguments. Required. + * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html). + * @param {Boolean} exists True if the instance with the specified ID exists; false otherwise. + */ + static exists( + id: any, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + /** + * Find object by ID with an optional filter for include/fields. + * + * @param {*} id Primary key value + * @options {Object} [filter] Optional Filter JSON object; see below. + * @property {String|Object|Array} fields Identify fields to include in return result. + *
See [Fields filter](http://loopback.io/doc/en/lb2/Fields-filter.html). + * @property {String|Object|Array} include See PersistedModel.include documentation. + *
See [Include filter](http://loopback.io/doc/en/lb2/Include-filter.html). + * @callback {Function} callback Callback function called with `(err, instance)` arguments. Required. + * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html). + * @param {Object} instance Model instance matching the specified ID or null if no instance matches. + */ + static findById( + id: any, + filter?: Filter, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + /** + * Find all model instances that match `filter` specification. + * See [Querying models](http://loopback.io/doc/en/lb2/Querying-data.html). + * + * @options {Object} [filter] Optional Filter JSON object; see below. + * @property {String|Object|Array} fields Identify fields to include in return result. + *
See [Fields filter](http://loopback.io/doc/en/lb2/Fields-filter.html). + * @property {String|Object|Array} include See PersistedModel.include documentation. + *
See [Include filter](http://loopback.io/doc/en/lb2/Include-filter.html). + * @property {Number} limit Maximum number of instances to return. + *
See [Limit filter](http://loopback.io/doc/en/lb2/Limit-filter.html). + * @property {String} order Sort order: either "ASC" for ascending or "DESC" for descending. + *
See [Order filter](http://loopback.io/doc/en/lb2/Order-filter.html). + * @property {Number} skip Number of results to skip. + *
See [Skip filter](http://loopback.io/doc/en/lb2/Skip-filter.html). + * @property {Object} where Where clause, like + * ``` + * { where: { key: val, key2: {gt: 'val2'}, ...} } + * ``` + *
See + * [Where filter](http://loopback.io/doc/en/lb2/Where-filter.html#where-clause-for-queries). + * + * @callback {Function} callback Callback function called with `(err, returned-instances)` arguments. Required. + * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html). + * @param {Array} models Model instances matching the filter, or null if none found. + */ + + static find( + filter?: Filter, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + /** + * Find one model instance that matches `filter` specification. + * Same as `find`, but limited to one result; + * Returns object, not collection. + * + * @options {Object} [filter] Optional Filter JSON object; see below. + * @property {String|Object|Array} fields Identify fields to include in return result. + *
See [Fields filter](http://loopback.io/doc/en/lb2/Fields-filter.html). + * @property {String|Object|Array} include See PersistedModel.include documentation. + *
See [Include filter](http://loopback.io/doc/en/lb2/Include-filter.html). + * @property {String} order Sort order: either "ASC" for ascending or "DESC" for descending. + *
See [Order filter](http://loopback.io/doc/en/lb2/Order-filter.html). + * @property {Number} skip Number of results to skip. + *
See [Skip filter](http://loopback.io/doc/en/lb2/Skip-filter.html). + * @property {Object} where Where clause, like + * ``` + * {where: { key: val, key2: {gt: 'val2'}, ...} } + * ``` + *
See + * [Where filter](http://loopback.io/doc/en/lb2/Where-filter.html#where-clause-for-queries). + * + * @callback {Function} callback Callback function called with `(err, returned-instance)` arguments. Required. + * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html). + * @param {Array} model First model instance that matches the filter or null if none found. + */ + static findOne( + filter?: Filter, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + /** + * Destroy all model instances that match the optional `where` specification. + * + * @param {Object} [where] Optional where filter, like: + * ``` + * {key: val, key2: {gt: 'val2'}, ...} + * ``` + *
See + * [Where filter](http://loopback.io/doc/en/lb2/Where-filter.html#where-clause-for-other-methods). + * + * @callback {Function} callback Optional callback function called with `(err, info)` arguments. + * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html). + * @param {Object} info Additional information about the command outcome. + * @param {Number} info.count Number of instances (rows, documents) destroyed. + */ + static destroyAll( + where?: Where, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + static remove( + where?: Where, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + static deleteAll( + where?: Where, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + /** + * Update multiple instances that match the where clause. + * + * Example: + * + *```js + * Employee.updateAll({managerId: 'x001'}, {managerId: 'x002'}, function(err, info) { + * ... + * }); + * ``` + * + * @param {Object} [where] Optional `where` filter, like + * ``` + * { key: val, key2: {gt: 'val2'}, ...} + * ``` + *
see + * [Where filter](http://loopback.io/doc/en/lb2/Where-filter.html#where-clause-for-other-methods). + * @param {Object} data Object containing data to replace matching instances, if AnyType. + * + * @callback {Function} callback Callback function called with `(err, info)` arguments. Required. + * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html). + * @param {Object} info Additional information about the command outcome. + * @param {Number} info.count Number of instances (rows, documents) updated. + * + */ + static updateAll( + where?: Where, + data?: PersistedData, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + static update( + where?: Where, + data?: PersistedData, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + /** + * Destroy model instance with the specified ID. + * @param {*} id The ID value of model instance to delete. + * @callback {Function} callback Callback function called with `(err)` arguments. Required. + * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html). + */ + static destroyById( + id: any, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + static removeById( + id: any, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + static deleteById( + id: any, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + /** + * Replace attributes for a model instance whose id is the first input + * argument and persist it into the datasource. + * Performs validation before replacing. + * + * @param {*} id The ID value of model instance to replace. + * @param {Object} data Data to replace. + * @options {Object} [options] Options for replace + * @property {Boolean} validate Perform validation before saving. Default is true. + * @callback {Function} callback Callback function called with `(err, instance)` arguments. + * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html). + * @param {Object} instance Replaced instance. + */ + static replaceById( + id: any, + data: PersistedData, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + /** + * Return the number of records that match the optional "where" filter. + * @param {Object} [where] Optional where filter, like + * ``` + * { key: val, key2: {gt: 'val2'}, ...} + * ``` + *
See + * [Where filter](http://loopback.io/doc/en/lb2/Where-filter.html#where-clause-for-other-methods). + * @callback {Function} callback Callback function called with `(err, count)` arguments. Required. + * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html). + * @param {Number} count Number of instances. + */ + static count( + where?: Where, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + /** + * Save model instance. If the instance doesn't have an ID, then calls [create](#persistedmodelcreatedata-cb) instead. + * Triggers: validate, save, update, or create. + * @options {Object} [options] See below. + * @property {Boolean} validate Perform validation before saving. Default is true. + * @property {Boolean} throws If true, throw a validation error; WARNING: This can crash Node. + * If false, report the error via callback. Default is false. + * @callback {Function} callback Optional callback function called with `(err, obj)` arguments. + * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html). + * @param {Object} instance Model instance saved or created. + */ + save(options?: Options, callback?: Callback): PromiseOrVoid; + + /** + * Determine if the data model is new. + * @returns {Boolean} Returns true if the data model is new; false otherwise. + */ + isNewRecord(): boolean; + + /** + * Deletes the model from persistence. + * Triggers `destroy` hook (async) before and after destroying object. + * @param {Function} callback Callback function. + */ + destroy( + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + remove( + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + delete( + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + /** + * Update a single attribute. + * Equivalent to `updateAttributes({name: 'value'}, cb)` + * + * @param {String} name Name of property. + * @param {Mixed} value Value of property. + * @callback {Function} callback Callback function called with `(err, instance)` arguments. Required. + * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html). + * @param {Object} instance Updated instance. + */ + updateAttribute( + name: string, + value: any, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + /** + * Update set of attributes. Performs validation before updating. + * + * Triggers: `validation`, `save` and `update` hooks + * @param {Object} data Data to update. + * @callback {Function} callback Callback function called with `(err, instance)` arguments. Required. + * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html). + * @param {Object} instance Updated instance. + */ + updateAttributes( + data: PersistedData, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + /** + * Replace attributes for a model instance and persist it into the datasource. + * Performs validation before replacing. + * + * @param {Object} data Data to replace. + * @options {Object} [options] Options for replace + * @property {Boolean} validate Perform validation before saving. Default is true. + * @callback {Function} callback Callback function called with `(err, instance)` arguments. + * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html). + * @param {Object} instance Replaced instance. + */ + replaceAttributes( + data: PersistedData, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + /** + * Reload object from persistence. Requires `id` member of `object` to be able to call `find`. + * @callback {Function} callback Callback function called with `(err, instance)` arguments. Required. + * @param {Error} err Error object; see [Error object](http://loopback.io/doc/en/lb2/Error-object.html). + * @param {Object} instance Model instance. + */ + reload( + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + /** + * Set the correct `id` property for the `PersistedModel`. Uses the `setId` method if the model is attached to + * connector that defines it. Otherwise, uses the default lookup. + * Override this method to handle complex IDs. + * + * @param {*} val The `id` value. Will be converted to the export type that the `id` property specifies. + */ + setId(val: any): void; + + /** + * Get the `id` value for the `PersistedModel`. + * + * @returns {*} The `id` value + */ + + getId(): any; + + /** + * Get the `id` property name of the constructor. + * + * @returns {String} The `id` property name + */ + + getIdName(): string; + + /** + * Get the `id` property name of the constructor. + * + * @returns {String} The `id` property name + */ + static getIdName(): string; +} + +export type PersistedModelClass = typeof PersistedModel; diff --git a/types/query.d.ts b/types/query.d.ts new file mode 100644 index 00000000..35be1281 --- /dev/null +++ b/types/query.d.ts @@ -0,0 +1,108 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: loopback-datasource-juggler +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +/** + * Operators for where clauses + */ +export declare enum Operators { + /** + * Equal operator (=) + */ + eq = 'eq', + /** + * Not equal operator (!=) + */ + neq = 'neq', + /** + * Greater than operator (>) + */ + gt = 'gt', + /** + * Greater than or equal operator (>=) + */ + gte = 'gte', + /** + * Less than operator (<) + */ + lt = 'lt', + /** + * Less than or equal (<=) + */ + lte = 'lte', + /** + * IN operator. For example, `{type: {inq: ['a', 'b', 'c']}}` + */ + inq = 'inq', + /** + * Between operator. For example, `{age: {between: [18, 40]}}` + */ + between = 'between', + /** + * Exists operator + */ + exists = 'exists', + /** + * AND operator + */ + and = 'and', + /** + * OR operator + */ + or = 'or', +} + +/** + * Matching criteria + */ +export interface Condition { + eq?: any; + neq?: any; + gt?: any; + gte?: any; + lt?: any; + lte?: any; + inq?: any[]; + between?: any[]; + exists?: boolean; + and?: Where[]; + or?: Where[]; +} + +/** + * Where object + */ +export interface Where { + and?: Where[]; // AND + or?: Where[]; // OR + [property: string]: Condition | any; +} + +/** + * Selection of fields + */ +export interface Fields { + [property: string]: boolean; +} + +/** + * Inclusion of related items + */ +export interface Inclusion { + relation: string; + scope?: Filter; +} + +/** + * Query filter object + */ +export interface Filter { + where?: Where; + fields?: string | string[] | Fields; + order?: string | string[]; + limit?: number; + skip?: number; + offset?: number; + include?: string | string[] | Inclusion[]; +} diff --git a/types/relation-mixin.d.ts b/types/relation-mixin.d.ts new file mode 100644 index 00000000..d9a51c48 --- /dev/null +++ b/types/relation-mixin.d.ts @@ -0,0 +1,586 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: loopback-datasource-juggler +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {Options} from './common'; +import {RelationDefinition} from './relation'; +import {PersistedModelClass} from './persisted-model'; + +/** + * Methods defined on this interface are mixed into a model class so that they + * can be used to set up relations between models programmatically. + */ +export interface RelationMixin { + /** + * Define a "one to many" relationship by specifying the model name. + * + * Examples: + * ``` + * User.hasMany(Post, {as: 'posts', foreignKey: 'authorId'}); + * ``` + * + * ``` + * Book.hasMany(Chapter); + * ``` + * Or, equivalently: + * ``` + * Book.hasMany('chapters', {model: Chapter}); + * ``` + * + * Query and create related models: + * + * ```js + * Book.create(function(err, book) { + * + * // Create a chapter instance ready to be saved in the data source. + * var chapter = book.chapters.build({name: 'Chapter 1'}); + * + * // Save the new chapter + * chapter.save(); + * + * // you can also call the Chapter.create method with the `chapters` property which will build a chapter + * // instance and save the it in the data source. + * book.chapters.create({name: 'Chapter 2'}, function(err, savedChapter) { + * // this callback is optional + * }); + * + * // Query chapters for the book + * book.chapters(function(err, chapters) { + * // all chapters with bookId = book.id + * console.log(chapters); + * }); + * + * // Query chapters for the book with a filter + * book.chapters({where: {name: 'test'}, function(err, chapters) { + * // All chapters with bookId = book.id and name = 'test' + * console.log(chapters); + * }); + * }); + * ``` + * + * @param {Object|String} modelTo Model object (or String name of model) to which you are creating the relationship. + * @options {Object} params Configuration parameters; see below. + * @property {String} as Name of the property in the referring model that corresponds to the foreign key field in the related model. + * @property {String} foreignKey Property name of foreign key field. + * @property {String} polymorphic Define a polymorphic relation name. + * @property {String} through Name of the through model. + * @property {String} keyThrough Property name of the foreign key in the through model. + * @property {Object|Function} scope Explicitly define additional scopes. + * @property {Boolean} invert Specify if the relation is inverted. + * @property {Object} model The model object. + */ + hasMany(modelTo: PersistedModelClass, params?: Options): RelationDefinition; + + /** + * Declare "belongsTo" relation that sets up a one-to-one connection with another model, such that each + * instance of the declaring model "belongs to" one instance of the other model. + * + * For example, if an application includes users and posts, and each post can be written by exactly one user. + * The following code specifies that `Post` has a reference called `author` to the `User` model via the `userId` property of `Post` + * as the foreign key. + * ``` + * Post.belongsTo(User, {as: 'author', foreignKey: 'userId'}); + * ``` + * You can then access the author in one of the following styles. + * Get the User object for the post author asynchronously: + * ``` + * post.author(callback); + * ``` + * Get the User object for the post author synchronously: + * ``` + * post.author(); + * ``` + * Set the author to be the given user: + * ``` + * post.author(user) + * ``` + * Examples: + * + * Suppose the model Post has a *belongsTo* relationship with User (the author of the post). You could declare it this way: + * ```js + * Post.belongsTo(User, {as: 'author', foreignKey: 'userId'}); + * ``` + * + * When a post is loaded, you can load the related author with: + * ```js + * post.author(function(err, user) { + * // the user variable is your user object + * }); + * ``` + * + * The related object is cached, so if later you try to get again the author, no additional request will be made. + * But there is an optional boolean parameter in first position that set whether or not you want to reload the cache: + * ```js + * post.author(true, function(err, user) { + * // The user is reloaded, even if it was already cached. + * }); + * ``` + * This optional parameter default value is false, so the related object will be loaded from cache if available. + * + * @param {Class|String} modelTo Model object (or String name of model) to which you are creating the relationship. + * @options {Object} params Configuration parameters; see below. + * @property {String} as Name of the property in the referring model that corresponds to the foreign key field in the related model. + * @property {String} primaryKey Property name of primary key field. + * @property {String} foreignKey Name of foreign key property. + * @property {Object|Function} scope Explicitly define additional scopes. + * @property {Object} properties Properties inherited from the parent object. + * @property {Object} options Property level options. + * @property {Boolean} options.invertProperties Specify if the properties should be inverted. + */ + belongsTo(modelTo: PersistedModelClass, params?: Options): RelationDefinition; + + /** + * A hasAndBelongsToMany relation creates a direct many-to-many connection with another model, with no intervening model. + * + * For example, if your application includes users and groups, with each group having many users and each user appearing + * in many groups, you could declare the models this way: + * ``` + * User.hasAndBelongsToMany('groups', {model: Group, foreignKey: 'groupId'}); + * ``` + * Then, to get the groups to which the user belongs: + * ``` + * user.groups(callback); + * ``` + * Create a new group and connect it with the user: + * ``` + * user.groups.create(data, callback); + * ``` + * Connect an existing group with the user: + * ``` + * user.groups.add(group, callback); + * ``` + * Remove the user from the group: + * ``` + * user.groups.remove(group, callback); + * ``` + * + * @param {String|Object} modelTo Model object (or String name of model) to which you are creating the relationship. + * @options {Object} params Configuration parameters; see below. + * @property {String} as Name of the property in the referring model that corresponds to the foreign key field in the related model. + * @property {String} foreignKey Property name of foreign key field. + * @property {String} throughTable The table name of the through model. + * @property {String} through Name of the through model. + * @property {String} polymorphic Define a polymorphic relation name. + * @property {Object|Function} scope Explicitly define additional scopes. + * @property {Object} model The model object. + */ + hasAndBelongsToMany( + modelTo: PersistedModelClass, + params?: Options, + ): RelationDefinition; + /** + * Define a "one to one" relationship by specifying the model name. + * + * Examples: + * ``` + * Supplier.hasOne(Account, {as: 'account', foreignKey: 'supplierId'}); + * ``` + * + * If the target model doesn’t have a foreign key property, LoopBack will add a property with the same name. + * + * The type of the property will be the same as the type of the target model’s id property. + * + * Please note the foreign key property is defined on the target model (in this example, Account). + * + * If you don’t specify them, then LoopBack derives the relation name and foreign key as follows: + * - Relation name: Camel case of the model name, for example, for the “supplier” model the relation is “supplier”. + * - Foreign key: The relation name appended with Id, for example, for relation name “supplier” the default foreign key is “supplierId”. + * + * Build a new account for the supplier with the supplierId to be set to the id of the supplier. + * ```js + * var supplier = supplier.account.build(data); + * ``` + * + * Create a new account for the supplier. If there is already an account, an error will be reported. + * ```js + * supplier.account.create(data, function(err, account) { + * ... + * }); + * ``` + * + * Find the supplier's account model. + * ```js + * supplier.account(function(err, account) { + * ... + * }); + * ``` + * + * Update the associated account. + * ```js + * supplier.account.update({balance: 100}, function(err, account) { + * ... + * }); + * ``` + * + * Remove the account for the supplier. + * ```js + * supplier.account.destroy(function(err) { + * ... + * }); + * ``` + * + * @param {Object|String} modelTo Model object (or String name of model) to which you are creating the relationship. + * @options {Object} params Configuration parameters; see below. + * @property {String} as Name of the property in the referring model that corresponds to the foreign key field in the related model. + * @property {String} primaryKey Property name of primary key field. + * @property {String} foreignKey Property name of foreign key field. + * @property {String} polymorphic Define a polymorphic relation name. + * @property {Object|Function} scope Explicitly define additional scopes. + * @property {Object} model The model object. + * @property {Object} properties Properties inherited from the parent object. + * @property {Function} methods Scoped methods for the given relation. + */ + hasOne(modelTo: PersistedModelClass, params?: Options): RelationDefinition; + + /** + * References one or more instances of the target model. + * + * For example, a Customer model references one or more instances of the Account model. + * + * Define the relation in the model definition: + * + * - Definition of Customer model: + * ```json + * { + "name": "Customer", + "base": "PersistedModel", + "idInjection": true, + "properties": { + "name": { + "type": "string" + }, + "age": { + "type": "number" + } + }, + "validations": [], + "relations": { + "accounts": { + "type": "referencesMany", + "model": "Account", + "foreignKey": "accountIds", + "options": { + "validate": true, + "forceId": false + } + } + }, + "acls": [], + "methods": {} +} + * ``` + * + * - Definition of Account model: + * ```json + * { + "name": "Account", + "base": "PersistedModel", + "idInjection": true, + "properties": { + "name": { + "type": "string" + }, + "balance": { + "type": "number" + } + }, + "validations": [], + "relations": {}, + "acls": [], + "methods": {} +} + * ``` + * + * On the bootscript, create a customer instance and for that customer instance reference many account instances. + * + * For example: + * ```javascript + * var Customer = app.models.Customer; + var accounts = [ + { + name: 'Checking', + balance: 5000 + }, + { + name: 'Saving', + balance: 2000 + } + ]; + Customer.create({name: 'Mary Smith'}, function(err, customer) { + console.log('Customer:', customer); + async.each(accounts, function(account, done) { + customer.accounts.create(account, done); + }, function(err) { + console.log('Customer with accounts:', customer); + customer.accounts(console.log); + cb(err); + }); + }); + * ``` + * + * Sample referencesMany model data: + * ```javascript + * { + id: 1, + name: 'John Smith', + accounts: [ + "saving-01", "checking-01", + ] +} + * ``` + * + * Supported helper methods: + * - customer.accounts() + * - customer.accounts.create() + * - customer.accounts.build() + * - customer.accounts.findById() + * - customer.accounts.destroy() + * - customer.accounts.updateById() + * - customer.accounts.exists() + * - customer.accounts.add() + * - customer.accounts.remove() + * - customer.accounts.at() + * + * @param {Object|String} modelTo Model object (or String name of model) to which you are creating the relationship. + * @options {Object} params Configuration parameters; see below. + * @property {String} as Name of the property in the referring model that corresponds to the foreign key field in the related model. + * @property {Any} default The default value. + * @property {Object} options Options to specify for the relationship. + * @property {Boolean} options.forceId Force generation of id for embedded items. Default is false. + * @property {Boolean} options.validate Denote if the embedded items should be validated. Default is true. + * @property {Boolean} options.persistent Denote if the embedded items should be persisted. Default is false. + * @property {Object|Function} scope Explicitly define additional scopes. + * @property {String} foreignKey Property name of foreign key field. + * @property {Object} properties Properties inherited from the parent object. + * @property {Function} methods Scoped methods for the given relation. + */ + referencesMany( + modelTo: PersistedModelClass, + params?: Options, + ): RelationDefinition; + + /** + * Represent a model that embeds another model. + * + * For example, a Customer embeds one billingAddress from the Address model. + * + * - Define the relation in bootscript: + * ```js + * Customer.embedsOne(Address, { + * as: 'address', // default to the relation name - address + * property: 'billingAddress' // default to addressItem + * }); + * ``` + * + * OR, define the relation in the model definition: + * + * - Definition of Customer model: + * ```json + * { + "name": "Customer", + "base": "PersistedModel", + "idInjection": true, + "properties": { + "name": { + "type": "string" + }, + "age": { + "type": "number" + } + }, + "validations": [], + "relations": { + "address": { + "type": "embedsOne", + "model": "Address", + "property": "billingAddress", + "options": { + "validate": true, + "forceId": false + } + } + }, + "acls": [], + "methods": {} +} + * ``` + * + * - Definition of Address model: + * ```json + * { + "name": "Address", + "base": "Model", + "idInjection": true, + "properties": { + "street": { + "type": "string" + }, + "city": { + "type": "string" + }, + "state": { + "type": "string" + }, + "zipCode": { + "type": "string" + } + }, + "validations": [], + "relations": {}, + "acls": [], + "methods": {} +} + * ``` + * + * Sample embedded model data: + * ```javascript + * { + id: 1, + name: 'John Smith', + billingAddress: { + street: '123 Main St', + city: 'San Jose', + state: 'CA', + zipCode: '95124' + } +} + * ``` + * + * Supported helper methods: + * - customer.address() + * - customer.address.build() + * - customer.address.create() + * - customer.address.update() + * - customer.address.destroy() + * - customer.address.value() + * + * @param {Object|String} modelTo Model object (or String name of model) to which you are creating the relationship. + * @options {Object} params Configuration parameters; see below. + * @property {String} as Name of the property in the referring model that corresponds to the foreign key field in the related model. + * @property {String} property Name of the property for the embedded item. + * @property {Any} default The default value. + * @property {Object} options Options to specify for the relationship. + * @property {Boolean} options.forceId Force generation of id for embedded items. Default is false. + * @property {Boolean} options.validate Denote if the embedded items should be validated. Default is true. + * @property {Boolean} options.persistent Denote if the embedded items should be persisted. Default is false. + * @property {Object|Function} scope Explicitly define additional scopes. + * @property {Object} properties Properties inherited from the parent object. + * @property {Function} methods Scoped methods for the given relation. + */ + embedsOne(modelTo: PersistedModelClass, params?: Options): RelationDefinition; + + /** + * Represent a model that can embed many instances of another model. + * + * For example, a Customer can have multiple email addresses and each email address is a complex object that contains label and address. + * + * Define the relation code in bootscript: + * ```javascript + Customer.embedsMany(EmailAddress, { + as: 'emails', // default to the relation name - emailAddresses + property: 'emailList' // default to emailAddressItems + }); + * ``` + * + * OR, define the relation in the model definition: + * + * - Definition of Customer model: + * ```json + * { + "name": "Customer", + "base": "PersistedModel", + "idInjection": true, + "properties": { + "name": { + "type": "string" + }, + "age": { + "type": "number" + } + }, + "validations": [], + "relations": { + "emails": { + "type": "embedsMany", + "model": "EmailAddress", + "property": "emailList", + "options": { + "validate": true, + "forceId": false + } + } + }, + "acls": [], + "methods": {} +} + * ``` + * + * - Definition of EmailAddress model: + * ```json + * { + "name": "EmailAddress", + "base": "Model", + "idInjection": true, + "properties": { + "label": { + "type": "string" + }, + "address": { + "type": "string" + } + }, + "validations": [], + "relations": {}, + "acls": [], + "methods": {} +} + * ``` + * + * Sample embedded model data: + * ```javascript + * { + id: 1, + name: 'John Smith', + emails: [{ + label: 'work', + address: 'john@xyz.com' + }, { + label: 'home', + address: 'john@gmail.com' + }] +} + * ``` + * + * Supported helper methods: + * - customer.emails() + * - customer.emails.create() + * - customer.emails.build() + * - customer.emails.findById() + * - customer.emails.destroyById() + * - customer.emails.updateById() + * - customer.emails.exists() + * - customer.emails.add() + * - customer.emails.remove() + * - customer.emails.at() + * - customer.emails.value() + * + * @param {Object|String} modelTo Model object (or String name of model) to which you are creating the relationship. + * @options {Object} params Configuration parameters; see below. + * @property {String} as Name of the property in the referring model that corresponds to the foreign key field in the related model. + * @property {String} property Name of the property for the embedded item. + * @property {Any} default The default value. + * @property {Object} options Options to specify for the relationship. + * @property {Boolean} options.forceId Force generation of id for embedded items. Default is false. + * @property {Boolean} options.validate Denote if the embedded items should be validated. Default is true. + * @property {Boolean} options.persistent Denote if the embedded items should be persisted. Default is false. + * @property {String} polymorphic Define a polymorphic relation name. + * @property {Object|Function} scope Explicitly define additional scopes. + * @property {Object} properties Properties inherited from the parent object. + * @property {Function} methods Scoped methods for the given relation. + */ + embedsMany( + modelTo: PersistedModelClass, + params?: Options, + ): RelationDefinition; +} diff --git a/types/relation.d.ts b/types/relation.d.ts new file mode 100644 index 00000000..f29ae471 --- /dev/null +++ b/types/relation.d.ts @@ -0,0 +1,407 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: loopback-datasource-juggler +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {AnyObject, Callback, Options, PromiseOrVoid} from './common'; +import {ModelData} from './model'; +import { + Count, + PersistedData, + PersistedModel, + PersistedModelClass, +} from './persisted-model'; +import {Filter, Where} from './query'; + +/** + * Relation types + */ +export enum RelationType { + belongsTo = 'belongsTo', + hasMany = 'hasMany', + hasOne = 'hasOne', + hasAndBelongsToMany = 'hasAndBelongsToMany', + referencesMany = 'referencesMany', + embedsOne = 'embedsOne', + embedsMany = 'embedsMany', +} + +/** + * Relation definition + */ +export declare class RelationDefinition { + name: string; + type: RelationType; + modelFrom: PersistedModelClass | string; + keyFrom: string; + modelTo: PersistedModelClass | string; + keyTo: string; + polymorphic: AnyObject | boolean; + modelThrough?: PersistedModelClass | string; + keyThrough?: string; + multiple: boolean; + properties: AnyObject; + options: Options; + scope: AnyObject; + embed?: boolean; + methods?: AnyObject; + + toJSON(): AnyObject; + defineMethod(name: string, fn: Function): Function; + applyScope(modelInstance: PersistedData, filter: Filter): void; + applyProperties(modelInstance: PersistedData, obj: AnyObject): void; +} + +/** + * Relation of a given model instance + */ +export declare class Relation< + S extends PersistedModel = PersistedModel, + T extends PersistedModel = PersistedModel +> { + constructor(definition: RelationDefinition, modelInstance: S); + + resetCache(cache: T): void; + getCache(): AnyObject; + callScopeMethod(methodName: string, ...args: any[]): any; + fetch( + condOrRefresh: boolean | AnyObject, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; +} + +export declare class BelongsTo< + S extends PersistedModel = PersistedModel, + T extends PersistedModel = PersistedModel +> extends Relation { + create( + targetModelData: PersistedData, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + build(targetModelData: PersistedData): T; + + update( + targetModelData: PersistedData, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + destroy( + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + related( + condOrRefresh: boolean | AnyObject, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + get(options?: Options, callback?: Callback): PromiseOrVoid; +} + +export declare class HasMany< + S extends PersistedModel = PersistedModel, + T extends PersistedModel = PersistedModel +> extends Relation { + removeFromCache(id: any): T | null; + + addToCache(inst: T): void; + + findById( + fkId: any, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + exists( + fkId: any, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + updateById( + fkId: any, + data: ModelData, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + destroyById( + fkId: any, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; +} + +export declare class HasManyThrough< + S extends PersistedModel = PersistedModel, + T extends PersistedModel = PersistedModel +> extends Relation { + findById( + fkId: any, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + destroyById( + fkId: any, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + create( + data: PersistedData, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + add( + acInst: any, + data: PersistedData, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + exists( + acInst: any, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + remove( + acInst: any, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; +} + +export declare class HasOne< + S extends PersistedModel = PersistedModel, + T extends PersistedModel = PersistedModel +> extends Relation { + create( + targetModelData: PersistedData, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + update( + targetModelData: PersistedData, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + destroy( + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + build(targetModelData: PersistedData): T; + + related( + condOrRefresh: any, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + get(options?: Options, callback?: Callback): PromiseOrVoid; +} + +export declare class HasAndBelongsToMany< + S extends PersistedModel = PersistedModel, + T extends PersistedModel = PersistedModel +> extends Relation {} + +export declare class ReferencesMany< + S extends PersistedModel = PersistedModel, + T extends PersistedModel = PersistedModel +> extends Relation { + related( + receiver: PersistedModelClass, + scopeParams: AnyObject, + condOrRefresh: any, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + findById( + fkId: any, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + exists( + fkId: any, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + updateById( + fkId: any, + data: PersistedData, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + destroyById( + fkId: any, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + at( + index: number, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + create( + targetModelData: PersistedData, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + build(targetModelData: PersistedData): T; + + add( + acInst: any, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + remove( + acInst: any, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; +} + +export declare class EmbedsOne< + S extends PersistedModel = PersistedModel, + T extends PersistedModel = PersistedModel +> extends Relation { + related( + condOrRefresh: any, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + prepareEmbeddedInstance(inst: T): void; + + embeddedValue(modelInstance: S): T; + + create( + targetModelData: PersistedData, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + build(targetModelData: PersistedData): T; + + update( + targetModelData: PersistedData, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + destroy( + options?: Options, + callback?: Callback, + ): PromiseOrVoid; +} + +export declare class EmbedsMany< + S extends PersistedModel = PersistedModel, + T extends PersistedModel = PersistedModel +> extends Relation { + prepareEmbeddedInstance(inst: T): void; + + embeddedList(modelInstance: T[]): void; + + embeddedValue(modelInstance: S): T[]; + + related( + receiver: PersistedModelClass, + scopeParams: AnyObject, + condOrRefresh: any, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + findById( + fkId: any, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + get( + fkId: any, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + exists( + fkId: any, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + updateById( + fkId: any, + data: PersistedData, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + set( + fkId: any, + data: PersistedData, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + destroyById( + fkId: any, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + unset( + fkId: any, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + destroyAll( + where: Where, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + at(index: number, callback?: Callback): PromiseOrVoid; + + create( + targetModelData: PersistedData, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + build(targetModelData: PersistedData): T; + + add( + acInst: any, + data: PersistedData, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + remove( + acInst: any, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; +} diff --git a/types/scope.d.ts b/types/scope.d.ts new file mode 100644 index 00000000..daa19b5a --- /dev/null +++ b/types/scope.d.ts @@ -0,0 +1,113 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: loopback-datasource-juggler +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {AnyObject, Callback, Options, PromiseOrVoid} from './common'; +import {ModelBase, ModelBaseClass, ModelData} from './model'; +import {Count} from './persisted-model'; +import {Filter, Where} from './query'; + +/** + * Definition metadata for scopes + */ +export declare class ScopeDefinition { + constructor(definition: AnyObject); + + isStatic: boolean; + modelFrom?: ModelBaseClass; + modelTo?: ModelBaseClass; + name: string; + params?: AnyObject; + methods?: AnyObject; + options?: Options; + + targetModel(receiver: ModelBaseClass | (() => ModelBaseClass)): void; + + /** + * Find related model instances + * @param {*} receiver The target model class/prototype + * @param {Object|Function} scopeParams + * @param {Boolean|Object} [condOrRefresh] true for refresh or object as a filter + * @param {Object} [options] + * @param {Function} cb + * @returns {*} + */ + related( + receiver: ModelBaseClass | (() => ModelBaseClass), + scopeParams: AnyObject | (() => AnyObject), + condOrRefresh: boolean | AnyObject, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + /** + * Define a scope method + * @param {String} name of the method + * @param {Function} function to define + */ + defineMethod(name: string, fn: Function): Function; +} + +/** + * Define a scope to the class + * @param {Model} cls The class where the scope method is added + * @param {Model} targetClass The class that a query to run against + * @param {String} name The name of the scope + * @param {Object|Function} params The parameters object for the query or a function + * to return the query object + * @param methods An object of methods keyed by the method name to be bound to the class + */ +export declare function defineScope( + cls: ModelBaseClass, + targetClass: ModelBaseClass, + name: string, + params: AnyObject, + methods: AnyObject, + options?: Options, +): ScopeDefinition; + +/** + * Methods injected by scopes + */ +export interface ScopedMethods { + build(data: ModelData): T; + + create( + data: ModelData, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + destroyAll( + where?: Where, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + updateAll( + where?: Where, + data?: ModelData, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + findById( + id: any, + filter: Filter, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + findOne( + filter: Filter, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; + + count( + where: Where, + options?: Options, + callback?: Callback, + ): PromiseOrVoid; +} diff --git a/types/transaction-mixin.d.ts b/types/transaction-mixin.d.ts new file mode 100644 index 00000000..7879b581 --- /dev/null +++ b/types/transaction-mixin.d.ts @@ -0,0 +1,47 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: loopback-datasource-juggler +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {Callback, Options, PromiseOrVoid} from './common'; + +/** + * Local transaction + */ +export interface Transaction { + /** + * Commit the transaction + * @param callback + */ + commit(callback?: Callback): PromiseOrVoid; + /** + * Rollback the transaction + * @param callback + */ + rollback(callback?: Callback): PromiseOrVoid; +} + +/** + * Isolation level + */ +export enum IsolationLevel { + READ_COMMITTED = 'READ COMMITTED', // default + READ_UNCOMMITTED = 'READ UNCOMMITTED', + SERIALIZABLE = 'SERIALIZABLE', + REPEATABLE_READ = 'REPEATABLE READ', +} + +/** + * Mixin for transaction support + */ +export interface TransactionMixin { + /** + * Begin a new transaction + * @param options + * @param callback + */ + beginTransaction( + options?: IsolationLevel | Options, + callback?: Callback, + ): PromiseOrVoid; +} diff --git a/types/validation-mixin.d.ts b/types/validation-mixin.d.ts new file mode 100644 index 00000000..4fe65bce --- /dev/null +++ b/types/validation-mixin.d.ts @@ -0,0 +1,303 @@ +// Copyright IBM Corp. 2018. All Rights Reserved. +// Node module: loopback-datasource-juggler +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {Callback, Options, PromiseOrVoid} from './common'; + +/** + * This class provides methods that add validation cababilities to models. + * Each of the validations runs when the `obj.isValid()` method is called. + * + * All of the methods have an options object parameter that has a + * `message` property. When there is only a single error message, this + * property is just a string; + * for example: `Post.validatesPresenceOf('title', { message: 'can not be blank' });` + * + * In more complicated cases it can be a set of messages, for each possible + * error condition; for example: + * `User.validatesLengthOf('password', { min: 6, max: 20, message: {min: 'too short', max: 'too long'}});` + * + */ +export interface Validatable { + /** + * Validate presence of one or more specified properties. + * + * Requires a model to include a property to be considered valid; fails when validated field is blank. + * + * For example, validate presence of title + * ``` + * Post.validatesPresenceOf('title'); + * ``` + * Validate that model has first, last, and age properties: + * ``` + * User.validatesPresenceOf('first', 'last', 'age'); + * ``` + * Example with custom message + * ``` + * Post.validatesPresenceOf('title', {message: 'Cannot be blank'}); + * ``` + * + * @param {String} propertyName One or more property names. + * @options {Object} options Configuration parameters; see below. + * @property {String} message Error message to use instead of default. + * @property {String} if Validate only if `if` exists. + * @property {String} unless Validate only if `unless` exists. + */ + validatesPresenceOf(...propertyNames: string[]): void; + validatesPresenceOf(propertyName: string, options?: Options): void; + + /** + * Validate absence of one or more specified properties. + * + * A model should not include a property to be considered valid; fails when validated field is not blank. + * + * For example, validate absence of reserved + * ``` + * Post.validatesAbsenceOf('reserved', { unless: 'special' }); + * ``` + * + * @param {String} propertyName One or more property names. + * @options {Object} options Configuration parameters; see below. + * @property {String} message Error message to use instead of default. + * @property {String} if Validate only if `if` exists. + * @property {String} unless Validate only if `unless` exists. + */ + validatesAbsenceOf(...propertyNames: string[]): void; + validatesAbsenceOf(propertyName: string, options?: Options): void; + + /** + * Validate length. + * + * Require a property length to be within a specified range. + * + * There are three kinds of validations: min, max, is. + * + * Default error messages: + * + * - min: too short + * - max: too long + * - is: length is wrong + * + * Example: length validations + * ``` + * User.validatesLengthOf('password', {min: 7}); + * User.validatesLengthOf('email', {max: 100}); + * User.validatesLengthOf('state', {is: 2}); + * User.validatesLengthOf('nick', {min: 3, max: 15}); + * ``` + * Example: length validations with custom error messages + * ``` + * User.validatesLengthOf('password', {min: 7, message: {min: 'too weak'}}); + * User.validatesLengthOf('state', {is: 2, message: {is: 'is not valid state name'}}); + * ``` + * + * @param {String} propertyName Property name to validate. + * @options {Object} options Configuration parameters; see below. + * @property {Number} is Value that property must equal to validate. + * @property {Number} min Value that property must be less than to be valid. + * @property {Number} max Value that property must be less than to be valid. + * @property {Object} message Optional object with string properties for custom error message for each validation: is, min, or max. + */ + validatesLengthOf(propertyName: string, options?: Options): void; + + /** + * Validate numericality. + * + * Requires a value for property to be either an integer or number. + * + * Example + * ``` + * User.validatesNumericalityOf('age', { message: { number: 'is not a number' }}); + * User.validatesNumericalityOf('age', {int: true, message: { int: 'is not an integer' }}); + * ``` + * + * @param {String} propertyName Property name to validate. + * @options {Object} options Configuration parameters; see below. + * @property {Boolean} int If true, then property must be an integer to be valid. + * @property {Boolean} allowBlank Allow property to be blank. + * @property {Boolean} allowNull Allow property to be null. + * @property {Object} message Optional object with string properties for 'int' for integer validation. Default error messages: + * - number: is not a number + * - int: is not an integer + */ + validatesNumericalityOf(propertyName: string, options?: Options): void; + + /** + * Validate inclusion in set. + * + * Require a value for property to be in the specified array. + * + * Example: + * ``` + * User.validatesInclusionOf('gender', {in: ['male', 'female']}); + * User.validatesInclusionOf('role', { + * in: ['admin', 'moderator', 'user'], message: 'is not allowed' + * }); + * ``` + * + * @param {String} propertyName Property name to validate. + * @options {Object} options Configuration parameters; see below. + * @property {Array} in Property must match one of the values in the array to be valid. + * @property {String} message Optional error message if property is not valid. + * Default error message: "is not included in the list". + * @property {Boolean} allowNull Whether null values are allowed. + */ + validatesInclusionOf(propertyName: string, options?: Options): void; + + /** + * Validate exclusion in a set. + * + * Require a property value not be in the specified array. + * + * Example: `Company.validatesExclusionOf('domain', {in: ['www', 'admin']});` + * + * @param {String} propertyName Property name to validate. + * @options {Object} options Configuration parameters; see below. + * @property {Array} in Property must not match any of the values in the array to be valid. + * @property {String} message Optional error message if property is not valid. Default error message: "is reserved". + * @property {Boolean} allowNull Whether null values are allowed. + */ + validatesExclusionOf(propertyName: string, options?: Options): void; + + /** + * Validate format. + * + * Require a model to include a property that matches the given format. + * + * Example: `User.validatesFormatOf('name', {with: /\w+/});` + * + * @param {String} propertyName Property name to validate. + * @options {Object} options Configuration parameters; see below. + * @property {RegExp} with Regular expression to validate format. + * @property {String} message Optional error message if property is not valid. Default error message: " is invalid". + * @property {Boolean} allowNull Whether null values are allowed. + */ + validatesFormatOf(propertyName: string, options?: Options): void; + + /** + * Validate using custom validation function. + * + * Example: + *```javascript + * User.validate('name', customValidator, {message: 'Bad name'}); + * function customValidator(err) { + * if (this.name === 'bad') err(); + * }); + * var user = new User({name: 'Peter'}); + * user.isValid(); // true + * user.name = 'bad'; + * user.isValid(); // false + * ``` + * + * @param {String} propertyName Property name to validate. + * @param {Function} validatorFn Custom validation function. + * @options {Object} options Configuration parameters; see below. + * @property {String} message Optional error message if property is not valid. Default error message: " is invalid". + * @property {Boolean} allowNull Whether null values are allowed. + */ + validate( + propertyName: string, + validatorFn: Function, + options?: Options, + ): void; + + /** + * Validate using custom asynchronous validation function. + * + * Example: + *```js + * User.validateAsync('name', customValidator, {message: 'Bad name'}); + * function customValidator(err, done) { + * process.nextTick(function () { + * if (this.name === 'bad') err(); + * done(); + * }); + * }); + * var user = new User({name: 'Peter'}); + * user.isValid(); // false (because async validation setup) + * user.isValid(function (isValid) { + * isValid; // true + * }) + * user.name = 'bad'; + * user.isValid(); // false + * user.isValid(function (isValid) { + * isValid; // false + * }) + * ``` + * + * @param {String} propertyName Property name to validate. + * @param {Function} validatorFn Custom validation function. + * @options {Object} options Configuration parameters; see below. + * @property {String} message Optional error message if property is not valid. Default error message: " is invalid". + * @property {Boolean} allowNull Whether null values are allowed. + */ + validateAsync( + propertyName: string, + validatorFn: Function, + options?: Options, + ): void; + + /** + * Validate uniqueness of the value for a property in the collection of models. + * + * Not available for all connectors. Currently supported with these connectors: + * - In Memory + * - Oracle + * - MongoDB + * + * ``` + * // The login must be unique across all User instances. + * User.validatesUniquenessOf('login'); + * + * // Assuming SiteUser.belongsTo(Site) + * // The login must be unique within each Site. + * SiteUser.validateUniquenessOf('login', { scopedTo: ['siteId'] }); + * ``` + * + * @param {String} propertyName Property name to validate. + * @options {Object} options Configuration parameters; see below. + * @property {RegExp} with Regular expression to validate format. + * @property {Array.} scopedTo List of properties defining the scope. + * @property {String} message Optional error message if property is not valid. Default error message: "is not unique". + * @property {Boolean} allowNull Whether null values are allowed. + * @property {String} ignoreCase Make the validation case insensitive. + * @property {String} if Validate only if `if` exists. + * @property {String} unless Validate only if `unless` exists. + */ + validatesUniquenessOf( + propertyName: string, + validatorFn: Function, + options?: Options, + ): void; + + /** + * Validate if a value for a property is a Date. + * + * Example + * ``` + * User.validatesDateOf('today', {message: 'today is not a date!'}); + * ``` + * + * @param {String} propertyName Property name to validate. + * @options {Object} options Configuration parameters; see below. + * @property {String} message Error message to use instead of default. + */ + validatesDateOf( + propertyName: string, + validatorFn: Function, + options?: Options, + ): void; +} + +/** + * ValidationError + */ +export declare class ValidationError extends Error { + statusCode?: number; + details: { + context: any; + codes: string[]; + messages: string[]; + }; +}