// Copyright IBM Corp. 2013,2019. All Rights Reserved. // Node module: loopback // This file is licensed under the MIT License. // License text available at https://opensource.org/licenses/MIT /*! * Module dependencies. */ 'use strict'; const express = require('express'); const loopbackExpress = require('./server-app'); const proto = require('./application'); const fs = require('fs'); const ejs = require('ejs'); const path = require('path'); const merge = require('util')._extend; const assert = require('assert'); const Registry = require('./registry'); const juggler = require('loopback-datasource-juggler'); const configureSharedMethods = require('./configure-shared-methods'); /** * LoopBack core module. It provides static properties and * methods to create models and data sources. The module itself is a function * that creates loopback `app`. For example: * * ```js * var loopback = require('loopback'); * var app = loopback(); * ``` * * @property {String} version Version of LoopBack framework. Static read-only property. * @property {Boolean} isBrowser True if running in a browser environment; false otherwise. Static read-only property. * @property {Boolean} isServer True if running in a server environment; false otherwise. Static read-only property. * @property {Registry} registry The global `Registry` object. * @property {String} faviconFile Path to a default favicon shipped with LoopBack. * Use as follows: `app.use(require('serve-favicon')(loopback.faviconFile));` * @class loopback * @header loopback */ const loopback = module.exports = createApplication; /*! * Framework version. */ loopback.version = require('../package.json').version; loopback.registry = new Registry(); Object.defineProperties(loopback, { Model: { get: function() { return this.registry.getModel('Model'); }, }, PersistedModel: { get: function() { return this.registry.getModel('PersistedModel'); }, }, defaultDataSources: { get: function() { return this.registry.defaultDataSources; }, }, modelBuilder: { get: function() { return this.registry.modelBuilder; }, }, }); /*! * Create an loopback application. * * @return {Function} * @api public */ function createApplication(options) { const app = loopbackExpress(); merge(app, proto); app.loopback = loopback; app.on('modelRemoted', function() { app.models().forEach(function(Model) { if (!Model.config) return; configureSharedMethods(Model, app.get('remoting'), Model.config); }); }); // Create a new instance of models registry per each app instance app.models = function() { return proto.models.apply(this, arguments); }; // Create a new instance of datasources registry per each app instance app.datasources = app.dataSources = {}; // Create a new instance of connector registry per each app instance app.connectors = {}; // Register built-in connectors. It's important to keep this code // hand-written, so that all require() calls are static // and thus browserify can process them (include connectors in the bundle) app.connector('memory', loopback.Memory); app.connector('remote', loopback.Remote); app.connector('kv-memory', require('loopback-datasource-juggler/lib/connectors/kv-memory')); if (loopback.localRegistry || options && options.localRegistry === true) { // setup the app registry const registry = app.registry = new Registry(); if (options && options.loadBuiltinModels === true) { require('./builtin-models')(registry); } } else { app.registry = loopback.registry; } return app; } function mixin(source) { for (const key in source) { const desc = Object.getOwnPropertyDescriptor(source, key); // Fix for legacy (pre-ES5) browsers like PhantomJS if (!desc) continue; Object.defineProperty(loopback, key, desc); } } mixin(require('./runtime')); /*! * Expose static express methods like `express.Router`. */ mixin(express); /*! * Expose additional loopback middleware * for example `loopback.configure` etc. * * ***only in node*** */ if (loopback.isServer) { fs .readdirSync(path.join(__dirname, '..', 'server', 'middleware')) .filter(function(file) { return file.match(/\.js$/); }) .forEach(function(m) { loopback[m.replace(/\.js$/, '')] = require('../server/middleware/' + m); }); loopback.urlNotFound = loopback['url-not-found']; delete loopback['url-not-found']; loopback.errorHandler = loopback['error-handler']; delete loopback['error-handler']; } // Expose path to the default favicon file // ***only in node*** if (loopback.isServer) { /*! * Path to a default favicon shipped with LoopBack. * * **Example** * * ```js * app.use(require('serve-favicon')(loopback.faviconFile)); * ``` */ loopback.faviconFile = path.resolve(__dirname, '../favicon.ico'); } /** * Add a remote method to a model. * @param {Function} fn * @param {Object} options (optional) */ loopback.remoteMethod = function(fn, options) { fn.shared = true; if (typeof options === 'object') { Object.keys(options).forEach(function(key) { fn[key] = options[key]; }); } fn.http = fn.http || {verb: 'get'}; }; /** * Create a template helper. * * var render = loopback.template('foo.ejs'); * var html = render({foo: 'bar'}); * * @param {String} file Path to the template file. * @returns {Function} */ loopback.template = function(file) { const templates = this._templates || (this._templates = {}); const str = templates[file] || (templates[file] = fs.readFileSync(file, 'utf8')); return ejs.compile(str, { filename: file, }); }; require('../lib/current-context')(loopback); /** * Create a named vanilla JavaScript class constructor with an attached * set of properties and options. * * This function comes with two variants: * * `loopback.createModel(name, properties, options)` * * `loopback.createModel(config)` * * In the second variant, the parameters `name`, `properties` and `options` * are provided in the config object. Any additional config entries are * interpreted as `options`, i.e. the following two configs are identical: * * ```js * { name: 'Customer', base: 'User' } * { name: 'Customer', options: { base: 'User' } } * ``` * * **Example** * * Create an `Author` model using the three-parameter variant: * * ```js * loopback.createModel( * 'Author', * { * firstName: 'string', * lastName: 'string' * }, * { * relations: { * books: { * model: 'Book', * type: 'hasAndBelongsToMany' * } * } * } * ); * ``` * * Create the same model using a config object: * * ```js * loopback.createModel({ * name: 'Author', * properties: { * firstName: 'string', * lastName: 'string' * }, * relations: { * books: { * model: 'Book', * type: 'hasAndBelongsToMany' * } * } * }); * ``` * * @param {String} name Unique name. * @param {Object} properties * @param {Object} options (optional) * * @header loopback.createModel */ loopback.createModel = function(name, properties, options) { return this.registry.createModel.apply(this.registry, arguments); }; /** * Alter an existing Model class. * @param {Model} ModelCtor The model constructor to alter. * @options {Object} config Additional configuration to apply * @property {DataSource} dataSource Attach the model to a dataSource. * @property {Object} [relations] Model relations to add/update. * * @header loopback.configureModel(ModelCtor, config) */ loopback.configureModel = function(ModelCtor, config) { return this.registry.configureModel.apply(this.registry, arguments); }; /** * Look up a model class by name from all models created by * `loopback.createModel()` * @param {String} modelName The model name * @returns {Model} The model class * * @header loopback.findModel(modelName) */ loopback.findModel = function(modelName) { return this.registry.findModel.apply(this.registry, arguments); }; /** * Look up a model class by name from all models created by * `loopback.createModel()`. Throw an error when no such model exists. * * @param {String} modelName The model name * @returns {Model} The model class * * @header loopback.getModel(modelName) */ loopback.getModel = function(modelName) { return this.registry.getModel.apply(this.registry, arguments); }; /** * Look up a model class by the base model class. * The method can be used by LoopBack * to find configured models in models.json over the base model. * @param {Model} modelType The base model class * @returns {Model} The subclass if found or the base class * * @header loopback.getModelByType(modelType) */ loopback.getModelByType = function(modelType) { return this.registry.getModelByType.apply(this.registry, arguments); }; /** * Create a data source with passing the provided options to the connector. * * @param {String} name Optional name. * @options {Object} options Data Source options * @property {Object} connector LoopBack connector. * @property {*} [*] Other connector properties. * See the relevant connector documentation. */ loopback.createDataSource = function(name, options) { return this.registry.createDataSource.apply(this.registry, arguments); }; /** * Get an in-memory data source. Use one if it already exists. * * @param {String} [name] The name of the data source. * If not provided, the `'default'` is used. */ loopback.memory = function(name) { return this.registry.memory.apply(this.registry, arguments); }; /*! * Built in models / services */ require('./builtin-models')(loopback); loopback.DataSource = juggler.DataSource;