Merge pull request #308 from strongloop/feature/cleanup-after-pr-304
[2.0] cleanup after pr 304
This commit is contained in:
commit
3f0fc094d5
|
@ -3,6 +3,8 @@
|
||||||
"content": [
|
"content": [
|
||||||
"lib/application.js",
|
"lib/application.js",
|
||||||
"lib/loopback.js",
|
"lib/loopback.js",
|
||||||
|
"lib/runtime.js",
|
||||||
|
"lib/registry.js",
|
||||||
"lib/middleware/token.js",
|
"lib/middleware/token.js",
|
||||||
"lib/models/access-token.js",
|
"lib/models/access-token.js",
|
||||||
"lib/models/access-context.js",
|
"lib/models/access-context.js",
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var DataSource = require('loopback-datasource-juggler').DataSource
|
var DataSource = require('loopback-datasource-juggler').DataSource
|
||||||
, loopback = require('../')
|
, registry = require('./registry')
|
||||||
, compat = require('./compat')
|
, compat = require('./compat')
|
||||||
, assert = require('assert')
|
, assert = require('assert')
|
||||||
, fs = require('fs')
|
, fs = require('fs')
|
||||||
|
@ -115,7 +115,8 @@ app.disuse = function (route) {
|
||||||
|
|
||||||
app.model = function (Model, config) {
|
app.model = function (Model, config) {
|
||||||
if(arguments.length === 1) {
|
if(arguments.length === 1) {
|
||||||
assertIsModel(Model);
|
assert(Model.prototype instanceof registry.Model,
|
||||||
|
'Model must be a descendant of loopback.Model');
|
||||||
if(Model.sharedClass) {
|
if(Model.sharedClass) {
|
||||||
this.remotes().addClass(Model.sharedClass);
|
this.remotes().addClass(Model.sharedClass);
|
||||||
}
|
}
|
||||||
|
@ -140,7 +141,7 @@ app.model = function (Model, config) {
|
||||||
// modeller does not understand `dataSource` option
|
// modeller does not understand `dataSource` option
|
||||||
delete modelConfig.dataSource;
|
delete modelConfig.dataSource;
|
||||||
|
|
||||||
Model = loopback.createModelFromConfig(modelConfig);
|
Model = registry.createModel(modelConfig);
|
||||||
|
|
||||||
// delete config options already applied
|
// delete config options already applied
|
||||||
['relations', 'base', 'acls', 'hidden'].forEach(function(prop) {
|
['relations', 'base', 'acls', 'hidden'].forEach(function(prop) {
|
||||||
|
@ -165,14 +166,6 @@ app.model = function (Model, config) {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
function assertIsModel(Model) {
|
|
||||||
assert(typeof Model === 'function',
|
|
||||||
'Model must be a function / constructor');
|
|
||||||
assert(Model.modelName, 'Model must have a "modelName" property');
|
|
||||||
assert(Model.prototype instanceof loopback.Model,
|
|
||||||
'Model must be a descendant of loopback.Model');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the models exported by the app. Returns only models defined using `app.model()`
|
* Get the models exported by the app. Returns only models defined using `app.model()`
|
||||||
*
|
*
|
||||||
|
@ -538,7 +531,7 @@ app.boot = function(options) {
|
||||||
|
|
||||||
// try to attach models to dataSources by type
|
// try to attach models to dataSources by type
|
||||||
try {
|
try {
|
||||||
require('./loopback').autoAttach();
|
registry.autoAttach();
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
if(e.name === 'AssertionError') {
|
if(e.name === 'AssertionError') {
|
||||||
console.warn(e);
|
console.warn(e);
|
||||||
|
@ -601,11 +594,12 @@ function dataSourcesFromConfig(config, connectorRegistry) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return require('./loopback').createDataSource(config);
|
return registry.createDataSource(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
function configureModel(ModelCtor, config, app) {
|
function configureModel(ModelCtor, config, app) {
|
||||||
assertIsModel(ModelCtor);
|
assert(ModelCtor.prototype instanceof registry.Model,
|
||||||
|
'Model must be a descendant of loopback.Model');
|
||||||
|
|
||||||
var dataSource = config.dataSource;
|
var dataSource = config.dataSource;
|
||||||
|
|
||||||
|
@ -613,14 +607,14 @@ function configureModel(ModelCtor, config, app) {
|
||||||
dataSource = app.dataSources[dataSource];
|
dataSource = app.dataSources[dataSource];
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(isDataSource(dataSource),
|
assert(dataSource instanceof DataSource,
|
||||||
ModelCtor.modelName + ' is referencing a dataSource that does not exist: "' +
|
ModelCtor.modelName + ' is referencing a dataSource that does not exist: "' +
|
||||||
config.dataSource +'"');
|
config.dataSource +'"');
|
||||||
|
|
||||||
config = extend({}, config);
|
config = extend({}, config);
|
||||||
config.dataSource = dataSource;
|
config.dataSource = dataSource;
|
||||||
|
|
||||||
loopback.configureModel(ModelCtor, config);
|
registry.configureModel(ModelCtor, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
function requireDir(dir, basenames) {
|
function requireDir(dir, basenames) {
|
||||||
|
@ -692,10 +686,6 @@ function tryReadDir() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isDataSource(obj) {
|
|
||||||
return obj instanceof DataSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
function tryReadConfig(cwd, fileName) {
|
function tryReadConfig(cwd, fileName) {
|
||||||
try {
|
try {
|
||||||
return require(path.join(cwd, fileName + '.json'));
|
return require(path.join(cwd, fileName + '.json'));
|
||||||
|
|
319
lib/loopback.js
319
lib/loopback.js
|
@ -3,10 +3,10 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var express = require('express')
|
var express = require('express')
|
||||||
|
, proto = require('./application')
|
||||||
, fs = require('fs')
|
, fs = require('fs')
|
||||||
, ejs = require('ejs')
|
, ejs = require('ejs')
|
||||||
, path = require('path')
|
, path = require('path')
|
||||||
, DataSource = require('loopback-datasource-juggler').DataSource
|
|
||||||
, merge = require('util')._extend
|
, merge = require('util')._extend
|
||||||
, assert = require('assert');
|
, assert = require('assert');
|
||||||
|
|
||||||
|
@ -26,24 +26,6 @@ var express = require('express')
|
||||||
|
|
||||||
var loopback = exports = module.exports = createApplication;
|
var loopback = exports = module.exports = createApplication;
|
||||||
|
|
||||||
/**
|
|
||||||
* True if running in a browser environment; false otherwise.
|
|
||||||
*/
|
|
||||||
|
|
||||||
loopback.isBrowser = typeof window !== 'undefined';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* True if running in a server environment; false otherwise.
|
|
||||||
*/
|
|
||||||
|
|
||||||
loopback.isServer = !loopback.isBrowser;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Framework version.
|
|
||||||
*/
|
|
||||||
|
|
||||||
loopback.version = require('../package.json').version;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expose mime.
|
* Expose mime.
|
||||||
*/
|
*/
|
||||||
|
@ -65,9 +47,6 @@ loopback.compat = require('./compat');
|
||||||
function createApplication() {
|
function createApplication() {
|
||||||
var app = express();
|
var app = express();
|
||||||
|
|
||||||
// Defer loading of `./application` until all `loopback` static methods
|
|
||||||
// are defined, because `./application` depends on loopback.
|
|
||||||
var proto = require('./application');
|
|
||||||
merge(app, proto);
|
merge(app, proto);
|
||||||
|
|
||||||
// Create a new instance of models registry per each app instance
|
// Create a new instance of models registry per each app instance
|
||||||
|
@ -90,16 +69,21 @@ function createApplication() {
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mixin(source) {
|
||||||
|
for (var key in source) {
|
||||||
|
var desc = Object.getOwnPropertyDescriptor(source, key);
|
||||||
|
Object.defineProperty(loopback, key, desc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mixin(require('./runtime'));
|
||||||
|
mixin(require('./registry'));
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Expose static express methods like `express.errorHandler`.
|
* Expose static express methods like `express.errorHandler`.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
for (var key in express) {
|
mixin(express);
|
||||||
Object.defineProperty(
|
|
||||||
loopback
|
|
||||||
, key
|
|
||||||
, Object.getOwnPropertyDescriptor(express, key));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Expose additional middleware like session as loopback.*
|
* Expose additional middleware like session as loopback.*
|
||||||
|
@ -110,13 +94,7 @@ for (var key in express) {
|
||||||
|
|
||||||
if (loopback.isServer) {
|
if (loopback.isServer) {
|
||||||
var middlewares = require('./express-middleware');
|
var middlewares = require('./express-middleware');
|
||||||
|
mixin(middlewares);
|
||||||
for (var key in middlewares) {
|
|
||||||
Object.defineProperty(
|
|
||||||
loopback
|
|
||||||
, key
|
|
||||||
, Object.getOwnPropertyDescriptor(middlewares, key));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -143,155 +121,6 @@ if (loopback.isServer) {
|
||||||
|
|
||||||
loopback.errorHandler.title = 'Loopback';
|
loopback.errorHandler.title = 'Loopback';
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a data source with passing the provided options to the connector.
|
|
||||||
*
|
|
||||||
* @param {String} name Optional name.
|
|
||||||
* @options {Object} Data Source options
|
|
||||||
* @property {Object} connector LoopBack connector.
|
|
||||||
* @property {*} Other properties See the relevant connector documentation.
|
|
||||||
*/
|
|
||||||
|
|
||||||
loopback.createDataSource = function (name, options) {
|
|
||||||
var ds = new DataSource(name, options, loopback.Model.modelBuilder);
|
|
||||||
ds.createModel = function (name, properties, settings) {
|
|
||||||
var ModelCtor = loopback.createModel(name, properties, settings);
|
|
||||||
ModelCtor.attachTo(ds);
|
|
||||||
return ModelCtor;
|
|
||||||
};
|
|
||||||
|
|
||||||
if(ds.settings && ds.settings.defaultForType) {
|
|
||||||
loopback.setDefaultDataSourceForType(ds.settings.defaultForType, ds);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ds;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a named vanilla JavaScript class constructor with an attached set of properties and options.
|
|
||||||
*
|
|
||||||
* @param {String} name Unique name.
|
|
||||||
* @param {Object} properties
|
|
||||||
* @param {Object} options (optional)
|
|
||||||
*/
|
|
||||||
|
|
||||||
loopback.createModel = function (name, properties, options) {
|
|
||||||
options = options || {};
|
|
||||||
var BaseModel = options.base || options.super;
|
|
||||||
|
|
||||||
if(typeof BaseModel === 'string') {
|
|
||||||
var baseName = BaseModel;
|
|
||||||
BaseModel = loopback.getModel(BaseModel);
|
|
||||||
|
|
||||||
if (BaseModel === undefined) {
|
|
||||||
if (baseName === 'DataModel') {
|
|
||||||
console.warn('Model `%s` is extending deprecated `DataModel. ' +
|
|
||||||
'Use `PeristedModel` instead.', name);
|
|
||||||
BaseModel = loopback.PersistedModel;
|
|
||||||
} else {
|
|
||||||
console.warn('Model `%s` is extending an unknown model `%s`. ' +
|
|
||||||
'Using `PersistedModel` as the base.', name, baseName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BaseModel = BaseModel || loopback.PersistedModel;
|
|
||||||
|
|
||||||
var model = BaseModel.extend(name, properties, options);
|
|
||||||
|
|
||||||
// try to attach
|
|
||||||
try {
|
|
||||||
loopback.autoAttachModel(model);
|
|
||||||
} catch(e) {}
|
|
||||||
|
|
||||||
return model;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a model as described by the configuration object.
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
*
|
|
||||||
* ```js
|
|
||||||
* loopback.createModelFromConfig({
|
|
||||||
* name: 'Author',
|
|
||||||
* properties: {
|
|
||||||
* firstName: 'string',
|
|
||||||
* lastName: 'string
|
|
||||||
* },
|
|
||||||
* relations: {
|
|
||||||
* books: {
|
|
||||||
* model: 'Book',
|
|
||||||
* type: 'hasAndBelongsToMany'
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* });
|
|
||||||
* ```
|
|
||||||
*
|
|
||||||
* @options {Object} model configuration
|
|
||||||
* @property {String} name Unique name.
|
|
||||||
* @property {Object=} properties Model properties
|
|
||||||
* @property {Object=} options Model options. Options can be specified on the
|
|
||||||
* top level config object too. E.g. `{ base: 'User' }` is the same as
|
|
||||||
* `{ options: { base: 'User' } }`.
|
|
||||||
*/
|
|
||||||
loopback.createModelFromConfig = function(config) {
|
|
||||||
var name = config.name;
|
|
||||||
var properties = config.properties;
|
|
||||||
var options = buildModelOptionsFromConfig(config);
|
|
||||||
|
|
||||||
assert(typeof name === 'string',
|
|
||||||
'The model-config property `name` must be a string');
|
|
||||||
|
|
||||||
return loopback.createModel(name, properties, options);
|
|
||||||
};
|
|
||||||
|
|
||||||
function buildModelOptionsFromConfig(config) {
|
|
||||||
var options = merge({}, config.options);
|
|
||||||
for (var key in config) {
|
|
||||||
if (['name', 'properties', 'options'].indexOf(key) !== -1) {
|
|
||||||
// Skip items which have special meaning
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options[key] !== undefined) {
|
|
||||||
// When both `config.key` and `config.options.key` are set,
|
|
||||||
// use the latter one
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
options[key] = config[key];
|
|
||||||
}
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Alter an existing Model class.
|
|
||||||
* @param {Model} ModelCtor The model constructor to alter.
|
|
||||||
* @options {Object} Additional configuration to apply
|
|
||||||
* @property {DataSource} dataSource Attach the model to a dataSource.
|
|
||||||
* @property {Object} relations Model relations to add/update.
|
|
||||||
*/
|
|
||||||
loopback.configureModel = function(ModelCtor, config) {
|
|
||||||
var settings = ModelCtor.settings;
|
|
||||||
|
|
||||||
if (config.relations) {
|
|
||||||
var relations = settings.relations = settings.relations || {};
|
|
||||||
Object.keys(config.relations).forEach(function(key) {
|
|
||||||
relations[key] = merge(relations[key] || {}, config.relations[key]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// It's important to attach the datasource after we have updated
|
|
||||||
// configuration, so that the datasource picks up updated relations
|
|
||||||
if (config.dataSource) {
|
|
||||||
assert(config.dataSource instanceof DataSource,
|
|
||||||
'Cannot configure ' + ModelCtor.modelName +
|
|
||||||
': config.dataSource must be an instance of loopback.DataSource');
|
|
||||||
ModelCtor.attachTo(config.dataSource);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a remote method to a model.
|
* Add a remote method to a model.
|
||||||
* @param {Function} fn
|
* @param {Function} fn
|
||||||
|
@ -324,132 +153,10 @@ loopback.template = function (file) {
|
||||||
return ejs.compile(str);
|
return ejs.compile(str);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* 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) {
|
|
||||||
name = name || 'default';
|
|
||||||
var memory = (
|
|
||||||
this._memoryDataSources
|
|
||||||
|| (this._memoryDataSources = {})
|
|
||||||
)[name];
|
|
||||||
|
|
||||||
if(!memory) {
|
|
||||||
memory = this._memoryDataSources[name] = loopback.createDataSource({
|
|
||||||
connector: loopback.Memory
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return memory;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
*/
|
|
||||||
loopback.getModel = function(modelName) {
|
|
||||||
return loopback.Model.modelBuilder.models[modelName];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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} The base model class
|
|
||||||
* @returns {Model} The subclass if found or the base class
|
|
||||||
*/
|
|
||||||
loopback.getModelByType = function(modelType) {
|
|
||||||
assert(typeof modelType === 'function', 'The model type must be a constructor');
|
|
||||||
var models = loopback.Model.modelBuilder.models;
|
|
||||||
for(var m in models) {
|
|
||||||
if(models[m].prototype instanceof modelType) {
|
|
||||||
return models[m];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return modelType;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the default `dataSource` for a given `type`.
|
|
||||||
* @param {String} type The datasource type
|
|
||||||
* @param {Object|DataSource} dataSource The data source settings or instance
|
|
||||||
* @returns {DataSource} The data source instance
|
|
||||||
*/
|
|
||||||
|
|
||||||
loopback.setDefaultDataSourceForType = function(type, dataSource) {
|
|
||||||
var defaultDataSources = this.defaultDataSources || (this.defaultDataSources = {});
|
|
||||||
|
|
||||||
if(!(dataSource instanceof DataSource)) {
|
|
||||||
dataSource = this.createDataSource(dataSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultDataSources[type] = dataSource;
|
|
||||||
return dataSource;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the default `dataSource` for a given `type`.
|
|
||||||
* @param {String} type The datasource type
|
|
||||||
* @returns {DataSource} The data source instance
|
|
||||||
*/
|
|
||||||
|
|
||||||
loopback.getDefaultDataSourceForType = function(type) {
|
|
||||||
return this.defaultDataSources && this.defaultDataSources[type];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attach any model that does not have a dataSource to
|
|
||||||
* the default dataSource for the type the Model requests
|
|
||||||
*/
|
|
||||||
|
|
||||||
loopback.autoAttach = function() {
|
|
||||||
var models = this.Model.modelBuilder.models;
|
|
||||||
assert.equal(typeof models, 'object', 'Cannot autoAttach without a models object');
|
|
||||||
|
|
||||||
Object.keys(models).forEach(function(modelName) {
|
|
||||||
var ModelCtor = models[modelName];
|
|
||||||
|
|
||||||
// Only auto attach if the model doesn't have an explicit data source
|
|
||||||
if(ModelCtor && (!(ModelCtor.dataSource instanceof DataSource))) {
|
|
||||||
loopback.autoAttachModel(ModelCtor);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
loopback.autoAttachModel = function(ModelCtor) {
|
|
||||||
if(ModelCtor.autoAttach) {
|
|
||||||
var ds = loopback.getDefaultDataSourceForType(ModelCtor.autoAttach);
|
|
||||||
|
|
||||||
assert(ds instanceof DataSource, 'cannot autoAttach model "'
|
|
||||||
+ ModelCtor.modelName
|
|
||||||
+ '". No dataSource found of type ' + ModelCtor.autoAttach);
|
|
||||||
|
|
||||||
ModelCtor.attachTo(ds);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Built in models / services
|
* Built in models / services
|
||||||
*/
|
*/
|
||||||
|
|
||||||
loopback.Model = require('./models/model');
|
|
||||||
loopback.PersistedModel = require('./models/persisted-model');
|
|
||||||
|
|
||||||
// temporary alias to simplify migration of code based on <=2.0.0-beta3
|
|
||||||
Object.defineProperty(loopback, 'DataModel', {
|
|
||||||
get: function() {
|
|
||||||
var stackLines = new Error().stack.split('\n');
|
|
||||||
console.warn('loopback.DataModel is deprecated, ' +
|
|
||||||
'use loopback.PersistedModel instead.');
|
|
||||||
// Log the location where loopback.DataModel was called
|
|
||||||
console.warn(stackLines[2]);
|
|
||||||
return loopback.PersistedModel;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
loopback.Email = require('./models/email');
|
loopback.Email = require('./models/email');
|
||||||
loopback.User = require('./models/user');
|
loopback.User = require('./models/user');
|
||||||
loopback.Application = require('./models/application');
|
loopback.Application = require('./models/application');
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*!
|
/*!
|
||||||
* Module Dependencies.
|
* Module Dependencies.
|
||||||
*/
|
*/
|
||||||
var loopback = require('../loopback');
|
var registry = require('../registry');
|
||||||
var compat = require('../compat');
|
var compat = require('../compat');
|
||||||
var juggler = require('loopback-datasource-juggler');
|
var juggler = require('loopback-datasource-juggler');
|
||||||
var ModelBuilder = juggler.ModelBuilder;
|
var ModelBuilder = juggler.ModelBuilder;
|
||||||
|
@ -218,7 +218,7 @@ Model._ACL = function getACL(ACL) {
|
||||||
return _aclModel;
|
return _aclModel;
|
||||||
}
|
}
|
||||||
var aclModel = require('./acl').ACL;
|
var aclModel = require('./acl').ACL;
|
||||||
_aclModel = loopback.getModelByType(aclModel);
|
_aclModel = registry.getModelByType(aclModel);
|
||||||
return _aclModel;
|
return _aclModel;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var Model = require('./model');
|
var Model = require('./model');
|
||||||
var loopback = require('../loopback');
|
var runtime = require('../runtime');
|
||||||
var RemoteObjects = require('strong-remoting');
|
var RemoteObjects = require('strong-remoting');
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
|
@ -893,7 +893,7 @@ PersistedModel.enableChangeTracking = function() {
|
||||||
|
|
||||||
Model.on('deletedAll', cleanup);
|
Model.on('deletedAll', cleanup);
|
||||||
|
|
||||||
if(loopback.isServer) {
|
if(runtime.isServer) {
|
||||||
// initial cleanup
|
// initial cleanup
|
||||||
cleanup();
|
cleanup();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,342 @@
|
||||||
|
/*
|
||||||
|
* This file exports methods and objects for manipulating
|
||||||
|
* Models and DataSources.
|
||||||
|
*
|
||||||
|
* It is an internal file that should not be used outside of loopback.
|
||||||
|
* All exported entities can be accessed via the `loopback` object.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
var assert = require('assert');
|
||||||
|
var extend = require('util')._extend;
|
||||||
|
var DataSource = require('loopback-datasource-juggler').DataSource;
|
||||||
|
|
||||||
|
var registry = module.exports;
|
||||||
|
|
||||||
|
registry.defaultDataSources = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
registry.createModel = function (name, properties, options) {
|
||||||
|
if (arguments.length === 1 && typeof name === 'object') {
|
||||||
|
var config = name;
|
||||||
|
name = config.name;
|
||||||
|
properties = config.properties;
|
||||||
|
options = buildModelOptionsFromConfig(config);
|
||||||
|
|
||||||
|
assert(typeof name === 'string',
|
||||||
|
'The model-config property `name` must be a string');
|
||||||
|
}
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
var BaseModel = options.base || options.super;
|
||||||
|
|
||||||
|
if(typeof BaseModel === 'string') {
|
||||||
|
var baseName = BaseModel;
|
||||||
|
BaseModel = this.getModel(BaseModel);
|
||||||
|
|
||||||
|
if (BaseModel === undefined) {
|
||||||
|
if (baseName === 'DataModel') {
|
||||||
|
console.warn('Model `%s` is extending deprecated `DataModel. ' +
|
||||||
|
'Use `PeristedModel` instead.', name);
|
||||||
|
BaseModel = this.PersistedModel;
|
||||||
|
} else {
|
||||||
|
console.warn('Model `%s` is extending an unknown model `%s`. ' +
|
||||||
|
'Using `PersistedModel` as the base.', name, baseName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseModel = BaseModel || this.PersistedModel;
|
||||||
|
|
||||||
|
var model = BaseModel.extend(name, properties, options);
|
||||||
|
|
||||||
|
// try to attach
|
||||||
|
try {
|
||||||
|
this.autoAttachModel(model);
|
||||||
|
} catch(e) {}
|
||||||
|
|
||||||
|
return model;
|
||||||
|
};
|
||||||
|
|
||||||
|
function buildModelOptionsFromConfig(config) {
|
||||||
|
var options = extend({}, config.options);
|
||||||
|
for (var key in config) {
|
||||||
|
if (['name', 'properties', 'options'].indexOf(key) !== -1) {
|
||||||
|
// Skip items which have special meaning
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options[key] !== undefined) {
|
||||||
|
// When both `config.key` and `config.options.key` are set,
|
||||||
|
// use the latter one
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
options[key] = config[key];
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
*/
|
||||||
|
|
||||||
|
registry.configureModel = function(ModelCtor, config) {
|
||||||
|
var settings = ModelCtor.settings;
|
||||||
|
|
||||||
|
if (config.relations) {
|
||||||
|
var relations = settings.relations = settings.relations || {};
|
||||||
|
Object.keys(config.relations).forEach(function(key) {
|
||||||
|
relations[key] = extend(relations[key] || {}, config.relations[key]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's important to attach the datasource after we have updated
|
||||||
|
// configuration, so that the datasource picks up updated relations
|
||||||
|
if (config.dataSource) {
|
||||||
|
assert(config.dataSource instanceof DataSource,
|
||||||
|
'Cannot configure ' + ModelCtor.modelName +
|
||||||
|
': config.dataSource must be an instance of DataSource');
|
||||||
|
ModelCtor.attachTo(config.dataSource);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.getModel(modelName)
|
||||||
|
*/
|
||||||
|
registry.getModel = function(modelName) {
|
||||||
|
return this.Model.modelBuilder.models[modelName];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
*/
|
||||||
|
registry.getModelByType = function(modelType) {
|
||||||
|
assert(typeof modelType === 'function',
|
||||||
|
'The model type must be a constructor');
|
||||||
|
var models = this.Model.modelBuilder.models;
|
||||||
|
for(var m in models) {
|
||||||
|
if(models[m].prototype instanceof modelType) {
|
||||||
|
return models[m];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return modelType;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* @header loopback.createDataSource(name, options)
|
||||||
|
*/
|
||||||
|
|
||||||
|
registry.createDataSource = function (name, options) {
|
||||||
|
var loopback = this;
|
||||||
|
var ds = new DataSource(name, options, loopback.Model.modelBuilder);
|
||||||
|
ds.createModel = function (name, properties, settings) {
|
||||||
|
var ModelCtor = loopback.createModel(name, properties, settings);
|
||||||
|
ModelCtor.attachTo(ds);
|
||||||
|
return ModelCtor;
|
||||||
|
};
|
||||||
|
|
||||||
|
if(ds.settings && ds.settings.defaultForType) {
|
||||||
|
this.setDefaultDataSourceForType(ds.settings.defaultForType, ds);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ds;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
* @header loopback.memory()
|
||||||
|
*/
|
||||||
|
|
||||||
|
registry.memory = function (name) {
|
||||||
|
name = name || 'default';
|
||||||
|
var memory = (
|
||||||
|
this._memoryDataSources || (this._memoryDataSources = {})
|
||||||
|
)[name];
|
||||||
|
|
||||||
|
if(!memory) {
|
||||||
|
memory = this._memoryDataSources[name] = this.createDataSource({
|
||||||
|
connector: loopback.Memory
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return memory;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the default `dataSource` for a given `type`.
|
||||||
|
* @param {String} type The datasource type
|
||||||
|
* @param {Object|DataSource} dataSource The data source settings or instance
|
||||||
|
* @returns {DataSource} The data source instance
|
||||||
|
*
|
||||||
|
* @header loopback.setDefaultDataSourceForType(type, dataSource)
|
||||||
|
*/
|
||||||
|
|
||||||
|
registry.setDefaultDataSourceForType = function(type, dataSource) {
|
||||||
|
var defaultDataSources = this.defaultDataSources;
|
||||||
|
|
||||||
|
if(!(dataSource instanceof DataSource)) {
|
||||||
|
dataSource = this.createDataSource(dataSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultDataSources[type] = dataSource;
|
||||||
|
return dataSource;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the default `dataSource` for a given `type`.
|
||||||
|
* @param {String} type The datasource type
|
||||||
|
* @returns {DataSource} The data source instance
|
||||||
|
* @header loopback.getDefaultDataSourceForType()
|
||||||
|
*/
|
||||||
|
|
||||||
|
registry.getDefaultDataSourceForType = function(type) {
|
||||||
|
return this.defaultDataSources && this.defaultDataSources[type];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attach any model that does not have a dataSource to
|
||||||
|
* the default dataSource for the type the Model requests
|
||||||
|
* @header loopback.autoAttach()
|
||||||
|
*/
|
||||||
|
|
||||||
|
registry.autoAttach = function() {
|
||||||
|
var models = this.Model.modelBuilder.models;
|
||||||
|
assert.equal(typeof models, 'object', 'Cannot autoAttach without a models object');
|
||||||
|
|
||||||
|
Object.keys(models).forEach(function(modelName) {
|
||||||
|
var ModelCtor = models[modelName];
|
||||||
|
|
||||||
|
// Only auto attach if the model doesn't have an explicit data source
|
||||||
|
if(ModelCtor && (!(ModelCtor.dataSource instanceof DataSource))) {
|
||||||
|
this.autoAttachModel(ModelCtor);
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
};
|
||||||
|
|
||||||
|
registry.autoAttachModel = function(ModelCtor) {
|
||||||
|
if(ModelCtor.autoAttach) {
|
||||||
|
var ds = this.getDefaultDataSourceForType(ModelCtor.autoAttach);
|
||||||
|
|
||||||
|
assert(ds instanceof DataSource, 'cannot autoAttach model "'
|
||||||
|
+ ModelCtor.modelName
|
||||||
|
+ '". No dataSource found of type ' + ModelCtor.autoAttach);
|
||||||
|
|
||||||
|
ModelCtor.attachTo(ds);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
registry.DataSource = DataSource;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Core models
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
registry.Model = require('./models/model');
|
||||||
|
registry.PersistedModel = require('./models/persisted-model');
|
||||||
|
|
||||||
|
// temporary alias to simplify migration of code based on <=2.0.0-beta3
|
||||||
|
Object.defineProperty(registry, 'DataModel', {
|
||||||
|
get: function() {
|
||||||
|
var stackLines = new Error().stack.split('\n');
|
||||||
|
console.warn('loopback.DataModel is deprecated, ' +
|
||||||
|
'use loopback.PersistedModel instead.');
|
||||||
|
// Log the location where loopback.DataModel was called
|
||||||
|
console.warn(stackLines[2]);
|
||||||
|
return this.PersistedModel;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
* This is an internal file that should not be used outside of loopback.
|
||||||
|
* All exported entities can be accessed via the `loopback` object.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
var runtime = exports;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if running in a browser environment; false otherwise.
|
||||||
|
* @header loopback.isBrowser
|
||||||
|
*/
|
||||||
|
|
||||||
|
runtime.isBrowser = typeof window !== 'undefined';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if running in a server environment; false otherwise.
|
||||||
|
* @header loopback.isServer
|
||||||
|
*/
|
||||||
|
|
||||||
|
runtime.isServer = !runtime.isBrowser;
|
||||||
|
|
|
@ -6,6 +6,7 @@ var app = require(path.join(ACCESS_CONTROL_APP, 'app.js'));
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
var USER = {email: 'test@test.test', password: 'test'};
|
var USER = {email: 'test@test.test', password: 'test'};
|
||||||
var CURRENT_USER = {email: 'current@test.test', password: 'test'};
|
var CURRENT_USER = {email: 'current@test.test', password: 'test'};
|
||||||
|
var debug = require('debug')('loopback:test:access-control.integration');
|
||||||
|
|
||||||
describe('access control - integration', function () {
|
describe('access control - integration', function () {
|
||||||
|
|
||||||
|
@ -96,6 +97,10 @@ describe('access control - integration', function () {
|
||||||
lt.describe.whenCalledRemotely('GET', '/api/users/:id', function() {
|
lt.describe.whenCalledRemotely('GET', '/api/users/:id', function() {
|
||||||
lt.it.shouldBeAllowed();
|
lt.it.shouldBeAllowed();
|
||||||
it('should not include a password', function() {
|
it('should not include a password', function() {
|
||||||
|
debug('GET /api/users/:id response: %s\nheaders: %j\nbody string: %s',
|
||||||
|
this.res.statusCode,
|
||||||
|
this.res.headers,
|
||||||
|
this.res.text);
|
||||||
var user = this.res.body;
|
var user = this.res.body;
|
||||||
assert.equal(user.password, undefined);
|
assert.equal(user.password, undefined);
|
||||||
});
|
});
|
||||||
|
|
|
@ -127,9 +127,9 @@ describe('loopback', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('loopback.createModelFromConfig(config)', function() {
|
describe('loopback.createModel(config)', function() {
|
||||||
it('creates the model', function() {
|
it('creates the model', function() {
|
||||||
var model = loopback.createModelFromConfig({
|
var model = loopback.createModel({
|
||||||
name: uniqueModelName
|
name: uniqueModelName
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -137,7 +137,7 @@ describe('loopback', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('interprets extra first-level keys as options', function() {
|
it('interprets extra first-level keys as options', function() {
|
||||||
var model = loopback.createModelFromConfig({
|
var model = loopback.createModel({
|
||||||
name: uniqueModelName,
|
name: uniqueModelName,
|
||||||
base: 'User'
|
base: 'User'
|
||||||
});
|
});
|
||||||
|
@ -146,7 +146,7 @@ describe('loopback', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('prefers config.options.key over config.key', function() {
|
it('prefers config.options.key over config.key', function() {
|
||||||
var model = loopback.createModelFromConfig({
|
var model = loopback.createModel({
|
||||||
name: uniqueModelName,
|
name: uniqueModelName,
|
||||||
base: 'User',
|
base: 'User',
|
||||||
options: {
|
options: {
|
||||||
|
|
|
@ -5,6 +5,7 @@ var SIMPLE_APP = path.join(__dirname, 'fixtures', 'simple-integration-app');
|
||||||
var app = require(path.join(SIMPLE_APP, 'app.js'));
|
var app = require(path.join(SIMPLE_APP, 'app.js'));
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
var expect = require('chai').expect;
|
var expect = require('chai').expect;
|
||||||
|
var debug = require('debug')('loopback:test:relations.integration');
|
||||||
|
|
||||||
describe('relations - integration', function () {
|
describe('relations - integration', function () {
|
||||||
|
|
||||||
|
@ -28,13 +29,19 @@ describe('relations - integration', function () {
|
||||||
this.url = '/api/stores/' + this.store.id + '/widgets';
|
this.url = '/api/stores/' + this.store.id + '/widgets';
|
||||||
});
|
});
|
||||||
lt.describe.whenCalledRemotely('GET', '/api/stores/:id/widgets', function() {
|
lt.describe.whenCalledRemotely('GET', '/api/stores/:id/widgets', function() {
|
||||||
|
|
||||||
it('should succeed with statusCode 200', function() {
|
it('should succeed with statusCode 200', function() {
|
||||||
assert.equal(this.res.statusCode, 200);
|
assert.equal(this.res.statusCode, 200);
|
||||||
});
|
});
|
||||||
describe('widgets (response.body)', function() {
|
describe('widgets (response.body)', function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
|
debug('GET /api/stores/:id/widgets response: %s' +
|
||||||
|
'\nheaders: %j\nbody string: %s',
|
||||||
|
this.res.statusCode,
|
||||||
|
this.res.headers,
|
||||||
|
this.res.text);
|
||||||
this.widgets = this.res.body;
|
this.widgets = this.res.body;
|
||||||
this.widget = this.res.body[0];
|
this.widget = this.res.body && this.res.body[0];
|
||||||
});
|
});
|
||||||
it('should be an array', function() {
|
it('should be an array', function() {
|
||||||
assert(Array.isArray(this.widgets));
|
assert(Array.isArray(this.widgets));
|
||||||
|
|
Loading…
Reference in New Issue